异步通信
使用消息系统的异步通信是系统获得近无限可扩展性的关键。和函数的同步调用相比,异步通信在时间(调用者不需要等待调用结果返回;调用和结果不是同步)和空间(调用和被调用者往往在不同的机器上,中间往往通过第三方完成通信)帮助分离/去耦合系统的组件之间的相互关系, 提高了系统的可用性和可扩展性。全面使用异步通信是 EBay的系统构架的三大战略之一。
异步的常见模式有两种:消息发送和周期性批处理工作
(一)消息发送
在“数据”一节我们中我们提到数据实体的概念。数据实体(ENTITY)是带关键字的数据对象。 因为数据实体具有完整商业意义, 可以独立完成一个业务操作。一个应用程序可以有很多数据实体,比如订单处理程序包括了很多订单, 每个订单有Order_ID作为标识符。而决策的业务流程,是在实体之间的相互作用(活动)构成的;一个实体如果和多个实体发生关系,就有多个活动。比如一个包括数个不同商品的订单的处理流程,订单和每个订购的商品都是不同的实体, 通知库存装运每个订购的商品都是一个个不同的活动。
在不使用分布式事务的情况下,当我们试图在不同的实体之间达成协议时,必须接受距离空间带来的不确定性。 在分布式系统中的决策中会涉及接受一段时间的不确定性, 这是不可避免的。在可以使用分布式事务的系统中,这种不确定性由该事务管理器管理, 表现在数据加锁; 在不能使用分布式事务的系统中, 这种不确定性的管理必须发生在业务逻辑层。必须使用工作流程来管理不确定性本身,而不是记录上锁。
引入Helland关于数据实体和活动的概念后,那么不同实体之间是如果联系呢?不同的活动是如何组成一个完整的业务流程呢?
近无限可扩展性系统的一个共同特性是多数程序使用 “至少成功一次” 的消息系统”(Pat Helland, 2007)。
如果不能在同一个事务中更新2个数据实体(基于实体的定义,A和B不能够同时作为一个原子更新事务的一部分), 那么需要一种机制来更新在不同事务中的数据。 方法是使用消息系统。
消息是从一个数据实体A传递到另一个数据实体B。消息发送事务结束后才能被接受端接受,对于发生端的事务而言,消息是异步的。 如果程序在一个事务中发出消息,然后事务非正常终止,这意味着B将看到后果发生了却没有原因! 因此消息的排队操作是严格的事务。 如果消息只能在发送端的消息被接受,然后数据库更新,然后是消息的确认回执。 问题是消息传递和数据库更新是异步,非相关的。这就造成了数据有可能重复传递的窗口。但是如果不接受偶尔的重复消息, 就有可能偶尔丢失消息,更难恢复。同时当系统横向扩展时,实体会被重新定位( 重新分区); 因此消息目的地也是在变动中的。 这也有可能改变消息的达到顺序。因此, 程序必须能够接受并且正确处理消息的重试和错误的消息到达序列。
因为我们假设消息的“至少一次”的输送模式, 消息的接受实体需要维持某种状态来识别重复发送的消息。消息可以分2类:对接受实体状态没有影响的和对接受实体状态有影响的。前一类直接支持幂等性,又叫恒等(nullipotent),后一类需要额外的编码。
幂等操作
在很多大城市的人行横道入口有按钮控制指示灯。当行人按过按钮后,系统进入等待状态,在等待状态中行人反复按按钮对系统没有任何影响,不会缩短或延长等待时间。这就是幂等操作例子。幂等操作在数学和计算机技术中非常重要( Wikipedia) 。
在计算机科学中,幂等主要是指一个操作执行一次或多次都产生相同的结果。在函数调用的情况下指第一次调用后修改后的状态保持不变。在函数编程语言中的幂等函数,是一个具有F(F(X))= F(X)性质的任何X值。在许多情况下,这是一个非常有用的性质; 因为它意味A->B中, A可以因为环境等各种原因根据需要重试而不用考虑操作会被会造成意想不到的后果 , 而B隐藏了跟踪操作是否已经被执行的实现, 从构架上简化了系统设计水平的复杂度。
在HTTP协议中,幂等性和安全性是区分几个标准操作的主要属性。按照HTTP协议标准,GET,PUT和DELETE是幂等的,但POST不是。 GET是读取数据操作,通常具有无副作用,所以它是幂等。PUT 用来存储一组给定的内容,因为每次执行相同存储的最终值保持不变,通常也是等幂的。删除给定的内容显然也是幂等的,没了就没了,回不去了:)。
在消息传输处理中,具有幂等性的系统即使事件或消息被接收不止一次,系统的产生相同的结果。
幂等性的实现有两类, 一类是操作本身具有幂等性,比如上面提到的HTTP GET和DELETE,一类是操作本身的性质不具有幂等性,比如记录交易的帐户余额。在这种情况下,需要一种方法来跟踪记录哪些操作已成功地应用, 那些操作仍然悬而未决。其中一种实现技术是使用一个表,该表记录已被应用的事务标识符。
使用异步方式编程的一些反模式(Anti-Pattern )
1. 使用同步过程调用异步流程,并且等待结果
虽然简化的调用端的编程复杂度, 但是可能引起超时问题,造成进程死锁; CPU,GC 和服务器进程的资源占用率非常高, 严重影响系统的可扩展性。
2. 过度使用异步进程。(BPEL)
在BPEL的业务流程中,过多使用异步进程, 不仅增加了系统开销,编程复杂度,也影响系统的效率
3. 错误使用数据库表作为消息列队的实现。
这是一个有争议的议题。市场上有很多消息管理系统,比如JMS的Apache ActiveMQ, OpenJMS和 Jboss Messaging ,Spread (扩展的虚拟同步总线),AMQP (高级消息队列协议)。但是很多原有的程序没有机会使用消息管理系统,而是使用数据库作为消息列队的实现。
数据库作为消息列队的问题在于3个方面, 轮询,锁和数据量。 虽然不同的作者对此有不同的观点,而且每个问题都有对应的解决方案, 在实际工作中, 随着队列里长度的增加(即使是10万条消息),因为索引重建问题,队列表的效率越来越差,最后导致系统关联的服务违反SLA。
曾经在工作中维护过某公司的付款系统。公司构建的基础是SOA,交易记录从上游通过消息系统传到下游的服务。系统使用数据库存储交易记录队列。北美和大陆的网上购物行为稍有不同,大陆的网上购物订单主要是周一到周五的工作日,而北美主要是周末。每个周一上午,上游高速积累了周末的大量交易记录在队列里,数据库表格的索引更新失效,以至于每步SQL查询都需要全表扫描, 消息没法传到下游,引起SLA的2级警报。唯一的办法是DBA手动重建索引。 基于这个经验,在后续的系统实现中,我们避免了使用数据库作为消息列队的实现。
4。错误的把尽可能多数据都放入消息,使得消息总线超负载。
当引入消息管理系统后,设计时很容易把尽可能多的事件转换成消息,把尽可能多的内容塞进消息本身。 尤其是在设计性能监测功能时,收集的数据越全面越有助于后期的诊断调试, 因此有些构架师希望能够捕捉系统的所有活动。但是任何信息的处理,储存和传输都是要占用系统资源,是需要成本的;而且消息总线超负荷,拥挤的消息流也影响系统的性能。
解决问题的策略依然是我们在“数据”一节中提到的对数据的重要性的区分。“不是所有的数据生而平等的”。同时考虑数据价值和发布成本, 对价值高而成本低的信息, 比如网上购物下单,100%的发布到消息总线,而价值低成本高的,完全忽略。 其他情况下使用取样的办法发布一定比例的信息, 尤其是一些性能监控指标。
(二)周期性批处理工作
很多周期性的批处理工作是放在夜间执行的,这样可以避开对白天的正常事务的影响。周期性批处理工作一个比较传统的应用是收集分布式系统各个节点的日志记录(因为有可能丢失部分节点日志和不能提供实时日志记录,这个方法不再使用)。在SOA系统中,有的时候需要把某些数据从一个服务复制到另一个服务, 有的时候需要针对不同的服务做总帐合计(业务需要,或者对系统最终一致性实现的核查), 这个时候往往需要脚本程序扫描数据库中某时段(1天)的数据,抽提,复制,分析。因为涉及到大量的读写操作,如果在白天运行,将严重影响正常业务。
模式名称: 在不同系统或程序间使用异步调用
描述: 在不同系统或程序间使用异步调用,提高系统的可用性和可扩展性。
动机/试图解决问题: 不同系统之间的同步调用使得系统之间高度依赖和耦合。不确定性风险和Bug容易通过同步调用扩散。
原理: 异步在时间和空间上降低了调用双方的耦合度,避免了SPOF,提高系统的可用性和可扩展性。
使用: SOA构架
相关模式:
模式名称: 使用“至少一次”的消息输送模式
描述: 消息的传输中具有不确定性,消息有可能丢失,需要重试。
动机/试图解决问题: 消息传递和接受方数据库更新是异步非相关的。这就造成了数据有可能重复传递的窗口。但是如果不接受哦而的重复消息, 就有可能偶尔丢失消息,更难恢复。
原理: 使用“至少一次”的消息输送模式, 保证消息不丢失
使用: SOA构架
模式名称: 实现服务的幂等性
描述: 服务的API应该具有幂等性,同一信息的重复调用对系统没有影响。如果调用需要返回结果,其返回的结果应该相同。
动机/试图解决问题: 消息传输中会出现重试情况,消息的接收方有可能不只一次得到同一信息。
原理: 实现幂等性的服务,需要记住那些消息已经被使用;在消息需要回执时,也需要记住回执的内容。
使用: SOA架构的服务实现中
模式名称: 使用成熟的消息管理系统
描述: 使用成熟的消息管理系统,而不是内部从头设计
动机/试图解决问题:很多公司自己构建消息管理系统,比如使用数据库作为消息队列的实现,在高负载情况下造成严重性能瓶颈
原理: 高性能消息管理系统的实现,比较复杂。最好使用市场上一些常见的产品,比如JMS的Apache ActiveMQ, OpenJMS和 Jboss Messaging 。
使用:
模式名称: 周期性批处理工作
描述: 一些任务往往需要较多的计算资源 ,同时实时性要求低,可以放在非业务时段周期性自动的批处理执行。
动机/试图解决问题: 一些实时性要求低的任务,往往需要扫描数据库中某时段的数据,抽提,复制,分析。因为涉及到大量的读写操作,如果在业务时段运行,将严重影响正常业务。
原理: 在非业务时段,用脚本自动的周期性批处理。
使用: 在SOA系统中,有的时候需要把某些数据从一个服务复制到另一个服务, 有的时候需要针对不同的服务做总帐合计(业务需要,或者对系统最终一致性实现的核查)
总结:
由于异步调用的优点(可用性,可扩展性),我们倾向于尽可能的使用异步调用。相对同步调用而言,异步调用极大程度的降低了调用双方的依赖性,因此适合于外部API或者第三方系统的调用,或者频繁修改的API; 如果被调用的API运行时间较长,异步调用可以屏蔽此影响。尽量不要构建自己的消息服务系统,而 使用市场上一些常见的产品,比如JMS的Apache ActiveMQ, OpenJMS和 Jboss Messaging 。
本文出自 “静水流深” 博客,转载请与作者联系!