【MSA】Spring Cloud Eureka

发布时间 2023-06-11 22:41:12作者: lihewei

1 Spring Cloud Eureka 简介

Spirng Cloud Eureka 使用 Netflix Eureka 来实现服务注册与发现(服务治理)。它既包含了服务端组件,也包含了客户端组件,并且服务端与客户端均采用java编写,所以Eureka主要适用于通过java实现的分布式系统,或是JVM兼容语言构建的系统。

  • Eureka服务端组件:即服务注册中心。它同其他服务注册中心一样,支持高可用配置。

  • Eureka客户端组件:主要处理服务的注册和发现。客户端服务通过注册和参数配置的方式,嵌入在客户端应用程序的代码中。在应用程序启动时,Eureka客户端向服务注册中心注册自身提供的服务,并周期性的发送心跳来更新它的服务租约。同时,他也能从服务端查询当前注册的服务信息并把它们缓存到本地,并周期性的刷新服务状态。

Eureka架构中的三个核心角色

  • 服务注册中心:Eureka服务端应用,提供服务管理(注册发现)功能
  • 服务提供者:对外发布服务,供消费者调用
    • 要求统一对外提供Rest风格服务
  • 服务消费者:服务提供者的调用方。从注册中心获取服务列表,通过列表调用服务提供者。

2. Eureka 和 Zookeeper 的区别

2.1 CAP 原则

  • 一致性(Consistency):在分布式系统中,所有节点在同一时刻的数据都是一致的。

  • 可用性(Availability):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。

  • 分区容错性(Partition tolerance)【这个特性是不可避免的】:保证系统中任意信息的丢失都不会影响系统的运行。

CAP 原则指的是在一个分布式系统中,这三个要素最多只能同时实现两点,不可能三者兼顾。

2.2 为什么 zookeeper 不适合做注册中心?

因为,在分布式系统中 zookeeper 遵循的是CP原则,即数据一致性 和 分区容错性,这就导致一种情况:当 master 节点因为网络故障与其他节点失去联系时,剩余节点会重新进行 leader 选举。问题在于,选举 leader 的时间太长,30 ~ 120s,且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得 zk 集群失去 master 节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。

所以说,作为注册中心,可用性的要求要高于一致性!

  • C : 数据的一致性 (A,B,C 里面的数据是一致的)Zookeeper: 注重数据的一致性。Eureka: 不是很注重数据的一致性!

  • A: 服务的可用性(若 zk 集群里面的 master 挂了怎么办)Paxos(多数派)

    在 zk 里面,若主机挂了,则 zk 集群整体不对外提供服务了,需要选一个新的出来(120s左右)才能继续对外提供服务!

    Eureka 注重服务的可用性,当 Eureka 集群只有一台活着,它就能对外提供服务

  • P:分区的容错性(在集群里面的机器,因为网络原因,机房的原因,可能导致数据不会同步),它是分布式必须需要实现的特性!

Zookeeper 注重数据的一致性,CP zk(注册中心,配置文件中心,协调中心)

Eureka 注重服务的可用性 AP eureka(注册中心)

3. Spring Cloud Eureka 快速入门

3.1 搭建 Eureka-server

  1. New Module时勾选“Eureka Server”
  2. 修改 pom.xml 文件
  3. 修改启动类:在启动类上添加@EnableEurekaServer注解(开启eureka注册中心服务端)
  4. 修改配置文件

eureka服务端依赖

 <!-- eureka 注册中心的服务端-->
 <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
 </dependency>

eureka服务端配置文件

server:
  port: 8761
spring:
  application:
    name: eureka-server #服务名称
 eureka:
   client:
     service-url:
 	  defaultZone: xxx:port #可以自己注册自己的地址

3.2 搭建 Eureka-client

  1. New Module时勾选“Spring web、Eureka Discovery Client”
  2. 添加eureka客户端依赖 pom.xml
  3. 在启动类上添加@EnableEurekaClient注解(标记此服务是eureka客户端)
  4. 修改配置文件

eureka客户端依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

eureka客户端配置文件

server:
  port: 8761
