关于服务注册中心

服务注册中心的本质是为了解耦服务提供者和服务消费者。
对于任何一个微服务,原则上都应该存在或者支持多个提供者(即部署多个服务实例),这是由微服务的分布式属性决定的。
更进一步,为了支持弹性扩缩容特性,一个微服务的提供者的数量和分布往往是动态变化的,无法预先确定。
因此,需要引入额外的组件来管理服务提供者的注册与发现,这个组件就是服务注册中心,

服务注册中心的一般原理

分布式微服务架构中,服务中心用于存储服务提供者地址信息,服务发布相关的属性信息。服务消费者通过主动查询和被动通知的方式获取服务提供者的地址信息(不需要通过硬编码得到提供者的地址信息,只需要知道当前系统发布了哪些服务,而不需要知道服务具体存在于什么位置——透明化路由)。
image.png
具体步骤描述:

  1. 服务提供者启动;
  2. 服务提供者将相关的服务信息主动注册到服务注册中心;
  3. 服务消费者获取服务注册中心:(1)pull模式:服务消费者主动拉群可用的服务提供者清单;(2)服务消费者订阅服务(当服务提供者有变化时,注册中心主动推送更新后的服务清单给消费者)。
  4. 服务消费者直接调用提供者

另外:注册中心需要完成服务提供者的健康监控,当发现服务提供者失效后需要及时剔除。

主流服务中心对比

Zookeeper

Zookeeper是一个分布式服务框架,是Apache Hadoop的一个子项目,主要用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项管理等。
简单来说,Zookeeper本质=存储+监听通知。
Zookeeper用来做服务注册中心,主要是因为它具有节点变更通知功能,只要客户端监听相关服务节点,服务节点的所有变更,都能及时地通知到监听客户端。这样作为调用方(服务消费者)只要使用Zookeeper的客户端就能实现服务节点的订阅和变更通知功能。
Zookeeper可用性也可以,只要超过半数以上的选举节点存活(Follower节点),整个集群就是可用的。

Eureka

由Netflix开源,集成到Spring Cloud体系中,是基于RestfulAPI风格开发的服务注册与发现组件。

Consul

Consul是基于Go语言开发的支持多数据中心分布式高可用的服务发布和注册和服务软件,采用Raft算法保证服务的一致性,且支持健康检查。

Nacos

Nacos是注册中心 + 配置中心的组合,可以解决微服务涉及到的服务注册与发现,服务配置,服务管理等问题。是Spring Cloud Aliaba核心组件之一,负责服务注册与发现,还有服务配置。

组件名 语言 CAP 对外暴露接口
Eureka Java AP HTTP
Consul Go CP HTTP/DNS
Zookeeper Java CP 客户端
Nacos Java 支持AP/CP切换 HTTP

C:数据一致性;A:高可用性;P:分区容错性(一定要满足)。 CAP不可能同时满足三个,要么AP,要么CP。

服务注册中心组件Eureka

Eureka基础架构

Eureka包含两个组件,Eureka Server 和 Eureka Client。
Eureka Client是一个Java客户端,用于简化与Eureka Server的交互。Eureka Server提供服务发现的能力。
各个微服务启动时,会通过Eureka Client向 Eureka Server进行注册自己的信息(例如网络信息),Eureka会存储微服务的信息。

Eureka交互流程及原理

image.png
(1)图中的 分区1分区2分区3 代表不同区不同机房的机器;
(2)图中每一个Eureka Server都是一个集群;
(3)图中 服务提供者向Eureka Server中注册服务,Eureka Server接受到注册事件会在集群和分区中进行数据同步,服务消费者可以从Eureka Server中获取到服务注册信息,进行服务调用;
(4)微服务启动后,会周期性地Eureka Server发送心跳(默认周期30秒),以续约自己的信息;
(5)Eureka Server在一定时间内(默认90秒)没有接到某个微服务的心跳,Eureka Server将会注销该服务节点。
(6)每个Eureka Server也是Eureka Client,多个Eureka Server之间通过复制的方式完成服务注册列表的同步。
(7)Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕机,服务消费者依然可以使用缓存中的信息找到服务提供者。
Eureka通过心跳检测,健康检查和客户端缓存等机制,提供系统的灵活性,可伸缩性和可用性。

Eureka应用及高可用集群

实现目标

(1)单实例Eureka Server -> 访问管理界面 -> Eureka Server集群
(2)服务提供者集群 (注册至Eureka Server集群)
(3)服务消费者集群(注册至Eureka Server集群,从Eureka Server集群获取服务信息)

搭建单例Eureka Server服务注册中心

服务信息

  1. 服务注册中心:cloud-eureka-server (8761)
  2. 服务提供者:service-resume (8080)
  3. 服务消费者:server-autodeliver (8090)

