低延迟系统的 11 个最佳实践

英文原文:11 Best Practices for Low Latency Systems

自从Google发布额外的一个500ms延迟将减少20%的流量以及亚马逊发现额外的100ms延迟会使销售量下降1%已经8年了。此后,开发者们一直奋战在延迟曲线的底部,甚至前端开发者们都在压缩JavaScriptCSS以及HTML来争取分毫时间。以下是各种低延迟系统设计时需牢记在心的最佳实践的一个概览。大多数这些建议考虑的是逻辑上极端,可以权衡使用。(感谢在Quora上问这个问题的匿名用户,这让我把我的想法写了下来)。

选择正确的语言

脚本语言不要使用,尽管它们越来越快,当你处理关键事务像拿掉进程的最后几毫秒时间时,你处理不掉解释型语言的开销。此外,你会想要一个强有力的记忆模型来进行无锁编程,所以你应该看Java、Scala、C++11或Go。 

把一切放在内存里

I/O会是延迟主因,所以确保你的所有数据在内存中。这通常意味着管理内存中的数据结构以及维护现有记录,这样你在重启机器或进程后能够重建之前的状态。维持记录的选择包括BitcaskKratiLevelDBBDB-JE。或者你也可以本地运行一个像redisMongoDB(memory >> data)这样的内存型数据库。但需要注意的是,在它们后台同步数据到硬盘crash时你仍旧可能丢失一些数据。

确保数据和处理程序的位置

网络跳数比磁盘寻道要快,但即使这样通过网络也会增加很多开销。理想情况下,数据应当完全在主机的内存中。像AWS云中几乎提供了1/4TB的内存,物理服务器提供多个TB现在也很常见。如果你需要运行在多个主机上,你应当确保数据和请求被适当的划分,使得在服务请求时所有必要的数据都在本地。 

保持系统未充分使用

低延迟总是需要有资源来处理新的请求。不要试图运行到你的软硬件资源的极限。总要有一些峰值储备用于突发情况。

保证内容切换最小化

内容切换是你正在运行的计算工作超过你拥有的资源的一个信号。你需要限制你的线程数使之与你系统的内核数相匹配,确保每个线程与它的主线程相关联。

确保读操作的连续性

各种形式的存储,无沦是轮转式的,还是基于闪存的或内存的,顺序使用的时候,它们的性能都会好一些。当进行内存的连续读操作时,你将会触发RAM级和CPU缓存级的预读处理。如果预读操作顺利的完成,那么下一步所需的数据在使用前都已在L1级的缓存中。促进这一处理的简便方法是:大量的使用原始数据类型的或结构体数组。不惜一切代价避免使用基于链表或者对象数组的随动指针。 

批量处理写入操作

这个听起来有背常理,但是你可以通过批量处理写操作获得明显的性能提升。然而,总有一些错觉:这就意味着在写入操作前系统需要等待任意的时间。相反地,线程在做输入/输出操作时会紧凑地循环。每次写操作都会在上一次操作完成后对到达的数据批量的处理。这种机制确保了系统快速的响应和良好的适应性。

优化缓存

在各个位置的优化中,内存的快速访问成了瓶颈。把线程与它们的主线性相关联有助于降低CPU缓存污染;连续性输入/输出处理,同样有利于缓存中的预加载。除此之外,你可以使用基本数据类型来降低内存的占用量,这样就可以有更多的数据加载到高速缓存中。另外你可参阅高速缓存参数无关算法,它通过递归的分解数据直到数据与缓存大小匹配,然后进行所需的处理。

尽可能多的使用非阻塞模式

使用非阻塞模式并使用不受约束的数据结构和算法。每次你在使用锁时都要深入到栈的操作系统层去处理锁,这是件令人头痛的事。通常如果你知道自己正在做什么,你得理解JVM、C++或者Go的内存模型才可以去应付锁的相关处理。

尽可能多的使用异步

任何处理特别是不需要创建响应输入/输出处理应当在关键路径之外处理。

尽可能多的并行处理

任何处理特别是可以并行操作的输入/输出处理应当并行的处理。例如你的高可用性策略包括把在磁盘中写入事务日志和把事务发送到二级服务器,这些动作都查可以并行处理的。

参考资料:

所有内容均来自于LMAX的Disruptor项目,您可以仔细研读或关注Martin Tompson的分享。

其它的博客:

posted @ 2014-02-24 15:28  李占卫  阅读(2051)  评论(0编辑  收藏  举报