网络基础回顾:TCP与UDP在行情场景下的抉择
做低延迟行情系统,第一个绕不开的问题就是:用 TCP 还是 UDP?
我见过不少团队,一上来就拍脑袋选 UDP,觉得它快。但实际跑起来,丢包、乱序、重传逻辑写得比业务代码还复杂。也有死守 TCP 的,结果行情峰值一来,延迟直接飙到毫秒级——在金融交易里,这已经算“灾难”了。
说白了,这不是简单的“谁快选谁”的问题。你得理解它们各自的脾气,再结合你的场景做取舍。
TCP:可靠但“慢”在哪里?
TCP 的可靠性,靠的是确认重传机制。你发一个包,对方得回一个 ACK,你才能发下一个。这本身就有延迟。
更关键的是,TCP 为了保证“公平”,内置了拥塞控制。当网络出现抖动,它会主动降低发送速率。这在行情场景下是致命的——行情数据不会等你,错过了就是错过了。
我记得有一次排查一个行情网关的延迟抖动,最后发现是 TCP 的慢启动在作祟。行情数据量突然增大,TCP 还在慢慢“试探”带宽,结果延迟从 50 微秒跳到了 2 毫秒。嗯,2 毫秒在别的场景不算什么,但在高频交易里,足够让策略亏钱了。
UDP:快,但你要自己兜底
UDP 没有握手,没有确认,没有重传。发出去就完了。延迟确实低,但代价是:丢包、乱序、重复,你都得自己处理。
在行情场景下,UDP 是主流选择。因为行情数据本身就有“时效性”——如果你丢了一个 tick,下一个 tick 马上就到,你完全可以忽略前一个。没必要为了一个过时的数据包,去阻塞后续所有数据。
我做过一个项目,用 UDP 接收美股行情。刚开始没做乱序处理,结果偶尔出现价格“回跳”——因为后发的包先到了,先发的包反而后到。你想想看,一个价格从 100 跳到 101,又跳回 100,策略直接懵了。
一张图看懂 TCP vs UDP 在行情场景下的抉择
下面这张图,是我自己总结的决策逻辑。每次做新项目,我都会拿出来看一眼。
Nagle算法与延迟的关系
Nagle 算法,说白了就是 TCP 的一个“攒包”机制。它会把多个小包攒成一个大的再发出去,目的是减少网络中的小包数量,提高带宽利用率。
但问题是,在低延迟场景下,这个“攒”的过程就是延迟。你想想看,行情数据来了,你希望立刻发出去,结果 Nagle 说“等等,再攒几个”,这一等可能就是几十毫秒。
我遇到过最夸张的一次,一个行情网关因为默认开启了 Nagle 算法,延迟从 100 微秒直接飙到 30 毫秒。排查了三天,最后发现是 Nagle 在作怪。从那以后,我所有低延迟项目的 TCP 连接,第一件事就是关掉 Nagle。
如何关闭 Nagle 算法?
在 Linux 下,通过 setsockopt 设置 TCP_NODELAY 选项即可:
int flag = 1;
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
就这么一行代码,延迟可能从毫秒级降到微秒级。但要注意,关闭 Nagle 后,小包会变多,网络带宽利用率会下降。在行情内网这种高带宽、低延迟的环境下,这点带宽浪费完全可以接受。
Socket缓冲区调优
Socket 缓冲区,是内核里的一块内存区域,用来暂存待发送或待接收的数据。它的大小直接影响延迟和吞吐量。
缓冲区太小,数据频繁被阻塞,延迟增加。缓冲区太大,内存浪费,而且数据在缓冲区里“排队”的时间也会变长。说白了,这是个 trade-off。
发送缓冲区 vs 接收缓冲区
| 缓冲区类型 | 作用 | 调优方向 |
|---|---|---|
| 发送缓冲区 | 暂存应用层写入、但尚未发送的数据 | 行情场景下,建议调小,减少数据在缓冲区内的排队时间 |
| 接收缓冲区 | 暂存内核收到、但应用层尚未读取的数据 | 行情场景下,建议调大,防止突发流量导致丢包 |
我个人的习惯是:发送缓冲区设为 16KB,接收缓冲区设为 256KB。这个配置在大多数行情场景下表现都不错。当然,具体数值还得根据你的数据量和网络带宽来微调。
如何调整 Socket 缓冲区大小?
同样通过 setsockopt:
int sendbuf = 16384; // 16KB
int recvbuf = 262144; // 256KB
setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sendbuf, sizeof(sendbuf));
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &recvbuf, sizeof(recvbuf));
缓冲区调优的避坑指南
- 不要盲目调大接收缓冲区:我曾经把接收缓冲区调到 1MB,结果内存占用飙升,反而因为缓存行失效导致性能下降。
- 注意内核参数限制:Linux 内核有 tcp_rmem 和 tcp_wmem 两个参数,分别控制 TCP 接收和发送缓冲区的最大值。你通过 setsockopt 设置的值不能超过这个上限。
- 结合 NUMA 架构考虑:在多核服务器上,Socket 缓冲区的内存分配可能跨 NUMA 节点,导致访问延迟增加。我建议绑定网卡中断和应用程序到同一个 NUMA 节点。
嗯,关于 TCP 与 UDP 的抉择、Nagle 算法、Socket 缓冲区调优,今天就聊这么多。这些基础概念看似简单,但真正在行情场景下用好,需要大量的实践和踩坑。希望我的经验能帮你少走一些弯路。