Skip to content
团子云技术 Lite 1.048596
Go back

Mooncake TE 阅读手记-06-Segment 与元数据发现

团团虾导读:Segment 概念的深度展开。回答三个关键问题:openSegment 到底做了什么(不是建连接,是查 etcd 元数据)、registerLocalMemory 在 RDMA transport 层的完整链路(ibv_reg_mr → 收集 lkey/rkey → 写入 etcd)、以及为什么 initiator 也必须注册内存。附带源码行号验证。

第二篇:Segment 与元数据发现机制

Segment 是什么

在 Mooncake 中,Segment 是一个节点的身份标识,由 local_server_name(格式为 ip:port)唯一命名。每个节点在调用 init() 时创建自己的 Local Segment,并通过元数据中心(etcd/redis)发布。

来自 transfer_metadata.h 中的 SegmentDesc 定义:

// 文件: mooncake-transfer-engine/include/transfer_metadata.h

struct SegmentDesc {
    std::string name;                         // 节点名称 (ip:port)
    std::string protocol;
    std::vector<DeviceDesc> devices;          // RDMA 设备信息 (lid, gid)
    Topology topology;                        // NUMA 拓扑
    std::vector<BufferDesc> buffers;          // 注册的内存缓冲区列表
    std::vector<NVMeoFBufferDesc> nvmeof_buffers;
    std::string cxl_name;
    uint64_t cxl_base_addr;
    std::string timestamp;
    RankInfoDesc rank_info;
    int tcp_data_port;
};

每个 BufferDesc 包含了远端访问所需的全部信息:

// 文件: mooncake-transfer-engine/include/transfer_metadata.h

struct BufferDesc {
    std::string name;                    // 位置标签 (如 "cpu:0")
    uint64_t addr;                       // 虚拟地址
    uint64_t length;                     // 长度
    // 注意: 当 ENABLE_MULTI_PROTOCOL 宏定义时,此处还会有一个 protocol 字段
    std::vector<uint32_t> lkey;          // RDMA 本地密钥 (每个 context 一个)
    std::vector<uint32_t> rkey;          // RDMA 远端密钥 (每个 context 一个)
    std::string shm_name;                // NVLink/HIP 共享内存名
    uint64_t offset;                     // CXL 偏移
    std::vector<std::string> tseg;       // UB/URMA 传输段
    std::vector<uint32_t> l_seg_index;   // UB/URMA 本地段索引
};

openSegment 的底层流程

当 initiator 调用 engine->openSegment("10.0.0.3:12345"),底层执行路径为:

transfer_engine_impl.cpp:453-476
TransferEngineImpl::openSegment(segment_name)
  -> metadata_->getSegmentID(segment_name)
       // transfer_metadata.cpp:1004-1024
       // 1. 检查本地缓存
       // 2. 若未命中,从 etcd/redis 拉取 SegmentDesc
       // 3. 分配数字 ID 并缓存
  -> 返回 SegmentID

关键实现细节(来自 transfer_metadata.cpp:1004-1024):

// 文件: mooncake-transfer-engine/src/transfer_metadata.cpp

TransferMetadata::SegmentID TransferMetadata::getSegmentID(
    const std::string &segment_name) {
    {
        RWSpinlock::ReadGuard guard(segment_lock_);
        if (segment_name_to_id_map_.count(segment_name))
            return segment_name_to_id_map_[segment_name];   // 缓存命中
    }

    // Fetch segment descriptor without holding lock (may involve network I/O)
    auto segment_desc = this->getSegmentDesc(segment_name);  // 从 etcd 拉取
    if (!segment_desc) return -1;

    // Update cache with write lock, double-check to avoid duplicate
    RWSpinlock::WriteGuard guard(segment_lock_);
    if (segment_name_to_id_map_.count(segment_name))
        return segment_name_to_id_map_[segment_name];
    SegmentID id = next_segment_id_.fetch_add(1);
    segment_id_to_desc_map_[id] = segment_desc;
    segment_name_to_id_map_[segment_name] = id;
    return id;
}

这条路径说明:openSegment 不是一个网络连接操作,而是一个元数据查询操作。它不建立 RDMA QP 连接(那发生在握手阶段),只是从 etcd 拉取远端 SegmentDesc 并分配本地 ID。

target_name 怎么填

