Nacos 中使用了哪些缓存?缓存的目的是什么?是如何实现的?
Nacos 在服务端和客户端都广泛的使用了缓存机制,下面着重介绍一下。
一、 Nacos 服务端缓存 (Server-Side Caching)
Nacos 服务端缓存的主要目的是提高读取性能、降低对底层存储(数据库或磁盘文件)的压力,并加速对客户端请求的响应。
-
缓存的内容:
- 服务实例信息: 注册到 Nacos 的服务实例列表(IP、端口、权重、元数据、健康状态等)。这是服务发现的核心数据,查询频率非常高。
- 服务信息: 服务的元数据,例如服务名、分组名、保护阈值等。
- 配置信息: 用户发布的配置内容(按
dataId
和group
区分)。客户端会频繁拉取或监听配置变更。 - 集群节点信息: Nacos 集群自身节点的状态和信息。
- 其他内部状态或元数据: 用于加速内部处理逻辑。
-
缓存的目的:
- 提升读性能/降低延迟: 从内存缓存中读取数据远快于从数据库或磁盘读取,可以极大加速客户端对服务列表或配置内容的查询请求。
- 减轻存储层负载: 大量的客户端(可能成千上万)会频繁请求服务实例和配置信息。如果每次请求都直接访问数据库,会对数据库造成巨大压力,容易成为性能瓶颈。缓存层可以挡住绝大部分读请求。
- 提高可用性: 即使底层存储暂时出现抖动,只要缓存中有数据,服务端仍然可以在一定程度上继续提供(可能是稍微过期的)服务发现和配置查询能力。
-
实现方式:
- 内存缓存: 主要使用 JVM 内存作为缓存存储。常见使用
java.util.concurrent.ConcurrentHashMap
等线程安全的 Map 结构来存储缓存数据,Key 通常是服务名、dataId
+group
的组合等。 - 缓存更新机制:
- 写操作触发更新/失效: 当有数据变更操作时(如服务注册、注销、心跳更新、配置发布/删除),Nacos 首先会更新底层持久化存储(如数据库)。操作成功后,会主动更新或失效相应的内存缓存。这是保证缓存一致性的关键。
- 事件驱动: Nacos 内部通常有事件发布/订阅机制。当数据发生变化并持久化成功后,会发布一个事件,相关的缓存监听器接收到事件后更新缓存。
- 一致性协议关联: 在集群模式下,数据的变更通常通过一致性协议(如 Raft 或 Nacos 早期版本的类 Distro 协议)同步到其他节点。当一个节点通过协议成功应用(Apply)了一个数据变更日志后,它会更新自己的内存缓存。这保证了集群中各个节点缓存状态的最终一致性。
- 懒加载/定时加载 (较少用于核心数据): 某些非核心或不经常变动的数据可能会在首次读取时加载,或通过定时任务定期从存储刷新。但对于服务实例和配置这类核心数据,主动更新是主要方式。
- 分层缓存 (可能): 某些实现可能会考虑使用多级缓存(如 L1/L2),但 Nacos 的核心缓存主要是基于内存的单层缓存。
- 内存缓存: 主要使用 JVM 内存作为缓存存储。常见使用
二、 Nacos 客户端缓存 (Client-Side Caching)
Nacos 客户端(即你的应用程序中引入 nacos-client
的部分)也实现了缓存,主要目的是提高应用程序性能、降低对 Nacos Server 的请求频率,并在 Nacos Server 短暂不可用时提供一定的容错能力。
-
缓存的内容:
- 服务实例列表: 客户端订阅的服务,其可用的实例信息会被缓存在客户端内存中。
- 配置信息: 客户端获取到的配置内容会被缓存在内存中,并且通常会有一个本地文件快照(Failover 机制)。
-
缓存的目的:
- 提升应用性能: 应用获取服务实例或配置时,可以直接从本地内存缓存读取,避免了网络请求的开销,速度极快。
- 降低服务端压力: 客户端不需要每次需要数据都去请求 Nacos Server,大大减少了服务端的 QPS。
- 提高应用容错性/可用性 (Failover):
- 服务发现: 如果 Nacos Server 暂时宕机或网络不通,客户端仍然可以使用本地缓存的(可能是稍微过期的)服务实例列表进行服务调用,避免完全瘫痪。
- 配置管理: 如果 Nacos Server 不可用,客户端可以加载本地磁盘上的配置快照文件 (
snapshot
目录),保证应用能够使用上一份正确的配置启动或运行。
-
实现方式:
- 内存缓存: 同样主要使用
ConcurrentHashMap
等内存结构存储服务实例和配置。 - 本地文件快照 (配置管理): Nacos Client 会将成功获取到的配置内容写入到本地磁盘的一个文件中(通常在用户主目录下的
nacos/config
目录的snapshot
子目录)。当启动时无法连接 Nacos Server 或获取配置失败时,会尝试从这个快照文件加载。 - 缓存更新机制:
- 服务发现:
- 定时拉取: 客户端会定时(可配置)向 Nacos Server 拉取最新的服务实例列表来更新本地缓存。
- UDP推送 (Nacos 1.x): Nacos 1.x 版本支持服务端通过 UDP 协议主动将变更推送给客户端(但 UDP 不可靠,仍需定时拉取保证)。
- gRPC 长连接/Watch (Nacos 2.x): Nacos 2.x 推荐使用 gRPC,客户端与服务端建立长连接。当服务实例发生变化时,服务端可以通过长连接主动将变更推送给客户端,实现更实时的更新。即使有推送,通常也会保留定时拉取作为兜底。
- 配置管理:
- 长轮询 (Long Polling): 客户端向服务端发起一次获取配置的请求。如果配置没有变更,服务端会 hold 住这个连接一段时间(例如 30 秒)。如果期间配置发生变更,服务端会立即返回变更内容;如果超时仍无变更,服务端返回无变更,客户端收到响应后立即发起下一次长轮询。这种方式可以准实时地获取配置变更,同时避免了大量无效的轮询请求。
- gRPC 长连接/Watch (Nacos 2.x): 类似服务发现,通过 gRPC 长连接实现配置变更的主动推送。
- 服务发现:
- 缓存失效: 客户端缓存通常没有复杂的过期策略,主要依赖于服务端的推送或客户端的定时/长轮询来更新。
- 内存缓存: 同样主要使用
总结:
Nacos 中的缓存是其架构设计的核心部分,遍布服务端和客户端。
- 服务端缓存 主要为了 提升性能和降低存储压力,通过内存缓存和主动更新机制实现。
- 客户端缓存 主要为了 提升应用性能、降低服务端压力和提供故障容错能力,通过内存缓存、本地文件快照以及与服务端的实时/准实时同步机制(如 gRPC Watch、长轮询)实现。