4、协议层优化:UDP vs TCP、内核旁路技术(DPDK、Solarflare)、RDMA与InfiniBand

做跨所套利,最怕什么?

怕延迟。更怕延迟抖动。

我见过太多团队,策略逻辑写得天衣无缝,结果一上实盘就亏钱。查到最后,问题出在网络协议栈上。说白了,你辛辛苦苦算出的价差信号,在操作系统内核里排队等着被处理,等轮到它的时候,黄花菜都凉了。

这一章,我们就来聊聊协议层怎么优化。我会从最基础的UDP和TCP选择讲起,一直聊到DPDK、Solarflare、RDMA这些“硬核”技术。嗯,都是我在项目中踩过的坑和总结的经验。

4.1 UDP vs TCP:延迟敏感场景下的生死抉择

很多新手会问:TCP那么可靠,为什么不用?

我的回答是:在跨所套利里,可靠性不等于低延迟

核心结论:跨所套利场景,99%的情况选UDP。TCP的重传机制和拥塞控制,在低延迟场景下是灾难。

我来说说为什么。

TCP为了保证数据不丢,搞了一套复杂的机制:三次握手、序列号、确认应答、超时重传。这些机制在文件传输、网页浏览里是功臣,但在毫秒必争的套利场景里,它们就是累赘。

举个例子。你通过TCP发送一个订单,网络丢了一个包。TCP会怎么做?它会等超时,然后重传。这个超时时间,Linux默认是200ms到3秒。你想想看,200ms在套利世界里是什么概念?足够价格跑出好几个来回。

UDP就不一样了。它只管发,不管收没收到。丢了?丢了就丢了。在套利场景里,我们更愿意接受偶尔丢一个包,也不愿意为了等重传而卡住整个流程。

特性 TCP UDP
连接建立 三次握手(增加RTT) 无连接(零开销)
可靠性 自动重传(延迟不可控) 不保证(应用层自行处理)
拥塞控制 有(会主动降速) 无(全速发送)
头部开销 20字节 8字节
适用场景 行情快照、批量数据 订单流、逐笔行情

我的经验:UDP丢包怎么办?我在项目中采用的做法是:在应用层实现一个轻量级的确认机制。比如每100微秒发一个心跳包,如果连续3个心跳没收到,就认为链路有问题,切换到备用线路。这样既保留了UDP的低延迟,又有了基本的可靠性保障。

4.2 内核旁路技术:绕过操作系统这个“中间商”

选UDP就够了吗?不够。

你想想看,数据从网卡到应用程序,中间要经过多少层?网卡驱动、内核协议栈、socket缓冲区、系统调用……每一层都是延迟。我实测过,在标准Linux下,一个UDP包从网卡到用户态程序,平均延迟在5-10微秒。看起来不多?但在高频场景里,这已经很大了。

怎么解决?内核旁路。说白了,就是让应用程序直接跟网卡对话,绕过内核这个“中间商”。

4.2.1 DPDK:数据面开发套件

DPDK(Data Plane Development Kit)是目前最主流的内核旁路方案。它的核心思想就一句话:把网卡的控制权从内核手里抢过来,交给用户态程序

具体怎么做?

  • UIO(Userspace I/O):通过UIO驱动,把网卡的硬件寄存器映射到用户态空间。应用程序可以直接读写寄存器,不需要系统调用。
  • 大页内存:DPDK使用2MB或1GB的大页,减少TLB miss。我测过,用大页比普通页能降低20%的延迟抖动。
  • 轮询模式:传统网卡用中断通知CPU“有数据来了”。中断有开销,而且在高并发下会触发中断风暴。DPDK改用轮询,CPU一直问网卡“有数据吗?”,虽然占CPU,但延迟极低且稳定。
  • 无锁队列:DPDK内部使用无锁环形队列(ring buffer),避免锁竞争。