spring:
  application:
    name: eureka-client-a #服务名称 
 eureka:
   client:
     service-url: #eureka 服务端和客户端的交互地址
 	   defaultZone: http://localhost:8761/eureka/

3.3 访问测试

http://localhost:8761

注意:在 eureka 里面是通过 spring.application.name 来区分服务的

4. Eureka 常用配置文件详解 ?

4.1 server 中常用的配置

server:
  port: 8761
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url: #eureka 服务端和客户端的交互地址,集群用,隔开
    	defaultZone: http://localhost:8761/eureka
    fetch-registry: true #是否拉取服务列表
    register-with-eureka: true #是否注册自己(单机 eureka 一般关闭注册自己,集群注意打开)
  server:
    eviction-interval-timer-in-ms: 30000 #清除无效节点的频率(毫秒)--定期删除
    enable-self-preservation: true #server 的自我保护机制,避免因为网络原因造成误剔除,生产环境建议打开
    renewal-percent-threshold: 0.85 #85%,如果在一个机房的 client 端,15 分钟内有 85%的 client 没有续约,那么则可能是网络原因,认为服务实例没有问题,不会剔除他们,宁可放过一万,不可错杀一个,确保高可用
  instance:
    hostname: localhost # 服务主机名称
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} # 实例 id
    prefer-ip-address: true # 服务列表以 ip 的形式展示
    lease-renewal-interval-in-seconds: 10 # 表示 eureka client 发送心跳给 server 端的频率
    lease-expiration-duration-in-seconds: 20 #表示 eureka server 至上一次收到 client 的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该实例

4.2 client 中常用的配置

server:
  port: 8080
spring:
  application:
    name: eureka-client
eureka:
  client:
    service-url: #eureka 服务端和客户端的交互地址,集群用,隔开
      defaultZone: http://localhost:8761/eureka
    register-with-eureka: true #注册自己
    fetch-registry: true #拉取服务列表
    registry-fetch-interval-seconds: 5 # 表示 eureka-client 间隔多久去拉取服务注册信息
  instance:
    hostname: localhost # 服务主机名称
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} # 实例 id
    prefer-ip-address: true # 服务列表以 ip 的形式展示
    lease-renewal-interval-in-seconds: 10 # 表示 eureka client 发送心跳给 server 端的频率
    lease-expiration-duration-in-seconds: 20 #表示 eureka server 至上一次收到 client 的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该实例

5. 构建高可用的 Eureka-Server 集群

注意:

  1. 多个eureka服务端要保证服务名,端口号一致
  2. 将这三个eureka服务部署到三台服务器上(ip不能一致)

5.1 集群改造:eureka-service配置文件

修改Eureka server-1、server-2、server-3 的配置文件

server:
  port: 8761 
spring:
  application:
    name: eureka-server #服务名称
eureka:
  client:
    fetch-registry: true #是否拉取服务列表
    register-with-eureka: true #是否注册自己(集群需要注册自己和拉取服务)
    service-url:
      defaultZone: http://10.1.61.121:8761/eureka/,http://10.1.61.122:8761/eureka/,http://10.1.61.123:8761/eureka/
  server:
    eviction-interval-timer-in-ms: 90000 #清除无效节点的评率(毫秒)
  instance:
  	#server 在等待下一个客户端发送的心跳时间,若在指定时间不能收到客户端心跳,则剔除此实例并且禁止流量
    lease-expiration-duration-in-seconds: 90 

5.2 集群改造:eureka-client配置文件

注意:集群需要注册自己和拉取服务配置

  • eureka.client.fetch-registry:true # 拉取服务列表
  • eureka.client.register-with-eureka:true # 注册自己到eureka-service上
server:
  port: 8001 
spring:
  application:
    name: eureka-client-a #服务名称
eureka:
  client:
    fetch-registry: true #是否拉取服务列表
    register-with-eureka: true #是否注册自己(集群需要注册自己和拉取服务)
    service-url:
      defaultZone: http://10.1.61.121:8761/eureka/,http://10.1.61.122:8761/eureka/,http://10.1.61.123:8761/eureka/
  instance:
  	instance-id: ${spring.application.name}:${server.port} # 注册到eureka-service的实力名称,一般这样显示
    lease-renewal-interval-in-seconds: 30 # eureka客户端向服务端发送心跳时间,每隔30s发送一次

