美文网首页
Spring Cloud系列之Commons - 2. 服务发现

Spring Cloud系列之Commons - 2. 服务发现

作者: 干货满满张哈希 | 来源:发表于2021-01-27 09:00 被阅读0次

    Spring Cloud Commons 主要包括如下模块的接口和默认实现:

    [图片上传失败...(image-9acb62-1611709199837)]

    其中的限流策略以及重试策略是没有天然带的,但是其他模块的实现一般会带上这些功能。我们先从服务发现相关接口开始分析

    服务发现相关

    核心接口DiscoveryClient

    DiscoveryClient

    public interface DiscoveryClient extends Ordered {
        int DEFAULT_ORDER = 0;
        //描述
        String description();
        //通过 serviceId 获取服务实例
        List<ServiceInstance> getInstances(String serviceId);
        //获取所有服务的名称
        List<String> getServices();
        @Override
        default int getOrder() {
            return DEFAULT_ORDER;
        }
    }
    

    DiscoveryClient 扩展了 Ordered 接口,这个和之前提到的@Order注解的作用是一样的。

    服务实例的信息包括:

    [图片上传失败...(image-eef92d-1611709199837)]

    public interface ServiceInstance {
    
        //实例id,并不是必须的
        default String getInstanceId() {
            return null;
        }
    
        //服务id,用于区分不同微服务
        String getServiceId();
    
        //服务实例提供服务的地址
        String getHost();
    
        //服务实例提供服务的端口
        int getPort();
    
        //是否使用的是 HTTPS
        boolean isSecure();
    
        //提供服务的 URI 地址
        URI getUri();
    
        //一些元数据信息
        Map<String, String> getMetadata();
    
        //使用的传输协议,例如 http,https 等等
        default String getScheme() {
            return null;
        }
    
    }
    

    Spring Cloud 从 Feinchley 版本之后,越来越重视异步 Reactor 编程与 WebFlux,所以所有同步的接口基本上都有对应的异步接口,这里的DiscoveryClient对应的就是ReactiveDiscoveryClient:

    public interface ReactiveDiscoveryClient extends Ordered {
        int DEFAULT_ORDER = 0;
        //描述
        String description();
        //通过 serviceId 获取服务实例,这里返回的是 Flux,究竟如何使用会在后面的例子中详细阐明
        Flux<ServiceInstance> getInstances(String serviceId);
        //获取所有服务的名称,这里返回的是 Flux,究竟如何使用会在后面的例子中详细阐明
        Flux<String> getServices();
        @Override
        default int getOrder() {
            return 0;
        }
    }
    

    如何通过配置文件配置服务实例?

    使用 SimpleDiscoveryClientSimpleReactiveDiscoveryClient

    假设要调用的微服务的域名是固定的,我们可以直接通过将这些域名写入配置文件。这个场景一般发生在:

    • 基于 Kubernetes ingress nginx 与 coredns 的内网域名解析负载均衡
    • 外网统一提供服务的域名

    我们通过一个例子来说明下 SimpleDiscoveryClientSimpleReactiveDiscoveryClient,这里的代码可以从这里下载,首先引入依赖:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.1</version>
    </parent>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-commons</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>2020.0.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    

    编写application.yml,这里列举了SimpleDiscoveryClientSimpleReactiveDiscoveryClient所有可能的配置:

    spring:
      cloud:
        discovery:
          client:
            # SimpleDiscoveryClient与SimpleReactiveDiscoveryClient的配置
            simple:
              instances:
                #微服务1
                service1:
                  #实例1
                  - host: instance1 #地址
                    port: 8080 #端口
                    instanceId: 'instance1:8080' #可以不填,实例id
                  #实例2
                  - uri: 'https://instance2:443' #指定了 scheme 为 https,host 为 instance2,端口为443
                #微服务2
                service2:
                  #实例3
                  - host: instance3 #地址
                    port: 80 #端口
                    instanceId: ${spring.cloud.discovery.client.simple.instances.service2[0].host}:${spring.cloud.discovery.client.simple.instances.service2[0].port} #可以不填,实例id
                  #实例4
                  - uri: 'https://instance4:8080' #指定了 scheme 为 https,host 为 instance4,端口为8080
              # 指定 SimpleDiscoveryClient的排序顺序为1,默认是0,越小越优先
              order: 1
    # actuator 配置
    management:
      endpoint:
        health:
          # health 接口总是输出详细信息
          show-details: always
      endpoints:
        jmx:
          exposure:
            # jmx 不暴露任何 actuator 接口
            exclude: '*'
        web:
          exposure:
            # http 暴露所有 actuator 接口
            include: '*'
    

    我们配置了四个不同实例:
    [图片上传失败...(image-c219e5-1611709199837)]

    我们可以直接通过 uri 配置,也可以具体配置其中的 host,port 和 isSecure,两者是等价的。例如:

    spring:
      cloud:
        discovery:
          client:
            # SimpleDiscoveryClient与SimpleReactiveDiscoveryClient的配置
            simple:
              instances:
                #微服务1
                service1:
                  #实例1
                  - host: instance1 #地址
                    port: 8080 #端口
    

    等价于

    spring:
      cloud:
        discovery:
          client:
            # SimpleDiscoveryClient与SimpleReactiveDiscoveryClient的配置
            simple:
              instances:
                #微服务1
                service1:
                  #实例1
                  - uri: http://instance1:8080
    

    instanceId 不一定需要指定,serviceId 会根据实例配置的上一级取,就算自己配置了,例如:

    spring:
      cloud:
        discovery:
          client:
            # SimpleDiscoveryClient与SimpleReactiveDiscoveryClient的配置
            simple:
              instances:
                #微服务1
                service1:
                  #实例1
                  - host: instance1 #地址
                    port: 8080 #端口
                    serviceId: service2 #无效,实际还是service1
    

    serviceId 还是实际合理的那个,也就是 service1。

    这些机制在后面的源码分析就会理解了。
    我们的测试代码会用到 Spring Boot 的事件机制,也就是在 ApplicationContext 到某一生命周期的时候,这些事件会被发布出来,由实现了对应事件的ApplicationListener接口的 Bean 消费,Spring boot 中,事件主要包括:

    • ApplicationStartingEvent:这个是spring boot应用一开始启动时,发出的事件,只是用来标识,应用开始启动了,一般没什么用
    • ApplicationEnvironmentPreparedEvent:这个是在创建好Environment(通过上下文配置,判断到底创建StandardServletEnvironment(针对Servlet环境),StandardReactiveWebEnvironment(针对Reactive环境)还是StandardEnvironment(针对无servlet环境))之后发出的事件。
    • ApplicationContextInitializedEvent: 这个是在创建好Context并调用ApplicationContextInitializer初始化context之后发布这个事件,在加载bean信息之前
    • ApplicationPreparedEvent:加载bean信息之后,但是还没有创建bean的时候,发步这个事件。这个事件是和调用ApplicationContextAware设置ApplicationContext一起进行的,可以看出,setApplicationContext方法里面不能去获取bean,因为bean可能还没有初始化完成
    • ApplicationStartedEvent: 加载初始化各种需要的bean并依赖注入之后,在运行ApplicationRunner做一些用户自定义的初始化操作之前,会发布这个事件。
    • ApplicationReadyEvent:运行ApplicationRunner做一些用户自定义的初始化操作之后,会发布这个事件。

    我们使用ApplicationReadyEventApplicationListener确保所有的DiscoveryClient都初始化完成并可以使用作为测试类。

    编写测试类:
    TestSimpleDiscoveryClient

    /**
     * 通过消费 ApplicationReadyEvent 来确保 DiscoveryClient 初始化完成并可用
     */
    @Slf4j
    @Component
    public class TestSimpleDiscoveryClient implements ApplicationListener<ApplicationReadyEvent> {
    
    
        /**
         * 初始化的方法返回类型是 DiscoveryClient 并且不是 Primary,这里只能通过 @Resource 自动装载不能通过 @Autowired
         * 这里不排除以后返回类型修改为 SimpleDiscoveryClient 的可能性
         * @see org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration
         */
        @Resource
        private SimpleDiscoveryClient simpleDiscoveryClient;
    
        @Override
        public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
            List<String> services = simpleDiscoveryClient.getServices();
            services.forEach(serviceId -> {
                log.info("{}: {}", serviceId, simpleDiscoveryClient.getInstances(serviceId));
            });
        }
    }
    
    

    TestSimpleReactiveDiscoveryClient

    /**
     * 通过消费 ApplicationReadyEvent 来确保 DiscoveryClient 初始化完成并可用
     */
    @Slf4j
    @Component
    public class TestSimpleReactiveDiscoveryClient implements ApplicationListener<ApplicationReadyEvent> {
        @Autowired
        private SimpleReactiveDiscoveryClient simpleReactiveDiscoveryClient;
    
        @Override
        public void onApplicationEvent(ApplicationReadyEvent event) {
            simpleReactiveDiscoveryClient.getServices().subscribe(serviceId -> {
                simpleReactiveDiscoveryClient.getInstances(serviceId).collect(Collectors.toList()).subscribe(serviceInstances -> {
                    log.info("{}: {}", serviceId, serviceInstances);
                });
            });
        }
    }
    

    启动类:DiscoveryClientMain

    @SpringBootApplication
    public class DiscoveryClientMain {
        public static void main(String[] args) {
            SpringApplication.run(DiscoveryClientMain.class, args);
        }
    }
    
    

    启动后,可以看到日志:

    2021-01-19 09:38:05.646  INFO 6168 --- [           main] .h.s.c.i.s.d.s.TestSimpleDiscoveryClient : service2: [DefaultServiceInstance{instanceId='instance3:80', serviceId='service2', host='instance3', port=80, secure=false, metadata={}}, DefaultServiceInstance{instanceId='null', serviceId='service2', host='instance4', port=8080, secure=true, metadata={}}]
    2021-01-19 09:38:05.647  INFO 6168 --- [           main] .h.s.c.i.s.d.s.TestSimpleDiscoveryClient : service1: [DefaultServiceInstance{instanceId='instance:8080', serviceId='service1', host='instance1', port=8080, secure=false, metadata={}}, DefaultServiceInstance{instanceId='null', serviceId='service1', host='instance2', port=443, secure=true, metadata={}}]
    2021-01-19 09:38:05.913  INFO 6168 --- [           main] .s.d.s.TestSimpleReactiveDiscoveryClient : service2: [DefaultServiceInstance{instanceId='instance3:80', serviceId='service2', host='instance3', port=80, secure=false, metadata={}}, DefaultServiceInstance{instanceId='null', serviceId='service2', host='instance4', port=8080, secure=true, metadata={}}]
    2021-01-19 09:38:05.913  INFO 6168 --- [           main] .s.d.s.TestSimpleReactiveDiscoveryClient : service1: [DefaultServiceInstance{instanceId='instance:8080', serviceId='service1', host='instance1', port=8080, secure=false, metadata={}}, DefaultServiceInstance{instanceId='null', serviceId='service1', host='instance2', port=443, secure=true, metadata={}}]
    
    

    相关文章

      网友评论

          本文标题:Spring Cloud系列之Commons - 2. 服务发现

          本文链接:https://www.haomeiwen.com/subject/seuozktx.html