【Spring Cloud(二)】服务注册与发现组件Eureka
一、Eureka原理
1、架构图
首先来看eureka的官方结构图
所有应用作为Eureka Client和Eureka Server交互,服务提供者启动时向Eureka Server注册自己的IP、端口、提供服务等信息,并定时续约更新自己的状态。
服务消费者通过Eureka Server发现得到所需服务的提供者地址信息,然后向服务提供者发起远程调用。
为了保证Eureka注册中心的高可用,可以集群部署,其中一个节点信息又更新时通知其他Server节点,不同节点的Eureka通过Replicate进行数据同步。
2、基本原理
在Eureka响应的过程中,有三个角色,分别是Eureka、服务提供者、服务消费者;
- 在服务启动后,服务提供者向Eureka注册自己的信息,如调用地址、提供服务信息等
- Eureka为服务注册中心,向外暴露自己的地址,负责管理、记录服务提供者的信息,同时将符合要求的服务提供者地址列表返回服务消费者
- 服务消费者向Eureka订阅服务,表达自己的需求,然后得到服务提供者消息,远程调用即可
Eureka包含两个组件:Eureka Server和Eureka Client,作用如下:
- Eureka Client是一个Java客户端,主要用来简化和Eureka Server的交互
- Eureka Server提供服务发现的能力,各个微服务启动时,通过Eureka Client向Eureka Server注册自己的信息,Server储存该服务的信息
- 微服务启动后,周期性(默认为30s)地向Server发送心跳信息,以续约自己的信息,超时(默认为90s)未接受到心跳信息,Server将注销该服务节点
- 在Eureka Server设置注销某个服务提供者节点后,并将该服务向其他订阅者发布,订阅者更新本地缓存信息
- 在集群中,每个Eureka Server同时也是Eureka Client,互相注册完成服务注册表的消息同步,避免因为某个Eureka节点挂掉而导致整个微服务架构挂掉
- Eureka Client会缓存Server中的信息,在调用服务前,首先查看缓存是否存在相关信息,本地的缓存定期更新
3、自我保护机制
由于在超时(默认90s)未接受到服务提供者的心跳续约信息后,Eureka Server就注销该实例,但往往由于微服务之间的跨进程调用,受网络通信情况影响较大,比如在微服务状态正常,但网络分区故障时,注销服务实例会让大部分微服务不可用,为避免这样的情况,引进了Eureka的自我保护机制。自我保护机制的原理是,当某个Eureka Server节点在短时间内丢失了过多的客户端节点时,该节点就进入自我保护机制,不再注销任何微服务实例,当网络故障恢复正常后,该节点自动退出自我保护机制。
自我保护机制根据自我保护阈值来区分是Eureka Server还是Eureka Client出了问题导致服务未能按时续约。
4、服务机制
一个服务按流程依次为服务注册,然后周期性地续约服务,若服务正常停止,则在停止服务之前向注册中心发送注销请求,若为非正常停止,则不会发送,该服务由Eureka Server主动剔除。
4.1、服务注册机制——Register
在服务提供者启动时用来注册服务,以及提供者状态发生变化时更新服务状态。
server接受服务注册后,首先保存注册信息;然后更新自我保护阈值,供服务剔除机制使用;最后同步服务信息,将此事件同步至其他Eureka Server节点。
4.2、服务续约机制——Renew
由服务提供者定期调用,向server发送心跳信息续约服务。
在注册中心接受到续约请求后,更新服务对象的最近续约时间,然后同步服务信息,将此事件同步至其他Eureka Server节点。
剔除服务之前会首先判断服务是否过期,是否过期的条件之一就是最近续约时间和当前时间差值是否大于阈值。
4.3、服务注销机制——Cancel
由服务提供者shut down时调用,用来注销自己的服务。
类似注册机制,在注册中心接受到cancel请求后,首先删除服务信息,然后更新(剔除服务)阈值,最后同步该事件到其他Eureka Server节点。
4.4、服务剔除机制——Eviction
服务剔除包括三个步骤,首先判断是否满足服务剔除的条件,然后找出过期的服务,最后执行服务的剔除。
判断是否满足服务剔除的条件——主要看Eureka是否开启自我保护机制
关闭自我保护机制的情况下,直接进入下一步;
开启自我保护机制的情况下,只有实际续约数大于自我保护阈值(说明大部分服务可用,判断Client出了问题,反之判断为Server出了问题),才会进入服务剔除流程。
自我保护阈值=服务总数*(60s / 客户端续约间隔)*自我保护阈值因子。
找出过期的服务
遍历所有服务,判断最近续约时间距离当前时间差值大于设定的阈值,就标记为过期,并将所有过期的服务保存到集合中。
剔除服务
遍历所有过期服务,通过洗牌算法确保每次都公平地选择出要剔除的服务,最后进行剔除。
4.5、拉取服务——Fetch Registries
由服务消费者调用,发现注册中心同名服务列表。
通常服务提供者地址信息列表会在本地缓存一份,默认每30s更新一次缓存信息。
4.6、服务同步——Replicate
用来实现Server之间的服务信息同步,服务提供者仅需在一个server注册就可在server集群中更新信息。
接受注册请求的节点将请求再次转发到其他的server节点,调用接口和参数等完全相同,除了标记isReplicate=true,避免重复的同步Replicate。
信息同步为异步方式完成,所有不保证节点信息的强一致性,只能保证最终一致。
4.7、新节点的加入(New Server Initialize)
新的server节点加入,启动时将自己作为消费者,拉取所有已注册信息,然后将每个服务注册到自身节点,标记isReplicate=true,完成初始化。
4.8、存储结构——两层Hash Map
二、服务注册中心的选择
1、CAP原则
CAP定理:在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance),三者不可同时获得。
- 一致性:在分布式系统中,所有数据备份在同一时刻是否为同样的值
- 可用性:集群中一部分节点故障后,该集群还能否正常响应客户端的读写请求
- 分区容错性:一个节点故障挂掉后,不影响其他节点的正常使用,即高可用性
CAP理论说明在一个分布式系统中,最多同时实现其中的两点,而由于当前的网络硬件必然会出现延迟丢包的问题,所以分区容错性是必要的,所以只能在一致性和可用性中间权衡。
2、注册中心的选择
目前主流的注册中心有Eureka、Zookeeper、consul等,各自的侧重点不同。
2.1、Zookeeper保证CP
Zookeeper在master节点因为网络故障而与其他节点失去联系后,剩余节点会重新进行leader节点的选举,而在选举期间(30~120s),整个zk集群都是不可用的,虽然服务最终也能恢复,但是漫长的选举时间导致注册中心经常长期不可用却是不能容忍的。
2.2、Consul保证CP
Consul强一致性带来的是:服务注册要比Eureka慢,因为consul的raft协议要求必须过半数的节点都写入成功才认为是注册成功;当Leader节点挂掉后,重新选举期间整个consul不可用,牺牲了集群的可用性。
2.3、Eureka保证AP
Eureka在设计时优先保证可用性,其中各个节点都是平等的,几个节点挂掉,剩余节点仍能提供注册和查询服务,只要有一台Eureka还在,就能保证服务可用(保证可用性),但不保证查询到的信息时最新的(不保证强一致性);除此之外,由于自我保护机制不再剔除服务实例的节点,仍能接受新服务的注册和查询请求,但不会被同步到其他节点上。
同时,Eureka还提供客户端(Eureka客户端程序负责向外提供注册与发现服务接口)缓存功能,所以极端情况下,客户端无法访问任何一个Server节点,消费者仍能通过客户端查询与获取注册服务信息。
因此,Eureka可以很好地应对因网络故障导致部分节点失联的情况,尽可能地保证整个集群的可用性。
三、demo工程的搭建
新建maven工程,删除src文件夹,作为项目运行环境。
1、server节点
新建module名为Server_1,为标准的SpringBoot工程,其目录结构如下。
在pom文件中添加Eureka-server和Eureka-client依赖。
启动类上添加@EnableEurekaServer注解,标注为Eureka Server节点。
在配置文件中增加相关配置信息。
server.port暴露服务端口。
spring.application.name为应用名,应用名为SpringCloud中服务调用的依据。(注:应用名中不可有下划线,否则会调用失败。)
eureka属性有server、client、instance等部分;
client.register-with-eureka为是否将应用注册到eureka,默认为true,单点server节点指定为false
client.fetch-registry为是否从注册中心拉取已注册信息,默认为true,单点server节点指定为false
client.service-url.defaultZone指定注册中心地址
启动应用,访问localhost:1001/即可看到eureka注册中心服务管理界面。
2、服务提供者——Hello_1
新建module名为Hello_1,SpringBoot工程,提供常规的SpringMVC访问控制,目录结构如下。
添加Eureka-server和Eureka-client依赖。
启动类添加@EnableDiscoveryClient注解,使用@EnableEurekaClient效果一样。
service和controller包中提供MVC的服务实现与访问控制功能。
在配置文件application.yml中,添加相关配置,将该应用注册到之前的Eureka Server节点,注册名为hello-app。
启动应用后,访问localhost:1001/即可看到刚注册的应用。
3、服务消费者——Introduce
继续新建module名为Introduce,调用hello-app服务。
目录结构仍为SpringBoot工程,实现SpringMVC访问控制。
除了添加Eureka的依赖外,添加Ribbon依赖(客户端负载均衡,后续要用到)。
启动类上加入@EnableEurekaClient注解,并注入RestTemplate Bean,添加@LoadBalanced注解,表明开启负载均衡功能。
controller包内提供访问控制。
service包内提供服务的具体实现,此处为对hello-app应用的远程调用,通过构造url,利用RestTemplate发起RestFul请求。
访问的URL构造为http://${请求应用名}/${请求路径}?${可能的参数列表}。
不要忘记在配置文件中增加相关配置信息。
启动应用后,访问localhost:1001/查看应用注册情况,访问localhost:8001/intro?name=world查看服务调用结果。
至此,完成了一个单点EurekaServer环境的搭建,并在其上注册两个应用,实现服务间的远程调用。