1. 概述
1.1 Eureka 是什么
Eureka 是 Netflix 的一个子模块,也是核心模块之一。Eureka 是一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。服务注册与发现对于微服务架构来说是非常重要的,有了服务与发现与注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件了。功能类似于 dubbo 的注册中心,比如 zookeeper。
1.2 Eureka 组件
1.2.1 Eureka Server
各个节点启动后,会在 EurekaServer 中进行注册,这样 EurekaServer 中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到
1.2.2 Eureka Client
EurekaClient 是一个 Java 客户端,用于简化 EurekaServer 的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会在 EurekaServer 发送心跳(默认周期为 30 秒)。如果 EurekaServer 在多个心跳周期内没有接收到某个节点的心跳,EurekaServer 将会从服务注册中把这个节点移除(默认 90 秒)
1.3 Eureka 自我保护机制
1.3.1 自我保护机制
默认情况下,EurekaServer 在 90 秒内没有检测到服务列表中的某微服务,则会自动将该微服务从服务列表中删除。但很多情况下并不是该微服务节点(主机)出了问题,而是由于网络抖动等原因使该微服务无法被 EurekaServer 发现,即无法检测到该微服务主机的心跳。若在短暂时间内网络恢复正常,但由于 EurekaServer 的服务列表中已经没有该微服务,所以该微服务已经无法提供服务了。
在短时间内若 EurekaServer 丢失较多微服务,即 EurekaServer 收到的心跳数量小于阈值,为了保证系统的可用性(AP),给那些由于网络抖动而被认为宕机的客户端 “重新复活” 的机会,Eureka 会自动进入自我保护模式:服务列表只可读取、写入,不可执行删除操作。当 EurekaServer 收到的心跳数量恢复到阈值以上时,其会自动退出 Self Preservation 模式。
1.3.2 默认值修改
启动自我保护的阈值因子默认为 0.85,即 85%。即 EurekaServer 收到的心跳数量若小于应该收到数量的 85% 时,会启动自我保护机制,使用 eureka.server.renewal-percent-threshold=0.75
可以更改自我保护机制的阈值
自我保护机制默认是开启的,可以通过修改 EurekaServer 中配置文件来关闭,但不建议关闭,使用 erueka.server.enable-self-preervation=false
禁用自我保护模式。
1.3.3 自我保护机制阈值
Renews threshold:Eureka Server 期待每分钟收到 client 端发送的续约总数,为了计算这个数,系统会统计从当前时刻开始向前的这 15 分钟内接收到的总续约数(一个瞬时值),假设其为 count,然后再根据阈值因子计算出其阈值续约数为 num = count * 0.85
,然后在平均到每分钟即是这个值,Renews threshold = num / 15
Renews (last min):Eureka Server 实际在最后一分钟收到的 client 端发送的续约的数量,其也是一个瞬时值
当 Renews (last min) < Renews threshold 时,会启动自我保护机制
1.4 Eureka VS ZooKeeper
1.4.1 ZooKeeper 保证 CP
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接 down 掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是 zk 会出现这样一种情况,当 master 节点因为网络故障与其他节点失去联系时,剩余节点会重新进行 leader 选举。问题在于,选举 leader 的时间太长,30~120 s,且选举期间整个 ZK 集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得 ZK 集群失去 master 节点是较大概率发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。
1.4.2 Eureka 保证 AP
Eureka 看明白了这一点,因此在设计时就优先保证可用性。Eureka 各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而 Eureka 的客户端在向某个 Eureka 注册时发现连接失败,则会自动切换至其他节点,只要有一台 Eureka 在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka 还有一种自我保护机制,如果在 15 分钟内超过 85% 的节点都没有正常的心跳,那么 Eureka 就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
-
Eureka 不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
-
Eureka 仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
-
当网络稳定时,当前实例新的注册信息会被同步到其它节点上
因此,Eureka 可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像 ZooKeeper 那样使整个注册服务瘫痪。
2. Eureka 环境搭建
2.1 Eureka server 环境搭建
代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见
tutorial-spring-cloud/tutorial-spring-cloud-eureka/tutorial-spring-cloud-eureka-single-7001
工程
2.1.1 配置文件
1. pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2. application.properties
# 端口号
server.port=7001
# 此实例注册到 eureka 服务端的 name
spring.application.name=tutorial-spring-cloud-eureka-single
# 禁用自我保护机制(默认开启)
eureka.server.enable-self-preservation=false
# 设置自我保护机制的阈值,默认是 0.85
eureka.server.renewal-percent-threshold=0.75
# 设置清理间隔(单位:毫秒 默认是 60*1000)
eureka.server.eviction-interval-timer-in-ms=6000
# eureka 服务端的实例名称
eureka.instance.hostname=localhost
# 指定是否向注册中心注册本机,false 表示不注册,因为本身就是 server
eureka.client.register-with-eureka=false
# 指定当前主机是否能够获取注册中心的服务注册列表,server 并不需要去检索服务
eureka.client.fetch-registry=false
# 设置 eureka server 地址
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
2.1.2 代码
1. EurekaApplication7001
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication7001 {
public static void main(String [] args){
SpringApplication.run(EurekaApplication7001.class, args);
}
}
2.2 provider 环境搭建
代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见
tutorial-spring-cloud/tutorial-spring-cloud-provider/tutorial-spring-cloud-eureka-single-7001
工程
2.2.1 配置文件
1. pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2. application.properties
# 端口号
server.port=5002
# 此实例注册到 eureka 服务端的 name
spring.application.name=tutorial-spring-cloud-provider-eureka
# 指定 eureka 服务注册中心地址
eureka.client.service-url.defaultZone=http://localhost:7001/eureka
# 此实例注册到 eureka 服务端的唯一的实例 ID
eureka.instance.instance-id=tutorial-spring-cloud-provider-eureka-5002
# 是否显示 IP 地址
eureka.instance.prefer-ip-address=true
# eureka 客户需要多长时间发送心跳给 eureka 服务器,表明它仍然活着,默认为 30 秒
eureka.instance.lease-renewal-interval-in-seconds=10
# eureka 服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为 90 秒
eureka.instance.lease-expiration-duration-in-seconds=30
# 设置 info 信息
info.app.name=tutorial-spring-cloud-provider-eureka
2.3 consumer 环境搭建
代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见
tutorial-spring-cloud/tutorial-spring-cloud-consumer/tutorial-spring-cloud-consumer-eureka-6002
工程
2.3.1 配置文件
1. pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2. application.properties
# 端口号
server.port=6002
# 此实例注册到 eureka 服务端的 name
spring.application.name=tutorial-spring-cloud-consumer-eureka
# 指定 eureka 服务注册中心地址
eureka.client.service-url.defaultZone=http://localhost:7001/eureka
# 此实例注册到 eureka 服务端的唯一的实例 ID
eureka.instance.instance-id=tutorial-spring-cloud-consumer-eureka-6002
# 是否显示 IP 地址
eureka.instance.prefer-ip-address=true
# eureka 客户需要多长时间发送心跳给 eureka 服务器,表明它仍然活着,默认为 30 秒
eureka.instance.lease-renewal-interval-in-seconds=10
# eureka 服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为 90 秒
eureka.instance.lease-expiration-duration-in-seconds=30
# 设置 info 信息
info.app.name=tutorial-spring-cloud-consumer-eureka
2.3.2 代码
1. ConsumerApplication6002
@SpringBootApplication
public class ConsumerApplication6002 {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication6002.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
2. ConsumerController
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
private static final String SERVICE_PROVIDER = "http://TUTORIAL-SPRING-CLOUD-PROVIDER-EUREKA/";
@RequestMapping("/info")
public Map<String, String> info() {
String url = SERVICE_PROVIDER + "provider/info";
return restTemplate.exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference<Map<String, String>>() {
}).getBody();
}
@RequestMapping("/discovery")
public Map<String, List<String>> discoveryClient() {
Map<String, List<String>> result = new HashMap<>(10);
List<String> serviceIds = discoveryClient.getServices();
serviceIds.forEach(serviceId -> {
List<String> temp = new ArrayList<>();
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
instances.forEach(serviceInstance -> temp.add(serviceInstance.getHost() + ":" + serviceInstance.getPort()));
result.put(serviceId, temp);
});
return result;
}
}
3. Eureka 集群环境搭建
代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见
tutorial-spring-cloud/tutorial-spring-cloud-eureka/tutorial-spring-cloud-eureka-cluster-7002
、tutorial-spring-cloud/tutorial-spring-cloud-eureka/tutorial-spring-cloud-eureka-cluster-7003
、tutorial-spring-cloud/tutorial-spring-cloud-eureka/tutorial-spring-cloud-eureka-cluster-7004
工程
3.1 配置文件
1. 第一台 server 的 application.properties
# 端口号
server.port=7002
# 此实例注册到 eureka 服务端的 name
spring.application.name=tutorial-spring-cloud-eureka-cluster
# eureka 服务端的实例名称
eureka.instance.hostname=eureka-7002.com
# 指定是否向注册中心注册本机,false 表示不注册,因为本身就是 server
eureka.client.register-with-eureka=false
# 指定当前主机是否能够获取注册中心的服务注册列表,server 并不需要去检索服务
eureka.client.fetch-registry=false
# 指定 eureka 服务注册中心地址
eureka.client.service-url.defaultZone=http://eureka-7002.com:7002/eureka,http://eureka-7003.com:7003/eureka,http://eureka-7004.com:7004/eureka
2. 第二台 server 的 application.properties
# 端口号
server.port=7003
# 此实例注册到 eureka 服务端的 name
spring.application.name=tutorial-spring-cloud-eureka-cluster
# eureka 服务端的实例名称
eureka.instance.hostname=eureka-7003.com
# 指定是否向注册中心注册本机,false 表示不注册,因为本身就是 server
eureka.client.register-with-eureka=false
# 指定当前主机是否能够获取注册中心的服务注册列表,server 并不需要去检索服务
eureka.client.fetch-registry=false
# 指定 eureka 服务注册中心地址
eureka.client.service-url.defaultZone=http://eureka-7002.com:7002/eureka,http://eureka-7003.com:7003/eureka,http://eureka-7004.com:7004/eureka
3. 第三台 server 的 application.properties
# 端口号
server.port=7004
# 此实例注册到 eureka 服务端的 name
spring.application.name=tutorial-spring-cloud-eureka-cluster
# eureka 服务端的实例名称
eureka.instance.hostname=eureka-7004.com
# 指定是否向注册中心注册本机,false 表示不注册,因为本身就是 server
eureka.client.register-with-eureka=false
# 指定当前主机是否能够获取注册中心的服务注册列表,server 并不需要去检索服务
eureka.client.fetch-registry=false
# 指定 eureka 服务注册中心地址
eureka.client.service-url.defaultZone=http://eureka-7002.com:7002/eureka,http://eureka-7003.com:7003/eureka,http://eureka-7004.com:7004/eureka