第一章:延迟的构成——拆解一次报价路由请求的全链路延迟
做市商报价路由,说白了就是一场与时间的赛跑。你报出去的价格,早到一毫秒,可能就成交了;晚到一毫秒,行情已经变了。我刚开始做这套系统时,总觉得延迟是个黑盒子——只知道慢,但不知道慢在哪。后来我养成了一个习惯:把一次报价请求从发出到成交,拆成五段来看。
这五段分别是:网络延迟、操作系统内核延迟、协议栈开销、应用层处理延迟、交易所撮合引擎延迟。每一段都有它的脾气。咱们一个一个说。
1. 网络延迟:物理定律是绕不过去的坎
网络延迟,说白了就是光在光纤里跑的时间。你想想看,从上海到东京,光速再快也得几十毫秒。这还没算上交换机、路由器这些中间设备带来的额外开销。
我在项目中遇到过最典型的情况:两个机房之间直线距离不到10公里,但网络延迟硬是多了2毫秒。查到最后发现,路由绕了个大圈,走了三个跳点才到。后来我们直接拉了专线,延迟直接砍掉一半。
这里有个避坑指南:千万别信网络供应商给的“理论延迟”。我曾经被坑过一次,对方说延迟小于1毫秒,结果实测3毫秒。后来我们坚持自己用硬件打时间戳测,才拿到真实数据。
2. 操作系统内核延迟:你写的代码不是直接跑在硬件上的
很多人以为,应用程序发一个网络包,直接就出去了。其实不是。你的数据要先经过操作系统内核,内核帮你做路由、做协议封装、做中断处理。这一套下来,几十微秒就没了。
我习惯把内核延迟拆成三块:
- 系统调用开销:从用户态切到内核态,一次上下文切换大约1-2微秒。你发一个包,至少两次切换(发送和接收)。
- 中断处理:网卡收到数据后,会触发中断。中断处理期间,CPU要保存现场、执行中断服务程序。这个时间不固定,但通常在几微秒到几十微秒之间。
- 调度延迟:如果你的进程被其他任务抢占了,那就要等下次被调度才能处理数据。这个时间最不可控,可能几微秒,也可能几毫秒。
3. 协议栈开销:TCP还是UDP,这是个问题
协议栈开销,说白了就是网络协议处理数据包时消耗的CPU时间。TCP和UDP的开销差别很大。
| 协议 | 典型开销(微秒) | 主要消耗点 |
|---|---|---|
| UDP | 1-3 | 校验和计算、头部解析 |
| TCP | 5-15 | 序列号管理、确认重传、拥塞控制 |
做市商报价路由,几乎清一色用UDP。为什么?因为TCP的确认重传机制,在低延迟场景下就是灾难。你想想看,一个报价包丢了,TCP要等超时重传,这一等就是几十毫秒。而UDP丢了就丢了,反正下一笔报价马上就来。
不过,UDP也有坑。我记得有一次,我们用的UDP包太大,超过了MTU,结果被IP层分片了。分片后的包,只要丢一片,整个包就废了。后来我们强制把包大小控制在MTU以内,才解决这个问题。
4. 应用层处理延迟:你的代码才是最大的变量
应用层处理延迟,这是咱们自己能控制的部分。我见过太多团队,网络延迟优化到极致了,结果应用层代码写得稀烂,一测延迟,全耗在业务逻辑里了。
应用层延迟通常包括:
- 数据序列化/反序列化:把内存里的数据结构转成网络字节流。用Protobuf还是FlatBuffers,差别很大。我个人习惯用FlatBuffers,因为它不需要解析,直接内存映射就能读。
- 业务逻辑计算:比如计算报价、检查风控规则。这里要注意,别在关键路径上做复杂计算。我建议把风控检查拆成两阶段:快速预检(几微秒)和深度检查(异步做)。
- 内存分配:频繁的malloc/free会导致延迟抖动。我习惯用对象池或者预分配内存,避免在运行时分配。
5. 交易所撮合引擎延迟:这是你管不了的部分
交易所的撮合引擎,说白了就是人家的地盘。你报进去的价格,要等交易所的撮合引擎处理完,才能知道是否成交。这个延迟你控制不了,但你可以测量它。
我习惯用往返时间(RTT)来估算交易所延迟。具体做法:
- 记录报价发出的时间戳(T1)。
- 记录收到成交回报的时间戳(T4)。
- 估算网络往返时间(RTT_net)。
- 交易所延迟 ≈ (T4 - T1) - RTT_net。
不过,这个估算有个前提:你的时间戳要足够精确。我建议用硬件时间戳,比如网卡自带的PTP功能。软件时间戳误差太大,测出来的数据基本没法用。
另外,不同交易所的撮合引擎延迟差异很大。有的交易所能做到微秒级,有的交易所要几毫秒。选交易所的时候,这个指标一定要看。
全链路延迟的SVG结构图
下面这张图,把一次报价路由请求的全链路延迟拆解清楚了。你可以看到,每一段延迟都有它的来源和优化方向。
小结
一次报价路由请求的延迟,不是单一因素决定的。网络延迟、内核延迟、协议栈开销、应用层处理、交易所撮合引擎,每一段都有它的优化空间。我个人习惯,先测量、再优化。没有数据支撑的优化,都是瞎忙活。
嗯,这一章就到这里。下一章咱们聊聊如何测量这些延迟——毕竟,你连延迟在哪都不知道,怎么优化?