eureka优缺点
- 设计原则 AP
- 优点:服务高可用
- 缺点
- 服务节点间的数据可能不一致
- Client-Server间的数据可能不一致
- 适用场景
- 云机房集群,跨越多机房部署
- 对注册中心服务可用性要求较高
注册中心核心原理
- 所有的注册中心都需要两个核心功能
- 地址统一维护(服务注册)
- 服务的上下线感知,而服务消费者向注册中心获取服务提供者列表有两种方式
- 消费者主动向注册中心拉取 (pull)
- 注册中心主动给服务消费者推送 (push)
服务注册发现(register)机制
- 服务提供者、服务消费者、以及服务注册中心自己,启动后都会向注册中心注册服务
- 注册中心服务接收到register请求后:
-
保存服务信息,将服务信息保存到registry中;
-
更新队列,将此事件添加到更新队列中,供Eureka Client增量同步服务信息使用。
-
清空二级缓存,即readWriteCacheMap,用于保证数据的一致性。
-
更新阈值,供剔除服务使用。
-
同步服务信息,将此事件同步至其他的Eureka Server节点。
-
- 流程:
- 注册
- 读取eureka server的地址信息和自己的配置信息,
- 然后将自己的信息封装在InstanceInfo实例中,
- 先把eureka server中的注册拉取到本地缓存起来
- 将封装的InstanceInfo实例发送到eureka server进行注册,然后初始化心跳检测以及缓存刷新
- 再次拉取注册表更新本地注册表信息
- 发现
- Eureka Client在启动时, 发送REST请求向Eureka Server取到注册到上面的其他服务的信息,并缓存在本地,默认30s轮询一次。
- 注册
服务续约(renew)机制
- 服务注册后,要定时(默认30S,可自己配置)向注册中心发送续约请求(心跳)”。
- 在每一个Eureka Client启动的时候,都会有一个HeartbeatThread的心跳线程
- 保证默认每隔30秒的时候向Eureka Server发送一个心跳
- 告诉Eureka Server当前的Eureka Client还存活着
- eureka.instance.lease-renewal-interval-in-seconds,这个参数可以来配置对应的心跳间隔时间
- 注册中心收到续约请求后:
- 先去自己的注册表中去,找到请求的对应的服务信息,更新服务对象的最近续约时间(即Lease对象的LastUpdateTimestamp)
- 同步服务信息,将此事件同步至其他的Eureka Server节点。
- 剔除服务之前会先判断服务是否已经过期,判断服务是否过期的条件之一是续约时间和当前时间的差值是不是大于阈值
服务注销(cancel)机制
- 服务正常停止之前会向注册中心发送注销请求,告诉注册中心“我要下线了”。
- 注册中心服务接收到cancel请求后:
- 删除服务信息,将服务信息从registry中删除;
- 更新队列,将此事件添加到更新队列中,供Eureka Client增量同步服务信息使用。
- 清空二级缓存,即readWriteCacheMap,用于保证数据的一致性。
更新阈值,供剔除服务使用。 - 同步服务信息,将此事件同步至其他的Eureka Server节点。
- 服务正常停止才会发送Cancel,如果是非正常停止,则不会发送,此服务由Eureka Server主动剔除。
服务剔除(evit)机制
- Eureka Server提供了服务剔除的机制,用于剔除没有正常下线的服务。
- 服务的剔除包括三个步骤
-
首先判断是否满足服务剔除的条件
- 有两种情况可以满足服务剔除的条件:
- 关闭了自我保护
- 如果开启了自我保护,需要进一步判断是Eureka Server出了问题,还是Eureka Client出了问题,如果是Eureka Client出了问题则进行剔除。
- 核心的条件是自我保护机制,Eureka自我保护机制是为了防止误杀服务而提供的一个机制。Eureka的自我保护机制“谦虚”的认为如果大量服务都续约失败,则认为是自己出问题了(如自己断网了),也就不剔除了;反之,则是Eureka Client的问题,需要进行剔除。而自我保护阈值是区分Eureka Client还是Eureka Server出问题的临界值:
- 如果超出阈值就表示大量服务可用,少量服务不可用,则判定是Eureka Client出了问题。
- 如果未超出阈值就表示大量服务不可用,则判定是Eureka Server出了问题。
- 举例:
- 如果有100个服务,续约间隔是30S,自我保护阈值0.85。
- 自我保护阈值=100 * 60 / 30 * 0.85 = 170。
- 如果上一分钟的续约数=180>170,则说明大量服务可用,是服务问题,进入剔除流程;
- 如果上一分钟的续约数=150<170,则说明大量服务不可用,是注册中心自己的问题,进入自我保护模式,不进入剔除流程。
- 有两种情况可以满足服务剔除的条件:
-
然后找出过期的服务
- 遍历所有的服务,判断上次续约时间距离当前时间大于阈值就标记为过期。并将这些过期的服务保存到集合中。
-
最后执行剔除。
- 在剔除服务之前先计算剔除的数量,然后遍历过期服务,通过洗牌算法确保每次都公平的选择出要剔除的任务,最后进行剔除。
- 执行剔除服务后:
- 删除服务信息,从registry中删除服务。
- 更新队列,将当前剔除事件保存到更新队列中。
- 清空二级缓存,保证数据的一致性。
- 实现过程参考AbstractInstanceRegistry.evict()方法。
-
自我保护机制
- Eureka Server在运行期间会去统计心跳失败的比例在15分钟之内是否低于85%
- 如果发现85%以上的服务都没有心跳,Eureka Server会认为当前实例的客户端与自己的心跳连接出现了网络故障,那么Eureka Server会把这些实例保护起来,让这些实例不会过期导致实例剔除。
- 这样做的目的是为了减少网络不稳定或者网络分区的情况下,Eureka Server将健康服务剔除下线的问题。 使用自我保护机制可以使得Eureka 集群更加健壮和稳定的运行。
- 进入自我保护状态后,会出现以下几种情况
- Eureka Server不再从注册列表中移除因为长时间没有收到心跳而应该剔除的过期服务
- Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上,保证当前节点依然可用。
服务获取机制
- Eureka Client获取服务有两种方式,全量同步和增量同步。获取流程是根据Eureka Server的多层数据结构进行的:
- 无论是全量同步还是增量同步,都是先从缓存中获取,如果缓存中没有,则先加载到缓存中,再从缓存中获取。(registry只保存数据结构,缓存中保存ready的服务信息。)
- 先从一级缓存中获取
- 先判断是否开启了一级缓存
- 如果开启了则从一级缓存中获取,如果存在则返回,如果没有,则从二级缓存中获取
- 如果未开启,则跳过一级缓存,从二级缓存中获取
- 再从二级缓存中获取
- 如果二级缓存中存在,则直接返回;
- 如果二级缓存中不存在,则先将数据加载到二级缓存中,再从二级缓存中获取。
- 先从一级缓存中获取
- 注意加载时需要判断是增量同步还是全量同步,增量同步从recentlyChangedQueue中load,全量同步从registry中load。
服务同步机制
- 服务同步机制是用来同步Eureka Server节点之间服务信息的。它包括Eureka Server启动时的同步,和运行过程中的同步。
- 启动时同步
- Eureka Server启动后,遍历eurekaClient.getApplications获取服务信息,并将服务信息注册到自己的registry中。
- 注意这里是两层循环,第一层循环是为了保证已经拉取到服务信息,第二层循环是遍历拉取到的服务信息。
- 运行过程中同步
- 当Eureka Server节点有register、renew、cancel请求进来时,会将这个请求封装成TaskHolder放到acceptorQueue队列中,然后经过一系列的处理,放到batchWorkQueue中。
- TaskExecutor.BatchWorkerRunnable是个线程池,不断的从batchWorkQueue队列中poll出TaskHolder,然后向其他Eureka Server节点发送同步请求。
- 在acceptorQueue向batchWorkQueue转化时,省略了中间的processingOrder和pendingTasks过程。
- 当同步失败时,会将失败的TaskHolder保存到reprocessQueue中,重试处理。
- 启动时同步
基本存储结构
- Eureka Server直接在内存中维护了一个ConcurrentHasnMap数据结构,用于保存Eureka Server服务注册信息。
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
private final ConcurrentHashMap
<String,Map<String, Lease<InstanceInfo>>>
registry = new ConcurrentHashMap();
}
JAVA实现Eureka单机版
eureka-server
- 创建一个eureka-server模块,并使用Spring Initializer初始化一个SpringBoot项目
- 勾选Eureka Server组件
- pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- 在启动类上添加@EnableEurekaServer注解来启用Euerka注册中心功能
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
- 在配置文件application.yml中添加Eureka注册中心的配置
server:
port: 8001 #指定运行端口
spring:
application:
name: eureka-server #指定服务名称
eureka:
instance:
hostname: localhost #指定主机地址
client:
fetch-registry: false #指定是否要从注册中心获取服务(注册中心不需要开启)
register-with-eureka: false #指定是否要注册到注册中心(注册中心不需要开启)
server:
enable-self-preservation: false #关闭保护模式
- 运行完成后访问地址http://localhost:8001/可以看到Eureka注册中心的界面
eureka-client
- 新建一个eureka-client模块,并在pom.xml中添加如下依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 在启动类上添加@EnableDiscoveryClient注解表明是一个Eureka客户端
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
}
- 在配置文件application.yml中添加Eureka客户端的配置
server:
port: 8101 #运行端口号
spring:
application:
name: eureka-client #服务名称
eureka:
client:
register-with-eureka: true #注册到Eureka的注册中心
fetch-registry: true #获取注册实例列表
service-url:
defaultZone: http://localhost:8001/eureka/ #配置注册中心地址
- 运行eureka-client,查看注册中心http://localhost:8001/发现Eureka客户端已经成功注册
JAVA搭建Eureka集群版
-
搭建两个注册中心
- 由于所有服务都会注册到注册中心去,服务之间的调用都是通过从注册中心获取的服务列表来调用,注册中心一旦宕机,所有服务调用都会出现问题。所以我们需要多个注册中心组成集群来提供服务
-
给eureka-sever添加配置文件application-replica1.yml配置第一个注册中心
server:
port: 8002
spring:
application:
name: eureka-server
eureka:
instance:
hostname: replica1
client:
serviceUrl:
defaultZone: http://replica2:8003/eureka/ #注册到另一个Eureka注册中心
fetch-registry: true
register-with-eureka: true
- 给eureka-sever添加配置文件application-replica2.yml配置第二个注册中心
server:
port: 8003
spring:
application:
name: eureka-server
eureka:
instance:
hostname: replica2
client:
serviceUrl:
defaultZone: http://replica1:8002/eureka/ #注册到另一个Eureka注册中心
fetch-registry: true
register-with-eureka: true
-
这里我们通过两个注册中心互相注册,搭建了注册中心的双节点集群,由于defaultZone使用了域名,所以还需在本机的host文件中配置一下。
- 修改本地host文件
- 127.0.0.1 replica1
- 127.0.0.1 replica2
- 修改本地host文件
-
运行Eureka注册中心集群
-
在IDEA中我们可以通过使用不同的配置文件来启动同一个SpringBoot应用。
-
从原启动配置中复制一个出来
-
配置启动的配置文件
-
启动两个eureka-server,访问其中一个注册中心http://replica1:8002/发现另一个已经成为其备份
-
-
修改Eureka-client配置文件,让其连接到集群
server:
port: 8102
spring:
application:
name: eureka-client
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://replica1:8002/eureka/,http://replica2:8003/eureka/ #同时注册到两个注册中心
- 启动后访问任意一个注册中心节点都可以看到eureka-client
Eureka注册中心添加认证
- 创建一个eureka-security-server模块,在pom.xml中添加以下依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 添加application.yml配置文件,配置登录注册中心的用户名和密码。
server:
port: 8004
spring:
application:
name: eureka-security-server
security: #配置SpringSecurity登录用户名和密码
user:
name: eureka
password: 123456
eureka:
instance:
hostname: localhost
client:
fetch-registry: false
register-with-eureka: false
- 添加Java配置WebSecurityConfig
- 默认情况下添加SpringSecurity依赖的应用每个请求都需要添加CSRF token才能访问,Eureka客户端注册时并不会添加,所以需要配置/eureka/**路径不需要CSRF token。
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/eureka/**");
super.configure(http);
}
}
- 运行eureka-security-server,访问http://localhost:8004发现需要登录认证
- eureka-client注册到有登录认证的注册中心,配置文件如下
- 配置文件中需要修改注册中心地址格式
http://${username}:${password}@${hostname}:${port}/eureka/
server:
port: 8103
spring:
application:
name: eureka-client
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka:123456@localhost:8004/eureka/
Eureka常用配置
eureka:
#eureka客户端配置
client:
#是否将自己注册到eureka服务端上去
register-with-eureka: true
#是否获取eureka服务端上注册的服务列表
fetch-registry: true
service-url:
# 指定注册中心地址
defaultZone: http://localhost:8001/eureka/
# 启用eureka客户端
enabled: true
#定义去eureka服务端获取服务列表的时间间隔
registry-fetch-interval-seconds: 30
#eureka客户端实例配置
instance:
#定义服务多久去注册中心续约
lease-renewal-interval-in-seconds: 30
#定义服务多久不去续约认为服务失效
lease-expiration-duration-in-seconds: 90
metadata-map:
#所在区域
zone: jiangsu
#服务主机名称
hostname: localhost
#是否优先使用ip来作为主机名
prefer-ip-address: false
#eureka服务端配置
server:
#关闭eureka服务端的保护机制
enable-self-preservation: false