KVCacheManager 支持多节点高可用(HA)部署模式。多个 KVCM 实例以一主多备的方式运行,通过分布式选主(Leader Election)选出唯一的 Leader 节点提供数据面服务,其余节点作为 Follower 待命。当 Leader 故障时,Follower 自动接管,实现故障自动转移。
核心设计目标:
- 自动选主:无需人工干预,节点启动后自动参与选举。
- 故障转移:Leader 故障或主动降级后,Follower 在租约过期窗口内自动接管。
- Leader 发现:客户端可向任意节点查询当前 Leader 地址,然后直连 Leader 进行数据面操作。
- 后端可扩展:选主协调层支持多种后端(内存、文件、Redis),适配不同部署场景。
┌─────────────────────────────────────────────────────────────┐
│ 推理引擎 (Client) │
│ │
│ ① GetClusterInfo → 发往任意节点 → 获取 leader_endpoint │
│ ② 直连 Leader 进行读写(数据面 + 管控面) │
│ ③ 若请求被拒绝(Leader 已变更),回到 ① 重新发现 │
└──────────┬──────────────────────────────────┬───────────────┘
│ │
┌────────▼────────┐ ┌────────▼─────────┐
│ Node (Follower) │ │ Node (Leader) │
│ │ │ │
│ 不处理读写请求 │ │ 可以处理所有请求 │
│ 仅提供: │ │ │
│ · Leader 查询 │ │ │
│ · 健康检查 │ │ │
│ · 运维监控 │ │ │
│ │ │ │
│ LeaderElector │ │ LeaderElector │
└────────┬─────────┘ └────────┬──────────┘
│ │
┌────────▼──────────────────────────────────▼──────────┐
│ CoordinationBackend │
│ (分布式锁 + KV 存储) │
│ 实现: Memory / File / Redis │
└───────────────────────────────────────────────────────┘
数据面和管控面的读写请求均由 Leader 处理。Follower 不处理业务请求,仅提供 Leader 查询(GetClusterInfo)、健康检查(CheckHealth)和运维监控(GetManagerClusterInfo)等能力。客户端收到 Follower 的拒绝后,应重新通过 GetClusterInfo 发现当前 Leader,将请求发往正确的节点。
协调后端是选主和节点信息存储的基础设施层,提供两类原子操作:
- 分布式锁接口(选主用): TryLock / RenewLock / Unlock / GetLockHolder
- KV 存储接口(节点端点信息存储用): SetValue / GetValue
三种实现:
| 实现 | URI 格式 | 适用场景 |
|---|---|---|
| CoordinationMemoryBackend | memory:// |
单进程测试 |
| CoordinationFileBackend | file:///path/to/dir |
单机开发/测试 |
| CoordinationRedisBackend | redis://[auth_token@]host:port/?db=<index>[¶ms] |
生产环境 |
Redis DB 只能通过 query 参数 db 指定,未配置时使用 DB 0;URI path(例如 /1)不会选择 DB。db > 0 要求 Redis 服务端支持 SELECT,因此不适用于 Redis Cluster。
LeaderElector 是选主状态机的核心实现,负责竞选、续约、降级和状态转换。
竞选成功 OnBecomeLeader
(获取分布式锁) 回调完成
FOLLOWER ──────────────► PROMOTING ──────────────► LEADER
▲ │
│ 锁丢失 / │
│ OnNoLongerLeader 主动降级 │
│ 回调完成 │
└──────────────── DEMOTING ◄──────────────────────┘
| 状态 | 含义 | 是否可对外服务 |
|---|---|---|
FOLLOWER |
备节点稳定态 | 仅响应 Leader 发现等非数据面请求 |
PROMOTING |
竞选成功后的晋升中间态(执行 OnBecomeLeader 回调) | 否 |
LEADER |
主节点稳定态 | 是 |
DEMOTING |
降级中间态(执行 OnNoLongerLeader 回调) | 否 |
中间状态(PROMOTING / DEMOTING)确保回调逻辑(数据恢复、清理)执行完毕后才切换到稳定态,避免请求在不一致状态下被处理。
LEADER → DEMOTING 的触发条件包括:租约过期、续约失败、主动调用 Demote 接口。
LeaderElector 内部维护 3 个线程:
| 线程 | 职责 | 周期 |
|---|---|---|
| LeaderLock 线程 | 执行选主循环:未获锁时尝试竞选,已获锁时续约 | loop_interval_ms(建议 < lease_ms / 10) |
| LeaseCheck 线程 | 独立检查租约是否过期,作为 LeaderLock 线程的安全兜底:当 LeaderLock 线程因阻塞导致未能及时感知租约过期时,LeaseCheck 线程可独立触发降级 | loop_interval_ms |
| StateTransition 线程 | 消费状态转换任务队列,串行执行所有状态变更及用户回调 | 事件驱动(条件变量唤醒) |
StateTransition 线程是单线程串行执行的,保证 PROMOTING → LEADER 和 DEMOTING → FOLLOWER 的回调不会并发,简化上层逻辑。
LeaderLock 线程每次循环:
1. 检查是否已持有锁:
├─ 未持有 → CampaignLeader()
│ ├─ 检查 forbid_campaign_time(降级冷却期)
│ ├─ GetLockHolder 查看当前锁状态
│ ├─ TryLock 尝试获锁
│ ├─ 成功 → 入队 PROMOTING 任务
│ └─ 失败 → 保持 FOLLOWER
│
└─ 已持有 → HoldLeader()
├─ RenewLock 续约
├─ 成功 → 更新 lease_expiration_time
└─ 失败 → 入队 DEMOTING 任务
每个节点启动时将自身连接信息(node_id、host、各服务端口)写入 CoordinationBackend 的 KV 存储。任意节点获取到 leader_node_id 后,即可查询到 Leader 的完整连接方式,用于 Leader 发现。
Server 启动时通过 CreateLeaderElector() 完成 HA 初始化:
Server::Init()
└─ CreateLeaderElector()
├─ 1. 解析 coordination_uri
├─ 2. 确定 host(advertised_host > NetUtil::GetLocalIp())
├─ 3. 确定 node_id(配置指定 > 自动生成)
├─ 4. 创建 CoordinationBackend 并 Init
├─ 5. 创建 LeaderElector
├─ 6. 注册回调: OnBecomeLeader / OnNoLongerLeader
└─ 7. WriteNodeInfo 将本节点端点信息写入 CoordinationBackend
OnBecomeLeader(晋升为 Leader):
- Registry 数据恢复(从持久化存储加载配置)
- 启动配置加载(首次启动时加载 startup_config)
- CacheManager 数据恢复(重建缓存索引)
- 恢复 CacheReclaimer(缓存回收线程)
- 启用数据面请求
OnNoLongerLeader(降级为 Follower):
- 暂停 CacheReclaimer
- 禁用数据面请求
- 等待所有进行中的数据面请求完成(优雅关闭)
- CacheManager 清理
- Registry 清理
优雅关闭保证降级过程中不会出现请求被中途丢弃的情况。
message GetClusterInfoResponse {
CommonResponseHeader header = 1;
string self_node_id = 2; // 处理该请求的节点 ID
string leader_node_id = 3; // 当前 Leader 节点 ID
NodeEndpoint leader_endpoint = 4; // Leader 的完整连接信息
}- 任意节点均可处理。
- 适用于推理引擎等数据面客户端发现 Leader。
message GetManagerClusterInfoResponse {
CommonResponseHeader header = 1;
int64 info_updated_time = 2; // 信息更新时间
int64 self_leader_expiration_time = 3; // 自身租约过期时间(仅 Leader 有效)
string self_node_id = 4;
string leader_node_id = 5;
NodeEndpoint leader_endpoint = 6; // Leader 的完整连接信息
}- 任意节点均可处理。
- 额外提供租约过期时间等运维信息。
message NodeEndpoint {
string node_id = 1;
string host = 2;
int32 meta_rpc_port = 3;
int32 meta_http_port = 4;
int32 admin_rpc_port = 5;
int32 admin_http_port = 6;
}1. 客户端持有任意节点的地址
2. 向任意节点发送 GetClusterInfo 请求
3. 从响应中获取 leader_endpoint
4. 使用 leader_endpoint 中的地址和端口直连 Leader
5. 若 Leader 变更(数据面请求被拒绝),重新执行步骤 2
客户端无需预先知道哪个节点是 Leader,可自主完成 Leader 发现和连接切换。
| 配置键 | 默认值 | 说明 |
|---|---|---|
kvcm.coordination.uri |
空(单节点模式) | 协调后端 URI。为空时使用内存后端(单节点)。旧配置名 kvcm.distributed_lock.uri 向后兼容。 |
kvcm.leader_elector.node_id |
自动生成 | 节点标识。不指定则自动生成(格式:{host}:{admin_http_port}_{随机串})。 |
kvcm.leader_elector.lease_ms |
10000 | 租约时间(毫秒)。 |
kvcm.leader_elector.loop_interval_ms |
100 | 选主循环间隔。建议 < lease_ms / 10。 |
kvcm.service.advertised_host |
空(自动检测) | 对外广告的主机地址。为空时使用 NetUtil::GetLocalIp()。 |
单节点(默认):
# 不配置 coordination.uri,使用内存后端
kvcm.leader_elector.lease_ms=600000
kvcm.leader_elector.loop_interval_ms=10000
多节点 HA(文件锁,开发/测试):
kvcm.coordination.uri=file:///shared/lock/dir
kvcm.leader_elector.lease_ms=10000
kvcm.leader_elector.loop_interval_ms=100
多节点 HA(Redis,生产):
kvcm.coordination.uri=redis://your_auth_token@redis-host:6379/?db=2&timeout_ms=5000&retry_count=3&cluster_name=kvcm_app_0
kvcm.leader_elector.lease_ms=10000
kvcm.leader_elector.loop_interval_ms=100
kvcm.service.advertised_host=10.0.0.1
Redis选主key通过cluster_name区分:kvcm_<cluster_name>_lock:/kvcm_<cluster_name>_kv:。
- Leader 停止续约,租约在
lease_ms后过期。 - Follower 的 LeaderLock 线程检测到锁未被持有,
TryLock成功。 - 新 Leader 执行 OnBecomeLeader 回调,恢复数据后开始服务。
- 故障转移时间 ≈
lease_ms(最差情况)。
- 调用
LeaderDemote接口,直接释放分布式锁并入队 DEMOTING 任务。 - StateTransition 线程执行 OnNoLongerLeader 回调(优雅清理)。
- 可选配置
forbid_campaign_time_ms防止原 Leader 立即重新竞选。 - Follower 竞选成功后成为新 Leader。
- Leader 续约失败,但锁可能尚未过期。
- 若续约持续失败直到租约过期,Leader 自动降级。
- Redis 恢复后,Follower 尝试竞选。
- 建议:
lease_ms应大于协调后端的预期最大不可用时间,避免不必要的主备切换。
由于采用单一协调后端(而非多数派投票),不存在脑裂风险:
- 能连接到协调后端的节点可以正常竞选和续约。
- 不能连接的节点无法续约,自动降级。
- 同一时刻最多只有一个节点持有锁(Leader)。
kv_cache_manager/
├── config/
│ ├── coordination_backend.h # 协调后端抽象接口
│ ├── coordination_memory_backend.h/cc # 内存实现
│ ├── coordination_file_backend.h/cc # 文件锁实现
│ ├── coordination_redis_backend.h/cc # Redis 实现
│ ├── coordination_backend_factory.h/cc # 工厂类
│ ├── leader_elector.h/cc # 选主核心逻辑
│ └── node_endpoint_info.h # 节点端点信息
├── common/
│ └── net_util.h/cc # 网络工具(GetLocalIp)
└── service/
├── server.cc # Server 集成层
├── meta_service_impl.cc # GetClusterInfo 实现
└── admin_service_impl.cc # GetManagerClusterInfo 实现
- 获取所有节点的能力:当前只能通过 Leader 发现接口获取 Leader 信息,尚不支持列出所有存活节点。后续需要支持获取全部节点列表,用于运维监控和管理场景。
- 节点信息的过期清理机制:节点端点信息写入 CoordinationBackend 后无 TTL,节点下线后其信息会残留。后续需要引入过期或心跳机制,自动清理已下线节点的信息。