Eureka 原理
Eureka 的服务注册机制
- 新的服务部署完成后,会访问 Eureka Server,来给自己注册。
- 每个 Eureka Client,每隔 30 秒,会从 Eureka Server 获取注册表的变化,以便及时得知哪些服务已经挂了。
- 每个 Eureka Client,每隔 30 秒,会发送心跳给 Eureka Server,证明自己还活着。
Eureka Server 的访问频率
假设现在有 100 个服务,每个服务部署到 20 台服务器上,机器是标准的 4 核 8G 配置。相当于 100 * 20 = 2000 个服务,也就是 2000 台服务器。每台服务器上都有一个 Eureka Client 组件,他们每隔 30 秒都会访问 2 次 Eureka Server,一次是获取注册表的变化,一次是发送心跳。
- 每分钟:一台服务器获取 2 次注册表的变化,发送 2 次心跳,2000 台服务器,Eureka Server 就要被访问 8000 次。
- 每秒:8000 / 60 ≈ 133 次,算上其它一些额外操作,我们算作 200 次左右。
- 每天:8000 * 60 * 24 = 1152 万次。
对于上述情况,Eureka Server 将要面对日千万级的访问量。
Eureka Server 的存储方式
首先,我们来看看 Eureka 存储注册表的源码。
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
}
- registry:以 ConcurrentHashMap 作为核心结构,说明注册表是基于纯内存的。key
是服务名称,如:user-service。value 是服务的多个实例。 - Map<String, Lease<InstanceInfo>>:key 是服务实例的 id,InstanceInfo
存储服务实例的具体信息,如 IP 地址、端口、hostname 等。 - Lease:维护服务实例的最近心跳时间。
结论:维护注册表、获取注册表的变化、更新心跳时间的操作,全部都发生在内存里,速度快也就不难理解了。
Eureka 的多级缓存机制
多级缓存尽可能保证注册表的数据不会频繁的出现读写冲突问题。也提高了速度,性能很高。
- 当 Eureka Client 获取注册表时,先读取 ReadOnlyCacheMap,如果没有,就去读 ReadWriteCacheMap,还没有,就直接读取注册表。
- 当注册表发生变化时,会过期掉 ReadWriteCacheMap,不影响读取 ReadOnlyCacheMap(30 秒内)。
- 30 秒后,Eureka Server 发现 ReadWriteCacheMap 已经被清空了,就会清空 ReadOnlyCacheMap。
- Eureka Client 再次获取注册表时,发现两个 CacheMap 都是空的,就会直接读取注册表,同时填充缓存。
Eureka 的特点总结
- 请求频率:每 30 秒获取一次注册表,每 30 秒发送一次心跳。保证了每秒处理几百次请求的能力。
- 纯内存注册表:所有请求都可以在内存中处理。保证了高性能。
- 多级缓存机制:避免发生频繁的读写冲突,进一步提升性能。
Eureka 与 Zookeeper
相同点
Eureka 和 Zookeeper 都在分布式系统中,充当着服务注册中心的角色。
作为服务管理组件,都需要具备以下三个能力:
- 熔断:当检测到某个服务异常时,要及时把它断掉,以防整个项目被拖垮。
- 降级:在业务高峰期,为了保障核心服务,把不重要的服务暂时停掉。
- 限流:面对突然的大量请求,为了保障服务节点的正常运行,要每隔一段时间,再放一批请求进去。
不同点
Zookeeper 作为第三方组件,被 Dubbo 使用;而 Eureka,是 Spring Cloud 中的自带组件。
说到这儿,我们有必要看看 Dubbo 与 Spring Cloud 的区别:
- 服务调用方式:Dubbo 是基于 RPC 调用的;Spring Cloud 是基于 HTTP 协议的 REST API
调用的。所以在远程调用的效率上,Dubbo 大约是 Spring Cloud 的三倍。 - 框架:Dubbo 是一个微服务治理的框架,依赖于很多第三方插件;而 Spring Cloud 有一套成熟的解决方案。
Zookeeper 放弃了一定的可用性,在服务器挂了后,需要选举出新的 leader,会产生延迟;而 Eureka 为了保证可用性,设定每个节点都是公平的,不需要选举 leader,但也放弃了一致性。
栗子
为了测试分布式,我们先配置 host,模拟服务端和客户端。
127.0.0.1 master
127.0.0.1 slave
127.0.0.1 slave2
创建 Eureka 服务项目(eurekaserver)
- 第一步:选择 Create New Project,创建一个 Spring Initializr 项目,选择 Web 组件和 Eureka Server 组件。
- 第二步:在启动类中加入 Eureka 的服务注解 @EnableEurekaServer。
- 第三步:配置服务属性。
- 第四步:启动项目,访问 127.0.0.1:8761,进入管理界面。
可以看到 EUREKASERVER 服务已经注册进去了。
创建 Eureka 服务提供项目(user)
- 第一步:选择 Create New Project,创建一个 Spring Initializr 项目,选择 Web 组件和 Eureka Discovery 组件。
- 第二步:在启动类中加入 Eureka 的客户端注解 @EnableEurekaClient。
- 第三步:配置客户端属性。
- 第四步:编写测试接口。
第五步:启动项目,访问 127.0.0.1:8761,进入管理界面。
可以看到 USER 服务也注册进去了。
创建 Eureka 服务调用项目(customer)
- 第一步:选择 Create New Project,创建一个 Spring Initializr 项目,选择 Web 组件和 Eureka Discovery 组件。
- 第二步:在启动类中加入 Eureka 的客户端注解 @EnableEurekaClient。
- 第三步:配置客户端属性。
- 第四步:调用服务端的接口。
- 第五步:启动项目,访问 127.0.0.1:8080/test/testByEureka。
我们可以看到 customer 成功的访问到了 user 服务。使用 HTTPclient 的方式访问接口,有点麻烦,我们可以使用 Eureka 中的 RestTemplate,不仅方便,还支持负载均衡。
再启动一个相同的 user,把端口改为 8200,访问 127.0.0.1:8761,进入管理界面。
可以看到现在有2个 USER 服务,端口分别为 8100 和 8200。
访问 127.0.0.1:8080/test/testByRestTemplate。
第一次调用了 8200 服务。
刷新时,调用了 8100 服务。
再次刷新,又调用了 8200 服务。(默认分配算法是轮询)