业务场景
一个系统,依赖很多外部系统的数据。对于一个请求过来,需要查询N个外部系统的数据,等全部数据拿到之后,做数据处理完之后,返回给请求端。如果依赖系统太长时间未返回,我们必须有一个超时响应机制返回给客户端。
简单方案
最简单的方案,就是通过同步的方式。一个客户端请求过来,假如依赖3个接口,则同步顺序的去请求三个接口,send1, recv1, send2, recv2, send3, recv3,拿到结果之后再做数据处理,再返回请求端
存在问题
因为是同步,所以每一步都需要等待,CPU阻塞在等待线程中无法做更多的事情。效率极低。
改进方案
直观上能想到的就是把同步改成异步。那么刚好可以配合上linux内核提供的epoll异步事件机制使用。那么要把同步改成异步,需要满足哪些条件?
1、自身的系统和请求的系统协议,必须支持异步。
简单来说,就是协议上要支持回带字段,才能区分是哪一次的会话请求。至于一些同步的依赖系统,后续会讲如何解决这类问题,这里先假设所有系统都支持回带字段。
2、会话管理机制。一个请求过来之后,系统必须会每一次客户端的请求创建一次会话,并记录会话是否超时。如果有接口超时之后还未获得数据,则必须通知客户端请求超时。从业务侧则体现在,一个请求如果太久没有返回,那到时返回给我也没有什么意义了,需要支持一个最大超时时间来返回数据。
3、状态机。
每一个业务消息,他可能每次依赖的接口之间有顺序之分,或者没有顺序之分。
- 场景1:可能依赖的三个接口,三个接口之间没有先后之分,可以同步并发请求。
- 场景2:依赖的三个接口,可能接口2依赖接口1返回的数据,因此,这种情况下,对于会话是需要状态机的支持。通过一个状态机来管理一个会话的状态。
实现方案
1、异步消息回带,这里只需要系统接口层面的支持即可。通过在协议中约定某些字段,原封不动的返回即可,方便系统做异步。
2、状态机,这里通过一个整数值存状态即可简单实现。每一个会话建立的话,即为初始状态。按照业务逻辑进行请求。每次依赖的接口返回时,即做状态的检查与切换。
3、会话管理。
这里简单介绍一下会话管理的一个算法模型实现。
通过一个双向链表来管理会话。
- 插入
当一个新的请求过来的时候,在链表头部插入一个会话节点。时间复杂度为O(1)。 - 删除
当一个会话的状态机到终态的时候,因为知道会话id,能直接找到该会话的地址,所以要从双向链表中删除一个节点,也是O(1)时间复杂度。 - 扫描过期
对于超时机制的支持,这里可以通过从队尾进行定期扫描来解决问题。由于最新的节点是在队头不停的插入,所以整个双向链表是一个按时间新旧顺序排列的有序链表。我们只需要从链表的尾部扫描即可,扫描的时候,比较会话建立的时间与当前时间,超过超时时间即结束会话。直接扫描到第一个不超时的会话即完全队列的扫描任务。
这个场景,刚好可以加到epoll_wait中的超时回调函数去完成
遗留问题
如果依赖的外部系统,不支持异步协议如何?
简单的解决思路,可以通过在本地调用端,写一个进程去做接口的转换。写一个简单的同步转异步服务。系统调用的时候,直接调用异步服务,转换服务再去请求同步的接口。