为系统扩展而采取的一些措施——异步
2016年12月17日 17:33:20
阅读数:1089
- 同步与互斥,提到异步必然要涉及到与之对应的另一个词“同步”,而提到“同步”很多人也会联想到另一个词“互斥”,同步是指多个操作之间产生了依赖或者先后顺序关系,互斥是指多个操作需要访问同一个资源,而这个资源又不能让多个操作同时进行,那么这多个操作之间就是互斥关系;
- 同步调用与异步调用,在服务设计时,同步调用是指调用方在发起调用之后必须一直等待直到调用结果返回才能进行后续的操作,异步调用是指调用发起方在发起调用之后立即返回继续执行后续的操作,而不需要等待调用的返回结果。举个简单的例子,同步就像打电话,异步就像发邮件;
- 同步调用和异步调用,二者各有其应用场景,如果相邻两个操作之间存在依赖关系,那么就必须选择同步调用,否则都可以采用异步,但是同步调用更为简单,如果在一个系统中滥用异步会造成系统复杂度增加;从扩展性角度来看,同步容易造成调用阻塞,异步调用更易于扩展,而且异步调用方式天然具备故障的隔离性,因此,在合适的场景选择同步能让你的系统更为简单,在合适的场景选择异步能让你的系统更具备扩展性;
例如,我们要设计一个数据收集和处理系统,该系统分为两个子服务:数据收集服务和数据处理服务,数据收集服务用于从客户端收集各种上报信息,客户端上报的所有数据需符合系统的格式要求(上报的消息必须为JSON格式字符串,在字符串中必须包含version、type、con三个字段,上报的不同消息的type的值不一样,上报的消息内容存储在con字段中),数据处理程序对收集服务传入的数据进行分类型处理,并根据type字段值存入数据库的不同表中,如果系统被设计成同步的方式,那么它会像像下图所示:
在同步的系统中,数据收集服务没收到一个客户端上报的消息就调用数据分析服务对该消息进行处理,数据分析服务将数据解析、转换之后存入数据库,然后将结果返回给数据收集服务,然后数据收集服务再将结果返回到客户端,那么在这个系统中数据收集服务调用数据分析服务是同步调用,因为它要等待数据分析服务返回的结果,而数据分析服务调用数据库也是同步调用,因为数据分析服务也要等待数据库的处理结果;
在上述设计的数据收集、分析系统中,有如下问题:(1)扩展性受影响,客户端并发量受到数据分析服务、数据库存储速度的影响;(2)系统可用性受影响,一般数据分析服务所做的事情稍微复杂,一旦这个服务出现问题,整个数据收集、分析系统都无法正常工作;
为解决上述同步过程中出现的问题,将系统改造成如下图所示:
我们引入一个异步队列(例如RabbitMQ)用于对“数据收集服务”和“数据分析服务”的解耦,异步队列可以对数据进行缓存并具备持久化能力,数据收集服务在受到客户端上报的数据时,只检查其格式是否符合要求(是否为JSON串?JSON中是否包含了version、type和con三个字段),检测通过的数据发送到异步队列中,数据一旦放入异步队列就立即返回;数据分析服务从异步队列中拉取消息,并对消息进行分析、处理然后将数据存储到数据库的相应表中;
在这个异步系统中,还存在很多同步调用,例如数据收集服务同步调用异步队列的接口来发送数据(很多异步队列的客户端也提供异步发送消息的功能),这里采用同步调用,是想告诉客户端,它的数据已经安全抵达服务器,而服务器内部会保证你的数据被安全的处理,但是可能不会立即处理,如果这里也采用了异步,就会出现问题:异步传输数据出现问题时,无法告知客户端,客户端就会抱怨明明已经把数据成功交给服务器,服务器却实际上没有成功处理它;数据分析从异步队列中拉取消息也是同步调用,即数据分析服务从异步队列中拉取一条消息进行处理,处理完毕之后再从异步队列中将该消息删除;
虽然系统中存在了同步调用,但是从整个系统而言,数据收集服务和数据分析的依赖性却消失了,由于数据收集服务做的事情非常少,它能接受客户端的更多的上报请求,而数据分析服务业务复杂还需要存储数据库,它可以慢慢地从异步队列中消费消息,即便客户端上报消息的速度超过了“数据分析服务”,那么整个系统也能正常运行,从故障隔离的角度来看,一旦数据分析服务出现故障,消息会被异步队列缓存,客户端的数据上报功能依然不受影响。
从上面的例子可以看出,同步和异步在架构设计时使用要非常灵活,没有明确的方案来告诉我们什么时候应该用同步什么时候应该用异步,我们只要保证整个系统满足稳定性、扩展性等相关的要求即可。 - 同步转异步时,阻碍最大的地方就是调用之间产生了依赖、顺序关系,从另一个角度来看就同步操作之间产生了状态,这时如果要想将这些操作转换为异步,那就要去掉这些状态,去掉状态的办法包括:(1)将状态相关的操作整合到一个操作里,这需要调用方来实现;(2)将状态缓存到一个第三方缓存中,例如Redis、Memched等等,这需要被调用来实现;