1 Eureka 是什么
Eureka 是 Spring Cloud Netflix 的一个子模块,也是核心模块之一。Eureka 是一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。
服务注册与发现对于微服务架构来说是非常重要的,有了服务发现与注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件了。功能类似于 dubbo 的注册中心,比如 Zookeeper。
2 基本概念
- Register:服务注册
服务提供者向 Eureka Serve 注册,注册发生在第一次心跳,它提供关于自己的元数据(诸如主机和端口,健康指标 URL 等)Eureka Server。
- Renew:服务续约
Eureka 客户会每隔 30 秒发送一次心跳来续约。通过续约来告知 Eureka Server 该 Eureka 客户仍然存在,没有出现问题。正常情况下,如果 Eureka Server 在 90 秒没有收到 Eureka 客户的续约,它会将实例从其注册表中删除。建议不要更改续约间隔。
注册信息和续订被复制到集群中的 Eureka Serve 所有节点。来自任何区域的 Eureka Client 都可以查找注册表信息(每 30 秒发生一次)。根据这些注册表信息,Application Client 可以远程调用 Applicaton Service 来消费服务
- Fetch Registries:获取注册列表信息
Eureka 客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每 30 秒钟)更新一次。每次返回注册列表信息可能与 Eureka 客户端的缓存信息不同,Eureka 客户端自动处理。
如果由于某种原因导致注册列表信息不能及时匹配,Eureka 客户端则会重新获取整个注册表信息。Eureka 服务器缓存注册列表信息,默认的情况下 Eureka 客户端使用压缩 JSON 格式来获取注册列表的信息。
- Cancel:服务下线
Eureka 客户端在程序关闭时向 Eureka 服务器发送取消请求。发送请求后,该客户端实例信息将从服务器的实例注册表中删除。
- Eviction 服务剔除
Eviction 用来定期(默认为每 60 秒)在 Eureka Server 检测失效的服务,检测标准就是超过一定时间没有 Renew 的服务, 默认失效时间为 90 秒,也就是如果有服务超过 90 秒没有向 Eureka Server 发起 Renew 请求的话,就会被当做失效服务剔除掉, Eureka 服务器会将该服务实例从服务注册列表删除。
3 自我保护模式
- 概述
保护模式主要用于一组客户端和 Eureka Server 之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server 将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。
如果在 Eureka Server 的首页看到以下这段提示,则说明 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 BEING EXPIRED JUST TO BE SAFE
- 何时进入自我保护模式
当一个新的 Eureka Server 出现时,它尝试从相邻节点获取所有实例注册表信息。如果从 Peer 节点获取信息时出现问题,Eureka Serve 会尝试其他的 Peer 节点。如果服务器能够成功获取所有实例,则根据该信息设置应该接收的更新阈值。如果有任何时间,Eureka Serve 接收到的续约低于为该值配置的百分比(默认为 15 分钟内低于 85%),则服务器开启自我保护模式,即不再剔除注册列表的信息。
这样做的好处就是,如果是 Eureka Server 自身的网络问题,导致 Eureka Client 的续约不上,Eureka Client 的注册列表信息不再被删除,也就是 Eureka Client 还可以被其他服务消费。
4 组件
Eureka 包含两个组件:Eureka Server 和 Eureka Client
1:Eureka Server 提供服务注册服务,各个节点启动后,会在 Eureka Server 中进行注册,这样 Eureka Server 中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到
2:Eureka Client 是一个 Java 客户端,用于简化 Eureka Server 的交互,客户端同时也具备一个内置的、使用轮询 (round-robin) 负载算法的负载均衡器。在应用启动后,将会向 Eureka Server 发送心跳(默认周期为 30 秒)。如果 Eureka Server 在多个心跳周期内没有接收到某个节点的心跳,Eureka Server 将会从服务注册表中把这个服务节点移除(默认 90 秒)
3:Eureka Server 之间将会通过复制的方式完成数据的同步。Eureka 还提供了客户端缓存的机制,即使所有的 Eureka Server 都挂掉了,客户端依然可以利用缓存中的信息消费其它服务的 API。Eureka 通过心跳检测、健康检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。
5 测试实战
创建一个 SpringBoot 项目,用来当作 Eureka Server
5.1 注册中心 Eureka
5.1.1 添加依赖
Springboot 还是采用 1.5.6.RELEASE
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
</parent>
5.1.2 Eureka Server 的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
5.1.3 添加 SpringCloud 版本的依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
5.1.4 添加 spring-boot-maven-plugin
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
5.1.5 添加启动注解
在 SpringBoot 的启动类上添加:@EnableEurekaServer
5.1.6 配置
可以采用 properties 或者 yml,比如 application.yml,示例如下:
server:
port: 8761
eureka:
instance:
hostname: localhost
#是否优选使用 ip 地址
prefer-ip-address: true
client:
#启动是否注册
registerWithEureka: false
#是否获取注册列表
fetchRegistry: false
serviceUrl:
defaultZone: http://localhost:8761/eureka/
5.1.7 启动
启动并查看注册中心:http://localhost:8761/, 如果不能正常显示,可以把 spring-cloud-netflix-eureka-server-1.3.0.RELEASE.jar 里面的 static 和 templates 的内容添加到工程的 resources 下面来
1.5.2 服务提供者
依赖基本跟 Eureka Server 的项目一样,但不是依赖 Eureka Server 了,而是:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
配置
可以采用 properties 或者 yml,比如 application.yml,示例如下:
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
server:
port: 8765
spring:
application:
name: userService
1.5.2.1 添加注解
在启动类上添加 @EnableDiscoveryClient 或者@EnableEurekaClient
- 1:对 Eureka Server 而言,服务提供者和服务消费者,都是 Eureka Server 的 Client,都是用一样的注解
- 2:两者区别:
SpringCloud 中的“Discovery Service”有多种实现,比如:eureka, consul, zookeeper 等。@EnableDiscoveryClient 注解是基于 spring-cloud-commons 依赖,可以在不同的服务注册服务器上使用;@EnableEurekaClient 注解是基于 spring-cloud-netflix 依赖,只能为 eureka 用
开发服务对外提供 REST 服务的 Controller,以及具体的服务实现
1.5.3 服务消费者
- 创建一个 SpringBoot 项目,用来当作服务消费者
- 依赖基本跟服务提供者的项目一样
- 配置
可以采用 properties 或者 yml,比如 application.yml,示例如下:
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
server:
port: 8764
spring:
application:
name: userConsumer
在启动类上添加 @EnableDiscoveryClient 或者@EnableEurekaClient
开发 Controller
做一个配置 Bean,用来提供 RestTemplate
开发具体的服务实现,例如:
@Service
public class UserService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private EurekaClient discoveryClient;
public String hiService(String name) {InstanceInfo instance = discoveryClient.getNextServerFromEureka("userService", false);
String path = instance.getHomePageUrl();
String ret = restTemplate.getForObject(path+"/userServiceProvider?name="+name,String.class);
return ret;
}
}
6 Eureka 高层架构
7 疑问
- 注册延迟:Eureka Client 一启动,不是立即向 Eureka Server 注册,它有一个延迟向服务端注册的时间,也就是会定期发送心跳到注册表,默认持续时间为 30 秒。
- Eureka Server 的响应缓存:Eureka Server 维护每 30 秒更新的响应缓存, 可通过更改配置 eureka.server.responseCacheUpdateIntervalMs 来修改。所以即使实例刚刚注册,它也不会出现在返回给客户的结果中
- Eureka Server 刷新缓存:Eureka 客户端保留注册表信息的缓存。该缓存每 30 秒更新一次
- 因而极端情况下可能需要 3 个心跳,客户端才能发现服务。
- 可以使用 eureka.instance.leaseRenewalIntervalInSeconds 更改期限,这将加快客户端连接到其他服务的过程。在生产中,最好坚持使用默认值,因为服务器内部有一些计算可以对租赁更新期进行假设。
8 Eureka 与 zookeeper 对比
- 回忆一下分布式系统的 CAP 理论:
一致性(C):系统在执行过某项操作后仍然处于一致的,在分布式系统中,更新操作执行成功后所有的用户都应该读取到最新的值,这样的系统被认为具有强一致性。
可用性(A):每个请求都能接受到一个响应,无论响应成功或失败。
分区容错性(P):系统在存在网络分区的情况下仍然可以接受请求并处理,这里网络分区是指由于某种原因网络被分成若干个孤立区域,而区域之间互不相通。
由于分区容错性在是分布式系统中必须要保证的,因此只能在 A 和 C 之间进行权衡。Zookeeper 保证的是 CP, 而 Eureka 则是 AP。
- Zookeeper 保证 CP
在任何时刻对 ZooKeeper 的访问请求能得到一致的数据结果,同时系统对网络分割具备容错性、但是它不能保证每次服务请求的可用性,也就是在极端环境下,ZooKeeper 可能会丢弃一些请求,消费者程序需要重新请求才能获得结果,比如重新选举 leader 的时间太长,且选举期间整个 zk 集群都是不可用的,这就导致在选举期间注册服务瘫痪。
- Eureka 保证 AP
实际应用中,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接 down 掉不可用,也就是说,服务注册功能对可用性的要求要高于一致性。
Eureka 服务节点在短时间里丢失了大量的心跳连接,那么这个 Eureka 节点会进入“自我保护模式”,同时保留那些“心跳死亡”的服务注册信息不过期。此时,这个 Eureka 节点对于新的服务还能提供注册服务,对于“死亡”的仍然保留,以防还有客户端向其发起请求。当网络故障恢复后,这个 Eureka 节点会退出“自我保护模式”。Eureka 的哲学是,同时保留“好数据”与“坏数据”总比丢掉任何数据要更好。
9 Eureka 集群
构建 Eureka Server 集群
- 启动多个 Eureka Server,配置 defaultZone 为其它的 Eureka Server
- 客户端连接的时候,defaultZone 的值可以写多个,用逗号区分
深入理解
实际应用中,因负载等原因,往往可能需要在生产环境构建多于两个的 Eureka Server 节点。那么对于如何配置 serviceUrl 来让集群中的服务进行同步,需要我们更深入的理解节点间的同步机制来做出决策。
Eureka Server 的同步遵循着一个非常简单的原则:只要有一条边将节点连接,就可以进行信息传播与同步。
1:两两注册的方式可以实现集群中节点完全对等的效果,实现最高可用性集群,任何一台注册中心故障都不会影响服务的注册与发现
2:Eureka Server 具备单方面有指向的服务传播与同步机制,在一些对服务发现有限制的情况下,可以利用这样的机制进行服务注册与发现的的单向控制
部署集群后同一路径访问:切换不同的服务返回结果
10 Eureka Server 之间通信
1:Eureka Client 首先会尝试与同一区域的 Eureka Server 进行连接,如果出现问题,或者服务器不在同一个区域中,则客户端将故障转移到其他区域中的服务器。
2:一旦服务器开始接收流量,在服务器上执行的所有操作都将被复制到服务器知道的所有对等节点。如果某个操作由于某种原因而失败,那么该信息将在下一个在服务器之间复制的心跳信号进行核对。
3:当 Eureka 服务器启动时,它会尝试从邻居节点获取所有实例注册表信息。如果在从节点获取信息时出现问题,服务器在放弃之前尝试所有的对等点。如果服务器能够成功获取所有实例,则会根据该信息设置应该接收的更新阈值。如果任何时候,续订低于为该值配置的百分比(在 15 分钟内低于 85%),则服务器将停止到期实例以保护当前实例注册表信息。也就是进入自我保护模式。
在这种情况下,如果服务器无法从邻居节点获取注册表信息,则会等待几分钟(5 分钟),以便客户端可以注册其信息。此时注册可能发生在孤立的服务器上,有些客户端可能会反映新的注册,而其他客户端可能不会。在网络连接恢复到稳定状态后,情况会自动自动更正。当对等方能够正常通信时,注册信息会自动同步。
1.10.1 Peer 之间的状态同步
Service Provider 只需要通知到任意一个 Eureka Server 后就能保证状态会在所有的 Eureka Server 中得到更新。
具体实现方式其实很简单,就是接收到 Service Provider 请求的 Eureka Server,把请求再次转发到其它的 Eureka Server,调用同样的接口,传入同样的参数,除了会在 header 中标记 isReplication=true,从而避免重复的 replicate。
Peer 之间的状态是采用异步的方式同步的,所以不保证节点间的状态一定是一致的,不过基本能保证最终状态是一致的。
1.10.2 Eureka Server 是怎么知道有多少 Peer 的呢?
Eureka Server 在启动后会调用 EurekaClientConfig.getEurekaServerServiceUrls 来获取所有的 Peer 节点,并且会定期更新。定期更新频率可以通过 eureka.server.peerEurekaNodesUpdateIntervalMs 配置。
这个方法的默认实现是从配置文件读取,所以如果 Eureka Server 节点相对固定的话,可以通过在配置文件中配置来实现。
如果希望能更灵活的控制 Eureka Server 节点,比如动态扩容 / 缩容,那么可以覆盖 getEurekaServerServiceUrls 方法,提供自己的实现,比如通过数据库读取 Eureka Server 列表
11 配置选项
1:SpringCloud 中的配置选项可以参看官方文档:http://cloud.spring.io/spring…
2:客户端的默认配置,在 Eureka 中是 https://github.com/Netflix/eu…
在 SpringCloud 结合 Netflix 中是:https://github.com/spring-clo…
3:服务端的默认配置,在 Eureka 中是:https://github.com/Netflix/eu…
在 SpringCloud 结合 Netflix 中是:
https://github.com/spring-clo…
12 添加链接权限
n 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
n 在配置文件中添加,示例如下:
security:
basic:
enbled: true
user:
name: admin
password: admin
消费者的连接方式
也需要用户名和密码,否则会报错,示例如下:
defaultZone: http://admin:cc@localhost:8761/eureka/
13 状态页和健康指标
1:Eureka 实例的状态页面和运行状况指示器分别默认为“/info”和“/health”,它们是 Spring Boot 中端点的默认位置。
2:默认情况下,Spring CLoud Eureka 中各个服务实例的健康检测并不是通过 spring-boot-actuator 模块的 /health 端点来实现的,而是依靠客户端心跳的方式来保持服务实例的存活。因此,默认的心跳方式作为健康检测并不保险,因为不能检测服务是否能有效提供服务
可以通过简单的配置,把 Eureka 客户端的健康检测交给 spring-boot-actuator 模块的 /health 端点,以实现更加全面的健康状态维护。详细步骤如下:
(1)在 pom.xml 中引入 spring-boot-starter-actuator 模块的依赖
(2)在 application.properties 中增加参数配置 eureka.client.healthcheck.enabled=true
3:如果要使用非默认上下文路径或 servlet 路径(例如 server.servletPath=/foo)或管理端点路径(例如 management.contextPath=/admin),则需要更改,如:
eureka:
instance:
statusPageUrlPath: ${management.context-path}/info
healthCheckUrlPath: ${management.context-path}/health