Eureka是Spring Cloud的一个原生的注册中心。Spring Cloud使用Eureka进行服务注册十分简单,只要在application.yml添加配置即可
eureka:
client:
#(向注册中心注册服务
register-with-eureka: true
service-url:
default-zone: http://eurekaServer:8761/eureka/eureka
instance:
appname: main-service0
其中http://eurekaServer:8761/eureka/eureka 是注册地址,其中路径中第一个eureka是Eureka服务的上下文路径
启动服务之后,和预期不一致报错如下
2020-07-26 15:36:54.624 WARN 15681 --- [freshExecutor-0] c.n.d.s.t.d.RetryableEurekaHttpClient : Request execution failure with status code 404; retrying on another server if available
2020-07-26 15:36:54.624 ERROR 15681 --- [freshExecutor-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_MAIN-SERVICE0/localhost:main-service:9090 - was unable to refresh its cache! status = Cannot execute request on any known server
com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server
at com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient.execute(RetryableEurekaHttpClient.java:112) ~[eureka-client-1.9.13.jar:1.9.13]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.getApplications(EurekaHttpClientDecorator.java:134) ~[eureka-client-1.9.13.jar:1.9.13]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$6.execute(EurekaHttpClientDecorator.java:137) ~[eureka-client-1.9.13.jar:1.9.13]
at com.netflix.discovery.shared.transport.decorator.SessionedEurekaHttpClient.execute(SessionedEurekaHttpClient.java:77) ~[eureka-client-1.9.13.jar:1.9.13]
at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.getApplications(EurekaHttpClientDecorator.java:134) ~[eureka-client-1.9.13.jar:1.9.13]
at com.netflix.discovery.DiscoveryClient.getAndStoreFullRegistry(DiscoveryClient.java:1069) [eureka-client-1.9.13.jar:1.9.13]
at com.netflix.discovery.DiscoveryClient.fetchRegistry(DiscoveryClient.java:983) [eureka-client-1.9.13.jar:1.9.13]
at com.netflix.discovery.DiscoveryClient.refreshRegistry(DiscoveryClient.java:1497) [eureka-client-1.9.13.jar:1.9.13]
at com.netflix.discovery.DiscoveryClient$CacheRefreshThread.run(DiscoveryClient.java:1464) [eureka-client-1.9.13.jar:1.9.13]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_252]
at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266) [na:1.8.0_252]
at java.util.concurrent.FutureTask.run(FutureTask.java) [na:1.8.0_252]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_252]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_252]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_252]
2020-07-26 15:36:54.684 ERROR 15681 --- [tbeatExecutor-0] c.n.d.s.t.d.RedirectingEurekaHttpClient : Request execution error. endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8761/eureka/}
看到日志endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8761/eureka/}这个地址很眼熟,是Eureka默认的地址,也就是说 default-zone: http://eurekaServer:8761/eureka/eureka 没有生效。
既然问题源于配置文件没有生效? 那么Spring Boot是如何加载配置的呢eureka.clinet的配置文件呢?
很不幸,spring boot对代码封装的太厉害,没啥头绪找到eureka.clinet的配置类,一种是搞明白的Spring Boot的启动流程,这个太复杂了,代码量也是极大的,放弃了。
那么不管黑猫白猫,目标是解决问题,我们知道自动加载的一般Spring Boot预定义好了的,而且基于SpringBoot基于约定的原则,Eureka自动配置类的会包含Eureka Auto Config等关键字。
通过类名查找发现确实包含EurekaClientAutoConfiguration.java,这大大缩小的代码范围量。看下这个类的方法,EurekaClientConfigBean包含关键字EurekaClient, Config 是个极大和配置相关代码
@Bean
@ConditionalOnMissingBean(value = EurekaClientConfig.class,
search = SearchStrategy.CURRENT)
public EurekaClientConfigBean eurekaClientConfigBean(ConfigurableEnvironment env) {
EurekaClientConfigBean client = new EurekaClientConfigBean();
if ("bootstrap".equals(this.env.getProperty("spring.config.name"))) {
// We don't register during bootstrap by default, but there will be another
// chance later.
client.setRegisterWithEureka(false);
}
return client;
}
将方法加上断点,Debug模式启动
确实执行了EurekaClientConfigBean方法,通过看117行的EurekaClientConfigBean类,类上说明是配置Bean
/**
* Eureka client configuration bean.
*
* @author Dave Syer
* @author Gregor Zurowski
*/
@ConfigurationProperties(EurekaClientConfigBean.PREFIX)
public class EurekaClientConfigBean implements EurekaClientConfig, Ordered {
/**
* Default prefix for Eureka client config properties.
*/
public static final String PREFIX = "eureka.client";
/**
* Default Eureka URL.
*/
public static final String DEFAULT_URL = "http://localhost:8761" + DEFAULT_PREFIX
+ "/";
public static final String DEFAULT_ZONE = "defaultZone";
@Autowired(required = false)
PropertyResolver propertyResolver;
/**
* Gets the region (used in AWS datacenters) where this instance resides.
*/
private String region = "us-east-1";
/**
* Gets the list of availability zones (used in AWS data centers) for the region in
* which this instance resides.
*
* The changes are effective at runtime at the next registry fetch cycle as specified
* by registryFetchIntervalSeconds.
*/
private Map<String, String> availabilityZones = new HashMap<>();
/**
* Map of availability zone to list of fully qualified URLs to communicate with eureka
* server. Each value can be a single URL or a comma separated list of alternative
* locations.
*
* Typically the eureka server URLs carry protocol,host,port,context and version
* information if any. Example:
* https://ec2-256-156-243-129.compute-1.amazonaws.com:7001/eureka/
*
* The changes are effective at runtime at the next service url refresh cycle as
* specified by eurekaServiceUrlPollIntervalSeconds.
*/
private Map<String, String> serviceUrl = new HashMap<>();
{
this.serviceUrl.put(DEFAULT_ZONE, DEFAULT_URL);
}
}
...
基本上我们确定EurekaClientConfigBean.java就是和yml中Eureka配置对应的Java类了。结合Debug,为此我们找到了yml配置和代码的调用调用链。
通过断点看到this.serviceUrl包含两个元素
default-zone与defaultZone两个变量。
II serviceUrl是在哪里用到?
protected static class RefreshableEurekaClientConfiguration {
public EurekaClient eurekaClient(ApplicationInfoManager manager,
EurekaClientConfig config, EurekaInstanceConfig instance,
@Autowired(required = false) HealthCheckHandler healthCheckHandler) {
//新建CloudEurekaClient
CloudEurekaClient cloudEurekaClient = new CloudEurekaClient(appManager,
config, this.optionalArgs, this.context);
cloudEurekaClient.registerHealthCheck(healthCheckHandler);
return cloudEurekaClient;
}
}
通过跟踪代码发现
/**
The class that is instrumental for interactions with Eureka Server.
Eureka Client is responsible for a) Registering the instance with Eureka Server b) Renewalof the lease with Eureka Server c) Cancellation of the lease from Eureka Server during shutdown
d) Querying the list of services/instances registered with Eureka Server
Eureka Client needs a configured list of Eureka Server java.net.URLs to talk to.These java.net.URLs are typically amazon elastic eips which do not change. All of the functions defined above fail-over to other java.net.URLs specified in the list in the case of failure.
*/
@Singleton
public class DiscoveryClient implements EurekaClient {
@Inject
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
try {
// default size of 2 - 1 each for heartbeat and cacheRefresh
scheduler = Executors.newScheduledThreadPool(2,
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-%d")
.setDaemon(true)
.build());
heartbeatExecutor = new ThreadPoolExecutor(
1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
.setDaemon(true)
.build()
); // use direct handoff
cacheRefreshExecutor = new ThreadPoolExecutor(
1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
.setDaemon(true)
.build()
); // use direct handoff
eurekaTransport = new EurekaTransport();
//这是查询了Eureka的节点列表
scheduleServerEndpointTask(eurekaTransport, args);
// finally, init the schedule tasks (e.g. cluster resolvers, heartbeat, instanceInfo replicator, fetch
initScheduledTasks();
}
}
DiscoveryClient 是和EurekaServer交互的工具类。
继续跟代码
因为没有在yml配置region属性,所以代码使用了默认"us-east-1",并且availabilityZones为空。所以myZone使用的是默认是值,详情见getZone()方法。
private List<AwsEndpoint> getClusterEndpointsFromConfig() {
String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion());
String myZone = InstanceInfo.getZone(availZones, myInstanceInfo);
//根据myZone求EurekaServer url列表
Map<String, List<String>> serviceUrls = EndpointUtils
.getServiceUrlsMapFromConfig(clientConfig, myZone, clientConfig.shouldPreferSameZoneEureka());
List<AwsEndpoint> endpoints = new ArrayList<>();
for (String zone : serviceUrls.keySet()) {
for (String url : serviceUrls.get(zone)) {
try {
endpoints.add(new AwsEndpoint(url, getRegion(), zone));
} catch (Exception ignore) {
logger.warn("Invalid eureka server URI: {}; removing from the server pool", url);
}
}
}
return endpoints;
}
通过这个我们可以看到,在没有制定region的情况下,会使用defaultZone从ServiceUrl中查询Eureka Server的URL列表,因为配置因为没有配置default-zone,所以使用代码默认初始化的 http://localhost:8761/eureka/
所以出现了上面的错误。
方案一: 指定defaultZone(而不是default-zone)的地址
eureka:
client:
register-with-eureka: true
service-url:
defaultZone: http://eurekaServer:8761/eureka/eureka
instance:
appname: main-service0
方案二: 显示指定zone( availability-zones)也有对应配置
eureka:
client:
register-with-eureka: true
region: beijing
availability-zones:
beijing: beijing-zone
service-url:
beijing-zone: http://eurekaServer:8761/eureka/eureka
instance:
appname: main-service0
结果如下
service-url和sercviceUrl为何都可以准确给和EurekaClientConfigBean?
老办法 在setServiceUrl()方法上打上断点,Debug查看流程
通过栈帧看到
在EurekaClientConfigBean调用sercviceUrl之前,先经过 BeanProperty.setValue()处理了,
并且beanProperty对象的setter方法正好sercviceUrl,而BeanProperty的属性name是service-url这和我们的配置文件是一样的。
到此,自然需要去看BeanProperty对象是如何构造的。
BeanProperty(String name, ResolvableType declaringClassType) {
this.name = DataObjectPropertyName.toDashedForm(name);
this.declaringClassType = declaringClassType;
}
打上断点运行。。。 太多了 以后看这个细节了
结论
- EurekaClientConfigBean定义的属性,在yml配置文件可以用驼峰法,也可以用中横线的方式命名,代码会处理"等效成驼峰法"
- 若是EurekaClientConfigBean定义的属性是个Map等,map的key的命名必须和代码的使用的变量保持一一致。代码不在转化处理。