5.3 测试

  • 注册服务:不管哪一台 server 都注册成功了

  • 宕机一台 server:Eureka server 的集群里面,没有主机和从机的概念,节点都是对等的,只有集群里面有一个集群存活,就能保证服务的可用性。 (主机 (写) 从 (读))只要有一台存活,服务就能注册和调用

6. Eureka 概念理解

服务注册(register)

Eureka Client会通过发送REST请求的方式,向Eureka Server注册自己的服务。注册时,提供自身的元数据,比如ip地址、端口、运行状况指标、主页地址等信息。Eureka Server接收到注册请求后,就会把这些元数据信息存储在一个双层的Map中。

服务续约(renew)

(默认每隔30s告诉注册中心,自己还活着)

在服务注册后,Eureka Client会维护一个心跳(心跳检测机制)来持续通知Eureka Server,说明服务一直处于可用状态,防止被剔除。默认每隔30秒eureka.instance.lease-renewal-interval-in-seconds发送一次心跳来进行服务续约。

# 租约续约间隔时间,默认30秒
eureka.instance.lease-renewal-interval-in-seconds: 30 

获取服务列表(get registry)

(默认每隔30s获取一次最新的服务清单)

服务消费者(Eureka Client)在启动的时候,会发送一个REST请求给Eureka Server,获取注册中心的服务清单,并且缓存在客户端本地。同时,为了性能及安全性考虑,Eureka Server会每隔30秒更新一次缓存中的服务清单。

# 每隔多久获取服务中心列表,(只读备份)
eureka.client.registry-fetch-interval-seconds: 30 

服务发现

根据服务名称发现服务的实例过程,客户端会在本地缓存服务端的列表,拉取列表是有间隔周期的 (导致服务上线 客户端不能第一时间感知到【可以容忍】)其实每次做服务发现 都是从本地的列表来进行的

服务调用

(根据服务列表,直接去访问服务提供者)

服务消费者在获取到服务清单后,可以根据清单中的服务信息,查找到该服务的地址,从而进行访问(远程调用)。

服务下线(cancel)

(正常关闭,发一个请求告诉注册中心)

当Eureka Client需要关闭或重启时,就不希望在这个时间段内再有请求进来,所以,就需要提前先发送REST请求给Eureka Server,告诉Eureka Server自己要下线了,Eureka Server在收到请求后,就会把该服务状态置为下线(DOWN),并把该下线事件传播出去。

失效剔除(evict)

(主要剔除非正常关闭的服务)

服务实例可能会因为网络故障等原因,导致不能提供服务,而此时该实例也没有发送请求给Eureka Server来进行服务下线。所以,还需要有服务剔除的机制。Eureka Server在启动的时候会创建一个定时任务,每隔一段时间(默认60秒),从当前服务清单中把超时没有续约((90秒后,把没有续约的服务记录到小账本)默认90秒eureka.instance.lease-expiration-duration-in-seconds)的服务剔除。

# 租约到期,服务时效时间,默认值90秒
eureka.instance.lease-expiration-duration-in-seconds: 90 

自我保护

既然Eureka Server会定时剔除超时没有续约的服务,那就有可能出现一种场景,网络一段时间内发生了异常,所有的服务都没能够进行续约,Eureka Server就把所有的服务都剔除了,这样显然不太合理。所以,就有了自我保护机制。自我保护机制是,当在短时间内,统计续约失败的比例,如果达到一定阈值,则会触发自我保护的机制,在该机制下,Eureka Server不会剔除任何的微服务,等到正常后,再退出自我保护机制。自我保护开关(eureka.server.enable-self-preservation: false)

#向Eureka服务中心集群注册服务
eureka.server.enable-self-preservation: false # 关闭自我保护模式(默认值是打开)
#Eureka会统计服务实例最近15分钟心跳续约的比例是否低于85%,如果低于则会触发自我保护机制。
renewal-percent-threshold: 0.85

如果触发自我保护:续约低于阈值,因此实例不会为了安全而过期

  • 自我保护模式下,不会剔除任何服务实例
  • 自我保护模式能够保证绝大多数服务的可用性