Spring Cloud源码分析之Eureka篇第八章:服务注册名称的来历

简介: 从源码分析Eureka client注册到Eureka server时,用于标记自己身份的标志的具体生成逻辑

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码): https://github.com/zq2599/blog_demos

关于服务注册名称

  • 服务注册名称,是指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页面信息如下,红框中就是注册名称:

image.png

  • 本文目标是通过分析Eureka client源码来找出这个名称是如何创建的;

关于源码版本

  • 本次分析的Spring Cloud版本为Edgware.RELEASE,对应的eureka-client版本为1.7.0;

从启动说起

  • 在spring-cloud-commons库的META-INF目录下有spring.factories文件,这是spring扩展规范的实现,这里面配置的类会被实例化,其中就包含了HostInfoEnvironmentPostProcessor这个类,如下图红框所示:

image.png

  • HostInfoEnvironmentPostProcessor实现了EnvironmentPostProcessor接口,来看看官方文档对EnvironmentPostProcessor的描述:

image.png

  • 上图红框中说明开发者可以自定义环境变量;
  • 上图绿框中说明EnvironmentPostProcessor的实现类必须在spring.factories文件中定义;
  • 因此HostInfoEnvironmentPostProcessor类的作用已经清楚了:自定义环境变量
  • 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.hostnamespring.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地址设置到环境变量中;

在配置类中保存服务名称

  • 接下来看看配置类EurekaClientAutoConfiguration,这里面主要是和Eureka相关的配置信息的数据和逻辑;
  • 请看方法eurekaInstanceConfigBean,该方法向Spring容器环境提供EurekaInstanceConfigBean实例,注意instance.setInstanceId(getDefaultInstanceId(propertyResolver))这一行:
@Bean
    @ConditionalOnMissingBean(value = 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));
  • 顺藤摸瓜,展开方法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,果然与源码一致;
  • 源码读到此处,禁不住手痒,按照上面的逻辑,在应用的aplication.yml中增加配置项spring.application.instance_id,看看能否生效,改过的aplication.yml内容如下图所示,红框中是新增的自定义实例ID配置:

image.png

  • 重启应用,重新注册到Eureka server,此时再看home页面如下图红框,服务注册名称果然已经更新:

image.png

使用配置类中的服务名称

  • 现在我们知道了EurekaInstanceConfigBean实例的instanceId字段被设置为"hostname:应用名称:自定义实例ID",接下来看该字段如何被提交到Eureka server;
  • 在EurekaClientAutoConfiguration类中有个eurekaApplicationInfoManager方法,为spring容器提供了ApplicationInfoManager实例:
@Bean
@ConditionalOnMissingBean(value = 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实例,就可以通过该实例获得服务注册名称;
  • Eureka client向Eureka server发起服务注册的操作是在DiscoveryClient类中进行的,该类的构造方法如下:

image.png

  • 如上图所示,红框中ApplicationInfoManager实例被注入,蓝框中表明DiscoveryClient的成员变量instanceInfo获得了InstanceInfo实例;
  • 具体的注册逻辑在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;
    }
  • 上述代码中的eurekaTransport.registrationClient.register(instanceInfo)方法,经过层层调用,最终调用了AbstractJerseyEurekaHttpClient类的register方法,如下图所示:

image.png

  • 上图的红框表明,POST请求时InstanceInfo实例被作为请求参数提交到了Eureka server;

Wireshark抓包验证

  • 至此,代码分析已经结束了,最后我们用Wireshark抓包来验证之前的分析结果,在Eureka client所在电脑上用Wireshark2.6.3来分析注册请求:

image.png

  • 如上图所示,红框中就是注册请求,绿框中是请求包头的全部内容,也就是前面看到的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实例;

欢迎关注阿里云开发者社区博客:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...
相关文章
|
1天前
|
监控 安全 Java
Spring cloud原理详解
Spring cloud原理详解
10 0
|
5天前
|
消息中间件 负载均衡 Java
【Spring Cloud 初探幽】
【Spring Cloud 初探幽】
14 1
|
7天前
|
Java 开发者 微服务
Spring Cloud原理详解
【5月更文挑战第4天】Spring Cloud是Spring生态系统中的微服务框架,包含配置管理、服务发现、断路器、API网关等工具,简化分布式系统开发。核心组件如Eureka(服务发现)、Config Server(配置中心)、Ribbon(负载均衡)、Hystrix(断路器)、Zuul(API网关)等。本文讨论了Spring Cloud的基本概念、核心组件、常见问题及解决策略,并提供代码示例,帮助开发者更好地理解和实践微服务架构。此外,还涵盖了服务通信方式、安全性、性能优化、自动化部署、服务网格和无服务器架构的融合等话题,揭示了微服务架构的未来趋势。
31 6
|
11天前
|
JSON Java Apache
Spring Cloud Feign 使用Apache的HTTP Client替换Feign原生httpclient
Spring Cloud Feign 使用Apache的HTTP Client替换Feign原生httpclient
|
11天前
|
负载均衡 Java 开发者
Spring Cloud:一文读懂其原理与架构
Spring Cloud 是一套微服务解决方案,它整合了Netflix公司的多个开源框架,简化了分布式系统开发。Spring Cloud 提供了服务注册与发现、配置中心、消息总线、负载均衡、熔断机制等工具,让开发者可以快速地构建一些常见的微服务架构。
|
13天前
|
消息中间件 Java RocketMQ
Spring Cloud RocketMQ:构建可靠消息驱动的微服务架构
【4月更文挑战第28天】消息队列在微服务架构中扮演着至关重要的角色,能够实现服务之间的解耦、异步通信以及数据分发。Spring Cloud RocketMQ作为Apache RocketMQ的Spring Cloud集成,为微服务架构提供了可靠的消息传输机制。
28 1
|
13天前
|
Dubbo Java 应用服务中间件
Spring Cloud Dubbo: 微服务通信的高效解决方案
【4月更文挑战第28天】在微服务架构的发展中,服务间的高效通信至关重要。Spring Cloud Dubbo 提供了一种基于 RPC 的通信方式,使得服务间的调用就像本地方法调用一样简单。本篇博客将探讨 Spring Cloud Dubbo 的核心概念,并通过具体实例展示其在项目中的实战应用。
15 2
|
13天前
|
监控 Java Sentinel
Spring Cloud Sentinel:概念与实战应用
【4月更文挑战第28天】在分布式微服务架构中,确保系统的稳定性和可靠性至关重要。Spring Cloud Sentinel 为微服务提供流量控制、熔断降级和系统负载保护,有效预防服务雪崩。本篇博客深入探讨 Spring Cloud Sentinel 的核心概念,并通过实际案例展示其在项目中的应用。
24 0
|
13天前
|
Cloud Native Java Nacos
Spring Cloud Nacos:概念与实战应用
【4月更文挑战第28天】Spring Cloud Nacos 是一个基于 Spring Cloud 构建的服务发现和配置管理工具,适用于微服务架构。Nacos 提供了动态服务发现、服务配置、服务元数据及流量管理等功能,帮助开发者构建云原生应用。
21 0