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

八年前,谷歌发现每 500ms 的延迟会让网络堵塞程度增加 20%,而亚马逊也察觉 100ms 的延迟会使销量降低 1%。从那以后,开发者就在减少延迟方面绞尽脑汁,以至于前端开发者试图从他们用的 JS、CSS 甚至 HTML 中挤出每一毫秒。这篇文章接下来要介绍的就是在设计低延迟系统是要时刻注意的一些最佳实践。大部分的建议看似荒谬却被证实为是有用的,当然有时也需要一些权衡。(感谢在Quora 提出这个问题的匿名用户,促使我将我的想法写下来)。

选择合适的语言

脚本语言就不需要考虑这一点。虽然编程语言一直以来都在变得越来越快,只要你试图将处理时间削减最后那么几毫秒,你就不能忽视解释型语言。此外,你可能会需要一个强大的内存模式来进行无锁编程,那么你就要考虑使用 Java,、Scala、C++11或者Go。

尽量使用内存

I/O会增加延迟,所以要确保所有的数据都在内存中。这通常意味着要管理你的内存数据结构,维护持久日志,这样当机器或者进程重启的时候能够恢复之前的状态。创建持久日志的工具有Bitcask、 Krati、LevelDB 和 BDB-JE。除此之外,你可能可以投巧地运行一个本地的,持久的内存数据库,比如说 Redis 或者MongoDB (当内存>>数据)。需要注意的是,当系统崩溃的时候你可能会丢失一些数据,因为它们的后台和硬盘是同步的。

保持数据和处理在同一位置

网络跃迁(hop)比硬盘搜索要快,但是它们仍然会间接增加延迟。理想说来,你的数据应该完全能够放的进一台主机里的存储器。AWS能够提供将近 1/4TB 的云内存,而物理服务器通常有很多 TB 的空间。如果你需要使用多台主机,那么你要确保你正确地划分好了数据和请求,这样服务所需要的所有数据就都是本地可取的。

保持系统未充分利用

低延迟要求一直有资源来处理请求。不要试图将你的硬件/软件用到极限。要永远给突发情况预留好空间。

最小化环境切换

环境转换意味着你在处理多于现有资源可以处理的计算任务。你应该将线程数量减少到系统内核数量,然后各个线程运行于各自的内核中。

有序读取

任何形式的存储,无论是旋转存储、闪存,还是内存,当有序使用的时候都会大大提高性能。当你有序读取内存时,你触发了 RAM 和 CPU 缓存级别的预取功能。如果处理正确,你需要的下一块数据会在你需要它之前就到达L1缓存。帮助这种处理的最简单的方法就是尽量使用原始数据类型或者结构的数组。按照这个原则,链表和对象数组的使用是无论如何都要避免的。

批量处理写任务

这听上去违反直觉,但是批量写可以大大提高性能。但是,这有一个误区,就是这意味着系统必须在执行写任务之前要等待一段时间。其实不然,一个线程应该是在一个紧凑的循环中不停地执行输入输出。每一个写操作都会批量处理自从上一次执行写任务之后接收到的所有数据。这样一个既快速又有适应性的系统就生成了。

重视缓存

经过所有这些优化处理后,快速访问内存就变成了一个瓶颈。将线程固定于他们自己的内核中可以减少 CPU 缓存污染,序列化输入输出也可以预取缓存。此外,使用原始数据类型可以减小内存大小,这样缓存就可以放下更多的数据。你也可以看看这个缓存导向算法:递归分解数据直到能够放进缓存中,然后再做其它必要的处理。

尽可能无锁

和无锁并无等待的数据结构和算法交个朋友。每次你使用锁,你都必须回溯到 OS 去干涉锁,这个锁就是个很大的系统开销。通常说来,如果你知道你在做什么,你就可以通过了解JVM、C++ 或者 Go 的内存模式来避开锁。

尽可能异步

任何程序,尤其任何 I/O,只要不是建立响应所必须的,就应该在关键路径之外进行。

尽可能并行化

任何程序,尤其任何I/O,只要能并行就应该并行处理。比如说如果你的高可用策略包括把交易日志写进硬盘并发送给第二个服务器,那么这些行为都是可以并行的。

1 4 收藏 评论

关于作者:Brook

进阶中的程序猿(新浪微博:@店小不2) 个人主页 · 我的文章 · 10

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部