搭建步骤

  1. cloud-parent父工程引入Spring Cloud依赖 ```xml org.springframework.cloud spring-cloud-dependencies Greenwich.RELEASE pom import

com.sun.xml.bind jaxb-core 2.2.11

javax.xml.bind jaxb-api

com.sun.xml.bind jaxb-impl 2.2.11

org.glassfish.jaxb jaxb-runtime 2.2.10-b140310.1920

javax.activation activation 1.1.1

  1. 2. **cloud-eureka-server子工程**
  2. - pom文件引入相关依赖
  3. ```xml
  4. <dependencies>
  5. <!--Eureka server依赖-->
  6. <dependency>
  7. <groupId>org.springframework.cloud</groupId>
  8. <artifactId>spring-cloud-starter-netflix-eurekaserver</artifactId>
  9. </dependency>
  10. </dependencies>
  • application.yml配置文件

    1. #Eureka server服务端⼝
    2. server:
    3. port: 8761
    4. spring:
    5. application:
    6. name: cloud-eureka-server # 应⽤名称,会在Eureka中作为服务的id标识(serviceId)
    7. eureka:
    8. instance:
    9. hostname: localhost
    10. client:
    11. service-url: # 客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址
    12. defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
    13. register-with-eureka: false # ⾃⼰就是服务不需要注册⾃⼰
    14. fetch-registry: false #⾃⼰就是服务不需要从Eureka Server获取服务信息,默认为true,置为false
  • 启动类声明项目为Eureka Server

    1. @SpringBootApplication
    2. @EnableEurekaServer // 声明本项⽬是⼀个Eureka服务
    3. public class LagouCloudEurekaServerApplication {
    4. public static void main(String[] args) {
    5. SpringApplication.run(LagouCloudEurekaServerApplication.class,args);
    6. }
    7. }
  • 执行启动类,访问 http://localhost:8761, 进入Eureka注册中心后台,EurekaServer发布成功

image.png
image.png

搭建Eureka Server高可用集群

搭建集群必要性

在生产环境中,会配置 Eureka Server集群实现高可用性。Eureka Server集群之中的节点通过点对点(P2P)通信方式共享服务注册表。
image.png

服务信息

  1. 服务注册中心:cloud-eureka-server (8761)
  2. 服务注册中心:cloud-eureka-server (8762)

搭建步骤

  1. 修改本机的host属性

通过在个人计算机中进行模拟集群搭建。

  1. 127.0.0.1 CloudEurekaServerA
  2. 127.0.0.1 CloudEurekaServerB
  1. 修改cloud-eureka-server工程的yml配置文件 ```yaml

    指定应⽤名称

    spring: application: name: cloud-eureka-server

第⼀个profile,后期启动spring-boot项⽬时,可通过命令参数指定

spring: profiles: CloudEurekaServerA server: port: 8761 eureka: instance: hostname: CloudEurekaServerA client: register-with-eureka: true fetch-registry: true serviceUrl:

  1. defaultZone: http://CloudEurekaServerB:8762/eureka

第⼆个profile,后期启动spring-boot项⽬时,可通过命令参数指定

spring: profiles: CloudEurekaServerB server: port: 8762 eureka: instance: hostname: CloudEurekaServerB client: register-with-eureka: true fetch-registry: true serviceUrl: defaultZone: http://CloudEurekaServerA:8761/eureka

  1. 说明:<br />(1)在一个实例中,将另外的一个实例作为集群中的镜像节点,那么在这个实例中的 `eureka.client.servuceUrl.defaultZone` 的值url中的 `CloudEurekaServerA` 需要与另外一个实例中的 `eureka.instance.hostname` 的值 `CloudEurekaServerA` 保持一致。<br />(2 `register-with-eureka` `fetch-registry` 在单节点设置为false,因为只有一台Eureka Server,并不需要自己注册自己,而现在有了集群,需要在集群的其他节点中注册本服务,故设置为true
  2. 3. 启动两次Spring Boot项目,分别使用两个不同的profiles
  3. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/757484/1615898827421-fe8d3307-0ec3-4af0-9599-43e0369bd3a1.png#height=368&id=dqi2E&margin=%5Bobject%20Object%5D&name=image.png&originHeight=368&originWidth=633&originalType=binary&ratio=1&size=107610&status=done&style=none&width=633)
  4. 4. 访问EurekaServer页面 `http://cloudeurekaservera:8761/` `http://cloudeurekaserverb:8762/` , 会发现注册中心 `cloud-eureka-server` 已经有两个节点,并且 `registered-replicas` (相邻集群复制节点)中已经包含了对方。
  5. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/757484/1615899083403-1114fecf-7ec7-4bf0-86da-68ec82e74f84.png#height=240&id=rKFUE&margin=%5Bobject%20Object%5D&name=image.png&originHeight=240&originWidth=633&originalType=binary&ratio=1&size=51823&status=done&style=none&width=633)<br />说明:除了使用同一个工程启动两次的方法外,也可以配置两个工程。
  6. <a name="sOHC7"></a>
  7. ### 微服务提供者注册到Eureka Server集群
  8. <a name="4Jm62"></a>
  9. #### 服务信息

