通过熔断器处理不稳定的外部服务


假设有这样的一个业务场景:有个外部请求服务接口每隔几天就会挂掉,不稳定,这个时候从队列任务中向该接口发起的 HTTP 请求会返回 500 错误,可能需要几个小时才能恢复。

延迟推送任务

在这种情况下,我们能做的就是让这个外部服务的不稳定对系统影响降到最低。比如出现问题后,延迟推送任务到消息队列,避免系统资源浪费:

实现熔断器(Circuit Breaker)

不过上面这种实现不够精细,一方面,一次请求失败可能是网络抖动造成的,这里缺乏重试机制;另一方面,在 1 个小时内,可能外部服务接口已经恢复了,但是还是必须要等到 1 小时才能发起请求。

为此,我们可以实现一个熔断器来阻止失败任务的重复执行:当我们发现某个服务连续多次出现故障,就要暂时停止向它发送请求,给它一些时间来恢复。

要实现熔断器模式,需要统计一段时间内连续失败的次数,我们基于缓存来存储这个失败次数:

如果请求失败,则初始化一个基于缓存的 failures 计数器统计失败次数,一旦服务恢复,则取消这个计数器,进而可以正常发起请求。

不过这里也没有体现出重试逻辑,和恢复访问的时机,接下来,我们继续对这段代码进行优化。

开启熔断器

这就涉及到熔断器的开启和关闭。

每次服务调用失败时,需要检查失败次数是否达到熔断器开启的阈值,如果到达,则开启熔断器,阻止其他任务继续发送请求:

这里我们将熔断器阈值设置为服务连续调用失败 10 次,如果达到这个阈值,则设置一个缓存键 circuit:open 来开启这个熔断器,有效时间是 10 分钟,即给对方服务 10 分钟的恢复时间,10 分钟后再重试。

检查熔断器是否开启

现在,每次运行任务前,可以先检查熔断器是否开启,如果开启,表示外部服务接口不可用,服务熔断,可以立即延迟任务执行,不再发起请求:

这里我们将任务推送回队列的延迟时间加上了一个随机值,变成 10-12 分钟,从而避免在熔断器关闭之后大量重试任务集中执行,对外部服务接口同时发起过多请求,进而触发频率限制机制或者让服务再度挂掉。

熔断器开启与判断的完整示例代码如下:

检查是否需要关闭熔断器

默认情况下,熔断器会开启 10 分钟,当它关闭后,我们不得不在触发熔断器阈值前再次发送 10 次请求,如果服务仍然不可用,则再次开启熔断器。针对这一流程,可以进一步进行优化。

具体实现思路是在熔断器关闭前发送一次请求,测试服务接口是否可用,进而决定是否真的关闭熔断器,如果请求成功,则关闭熔断器,否则继续开启 10 分钟。

在实现对应代码之前,先引入一个「半开启」状态的概念:如果熔断器打开时间小于 8 分钟,则稍后重试这个任务,否则让这个任务继续运行,即使熔断器处于打开状态,我们将这种状态称之为「半开启」状态。

也就是说,我们将 8 分钟作为一个分水岭,小于 8 分钟的话,熔断器处于打开状态,否则,熔断器处于半开启状态,在这个状态下,会发起一次请求尝试,这就把原来的 10 分钟后重试调整为了 8 分钟后重试。

半开启状态对应的实现代码如下:

如果有多个队列处理器进程在运行的话,可能会存在多个并发执行的任务,如果你只想要一个任务处于「半开启」状态,需要引入锁机制来阻止其他进程的运行。

然后我们在这次尝试请求失败后,在半开启状态打开的情况下,直接重启熔断器,而如果请求成功,则通过移除缓存键来关闭熔断器,让队列任务恢复正常运行:

以下是最终的完整实现代码:


点赞 取消点赞 收藏 取消收藏

<< 上一篇: 限制队列任务的执行频率

>> 下一篇: 处理外部服务接口未响应