低延迟网络技术和银弹

4
在进行低延迟网络时,程序员和系统设计师应该考虑以下几点:
  1. 必须同时考虑硬件、系统和协议的设计

  2. 使用UDP而非TCP开发协议,并在应用层实现简单的ack-nak重传逻辑

  3. 尽量减少进程或线程消费和打包数据所需的上下文切换次数(最好为零)

  4. 选择操作系统的最佳选择器(如select、kqueue、epoll等)

  5. 使用具有大量内置缓冲区(FIFO)的高质量网卡和交换机

  6. 使用多个网卡,特别是针对下行和上行数据流

  7. 尽量减少其他设备或软件生成的中断请求(如果不需要,就移除它们)

  8. 减少互斥锁和条件的使用。在可能的情况下,使用无锁编程技术。利用架构的CAS能力。(无锁容器)

  9. 考虑单线程设计而非多线程设计-上下文切换非常昂贵。

  10. 了解并正确利用架构的缓存系统(L1/L2、RAM等)

  11. 优先掌握内存管理的完全控制权,而不是委托给垃圾回收器

  12. 使用高质量的电缆,尽量缩短电缆长度,减少扭曲和弯曲次数

我的问题:我想知道其他Stack Overflow用户认为在进行低延迟网络时还需要考虑哪些重要因素。 请随意评价以上观点。

9
网络延迟怎么可能算是主观的?它非常客观和可衡量。 - Jerry Coffin
2
对于第二点,如果你需要在UDP中实现确认等功能,那么你可能只是在实现类似TCP的功能,最好使用真正的TCP。 - seand
3
也许可以加入这样一句话:尽可能将机器专用于网络处理,以避免中断等与网卡竞争的情况。 - seand
3
关于 #11,这是一个很好的观点。人们喜欢提及现在垃圾收集(GC)有多么好,但仍然需要付出代价并可能导致不可预测的暂停。我还要补充一点,在连接期间尽量避免手动堆管理(malloc)。 - seand
1
也许更倾向于使用小的数据包大小。这可能会降低吞吐量(由于头部开销),但可以减少延迟,这是所述目标。 - seand
显示剩余5条评论
3个回答

8
电缆质量通常是一个误区。我认为更应该考虑连接网络分析仪,以查看是否出现了足够的重传以引起关注。如果有很多,则尝试隔离它们发生的位置,并更换导致问题的电缆。如果没有导致重传的错误,则电缆对延迟(几乎)没有影响。
网卡和(特别是)交换机上的大型缓冲区本身不会降低延迟。事实上,为了真正最小化延迟,通常要使用尽可能小的缓冲区,而不是较大的缓冲区。数据在缓冲区中而不是立即处理会增加延迟。说实话,这很少值得担心,但还是要注意。如果您真的想最小化延迟(并且对带宽的关注度较低),您最好使用集线器而不是交换机(现在比较难找到,但只要网络拥塞足够低,延迟就会很低)。
多个网卡可以大大提高带宽,但对延迟的影响通常相当小。
编辑:然而,我的主要建议是要有一个尺度感。将网络电缆缩短一英尺可以节省约一纳秒的时间,与加速处理数据包的几个汇编语言指令相同。
底线:像任何其他优化一样,要想走得更远,您需要测量延迟出现的地方,然后再采取措施来减少它。在大多数情况下,缩短电线长度(以一个例子)不会有足够的差异值得注意,因为它本来就很快。如果某些东西开始需要10微秒,那么除非您的速度非常快,否则无论您做什么,都不会使其加速超过10微秒,因此,除非10微秒在您的时间中占据了重要的百分比,否则没有攻击的必要。

3
有时候电缆技术员和网络管理员会将电缆剪得比所需长度更长,然后扭曲电缆并藏在机柜的边角处以保持整洁。扭曲双绞线信号传输的问题在于,这种行为可能导致信号严重衰减,在微秒级的延迟考虑时会引起问题。 - Gelly Ristor
@Gelly:这是数字信号。只有两种可能性:要么它将信号降解到需要重新传输数据包的程度(我已经建议查找此问题),要么它被正确接收,并且不会引起任何额外的延迟(除了额外电线的纳秒/英尺)。 - Jerry Coffin
3
不,他说的有道理。在当前网络传输速度下,你必须将传输视为一种混合的模拟-数字设计,并且弯曲双绞线电缆中的信号衰减实际上是一种天线效应。无疑,天线效应属于模拟方面。 - MSalters
1
@MSalters:是的,但影响(如果有)就是需要重新传输数据包。你谈论的是可能的原因;我谈论的是影响,并指出如果没有这种影响,那么它不会增加延迟。如果您正在接收重传,则弯曲的电缆可能是原因-但如果您没有接收到重传,则更换/缩短/整理电缆只是在寻找问题的解决方法。 - Jerry Coffin
这是完全正确的;您可以在数字方面衡量影响。以太网的CRC32足够强大,你会有比无声错误更多的重传。但是计算错误并不是真正的设计步骤,而且我认为问题是在设计阶段该怎么做。在那个阶段,让您的电缆计划正确,以后修复要花费更多。 - MSalters

6

其他:

1:使用用户态网络堆栈

2:在相同的套接字上服务中断,与处理代码共享缓存

3:优先选择固定长度协议,即使它们在字节方面稍微大一些(解析更快)

4:忽略网络字节顺序约定,只使用本地顺序

5:不要在例程和对象池中分配内存(尤其是垃圾收集语言)

6:尽可能减少字节拷贝(在TCP发送中较难实现)

7:使用截止式交换模式

8:修改网络堆栈以消除TCP慢启动

9:广告一个巨大的TCP窗口(但不要使用它),这样对方可以同时拥有很多未决包

10:关闭NIC合并,特别是发送时(如果需要,则在应用程序堆栈中分组)

11:更喜欢铜线而不是光纤

我还可以继续说下去,但这应该让人们思考了

我不同意的一个观点:

1:网络电缆很少出问题,除非损坏了(在电缆类型方面有一个例外)


3

这可能有些显而易见,但这是一种我很满意的技术,它适用于UDP和TCP,因此我将写下来:

1)永远不要排队大量的输出数据:特别是,尽可能避免将内存中的数据结构转换为序列化字节缓冲区,直到最后一刻。相反,当您的发送套接字select()准备好写入时,展平相关/脏数据结构的当前状态,并立即发送(send())出去。这样,数据就不会在发送方“积累”。 (还要确保将套接字的SO_SNDBUF设置为尽可能小,以最小化内核内部的数据排队)

2)如果您的数据以某种方式进行了键控,则可以在接收端执行类似的操作:而不是执行(读取数据消息、处理数据消息、重复)循环,您可以读取所有可用的数据消息,然后将它们放入一个具有键控数据结构(例如哈希表)中,直到套接字没有更多可用的数据可读取,然后(仅在此时)遍历数据结构并处理数据。这样做的好处是,如果您的接收客户端必须对接收到的数据执行任何非常规处理,则过时的传入消息将自动/隐式丢弃(因为它们的替换将覆盖它们在键控数据结构中),因此传入的数据包不会堵塞在内核的传入消息队列中。 (当然,您可以让内核的队列填满并丢弃数据包,但这样一来,您的程序将读取“旧”数据包并丢弃“较新”的数据包,这通常不是您想要的)。作为进一步的优化,您可以使I/O线程将键控数据结构移交给单独的处理线程,以便处理不会受到I/O的阻止。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接