// DPDK初始化示例(简化版)
int main(int argc, char *argv[]) {
    // 1. 初始化EAL(环境抽象层)
    rte_eal_init(argc, argv);
    
    // 2. 分配内存池
    struct rte_mempool *mbuf_pool = rte_pktmbuf_pool_create("MBUF_POOL", 
        NUM_MBUFS, MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
    
    // 3. 初始化网卡端口
    rte_eth_dev_configure(port_id, 1, 1, &port_conf);
    rte_eth_rx_queue_setup(port_id, 0, 1024, rte_eth_dev_socket_id(port_id), NULL, mbuf_pool);
    rte_eth_dev_start(port_id);
    
    // 4. 轮询收包
    while (1) {
        struct rte_mbuf *bufs[BURST_SIZE];
        uint16_t nb_rx = rte_eth_rx_burst(port_id, 0, bufs, BURST_SIZE);
        // 处理收到的包...
    }
}

避坑指南:我曾经在一个项目里,DPDK收包延迟从2微秒突然跳到50微秒。查了两天才发现,是CPU频率缩放(frequency scaling)搞的鬼。DPDK默认把CPU跑在省电模式,频率一降,轮询效率就崩了。解决方案:用cpupower工具把CPU固定在最高频率,或者用isolcpus内核参数把CPU隔离出来。

4.2.2 Solarflare:硬件加速的另一种思路

DPDK是纯软件方案,Solarflare走的是硬件路线。Solarflare网卡自带一个可编程的FPGA,叫EFCT(Enhanced Firmware Core Technology)。

什么意思呢?就是你可以把一些网络处理逻辑直接烧到网卡上。比如TCP/IP协议栈、甚至部分应用逻辑,都在网卡上完成。CPU只需要从网卡的内存里直接读数据就行。

我对比过,Solarflare的延迟比DPDK还要低1-2微秒。但代价是贵,而且绑定硬件。DPDK是开源的,随便什么网卡都能用(当然,Intel网卡效果最好)。

方案 延迟(典型值) 成本 灵活性 部署难度
标准Linux协议栈 5-10微秒
DPDK 1-3微秒
Solarflare 0.5-1微秒
RDMA 0.5-2微秒

4.3 RDMA与InfiniBand:终极武器

如果说DPDK和Solarflare是“绕过内核”,那RDMA就是“绕过CPU”。

RDMA(Remote Direct Memory Access)允许一台机器的应用程序直接读写另一台机器的内存,不需要经过CPU、不需要经过操作系统、不需要经过协议栈。数据从源端网卡直接到目的端内存,延迟可以做到1微秒以内。

RDMA有三种实现方式:

  • InfiniBand:原生RDMA,从硬件到协议都是为RDMA设计的。延迟最低,但需要专用网卡和交换机。价格嘛……嗯,一套InfiniBand网络够买好几台服务器了。
  • RoCE(RDMA over Converged Ethernet):在标准以太网上跑RDMA。成本低,但需要交换机支持PFC(优先级流控),否则丢包会导致性能雪崩。
  • iWARP:基于TCP的RDMA。兼容性好,但延迟比前两者高。

我的建议:如果你的交易所之间距离很近(同一个机房),而且预算充足,直接上InfiniBand。如果跨机房,RoCE v2是更现实的选择。iWARP我一般不推荐,它的延迟优势不明显,还不如用DPKD。

RDMA的编程模型跟传统socket完全不同。它使用“verbs”接口,核心概念包括:

  • QP(Queue Pair):发送队列和接收队列的组合。每个QP相当于一个连接。
  • MR(Memory Region):注册一段内存,让网卡可以直接访问。注册后,网卡知道这段内存的物理地址和权限。
  • WR(Work Request):提交给QP的操作请求,比如“发送这个buffer”或“接收数据到这个buffer”。
  • CQ(Completion Queue):操作完成后,网卡把完成事件放到CQ里,应用程序轮询CQ获取结果。
// RDMA发送示例(伪代码)
// 1. 创建QP
struct ibv_qp *qp = ibv_create_qp(pd, &qp_init_attr);

// 2. 注册内存区域
struct ibv_mr *mr = ibv_reg_mr(pd, buffer, BUFFER_SIZE, 
    IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE);

// 3. 提交发送请求
struct ibv_send_wr wr = {0};
wr.wr_id = 1;
wr.opcode = IBV_WR_SEND;
wr.send_flags = IBV_SEND_SIGNALED;
wr.sg_list = &sge;
wr.num_sge = 1;
ibv_post_send(qp, &wr, &bad_wr);

// 4. 轮询完成队列
struct ibv_wc wc;
while (ibv_poll_cq(cq, 1, &wc) == 0) {
    // 等待完成
}

避坑指南:RDMA最坑的地方是内存注册。我刚开始用的时候,每发一个包就注册一次内存,结果延迟比TCP还高。后来才明白,内存注册是 heavyweight 操作,应该在初始化时一次性注册好,后续只复用。另外,RDMA的buffer大小要跟MTU对齐,否则会触发分片,延迟飙升。

4.4 知识体系总览

说了这么多,我画了一张图,帮你理清思路。

协议层优化知识体系 协议选择:UDP vs TCP 内核旁路技术 DPDK 软件方案,开源免费 延迟:1-3微秒 Solarflare 硬件加速,FPGA卸载 延迟:0.5-1微秒 RDMA / InfiniBand 绕过CPU,直接内存访问 延迟:0.5-2微秒 目标:亚微秒级延迟 + 零抖动

从这张图你可以看到,优化路径是清晰的:先选对协议(UDP),再绕过内核(DPDK/Solarflare),最后如果条件允许,直接上RDMA。每一步都能把延迟再压低一个数量级。

最后说一句,技术选型没有银弹。我在一个项目里用DPDK跑得很好,换到另一个环境就各种问题。关键是要理解每项技术的原理和适用场景,然后根据你的实际情况做取舍。嗯,这就是我这些年最大的体会。