本文是Jeff Dean大神的The Tail at Scale一文的阅读笔记,本文介绍了一些缓解大规模响应式Web服务中延迟波动问题的技术。服务的提供方希望延迟的分布尽量窄,从而让服务的响应更加可预测。除了容忍错误,大规模容忍延迟长尾的能力也同样重要。
延迟波动产生的原因
响应时间产生长尾可能由多种原因造成:
- 共享资源:多个应用竞争硬件资源或者多个请求竞争软件资源;
- 守护进程:调度执行守护进程的时候会产生延迟;
- 全局资源共享:运行在不同机器上的应用会竞争一些全局资源(例如网关、共享文件系统等);
- 维护性活动:系统中的后台维护活动(例如带垃圾回收的编程语言的垃圾回收)也会造成延迟;
- 排队:在多个层级上的排队会加重延迟波动。
另外,硬件也会造成延迟波动:
- 功率限制:CPU通常会通过控制功率来控制温度;
- 垃圾回收:固态存储需要回收存储空间;
- 能耗管理:在切换省电模式时会产生额外延迟。
缓解大规模在线服务中延迟波动的通用方法是将多个子操作由多个组件并行处理。然而,单个组件的延迟波动会对整个系统造成更大的影响,因为一个单个机器的延迟会拖慢整个服务。延迟波动可以通过严格限制资源、软件实时性设计和提高稳定性来消除。
消除组件层面的波动
- 差异化服务类型和高层队列:计算机系统中存在很多队列,例如网络栈、磁盘写入等,可以考虑让上层应用自己维护一个队列,在确定需要执行操作的时候再将请求交给底层,这样应用程序可以实现对排队过程的控制。
- 消除线端阻塞:可以将耗时较长的请求分割为多个可以交替执行的请求;
- 管理后台活动和同步触发:例如编程语言运行时的垃圾回收、日志文件系统的日志压缩都会产生延迟波动。可以采用节流、将高负荷操作分割为低负荷操作以及在低负载的时候触发后台活动等方法解决。为了让某一机器的活动影响整体系统延迟,可以让所有机器同步执行一些活动。
容忍延迟波动
解决延迟波动的方法有两类:请求之内的短时调整和请求之间的长期调整。
请求之内的短期调整
- 对冲请求:一种简单的方法就是将一个请求发送给多个副本,然后使用最先返回的回复。一种实现就是当等待当前请求时间超过95%的时间之后,向第二个副本发起对冲请求。为了减少对冲请求的损耗,其实可以降低处理对冲请求的优先级。
- 捆绑请求:将请求再多个服务器上排队,并且服务器之间能够知道彼此的等待状态。如果一个服务器开始处理请求,需要告知其他服务器取消排队。服务器需要等待一个消息延迟确保取消消息的到达。
除了以上两种方法之外,还有种思路就是事先探测队列,然后将请求交给低负载的服务器。但是这个方法存在三个问题:
- 检测时和请求时的负载会改变;
- 待处理请求的用时通常难以预测;
- 如果客户端都选择低负载的服务器,那么这个服务器会进入高负载状态。
上面这些方法起作用的前提是:造成请求延迟波动的成因并不会影响其他请求。
请求之间的长期调整
一个常用的容忍延迟的方案是将数据分块均匀地分配给每台机器,一台机器对应一个分区,但是这面临两个问题:
- 每台机器的性能既不是均匀的,也不是一成不变的(因为温度控制、共享的负载干涉);
- 一些异常分区会导致负载不均衡(例如有些项目比较热门导致相应的分区负载增加)。
由于粗粒度现象(服务耗时变化和负载不均衡)造成的延迟波动可以用以下方法解决:
- 微分区:让分区数据远远大于机器数量,然后通过动态分配分区到机器来实现负载均衡。
- 选择性副本:检测那些导致负载不均衡的项目,然后通过创建项目额外的副本来缓解负载不均衡问题。
- 延迟感应察看:调度服务器可以将一些特别慢的机器移除或者列入观察,等延迟恢复之后再重新启用。
大规模的信息抽取系统
在大规模的信息抽取系统中,快速返回较好的结果比较慢地返回最好的结果要好。有两种技术解决这种系统中的延迟波动问题:
- 足够好:当从多个服务器请求查询结果时,可以适当放弃那些返回较慢的部分结果;
- 金丝雀请求:某些请求可能触及一些未测试的代码,会耗尽服务器资源或者导致服务器崩溃,可以将这些请求先发送给一两个服务器,当请求成功后再发给全部服务器。
优化修改的延迟
上面提到的技术都是针对读取操作的,对于修改操作来说甚至更加容易解决:
- 对延迟敏感的修改通常非常少;
- 修改操作可以在返回用户结果之后再进行;
- 很多服务都可以容忍不一致的更新模型;
- 要求一致更新的模型通常基于多数算法,本身就是可以处理延迟长尾的。
参考文献
- Dean, Jeffrey, and Luiz André Barroso. "The tail at scale." Communications of the ACM 56.2 (2013): 74-80.