基于Raft分布式Kv存储:Kvserver怎么与上层KvDB沟通?

📅 2026/7/30 2:21:00 👁️ 阅读次数 📝 编程学习
基于Raft分布式Kv存储:Kvserver怎么与上层KvDB沟通?

这里需要先澄清:项目里的“上层 KVDB”并不是独立进程,也没有通过 RPC 与KvServer通信。

KVDB 就是KvServer内部持有的跳表对象

class KvServer { private: SkipList<std::string, std::string> m_skipList; };

因此两者通过普通的 C++ 成员函数调用沟通。

```mermaid flowchart TD C["Clerk 客户端"] -->|"RPC"| K["KvServer RPC 接口"] K -->|"Start(Op)"| R["本机 Raft"] R -->|"已提交 ApplyMsg"| AC["applyChan"] AC --> A["KvServer 应用线程"] A -->|"insert_set_element"| DB["跳表 KVDB"] K -->|"search_element"| DB ```

写操作
客户端发送 Put 后:

Clerk → KvServer::PutAppend() → Raft::Start(op) → Raft 集群复制并提交日志 → applyChan → KvServer::GetCommandFromRaft() → ExecutePutOpOnKVDB() → m_skipList.insert_set_element()

关键代码:

void KvServer::ExecutePutOpOnKVDB(Op op) { m_mtx.lock(); m_skipList.insert_set_element(op.Key, op.Value); m_lastRequestId[op.ClientId] = op.RequestId; m_mtx.unlock(); }

见 [kvServer.cpp (line 66)](/C:/Users/LENOVO/Desktop/KVstorageBaseRaft-cpp-main/src/raftCore/kvServer.cpp:66)。

最重要的一点是:KvServer 收到 Put RPC 后不会立即写跳表,必须等 Raft 日志提交后才能写。

Raft 如何通知 KvServer
KvServer和本机Raft共享一个队列:

std::shared_ptr<LockQueue<ApplyMsg>> applyChan;

初始化 Raft 时传进去:

m_raftNode->init(servers, m_me, persister, applyChan);

Raft 提交日志后执行:

applyChan->Push(message);

KvServer 一直阻塞读取:

void KvServer::ReadRaftApplyCommandLoop() { while (true) { auto message = applyChan->Pop(); if (message.CommandValid) { GetCommandFromRaft(message); } if (message.SnapshotValid) { GetSnapShotFromRaft(message); } } }

见 [kvServer.cpp (line 281)](/C:/Users/LENOVO/Desktop/KVstorageBaseRaft-cpp-main/src/raftCore/kvServer.cpp:281)。

随后GetCommandFromRaft()解析Op并修改 KVDB:

if (!ifRequestDuplicate(op.ClientId, op.RequestId)) { if (op.Operation == "Put") { ExecutePutOpOnKVDB(op); } if (op.Operation == "Append") { ExecuteAppendOpOnKVDB(op); } }

读操作
Get 也先进入 Raft,提交后唤醒等待的 RPC 线程,最后直接查询跳表:

m_skipList.search_element(op.Key, *value);

对应流程:

KvServer::Get() → Raft::Start(GetOp) → 等待该日志提交 → ExecuteGetOpOnKVDB() → m_skipList.search_element() → 返回 value

Get 进入 Raft 的目的,是保证读取发生在之前已经提交的写操作之后,提供线性一致读。

快照通信
KvServer 制作快照时直接调用跳表:

m_serializedKVData = m_skipList.dump_file();

恢复快照时:

m_skipList.load_file(m_serializedKVData);

所以 KvServer 和 KVDB 之间有三类直接调用:

写入:insert_set_element(key, value) 读取:search_element(key, value) 快照:dump_file() / load_file()