先看下如下图,两个服务之间的调用 A服务调用另外一个B服务。
图片
在这个图当中有个接口A需要调用另外一个服务的接口B。这里看似没有什么问题。
例如,本身A服务接口执行逻辑需要5ms执行完后再调用B服务接口的,调用B接口执行完成需要4s,比如下面的下定单扣库存的流程:
图片
这里整个接口调用链执行完成需要实际T=4s+5ms;
当有大量的请求调用时我们的所有线程都会被阻塞T的时间。本身Tomcat线程池的线程数量是有限的,这就会造成很多的客户端只能等待,尤其是越是后来的请求等待的时间会越长,同时由于这一个接口把所有的tomcat线程全部占用完,导致了其他的所有服务不可用全部处于等待状态,从而会拖垮整个tomcat,而这种现象我们称诶雪崩效应。
对于服务之间的调用我们也应该能够设置一个超时时间,不能让其一直等待下去。当超过了给定的时间后我们也能够给予用户一个响应,通过这种方式来避免这种级联的故障。如上所述,当整个A服务不可用的时候 这时候的B服务也就不可用了,这种现象被称为雪崩效应。
针对造成雪崩效应的不同场景,可以使用不同的应对策略,没有一种通用所有场景的策略,参考如下:
在程序中我们能通过Hystrix来实现资源的隔离,保护我们的服务,不至于导致整个tomcat服务不可用。
Hystrix是Netflix开源的一款针对分布式系统的延迟和容错库,目的是用来隔离分布式服务故障。它提供线程和信号量隔离,以减少不同服务之间资源竞争带来的相互影响;提供优雅降级机制;提供熔断机制使得服务可以快速失败,而不是一直阻塞等待服务响应,并能从中快速恢复。Hystrix通过这些机制来阻止级联失败并保证系统弹性、可用。通过下图来理解:
图片
也就是在调用B服务接口的时候我们放到另外的一个线程中去执行,防止出现上面说的问题。
接下来我们来通过程序代码来看看如何使用hystrix。
这里我们以调用订单服务为例
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.2.1.RELEASE</version></dependency>
通过继承HystrixCommand
public class OrdersCommand extends HystrixCommand<Orders> { private RestTemplate restTemplate ; private Long id ; public OrdersCommand(RestTemplate restTemplate, Long id) { super(buildSetter()) ; this.restTemplate = restTemplate ; this.id = id ; } private static Setter buildSetter() { comflix.hystrix.HystrixThreadPoolProperties.Setter threadPoolProp = comflix.hystrix.HystrixThreadPoolProperties.Setter() ; threadPoolProp.withCoreSize(5) .withKeepAliveTimeMinutes(5) .withMaxQueueSize(Integer.MAX_VALUE) .withQueueSizeRejectionThreshold(1000) ; comflix.hystrix.HystrixCommandProperties.Setter commandProp = comflix.hystrix.HystrixCommandProperties.Setter() ; commandProp.withCircuitBreakerEnabled(true) .withExecutionTimeoutInMilliseconds(6000) .withRequestCacheEnabled(true) .withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD); return Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("orders")) .andCommandKey(HystrixCommandKey.Factory.asKey("getOrder")) .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("order-pool")) .andThreadPoolPropertiesDefaults(threadPoolProp) .andCommandPropertiesDefaults(commandProp) ; } @Override protected Orders run() throws Exception { return restTemplate.getForObject("http://localhost:9810/orders/queryOrder/{1}", Orders.class, id); } @Override protected Orders getFallback() { return new Orders() ; } @Override protected String getCacheKey() { return "order-" + this.id ; }}
这里我们实现了父类的getFallback方法
该方法为当服务调用失败或者超时会被调用。
这里通过buildSetter方法来构建hystrix相关的配置。说明:
threadPoolProp.withCoreSize(5) .withKeepAliveTimeMinutes(5) .withMaxQueueSize(Integer.MAX_VALUE) .withQueueSizeRejectionThreshold(1000) ;
以上是对线程池的配置。
HystrixCommandProperties.Setter().withCircuitBreakerRequestVolumeThreshold(int)表示在滑动窗口中,至少有多少个请求,才可能触发断路。Hystrix 经过断路器的流量超过了一定的阈值,才有可能触发断路。比如说,要求在 10s 内经过断路器的流量必须达到 20 个,而实际经过断路器的流量才 10 个,那么根本不会去判断要不要断路。
HystrixCommandProperties.Setter().withCircuitBreakerErrorThresholdPercentage(int)表示异常比例达到多少,才会触发断路,默认值是 50(%)。如果断路器统计到的异常调用的占比超过了一定的阈值,比如说在 10s 内,经过断路器的流量达到了 30 个,同时其中异常访问的数量也达到了一定的比例,比如 60% 的请求都是异常(报错 / 超时 / reject),就会开启断路。
HystrixCommandProperties.Setter().withCircuitBreakerSleepWindowInMilliseconds(int)断路开启,也就是由 close 转换到 open 状态(close -> open)。那么之后在 SleepWindowInMilliseconds 时间内,所有经过该断路器的请求全部都会被断路,不调用后端服务,直接走 fallback 降级机制。而在该参数时间过后,断路器会变为 half-open 半开闭状态,尝试让一条请求经过断路器,看能不能正常调用。如果调用成功了,那么就自动恢复,断路器转为 close 状态。
EnabledHystrixCommandProperties.Setter().withCircuitBreakerEnabled(boolean)控制是否允许断路器工作,包括跟踪依赖服务调用的健康状况,以及对异常情况过多时是否允许触发断路。默认值是 true。
HystrixCommandProperties.Setter().withCircuitBreakerForceOpen(boolean)如果设置为 true 的话,直接强迫打开断路器,相当于是手动断路了,手动降级,默认值是 false。
ForceClosedHystrixCommandProperties.Setter().withCircuitBreakerForceClosed(boolean)
如果设置为 true,直接强迫关闭断路器,相当于手动停止断路了,手动升级,默认值是 false。
参考:
图片
comflix.hystrix.HystrixCommandProperties.Setter commandProp = comflix.hystrix.HystrixCommandProperties.Setter() ; commandProp.withCircuitBreakerEnabled(true) .withExecutionTimeoutInMilliseconds(6000) .withRequestCacheEnabled(true) .withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD);
以上是对命令属性的配置。
withCircuitBreakerEnabled:控制是否允许断路器工作,包括跟踪依赖服务调用的健康状况,以及对异常情况过多时是否允许触发断路。默认值是 true。
withExecutionTimeoutInMilliseconds:执行超时时间的设置。如果一个 command 运行时间超过了设定的时长,那么就被认为是 timeout,然后 Hystrix command 标识为 timeout,同时执行 fallback 降级逻辑。
withExecutionIsolationStrategy:执行隔离的策略,这里设置为线程。还可以设置为基于信号量的
ExecutionIsolationStrategy.SEMAPHORE:一般如果并发量比较大的情况下我们用信号量,性能要好。如果并发量大你还用线程池,那么你该创建多少的线程呢?而过多的线程带来了更多线程的切换而影响性能。
return Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("orders")) .andCommandKey(HystrixCommandKey.Factory.asKey("getOrder")) .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("order-pool")) .andThreadPoolPropertiesDefaults(threadPoolProp) .andCommandPropertiesDefaults(commandProp) ;
withGroupKey:服务分组;比如这里调用订单系统就是一个服务分组。模块;
andCommandKey:服务标识;比如这里订单系统有一个获取订单信息服务。子模块;
andThreadPoolKey:线程池名称;
andThreadPoolPropertiesDefaults:线程池配置;
andCommandPropertiesDefaults:命令属性配置;
示例:
@GetMapping("/custom/{id}")public Object custom(@PathVariable Long id) { HystrixRequestContext ctx = HystrixRequestContext.initializeContext() ; try { OrdersCommand command = new OrdersCommand(restTemplate, id) ; System.out.println(Thread.currentThread().getName() + ": " + System.currentTimeMillis()) ; Orders res = command.execute() ; } finally { ctx.shutdown() ; } return null ;}
注意:HystrixRequestContext ctx =HystrixRequestContext.initializeContext() ;这行代码必须调用。
通过注解的方式。
@Servicepublic class RemoteHystrixService { @Resource private RestTemplate restTemplate ; /** * <p> * groupKey: 服务分组;比如这里调用订单系统就是一个服务分组。模块 * commandKey: 服务标识;比如这里订单系统有一个获取订单信息服务。子模块 * threadPoolKey: 线程池名称; * threadPoolProperties:线程池配置 * </p> * @author 爷爷 * @param id * @return Orders */ @HystrixCommand(fallbackMethod = "defaultOrder", groupKey = "orders", commandKey = "getOrder", threadPoolKey = "order-pool", threadPoolProperties = { @HystrixProperty(name = "coreSize", value = "10"), @HystrixProperty(name = "keepAliveTimeMinutes", value = "5"), @HystrixProperty(name = "maxQueueSize", value = "1000000"), @HystrixProperty(name = "queueSizeRejectionThreshold", value = "1000") }, commandProperties = { @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"), @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "6000") } ) public Orders getOrder(Long id) { System.out.println(Thread.currentThread() + ", start") ; Orders res = restTemplate.getForObject("http://localhost:9810/orders/queryOrder/{1}", Orders.class, id); System.out.println(Thread.currentThread() + ", end") ; return res ; } public Orders defaultOrder(Long id) { return new Orders() ; }}
这里具体注解属性的说明与方式1中 一一对应。
本文链接:http://www.28at.com/showinfo-26-17895-0.html快速入门 | 轻松掌握Hystrix实现资源隔离保护系统稳定
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
下一篇: 两种基于时间窗口的限流器的简单实现