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

posted @ 2023-02-08 15:56  misaka-mikoto  阅读(29)  评论(0编辑  收藏  举报