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 都做了特殊化处理。

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号