Hystrix的应用案例:多短信供应商的自动切换与恢复
本文转载自公众号:永辉云创技术
该号由我参与维护,欢迎大家关注支持!!!
案例背景
目前的互联网应用几乎都会使用外部供应商的短信接口来实现一些验证、确认或是推广营销的行为。其中,对于验证、确认类的操作是与业务逻辑直接串联相关的,所以短信发送是否成功非常重要。
由于短信的发送接口是一个外部服务,我们自身无法控制它的可用性,当供应商端内部故障或网络故障等其他外在原因出现推送服务调用延迟过高的时候,我们的内部服务频繁调用对接短信接口的通知服务时,这些调用线程会因为外部资源的延迟高导致资源释放不及时,有导致通知服务资源耗尽的风险。
实现方案
首先,为了让通知服务不因为依赖资源的故障导致自身资源的耗尽,而影响到其他渠道通知服务的正常使用,因此引入Hystrix来保护对短信接口的调用,保护主要分几个方面:
资源隔离:使用线程池来隔离对外部资源的调用,就算外部资源出现问题,在高并发情况下,资源的占用也有上限,不会挤压该服务实例的其他接口
快速失败:通过熔断器在抛出异常的时候进行计数,在固定时间窗里错误情况达到一定百分比,就将熔断器打开。打开之后,在一定时间窗内所有请求将快速失败,而不是等待超时。
自动恢复:在熔断器打开的状态下,将启动一个计时器,这个计时称为休眠时间。当休眠时间耗尽之后,熔断器再次闭合,重新尝试极光推送,如果这次调用成功,将恢复正常服务;如果调用失败,熔断器重新打开,继续休眠。
其次,准备多个短信供应商渠道,在当主要使用的供应商接口故障的时候可以切换到备用供应商来实现短信的发送(用备用供应商接口作为降级逻辑,而不是快速失败,保障短信更高的送达率)。
代码实现
第一步:引入Hystrix的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
第二步:在应用主类中使用@EnableCircuitBreaker或@EnableHystrix注解开启Hystrix的使用,比如:
@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
第三步:编写短信发送的服务,并使用Hystrix来包装实现资源隔离、服务降级和自动恢复:
public class MessageService {
@HystrixCommand(
fallbackMethod = "sendMessageSecondary",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000")
},
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "20"),
@HystrixProperty(name = "maximumSize", value = "50"),
@HystrixProperty(name = "allowMaximumSizeToDivergeFromCoreSize", value = "true")
}
)
public void sendMessagePrimary(MessagePush messagePush){
// 调用供应商A的短信接口
}
public void sendMessageSecondary(MessagePush messagePush){
// 调用供应商B的短信接口
}
}
运行原理
具体的代码配置实现很简单,但是其中蕴含了很多内容逻辑,下图描述了本次整合案例的具体实现原理:
如上图,通过Hystrix对GJPushHandler的 sendMessagePrimary
方法做了保护。它的保护逻辑主要分为下面几个部分:
线程隔离
如上图蓝色区域是通知服务中两个发送函数的调用关系。其中, sendMessagePrimary
方法由于加上了@HystrixCommand注解,所以该方法的调用已经得到了线程池的资源保护。
换言之,如果该方法调用逻辑阻塞,那么不断增加调用的话,线程池满之后就会出现拒绝服务的异常。因此该方法的调用得到了资源上的保护,不会出现高并发导致无限制的占用资源上升情况。
熔断机制
如上图蓝色区域的左边部分描述了熔断机制的逻辑:
正常情况下:由于熔断开发默认是关闭的,所以 sendMessagePrimary
方法的执行逻辑是一个通路,任何调用都会直接进入到该方法的业务逻辑,即对外部资源的调用。
异常情况下:外部资源出现了问题,此时 sendMessagePrimary
方法的调用可能会出现超时或者抛出连接异常等各种非HystrixRequestException异常,这个时候当我们判定这些异常属于熔断统计范围内的异常就进行记住。当一定时间窗(10秒)内出现错误请求百分比超过50%的情况下,认为当前外部资源服务不稳定,需要打开熔断器,进入熔断状态。这个时候,由于熔断器处于打开状态,所以 sendMessagePrimary
方法的调用将直接降级为对 sendMessageSecondary
方法的调用。
该方案可以有效的帮助在异常情况下,通过熔断器来自动的从故障资源的调用切换到备用资源的调用。
自动恢复
如上图底部左边部分描述了自动恢复的逻辑:
在熔断机制的异常情况逻辑中,当熔断器打开的时候,会自动的启动自动恢复休眠窗(一个计时器,默认10秒),在这个休眠期内,所有请求都会快速失败。但是当休眠期到期的时候,此时熔断器会进入半开状态,让下一次请求继续调用外部资源,而不是快速失败。如果这时候调用成功,熔断开关置为关闭状态。反之,熔断开关继续打开状态,再次进入快速失败的状态,并继续进行休眠,等待下一次尝试恢复。
总结展望
上面介绍的机制虽然已经实现了对多个短信供应商接口的自动切换,这样的保障主要是对于接口调用层面,但是实际上短信是否真正送达更为复杂。如果做的更好,需要做更多努力,可以通过外部的送达率收集来控制上面原理图中的熔断开关,以实现更为精准的自动化控制。
热文推荐:
Spring Boot快速开发利器:Spring Boot CLI
IntelliJ IDEA 2018.1正式发布!还能这么玩?
其他推荐:
Spring Cloud构建微服务架构:分布式配置中心(加密解密)
Spring Boot使用@Async实现异步调用:线程池的优雅关闭
Spring Boot使用@Async实现异步调用:自定义线程池
长按指纹
一键关注
深入交流、更多福利
扫码加入我的知识星球
点击 “阅读原文” 看看本号其他精彩内容