PaxosStore的消息传递
PaxosStore 中定义了一种名为 Command 的消息,模块间的通信通过 Command 的传递实现。消息传递的方式有两种,一种是通过消息队列传递,一种是使用 Protobuf 序列化后通过网络收发。
clsPaxosCmd 派生了 clsPaxosCmd / clsClientCMd / clsWriteBatchCmd / clsRecoverCmd 四种类型的 Command 消息,其中只有 clsPaxosCmd 会经过网络传收发:
消息队列
PaxosStore 中线程间消息的传递通过消息队列实现。这里消息队列由无锁循环队列 LockFreeQueue 实现,定义于 utils/lock_free_queue.h,通过 CAS 实现多线程下的并发写入:
#pragma once
#include "certain/errors.h"
#include "utils/header.h"
#include "utils/memory.h"
namespace certain {
// Lock-free Queue based on Ring Buffer
template <typename T>
class LockFreeQueue {
public:
explicit LockFreeQueue(uint64_t capacity)
: capacity_(capacity),
items_(std::make_unique<std::unique_ptr<T>[]>(capacity)) {}
uint64_t Size() const {
auto t = tail_.load();
auto h = head_.load();
return h - t;
}
bool Empty() const { return Size() == 0; }
bool Full() const { return Size() >= capacity_; }
template <class O>
int PopByOneThread(std::unique_ptr<O>* item) {
static_assert(std::is_same<T, O>::value || std::is_base_of<T, O>::value,
"Type Mismatch");
auto t = tail_.load();
auto h = head_.load();
if (h == t) {
return kUtilsQueueEmpty;
}
auto& pop = items_[t % capacity_];
if (pop == nullptr) {
return kUtilsQueueConflict;
}
unique_cast<T>(*item) = std::move(pop);
tail_.fetch_add(1);
return 0;
}
template <class O>
int PushByMultiThread(std::unique_ptr<O>* item, int retry_times = 5) {
static_assert(std::is_same<T, O>::value || std::is_base_of<T, O>::value,
"Type Mismatch");
if (item->get() == nullptr) {
return kUtilsInvalidArgs;
}
for (int i = 0; i < retry_times || retry_times == -1; ++i) {
auto t = tail_.load();
auto h = head_.load();
if (h - t >= capacity_) {
return kUtilsQueueFull;
}
// CAS
if (head_.compare_exchange_strong(h, h + 1)) {
unique_cast<T>(*item).swap(items_[h % capacity_]);
return 0;
}
}
return kUtilsQueueConflict;
}
private:
static constexpr size_t kCacheLineSize = 64;
alignas(kCacheLineSize) std::atomic<uint64_t> head_{0};
alignas(kCacheLineSize) std::atomic<uint64_t> tail_{0};
alignas(kCacheLineSize) const uint64_t capacity_;
std::unique_ptr<std::unique_ptr<T>[]> items_;
};
} // namespace certain
PaxosStore 定义了一个全局的消息队列单例 AsyncQueueMng,统一管理各个组件使用的消息队列,定义于 src/async_queue_mng.h
单例的好处是全局可见,如果希望给某个组件发消息,则可以通过单例获取组件对应的队列、调用 PushByMultiThread 将消息推入其中即可。使用消息队列也使得组件之间逻辑解耦。
网络收发
PaxosStore 中进程间消息的传递通过 Socket 实现,包括收和发两个数据方向。
收消息是将从 IOChannel 中读取到的字节流反序列化为 Command 命令、推入 IOReqQueue 队列中;
发则是将 IORsqQueue 队列中的 Command 命令取出、序列化为字节流后通过 IOChannel 发送。
首先来看收消息的过程。clsIOWorker 继承 clsIOHandlerBase,当 IOChannel 有新数据可读时会调用 clsIOWorker::HandleRead
再来看发消息的过程。ConsumeIORspQueue 从 IORsqQueue 队列中取出 Command 并执行序列化,拿到对应的 IOChannel 执行 AppendWriteBytes 写入缓存中;Epoll 对可写的 IOChannel 执行 Flush。
总结
PaxosStore 单个 Server 进程内会启动多个 Worker 线程,线程间通过消息队列传递消息;而多个 Server 进程间则通过 Protobuf + Socket 传递消息。通信部分由于使用 Epoll 边缘触发的原因,对仍然可读可写的 fd 都做了特殊化处理。