这是SpringCloud实战系列中第4篇文章,了解前面第两篇文章更有助于更好理解本文内容:
①SpringCloud 实战:引入Eureka组件,完善服务治理
②SpringCloud 实战:引入Feign组件,发起服务间调用
③SpringCloud 实战:使用 Ribbon 客户端负载均衡java
Hystrix 是一个延迟和容错库,旨在隔离对远程系统、服务和第三方库的访问点,中止级联故障,并在故障不可避免的复杂分布式系统中实现恢复能力。git
在分布式微服务的架构体系下,通常都会存在多层级服务服务的调用链,当链路中的某个服务发生异常,最后致使整个系统不可用,这种现象称为服务雪崩效应。github
如上图所示,从最开始的整个系统正常状态,到单个服务出现异常,再到多个服务出现异常,到最后整个系统可不用,整个过程就是服务雪崩效应。若是在单个服务出现异常的时候,咱们能及时发现、预防、处理,也就不会出现级联效果致使整个系统不可用。Hystrix 就是来保证上面的状况发生时能中止级联故障,保证系统稳定运行的。web
Hystrix 支持线程池和信号量两种隔离方式,默认使用的线程池隔离。spring
线程池隔离是当用户请求到 A 服务后,A 服务须要调用其余服务,这个时候能够为不一样的服务建立独立的线程池,假如 A 须要调用 B 和 C,那么能够建立 2 个独立的线程池,将调用 B 服务的线程丢入到一个线程池,将调用 C 服务的线程丢入到另外一个线程池,这样就起到隔离效果,就算其中某个线程池请求满了,没法处理请求了,对另外一个线程池也没有影响。缓存
信号量隔离就比较简单了,信号量就是一个计数器,好比初始化值是 100,那么每次请求过来的时候就会减 1,当信号量计数为 0 的时候,请求就会被拒绝,等以前的请求处理完成后,信号量会加 1,同时也起到了限流的做用,这就是信号量隔离,信号量隔离是在请求主线程中执行的。架构
线程池隔离的特色是 Command 运行在独立的线程池中,能够支持超时,是单独的线程,支持异步。信号量隔离运行在调用的主线程中,不支持超时,只能同步调用。并发
引入相关依赖app
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
在ribbon-client
服务的启动类上添加注解@EnableHystrix
这时候服务已经能够启动成功了负载均衡
HystrixCommand 注解做用于方法上,哪一个方法想要使用 Hystrix 来进行保护,就在这个方法上增长 HystrixCommand 注解。
好比在咱们的queryPort方法上添加@HystrixCommand注解:
@HystrixCommand(commandKey = "queryPort") @GetMapping("queryPort") public String queryPort(){ return providerFeign.queryPort(); }
其中commandKey不指定的话,会默认使用方法名,这里也是queryPort;
@HystrixCommand 有不少默认的配置,好比超时时间,隔离方式等;咱们能够手动指定配置信息有好比 commandKey、groupKey、fallbackMethod 等。
配置回退方法fallbackMethod
使用@HystrixCommand 注解方式配置回退方法,须要将回退方法定义在HystrixCommand所在的类中,且回退方法的签名与调用的方法签名(入参,返回值)应该保持一致,好比:
private String queryPortFallBack(){ return "sorry queryPort,jinglingwang.cn no back!"; } //调用方法改造 @HystrixCommand(commandKey = "queryPort",fallbackMethod = "queryPortFallBack")
而后咱们把eureka-provider服务停掉或者故意超时,访问接口会出现以下图所示的结果:
咱们也能够结合@HystrixProperty
注解来丰富咱们的配置
@HystrixCommand(commandKey = "queryPort",commandProperties ={ @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000"),//超时时间,默认1000,即1秒 @HystrixProperty(name = "execution.isolation.strategy",value = "SEMAPHORE"),//信号量隔离级别 @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests",value = "50") //信号量模式下,最大请求并发数,默认10 },fallbackMethod = "queryPortFallBack") @GetMapping("queryPort") public String queryPort(){ return providerFeign.queryPort(); }
上面的一些配置信息咱们还能够配置到配置文件中,效果是同样的:
# queryPort 是@HystrixCommand注解里面的commandKey # 隔离方式,SEMAPHORE:信号量隔离,THREAD:线程隔离(默认值) hystrix.command.queryPort.execution.isolation.strategy = SEMAPHORE # 信号量模式下,最大请求并发数,默认10 hystrix.command.queryPort.execution.isolation.semaphore.maxConcurrentRequests = 50 # 超时时间,默认值是1000,也就是1秒;在HystrixCommandProperties类能够看到 hystrix.command.queryPort.execution.isolation.thread.timeoutInMilliseconds = 3000
下面的代码展现了线程隔离级别下的配置示例:
@HystrixCommand(commandKey = "queryTempPort", threadPoolProperties = { @HystrixProperty(name = "coreSize", value = "30"), @HystrixProperty(name = "maxQueueSize", value = "101"), @HystrixProperty(name = "keepAliveTimeMinutes", value = "2"), @HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"), @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"), @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1440") } ,fallbackMethod = "queryTempPortFallBack") @GetMapping("queryTempPort") public String queryTempPort(){ return providerTempFeign.queryPort(); }
咱们也可使用@DefaultProperties
注解来配置默认属性;
@DefaultProperties是做用在类上面的,能够配置一些好比groupKey、threadPoolKey、commandProperties、threadPoolProperties、ignoreExceptions和raiseHystrixExceptions等属性。方法级别的@HystrixCommand命令中单独指定了的属性会覆盖默认的属性,好比:
@RestController @DefaultProperties(groupKey = "DefaultGroupKey") public class RibbonController{ ... @HystrixCommand(commandKey = "queryTempPort",groupKey="eureka-provider-temp", threadPoolProperties = { @HystrixProperty(name = "coreSize", value = "30"), @HystrixProperty(name = "maxQueueSize", value = "101"), @HystrixProperty(name = "keepAliveTimeMinutes", value = "2"), @HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"), @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"), @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1440") } ,fallbackMethod = "queryTempPortFallBack") @GetMapping("queryTempPort") public String queryTempPort(){ return providerTempFeign.queryPort(); } }
开启Feign对Hystrix的支持
在配置文件添加以下配置
# 若是为true,则将使用Hystrix断路器包装OpenFeign客户端,默认是false feign.hystrix.enabled=true
配置fallback
为Feign配置回退方法,将fallback属性设置成回退的类名,例如:
@Component public class ProviderTempFeignFallback implements ProviderTempFeign{ @Override public String queryPort(){ return "sorry ProviderTempFeign, jinglingwang.cn no back!"; } } @FeignClient(value = "eureka-provider-temp",fallback = ProviderTempFeignFallback.class) public interface ProviderTempFeign{ @RequestMapping("/queryPort") String queryPort(); }
咱们保留上面的@HystrixCommand注解,而后启动项目,把eureka-provider项目的接口加一个断点,保证接口会超时。同时配置有两个fallback时,发现最后生效的是@HystrixCommand注解配置的fallback,说明@HystrixCommand注解的优先级要高一些,返回结果如图:
而后咱们把@HystrixCommand注解注释掉,再重启,成功执行了Feign配置的fallback,效果如图:
fallback返回失败的缘由
若是须要访问致使失败回退的缘由,可使用@FeignClient内的fallbackFactory属性。
@Component public class ProviderFeignFallbackFactory implements FallbackFactory<ProviderFeign>{ @Override public ProviderFeign create(Throwable cause){ return new ProviderFeign(){ @Override public String queryPort(){ return "sorry ProviderFeignFallbackFactory, jinglingwang.cn no back! why? ==>" + cause.getCause(); } }; } } @FeignClient(value = "eureka-provider",fallbackFactory = ProviderFeignFallbackFactory.class) public interface ProviderFeign{ /** * 调用服务提供方,其中会返回服务提供者的端口信息 * @return jinglingwang.cn */ @RequestMapping("/queryPort") String queryPort(); }
网关中使用Hystrix等到了整合网关的时候再细讲。
默认配置是全局有效的
# 配置 Hystrix 默认的配置 # To set thread isolation to SEMAPHORE hystrix.command.default.execution.isolation.strategy: SEMAPHORE hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 3000 hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests: 40
单独为Feign Client 来指定超时时间
# 单独为 ProviderFeign 配置 hystrix.command.ProviderFeign.execution.isolation.strategy = SEMAPHORE # 超时时间 hystrix.command.ProviderFeign.execution.isolation.thread.timeoutInMilliseconds = 5000 # 最大请求并发数,默认10 hystrix.command.ProviderFeign.execution.isolation.semaphore.maxConcurrentRequests: 200
单独为ProviderTempFeign类的queryPort()方法进行配置
# 单独为ProviderTempFeign类的queryPort()方法配置 hystrix.command.ProviderTempFeign#queryPort().execution.isolation.strategy = THREAD # 超时时间 hystrix.command.ProviderTempFeign#queryPort().execution.isolation.thread.timeoutInMilliseconds = 5000
使用 @HystrixCommand 注解配置
具体作法能够参考上面的示例代码
Hystrix的配置项有不少,其余属性的配置key能够参考HystrixCommandProperties
类。
Hystrix 的超时时间是和Ribbon有关联的,若是配置的不对,可能会出现莫名其妙的问题。
在Hystrix源码里面是建议hystrixTimeout
应该大于等于ribbonTimeout
的时间的,不然会输出一句警告:
LOGGER.warn("The Hystrix timeout of " + hystrixTimeout + "ms for the command " + commandKey + " is set lower than the combination of the Ribbon read and connect timeout, " + ribbonTimeout + "ms.");
而在取ribbonTimeout
配置值的时候,是有一个计算公式的:
ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1);
假如咱们Ribbon的超时时间配置以下:
#读超时 ribbon.ReadTimeout=3000 #链接超时 ribbon.ConnectTimeout=3000 #同一台实例最大重试次数,不包括首次调用 ribbon.MaxAutoRetries=0 #重试负载均衡其余的实例最大重试次数,不包括首次调用 ribbon.MaxAutoRetriesNextServer=1
将上面的值代入到公式计算,获得结果:ribbonTimeout=(3000+3000)(0+1)(1+1),结果为12000,也就是说Hystrix 的超时时间建议配置值要大于等于12000,也就是12秒。
引入dashboard依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
在启动类上加入注解@EnableHystrixDashboard
而后启动项目,访问http://localhost:7071/hystrix,咱们能够看到以下页面:
可是这时候并不能直接使用,须要对项目进行监控,首先要有对应的 Stream 地址,Stream 产生数据源,而后将仪表板指向Hystrix客户端应用程序中的单个实例/hystrix.stream端点。
咱们在被ribbon-client项目中加入 spring-boot-starter-actuator
依赖,只有加入了 actuator 才能暴露出 hystrix.stream 端点。
而后再配置文件添加以下配置:
management.endpoints.web.exposure.include = hystrix.stream
启动项目,访问http://localhost:7071/actuator/hystrix.stream接口,你会发现页面一直在显示ping;
而后把该地址配置到上面的页面中,点击monitor,OK,等待loading。
而后咱们随便访问一些接口,就能够看到监控内容了。
代码示例:Github ribbon client