Spring Boot关于Eureka配置文件解析

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模式启动

image.png

确实执行了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配置和代码的调用调用链。

image.png

通过断点看到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交互的工具类。

继续跟代码


image.png

因为没有在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;
    }

image.png

通过这个我们可以看到,在没有制定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
image.png

方案二: 显示指定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

结果如下


image.png

service-url和sercviceUrl为何都可以准确给和EurekaClientConfigBean?

老办法 在setServiceUrl()方法上打上断点,Debug查看流程
通过栈帧看到

image.png

在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;
}                 

打上断点运行。。。 太多了 以后看这个细节了

结论

  1. EurekaClientConfigBean定义的属性,在yml配置文件可以用驼峰法,也可以用中横线的方式命名,代码会处理"等效成驼峰法"
  2. 若是EurekaClientConfigBean定义的属性是个Map等,map的key的命名必须和代码的使用的变量保持一一致。代码不在转化处理。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350