文章目录
前言
接着上章【SpringCloudNetflix-OpenFeign客户端负载均衡】,继续学习Hystrix熔断器
一、Hystrix是什么?
Hystrix是国外知名的视频网站Netflix所开源的非常流行的高可用架构框架。Hystrix能够完美的解决分布式系统架构中打造高可用服务面临的一系列技术难题,如雪崩。
Hystrix是处理依赖隔离的框架,将出现故障的服务通过熔断、降级等手段隔离开来,这样不影响整个系统的主业务(比如你得了传染病是不是要把你关起来隔离呢),同时也是可以帮我们做服务的治理和监控。
Hystrix的英文是豪猪,中文翻译为 熔断器,其思想来源于我们家里的保险开关,当家里出现短路,保险开关及时切掉电路,保证家里人员的安全,其目的就是起保护作用。
Hystrix其设计原则如下:
- 防止单个服务异常导致整个微服务故障。
- 快速失败,如果服务出现故障,服务的请求快速失败,线程不会等待。
- 服务降级,请求故障可以返回设定好的二手方案数据(兜底数据)。
- 熔断机制,防止故障的扩散,导致整个服务瘫痪。
- 服务监控,提供了Hystrix Bashboard仪表盘,实时监控熔断器状态
二、雪崩效应
在微服务里面,服务的调用非常复杂的 ,一个请求往往需要很多的微服务共同完成,可能会形成很长的服务调用链,在整个服务调用链中,某一个服务发生故障会导致调用它的服务跟着异常,然后导致整个调用链调用的异常,甚至导致整个微服务瘫痪 , — 这就是雪崩效应。如下图:
三、Hystrix的功能
1. 资源隔离(流控,限流)
资源隔离包括线程池隔离和信号量隔离,作用是限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用 ,这里可以简单的理解为资源隔离就是限制请求的数量。
-
线程池隔离:使用一个线程池来存储当前请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求先入线程池队列。这种方式要为每个依赖服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)
-
信号量隔离:使用一个原子计数器(或信号量)记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃该类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)
2. 服务熔断
熔断机制是对服务链路的保护机制,如果链路上的某个服务不可访问,调用超时,发生异常等,服务会触发降级返回托底数据,然后熔断服务的调用(失败率达到某个阀值服务标记为短路状态),当检查到该节点能正常使用时服务会快速恢复。
简单理解就是当服务不可访问了或者服务器报错了或者服务调用超过一定时间没返回结果,就立马触发熔断机制配合降级返回预先准备的兜底数据返回,不至于长时间的等待服务的相应造成大量的请求阻塞,也不至于返回一些错误信息给客户端,而是返回一些兜底数据。
3. 降级机制
超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。
简单理解就是服务降级就是当服务因为网络故障,服务器故障,读取超时等原因造成服务不可达的情况下返回一些预先准备好的数据给客户端。
在生活中服务降级到处可见,如在系统故障我们会返回友好的提示“服务暂时不可用”,或者如淘宝双11期间退款服务,留言服务暂时不可用,这个就是为了保证正常的购物流程相关服务没问题,然后人为的关闭一些不重要的服务,配合降级返回一些托底数据返回给用户(比如返回友好的提示信息“暂时不能退款”)。
4. 缓存
提供了请求缓存、请求合并实现 , 在高并发的场景之下,Hystrix请求缓存可以方便地开启和使用请求缓存来优化系统,达到减轻高并发时请求线程的消耗、降低请求响应时间的效果。
四,Hystrix工作机制
正常情况下,断路器处于关闭状态(Closed),如果调用持续出错或者超时达到设定阈值,电路被打开进入熔断状态(Open),这时请求这个服务会触发快速失败(立马返回兜底数据不要让线程死等),后续一段时间内的所有调用都会被拒绝(Fail Fast),一段时间以后(withCircuitBreakerSleepWindowInMilliseconds=5s),保护器会尝试进入半熔断状态(Half-Open),允许少量请求进来尝试,如果调用仍然失败,则回到熔断状态,如果调用成功,则回到电路闭合状态。
五,代码实现
接上一章【SpringCloudNetflix-OpenFeign客户端负载均衡】,在springcloud-order-server工程的基础身上做修改,我们让Hystrix和Ribbon配合使用,下一章节我们讲Feign和Hystirx配合使用。
1.导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2. 配置类开启Hystrix
主配置类通过 @EnableCircuitBreaker 标签开启熔断功能
/**
* 订单的启动类
*/
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //开启Hystrix熔断
public class OrderServerApplication{
}
3.方法熔断
通过 @HystrixCommand 标签标记方法熔断,标签的fallbackMethod属性指定拖地方法。那么当该方法在像远程服务发起调用出现异常,或是方法本身出现异常都会触发托底方法的执行,最终结果是托底方法的返回结果。
代码如下(示例):
//浏览器调用该方法
@HystrixCommand(fallbackMethod = "fallbackMethod") //方法熔断
@RequestMapping(value = "/order/{id}",method = RequestMethod.GET)
public User getById(@PathVariable("id")Long id){
//发送http请求调用 user的服务,获取user对象 : RestTemplate
//user的ip,user的端口,user的Controller路径
//String url = "http://localhost:1020/user/"+id;
String url = "http://user-server/user/"+id;
//发送http请求
return restTemplate.getForObject(url, User.class);
}
//降级方法 , 参数和返回值必须和被熔断的方法一致 ,方法名要和 fallbackMethod 的值一致
public User fallbackMethod(@PathVariable("id")Long id){
//返回托底数据
return new User(-1L ,"无此用户","用户服务不可用");
}
我们可以在每个方法上打@HystrixCommand(fallbackMethod = “fallbackMethod”) 标签进行方法单独熔断,也可以在Controller使用 @DefaultProperties做统一配置,如
@RestController
@DefaultProperties(defaultFallback ="fallbackMethod") //统一降级配置
public class OrderController {
@HystrixCommand //方法熔断
@RequestMapping(value = "/order/{id}",method = RequestMethod.GET)
public User getById(@PathVariable("id")Long id){
}
4 托底方法
//托底方法
public User fallbackMethod(){
//返回友好的提示信息
return new User(-1L,"无此用户","用户服务不可用");
}
注意:公共的托底方法不应该有参数。返回类型必须和熔断的方法的返回类型兼容,比如这里可以返回User或者User的子类。
在生产环境中我们可以让所有的方法都有相同的返回结果,如统一的JSON返回结果(JSONResult) ,那么在默认的降级方法中的返回类型就可以使用JSONResult了。
六,Hystrix参数配置
1.熔断参数配置
hystrix:
command:
default:
circuitBreaker:
requestVolumeThreshold: 20 #20个请求中
errorThresholdPercentage: 50 #出错百分比阈值
sleepWindowInMilliseconds: 50000 #短路5秒钟,尝试恢复
Hystrix提供了如下的几个关键参数(com.netflix.hystrix.HystrixCommandProperties),来对一个熔断器进行配置:
- hystrix.command.default.circuitBreaker.requestVolumeThreshold 滑动窗口的大小,默认为20
- hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds 过多长时间熔断器再次检测是否开启默认5000(5s)
- hystrix.command.default.circuitBreaker.errorThresholdPercentage 错误率,默认50%
3个参数放在一起,所表达的意思就是:
每当20个请求中,有50%失败时,熔断器就会打开,此时再调用此服务,将会直接返回失败,不再调远程服务。直到5s钟之后,重新检测该触发条件,判断是否把熔断器关闭,或者继续打开。
2.超时设置
我们知道在服务的远程调用过程中,如果调用时间过久会触发Ribbon的超时,Hystrix也有超时配置 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
我们往往会把Ribbon和Hystrix的超时配合起来配置:
代码如下(示例):
ribbon:
MaxAutoRetries: 1 #最大重试次数
MaxAutoRetriesNextServer: 1 #切换实例的重试次数
OkToRetryOnAllOperations: false # 对所有的操作请求都进行重试,
ConnectTimeout: 1000 #请求连接的超时时间
ReadTimeout: 1800 #请求处理的超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000 #hystrix超时,置thread和semaphore两种隔离策略的超时时间
需要注意的是,我们设置了重试机制就需要Hystrix超时时间大于Ribbon超时时间,因为Hystrix超时后会直接熔断就不会再出发重试
hystrix超时=(1 + MaxAutoRetries + MaxAutoRetriesNextServer) * ReadTimeout
如果要针对于某个API做单独的超时设置也可以通过如下方式单独指定
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
@RequestMapping(value = "/order/{id}",method = RequestMethod.GET)
public User getById(@PathVariable("id")Long id){
3. 资源隔离模式
在Hystrix可以通过execution.isolation.strategy配置切换资源隔离模式:线程池或者信号量
hystrix:
command:
default:
execution:
isolation:
strategy: THREAD #默认是线程池,可以修改为信号量: SEMAPHORE
除了上面方式也可以使用如下方式单独指定:
@HystrixCommand(fallbackMethod = "", commandProperties=@HystrixProperty(name="execution.isolation.strategy", value="THREAD") )
@RequestMapping(value = "/order/{id}",method = RequestMethod.GET)
public User getById(@PathVariable("id")Long id){
}
4. 最大请求设置
通过 execution.isolation.semaphore.maxConcurrentRequests 配置信号量模式下的最大请求数量(并发)
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 30000 #hystrix超时
strategy: SEMAPHORE #默认是线程池,可以修改为信号量: SEMAPHORE
semaphore:
maxConcurrentRequests: 10 # SEMAPHORE模式下 , 1秒钟最大请求数量,默认10
在线程池模式下通过 hystrix.threadpool.default.coreSize设置线程并发数量,
通过hystrix.threadpool.default.maxQueueSize 设置线程池队列模式,通过hystrix.threadpool.default.queueSizeRejectionThreshold设置排队数量 ,见:com.netflix.hystrix.HystrixThreadPoolProperties
hystrix:
threadpool:
default:
coreSize: 10 #最大线程数 , 并发执行的最大线程数,默认10
maxQueueSize: -1 #最大排队长度。默认-1,使用SynchronousQueue。其他值则使用 LinkedBlockingQueue。
queueSizeRejectionThreshold: 5
...
5.Fallback设置
并发达到 hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests 配置的值时,fallback会被被调用,模式10
通过配置hystrix.command.default.fallback.enabled 开启fallback ,即请求失败尝试走托底
代码如下(示例):
hystrix:
command:
default:
fallback:
enabled: true #请求失败时是否要走托底
isolation:
semaphore:
maxConcurrentRequests: 10 #fallback最大并发数量
七,OpenFeign使用Hystrix
支付服务 springcloud-pay-server之前集成了Feign,修改该工程集成Hystrix。我们除了要给Feign开启Hystrix以外还需要为Feign接口编写托底类。
1.开启Hystrix
通过feign.hystrix.enabled=true开启Hystrix
feign:
hystrix:
enabled: true #开启熔断支持
ribbon:
ReadTimeout: 1000
SocketTimeout: 1000
ConnectTimeout: 1000
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 4000
2.Fiegn接口熔断-fallback方式
服务通过Feign接口调用异常或超时需要触发降级,返回托底数据。这里有两种方式,分别是通过@FeignClient(fallback=…) ,以及@FeignClient(fallbackFactory=…) 来指定托底类,区别在于通过fallback的方式编写的托底是没办法打印出异常日志的 ,而fallbackFactory方式是可以打印出异常日志, 我们先来看第一种写法:
@FeignClient(value = "user-server",fallback = UserFeignClientFallback.class)
public interface UserFeignClient {
//订单服务来调用这个方法 http://localhost:1020/user/10
// @GetMapping(value = "/user/{id}" )
@RequestMapping(value = "/user/{id}",method = RequestMethod.GET)
User getById(@PathVariable("id")Long id);
}
提示: fallback = UserFeignClientFallback.class : 该类是当前接口的实现类 ,也是托底数据所在的处理类
托底实现类:
// 让Spring扫描到该托底类
// 需要交给Spirng管理,类上需要打 @Component 注解
@Component
public class UserFeignClientFallback implements UserFeignClient {
//日志打印器
private Logger log = LoggerFactory.getLogger(UserFeignClientFallback.class);
@Override
public User getById(Long id) {
log.info("用户服务不可用");
//托底数据
return new User(-1l,"无此用户","用户服务不可用");
}
}
3.Fiegn接口熔断-fallbackFactory方式
使用fallbackFactory属性,使用工厂方式指定托底
// fallbackFactory配置降级,服务通过Feign接口调用异常或超时需要触发降级,返回托底数据,并返回异常信息
@FeignClient(value = "user-service",fallbackFactory = UserFeignClientFallbackFactory.class)
public interface UserFeignClient {
@GetMapping("/user/{id}")
User getUserById(@PathVariable("id") Long id); // 直接复制user服务controller方法
}
编写托底类
工程方式的托底类需要去实现 FallbackFactory接口 ,并指定泛型为“”Feign客户端接口(UserFeignClient )。FallbackFactory的create方法返回了Feign客户端接口的实例,该方法的throwable是参数是Feign调用失败的异常信息,如下:
// 交给spring管理
// FallbackFactory于定义当服务调用出现故障或异常时的降级处理逻辑
@Component
public class UserFeignClientFallbackFactory implements FallbackFactory<UserFeignClient> {
/**
* 该方法在服务调用发生故障时被调用,它接收一个Throwable类型的参数,表示调用产生的异常。
* FallbackFactory可以根据这个异常类型,返回一个实现了原始服务接口的降级逻辑实例,
* 用来替代原始服务的调用。
* @param throwable
* @return
*/
@Override
public UserFeignClient create(Throwable throwable) {
// 打印错误异常信息
throwable.printStackTrace();
// 返回降级数据
return new UserFeignClient() {
@Override
public User getUserById(Long id) {
return new User(-1l,"","系统错误,请稍后重试");
}
};
}
}
总结
对Hystrix熔断器的基础知识学习和掌握基本使用