一、什么是Hystrix
Hystrix是Netflix的一个开源框架,地址如下:https://github.com/Netflix/Hystrix
中文名为“豪猪”,即平时很温顺,在感受到危险的时候,用刺保护自己;在危险过去后,还是一个温顺的肉球。
所以,整个框架的核心业务也就是这2点:
- 何时需要保护
- 如何保护
二、何时需要保护
对于一个系统而言,它往往承担着2层角色,服务提供者与服务消费者。对于服务消费者而言最大的痛苦就是如何“明哲保身”,做过网关项目的同学肯定感同身受。
上面是一个常见的系统依赖关系,底层的依赖往往很多,通信协议包括socket、HTTP、Dubbo、webservice等等。当通信层发生网络抖动以及所依赖的系统发生业务响应异常时,我们业务本身所提供的服务能力也直接会受到影响。
这种效果传递下去就很有可能造成雪崩效应,即整个业务联调发生异常,比如业务整体超时,或者订单数据不一致。
那么核心问题就来了,如何检测业务处于异常状态?
成功率!成功率直接反映了业务的数据流转状态,是最直接的业务表现。
当然,也可以根据超时时间做判断,比如Sentinel的实现。其实这里概念上可以做一个转化,用时间做超时控制,超时=失败,这依然是一个成功率的概念。
三、如何保护
如同豪猪一样,“刺”就是他的保护工具,所有的攻击都会被刺无情的怼回去。
在Hystrix的实现中,这就出现了“熔断器(CircuitBreaker)”的概念,即当前的系统是否处于需要保护的状态。
当熔断器处于开启的状态时,所有的请求都不会真正的走之前的业务逻辑,而是直接返回一个约定的信息,即fallBack。通过这种快速失败原则保护我们的系统。
但是,系统不应该永远处于“有刺”的状态,当危险过后需要恢复正常。
于是对熔断器的核心操作就是如下几个功能:
- 如果成功率过低,就打开熔断器,阻止正常业务
- 随着时间的流动,熔断器处于半打开状态,尝试性放入一笔请求
熔断器的原理很简单,如同电力过载保护器。
- 它可以实现快速失败,如果它在一段时间内侦测到许多类似的错误,会强迫其以后的多个调用快速失败,不再访问远程服务器,从而防止应用程序不断地尝试执行可能会失败的操作,使得应用程序继续执行而不用等待修正错误,或者浪费CPU时间去等到长时间的超时产生。
- 熔断器也可以使应用程序能够诊断错误是否已经修正,如果已经修正,应用程序会再次尝试调用操作。
熔断器模式就像是那些容易导致错误的操作的一种代理。这种代理能够记录最近调用发生错误的次数,然后决定使用允许操作继续,或者立即返回错误。
熔断器机制
熔断器很好理解, 当Hystrix Command请求后端服务失败数量超过一定比例(默认50%), 断路器会切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务.
断路器保持在开路状态一段时间后(默认5秒), 自动切换到半开路状态(HALF-OPEN). 这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN).
Hystrix的断路器就像我们家庭电路中的保险丝, 一旦后端服务不可用, 断路器会直接切断请求链, 避免发送大量无效请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力.
- 熔断器的核心API如下图:
四、限流、熔断、隔离、降级
这四个概念是我们谈起微服务会经常谈到的概念,这里我们讨论的是Hystrix的实现方式。
限流
- 这里的限流与guava的RateLimiter的限流差异比较大,一个是为了“保护自我”,一个是“保护下游”。
- 当对服务进行限流时,超过的流量将直接fallback,即熔断。而RateLimiter关心的其实是“流量整形”,将不规整流量在一定速度内规整。
熔断
- 当我的应用无法提供服务时,我要对上游请求熔断,避免上游把我压垮。
- 当我的下游依赖成功率过低时,我要对下游请求熔断,避免下游把我拖垮。
降级
- 降级与熔断紧密相关,熔断后业务如何表现,约定一个快速失败的fallback,即为服务降级。
- Fallback相当于是降级操作. 对于查询操作, 我们可以实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值. fallback方法的返回值一般是设置的默认值或者来自缓存.
隔离
在Hystrix中, 主要通过线程池来实现资源隔离.
通常在使用的时候我们会根据调用的远程服务划分出多个线程池. 例如调用产品服务的Command放入A线程池, 调用账户服务的Command放入B线程池.
这样做的主要优点是运行环境被隔离开了. 这样就算调用服务的代码存在bug或者由于其他原因导致自己所在线程池被耗尽时, 不会对系统的其他服务造成影响.
但是带来的代价就是维护多个线程池会对系统带来额外的性能开销.
如果是对性能有严格要求而且确信自己调用服务的客户端代码不会出问题的话, 可以使用Hystrix的信号模式(Semaphores)来隔离资源.
- 业务之间不可互相影响,不同业务需要有独立的运行空间。
- 最彻底的,可以采用物理隔离,不同的机器部署。
- 次之,采用进程隔离,一个机器多个tomcat。
- 次之,请求隔离。
- 由于Hystrix框架所属的层级为代码层,所以实现的是请求隔离,线程池或信号量。
普通Javaweb结合AOP使用Hystrix
maven坐标
<!-- https://mvnrepository.com/artifact/com.netflix.hystrix/hystrix-metrics-event-stream -->
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-metrics-event-stream</artifactId>
<version>1.5.12</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.netflix.hystrix/hystrix-javanica -->
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>1.5.12</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-servo-metrics-publisher</artifactId>
<version>1.5.12</version>
</dependency>
web.xml
<!--HystrixMetricsStreamServlet-->
<servlet>
<display-name>HystrixMetricsStreamServlet</display-name>
<servlet-name>HystrixMetricsStreamServlet</servlet-name>
<servlet-class>com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HystrixMetricsStreamServlet</servlet-name>
<url-pattern>/hystrix.stream</url-pattern>
</servlet-mapping>
spring 配置
<aop:aspectj-autoproxy/>
<!--用来拦截处理HystrixCommand注解-->
<bean id="hystrixCommandAspect" class="com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect"/>
service实现类
@HystrixCommand(fallbackMethod="getFallback",commandKey="getUserByName",groupKey="UserGroup",
threadPoolKey="getUserByNameThread")
@Override
public String getUserByName(String name) {
int i=1/0;
return "Hello " + name + "!";
}
protected String getFallback(String name) {
return "faild";
}
service接口
public String getUserByName(String name);
controller类
@Controller
@RequestMapping("/test")
public class RsaControllerTest {
private static final Logger log = LoggerFactory.getLogger(RsaControllerTest.class);
@Autowired
private UserService userService;
@RequestMapping(value="hystrix/{name}",method=RequestMethod.GET)
@ResponseBody
public String getUserByName (@PathVariable String name) {
String user = userService.getUserByName(name);
return "Hello " + user + "!";
}
}
测试结果
- 控制台的输出
Hystrix的监控
这种接口数据不便于我们阅读,所以,我们可以借助Hystrix-dashboard转化为图形化的界面