一、背景
客户端请求Web服务架构中,一般有同步阻塞模型和异步回调两种模型。对于服务端耗时较长,例如音视频转码等重操作的服务,异步回调模型相比同步模型有许多的优势:
- 不会阻塞客户端的请求线程,可以提高客户端的线程利用率。
- 服务端根据自身的处理能力进行处理,保证服务端的稳定性。不会由于峰值请求造成服务端过载。
但是,在异步回调模型中,由于多了一次回调的链路,会带来更多的可用性问题。因此,本文主要讨论在异步回调模型中,如何制定有效的回调策略来保证回调链路的成功率,并提高整个异步服务的可用性。
二、异步回调模型
异步回调模型可以参考引用1中的描述,一般有以下两种细分模型:
2.1 Asynchronous Web Service Using a Single Request Queue
2.2 Asynchronous Web Service Using a Request and a Response Queue
其中2.2的模型虽然更复杂,但可以有效的提高服务端的资源使用率。避免由于回调阻塞导致处理能力的下降。同时可以增加一些判重策略,防止回调服务出现故障时,由于客户端重试导致服务端重复处理的资源浪费。
在分布式服务场景下,2.2 模型还可以细分为Inner Response Queue及outer Response Queue两种:
- Inner Response Queue为每个服务端app使用内存队列作为Response Queue,由内部的线程作为Callback Client。
- Outer Response Queue为使用统一的消息队列中间件作为Response Queue,另外部署一套Callback Client服务来处理这些Response。
Outer模型相比Inner模型部署结构较为复杂,但与处理结构完全解耦,可以针对回调做更多策略,同时可以防止由于处理app宕机造成的Response丢失(不过由于callback client以及消息中间件策略的问题,仍然会存在response丢失的风险)。
以上各个模型各有优劣,应该根据业务场景选择合适的模型。
三、回调策略
对于异步回调模型,callback service一般由业务方提供,无法对可用性做保证。因此callback client必须要制定一些策略尽量的应对callback service失败的情况。尤其是在一个callback client对应多个callback service的场景下,需要尽量防止由于某些service的问题,影响回调其他service的情况。
Callback Service 失败场景:
1. client与service之间出现网络波动,甚至中断。
callback client请求service时,响应域名解析失败或者client请求超时。
快速重试策略可以解决网络波动问题。轮询重试策略可以解决短时间中断问题。
2. service超时。
callback service处理回调超时。这种情况除去网络问题,一般是由于service负载过高,或者设计存在问题导致。这种场景如果过度重试一般会造成service雪崩。解决的方案是周知业务方,由业务方对callback service进行排查。
因此,当出现这种情况时,需要能尽快的感知到。感知的方案可以是记录错误日志以及进行报警,但报警策略需要合理设计,防止由于业务方的原因影响正常的报警。
更为合理的callback service设计是: 收到回调请求时,直接响应202(Accepted)状态码,表示已经收到回调请求。callback client不需要关注service的处理结果。
3. service服务异常响应。
当callback service响应异常的响应码时,表示service此时无法接受回调请求。一般可以通过重试策略解决这类问题,如果多次重试后仍然失败,则需要周知业务方进行排查。
4. service响应时间过长。
callback service 合理的设计不应该等处理完后再响应client,而是收到请求直接响应202状态码。
但对于某些特殊的场景,callback service可能希望如果回调请求处理不成功,client可以重新发起回调,这样可以简化一些callback service的设计。因此client可以通过重试策略解决这种场景。同时要注意防止某个service响应时间过长影响其他service的回调,解决的方案是对不同的callback service使用不同的response queue。或者增加callback client的并发来减少这种情况的影响。
5. ex
通过总结以上回调策略,得出的最大结论是: 合理的设计callback service来应对各种场景,callback client能做的实在有限。
四、重试策略
1. 快速重试策略。
回调失败后,等待毫秒级别的间隔后重试。这种策略的重试次数一般较少,可以解决一些由于服务或网络波动导致的小概率离散型回调失败(离散型回调失败是指同一个时间点的回调,有些成功,有些失败)。是最为常用的重试策略。
2. 轮询重试策略(短延时重试策略)
回调失败后,等待一定间隔后,将该条回调重新写入Response Queue进行重试,重写的次数可以较多。该策略可以解决由于服务或网络波动导致的短时间连续型回调失败(连续型回调失败是指某个时间段,所有的回调请求都失败)。但这种策略会导致回调的请求量大量放大,对callback service/client 造成一定的压力,甚至造成雪崩。
如果Response Queue中有多个callback service,当某些service出现问题,某些是正常的时候,这种策略会对正常的callback service造成一些影响。解决的方案是将失败的回调写入另外一个failover response queue。
3. 长延时重试策略
回调失败后,等待较长的时间(小时及以上级别)再进行重试。这种策略可以在Callback Service出现网络中断或者服务故障的时候,保证一定的回调成功率,防止由于回调失败造成的无效处理。适用于处理资源消耗极高、回调延时不敏感的服务,例如离线计算任务。
该策略的实现方式一般依赖于Outer Response Queue模型,需要额外的消息中间件来保存回调。
4. 记录手动重试策略
回调失败后,记录回调内容。后期再利用工具手动进行重试。这种策略和延时重试策略解决相同的失败情况。这两种策略主要解决由于callback client和service之间较长时间段不可通信的场景,例如网络中断,callback service故障。这种策略相比长延时重试策略更加灵活,因为当出现这类问题的时候,需要等待多久后以及如何重试都是不可控的。
两种策略都会带来较多额外的开发和部署成本。
五、轮询回调模型设计
轮询回调模型可以较好的覆盖各种回调失败场景。整体流程大致如图:
- Request MDB 处理完请求后将Response写入Response Queue。
- Callback Client 从Response Queue中取出Response。
- Callback Client 根据Response执行回调。如果回调失败,则采用快速重试策略进行快速重试。
- Callback Client 执行快速重试策略后仍然无法回调成功,则将Response 写入Failover Response Queue。Callback Client 重新执行步骤2。
- Failover Callback Client 从Failover Resonse Queue中取出Response。
- Failover Callback Client 根据Response 执行回调。如果回调失败,则采用快速重试策略进行快速重试。
- Failover Callback Client 执行快速重试策略后仍然无法回调成功,则将Response 写入Failover Response Queue。Failover Callback Client 重新执行步骤5。
该模型有几个需要注意的地方:
- Failover 流程中,每个Response的二次failover需要增加一个时间间隔。否则可能出现failover callback client不停重试一个callback,对callback service造成雪崩。通过设置这个failover interval以及failover times,则可以基本计算出最长可重试的时间范围。公式为 (interval+x)*times,其中x为平均每次callback消耗的时间。
- 当超过failover times limit,仍然无法重试成功,则应该对这条Response进行记录。提供之后手动重试的可能性。
六、引用
本文地址