服务提供者:service-resume (8080) 服务提供者:service-resume (8081)

  1. <a name="N3rQF"></a>
  2. #### 搭建步骤
  3. 1. 父工程引入 `spring-cloud-commons` 依赖
  4. ```xml
  5. <dependency>
  6. <groupId>org.springframework.cloud</groupId>
  7. <artifactId>spring-cloud-commons</artifactId>
  8. </dependency>
  1. pom文件引入坐标,添加eureka client相关坐标

    1. <dependency>
    2. <groupId>org.springframework.cloud</groupId>
    3. <artifactId>spring-cloud-starter-netflix-eurekaclient</artifactId>
    4. </dependency>
  2. 配置application.yml文件

    1. eureka:
    2. client:
    3. serviceUrl: # eureka server的路径
    4. defaultZone: http://cloudeurekaservera:8761/eureka/,http://cloudeur
    5. ekaserverb:8762/eureka/ #把 eureka 集群中的所有 url 都填写了进来,也可以只写⼀台,因为各个 eureka server 可以同步注册表
    6. instance:
    7. #使⽤ip注册,否则会使⽤主机名注册了(此处考虑到对⽼版本的兼容,新版本经过实验都是ip)
    8. prefer-ip-address: true
    9. #⾃定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
    10. # @project.version@ 工程版本,例如 1.0-SNAPSHOT(加上版本号,便于多版本管理)
    11. instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.vers
    12. ion@

    instance-id添加版本号的效果
    image.png

  3. 启动类添加注解 ```java @EnableDiscoveryClient @EnableEurekaClient

—- 相关说明 —- 1) 从 Spring Cloud Edgware版本开始,@EnableDiscoveryClient 或 @EnableEurekaClient可省略。只需要加上 相关依赖,并进行相应配置,即可将微服务注册到服务发现组件上。 2) @EnableDiscoveryClient 和 @EnableEurekaClient 二者功能一样。@EnableDiscoveryClient通用性更好,支持 包括Eureka在内的其他注册中心。

  1. <a name="CGSaz"></a>
  2. ### 微服务消费者注册到Eureka Server集群
  3. <a name="SZ5bP"></a>
  4. #### 服务信息

服务消费者:server-autodeliver (8090) 服务消费者:server-autodeliver (8091)

  1. <a name="OivlV"></a>
  2. #### 搭建步骤
  3. 1. pom文件引入坐标,添加eureka client相关坐标
  4. ```xml
  5. <dependency>
  6. <groupId>org.springframework.cloud</groupId>
  7. <artifactId>spring-cloud-commons</artifactId>
  8. </dependency>
  9. <dependency>
  10. <groupId>org.springframework.cloud</groupId>
  11. <artifactId>spring-cloud-starter-netflix-eurekaclient</artifactId>
  12. </dependency>
  1. 配置application.yml文件

    1. server:
    2. port: 8090
    3. eureka:
    4. client:
    5. serviceUrl: # eureka server的路径
    6. defaultZone: http://cloudeurekaservera:8761/eureka/,http://cloudeur
    7. ekaserverb:8762/eureka/ #把 eureka 集群中的所有 url 都填写了进来,也可以只写⼀台,因为各个 eureka server 可以同步注册表
    8. instance:
    9. #使⽤ip注册,否则会使⽤主机名注册了(此处考虑到对⽼版本的兼容,新版本经过实验都是ip)
    10. prefer-ip-address: true
    11. #⾃定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
    12. instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.vers
    13. ion@
    14. spring:
    15. application:
    16. name: service-autodeliver
  2. 启动类添加注解 @EnableDiscoveryClient

    服务消费者调用服务提供者

    image.png

    Eureka细节详解

    Eureka元数据详解

    Eureka的元数据有两种,标准元数据和自定义元数据。
    标准元数据:主机名、IP地址、端口号等信息。这些信息都会被发布在服务注册表中,用于服务之间的调用。
    自定义元数据:可以使用 eureka.instance.metadata-map 配置,符合 KEY/VALUE 存储格式。
    这些元数据可以在远程客户端中访问。

    1. eureka:
    2. instance:
    3. prefer-ip-address: true
    4. metadata-map:
    5. # 自定义元数据(kv自定义)
    6. cluster: cl1
    7. region: rn1

    ```java @Autowired private DiscoveryClient discoveryClient;

