写在最前面:谈谈服务雪崩、降级与熔断
向上限流,向下熔断!
服务降级有很多种降级方式,如开关降级、限流降级、熔断降级!
服务熔断属于降级方式的一种!
1. 分布式系统遇到的问题
在分布式服务中,一个业务逻辑通常会依赖多个服务,如果其中的积分服务不可用,就会出现线程池里所有的线程因等待响应而被阻塞,从而造成整个服务链路不可用,进而导致整个系统的服务雪崩。如图:
服务雪崩效应:因服务提供者的不可用导致服务调用者的不可用,并将不可用逐渐放大的过程,就叫服务雪崩效应。
导致服务不可用的原因:
在服务提供者不可用的时候,会出现大量重试的情况:用户重试、代码逻辑重试,这些重试最终导致进一步加大请求流量。所以导致雪崩效应的根本原因是:大量请求线程同步等待造成的资源耗尽。
解决方案
常见的容错机制:
- 超时机制:调用下游时加入超时机制,一旦超时就释放资源,在一定程度上可以抑制资源耗尽的问题;
- 服务限流:下游提供固定QPS处理的能力,超过后的请求直接拒绝;
- 隔离:用户的请求将不再直接访问服务,而是通过线程池中空闲的线程来访问,如果线程池满了,则执行降级处理;
- 服务熔断:下游服务不稳定或网络抖动时暂时关闭请求;
- 服务降级:当某个服务熔断之后,服务将不再被调用,而是用本地的fallback回调返回固定值。(这里熔断和降级概念上不是很清晰!)
2. Sentinel:分布式系统的流量防卫兵
Sentinel是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。
3. 快速开始
官网:快速开始、基本使用-资源与规则
- 引入Sentinel核心库依赖:
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-core</artifactId> </dependency> - 定义资源、流控规则:
@Slf4j @RestController @RequestMapping("/sentinel") public class SentinelController { private static final String RESOURCE_NAME = "resource"; @PostConstruct private void initFlowRules(){ // 配置规则 List<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(); rule.setResource(RESOURCE_NAME); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(1); rules.add(rule); FlowRuleManager.loadRules(rules); } @PostMapping("/query") public String query(@RequestBody Object obj) { log.debug("sentinel 请求req:{}", JSON.toJSON(obj)); Entry entry = null; try { // 定义资源 entry = SphU.entry(RESOURCE_NAME); // 被保护的逻辑 log.info("hello world"); return "success"; } catch (BlockException e) { // 处理被流控的逻辑 log.info("block!"); } finally { if (entry != null) { entry.exit(); } } return "error"; } } - 跑一把,nice:
缺点:
- 业务侵入性很强,需要在controller中写入非业务代码;
- 配置不灵活,若需要添加新的受保护资源,需要手动添加init方法来添加流控规则。
4. @SentinelResource注解
- 引入sentinel切面依赖:
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-annotation-aspectj</artifactId> </dependency> - 定义规则(流量控制规则),注解定义资源:
/** * SentinelResource注解 * value:定义资源 * blockHandlerClass/blockHandler:指定限流降级处理的类/方法 * fallbackClass/fallback: 指定接口业务异常处理的类/方法 * exceptionsToIgnore:指定哪些异常被排除掉,不会计入异常统计中,也不会进入fallback逻辑中,而是会原样抛出 * 特别地,若blockHandler和fallback都进行了配置,则被限流降级而抛出BlockException时只会进入blockHandler处理逻辑 */ @PostMapping("/annotation/query") @SentinelResource(value = RESOURCE_NAME, blockHandlerClass = StockFeignClientBlockHandler.class, blockHandler = "query", fallbackClass = StockFeignClientFallBackHandler.class, fallback = "query") public String query2(@RequestBody Object obj) { log.debug("sentinel 请求2req:{}", JSON.toJSONString(obj)); // 业务逻辑 Integer num = 1 / 0; log.info("hello world"); return "success"; } @Slf4j public class StockFeignClientBlockHandler { /** * 降级方法: * 1. 一定是public方法; * 2. 如果和原方法不在同一个类,用static修饰; * 3. 入参返参和原方法保持一致,且加BlockException入参 */ public static String query(Object req, BlockException blockException) { log.warn("请求库存StockFeignClient.query,接口熔断,req={}", JSON.toJSONString(req)); return StringUtils.EMPTY; } } @Slf4j public class StockFeignClientFallBackHandler { /** * 业务异常处理方法: * 1. 一定是public方法; * 2. 如果和原方法不在同一个类,用static修饰; * 3. 入参返参和原方法保持一致,且可以额外多一个Throwable类型的参数用于接收对应的异常。 */ public static String query(Object req, Throwable throwable) { log.warn("请求库存StockFeignClient.query,业务异常,req={}", JSON.toJSONString(req), throwable); return StringUtils.EMPTY; } } - 跑一把,nice:
5. Sentinel 规则的分类
Sentinel 的所有规则都可以在内存态中动态地查询及修改,修改之后立即生效。
Sentinel支持以下几种规则:流量控制规则/熔断降级规则、系统保护规则、来源访问控制规则 和 热点参数规则。
5.1 流量控制规则
详见:流量控制规则
5.2 熔断降级规则
详见:熔断降级规则
Sentinel的熔断策略:对弱依赖应用才能配置熔断降级!!!
6. Sentinel 控制台
Sentinel 控制台是流量控制、熔断降级规则统一配置和管理的入口,它为用户提供了机器自发现、簇点链路自发现、监控、规则配置等功能。在 Sentinel 控制台上,我们可以配置规则并实时查看流量控制效果。
-
下载控制台jar包在本地启动:java -jar -Dserver.port=8848 sentinel-dashboard-1.8.0.jar
-
访问控制台:http://localhost:8848/#/login 默认用户名密码:sentinel/sentinel
Sentinel会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包,所以要确保客户端有访问量!
7. SpringCloud Alibaba整合Sentinel
-
引入依赖:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> -
添加配置,为应用配置sentinel控制台地址:
#sentinel的控制台地址 spring.cloud.sentinel.transport.dashboard=127.0.0.1:8848 #指定应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer spring.cloud.sentinel.transport.port=8846 -
启动应用,发起一次调用,nice:
7.1 微服务和Sentinel Dashboard通信原理
通信细节:Sentinel学习(六) —— 控制台和客户端通信原理
8. Setinel控制台规则配置详解
8.1 流控规则
流量控制(flow control),其原理是监控应用流量的QPS或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
-
阀值类型:
- QPS;
- 线程数(并发数控制用于保护业务线程池不被慢调用耗尽)
-
流控模式:
- 直接(默认);
- 关联(两个资源之间具有资源争抢或者依赖关系时);
- 链路(根据调用链路入口限流)。
-
流控效果:
- 快速失败(默认);
- Warm Up(避免激增流量打垮系统);
- 排队等待(脉冲流量:用于处理间隔性突发的流量)。
8.2 熔断降级规则
我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
- 熔断策略:
经过熔断时长后熔断器会进入探测恢复状态(HALF OPEN状态),若接下来的一个请求响应时间小于设置的慢调用 RT则结束熔断,若大于设置的慢调用RT则会再次被熔断。(异常比例和异常数同理!!)- 慢调用比例;
- 异常比例;
- 异常数。
- 慢调用比例;
9. 热点参数流控
9.1 使用场景
9.2 配置方式
注意:资源名必须是@SentinelResource(value="资源名")中配置的资源名,热点规则依赖于注解!
参数为基本数据类型 OR 对象 的不同写法:
// 入参实现ParamFlowArgument对象写法
@Data
public class HotParamFlow implements ParamFlowArgument {
/**
* 用户ID
*/
private Long userId;
/**
* 场景
*/
private String scene;
/**
* 其它参数
*/
private Object obj;
@Override
public Object paramFlowKey() {
return this.scene;
}
}
根据场景限流的思考:
两种限流思路:
1. 普通限流:entry = SphU.entry(scene); // scene是一个入参,String类型
此时资源个数是scene的枚举数量,可以在控制台根据不同的scene值设置不同的限流值,场景不配置不限流。
2. 热点参数限流:@SentinelResource(value = "scene.Resource") // 注解下方法入参有scene
此时资源个数是1个,配置的限流阀值对所有的scene值都生效,可以配置例外项。
扩展:
- sentinel滑动窗口流量统计(Sentinel使用Bucket统计一个窗口时间内的各项指标数据,这些指标数据包括请求总数、成功总数、异常总数、总耗时、最小耗时、最大耗时等。)
- Sentinel限流实现原理(SphU.entry(resource)实现限流的链路分析)
- 漏桶算法和令牌桶算法,区别到底在哪里?