星巴克的异步处理模型

和其他生意一样,星巴克最感兴趣的就是将订单的处理能力最大化,因为完成订单越多赚的钱也越多。因此,他们使用异步处理的模式:当您下单以后,收银员会在相应的杯子上做好标记,并将被子放入队列中。这个队列实际上就是在咖啡机顶上配好被子,它将收银员与冲调师的工作分开,使得收银员即使在冲调师忙里偷闲休息一下的时候也可以继续接受顾客的订单。 当店里生意很忙的时候,该队列还允许多个冲调师在“相互竞争消费者”的模式下工作。

在利用异步处理优势的同时,星巴克也需要面对异步处理所固有的缺陷。相关性就是其中一个方面。顾客的饮料订单不一定可以按照下单的顺序来完成,这里的原因主要有两点:首先,不同的冲调师用的是不同的设备。例如,混合型饮料所需的冲调时间要比一杯普通的咖啡长。其次,冲调师可以一次性批量处理多张订单,从而节省总的处理时间。 因此,星巴克需要解决相关性的问题:乱“模式”与我们在消息处理体系结构中所采用的方案是一致的,即星巴克会使用一种明确的相关性标识符,即在杯子外面写上顾客的姓名,然后等饮料冲调好之后叫顾客来取。而在其他国家,大多是通过饮料的类型将顾客与饮料关联起来。 在异步消息处理的模式中,异常处理是个难点。如果您相信实践出真知的话,那么不妨参考一下星巴克是如何处理异常的:如果您无法付账,那么他们会在饮料已经冲调完成的情况下把饮料倒掉,否则就直接吧杯子从队列中拿出来;如果他们把饮料弄错了,或者您觉得不合口味,那么他们会为您重新冲调;如果机器坏了,他们根本无法冲调咖啡,那么他们会把钱退给您。上述的每一个场景都描述了一种不同但十分有趣的异常处理策略: 一笔勾销 这种对错误的处理策略最为简单:什么也不做,或者将已经做完的全部作废。尽管听上去不是什么好主意,但在现实中这种做法还是可以接受的。如果损失不大,那么建立一种纠错方案所花费的代价可能要比就此罢休更加高昂。以我曾经工作过的几家ISP供应商为例,在处理计费/服务周期不一致的问题时,他们采用的就是这个办法。他们的客户有可能在被收费之前就把服务取消,但由于这种损失不大,所以不会影响到公司的正常运营。他们会定期根据报表查出这些“免费”的帐户,然后将其关闭。 重试 当操作集合(也就是“事务”)中的某一步失败时,我们有两种选择:一个就是将所有完成的操作撤销;而另一个则是重试失败的那步操作。如果失败的操作重新提交后确实有成功的可能,那么重试还是一个不错的选择。例如,如果失败的原因是违反了业务规则,那么重试就不太可能管用;而如果失败的原因只是外部的系统暂时不可用,那么重试则是很有可能成功。此外,采用Idempotent Receiver的重试方案是一个特例。在这种情况下,我们可以简单的将所有的操作重新提交,因为 IdempotentReceiver 会忽略重复的消息。 补救 最后一个选择就是把所有已经完成的操作撤销,并将系统恢复到原先的状态。这种“补救措施”很有效,例如在处理财务系统遇到问题时,我们可以把已经扣的钱重新存到帐户上。 而两阶段提交不同于上述这些策略,它需要依赖互相分离的准备阶段与执行阶段。还是以星巴克为例,如果他们采用两阶段提交,哪么顾客需要把钱放在收银台上并等在那里,而收银员则需要把收据准备好;然后冲调师再把冲调完成的饮料也放在收银台之上;最后大家在一瞬间同时出手,交换钱、收据和饮料。无论是收银员还是顾客,在“事务”完成之前都不能离开。这种两阶段提交方式一定会让星巴克破产,因为该方法必然使得他们在单位时间内接待顾客的数量急剧下降。这个例子很好的说明了,尽管两阶段提交能把生活变得十分简单,但是由于它要求在多个异步操作的序列间维持事务 资源的一定状态,因此,它阻碍了消息的自由传递(也就等于损害了可伸缩性)。 顾客与咖啡馆员工之间的交互也是一个很好的例子,它展示了一种简单而常见的会话模式。双方的交互过程包含了一个短暂的同步阶段(选择饮料及付账),还包含了一个时间较长的异步阶段(店员冲调饮料与顾客收到饮料)。这种类型的会话在各种购买场景中十分常见。例如,当您在Amazon上订购商品时候,短暂的同步交互仅仅是产生了一个订单号,而后续所有的步骤(信用卡扣款、打包、送货)都是异步完成的。当这些后续步骤完成后,您会收到email通知(也是异步)。如果其中某个环节出错了,Amazon通常会采取补救措施(向信用卡退款)或者重试(把丢失的货物重新发送)的策略。 总的来说,在真实世界里常常可以看到异步处理的情况。我们的日常生活中就包含了许多协同的异步交互(阅读及回复email、购买咖啡饮料等等。这就意味着可以使用一种异步的消息处理体系来很自然的模拟这些交互,也说明观察日常生活有助于我们设计成功的消息处理方案。

posted on 2011-12-17 16:42  Milton  阅读(261)  评论(0编辑  收藏  举报

导航