• 服务注册与服务发现


    服务注册与服务发现

    Eureka的架构

    Eureka客户端:使用了@EnableEurekaClient注解的应用服务,如订单服务等,甚至Eureka本身也是一个客户端
    Eureka服务端:使用了@EnableEurekaServer注解的应用服务,该服务提供了注册表以及对服务节点的操作
    服务提供者:服务启动后,可以向注册中心发起register请求,将服务信息注册进去
    服务消费者:服务启动后,可以从注册中心拉取服务信息,并根据所得的服务信息调用服务
    他们之间的关系为:
    Eureka-server你可以当作他是一个单独的服务,而跟它对接的都是Eureka-client,而Eureka-client中则包含了服务消费者和服务提供者等角色。例如订单服务中包含一个消费者A,商品服务中包含一个提供者B,现在消费者A想查询该订单对应的商品信息,那就是查询提供者B的服务,那么首先A和B都必须先把自己作为一个eureka客户端,然后B将自己的服务信息发送到eureka的服务端中,A再从eureka服务端中拉取B的服务信息(地址),然后发起请求。

    服务端

    所需依赖

    
        org.springframework.cloud
         spring-cloud-starter-netflix-eureka-server
    
    
    • 1
    • 2
    • 3
    • 4

    启动服务时需要添加两个注解

    @EnableEurekaServer
    @SpringBootApplication
    
    • 1
    • 2

    客户端

    所需依赖

    
       org.springframework.boot
       spring-boot-starter-web
    
    
       org.springframework.cloud
       spring-cloud-starter-netflix-eureka-client
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    启动服务需要配置

    @EnableEurekaClient
    @SpringBootApplication
    
    • 1
    • 2

    关系

    1、服务提供者、服务消费者、服务发现组件三者之间的关系大致如下:
    1)各个微服务在启动时时,将自己的网络地址等信息注册到服务发现组件上(eureka,zookeeper,Consul,spring cloud alibaba的nacos),服务发现组件会存储这些信息。
    2)服务消费者会从服务发现组件查询服务提供者的网络地址,然后将服务地址列表缓存到本地,然后根据服务名负载均衡调用服务提供者的接口。
    3)各个微服务与服务发现组件使用一定的机制来维持心跳,服务发现组件若发现有服务没有提供心跳,那么服务发现组件会将该服务剔除。
    4)微服务网络地址发生变更(例如实例增减或者IP端口发生变化等),会重新注册到服务发现组件上,使用这种方式,可以避免因网络变化导致服务之间的通讯停止,服务消费者也无须人工的修改网络地址。

    Eureka简介

    Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。
    1.Eureka包含两个组件:Eureka Server和Eureka Client。
    Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
    Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也就是一个内置的、使用轮询负载算法的负载均衡器。
    2.在应用启动后,Eureka Client会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。
    3.Eureka Server之间通过复制的方式完成数据的同步,Eureka还提供了客户端缓存机制,即使所有的Eureka Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。综上,Eureka通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。

    三、Eureka一些特性及配置过程时需要注意的问题
    1.Eureka能够保证AP,即当Eureka中的某个节点挂掉后,剩余的节点继续仍然可以提供服务的发现与注册服务。而Eureka的客户端在向某个Eureka或者发现了当前的Eureka不可用时,会自动切换到其他的节点,也就是说Eureka的节点是平等的,只要有一台Eureka服务器在,就能保证服务的可以继续被使用。
    2.Eureka的自我保护机制,我们在注册服务时,如发生断网的情况,Eureka不能接收到当前服务的任何心跳请求,Eureka会在默认的90s后,将该服务进行强制剔除,这样就能保证到网络故障时,虽然失去了部分节点,但不会像zookeeper那样会使整个注册服务瘫痪。当网络稳定时,新的实例会同步到其他节点中。
    3.相关配置问题
    服务端的配置如下,不注册自身

    security:
      basic:
        enabled: true
      user:
        name: user
        password: password123
    eureka:
      client:
        register-with-eureka: false
        fetch-registry: false
        service-url:
          defaultZone: http://user:password123@localhost:8761/eureka
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    引入依赖

    
    
        org.springframework.cloud
        spring-cloud-starter-eureka-server
        1.2.3.RELEASE
    
     
    
         org.springframework.cloud
         spring-cloud-starter-eureka
         1.2.3.RELEASE
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    Eureka客户端应用服务注册流程

    1、应用集成eureka客户端依赖,配置eureka相关信息;
    2、当应用启动完成后,应用会自动向eureka-server服务发请求,将自身的示例信息注册到eureka-server;
    3、会启动心跳进程和服务状态检测时间;心跳每隔30s一次;应用服务状态发生变更,也会想eureka-server服务进行服务重新注册;
    4、当应用服务进行关闭时,会调用shutdown方法,进行服务下线的操作;当服务因断电/网络问题/意外停机,无法正确的下线服务时,这时心跳就会起到相对应的作用,如果eureka-server服务发现应用超过三次(90s)没有心跳,那么就会将服务进行保护/下线;

    源码分析

    源码分析:
    应用在服务启动时自动注册的实现:配置类:EurekaClientAutoConfiguration里面向IOC容器内注册了EurekaAutoServiceRegistration.class对象

    @Bean
    @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    @ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
    public EurekaAutoServiceRegistration eurekaAutoServiceRegistration(ApplicationContext context, EurekaServiceRegistry registry,
                                                                           EurekaRegistration registration) {
        return new EurekaAutoServiceRegistration(context, registry, registration);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    EurekaAutoServiceRegistration.class实现了SmartLifecycle.class
    SmartLifecycle.class这个类是在应用启动后,自动调用start()

    @Override
    public void start() {
        // 端口配置
        if (this.port.get() != 0) {
               if (this.registration.getNonSecurePort() == 0) {
                    this.registration.setNonSecurePort(this.port.get());
               }
                if (this.registration.getSecurePort() == 0 && this.registration.isSecure()) {
                    this.registration.setSecurePort(this.port.get());
                }
            }
    
            // only initialize if nonSecurePort is greater than 0 and it isn't already running
            // because of containerPortInitializer below
             if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
              // 进行主动注册
                this.serviceRegistry.register(this.registration);
                // 发布注册事件
                this.context.publishEvent(
                       new InstanceRegisteredEvent<>(this, this.registration.getInstanceConfig()));
                this.running.set(true);
             }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    EurekaServiceRegistry#register() 注册代码如下:

    @Override
    public void register(EurekaRegistration reg) {
         maybeInitializeClient(reg);
         if (log.isInfoEnabled()) {
              log.info("Registering application " + reg.getApplicationInfoManager().getInfo().getAppName()
                  + " with eureka with status "
                  + reg.getInstanceConfig().getInitialStatus());
         }
         reg.getApplicationInfoManager()
            .setInstanceStatus(reg.getInstanceConfig().getInitialStatus());
          // 进行注册健康检查
         reg.getHealthCheckHandler().ifAvailable(healthCheckHandler ->
         reg.getEurekaClient().registerHealthCheck(healthCheckHandler));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    进行安全健康检查同时注册信息 DiscoveryClient#registerHealthCheck()

    @Override
        public void registerHealthCheck(HealthCheckHandler healthCheckHandler) {
            if (instanceInfo == null) {
                logger.error("Cannot register a healthcheck handler when instance info is null!");
            }
            if (healthCheckHandler != null) {
                this.healthCheckHandlerRef.set(healthCheckHandler);
                // schedule an onDemand update of the instanceInfo when a new healthcheck handler is registered
                if (instanceInfoReplicator != null) {
                    // 进行注册
                    instanceInfoReplicator.onDemandUpdate();
                }
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在上述的onDemandUpdate()方法中有个register方法,代码如下
    DiscoveryClient#register()

    boolean register() throws Throwable {
            logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
            EurekaHttpResponse httpResponse;
            try {            // 发请求给配置url进行注册 
                httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
            } catch (Exception e) {
                logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e);
                throw e;
            }
            if (logger.isInfoEnabled()) {
                logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode());
            }
            // 返回204码代表注册成功
            return httpResponse.getStatusCode() == 204;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    AbstractJerseyEurekaHttpClient#register()

    @Override
    public EurekaHttpResponse register(InstanceInfo info) {
       String urlPath = "apps/" + info.getAppName();
       ClientResponse response = null;
       try {
           Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
           addExtraHeaders(resourceBuilder);
           response = resourceBuilder.header("Accept-Encoding", "gzip").type(MediaType.APPLICATION_JSON_TYPE).accept(MediaType.APPLICATION_JSON).post(ClientResponse.class, info);
           return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
        } finally {
           if (logger.isDebugEnabled()) {
                logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(), response == null ? "N/A" : response.getStatus());
         }
         if (response != null) {
              response.close();
          }
       }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
  • 相关阅读:
    文本向量化
    汽车冲压车间的RFID技术设计解决方案
    ESP32-WROOM-32 ESP32 wifi模块基本参数与引脚定义
    全网最简单解决方式1045-Access denied for user root@localhost(using password:YES)
    半导体芯片制造行业MES系统解决方案
    Java方法相关知识点
    队列的实现(c语言实现)
    注解与自定义注解
    《乔布斯传》英文原著重点词汇笔记(五)【 chapter three 】
    快鲸scrm系统:解决企业管理的三大核心痛点问题
  • 原文地址:https://blog.csdn.net/Artisan_w/article/details/132699090