服务熔断Hystrix
1. Hystrix是什么
分布式系统环境下,服务间类似依赖非常常见,一个业务调用通常依赖多个基础服务。如下图,对于同步调用,当库存服务不可用时,商品服务请求线程被阻塞,当有大批量请求调用库存服务时,最终可能导致整个商品服务资源耗尽,无法继续对外提供服务。并且这种不可用可能沿请求调用链向上传递,这种现象被称为雪崩效应。
为了解决此问题,微服务架构中引入了一种叫熔断器的服务保护机制。
熔断器也有叫断路器,他们表示同一个意思,最早来源于微服务之父Martin Fowler的论文CircuitBreaker一文。“熔断器”本身是一种开关装置,用于在电路上保护线路过载,当线路中有电器发生短路时,能够及时切断故障电路,防止发生过载、发热甚至起火等严重后果。
微服务架构中的熔断器,就是当被调用方没有响应,调用方直接返回一个错误响应即可,而不是长时间的等待,这样避免调用时因为等待而线程一直得不到释放,避免故障在分布式系统间蔓延;
Spring Cloud Hystrix实现了熔断器、线程隔离等一系列服务保护功能。该功能也是基于Netflix的开源框架Hystrix实现的,该框架的目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备服务降级、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等强大功能。
2.引起雪崩效应常见场景
硬件故障:如服务器宕机,机房断电,光纤被挖断等
流量激增:如异常流量,重试加大流量等
缓存击穿:一般发生在应用重启,所有缓存失效时,以及短时间内大量缓存失效时。大量的缓存不命中,使请求直击后端服务,造成服务提供者超负荷运行,引起服务不可用
程序 BUG:如程序逻辑导致内存泄漏,JVM 长时间 FullGC 等
同步等待:服务间采用同步调用模式,同步等待造成的资源耗尽
3. Hystrix快速入门
当有服务调用的时候,才会出现服务雪崩,所以Hystrix常和OpenFeign,Ribbon一起出现。
在SpringCloud中使用熔断器Hystrix, 构建产品服务product-service,订单服务order-service
3.1创建注册中心eurkea-server
3.2设置eurke-server中的application的配置
server:
port: 9100
spring:
application:
name: eureka-server
eureka:
client:
register-with-eureka: false #由于我们目前创建的应用是一个服务注册中心,而不是普通的应用,默认情况下,这个应用会向注册中心(也是它自己)注册它自己,设置为false表示禁止这种自己向自己注册的默认行为
fetch-registry: false #由于我们目前创建的应用是一个服务注册中心,而不是普通的应用,默认情况下,这个应用会向注册中心(也是它自己)注册它自己,设置为false表示禁止这种自己向自己注册的默认行为
3.3产品服务product-service(提供者)提供的服务
@RequestMapping("/product")
public String queryProduct(){
return "商品查询成功,小米笔记本,质量杠杠滴";
}
3.4application文件配置内容
server:
port: 8081
spring:
application:
name: product-service
eureka:
client:
service-url:
defaultZone: http://localhost:9100/eureka
3.5创建消费者项目order-service中添加hystrix依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
3.6在入口类@EnableCircuitBreaker注解开启断路器功能
也可以使用一个名为@SpringCloudApplication的注解代替主类上的三个注解;
@SpringCloudApplication
@EnableCircuitBreaker
@EnableDiscoveryClient
@EnableFeignClients
public class OrderserviceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderserviceApplication.class, args);
}
}
3.7.开启熔断保护
SpringCloud Fegin默认已为Feign整合了hystrix,所以添加Feign依赖后就不用在添加hystrix,
按照以下步骤开发:
(1)order-service中application文件中添加以下配置,开启hystrix
#在feign中开启熔断服务
feign:
circuitbreaker:
enabled: true
3.8自定义一个FeignClient接口的实现类
这个类就是熔断降级触发的逻辑代码
@Component
public class ProductFeignClientCallback implements ProductFeignClient{
@Override //降级服务的实现
public String queryProduct() {
return "产品服务正在升级中,暂时不可用,请客官耐心等待....";
}
}
/*fallback =ProductFeignImpl.class 熔断调用 实现类*/
@FeignClient(name = "product-service",fallback =ProductFeignClientCallback.class)
public interface ProductFeign {
@GetMapping("/product")
String queryProduct();
@GetMapping("/product/{id}") //务必和原始服务提供者方法保持一致
String detail(@PathVariable Integer id);
@PostMapping("/add")
String addProduct(@RequestParam("id") Integer id,@RequestParam("name") String name);
}
3.9使用RestTemplate调用,实现熔断
启动类添加注解@EnableCircuitBreaker
@GetMapping("/orderProduct")
@HystrixCommand(fallbackMethod = "productFallback")
public String orderProduct()
{
String url="http://product-service/product";
return "产品订购完成:"+restTemplate.getForObject(url,String.class);
}
public String productFallback()
{
return "抢购的人太多了,请稍后再来...";
}
注意:
熔断的方法名可以不需要和主方法名保持一致
但是错误的方法返回值类型必须和主方法返回值类型保持一致
错误错误的方法参数类型必须和主方法的参数类型保持一致
否则会报fallbackMethod找不到的异常
3.9在接口上添加熔断类型的注解
@FeignClient(name="product-service",fallback = ProductFeignClientCallback.class)
public interface ProductFeignClient {
}
3.10执行测试
(1)执行测试,当产品服务正常工作时,订单服务正确调用产品服务
(2)关闭产品服务,则执行消费者提供的熔断降级方法
4.常用配置
server:
port: 8081
spring:
application:
name: consumer-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
fetch-registry: true
register-with-eureka: true
instance:
instance-id: ${spring.application.name}:${server.port}
prefer-ip-address: true
feign:
circuitbreaker:
enabled: true
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: BASIC
hystrix:
command:
default:
execution:
isolation:
strategy: THREAD # 隔离方式 THREAD线程隔离集合和SEMAPHORE信号量隔离级别
thread:
timeoutInMilliseconds: 1000 # 调用超时时间
semaphore:
maxConcurrentRequests: 1000 # 信号量隔离是最大并发请求数
circuitBreaker:
requestVolumeThreshold: 3 #失败次数(阀值)
errorThresholdPercentage: 60 #失败率
sleepWindowInMilliseconds: 5000 # 断路器处理打开状态后 多久变成半开
metrics:
rollingStats:
timeInMilliseconds: 10000 # 统计窗口时间
5.隔离方式
hystrix的一项核心功能,就是所谓的资源隔离,资源隔离要解决的最核心的问题,就是将多个依赖服务的调用分别隔离到各自的资源池内。避免对某一个依赖服务的调用,因为依赖服务的接口调用的延迟或者失败,导致服务所有的线程资源全部耗费在这个服务的接口调用上。一旦某个服务的线程资源全部耗尽的话,可能就导致服务就会崩溃,甚至说这种故障会不断蔓延.hystrix支持两种方式的资源隔离
5.1线程池隔离(默认的方式)
隔离是通过线程池来做到的,也就是说他的隔离粒度是线程池。一个请求进来都经过一个线程池。
当前端发起请求过来到服务A或者B之后,服务A和服务B是通过线程池隔离的。服务A是否熔断,是否正常都和服务B无关。
他其实是一个异步编程,用线程池将后面的服务包裹了起来,至于服务内部tomcate的线程运行怎么样是无关的。他适合于绝大多数的场景,对于一些超时的场景都非常好用。但是既然是通过线程池来操作的,不可避免的就是线程之间的计算开销,以及线程上下文的切换,调度消耗。
适合绝大多数的场景。特别是 对其他服务的网络请求与调用存在 timeout 问题时。
如果当前请求的服务,需要耗时很长才能完成服务的响应,为了防止超时,可以使用的线程池杜绝服务因为超时出现问题。
5.2信号量隔离
线程隔离会带来线程开销,有些场景(比如无网络请求场景)可能会因为用开销换隔离得不偿失,为此hystrix提供了信号量隔离,当服务的并发数大于信号量阈值时将进入fallback 。
隔离是通过信号量来做到的。其实是一个计数器。一个请求进来就会减少一个信号,一个请求完成就会增加一个信号。信号量的调用是同步的,也就是说他会阻塞直到请求回来。
适合与对内部的一些比较复杂的业务逻辑的访问,而不是对外部依赖的访问。
像这种访问系统内部的代码,其实不涉及网络请求,那么只要做信号量的普通限流就可以了,因为不需要去捕获 timeout 类似的问题,程序在做 “算法+数据结构” 等操作时如果效率不是太高,并发量突然太高,因为比较微耗时,导致很多线程卡在这里的话,不太好,所以进行一个基本的资源隔离和访问,避免内部复杂的低效率的代码,导致大量的线程被hang住。
6.Hystrix服务熔断开启条件
熔断机制是应对雪崩效应的一种微服务链路保护机制,一般来说,每个服务都需要熔断机制的。如高压电路中,如果某个地方的电压过高,熔断器就会熔断,对电路进行保护。在微服务架构中,熔断机制也是起着类似的作用。
当链路的某个微服务不可用,或响应超时,或宕机,或异常时,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
熔断器开启的条件
当请求达到阀值时候:默认 10 秒内 20 次请求,当请求失败率达到默认 50% 的时,此时断路器将会开启,所有的请求都不会执行
断路器关闭的条件
当断路器开启 5 秒(默认)时,这时断路器是半开状态, 会允许其中一个请求执行
当链路的某个微服务不可用,或响应超时,或宕机,或异常时,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
熔断器开启的条件
当请求达到阀值时候:默认 10 秒内 20 次请求,当请求失败率达到默认 50% 的时,此时断路器将会开启,所有的请求都不会执行
断路器关闭的条件
当断路器开启 5 秒(默认)时,这时断路器是半开状态, 会允许其中一个请求执行
如果执行成功,则断路器会关闭;如果失败,则继续开启。循环重复这两个流程