团团虾导读: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 层面执行了两件事:
- 注册到所有已安装的 transport(
transfer_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;
}
- 在 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;
}
metadata_->addLocalMemoryBuffer将 BufferDesc 追加到本地 SegmentDesc,并updateLocalSegmentDesc()同步到 etcd(transfer_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 操作同时需要两端的密钥:
- 本地端的 lkey(local key)— 证明发起方有权读取/写入自己的本地内存
- 远端端的 rkey(remote key)— 证明发起方被授权访问远端内存
Initiator 自己的 source buffer 必须注册到 RNIC 才能获取 lkey。在 RDMA 操作中,Initiator 通过自己的 registerLocalMemory 获取 source buffer 的 lkey(本地密钥),通过 etcd 元数据中心获取 target buffer 的 rkey(远端密钥)——两者缺一不可。