首页 / 资讯中心 / 文章详情

140、RDMA编程案例:基于RDMA的金融行情分发系统

140、RDMA编程案例:基于RDMA的金融行情分发系统 ★ FEATURED ARTICLE
一、从一次诡异的行情延迟说起去年做券商极速交易系统时遇到一个让人抓狂的问题行情数据通过TCP分发偶尔会出现几十毫秒的延迟抖动。用perf top一看网卡中断处理占了CPU的15%内核协议栈的锁竞争在行情爆发时直接导致优先级反转。当时团队里有人提议上FPGA但考虑到成本和灵活性我坚持用RDMA改造——结果第一个版本跑起来延迟反而比TCP还高。排查了两天发现是QPQueue Pair数量配置不当。我们给每个订阅者分配独立的QP行情快照时同时触发上百个WRWork Request提交导致HCAHost Channel Adapter内部拥塞。后来改成共享QP多播机制延迟直接从15μs降到3μs。这个教训让我意识到RDMA不是简单的“替换TCP”它的性能释放依赖对硬件队列模型的理解。二、行情分发系统的核心设计约束金融行情系统对延迟极度敏感通常要求端到端延迟10μs同时支持数千个订阅者。传统TCP方案的问题在于内核协议栈中断处理每个数据包触发中断行情爆发时中断风暴导致CPU空转内存拷贝数据从网卡到内核再到用户态至少两次拷贝锁竞争多线程处理socket时accept/send的锁开销在连接数增多时指数级增长RDMA的解决方案是绕过内核让HCA直接与应用内存交互。但这里有个关键点行情分发是“一对多”模型而RDMA原生支持的是“一对一”的RC可靠连接和“一对多”的UD不可靠数据报。我们最终选择了混合方案核心行情源使用UD多播一个WR就能将数据发送到多播组的所有成员控制指令如订阅/退订使用RC保证可靠性每个订阅者维护一个接收缓冲区环通过轮询而不是中断来获取数据三、代码实现中的关键细节3.1 初始化阶段别踩内存注册的坑// 分配接收缓冲区注意这里必须使用hugepage// 别用malloc否则注册到HCA时性能会暴跌char*recv_bufmmap(NULL,BUF_SIZE,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB,-1,0);if(recv_bufMAP_FAILED){// 如果hugepage没配置回退到普通页但延迟会高30%recv_bufaligned_alloc(4096,BUF_SIZE);}// 注册内存区域到HCAstructibv_mr*mribv_reg_mr(pd,recv_buf,BUF_SIZE,IBV_ACCESS_LOCAL_WRITE|IBV_ACCESS_REMOTE_WRITE);// 这里踩过坑忘记设置IBV_ACCESS_REMOTE_WRITE导致接收端无法写入内存注册是RDMA最容易被忽视的性能瓶颈。HCA需要将虚拟地址映射到物理地址这个过程涉及IOMMU页表更新。如果缓冲区是动态分配的每次注册都会触发TLB shootdown。我们的做法是在初始化时预分配一个大的内存池按需切分避免运行时频繁注册。3.2 多播组的建立别这样写// 错误示范每个订阅者单独加入多播组for(inti0;inum_subscribers;i){// 这样会导致HCA内部多播转发表频繁更新ibv_attach_mcast(qp,mgid,i);}// 正确做法先创建多播组再让所有QP加入同一个组structibv_ah*ahibv_create_ah(pd,ah_attr);// 注意多播组的GID必须与网卡端口匹配否则收不到数据多播组的创建有个隐藏陷阱不同厂商的HCA对多播组的数量有限制。Mellanox CX5系列最多支持64个多播组超过后性能会断崖式下跌。我们的行情系统只使用一个多播组所有行情数据通过同一个GID发送订阅者通过应用层过滤比如在接收端检查行情代码来区分。3.3 发送端的WR提交轮询比事件更可靠// 发送行情快照structibv_send_wrwr,*bad_wr;structibv_sgesge;// 填充WR结构wr.wr_id(uint64_t)行情序号;// 用于后续完成事件追踪wr.opcodeIBV_WR_SEND;wr.send_flagsIBV_SEND_SIGNALED;// 必须设置否则不知道发送完成wr.sg_listsge;wr.num_sge1;sge.addr(uint64_t)行情数据指针;sge.length行情数据长度;sge.lkeymr-lkey;// 提交WR到发送队列if(ibv_post_send(qp,wr,bad_wr)){// 这里踩过坑忘记检查返回值导致WR丢失fprintf(stderr,ibv_post_send failed, bad_wr id: %lu\n,bad_wr-wr_id);}发送完成后必须轮询完成队列CQ来回收WR。我们使用一个独立的线程轮询CQ而不是依赖事件通知——事件通知在行情爆发时会产生大量上下文切换延迟反而更高。// 轮询CQ注意这里不能阻塞太久structibv_wcwc;while(1){intneibv_poll_cq(cq,1,wc);if(ne0){if(wc.status!IBV_WC_SUCCESS){// 处理错误通常是QP进入错误状态handle_qp_error(qp,wc);}// 根据wr_id回收发送缓冲区free_send_buf(wc.wr_id);}// 如果没有完成事件检查是否有新行情要发送if(has_new_market_data()){submit_send_wr();}}3.4 接收端的零拷贝设计接收端的关键是避免数据拷贝。我们让每个订阅者维护一个环形缓冲区HCA直接写入这个缓冲区应用层通过指针访问。// 接收缓冲区环每个槽位包含数据和完成标志structrecv_slot{chardata[MAX_MSG_SIZE];volatileuint32_tcompleted;// 使用volatile防止编译器优化};// 预先提交所有接收WRfor(inti0;iRING_SIZE;i){structibv_recv_wrwr,*bad_wr;structibv_sgesge;sge.addr(uint64_t)ring[i].data;sge.lengthMAX_MSG_SIZE;sge.lkeymr-lkey;wr.wr_idi;wr.sg_listsge;wr.num_sge1;ibv_post_recv(qp,wr,bad_wr);}// 应用层轮询检查完成标志// 别用ibv_poll_cq那样会引入额外的系统调用while(1){uint32_theadatomic_load(shared_head);// 原子操作获取最新位置while(currenthead){process_market_data(ring[current%RING_SIZE].data);// 处理完后重新提交接收WRrepost_recv_wr(current%RING_SIZE);current;}}这里有个性能优化点我们使用共享内存的原子变量来传递head指针而不是通过CQ事件。这样应用层可以完全在用户态轮询避免了任何系统调用。代价是如果应用层处理太慢会导致接收缓冲区溢出——所以环形缓冲区的大小要根据行情峰值速率计算通常设置为能缓存100ms的数据量。四、调试中的血泪教训4.1 网卡固件版本导致的多播丢包上线前压测发现多播组中总有0.1%的订阅者收不到行情。用ibv_devinfo查看发现HCA固件版本是2.42.1000而Mellanox官方文档说2.42.5000以上才修复了多播转发表的bug。升级固件后问题消失。4.2 内存对齐引发的性能抖动有段时间行情延迟在2μs到8μs之间随机波动。用perf stat看发现L1缓存miss率异常高。排查发现接收缓冲区没有按cache line对齐导致HCA写入时触发了伪共享。改成64字节对齐后延迟稳定在3μs以内。4.3 QP数量与CPU亲和性的关系最初我们为每个订阅者创建独立的QP结果发现当订阅者超过200个时ibv_post_send的延迟从1μs飙升到20μs。原因是HCA内部的调度器需要维护大量QP状态。后来改成共享QP应用层分发QP数量控制在16个以内每个QP绑定到固定的CPU核心延迟恢复。五、性能数据与调优建议最终系统在10Gbps网络上实现了端到端延迟平均2.8μs99.9%分位4.1μs吞吐量单QP每秒300万笔行情每笔64字节支持1024个订阅者CPU占用率仅15%几个个人经验不要迷信RDMA的“零拷贝”零拷贝的前提是内存注册和地址映射已经完成。如果频繁注册/注销内存性能比TCP还差。预分配大内存池是必须的。轮询比事件驱动更适合行情场景事件通知在低负载时省CPU但在行情爆发时中断处理的开销会吃掉所有性能增益。我们的做法是行情时段用轮询空闲时段切到事件等待。多播组不是银弹RDMA多播依赖硬件多播树交换机需要支持IGMP Snooping。如果网络中有不支持RDMA多播的交换机数据会广播到所有端口反而增加延迟。调试工具链要提前准备ibv_devinfo、ibstat、perftest这些工具要提前验证。我们曾因为网卡端口配置错误link_layerInfiniBand vs Ethernet浪费了两天时间。最后也是最重要的RDMA编程的本质是管理硬件队列。理解QP、CQ、SRQ共享接收队列的硬件实现比记住API参数更重要。遇到性能问题先看HCA的队列深度和拥塞状态而不是盲目调代码。行情系统上线后延迟抖动从TCP的50ms降到RDMA的5μs以内。但运维同学抱怨说RDMA的调试比TCP难十倍——没有tcpdump没有netstat出了问题只能靠ibv_wc的error code猜。所以如果你决定用RDMA请确保团队里有至少一个人能看懂HCA的硬件寄存器手册。关键词核心关键词RDMA、QP、HCA、WR、TCP、CQ、GID、多播、中断、UD精准长尾关键词RDMA编程案例、基于RDMA的金融行情分发系统、一、从一次诡异的行情延迟说起、二、行情分发系统的核心设计约束、三、代码实现中的关键细节、2 多播组的建立别这样写、RDMA编程案例详解、RDMA编程案例实战、RDMA编程实战、RDMA性能优化、RDMA协议详解、高速网络编程
阅读完成 · 觉得有帮助?
咨询建站