现状
分布式场景中. 若服务不稳定, 会导致调用方服务也不可用, 从而造成雪崩效应. 因此要对在原服务不可用时进行熔断降级处理.
分析
熔断降级可以服务端限流, 网关限流, 客户端限流.
1. 客户端限流: 在调用方法发起请求时检查是否达到阀值. 若达到阀值, 不发起调用请求
优点: 可以在服务消费端直接控制流量出口, 减少不必要请求的发起.
缺点: 客户端需要感知服务运行指标和容灾规则. 每个业务方需要重复开发
2. 服务端限流: 服务提供方自定义容灾逻辑, 在收到请求后再根据当前状态判断是否走 fallback 逻辑
优点: 容灾规则, 阀值完全封装在服务提供者. 对调用方无感知.
缺点: 若服务提供者都挂了, 无法进行容灾.
3. 网关限流: 原本直接调用提供者的请求都由网关层代理转发. 容灾规则的配置, 降级逻辑都封装在网关层.
优点: 客户端, 服务端都无需感知容灾逻辑.
缺点: 多了一次网络请求, rt 变大
大部分情况下, 我们都是选择服务端限流. 但客户端对数据平台的接口是强依赖的. 若搜索应用挂了, 客户端还是需要看到数据. 相比高可用, 略微的 rt 变大是可以接受的, 所以启动一个数据容灾网关
技术选型
现在了解到的开源容灾框架有 hystrix,sentinel 两种.
hystrix: 常用于 springcloud 的一个熔断降级组件. 主要功能是不同服务之间的资源隔离, 失败降级. 底层实现是 Rxjava. 它提供两种资源隔离的模式: 信号量隔离和线程池隔离. 一般使用线程池隔离. 耗费一定资源, 但相比之下支持超时和异步执行. 听起来可以覆盖大部分场景, 但它不支持更高要求的流控, 如 qps 的控制. 所以需要单独采用令牌漏桶来做流量控制.
sentinel: 阿里开源的分布式流量控制组件. 支持流控, 熔断降级, 系统保护等. 所有的资源都对应一个资源名称以及一个 Entry. 每一个 Entry 创建的时候, 同时也会创建一系列插件 (系统保护插件: SystemSlot, 流控插件: FlowSlot, 熔断降级插件 LDegradeSlot 等). 每个插件会监控自己职责范围内的指标. NodeSelectorSlot 将各个资源的调用路径以树状存储, 用于限流降级. 调用者通过创建上下文, 请求 token 来执行方法. 若没有抛出 BlockException, 表示请求成功. 它支持并发数 / qps 的流量控制, 也支持熔断降级.
对比: 1.hystrix 的熔断都围绕线程池展开. 更适合做资源隔离, 但单个应用有多个服务时线程池开销会造成浪费. hystrix 是单个超时立即熔断, 控制力度更细. 多个微服务的场景可以考虑用这种. 2.sentinel 是基于并发数, 支持的场景也更复杂, 开销小, 适合在保证服务稳定的情况下提高吞吐量. 但它的超时是 5 次请求的平均响应时间. 并不是很严格. 但对于大多数场景而言可以接受
接入方式
sentinel 支持 API 和注解两种接入方式. 作为容灾网关, 之后可以会接很多接口. 为了接入简单, 对代码无侵入. 需要使用注解的方式. 但是原生的 @SentinelResource 有几个问题:
1. 只能指定资源名称, fallback 方法. 用户还是需要通过 API 创建容灾规则,
2. 而且 fallback 方法入参要加上 BlockException. 这样的接入方式不是很优雅.
3. 流控异常 FlowException 的方法要另外指定.
于是基于 sentinel 封装了一层自定义注解 @AegisResource
@AegisResource(value = "hello",limitThread = 0,timeOut = 100,failRate = 0.5,timeWindows = 100,fallback = "exceptionHandler")
参数说明:
value: 资源名称, 默认为方法名
limitThread: 最大线程数, 默认 - 1, 即不启用
timeOut: 接口超时时间, 默认 - 1, 即不启用
failRate: 失败率, 默认 - 1, 即不启用
timeWindows: 触发降级但持续时间, 默认 100
fallback: 降级方法, 必须指定
接入 demo
- /**
- * 保护的方法
- * @return
- */
- @GetMapping("resourcetest")
- @AegisResource(value = "hello",limitThread = 0,timeOut = 100,failRate = 0.5,timeWindows = 100,fallback = "exceptionHandler")
- public String hello() {
- return "ok";
- }
- /**
- * 降级的方法
- * @return
- */
- public String exceptionHandler() {
- // Do some log here.
- return "Oops, error occurred at" ;
- }
新接口只需写好希望执行的方法和降级方法, 然后在希望执行的方法上加入 @AegisResource(fallBack="fallback 的方法名") 就可以无侵式入地进行容灾. 切面定义了默认容灾阀值. 也可以在对应属性上设置自定义的阀值.
后期规划
目前容灾网关可以满足目前的需求. 目前有开源的控制台, 可以查看服务调用大盘, 动态调整容灾规则. 缺点是目前指标的搜集是 http 方式. 容灾规则, 运行指标也没有持久化存储. 后期如果需要, 可以借助现有的开源控制台进行二次开发.
来源: https://juejin.im/post/5c6e8f42f265da2dda694bc5