(服务注册名称的来历)

一万年来谁著史,三千里外欲封侯。这篇文章主要讲述:服务注册名称的来历相关的知识,希望能为你提供帮助。
欢迎访问我的GitHub
关于服务注册名称

  • 服务注册名称,是指Eureka client注册到Eureka server时,用于标记自己身份的标志,举例说明,以下是个简单的Eureka client配置:
    server: port: 8082 spring: application: name: springcloud-deep-provider eureka: client: serviceUrl: defaultZone: http://localhost:8081/eureka/

  • 这样配置的应用,启动后如果在Eureka server注册成功,那么Eureka server的home页面信息如下,红框中就是注册名称:
    (服务注册名称的来历)

    文章图片

  • 本文目标是通过分析Eureka client源码来找出这个名称是如何创建的;
关于源码版本
  • 本次分析的Spring Cloud版本为Edgware.RELEASE,对应的eureka-client版本为1.7.0;
从启动说起
  1. 在spring-cloud-commons库的META-INF目录下有spring.factories文件,这是spring扩展规范的实现,这里面配置的类会被实例化,其中就包含了HostInfoEnvironmentPostProcessor这个类,如下图红框所示:
    (服务注册名称的来历)

    文章图片
  2. HostInfoEnvironmentPostProcessor实现了EnvironmentPostProcessor接口,来看看官方文档对EnvironmentPostProcessor的描述:
    (服务注册名称的来历)

    文章图片

    • 上图红框中说明开发者可以自定义环境变量;
    • 上图绿框中说明EnvironmentPostProcessor的实现类必须在spring.factories文件中定义;
    • 因此HostInfoEnvironmentPostProcessor类的作用已经清楚了:==自定义环境变量==;
  3. HostInfoEnvironmentPostProcessor源码如下:
    public class HostInfoEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered // Before ConfigFileApplicationListener private int order = ConfigFileApplicationListener.DEFAULT_ORDER - 1; @Override public int getOrder() return this.order; @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) InetUtils.HostInfo hostInfo = getFirstNonLoopbackHostInfo(environment); LinkedHashMap< String, Object> map = new LinkedHashMap< > (); map.put("spring.cloud.client.hostname", hostInfo.getHostname()); map.put("spring.cloud.client.ipAddress", hostInfo.getIpAddress()); MapPropertySource propertySource = new MapPropertySource( "springCloudClientHostInfo", map); environment.getPropertySources().addLast(propertySource); private HostInfo getFirstNonLoopbackHostInfo(ConfigurableEnvironment environment) InetUtilsProperties target = new InetUtilsProperties(); RelaxedDataBinder binder = new RelaxedDataBinder(target, InetUtilsProperties.PREFIX); binder.bind(new PropertySourcesPropertyValues(environment.getPropertySources())); try (InetUtils utils = new InetUtils(target)) return utils.findFirstNonLoopbackHostInfo();

    • 上述代码有两处需要注意:
      第一,设置了两个环境变量:==spring.cloud.client.hostname==和==spring.cloud.client.ipAddress==;
      第二,getFirstNonLoopbackHostInfo方法返回的对象中,127.0.0.1这样的IP地址是会被过滤掉的,过滤逻辑很简单,源码在Inet4Address类的isLoopbackAddress方法:
      public boolean isLoopbackAddress() /* 127.x.x.x */ byte[] byteAddr = getAddress(); return byteAddr[0] == 127;

      小结:HostInfoEnvironmentPostProcessor的作用是把本机的hostname和IP地址设置到环境变量中;