@Test public void test() { // 从EurekaServer获取指定微服务实例 List serviceInstanceList = discoveryClient.getInstances(“lagou-service-resume”); // 循环打印每个微服务实例的元数据信息 for (int i = 0; i < serviceInstanceList.size(); i++) { ServiceInstance serviceInstance = serviceInstanceList.get(i); System.out.println(serviceInstance); } }

  1. 元数据如下:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/757484/1616070515503-da9ab95f-c3dd-4f82-b552-16318e4738a3.png#height=226&id=oxu65&margin=%5Bobject%20Object%5D&name=image.png&originHeight=451&originWidth=659&originalType=binary&ratio=1&size=128039&status=done&style=none&width=329.5)![image.png](https://cdn.nlark.com/yuque/0/2021/png/757484/1616070539670-fbfd23d8-905a-4c28-a658-224d6964c4a9.png#height=166&id=VBBDb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=284&originWidth=647&originalType=binary&ratio=1&size=94610&status=done&style=none&width=378)
  2. <a name="1I4Y9"></a>
  3. ### Eureka客户端详解
  4. 服务提供者和服务消费者都为Eureka客户端。需要向EurekaServer注册服务。<br />服务提供者需完成服务续约(心跳)、注册服务操作。
  5. <a name="ZZSWE"></a>
  6. #### 服务注册(服务提供者)
  7. 1)导入 `eureka-client` 依赖坐标,配置Eureka服务注册中心地址。<br />(2)服务在启动时会向注册中心发起注册请求,携带服务元数据信息。<br />(3Eureka注册中心会把服务的信息保存在Map中。
  8. <a name="X4VoS"></a>
  9. #### 服务续约(服务提供者)
  10. 服务每隔30秒会向注册中心续约(心跳)一次(也称为报活),如果没有续约,租约会在90秒后到期,然后服务就会失效。<br />每隔30秒的续约操作称之为心跳检测。
  11. ```yaml
  12. #向Eureka服务中⼼集群注册服务
  13. eureka:
  14. instance:
  15. # 租约续约间隔时间,默认30秒
  16. lease-renewal-interval-in-seconds: 30
  17. # 租约到期,服务时效时间,默认值90秒,服务超过90秒没有发⽣⼼跳,EurekaServer会将服务从列表移除
  18. lease-expiration-duration-in-seconds: 90

获取服务列表(服务消费者)

每隔30秒服务会从注册中心拉取一份服务列表,这个时间可以通过配置修改。

  1. #向Eureka服务中⼼集群注册服务
  2. eureka:
  3. client:
  4. # 每隔多久拉取⼀次服务列表
  5. registry-fetch-interval-seconds: 30

(1) 服务消费者启动时,会从Eureka Server服务列表中获取只读备份,缓存到本地;
(2)每隔30秒,会重新获取并更新数据;
(3)每隔30秒的时间,可以通过配置 eureka.client.registry-fetch-interval-seconds 修改。

Eureka服务端详解

服务下线

(1)当服务正常关闭操作时,会发送服务下线的rest请求给EurekaServer。
(2)服务中心接受请求后,将该服务置为下线状态。

失效剔除

Eureka Server会定时进行检查(通过配置间隔值: eureka.server.eviction-interval-timer-in-ms ,默认为 60s ),如果发现客户端实例在一定时间内没有发送心跳(客户端配置的 eureka.instance.lease-expiration-duration-in-seconds ,默认值为 90s ),则会注销实例。

自我保护

服务提供者会向服务注册中心注册自己,并会在每隔30s向注册中心发送心跳进行报活(心跳检测)操作。
但是,如果在心跳检测过程中,假如服务提供者和注册中心之间的网络存在问题(不代表服务提供者不可用,不代表服务消费者无法访问到服务提供者),导致在15分钟内85%的客户端节点没有正常的心跳,那么Eureka就会认为客户端与注册中心出现了网络故障,Eureka Server就会自动进入自我保护机制。

为什么会有自我保护机制: 默认情况下,如果Eureka Server在一定时间内(默认90秒)没有收到某个微服务实例的心跳,Eureka Server将会移除该实例。 但是当网络分区故障发生时,微服务与Eureka Server之间是无法通信,但是微服务本身是正常运行,此时不应该移除这个微服务,所以引入了自我保护机制。

当处于自我保护模式时,服务注册中心页面会显示如下提示信息:
image.png
当处于自我保护模式时,
(1)不会剔除任何服务实例(可能是服务提供者和EurekaServer之间的网络问题),从而保证大多数服务依然可用。
(2)Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上,保证当前节点依然可用。当网络稳定时,当前Eureka Server新的注册信息会被同步到其他的节点上。
(3)在Eureka Server工程的中可以通过配置 eureka.server.enable-self-presevation 自我保护的关闭,默认是打开的。(建议生产环境中打开自我保护机制)。

  1. eureka:
  2. server:
  3. enable-self-preservation: false # 关闭⾃我保护模式(缺省为打开)