target_name 就是 target 节点在调用 init() 时传入的 local_server_name,格式是 ip:port。target 可以自行决定这个名字,只要它是网络可达的唯一标识。

发现方式:所有节点的 local_server_name 都存储在 etcd 中以 rpc_meta_prefix + server_name 为 key 的条目中。Initiator 可以通过 etcd 查询所有在线节点,然后选择目标 segment。

registerLocalMemory 做了什么事

registerLocalMemory 在 transfer engine 层面执行了两件事:

  1. 注册到所有已安装的 transporttransfer_engine_impl.cpp:525-528
// 文件: mooncake-transfer-engine/src/transfer_engine_impl.cpp:511-534

int TransferEngineImpl::registerLocalMemory(void* addr, size_t length,
                                            const std::string& location,
                                            bool remote_accessible,
                                            bool update_metadata) {
    if (checkOverlap(addr, length)) {
        LOG(ERROR) << "Transfer Engine does not support overlapped memory region";
        return ERR_ADDRESS_OVERLAPPED;
    }
    if (length == 0) {
        LOG(ERROR) << "Transfer Engine does not support zero length memory region";
        return ERR_INVALID_ARGUMENT;
    }
    for (auto transport : multi_transports_->listTransports()) {
        int ret = transport->registerLocalMemory(
            addr, length, location, remote_accessible, update_metadata);
        if (ret < 0) return ret;
    }

    std::unique_lock<std::shared_mutex> lock(mutex_);
    insertMemoryRegionLocked({addr, length, location, remote_accessible});
    return 0;
}
  1. 在 RDMA transport 层,调用 ibv_reg_mr 注册到 RNIC,并将 addr/lkey/rkey 写入 etcd

以下为简化伪代码(实际函数约120行,含并行注册、memory pre-touch、relaxed ordering 等逻辑):

// 文件: mooncake-transfer-engine/src/transport/rdma_transport/rdma_transport.cpp:183-303(简化伪代码)

int RdmaTransport::registerLocalMemoryInternal(void *addr, size_t length,
                                               const std::string &name, ...) {
    BufferDesc buffer_desc;
    // 对每个 RNIC context 注册 MR,获取 lkey/rkey
    for (auto &context : context_list_) {
        int ret = context->registerMemoryRegion(addr, length, access_rights);
        if (ret) { ... }
    }
    // 收集所有 context 的 lkey/rkey
    for (auto &context : context_list_) {
        buffer_desc.lkey.push_back(context->lkey(addr));
        buffer_desc.rkey.push_back(context->rkey(addr));
    }
    // 构造 BufferDesc 并写入 etcd
    buffer_desc.name = name;          // "cpu:0"
    buffer_desc.addr = (uint64_t)addr;
    buffer_desc.length = length;
    int rc = metadata_->addLocalMemoryBuffer(buffer_desc, update_metadata);
    return rc;
}
  1. metadata_->addLocalMemoryBuffer 将 BufferDesc 追加到本地 SegmentDesc,并 updateLocalSegmentDesc() 同步到 etcdtransfer_metadata.cpp:1059-1071
// 文件: mooncake-transfer-engine/src/transfer_metadata.cpp:1059-1071

int TransferMetadata::addLocalMemoryBuffer(const BufferDesc &buffer_desc,
                                           bool update_metadata) {
    {
        RWSpinlock::WriteGuard guard(segment_lock_);
        auto new_segment_desc = std::make_shared<SegmentDesc>();
        auto &segment_desc = segment_id_to_desc_map_[LOCAL_SEGMENT_ID];
        *new_segment_desc = *segment_desc;
        segment_desc = new_segment_desc;
        segment_desc->buffers.push_back(buffer_desc);   // 追加到缓冲区列表
    }
    if (update_metadata) return updateLocalSegmentDesc();  // 写入 etcd
    return 0;
}

为什么 initiator 也需要 registerLocalMemory

RDMA 操作同时需要两端的密钥

Initiator 自己的 source buffer 必须注册到 RNIC 才能获取 lkey。在 RDMA 操作中,Initiator 通过自己的 registerLocalMemory 获取 source buffer 的 lkey(本地密钥),通过 etcd 元数据中心获取 target buffer 的 rkey(远端密钥)——两者缺一不可。



Share this post on:

Previous Post
Mooncake TE 阅读手记-07-RDMA 寻址深度解析
Next Post
Mooncake TE 阅读手记-05-最小 C++ 示例全解