各位做微服务或者大数据平台的兄弟,应该都有过这种经历:注册中心里的服务明明已经被下线了,但新上线的任务或请求还是往那个实例上打。排查半天,发现不是代码逻辑问题,而是卡在了注册中心的缓存更新上。Eureka 作为服务注册与发现的老牌组件,在 Spring Cloud 体系里被广泛使用,大数据平台里做数据采集、实时计算任务调度时也经常拿它做服务注册中心。今天我就把 Eureka 服务发现的缓存更新机制从头到尾扒一遍,说清楚三层缓存之间的关系、触发更新的真实链路,以及在大规模集群下那几个让人头秃的坑。
这篇文章不是那种只讲概念的科普,我会结合自己的实际项目经验,把注册、续约、剔除、增量抓取这些操作和缓存更新的关系理清楚,最后附上一个真实踩坑案例的排查过程。适合正在用 Eureka 做服务注册中心、尤其是集群规模不小、对服务上下线延迟比较敏感的同学参考。
1. 注册中心为什么要设计三层缓存,而不是一把锁保护一张表
1.1 先搞清楚 Eureka Server 的注册数据到底存在哪
很多人刚开始看 Eureka 源码的时候都会懵一下,觉得注册表不就是个 Map 吗?实际上在 Eureka Server 内部,服务的实例信息确实是存在一个 ConcurrentHashMap 里的,这个 Map 叫 registry。但客户端拿到的注册表信息,并不是每次请求都直接去这个 Map 里读,中间还隔了两层缓存。
这三层结构可以简单理解为:
- 第一层:
registry,最底层的注册表,保存了所有服务实例的原始信息,也是唯一的数据来源。 - 第二层:
readWriteCacheMap,一个基于 Guava Cache 的读写缓存,服务注册、下线、状态变更时会主动失效对应的缓存条目,以保证最新变更能被快速读取。 - 第三层:
readOnlyCacheMap,一个只读缓存,默认每 30 秒从readWriteCacheMap同步一次,客户端拉取注册表时默认命中的就是这一层。
为什么要加这么两层缓存,不直接让客户端读最底层的注册表?答案很简单:服务注册和发现的场景是典型的读多写少。一个大数据平台的注册中心里,可能有几百上千个实例,每个实例每 30 秒还要发一次心跳。如果所有读请求都直接落到 registry 上,再用一把锁保护一致性,那么在高并发心跳和客户端频繁拉取注册表的双重压力下,锁竞争会非常严重,注册中心的响应延迟和 CPU 消耗都会直线上升。
1.2 三层结构各自的定位和性能取舍
我先说说 readWriteCacheMap。它本质上是注册表和只读缓存之间的中间层,承担的是“数据新鲜度”和“并发读性能”之间的平衡。写入操作(注册、下线、状态变更)发生后,会主动让这个缓存里的对应 key 失效,这样下一次读就能拿到最新数据。同时它又被配置了一个过期时间,默认 180 秒,也就是说即使没有主动失效,每条缓存过了 180 秒也会被过期掉,防止极端情况下数据长期不更新。
readOnlyCacheMap 就更有意思了,它连主动失效都没有。这个只读缓存只在两个时机被更新:
- 每 30 秒的定时同步任务,把
readWriteCacheMap的内容整体刷到readOnlyCacheMap。 - 客户端发来注册表拉取请求时,如果发现
readOnlyCacheMap里的数据还是旧版本,会触发一次同步更新。
注意第二点:客户端请求触发同步,是由于 readOnlyCacheMap 内部记录了一个 lastUpdateTime,当缓存内容过期或者版本对不上时,请求线程会去 readWriteCacheMap 拉最新数据并更新只读缓存。这个设计的优点是大幅降低了缓存失效的粒度,缺点就是:服务端做了变更之后,客户端看到变更的最大延迟,是由这个 30 秒定时周期决定的。
1.3 为什么不是“一层缓存 + 主动失效”就够了
这是个值得深入想的问题。如果只保留 readWriteCacheMap,客户端每次拉注册表都直接读它,主动失效也能保证新数据第一时间被读到,为什么还要多一层 readOnlyCacheMap 挡在前面?
原因在于 readWriteCacheMap 底层用的 Guava Cache 在并发读上有它的代价。Guava Cache 在缓存过期、条目失效时涉及锁操作和回调处理,高并发场景下频繁的失效和重建会带来不小的开销。而 readOnlyCacheMap 的读操作就是最简单不过的 ConcurrentHashMap 读,几乎零成本,适合应对客户端高频的注册表拉取。
所以在 Eureka 的默认设计里,客户端的注册表拉取请求是不直接读 readWriteCacheMap 的,而是读 readOnlyCacheMap。只有当 readOnlyCacheMap 里没有数据时,才会穿透到 readWriteCacheMap。这一层只读缓存的引入,本质上是在用“
