# spring-cloud-study
**Repository Path**: sgyt1024/spring-cloud-study
## Basic Information
- **Project Name**: spring-cloud-study
- **Description**: 已学习为主
使用借鉴:纯洁的微笑(https://gitee.com/ityouknow/spring-cloud-examples)
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2021-03-12
- **Last Updated**: 2021-11-02
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Spring Cloud Examples
Spring Cloud 使用的各种示例,以最简单、最实用为标准
[Spring Cloud 中文索引](https://github.com/ityouknow/awesome-spring-cloud) | [Spring Boot学习示例代码](https://github.com/ityouknow/spring-boot-examples) | [参与贡献](https://github.com/ityouknow/spring-cloud-examples/issues)
[English](README_EN.md) | **[github地址](https://github.com/ityouknow/spring-cloud-examples)** | **[码云地址](https://gitee.com/ityouknow/spring-cloud-examples)**
Spring Cloud 使用的各种示例,以最简单、最实用为标准
**[Spring Boot 2.0 最全使用教程](https://github.com/ityouknow/spring-boot-leaning)**
- [spring-cloud-eureka](https://github.com/ityouknow/spring-cloud-examples/tree/master/spring-cloud-eureka):eureka server单机、双机、集群示例
- [eureka-producer-consumer](https://github.com/ityouknow/spring-cloud-examples/tree/master/eureka-producer-consumer):利用eureka实现服务提供与调用示例
- [spring-cloud-hystrix](https://github.com/ityouknow/spring-cloud-examples/tree/master/spring-cloud-hystrix):Hystrix熔断的使用示例
- [hystrix-dashboard-turbine](https://github.com/ityouknow/spring-cloud-examples/tree/master/hystrix-dashboard-turbine):熔断监控Hystrix Dashboard和Turbine的示例
- [spring-cloud-config-git](https://github.com/ityouknow/spring-cloud-examples/tree/master/spring-cloud-config-git):配置中心git版本示例
- [spring-cloud-config-svn-refresh](https://github.com/ityouknow/spring-cloud-examples/tree/master/spring-cloud-config-svn-refresh):配置中心svn版本示例,客户端refresh版本示例
- [spring-cloud-config-eureka](https://github.com/ityouknow/spring-cloud-examples/tree/master/spring-cloud-config-eureka):配置中心服务化和高可用代码示例
- [spring-cloud-config-eureka-bus](https://github.com/ityouknow/spring-cloud-examples/tree/master/spring-cloud-config-eureka-bus):配置中心和消息总线示例(配置中心终结版)
- [gateway-service-zuul](https://github.com/ityouknow/spring-cloud-examples/tree/master/gateway-service-zuul):Spring Cloud Zuul使用初级篇 网关 均衡负载
- [spring-cloud-zuul](https://github.com/ityouknow/spring-cloud-examples/tree/master/spring-cloud-zuul):Spring Cloud Zuul使用高级篇 Filter 鉴权 熔断 重试
- [spring-cloud-sleuth-zipkin](https://github.com/ityouknow/spring-cloud-examples/tree/master/spring-cloud-sleuth-zipkin): 利用Sleuth、Zipkin对Spring Cloud应用进行服务追踪分析
- [spring-boot-admin-eureka](https://github.com/ityouknow/spring-cloud-examples/tree/master/spring-boot-admin-eureka): 使用Spring Boot Admin 对Spring Cloud集群进行监控示例
- [spring-cloud-consul](https://github.com/ityouknow/spring-cloud-examples/tree/master/spring-cloud-consul): Spring Cloud 使用 Consul 作为服务中心示例
- [spring-cloud-gateway](https://github.com/ityouknow/spring-cloud-examples/tree/master/spring-cloud-gateway): Spring Cloud 使用 Consul 作为服务中心示例
学习系列:
- [springcloud(一):大话Spring Cloud](http://www.ityouknow.com/springcloud/2017/05/01/simple-springcloud.html)
- [springcloud(二):注册中心Eureka](http://www.ityouknow.com/springcloud/2017/05/10/springcloud-eureka.html)
- [springcloud(三):服务提供与调用](http://www.ityouknow.com/springcloud/2017/05/12/eureka-provider-constomer.html)
- [springcloud(四):熔断器Hystrix](http://www.ityouknow.com/springcloud/2017/05/16/springcloud-hystrix.html)
- [springcloud(五):熔断监控Hystrix Dashboard和Turbine](http://www.ityouknow.com/springcloud/2017/05/18/hystrix-dashboard-turbine.html)
- [springcloud(六):配置中心git示例](http://www.ityouknow.com/springcloud/2017/05/22/springcloud-config-git.html)
- [springcloud(七):配置中心svn示例和refresh](http://www.ityouknow.com/springcloud/2017/05/23/springcloud-config-svn-refresh.html)
- [springcloud(八):配置中心服务化和高可用](http://www.ityouknow.com/springcloud/2017/05/25/springcloud-config-eureka.html)
- [springcloud(九):配置中心和消息总线(配置中心终结版)](http://www.ityouknow.com/springcloud/2017/05/26/springcloud-config-eureka-bus.html)
- [springcloud(十):服务网关zuul](http://www.ityouknow.com/springcloud/2017/06/01/gateway-service-zuul.html)
- [springcloud(十一):服务网关Zuul高级篇](http://www.ityouknow.com/springcloud/2018/01/20/spring-cloud-zuul.html)
- [springcloud(十二):使用Spring Cloud Sleuth和Zipkin进行分布式链路跟踪](http://www.ityouknow.com/springcloud/2018/02/02/spring-cloud-sleuth-zipkin.html)
- [springcloud(十三):Spring Cloud Consul 使用详解](http://www.ityouknow.com/springcloud/2018/07/20/spring-cloud-consul.html)
- [Spring Cloud (十四):Spring Cloud 开源软件都有哪些?](http://www.ityouknow.com/springcloud/2018/08/06/spring-cloud-open-source.html)
- [springcloud(十五):服务网关 Spring Cloud GateWay 初级篇](http://www.ityouknow.com/springcloud/2018/12/12/spring-cloud-gateway.html)
综合篇:
- **[Spring Cloud在国内中小型公司能用起来吗?](http://www.ityouknow.com/springcloud/2017/09/11/can-use-springcloud.html)**
- **[中小型互联网公司微服务实践-经验和教训](http://www.ityouknow.com/springcloud/2017/10/19/micro-service-practice.html)**
- **[从架构演进的角度聊聊Spring Cloud都做了些什么?](http://www.ityouknow.com/springcloud/2017/11/02/framework-and-springcloud.html)**
- **[阿里Dubbo疯狂更新,关Spring Cloud什么事?](http://www.ityouknow.com/springcloud/2017/11/20/dubbo-update-again.html)**
> 如果大家想了解关于springcloud的其它方面应用,也可以以[issues](https://github.com/ityouknow/spring-cloud-examples/issues)的形式反馈给我,我后续来完善。
使用借鉴:纯洁的微笑(https://gitee.com/ityouknow/spring-cloud-examples)
# Eureka
## What
1、Spring-Cloud Euraka介绍
Spring-Cloud Euraka是Spring Cloud集合中一个组件,它是对Euraka的集成,用于服务注册和发现。Eureka是Netflix中的一个开源框架。它和 zookeeper、Consul一样,都是用于服务注册管理的,同样,Spring-Cloud 还集成了Zookeeper和Consul。
在项目中使用Spring Cloud Euraka的原因是***它可以利用Spring Cloud Netfilix中其他的组件,如zull等,因为Euraka是属于Netfilix的。\***
2、Euraka介绍
Eureka由多个instance(服务实例)组成,这些服务实例可以分为两种:Eureka Server和Eureka Client。为了便于理解,我们将Eureka client再分为Service Provider和Service Consumer。
- Eureka Server 提供服务注册和发现
- Service Provider 服务提供方,将自身服务注册到Eureka,从而使服务消费方能够找到
- Service Consumer服务消费方,从Eureka获取注册服务列表,从而能够消费服务
## Why
### 原理

- Eureka客户端(以下简称客户端)启动后,定时向Eureka服务端(以下简称服务端)注册自己的服务信息(服务名、IP、端口等);
- 客户端启动后,定时拉取服务端以保存的服务注册信息;
- 拉取服务端保存的服务注册信息后,就可调用消费其他服务提供者提供的服务。
### 比较
| | Nacos | Eureka | Consul | CoreDNS | Zookeeper |
| --------------- | ----------------------- | ----------- | ----------------- | ---------- | ---------- |
| 一致性协议 | CP+AP | AP | CP | — | CP |
| 健康检查 | TCP/HTTP/MYSQL/Client | Beat Client | TCP/HTTP/gRPC/Cmd | — | Keep Alive |
| 负载均衡策略 | 权重/ metadata/Selector | Ribbon | Fabio | RoundRobin | — |
| 雪崩保护 | 有 | 有 | 无 | 无 | 无 |
| 自动注销实例 | 支持 | 支持 | 不支持 | 不支持 | 支持 |
| 访问协议 | HTTP/DNS | HTTP | HTTP/DNS | DNS | TCP |
| 监听支持 | 支持 | 支持 | 支持 | 不支持 | 支持 |
| 多数据中心 | 支持 | 支持 | 支持 | 不支持 | 不支持 |
| 跨注册中心同步 | 支持 | 不支持 | 支持 | 不支持 | 不支持 |
| SpringCloud集成 | 支持 | 支持 | 支持 | 不支持 | 不支持 |
| Dubbo集成 | 支持 | 不支持 | 不支持 | 不支持 | 支持 |
| K8S集成 | 支持 | 不支持 | 支持 | 支持 | 不支持 |
**Spring Cloud Eureka -> AP**

Spring Cloud Netflix 在设计 Eureka 时就紧遵AP原则(尽管现在2.0发布了,但是由于其闭源的原因 ,但是目前 Ereka 1.x 任然是比较活跃的)。
Eureka Server 也可以运行多个实例来构建集群,解决单点问题,但不同于 ZooKeeper 的选举 leader 的过程,Eureka Server 采用的是Peer to Peer 对等通信。这是一种去中心化的架构,无 master/slave 之分,每一个 Peer 都是对等的。在这种架构风格中,节点通过彼此互相注册来提高可用性,每个节点需要添加一个或多个有效的 serviceUrl 指向其他节点。每个节点都可被视为其他节点的副本。
在集群环境中如果某台 Eureka Server 宕机,Eureka Client 的请求会自动切换到新的 Eureka Server 节点上,当宕机的服务器重新恢复后,Eureka 会再次将其纳入到服务器集群管理之中。当节点开始接受客户端请求时,所有的操作都会在节点间进行复制(replicate To Peer)操作,将请求复制到该 Eureka Server 当前所知的其它所有节点中。
当一个新的 Eureka Server 节点启动后,会首先尝试从邻近节点获取所有注册列表信息,并完成初始化。Eureka Server 通过 getEurekaServiceUrls() 方法获取所有的节点,并且会通过心跳契约的方式定期更新。
默认情况下,如果 Eureka Server 在一定时间内没有接收到某个服务实例的心跳(默认周期为30秒),Eureka Server 将会注销该实例(默认为90秒, eureka.instance.lease-expiration-duration-in-seconds 进行自定义配置)。
当 Eureka Server 节点在短时间内丢失过多的心跳时,那么这个节点就会进入自我保护模式。
Eureka的集群中,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
Eureka不再从注册表中移除因为长时间没有收到心跳而过期的服务;
Eureka仍然能够接受新服务注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用);
当网络稳定时,当前实例新注册的信息会被同步到其它节点中;
因此,Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使得整个注册服务瘫痪。
## How
### 客户端注册
##### 项目maven依赖
```
4.0.0
com.neo
spring-cloud-consumer-hystrix
0.0.1-SNAPSHOT
jar
spring-cloud-consumer-hystrix
Demo project for Spring cloud consumer hystrix
org.springframework.boot
spring-boot-starter-parent
1.5.3.RELEASE
UTF-8
UTF-8
1.8
Dalston.RELEASE
org.springframework.cloud
spring-cloud-starter-feign
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
```
##### 项目启动器
```
package com.neo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
```
##### 项目配置
```
spring.application.name=spring-cloud-consumer-hystrix
server.port=9001
feign.hystrix.enabled=true
eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka/
```
### 高可用方案
#### 1.修改host文件
##### window环境
hosts文件位置:C:\windows\system32\drivers\etc
刷新方式:
win+r,输入CMD,回车
在命令行执行:
ipconfig /flushdns #清除DNS缓存内容。
ipconfig /displaydns //显示DNS缓存内容
##### linux环境
文件位置:/etc/hosts
刷新命令:systemctl restart nscd
在`hosts`文件中添加对peer1和peer2的转换
```
127.0.0.1 peer1
127.0.0.1 peer2
127.0.0.1 peer3
```
#### 2.项目配置
##### 项目maven依赖
```
4.0.0
com.neo
spring-cloud-eureka-cluster
0.0.1-SNAPSHOT
jar
spring-cloud-eureka-cluster
Demo project for Spring cloud eureka
org.springframework.boot
spring-boot-starter-parent
1.5.3.RELEASE
UTF-8
UTF-8
1.8
Dalston.RELEASE
org.springframework.cloud
spring-cloud-starter
org.springframework.cloud
spring-cloud-starter-eureka-server
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
```
##### 项目启动器
```
package com.neo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class SpringCloudEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudEurekaApplication.class, args);
}
}
```
##### 项目配置
创建peer1服务中心的配置,创建`application.yml`,并将serviceUrl指向peer2,peer3
```
spring:
application:
name: spring-cloud-eureka
server:
port: 8000
eureka:
instance:
hostname: peer1
client:
serviceUrl:
defaultZone: http://peer2:8001/eureka/,http://peer3:8002/eureka/
```
创建peer2服务中心的配置,创建`application.yml`,并将serviceUrl指向peer1,peer3
```
spring:
application:
name: spring-cloud-eureka
server:
port: 8001
eureka:
instance:
hostname: peer2
client:
serviceUrl:
defaultZone: http://peer1:8000/eureka/,http://peer3:8002/eureka/
```
创建peer3服务中心的配置,创建`application.yml`,并将serviceUrl指向peer1,peer2
```
spring:
application:
name: spring-cloud-eureka
server:
port: 8002
eureka:
instance:
hostname: peer3
client:
serviceUrl:
defaultZone: http://peer1:8000/eureka/,http://peer2:8001/eureka/
```
分别启动服务

### 控制台参数
#### System Status
Environment: 环境,默认为test,该参数在实际使用过程中,可以不用更改
Data center: 数据中心,使用的是默认的是 “MyOwn”
Current time:当前的系统时间
Uptime:已经运行了多少时间
Lease expiration enabled:是否启用租约过期 ,自我保护机制关闭时,该值默认是true, 自我保护机制开启之后为false。
Renews threshold: 每分钟最少续约数,Eureka Server 期望每分钟收到客户端实例续约的总数。
Renews (last min): 最后一分钟的续约数量(不含当前,1分钟更新一次),Eureka Server 最后 1 分钟收到客户端实例续约的总数。
#### 红字提醒
1. RENEWALS ARE LESSER THAN THE THRESHOLD. THE SELF PRESERVATION MODE IS TURNED OFF.THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS:自我保护机制关闭
2. EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE
NOT BEING EXPIRED JUST TO BE SAFE:自我保护机制触发
3. THE SELF PRESERVATION MODE IS TURNED OFF.THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS:自我保护机制关闭状态下,续约客户端数量未达到阈值
#### DS Replicas
集群中的其他节点
#### General Info
1. total-avail-memory:总共可用内存
2. num-of-cpus:cpu个数
3. current-memory-usage:当前已用内存的百分比
4. registered-replicas:集群中的其他节点
5. unavailable-replicas:集群中不可用的节点。如果注册中心本身不注册的话,为不可用,即register-with-eureka=true会显示在这里
6. available-replicas:集群中可用的节点
### 配置参数
| 配置参数 | 默认值 | 说明 |
| ------------------------------------------------------- | ------- | ------------------------------------------------------------ |
| 服务注册中心配置 | | Bean类:org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean |
| eureka.server.enable-self-preservation | false | 关闭注册中心的保护机制,Eureka 会统计15分钟之内心跳失败的比例低于85%将会触发保护机制,不剔除服务提供者,如果关闭服务注册中心将不可用的实例正确剔除 |
| eureka.server.renewal-percent-threshold | | 我保护模式阈值,默认0.85 |
| eureka.server.eviction-interval-timer-in-ms | | 清理注册表的时间间隔,单位ms,默认60 000 |
| eureka.server.use-read-only-response-cache | | 是否开启三级缓存 |
| eureka.server.response-cache-update-interval-ms | | 三级缓存从二级缓存的同步间隔,单位ms,默认30 000 |
| eureka.server.response-cache-auto-expiration-in-seconds | | 二级缓存生效时间,单位s,默认180 |
| 服务实例类配置 | | Bean类:org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean |
| eureka.instance.prefer-ip-address | false | 不使用主机名来定义注册中心的地址,而使用IP地址的形式,如果设置了eureka.instance.ip-address 属性,则使用该属性配置的IP,否则自动获取除环路IP外的第一个IP地址 |
| eureka.instance.ip-address | | IP地址 |
| eureka.instance.hostname | | 设置当前实例的主机名称 |
| eureka.instance.appname | | 服务名,默认取 spring.application.name 配置值,如果没有则为 unknown |
| eureka.instance.lease-renewal-interval-in-seconds | 30 | 定义服务续约任务(心跳)的调用间隔,单位:秒 |
| eureka.instance.lease-expiration-duration-in-seconds | 90 | 定义服务失效的时间,单位:秒 |
| eureka.instance.status-page-url-path | /info | 状态页面的URL,相对路径,默认使用 HTTP 访问,如果需要使用 HTTPS则需要使用绝对路径配置 |
| eureka.instance.status-page-url | | 状态页面的URL,绝对路径 |
| eureka.instance.health-check-url-path | /health | 健康检查页面的URL,相对路径,默认使用 HTTP 访问,如果需要使用 HTTPS则需要使用绝对路径配置 |
| eureka.instance.health-check-url | | 健康检查页面的URL,绝对路径 |
| 服务注册类配置 | | Bean类:org.springframework.cloud.netflix.eureka.EurekaClientConfigBean |
| eureka.client.service-url.defaultZone | | 指定服务注册中心地址,类型为 HashMap,并设置有一组默认值,默认的Key为 defaultZone;默认的Value为 http://localhost:8761/eureka ,如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。如果服务注册中心加入了安全验证,这里配置的地址格式为: http://%3Cusername%3E:%3Cpassword%3E@localhost:8761/eureka 其中 为安全校验的用户名; 为该用户的密码 |
| eureka.client.fetch-registery | true | 检索服务 |
| eureka.client.registery-fetch-interval-seconds | 30 | 从Eureka服务器端获取注册信息的间隔时间,单位:秒 |
| eureka.client.register-with-eureka | true | 启动服务注册 |
| eureka.client.eureka-server-connect-timeout-seconds | 5 | 连接 Eureka Server 的超时时间,单位:秒 |
| eureka.client.eureka-server-read-timeout-seconds | 8 | 读取 Eureka Server 信息的超时时间,单位:秒 |
| eureka.client.filter-only-up-instances | true | 获取实例时是否过滤,只保留UP状态的实例 |
| eureka.client.eureka-connection-idle-timeout-seconds | 30 | Eureka 服务端连接空闲关闭时间,单位:秒 |
| eureka.client.eureka-server-total-connections | 200 | 从Eureka 客户端到所有Eureka服务端的连接总数 |
| eureka.client.eureka-server-total-connections-per-host | 50 | 从Eureka客户端到每个Eureka服务主机的连接总数 |
| 图显示 | | |
| eureka.dashboard.enabled | | 是否开启web控制台,默认true |
| eureka.dashboard.path | | web控制台访问路径,默认/ |
### 自我保护
#### 概念
如果Eureka服务器检测到数量超过预期数量的注册客户端已以不正当的方式终止了他们的连接,并且同时正等待逐出,则它们将进入自我保存模式。这样做是为了确保灾难性的网络事件不会清除eureka注册表数据,并将其传播到下游的所有客户端。
#### 说明
假设A和B两个client之间网络正常,但是A与server之间网络异常,如果server发现A心跳超时后立即移除A的注册信息,那么B拉取最新的注册表后就无法调用A的服务,但实际上AB之间网络是正常的。
#### 触发条件
为了更好地了解自我保护,首先了解Eureka客户如何“结束”他们的注册生命周期将很有帮助。Eureka协议要求客户端永久离开时执行明确的注销操作。例如,在提供的Java客户端中,这是通过shutdown()方法完成的。连续3次心跳续订失败的所有客户端将被视为不正常的终止,并且将由后台驱逐过程逐出。只有当当前注册表的> 15%处于此更高状态时,才会启用自我保存。
处于自我保留模式时,eureka服务器将停止逐出所有实例,直到发生以下任何一种情况:
1. 它看到的心跳续订次数又回到了预期的阈值之上,或者
2. 自我保护功能已禁用(请参见下文)
默认情况下会启用自我保留,并且启用自我保留的默认阈值>当前注册表大小的15%。
**期望每分钟收到的心跳数量 = 实例数量*2**,因为默认每30s发送一次心跳
**触发阈值 = 期望 * 0.85**,0.85为默认阈值因子。
当 **上一分钟收到的心跳数量 < 触发阈值** 时触发自我保护,0.85为默认阈值因子
在新服务注册时、服务退出时、每隔15分钟,会重新计算一次触发阈值
```java
// 更新阈值
private void updateRenewalThreshold() {
try {
Applications apps = eurekaClient.getApplications();
// 实例数量
int count = 0;
// 遍历服务(应用)
for (Application app : apps.getRegisteredApplications()) {
// 遍历服务的每个实例
for (InstanceInfo instance : app.getInstances()) {
if (this.isRegisterable(instance)) {
++count;
}
}
}
synchronized (lock) {
// 如果 当前实例数量 > 阈值因子 * 更新前的实例数量 || 自我保护模式关闭
// PS:不懂为什么要这么判断???
if ((count) > (serverConfig.getRenewalPercentThreshold() * expectedNumberOfClientsSendingRenews)
|| (!this.isSelfPreservationModeEnabled())) {
// 用新的实例数量覆盖
this.expectedNumberOfClientsSendingRenews = count;
// 计算并更新阈值
updateRenewsPerMinThreshold();
}
}
logger.info("Current renewal threshold is : {}", numberOfRenewsPerMinThreshold);
} catch (Throwable e) {
logger.error("Cannot update renewal threshold", e);
}
}
// 计算并更新阈值
protected void updateRenewsPerMinThreshold() {
// numberOfRenewsPerMinThreshold为阈值
// expectedNumberOfClientsSendingRenews为已注册的实例数量
// getExpectedClientRenewalIntervalSeconds()获取配置的心跳间隔
// getRenewalPercentThreshold()获取配置的阈值因子
this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
* (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
* serverConfig.getRenewalPercentThreshold());
}
```
#### 阈值配置
要在示例中更改自我保存阈值,请设置属性:`eureka.renewalPercentThreshold=[0.0, 1.0]`。
#### 注册表三级缓存
1. 缓存有三个:registry一级缓存,readWriteCacheMap二级缓存,readOnlyCacheMap三级缓存
+ registry:类型为ConcurrentHashMap。注册信息变动时直接修改,实时同步到readWriteCacheMap
+ readWriteCacheMap:类型为LoadingCache,使用guava提供的CacheLoader。缓存有效期为180s
+ readOnlyCacheMap:类型为ConcurrentHashMap。每隔30s从readWriteCacheMap读取配置
2. 获取注册表时,先从readOnlyCacheMap中获取,没有再从readWriteCacheMap获取,没有再从registry获取
3. 建议关闭三级缓存,并缩短清理注册表间隔、心跳间隔等,以增强一致性
### REST操作
**appID**是应用程序的名称,**instanceID**是与**实例**关联的唯一ID。在AWS云中,instanceID是**实例的实例ID**,在其他数据中心中,instanceID是**实例**的**主机名**。
对于JSON / XML,提供的内容类型必须为**application / xml**或**application / json**。
| **手术** | **HTTP动作** | **描述** |
| ------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 注册新的应用程序实例 | POST / eureka / v2 / apps / **appID** | 输入:JSON / XML有效负载HTTP代码:成功时输入204 |
| 注销应用程序实例 | 删除/ eureka / v2 / apps / **appID** / **instanceID** | HTTP代码:成功200 |
| 发送应用实例心跳 | PUT / eureka / v2 / apps / **appID** / **instanceID** | HTTP代码: * 200表示成功 * 404(如果**instanceID**不存在) |
| 查询所有实例 | GET / eureka / v2 / apps | HTTP代码:成功时输出200:输出:JSON / XML |
| 查询所有**appID**实例 | GET / eureka / v2 / apps / **appID** | HTTP代码:成功时输出200:输出:JSON / XML |
| 查询特定的**appID** / **instanceID** | GET / eureka / v2 / apps / **appID** / **instanceID** | HTTP代码:成功时输出200:输出:JSON / XML |
| 查询特定的**instanceID** | GET / eureka / v2 / instances / **instanceID** | HTTP代码:成功时输出200:输出:JSON / XML |
| 使实例停止服务 | PUT / eureka / v2 / apps / **appID** / **instanceID** / status?value = OUT_OF_SERVICE | HTTP代码: * 200表示成功 * 500表示失败 |
| 将实例移回服务中(删除替代) | DELETE / eureka / v2 / apps / **appID** / **instanceID** / status?value = UP(该值= UP是可选的,由于删除了覆盖,它用作后备状态的建议) | HTTP代码: * 200表示成功 * 500表示失败 |
| 更新元数据 | PUT / eureka / v2 / apps / **appID** / **instanceID** /元数据?key = value | HTTP代码: * 200表示成功 * 500表示失败 |
| 查询特定**VIP地址**下的所有实例 | GET / eureka / v2 / vips / **vipAddress** | * HTTP代码:成功时输出200:输出:JSON / XML * 404(如果**vipAddress**不存在)。 |
| 查询特定**安全VIP地址**下的所有实例 | GET / eureka / v2 / svips / **svipAddress** | * HTTP代码:成功时输出200:输出:JSON / XML * 404(如果**svipAddress**不存在)。 |
注册时,您需要发布符合此XSD的XML(或JSON)正文:
```
<?xml版本= “ 1.0 ”编码= “ UTF-8 ”?>
< xsd :模式 xmlns :xsd = “ http://www.w3.org/2001/XMLSchema ” elementFormDefault = “合格” attributeFormDefault = “不合格” >
< xsd :元素 名称= “实例” >
< xsd :complexType >
< xsd :all >
<!- ec2中的
主机名应该是公共dns名称,在ec2中,公共dns名称将始终解析为其私有IP- >
< xsd :元素 名称= “ hostName ” type = “ xsd:string ” />
< xsd :元素 名称= “ app ” 类型= “ xsd:string ” />
< xsd :元素 名称= “ ipAddr ” 类型= “ xsd:string ” />
< xsd :元素 名称= “ vipAddress ” 类型= “ xsd:string ” />
< xsd :元素 名称= “ secureVipAddress ” 类型= “ xsd:string ” />
< xsd :元素 名称= “ status ” 类型= “ statusType ” />
< xsd :元素 名称= “端口” 类型= “ xsd:positiveInteger ” minOccurs = “ 0 ” />
< xsd :元素 名称= “ securePort ” 类型= “ xsd:positiveInteger ” />
< xsd :元素 名称= “ homePageUrl ” type = “ xsd:string ” />
< xsd :元素 名称= “ statusPageUrl ” type = “ xsd:string ” />
< xsd :元素 名称= “ healthCheckUrl ” type = “ xsd:string ” />
< xsd :元素 ref = “ dataCenterInfo ” minOccurs = “ 1 ” maxOccurs = “ 1 ” />
<!-可选->
< xsd :元素 ref = “ leaseInfo ” minOccurs = “ 0 ” />
<!-可选应用特定的元数据->
< xsd :元素 名称= “元数据” 类型= “ appMetadataType ” minOccurs = “ 0 ” />
xsd :所有>
xsd :complexType >
xsd :元素>
< xsd :元素 名称= “ dataCenterInfo ” >
< xsd :complexType >
< xsd :全部>
< xsd :元素 名称= “名称” 类型= “ dcNameType ” />
<!-仅当名称为Amazon时才需要元数据->
< xsd :元素 名称= “元数据” 类型= “ amazonMetdataType ” minOccurs = “ 0 ” />
xsd :所有>
xsd :complexType >
xsd :元素>
< xsd :元素 名称= “ leaseInfo ” >
< xsd :complexType >
< xsd :全部>
<!-(可选),如果您想更改租约的长度-默认值为90秒->
< xsd :元素 名称= “ evictionDurationInSecs ” minOccurs = “ 0 ” type = “ xsd:positiveInteger ” />
xsd :所有>
xsd :complexType >
xsd :元素>
< xsd :simpleType 名称= “ dcNameType ” >
<!-使用'枚举'将值限制为一组值->
< xsd :限制基= “ xsd:string ” >
< xsd :枚举值= “ MyOwn ” />
< xsd :枚举值= “ Amazon ” />
xsd :限制>
xsd :simpleType >
< xsd :simpleType 名称= “ statusType ” >
<!-使用'枚举'将值限制为一组值->
< xsd :限制基= “ xsd:string ” >
< xsd :枚举值= “ UP ” />
< xsd :枚举值= “ DOWN ” />
< xsd :枚举值= “ STARTING ” />
< xsd :枚举值= “ OUT_OF_SERVICE ” />
< xsd :枚举值= “ UNKNOWN ” />
xsd :限制>
xsd :simpleType >
< xsd :complexType 名称= “ amazonMetdataType ” >
<!-来自 http://docs.amazonwebservices.com/AWSEC2/latest/DeveloperGuide/index.html?AESDG-chapter-instancedata.html ->
< xsd :所有>
< xsd :元素 名称= “ ami-launch-index ” 类型= “ xsd:string ” />
< xsd :元素 名称= “ local-hostname ” type = “ xsd:string ” />
< xsd :元素 名称= “可用区” type = “ xsd:string ” />
< xsd :元素 名称= “实例ID ” type = “ xsd:string ” />
< xsd :元素 名称= “ public-ipv4 ” type = “ xsd:string ” />
< xsd :元素 名称= “公共主机名” type = “ xsd:string ” />
< xsd :元素 名称= “ ami-manifest-path ” type = “ xsd:string ” />
< xsd :元素 名称= “ local-ipv4 ” type = “ xsd:string ” />
< xsd :元素 名称= “主机名” type = “ xsd:string ” />
< xsd :元素 名称= “ ami-id ” 类型= “ xsd:string ” />
< xsd :元素 名称= “实例类型” type = “ xsd:string ” />
xsd :所有>
xsd :complexType >
< xsd :complexType 名称= “ appMetadataType ” >
< xsd :序列>
<!-这是可选的应用程序特定名称,值元数据->
< xsd :任何 minOccurs = “ 0 ” maxOccurs = “ unbounded ” processContents = “ skip ” />
xsd :序列>
xsd :complexType >
xsd :模式>
```
**更新**
例如:PUT / eureka / v2 / apps / MYAPP / i-6589ef6
响应:
状态:200(成功)
404(Eureka不了解您,请先注册自己)
500(失败)
**取消**
(如果Eureka没有从evictionDurationInSecs内的服务节点获取心跳,则该节点将自动注销)
例如:DELETE / eureka / v2 / apps / MYAPP / i-6589ef6
响应:
状态:200(成功)
500(失败)
### 其他
1. client**续约**时如果发现server的注册表中没有自己(续约请求返回404),此时会**重新发起注册**。并不是只有启动时才会发起注册,因此client可以比server先启动
2. server端将注册表存储在**本地内存**中,类型为ConcurrentHashMap>>,其中key为服务名,value为服务的多个实例组成的map。实例map中key为实例id,value为实例信息。实例信息是一个泛型对象,其中InstanceInfo保存实例的基本信息,如ip、端口,Lease保存实例的注册信息,如心跳、状态。
### 示例代码下载地址
纯洁的微笑
https://gitee.com/ityouknow/spring-cloud-examples
### 问题:
#### 1.客户端启动时如何注册到服务端
##### 源码分析:
Eureka客户端在启动后,会创建一些定时任务,其中就有一个任务heartbeatExecutor就是就是处理心跳的线程池,部分源码(源码位置:com.netflix.discovery.DiscoveryClient)如下:
```
heartbeatExecutor = new ThreadPoolExecutor(
1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
.setDaemon(true)
.build()
); // use direct handoff
...此处省略其他代码
//finally, init the schedule tasks (e.g. cluster resolvers, heartbeat, instanceInfo replicator, fetch
initScheduledTasks();
```
查看方法initScheduledTasks以及注释,可知该方法是初始化所有的任务(schedule tasks)。
```
/**
*Initializes all scheduled tasks.
*/
private void initScheduledTasks() {
...
// Heartbeat timer
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
...
}
```
在上述方法中,第15行创建了一个线程HeartbeatThread,该线程就是处理心跳任务:
```
/**
*The heartbeat task that renews the lease in the given intervals.
*/
private class HeartbeatThread implements Runnable {
public void run() {
if (renew()) {
lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
}
}
}
/**
*Renew with the eureka service by making the appropriate REST call
*/
boolean renew() {
EurekaHttpResponse httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
logger.debug(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode());
if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
REREGISTER_COUNTER.increment();
logger.info(PREFIX + "{} - Re-registering apps/{}", appPathIdentifier, instanceInfo.getAppName());
long timestamp = instanceInfo.setIsDirtyWithTime();
boolean success = register();
if (success) {
instanceInfo.unsetIsDirty(timestamp);
}
return success;
}
return httpResponse.getStatusCode() == Status.OK.getStatusCode();
} catch (Throwable e) {
logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
return false;
}
}
```
在renew方法中,首先会发送一个心跳数据到服务端,服务端返回一个状态码,如果是NOT_FOUND(即404),表示Eureka服务端不存在该客户端的服务信息,那么就会向服务端发起注册请求(上面代码25行调用register方法):
```java
boolean register() throws Throwable {
logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
EurekaHttpResponse httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
} catch (Exception e) {
logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e);
throw e;
}
if (logger.isInfoEnabled()) {
logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode());
}
return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode();
}
```
在register方法中,向服务端的注册信息instanceInfo,它是com.netflix.appinfo.InstanceInfo,包括服务名、ip、端口、唯一实例ID等信息。
##### 总结
Eureka客户端在启动时,首先会创建一个心跳的定时任务,定时向服务端发送心跳信息,服务端会对客户端心跳做出响应,如果响应状态码为404时,表示服务端没有该客户端的服务信息,那么客户端则会向服务端发送注册请求,注册信息包括服务名、ip、端口、唯一实例ID等信息。
#### 2.服务端如何保存客户端服务信息
##### 源码分析
服务端注册源码(com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl.class的方法register)如下:
```
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
super.register(info, leaseDuration, isReplication);
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
```
第7行调用了父类(com.netflix.eureka.registry.AbstractInstanceRegistry)register方法,源码如下:
```
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
...
private final ConcurrentHashMap>> registry
= new ConcurrentHashMap>>();
...
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
read.lock();
Map> gMap = registry.get(registrant.getAppName());
REGISTER.increment(isReplication);
if (gMap == null) {
final ConcurrentHashMap> gNewMap = new ConcurrentHashMap>();
gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
if (gMap == null) {
gMap = gNewMap;
}
}
...
}
}
```
在register方法中,我们可以看到将服务实例信息InstanceInfo注册到了register变量中,它其实就是一个ConcurrentHashMap。
##### 总结
客户端通过Jersey框架(亚马逊的一个http框架)将服务实例信息发送到服务端,服务端将客户端信息放在一个ConcurrentHashMap对象中。
#### 3.客户端如何拉取服务端已保存的服务信息
##### 源码分析:
com.netflix.discovery.DiscoveryClient.class 中 initScheduledTasks方法中:
```
private void initScheduledTasks() {
if (clientConfig.shouldFetchRegistry()) {
// registry cache refresh timer
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread()
),
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
...
}
```
上述代码中初始化了一个刷新缓存的定时任务,我们看到第14行的新建了一个线程CacheRefreshThread(源码不再列出),既是用来定时刷新服务端已保存的服务信息。
##### 总结:
通过3.1节源码总结:客户端拉取服务端服务信息是通过一个定时任务定时拉取的,每次拉取后刷新本地已保存的信息,需要使用时直接从本地直接获取。
#### 4.如何构建高可用的Eureka集群
首先,搭建一个高可用的Eureka集群,只需要在每个注册中心(服务端)通过配置:
eureka.client.service-url.defaultZone
指定其他服务端的地址,多个使用逗号隔开,如:
eureka.client.service-url.defaultZone=http://localhost:10000/eureka/,http://localhost:10001/eureka/,http://localhost:10002/eureka/
在eureka的高可用状态下,这些注册中心是对等的,他们会互相将注册在自己的实例同步给其他的注册中心,同样是通过问题1的方式将注册在自己上的实例注册到其他注册中心去。
那么问题来了,一旦 其中一个eureka收到一个客户端注册实例时,既然eureka注册中心将注册在自己的实例同步到其他注册中心中的方式和客户端注册的方式相同,那么在接收的eureka注册中心一端,会不会再同步回给注册中心(或者其他注册中心),从而导致死循环。
##### 源码分析:
PeerAwareInstanceRegistryImpl类的register方法,在该方法中的最后一行:
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
replicateToPeers方法字面意思是同步或者复制到同事(即其他对等的注册中心),最后一个参数为isReplication,是一个boolean值,表示是否同步(复制),如果是客户端注册的,那么为false,如果是其他注册中心同步的则为true,replicateToPeers方法中,如果isReplication=false时,将会发起同步(第19行):
```
private void replicateToPeers(Action action, String appName, String id,
InstanceInfo info /* optional */,
InstanceStatus newStatus /* optional */, boolean isReplication) {
Stopwatch tracer = action.getTimer().start();
try {
if (isReplication) {
numberOfReplicationsLastMin.increment();
}
// If it is a replication already, do not replicate again as this will create a poison replication
if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
return;
}
for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
// If the url represents this host, do not replicate to yourself.
if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
continue;
}
replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
}
} finally {
tracer.stop();
}
}
```
##### 总结:
搭建高可用的Eureka集群,只需要在注册中心的配置文件中配置其他注册中心的地址,配置属性如下:
eureka.client.service-url.defaultZone
注册中心收到注册信息后会判断是否是其他注册中心同步的信息还是客户端注册的信息,如果是客户端注册的信息,那么他将会将该客户端信息同步到其他注册中心去;否则收到信息后不作任何操作。通过此机制避免集群中信息同步的死循环。
#### 5.心跳和服务剔除机制是什么
##### 源码分析
在eureka源码中,有个evict(剔除,驱逐,源码位置:com.netflix.eureka.registry.AbstractInstanceRegistry,代码清单5.1)的方法:
```
public void evict(long additionalLeaseMs) {
logger.debug("Running the evict task");
if (!isLeaseExpirationEnabled()) {
logger.debug("DS: lease expiration is currently disabled.");
return;
}
// We collect first all expired items, to evict them in random order. For large eviction sets,
// if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,
// the impact should be evenly distributed across all applications.
List> expiredLeases = new ArrayList<>();
for (Entry>> groupEntry : registry.entrySet()) {
Map> leaseMap = groupEntry.getValue();
if (leaseMap != null) {
for (Entry> leaseEntry : leaseMap.entrySet()) {
Lease lease = leaseEntry.getValue();
if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) {
expiredLeases.add(lease);
}
}
}
}
// To compensate for GC pauses or drifting local time, we need to use current registry size as a base for
// triggering self-preservation. Without that we would wipe out full registry.
int registrySize = (int) getLocalRegistrySize();
int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
int evictionLimit = registrySize - registrySizeThreshold;
int toEvict = Math.min(expiredLeases.size(), evictionLimit);
if (toEvict > 0) {
logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict, expiredLeases.size(), evictionLimit);
Random random = new Random(System.currentTimeMillis());
for (int i = 0; i < toEvict; i++) {
// Pick a random item (Knuth shuffle algorithm)
int next = i + random.nextInt(expiredLeases.size() - i);
Collections.swap(expiredLeases, i, next);
Lease lease = expiredLeases.get(i);
String appName = lease.getHolder().getAppName();
String id = lease.getHolder().getId();
EXPIRED.increment();
logger.warn("DS: Registry: expired lease for {}/{}", appName, id);
internalCancel(appName, id, false);
}
}
}
```
在上述代码第4行,做了isLeaseExpirationEnabled(字面意思:是否启用租约到期,即是否开启了服务过期超时机制,开启之后就会将过期的服务进行剔除)的if判断,源码(com.netflix.eureka.registry
.PeerAwareInstanceRegistryImpl实现类中,代码清单5.2)如下:
```
@Override
public boolean isLeaseExpirationEnabled() {
if (!isSelfPreservationModeEnabled()) {
// The self preservation mode is disabled, hence allowing the instances to expire.
return true;
}
return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;
}
```
同样在上述方法开始的第3行也做了isSelfPreservationModeEnabled方法的判断,该方法是判断是否开启了自我保护机制(有关自我保护机制有关说明在第6节),接下来看到第4行的注释翻译如下:
自保存模式被禁用,因此允许实例过期
也就是说如果关闭了自我保护机制,那么直接就允许实例过期,也就是说可以将过期的服务实例剔除。那如果开启了自我保护机制,会做如下判断(代码清单5.3):
numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold
getNumOfRenewsInLastMin即最后一分钟接收到的心跳总数,numberOfRenewsPerMinThreshold 表示收到一分钟内收到服务心跳数临界值(后简称临界值),也就是说当临界值大于0,且最后一分钟接收到的心跳总数大于临界值时,允许实例过期,他的计算方式源码如下(代码清单5.4):
```
protected void updateRenewsPerMinThreshold() {
this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
* (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
* serverConfig.getRenewalPercentThreshold());
}
```
其中:
this.expectedNumberOfClientsSendingRenews:接收到的客户端数量
serverConfig.getExpectedClientRenewalIntervalSeconds():客户端发送心跳时间的间隔,默认是30秒
serverConfig.getRenewalPercentThreshold():一个百分比率阈值,默认是0.85,可以通过配置修改
从上述代码的计算方法可以看出:
一分钟内收到服务心跳数临界值 = 客户端数量 * (60/心跳时间间隔) * 比率
带入默认值:
一分钟内收到服务心跳数临界值 = 客户端数量 * (60/30) * 0.85
= 客户端数量 * 1.7
所以假如有总共有10个客户端,那么表示一分钟至少需要收到17次心跳。
所以代码清单5.3的解析就是,如果开启只我保护机制,那么一分钟内收到的心跳数大于一分钟内收到服务心跳数临界值时,则启用租约到期机制,即服务剔除机制。
那么最终回到代码清单5.1的第4行的if判断,即如果没有启用服务剔除机制(即开启了自我保护机制或者一分钟收到的心跳数小于临界值),那么直接return结束,不做任何操作。否则代码继续运行,从代码的第9行注释到最后,可以看出先跳出已过期的服务实例,然后通过随机数的方式将这些已过期的实例进行剔除。
##### 总结
心跳机制:
客户端启动后,就会启动一个定时任务,定时向服务端发送心跳数据,告知服务端自己还活着,默认的心跳时间间隔是30秒。
服务剔除机制:
1.如果开启了自我保护机制,那么所有的服务,包括长时间没有收到心跳的服务(即已过期的服务)都不会被剔除;
2.如果未开启自我保护机制,那么将判断最后一分钟收到的心跳数与一分钟收到心跳数临界值(计算方法参考5.1节)比较,如果前者大于后者,且后者大于0的话,则启用服务剔除机制;
3.一旦服务剔除机制开启,则Eureka服务端并不会直接剔除所有已过期的服务,而是通过随机数的方式进行剔除,避免自我保护开启之前将所有的服务(包括正常的服务)给剔除。
#### 6.Eureka自我保护机制是什么
由于在第5节中已经提到了有关Eureka自我保护机制的用途以及它在服务剔除机制中起到的作用,这里不再结合源码分析,这里分析Eureka为什么要采用自我保护机制。
在分布式系统的CAP理论中,Eureka采用的AP,也就是Eureak保证了服务的可用性(A),而舍弃了数据的一致性(C)。当网络发生分区时,客户端和服务端的通讯将会终止,那么服务端在一定的时间内将收不到大部分的客户端的一个心跳,如果这个时候将这些收不到心跳的服务剔除,那可能会将可用的客户端剔除了,这就不符合AP理论。