在配置类中保存服务名称
  1. 接下来看看配置类EurekaClientAutoConfiguration,这里面主要是和Eureka相关的配置信息的数据和逻辑;
  2. 请看方法eurekaInstanceConfigBean,该方法向Spring容器环境提供EurekaInstanceConfigBean实例,注意==instance.setInstanceId(getDefaultInstanceId(propertyResolver))==这一行:
    @Bean @ConditionalOnMissingBean(value = https://www.songbingjia.com/android/EurekaInstanceConfig.class, search = SearchStrategy.CURRENT) public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils, ManagementMetadataProvider managementMetadataProvider) throws MalformedURLException PropertyResolver eurekaPropertyResolver = new RelaxedPropertyResolver(this.env,"eureka.instance."); String hostname = eurekaPropertyResolver.getProperty("hostname"); boolean preferIpAddress = Boolean.parseBoolean(eurekaPropertyResolver.getProperty("preferIpAddress")); boolean isSecurePortEnabled = Boolean.parseBoolean(eurekaPropertyResolver.getProperty("securePortEnabled")); String serverContextPath = propertyResolver.getProperty("server.contextPath", "/"); int serverPort = Integer.valueOf(propertyResolver.getProperty("server.port", propertyResolver.getProperty("port", "8080"))); Integer managementPort = propertyResolver.getProperty("management.port", Integer.class); // nullable. should be wrapped into optional String managementContextPath = propertyResolver.getProperty("management.contextPath"); // nullable. should be wrapped into optional Integer jmxPort = propertyResolver.getProperty("com.sun.management.jmxremote.port", Integer.class); //nullable EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils); instance.setNonSecurePort(serverPort); //服务自身的名称在此设置,保存到instance后,其他地方就可以使用了 instance.setInstanceId(getDefaultInstanceId(propertyResolver));

  3. 顺藤摸瓜,展开方法getDefaultInstanceId:
    public static String getDefaultInstanceId(PropertyResolver resolver) RelaxedPropertyResolver relaxed = new RelaxedPropertyResolver(resolver); String vcapInstanceId = relaxed.getProperty("vcap.application.instance_id"); if (StringUtils.hasText(vcapInstanceId)) return vcapInstanceId; String hostname = relaxed.getProperty("spring.cloud.client.hostname"); String appName = relaxed.getProperty("spring.application.name"); String namePart = combineParts(hostname, SEPARATOR, appName); String indexPart = relaxed.getProperty("spring.application.instance_id", relaxed.getProperty("server.port")); return combineParts(namePart, SEPARATOR, indexPart);

    • 如上述代码所示,真相大白,服务注册名称一共有三部分:hostname、应用名称、自定义实例ID,如果自定义实例ID没有配置就用监听端口代替;
  • 此时再来回顾之前在Eureka server的home页面上看到的服务注册名:localhost:springcloud-deep-provider:8082,果然与源码一致;
    1. 源码读到此处,禁不住手痒,按照上面的逻辑,在应用的aplication.yml中增加配置项==spring.application.instance_id==,看看能否生效,改过的aplication.yml内容如下图所示,红框中是新增的自定义实例ID配置:
      (服务注册名称的来历)

      文章图片
    2. 重启应用,重新注册到Eureka server,此时再看home页面如下图红框,服务注册名称果然已经更新:
      (服务注册名称的来历)

      文章图片
使用配置类中的服务名称
  • 现在我们知道了EurekaInstanceConfigBean实例的instanceId字段被设置为" hostname:应用名称:自定义实例ID" ,接下来看该字段如何被提交到Eureka server;
  1. 在EurekaClientAutoConfiguration类中有个eurekaApplicationInfoManager方法,为spring容器提供了ApplicationInfoManager实例:
    @Bean @ConditionalOnMissingBean(value = https://www.songbingjia.com/android/ApplicationInfoManager.class, search = SearchStrategy.CURRENT) public ApplicationInfoManager eurekaApplicationInfoManager( EurekaInstanceConfig config) //config就是前面看到的EurekaInstanceConfigBean实例, //EurekaInstanceConfig是个接口,EurekaInstanceConfigBean是该接口的实现, //instanceInfo实例中已经保存了EurekaInstanceConfigBean的信息,也包括instanceId字段 InstanceInfo instanceInfo = new InstanceInfoFactory().create(config); //生成ApplicationInfoManager实例, //config和instanceInfo都被设置为ApplicationInfoManager实例的成员变量 return new ApplicationInfoManager(config, instanceInfo);

    如上所示,spring容器中有了ApplicationInfoManager实例,就可以通过该实例获得服务注册名称;
  2. Eureka client向Eureka server发起服务注册的操作是在DiscoveryClient类中进行的,该类的构造方法如下:
    (服务注册名称的来历)

    文章图片
如上图所示,红框中ApplicationInfoManager实例被注入,蓝框中表明DiscoveryClient的成员变量instanceInfo获得了InstanceInfo实例;
  1. 具体的注册逻辑在DiscoveryClient的register方法中,可见成员变量instanceInfo被当作入参传入了注册逻辑的API:
    /** * Register with the eureka service by making the appropriate REST call. */ boolean register() throws Throwable logger.info(PREFIX + appPathIdentifier + ": registering service..."); EurekaHttpResponse< Void> httpResponse; try //以成员变量instanceInfo作为入参进行注册 httpResponse = eurekaTransport.registrationClient.register(instanceInfo); catch (Exception e) logger.warn(" - registration failed ", PREFIX + appPathIdentifier, e.getMessage(), e); throw e; if (logger.isInfoEnabled()) logger.info(" - registration status: ", PREFIX + appPathIdentifier, httpResponse.getStatusCode()); return httpResponse.getStatusCode() == 204;

  2. 上述代码中的==eurekaTransport.registrationClient.register(instanceInfo)==方法,经过层层调用,最终调用了AbstractJerseyEurekaHttpClient类的register方法,如下图所示:
    (服务注册名称的来历)

    文章图片
  • 上图的红框表明,POST请求时InstanceInfo实例被作为请求参数提交到了Eureka server;
Wireshark抓包验证
  • 至此,代码分析已经结束了,最后我们用Wireshark抓包来验证之前的分析结果,在Eureka client所在电脑上用Wireshark2.6.3来分析注册请求:
    (服务注册名称的来历)

    文章图片

  • 【(服务注册名称的来历)】如上图所示,红框中就是注册请求,绿框中是请求包头的全部内容,也就是前面看到的InstanceInfo实例的内容,蓝框中的内容,就是服务注册名称的键值对,值就是Eureka server收到的注册名称;
  • 最后来小结一下,服务注册名称从诞生到提交至Eureka server的过程:
    1. HostInfoEnvironmentPostProcessor将本机的hostname和IP地址设置到应用环境变量中;
    2. 配置类EurekaClientAutoConfiguration中,创建一个EurekaInstanceConfigBean类型的bean,其instanceId字段就是即将上报到Eureka server的自身名称,instanceId字段的内容由hostname、应用名称、自定义实例ID拼接而成,其中自定义实例ID来自配置项" spring.application.instance_id" ,如果不存在就用服务监听端口代替;
    3. ApplicationInfoManager类型的bean在创建时被注入EurekaInstanceConfigBean实例,用于创建ApplicationInfoManager的成员变量instanceInfo;
    4. DiscoveryClient的构造方法中注入了ApplicationInfoManager,于是DiscoveryClient的成员变量instanceInfo就被赋值为ApplicationInfoManager的成员变量instanceInfo;
    5. DiscoveryClient的register方法负责注册到Eureka server的逻辑,用到的参数就是成员变量instanceInfo;
    6. 发起注册网络请求的操作最终由AbstractJerseyEurekaHttpClient类的register方法完成,POST的内容就是instanceInfo实例;
欢迎关注51CTO博客:程序员欣宸

    推荐阅读