人们总是错误地使用队列,最坏的情况是用它解决“超载(overload)”问题。Fred Hebert是《Learn You Some Erlang for Great Good!》一书的作者。在这本Erlang入门书籍中,他结合生动的插图、恰当的实例以浅显易懂的方式讲解了技术问题。近日,他以同样的方式阐释了为什么“队列不能解决超载”。
他将系统比作一个洗手池,如下所示:
在正常的操作下,数据只从左侧流入,出口可以处理所有数据。但在一些重大活动期间,比如圣诞节,可能会出现如下情况:
数据从左右两侧同时流入系统。如果数据输入越来越快,那么出口就可能会无法及时处理所有数据。这时,人们通常会考虑增加一个队列缓冲区(如上图的水槽)存储临时数据。但不管队列多大,持续的超载都会导致如下情况的发生:
队列满了,系统崩溃。这时候,开发人员会查看堆栈跟踪、队列、数据库查询以及调用的API。但经过各种优化,甚至更换更大的服务器后,系统仍然无法承受这种持续的超载,因为瓶颈在出口(下图中红箭头所示的位置):
该瓶颈可能是数据库,可能是磁盘、带宽或CPU。不消除这种瓶颈,任何优化都是徒劳。所以此时,开发人员应该做的是阻塞输入,即“反压(back-pressure)”或者丢弃数据,即“卸载(load-shedding)”。可能有人会认为,反压会招致用户的不满。但实际上,即使不主动反压,当系统负载达到一定程度后,速度也会降低,甚至崩溃。所以,虽然反压会降低用户的输入速度,但却可以保证系统的运行。另外,引入队列作为一种优化机制会违背端到端原则。因此,开发人员应该设置更多允许超时的地方,提供故障检测方法,并将其反馈给用户。
如上图所示,开发人员可以在识别出系统瓶颈后设置相应的反压机制,避免数据流入过快。而依据检查点的不同,开发人员可以对延迟和吞吐量实现不同层次的优化。
借助反压或卸载,开发人员可以获得以下好处:
- 合适的服务质量指标
- 减少紧急修复的次数
- 根据账户限制和优先通道收费的方式
- 系统更稳定
总之,如果API设计考虑了端到端原则和幂等性,那么反压或卸载对调用者而言通常不会成为问题,因为它们可以安全地重试请求。