原创作者:田超凡(程序员田宝宝)
版权所有,引用请注明原作者,严禁复制转载
Part 1 理论部分
1 什么是高可用注册中心?
在微服务中,注册中心非常核心,可以实现服务治理,当服务注册出现故障的时候,可能会导致微服务不能访问,这时候就需要对注册中心实现高可用集群模式。
2 Eureka注册中心高可用实现机制?
Eureka高可用实际上是将自己作为一个服务向其他的服务注册中心中注册自己,这样就可以形成一组相互注册的服务注册中心,从而实现了服务清单的互相同步,达到高可用的效果。
3 Eureka注册中心实现机制?
Eureka注册中心的实现机制包括:服务消费模式和服务注册模式
服务消费模式包括:获取服务和服务下线
获取服务:
当消费者启动的时候,会使用服务别名来生成一个REST请求到服务注册中心中来获取对应的服务信息,然后会缓存到消费者客户端本地的JVM中,同时消费者客户端会每隔30秒到服务注册中心中更新一次。
更新的时间频率可以通过参数设置,参数是eureka.client.registry-fetch-interval-seconds,该参数默认值是30,单位是秒。
服务下线:
在系统运行过程中必然会面临关闭或重启服务某个实例的情况,在服务关闭期,我们希望客户端不再继续调用已经关闭了的服务实例,所以在客户端程序中,当某个服务实例执行正常的关闭操作时,会触发一个服务下线的REST请求到EurekaServer中,告诉注册中心:我要下线了。注册中心接收到该请求后,会将该服务实例的状态设置为DOWN(已下线),然后会把该服务下线事件传播出去。
服务注册模式包括:失效剔除和自我保护。
失效剔除:
有些时候,服务实例并不可以正常下线,可能因为内存溢出、网络故障等问题导致服务不能正常工作,而服务注册中心并未收到服务下线的请求,为了在服务清单中,把这些虽然状态存活,但是实际已无法提供服务的服务实例及时剔除掉,EurekaServer在启动的时候会创建一个定时任务,默认每隔一段时间(默认是60秒)将服务清单中超时(默认是90秒)未续约的服务实例剔除出去。
自我保护:
默认情况下,EurekaClient会定时向EurekaServer发送心跳,如果EurekaServer在一定时间内没有接收到EurekaClient发送的心跳,则会将该实例从注册服务列表中剔除(默认是90秒),但是如果在短时间内丢失大量的实例心跳,EurekaServer会开启自我保护机制,在自我保护机制生效期间,Eureka不会剔除该服务。
Eureka自我保护产生的原因:
在开发测试中,需要频繁重启微服务实例,但是很少会把EurekaServer一起重启,这是因为在开发过程中基本不会修改Eureka注册中心,当一分钟内接收到的心跳数大量减少时,会触发该自我保护机制,可以在Eureka管理界面中看到两个核心参数:ReNews.Threshold和ReNews.Lastmin,当后者(最后一分钟接收到的心跳数)小于前者(心跳阈值),触发该自我保护机制,会出现红色的警告,通过警告我们可以了解,Eureka认为虽然没有接收到实例发送的心跳,但是Eureka认为实例是健康的,Eureka会保护这些服务实例,不会把他们从注册表中删掉。该自我保护机制的目的是避免网络连接故障,因为发生网络故障时,微服务和注册中心不能正常通信,但是服务本身还是健康的,不应该注销该服务。如果Eureka因为网络故障误删掉了该微服务,那后续即使网络恢复了,该微服务也不会重新注册到EurekaServer中了,这是因为,微服务只有在启动的时候才会发送注册请求,在启动之后只会发送心跳和服务列表请求,这样的话,该实例虽然运行着,但是永远无法被其他服务所感知。所以,EurekaServer在短时间内丢失过多的客户端心跳时,会启动自我保护模式,在该模式下,Eureka会保护注册表中的信息,不会再注销任何微服务。当网络恢复后,Eureka会自动退出自我保护模式,自我保护模式可以让集群更加健壮。
但是如果在开发和测试阶段,需要频繁重启发布服务,如果触发了自我保护机制,则旧的服务实例不会被删除,这时请求可能会访问到旧的服务实例,而旧的服务实例已经关闭了,这样就会导致请求错误,影响开发测试。所以,在开发和测试阶段,可以将自我保护模式关闭。只需在EurekaServer配置文件中添加配置即可。
但是在生产环境中,服务实例不会频繁重启,所以,必须把自我保护机制打开,
否则网络一旦中断,就无法恢复。
另外关于Eureka自我保护还有很多个性化的配置,在后续微服务组件源码解读中会再详细进行说明。
另外,还需要注意考虑在网络不可达的情况下,调用接口的幂等、重试、补偿等机制。
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| EMERGENCY!EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT.RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEGING EXPIRED JUST TO BE SAFE. |
4 什么是Consul?
Consul是一个基于Go语言开发的开源的分布式服务发现和配置管理系统,他的优点有很多,包括:
1 基于Raft协议,非常简洁
2 支持健康检查,同时支持HTTP和DNS协议
3 支持跨数据中心的WAN集群
4 提供图形界面
5 跨平台的支持
5 @EnableDiscoveryClient和@EnableEurekaClient的区别?
1 @EnableDiscoveryClient注解依赖于spring-cloud-commons,并且实现在classpath中,适用于consul、zookeeper注册中心。
2 @EnableEurekaClient注解依赖于spring-cloud-netflix,只适用于Eureka注册中心
6 Zookeeper和Eureka的区别?
之前提到,在分布式系统中都需要遵循CAP定律:即同一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)。由于P(分区容错性)是分布式系统必须保证的,所以我们只能在C(一致性)和A(可用性)之间进行权衡,对这两个注册中心而言,zookeeper保证的是CP,eureka保证的是AP。
C:一致性,所有数据一致更新,所有数据更新实时同步
A:可用性,好的响应性能
P:分区容错性,可靠性,所有分布式系统必须保证
Zookeeper保证的是CP:
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟前的注册信息,但是不能接受服务直接宕掉不可用,也就是说,在微服务架构中,服务注册功能对可用性的要求要高于一致性。但是zookeeper可能会出现一种情况,即当leader因为网络故障和其他节点失去联系,剩余的机器会重新开始leader选举,但问题在于,zookeeper选举leader的时间太长,普遍要30-120秒,且在选举期间整个zookeeper集群都是不可用的,这是因为zookeeper要保证每台机器的数据强一致性,这就导致在leader选举期间服务注册功能直接瘫痪。
在云部署环境下,可能因为网络问题导致zookeeper集群丢失leader是大概率会发生的事情,虽然最终服务能够恢复,但是漫长的服务选举导致了服务注册功能长期不可用在微服务理念中是不能容忍的。
Eureka保证的是AP:
Eureka在设计的时候就优先保证可用性,Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点工作,剩余的节点依然可以正常提供注册和查询服务。而Eureka客户端向某个Eureka注册时如果发现连接失败,会自动切换到其他节点。只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果15min内超过85%以上的节点都没有正常心跳,那么Eureka认为客户端和注册中心出现了网络故障,此时会出现以下几种情况:
1 Eureka不再从注册列表中移除因为长时间没有获取到心跳而应该过期的服务
2 Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点(即保证当前节点依然可用)
3 当网络稳定时,当前实例新注册的信息会被同步到其他节点中。
因此,Eureka可以很好地应对因为网络故障导致的部分节点失去联系的情况,而不会像zookeeper那样直接使整个服务注册功能瘫痪。
Part 2 实践部分
Eureka集群环境搭建
Eureka01配置
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ###服务端口号 server: port: 8100 ###eureka 基本信息配置 spring: application: name: eureka-server eureka: instance: ###注册到eurekaip地址 hostname: 127.0.0.1 client: ++serviceUrl++: defaultZone: http://127.0.0.1:8200/eureka/ ###因为自己是为注册中心,不需要自己注册自己 register-with-eureka: true ###因为自己是为注册中心,不需要检索服务 fetch-registry: true |
Eureka02配置
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ###服务端口号 server: port: 8200 ###eureka 基本信息配置 spring: application: name: eureka-server eureka: instance: ###注册到eurekaip地址 hostname: 127.0.0.1 client: ++serviceUrl++: defaultZone: http://127.0.0.1:8100/eureka/ ###因为自己是为注册中心,不需要自己注册自己 register-with-eureka: true ###因为自己是为注册中心,不需要检索服务 fetch-registry: true |
客户端集成Eureka集群
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| server: port: 8000 spring: application: name: app-ittcf-member #eureka: # client: # service-url: # defaultZone: http://localhost:8100/eureka ###集群地址 eureka: client: service-url: defaultZone: http://localhost:8100/eureka,http://localhost:8200/eureka register-with-eureka: true fetch-registry: true |
Maven配置
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> </parent> <!-- 管理依赖 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.M7</version> <type>++pom++ </type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!--SpringCloud ++eureka++-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-++netflix++ -++eureka++-server</artifactId> </dependency> </dependencies> <!-- 注意: 这里必须要添加, 否者各种依赖有问题 --> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/libs-milestone\</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> |
Eureka详解
关闭服务保护
Eureka服务器端配置
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ###服务端口号 server: port: 8100 ##定义服务名称 spring: application: name: app-ittcf-eureka eureka: instance: ###注册中心ip地址 hostname: 127.0.0.1 client: ++serviceUrl++ : ##注册地址 defaultZone: http://${eureka.instance.hostname}:8100/eureka/ ####因为自己是注册中心,是否需要将自己注册给自己的注册中心(集群的时候是需要是为true) register-with-eureka: false ###因为自己是注册中心, 不需要去检索服务信息 fetch-registry: false server: # 测试时关闭自我保护机制,保证不可用服务及时踢出 enable-self-preservation: false eviction-interval-timer-in-ms: 2000 |
核心配置
|----------------------------------------------------------------------------------------------------------------------|
| server: # 测试时关闭自我保护机制,保证不可用服务及时踢出 enable-self-preservation: false ##剔除失效服务间隔 eviction-interval-timer-in-ms: 2000 |
Eureka客户端配置
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ###订单服务的端口号 server: port: 8001 ###服务别名----服务注册到注册中心名称 spring: application: name: app-ittcf-order eureka: client: service-url: ##### 当前用户服务注册到eureka服务地址 # defaultZone: http://localhost:8100/eureka,http://localhost:9100/eureka defaultZone: http://localhost:8100/eureka ### 需要将我的服务注册到eureka上 register-with-eureka: true ####需要检索服务 fetch-registry: true registry-fetch-interval-seconds: 30 # 心跳检测检测与续约时间 # 测试时将值设置设置小些,保证服务关闭后注册中心能及时踢出服务 instance: ###Eureka客户端向服务端发送心跳的时间间隔,单位为秒(客户端告诉服务端自己会按照该规则) lease-renewal-interval-in-seconds: 1 ####Eureka服务端在收到最后一次心跳之后等待的时间上限,单位为秒,超过则剔除(客户端告诉服务端按照此规则等待自己) lease-expiration-duration-in-seconds: 2 |
核心配置
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| # 心跳检测检测与续约时间 # 测试时将值设置设置小些,保证服务关闭后注册中心能及时踢出服务 instance: ###Eureka客户端向服务端发送心跳的时间间隔,单位为秒(客户端告诉服务端自己会按照该规则) lease-renewal-interval-in-seconds: 1 ####Eureka服务端在收到最后一次心跳之后等待的时间上限,单位为秒,超过则剔除(客户端告诉服务端按照此规则等待自己) lease-expiration-duration-in-seconds: 2 |
Eureka闭源了,怎么办?
Eureka闭源了,可以使用其他的注册代替:Consul、Zookeeper
使用Consul来替换Eureka
Consul环境搭建
官方下载地址下载window版,解压得到一个可执行文件。
设置环境变量,让我们直接在cmd里可直接使用consul使命。在path后面添加consul所在目录例如D:\soft\consul_1.1.0_windows_amd64
启动consul命
consul agent -dev -ui -node=cy
-dev开发服务器模式启动,-node结点名为cy,-ui可以用界面访问,默认能访问。
测试访问地址:http://localhost:8500
Consul客户端
Maven依赖信息
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> </parent> <!-- 管理依赖 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.M7</version> <type>++pom++ </type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- SpringBoot整合Web组件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--SpringCloud consul-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> </dependencies> <!-- 注意: 这里必须要添加, 否者各种依赖有问题 --> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/libs-milestone\</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> |
客户端配置文件
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ###eureka 服务端口号 server: port: 8502 spring: application: name: consul-order ####consul注册中心地址 cloud: consul: host: localhost port: 8500 discovery: hostname: 192.168.0.220 |
DiscoveryClient用法
discoveryClient接口 可以获取注册中心上的实例信息。
@EnableDiscoveryClient 开启其他注册中心 比如consul、zookeeper
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @SpringBootApplication @EnableDiscoveryClient public class AppMember { public static void main(String[] args ) { SpringApplication.run(AppMember. class , args ); } } |
获取注册中心上信息
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @RequestMapping("/getServiceUrl") public List<String> getServiceUrl() { List<ServiceInstance> list = discoveryClient.getInstances("zk-member"); List<String> services = new ArrayList<>(); for (ServiceInstance serviceInstance : list) { if (serviceInstance != null ) { services.add(serviceInstance.getUri().toString()); } } return services; } |
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @RestController public class OrderApiController { @Autowired private RestTemplate restTemplate; @Autowired private DiscoveryClient discoveryClient; // 订单服务调用用户服务 @RequestMapping("/getOrder") public String getOrder() { // 有两种方式,一种是采用服务别名方式调用,另一种是直接调用 使用别名去注册中心上获取对应的服务调用地址 String serviceUrl = getServiceUrl("consul-member") + "/getMember"; String result = restTemplate.getForObject(serviceUrl, String.class ); System.out .println("订单服务调用用户服务result:" + result); return result; } public String getServiceUrl(String name) { List<ServiceInstance> list = discoveryClient.getInstances(name); if (list != null && !list.isEmpty()) { return list.get(0).getUri().toString(); } return null ; } ##### } |
使用Zookeeper来替换Eureka
Zookeeper简介
Zookeeper是一个分布式协调工具,可以实现服务注册与发现、注册中心、消息中间件、分布式配置中心等。
环境搭建
启动zk服务器端
Maven依赖信息
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> </parent> <!-- 管理依赖 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.M7</version> <type>++pom++ </type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- SpringBoot整合Web组件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- SpringBoot整合++eureka++客户端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-++zookeeper++-discovery</artifactId> </dependency> </dependencies> <!-- 注意: 这里必须要添加, 否者各种依赖有问题 --> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/libs-milestone\</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> |
application.yml
用户配置文件
|---------------------------------------------------------------------------------------------------------------------------------------------|
| ###订单服务的端口号 server: port: 8002 ###服务别名----服务注册到注册中心名称 spring: application: name: zk-member cloud: zookeeper: connect-string: 127.0.0.1:2181 |
订单配置文件
|--------------------------------------------------------------------------------------------------------------------------------------------|
| ###订单服务的端口号 server: port: 8003 ###服务别名----服务注册到注册中心名称 spring: application: name: zk-order cloud: zookeeper: connect-string: 127.0.0.1:2181 |
启动zk-member服务和zk-order服务,可以发现在zk服务器端上有对应的节点信息
本文部分素材转载自蚂蚁课堂