微服务保护学习笔记(四)FeignClient整合Sentinel、线程隔离(舱壁模式)、熔断降级
文章目录
- 前言
- 3 隔离和降级
- 3.1 FeignClient整合Sentinel
- 3.1.1 搭建SpringCloud项目
- 3.1.2 修改配置文件
- 3.1.3 编写失败降级逻辑
- 3.2 线程隔离(舱壁模式)
- 3.2.1 线程隔离的实现方式
- 3.2.2 线程池隔离
- 3.3 熔断降级
- 3.3.1 熔断降级的实现方式
- 3.3.2 慢调用比例
- 3.3.3 异常比例、异常数
前言
微服务保护学习笔记(一)雪崩问题及解决方案、Sentinel介绍与安装
微服务保护学习笔记(二)簇点链路、流控操作、流控模式(关联、链路)
微服务保护学习笔记(三)流控效果(warm up、排队等待)、热点参数限流
3 隔离和降级
限流是一种预防措施,它可以尽量避免因高并发而引起的服务故障。但服务还会因为其它原因而故障,而要将这些故障控制在一定范围内避免雪崩,就要依靠线程隔离(舱壁模式)和熔断降级手段了。
-
线程隔离:服务调用者在调用服务提供者时,给每个调用请求分配独立线程池,出现故障时,这个请求最多消耗这个线程池内的资源,避免把调用者的所有资源耗尽。
-
熔断降级:在调用方这边加入断路器,统计对服务提供者的调用,如果调用的失败比例过高,则熔断该业务,不允许访问该服务提供者。
可以看到,不管是线程隔离还是熔断降级,都是对客户端(调用方)的保护,需要在调用方发起远程调用时做线程隔离或者服务熔断。
SpringCloud微服务远程调用都是基于Feign来完成的,因此需要将Feign与Sentinel整合,在Feign里面实现线程隔离和服务熔断。
3.1 FeignClient整合Sentinel
3.1.1 搭建SpringCloud项目
创建一个SpringCloud项目,sd-user-service
和sd-order-service
微服务均注册到Eureka中:
sd-user-service
微服务中有一个/user/query
接口,调用该接口后会通过Feign调用sd-order-service
微服务:
// com.hsgx.user.controller.UserController@Autowired
private OrderClient orderClient;@GetMapping("/query")
public String query(String id) {String order = orderClient.queryById(id);return "查询用户" + id + "成功," + order;
}
// com.hsgx.order.controller.OrderController@GetMapping("/query")
public String query(String id) {return "查询订单" + id + "成功";
}
3.1.2 修改配置文件
修改sd-user-service
微服务(调用方)的配置文件,开启Feign的Sentinel功能:
feign:sentinel:enabled: true
3.1.3 编写失败降级逻辑
业务失败后,不能直接报错,而应该返回一个友好提示或者默认结果,这就是失败降级逻辑。
- 1)在
sd-user-service
微服务中定义类,实现FallbackFactory
// com.hsgx.user.config.OrderClientFallbackFactorypublic class OrderClientFallbackFactory implements FallbackFactory<OrderClient> {@Overridepublic OrderClient create(Throwable throwable) {return new OrderClient() {@Overridepublic String queryById(String id) {return "调用order微服务失败";}};}
}
- 2)在
DefaultFeignConfiguration
类中将OrderClientFallbackFactory
注册为一个Bean
// com.hsgx.user.config.DefaultFeignConfiguration@Bean
public OrderClientFallbackFactory orderClientFallbackFactory(){return new OrderClientFallbackFactory();
}
- 3)在
OrderClient
接口中使用OrderClientFallbackFactory
// com.hsgx.user.feign.OrderClient@FeignClient(value = "sd-order-service",configuration = DefaultFeignConfiguration.class,fallback = OrderClientFallbackFactory.class)
public interface OrderClient {@GetMapping("/order/query")String queryById(@RequestParam("id") String id);
}
重启sd-user-service
微服务后,再次访问/user/query
接口,然后查看Sentinel控制台,可以看到新的簇点链路:
3.2 线程隔离(舱壁模式)
3.2.1 线程隔离的实现方式
如上图所示,线程隔离有两种方式实现:
- 线程池隔离:给每个调用业务分配一个线程池,利用线程池本身实现隔离效果。例如,给访问服务A的线程分配一个单独的线程池,如果访问服务A失败,则只在当前线程池中失败,而不会影响其他线程池。
- 信号量隔离(即QPS):不创建线程池,而是计数器模式,记录调用业务使用的线程数量,达到信号量上限时,禁止新的请求。
3.2.2 线程池隔离
在给资源添加限流规则时,阈值类型可以选择“QPS”或者“线程数”。而选择“线程数”时,就是该资源能使用的线程数最大值,从而实现线程隔离。
下面对线程池隔离进行测试:
- 1)配置隔离规则,使
GET:http://sd-order-service/order/query
资源的最大线程数为2
- 2)使用jmeter进行测试,使线程数为10
- 3)测试
/user/query?id=1
测试结果发现,虽然结果都是通过,不过部分请求得到的响应是降级后的。测试结果符合预期。
3.3 熔断降级
3.3.1 熔断降级的实现方式
熔断降级思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该调用服务,即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。
断路器控制熔断和放行是通过状态机来完成的:
如上图所示,状态机包括三个状态:
- closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则会切换到open状态。
- open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。open状态5秒后会进入half-open状态。
- half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。如果请求成功,则切换到closed状态;如果请求失败,则切换到open状态。
在Sentinel控制台新增降级规则时,有三种熔断策略可选:
3.3.2 慢调用比例
响应时长(RT)大于指定时长的调用请求则被认定为慢调用请求。该策略的原理如下:
在以上配置中,RT超过500ms的请求则是慢调用请求。统计最近10000ms内的调用请求,如果请求量超过10次,且慢调用比例超过0.5(即超过5次),则触发熔断,熔断时长为5秒。然后状态机进入half-open状态,放行一次请求做测试。
下面对慢调用策略进行测试:
- 1)修改
sd-order-service
微服务的/order/query
接口,通过休眠模拟一个延迟时间:
@RestController
@RequestMapping("/order")
public class OrderController {@GetMapping("/query")public String query(String id) throws InterruptedException {if("1".equals(id)) {// 当id=1时,触发慢调用Thread.sleep(600);}return "查询订单" + id + "成功";}}
此时调用/order/query?id=1
接口的响应时间较长,超过500ms,将被认定为慢调用请求:
而调用/order/query?id=2
接口的响应时间很短,不超过500ms,将不会被认定为慢调用请求:
- 2)设置熔断规则
- 3)使用jmeter进行测试,设置20秒内发送50个请求
- 4)调用
/user/query?id=1
接口
- 5)调用
/user/query?id=2
接口
3.3.3 异常比例、异常数
异常比例和异常数策略的原理是一样的,只是前者以比例来限定阈值,后置以具体的数值来限定阈值,例如:
在以上配置中,统计最近1000ms内的调用请求,如果请求量超过10次,且异常比例超过0.4(异常数超过4次),则触发熔断,熔断时长为5秒。
下面对异常比例策略进行测试:
- 1)修改
sd-order-service
微服务的/order/query
接口,手动抛出异常,模拟出现异常:
@GetMapping("/query")
public String query(String id) throws InterruptedException {if("1".equals(id)) {// 当id=1时,触发慢调用Thread.sleep(600);}if("2".equals(id)) {// 当id=2时,触发异常throw new RuntimeException("查询订单异常");}return "查询订单" + id + "成功";
}
- 2)调用
/user/query?id=3
接口,此时是正常访问的
- 3)设置上述熔断规则,并使用jmeter进行测试,设置1秒内发送20个请求调用
/user/query?id=2
接口,触发熔断
- 4)在5s内再次调用
/user/query?id=3
接口,会发现已被阻止,即熔断已发生。5s后再次调用,发现服务恢复正常。
…
本节完,更多内容请查阅分类专栏:微服务学习笔记
感兴趣的读者还可以查阅我的另外几个专栏:
- SpringBoot源码解读与原理分析
- MyBatis3源码深度解析
- Redis从入门到精通
- MyBatisPlus详解
- SpringCloud学习笔记