对于 Web 服务而言,提升可扩展性的主要途径是将耗时的同步工作改成异步处理,从而允许将这些工作“外包”给多个 Worker 去做,或者提前完成能够预知的部分。
异步机制与可扩展性之间的关系需要从(异步)并行处理的优势说起。
串行、并发与并行
可扩展性,意味着能通过向系统添加资源的方式应对不断增加的工作量。
对于多个任务,一般有 3 种执行策略:
- 串行:一个接一个地同步执行,总耗时为所有任务之和
- 并发:多 CPU/多核并发执行,总耗时取决于最慢的任务耗时
- 并行:异步并行执行,总耗时同样取决于最慢的任务耗时
异步的意义
除了可扩展性方面的优势外,异步更多的意义在于:
- 可打断/可暂停:允许分片执行,而不必一次做完
- 可调度:允许更细粒度的时间管理,比如闲时主动执行、甚至离线执行
- 并行的可能性:异步返回结果,意味着任务可以交给其他worker执行
- 解耦生产者和消费者:把worker分离出去,使之能够独立扩展,比如只加worker而不加web server
消息队列
实现上,最常见的异步机制当然是消息队列:由消息队列提供异步通信协议,消息的发送方和接收方不需要同时与消息队列进行交互。
例如,邮件系统中,发件人将邮件发出之后,可以继续处理其他内容,而无需等待收件人响应。在用户看来,任务是立即完成的,但实际上是在一段时间之后才真正完成(所谓异步)。
消息队列的运作中有 3 个角色,消息的生产者、消费者以及经纪人(Broker):
- 生产者:应用程序往队列里发布任务后,立即告知用户该任务的状态
- 消费者:worker拿到任务并执行,完成之后通知
- Broker:从队列里取出任务并分发给worker,并负责管理任务的完整性,包括失败重试、(根据worker的实际状态)动态调整分发策略
消息队列还得考虑下面的一些问题:
- 消息可能会丢失,比如消费者挂了、消息队列挂了
- 消息可能会重发多次,比如消费者消费完忘记发送ack了
- 可能会出现忙闲不均的情况,比如轮流分发的话,有些worker接到的总是重活儿
- 队列可能溢出,比如worker太少或太忙,导致消息迅速堆积
更多的关于MQ的设计的方面的可以参考一下以前的笔记:
消息队列重要机制讲解以及MQ设计思路
漏斗模型
消息队列就像一个漏斗,用来控制流量和流速:
汹涌而至的消息从敞口流入,经过缩口以固定的流速输出给(消息的)消费者:
如果生产速率始终低于消费速率,倒进来立即就流走了,那么就不需要漏斗(消息队列)。
如果生产速率始终大于消费速率,就会在漏斗中累积,最终填满、溢出,导致消息丢失:
这种现象称为Back pressure(反向压力),下游消费速度限制了传输,此时可以限制队列大小,排满了就返回 503,稍后重试(比如采取指数退避策略,让重试间隔越来越长)。所以,只有生产速率在短期内大于(而不是始终大于)消费速率的情况下,漏斗才有意义——用来吸收暂时的超量生产量。
利特尔法则
队列中的消息实际上等同于在排队。
排队理论中一个有名的定理叫利特尔法则:
L = λWL: average number of items in the queuing system(排队系统中的平均项目数)
W: average waiting time in the system for an item(项目在系统中的平均等待时间)
A: average number of items arriving per unit time(单位时间内到达的平均项目数)
适用于任何稳定运转的排队系统:
“排队系统”由离散对象组成,我们称之为“物品”,它们以某种速率“到达”到“系统”在系统中,这些项目可能会形成一个或多个队列,最终接收“服务”并退出。
“稳定运转”,是指不包括系统启动、退出等过渡状态.
"物品”经过的状态:到达 -> 排队 -> 离开。
L = λW就是说:
队伍的平均长度 = 平均到达速度 * (每一项的)平均等待时间
例如:
酒窖:偶尔买一瓶放进去,平均每个月买 8 瓶,朋友聚会时候喝掉一些,但多数时候窖里都有 160 瓶,那么,就可以知道取出来喝的时候,每瓶酒在酒窖里平均存放了 1.67 年(W = 160 / (8 * 12)),离陈酿还有一段距离
邮箱:平均每天收到 50 封新邮件,邮箱里多数时候有 150 封未读邮件,那么平均每 3 天(W = 150 / 50)处理一封邮件,可以作为邮件处理效率的衡量标准
工厂:每天平均加工 1000 份原材料,在制品(WIP, work-in-process)数量平均有 45000 个,那么每件产品的加工周期是 45 天(W = 45000 / 1000),周期比较长
医院:某地区平均每天诞生 5 名新生儿,待产妈妈平均会在产房待 2.5 天,那么,产房通常住有 12.5 位(L = 5 * 2.5)待产妈妈,也就是说,产房床位不能少于 13 张,对应的医护人员也不能太少
三项中,只要知道两个(易知的)就能粗略得出另一个(难算的),这就是利特尔法则的意义。
参考
http://www.ayqy.net/blog/asynchronism/