一、简介
Eureka 是Netflix公司开源的一个服务注册与发现的组件,和其他Netflix 公司的服务组件(例如负载均衡、 熔断器、网关等) 一起,被Spring Cloud 社区整合为Spring Cloud Netflix 模块。 Eureka 是 Netflix 贡献给Spring Cloud的一个框架!
二、常见的注册中心分类
| 注册中心 | 提供方 | 地址 | |
|---|---|---|---|
| Consul | Spring | spring.io/projects/sp… | |
| Eureka | Netflix | github.com/Netflix/eur… | |
| Nacos | alibaba | nacos.io/zh-cn/ | |
| Zookeeper | yahoo |
三、什么是ACP原则
3.1 APC原则
CAP原则又指CAP定理,是指在一个分布式系统中有如下三个原则:
- 一致性 (Consistency)
- 可用性 (Availability)
- 分区容错性 (Partition tolerance)(这个特性是不可避免的) APC原则指的是,这三个要素只能同时实现其中两点,不可能三者兼顾
3.2 分布式特征
C:数据的一致性,A、B、C里面的数据都是一致性 B:服务的可用性() P:分区的容错性(在集群里面的机器。因为网络原因,机房的原因。可能导致数据不会同步),他在分布式需要实现的特性
Zookeeper 注重数据的一致性,CP zk(注册中心,配置文件中心,协调中心) Eureka 注重服务的可用性 AP eureka (注册中心)
四、Eureka快速入门
==注意:SpringCloud版本号和Springboot要一一对应上,详细参考官网,这里就不列举了== 本文采用了SpringCloud的Hoxton.SR12版本,对应Springboot版本2.3.12.RELEASE
这里我们准备两个服务 Eureka-server 服务端, Eureka-client客户端
4.1 Eureka-server服务端
4.1.1 导入Eureka-server服务端的依赖
<!--Eureka注册中心服务端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
4.1.2 启动类增加服务端注解
@EnableEurekaServer
4.1.3 修改配置文件
Eureka服务端默认端口是8761 在微服务中我们一般都要给应用起个名字,方便好辨识
4.2 Eureka-client 客户端
4.2.1 导入Euaeka-client客户端的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
4.2.2 启动类增加客户端注解
@EnableEurekaClient
4.2.3 修改配置文件
server:
port: 8001
spring:
application:
name: erueka-client
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka ## 注册到8761注册中心
4.3 访问测试
4.5 注册中心的状态标识
- UP: 服务是上线的,括号里面是具体服务实例的个数,提供服务的最小单元
- DOWN: 服务是下线的
- UN_KONW: 服务的状态未知
4.5.1 服务的实例名称
4.6 常用的配置文件设置
4.6.1 Server中常用的设置
server:
port: 8761
spring:
application:
name: erueka-server
eureka:
server:
enable-self-preservation: true #server 的自我保护机制,避免因为网络原因造成误剔除,生产环境建议打开
renewal-percent-threshold: 0.85 #85%,如果在一个机房的client端,15分钟内有85%的client没有续约,那么则可能是网络原因,认为服务实例没有问题,不会剔除他们,宁可放过一万,不可错杀一个,确保高可用
expected-client-renewal-interval-seconds: 30 #客户端续约时间间隔(秒),用于计算每分钟续约次数
eviction-interval-timer-in-ms: 30000 #清除无效节点的频率(毫秒)--定期删除
client:
service-url: #eureka 服务端和客户端的交互地址,集群用,隔开
defaultZone: http://localhost:8761/eureka/
register-with-eureka: false #是否注册自己(单机eureka一般关闭注册自己,集群注意打开)
fetch-registry: true #是否拉取服务列表
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 #客户端向服务端发送心跳的时间间隔(秒)
lease-expiration-duration-in-seconds: 20 #服务端在收到最后一次心跳后等待时间上限(秒)
4.6.2 Client的常用配置
server:
port: 8001
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的心跳之后,等待 下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该实例
五、构建高可用的Eureka-server集群
5.1 修改配置文件
5.1.1 server-01
server:
port: 8761 #为什么是8761,其他端口就报错
spring:
application:
name: eureka-server #服务名称
eureka:
client:
fetch-registry: true #是否拉取服务列表 #是否注册自己(集群需要注册自己和拉取服务) register-with-eureka: true
service-url:
defaultZone: http://localhost:8762/eureka/,http://localhost:8763/eureka/
server:
eviction-interval-timer-in-ms: 90000
instance:
lease-expiration-duration-in-seconds: 90 #清除无效节点的评率(毫秒) #server 在等待下一个客户端发送的心跳 时间,若在指定时间不能收到客户端心跳,则剔除此实例并且禁止流量
5.1.2 server-02
server:
port: 8762 #为什么是8761,其他端口就报错
spring:
application:
name: eureka-server #服务名称
eureka:
client:
fetch-registry: true #是否拉取服务列表 #是否注册自己(集群需要注册自己和拉取服务) register-with-eureka: true
service-url:
defaultZone: http://localhost:8761/eureka/,http://localhost:8763/eureka/
server:
eviction-interval-timer-in-ms: 90000
instance:
lease-expiration-duration-in-seconds: 90 #清除无效节点的评率(毫秒) #server 在等待下一个客户端发送的心跳 时间,若在指定时间不能收到客户端心跳,则剔除此实例并且禁止流量
5.1.3 server-03
server:
port: 8763 #为什么是8761,其他端口就报错
spring:
application:
name: eureka-server #服务名称
eureka:
client:
fetch-registry: true #是否拉取服务列表 #是否注册自己(集群需要注册自己和拉取服务) register-with-eureka: true
service-url:
defaultZone: http://localhost:8761/eureka/,http://localhost:8763/eureka/
server:
eviction-interval-timer-in-ms: 90000
instance:
lease-expiration-duration-in-seconds: 90 #清除无效节点的评率(毫秒) #server 在等待下一个客户端发送的心跳 时间,若在指定时间不能收到客户端心跳,则剔除此实例并且禁止流量
==注意:如果写成dlocalhost的,Eureka会认为同一个服务部署了多份,就不认为是一个集群。
==这时候我们需要修改本地hosts文件,给这三个服务配置不同的域名即可
Eureka server 的集群里面,没有主机和从机的概念,节点都是对等的,只有集群里面有一 个集群存活,就能保证服务的可用性。 (主机 (写) 从 (读)) 只要有一台存活,服务就能注册和调用
六、Eureka概念理解
6.1 服务的注册
当项目启动时(eureka的客户端),就会向eureka-server发送自己的元数据(原始数据) (运行的ip,端口port,健康的状态监控等,因为使用的是http/ResuFul请求风格), eureka-server 会在自己内部保留这些元数据(内存中)。(有一个服务列表)(restful风 格,以http动词的请求方式,完成对url资源的操作)
6.2 服务的续约
项目启动成功了,除了向eureka-server注册自己成功,还会定时的向eureka-server汇 报自己,心跳,表示自己还活着。(修改一个时间)
6.3 服务的下线(主动下线)
当项目关闭时,会给eureka-server报告,说明自己要下机了
6.4 服务的剔除(被动下线,主动剔除)
当项目超过了指定时间没有向eureka-server汇报自己,那么eureka-server就会认为此 节点死掉了,会把它剔除掉,也不会放流量和请求到此节点了。
七、工作原理
7.1 Eureka的工作原理
- Eureka Server 启动成功,等待服务端注册。在启动过程中如果配置了集群,集群之间定时通过 Replicate 同步注册表,每个 Eureka Server 都存在独立完整的服务注册表信息。
- Eureka Client 启动时根据配置的 Eureka Server 地址去注册中心注册服务。
- Eureka Client 会每 30s 向 Eureka Server 发送一次心跳请求,证明客户端服务正常。
- 当 Eureka Server 90s 内没有收到 Eureka Client 的心跳,注册中心则认为该节点失效,会注销该实例。
- 单位时间内 Eureka Server 统计到有大量的 Eureka Client 没有上送心跳,则认为可能为网络异常,进入自我保护机制,不再剔除没有上送心跳的客户端。
- 当 Eureka Client 心跳请求恢复正常之后,Eureka Server 自动退出自我保护模式。
- Eureka Client 定时全量或者增量从注册中心获取服务注册表,并且将获取到的信息缓存到本地。
- 服务调用时,Eureka Client 会先从本地缓存找寻调取的服务。如果获取不到,先从注册中心刷新注册表,再同步到本地缓存。
- Eureka Client 获取到目标服务器信息,发起服务调用。
- Eureka Client 程序关闭时向 Eureka Server 发送取消请求,Eureka Server 将实例从注册表中删除。
7.2 Eureka的自我保护
默认情况下,如果 Eureka Server 在一定的 90s 内没有接收到某个微服务实例的心跳,会注销该实例。但是在微服务架构下服务之间通常都是跨进程调用,网络通信往往会面临着各种问题,比如微服务状态正常,网络分区故障,导致此实例被注销。
固定时间内大量实例被注销,可能会严重威胁整个微服务架构的可用性。为了解决这个问题,Eureka 开发了自我保护机制,那么什么是自我保护机制呢?
Eureka Server 在运行期间会去统计心跳失败比例在15分钟之内是否低于85%,如果低于85%,Eureka Server 即会进入自我保护机制。 Eureka Server 进入自我保护机制,会出现以下几种情况:
- Eureka 不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
- Eureka 仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)。
- 当网络稳定时,当前实例新的注册信息会被同步到其它节点中。 Eureka 自我保护机制是为了防止误杀服务而提供的一个机制。当个别客户端出现心跳失联时,则认为是客户端的问题,剔除掉客户端;当 Eureka 捕获到大量的心跳失败时,则认为可能是网络问题,进入自我保护机制;当客户端心跳恢复时,Eureka 会自动退出自我保护机制。
如果在保护期内刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一个无效的服务实例,即会调用失败。对于这个问题需要服务消费者端要有一些容错机制,如重试,断路器等。
通过在 Eureka Server 配置如下参数,开启或者关闭保护机制,生产环境建议打开:
eureka.server.enable-self-preservation=true
7.3 Eureka的缓存机制
Eureka Server 在运行期间就是一个普通的 Java 项目,并没有使用数据库之类的存储软件,那么在运行期间是如何存储数据的呢?
Eureka Server 的数据存储分了两层:数据存储层和缓存层。数据存储层记录注册到 Eureka Server 上的服务信息,缓存层是经过包装后的数据,可以直接在 Eureka Client 调用时返回。
7.3.1 数据存储层的数据结构
Eureka Server 的数据存储层是双层的 ConcurrentHashMap,我们知道ConcurrentHashMap 是线程安全高效的 Map 集合
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry= new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
第一层的 ConcurrentHashMap 的 key=spring.application.name 也就是客户端实例注册的应用名;value 为嵌套的 ConcurrentHashMap。
第二层嵌套的 ConcurrentHashMap 的 key=instanceId 也就是服务的唯一实例 ID,value 为 Lease 对象,Lease 对象存储着这个实例的所有注册信息,包括 ip 、端口、属性等。
根据这个存储结构我们可以发现,Eureka Server 第一层都是存储着所有的服务名,以及服务名对应的实例信息,最终的数据结构如下:
7.3.2 缓存层的结构
Eureka Server 为了提供响应效率,提供了两层的缓存结构,将 Eureka Client 所需要的注册信息,直接存储在缓存结构中。
第一层缓存:readOnlyCacheMap,本质上是 ConcurrentHashMap,依赖定时从 readWriteCacheMap 同步数据,默认时间为 30 秒。
readOnlyCacheMap : 是一个只读缓存,这个主要是为了供客户端获取注册信息时使用,其缓存更新,依赖于定时器的更新,通过和 readWriteCacheMap 的值做对比,如果数据不一致,则以 readWriteCacheMap 的数据为准。
第二层缓存:readWriteCacheMap,本质上是 Guava 缓存。
readWriteCacheMap:readWriteCacheMap 的数据主要同步于存储层。当获取缓存时判断缓存中是否没有数据,如果不存在此数据,则通过 CacheLoader 的 load 方法去加载,加载成功之后将数据放入缓存,同时返回数据。
readWriteCacheMap 缓存过期时间,默认为 180 秒,当服务下线、过期、注册、状态变更,都会来清除此缓存中的数据。
Eureka Client 获取全量或者增量的数据时,会先从一级缓存中获取;如果一级缓存中不存在,再从二级缓存中获取;如果二级缓存也不存在,这时候先将存储层的数据同步到缓存中,再从缓存中获取。
通过 Eureka Server 的二层缓存机制,可以非常有效地提升 Eureka Server 的响应时间,通过数据存储层和缓存层的数据切割,根据使用场景来提供不同的数据支持。
7.3.3 缓存设计带来的一些思考
除了 Eureka Server 端存在缓存外,Eureka Client 也同样存在着缓存机制,Eureka Client 启动时会全量拉取服务列表,启动后每隔 30 秒从 Eureka Server 量获取服务列表信息,并保持在本地缓存中。
Eureka Client 增量拉取失败,或者增量拉取之后对比 hashcode 发现不一致,就会执行全量拉取,这样避免了网络某时段分片带来的问题,同样会更新到本地缓存。
同时对于服务调用,如果涉及到 ribbon 负载均衡,那么 ribbon 对于这个实例列表也有自己的缓存,这个缓存定时(默认30秒)从 Eureka Client 的缓存更新。
这么多的缓存机制可能就会造成一些问题,一个服务启动后可能最长需要 90s 才能被其它服务感知到:
首先,Eureka Server 维护每 30s 更新的响应缓存。 Eureka Client 对已经获取到的注册信息也做了 30s 缓存。 负载均衡组件 Ribbon 也有 30s 缓存。 这三个缓存加起来,就有可能导致服务注册最长延迟 90s ,这个需要我们在特殊业务场景中注意其产生的影响。
八、添加用户认证
当我们配置完 Spring Cloud Eureka 注册中心的时候,默认访问首页直接进入注册中心。
这样不管是谁,都可以直接进入太不安全,如果是在内网还好一点,如在外网,则把你的所有服务都暴露在外,非常不安全。
在Eureka Server 和Eureka Client 的pom文件添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
==注意:springboot引入spring-boot-starter-security做安全校验后,自动开启CSRF安全认证。
8.1 Eureka server
1、在application.properties中设置用户名和密码
spring.security.user.name=test
spring.security.user.password=123456
2、注册中心地址也要进行修改,如下
eureka.client.service-url.defaultZone=http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:9090/os/eureka
3、默认情况下,当Spring Security在classpath中,它将要求向应用程序的每个请求都发送一个有效的CSRF令牌。Eureka客户端通常不会拥有有效的跨站点请求伪造(CSRF)令牌,所以需要手动关闭CSRF。
在Eureka-Server创建Java类
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()// 关闭CSRF
.authorizeRequests()
.antMatchers("/actuator/**").permitAll()
.anyRequest()
.authenticated().and().httpBasic();
//super.configure(http); // 上面设置了请求路径就不能使用默认的了,这里不能打开
}
}
8.2 Eureka Client
上面eureka server中的eureka.instance.hostname=localhost,所以需要修改Eureka Client的配置文件:
eureka.client.service-url.defaultZone=http://test:123456@localhost:9090/os/eureka