熔断策略
简述
熔断:在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整 体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。
降级:为服务提供一个托底方案,一旦服务无法正常调用,就使用托底方案
(1)慢调用比例 (SLOW_REQUEST_RATIO)
选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
(2)异常比例 (ERROR_RATIO)
当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态,若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
(3)异常数 (ERROR_COUNT)
当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
熔断器有3种状态:关闭、开启、半开启,其判断方式如下图
框架选型
1)自定义实现滑动窗口算法
优点:算法自主实现,技术可控性和扩展性都更强
缺点:实现难度极大,需要支持的熔断策略多且复杂,同时对于集群的支持也相对困难
2)对接已有成熟框架,例如Hystrix 、Rate Limiter、Sentinel等
优点:开箱即用,对接成本低,支持的场景丰富
缺点:拓展性差,且对于框架是否提供的某项功能高度依赖,如框架不提供则很难去扩展支持
综合对比最终选择Sentinel作为熔断降级的框架,同时也可以为以后支持更丰富的限流策略提供支持
基本概念介绍
资源
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
熔断规则参数
具体实现
1)哪些请求可以进行熔断策略监控?
非服务分组的api测试/正式调用,且需要关联了熔断策略
2)如果对资源进行定义以及代码实现?
String resName = SentinelRuleManager.getResourceName(apiConfig.getId(), Boolean.TRUE.equals(isTest));
Entry entry = null;
try {
entry = SphU.entry(resName);
// Your logic here.
return chain.filter(exchange);
} catch (BlockException be) {
// Handle request rejection.
return handleBlockedRequest(exchange, apiConfig);
} catch (Throwable t) {
Tracer.trace(t);
throw new ServiceException(t.getMessage(), t);
} finally {
if (entry != null) {
entry.exit(1, resName);
}
}
- 通过apiId加上是否测试调用的标志进行组合,生成一个唯一的资源名称,sentinel的监控维度是资源+规则。
3)动态规则设计
- 项目启动时会从数据库读取历史规则和api的绑定数据加载进sentinel
- 后续规则的修改都会通过DegradeRuleManager.setRulesForResource()操作
4)集群问题
由于熔断规则无法直接使用集群配置实现集群总体模式,所以技术实现上采用主节点模式,通过主节点进行规则加载和阈值判断,集群所有示例都正常执行api请求。
- 集群总体模式:即限制整个集群内的某个资源的总体 qps 不超过此阈值。
主节点的选取通过gateway配置文件配置(由于是通过ip指定的,所以devops的容器部署模式需要进行验证是否可用):
# 指定集群中一台机器作为主节点,不得配置localhost或127.0.0.1
gateway.main.ip = 192.168.98.241
5)新增数据库表
-- 创建熔断规则表
CREATE TABLE `da_degrade_rule` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tenant_id` int(11) NOT NULL COMMENT '租户id',
`project_id` int(11) NOT NULL COMMENT '项目id',
`degrade_type` int(2) NOT NULL COMMENT '熔断策略类型 0:慢调用比例 1:异常比例 2:异常数',
`name` varchar(255) NOT NULL COMMENT '策略名称',
`count` decimal(10,2) DEFAULT NULL COMMENT '慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值',
`time_window` int(11) DEFAULT NULL COMMENT '熔断时长,单位为 s',
`min_request` int(11) DEFAULT NULL COMMENT '熔断触发的最小请求数',
`stat_interval` int(11) DEFAULT NULL COMMENT '统计时长(单位为 s)',
`slow_ratio_threshold` decimal(10,2) DEFAULT NULL COMMENT '慢调用比例阈值,仅慢调用比例模式有效',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`create_user_id` int(11) NOT NULL COMMENT '创建用户id',
`modify_user_id` int(11) NOT NULL COMMENT '更新用户id',
`is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0正常 1逻辑删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='熔断降级策略';
-- 增加降级内容字段
ALTER TABLE `da_api` ADD COLUMN `degradation` text COMMENT '降级后返回内容';
ALTER TABLE `da_api_test` ADD COLUMN `degradation` text COMMENT '降级后返回内容';
ALTER TABLE `da_api_version_record` ADD COLUMN `degradation` text COMMENT '降级后返回内容';
-- 增加熔断降级关联字段
ALTER TABLE `da_api` ADD COLUMN `degrade_rule_id` int(11) DEFAULT NULL COMMENT '熔断降级id';
ALTER TABLE `da_api_test` ADD COLUMN `degrade_rule_id` int(11) DEFAULT NULL COMMENT '熔断降级id';
6)熔断器的开启状态获取
通过注册器回调方法实现,当熔断器状态变更则会回调进行通知,通知结果存储到redis中,每个资源有一个熔断器状态,默认为close,这部分信息会添加到api调用的返回结果中。
当用户配置了自定义降级内容后,熔断器的相关信息不会添加到返回结果中。
// 注册回调方法
EventObserverRegistry.getInstance().addStateChangeObserver("degrade-register",
(prevState, newState, rule, snapshotValue) -> {
//enum: CircuitBreaker.State.OPEN
String cacheKey = String.format(RedisKey.API_DEGRADE_RES_STATE, rule.getResource());
redisService.setExpiredAfter10Min(cacheKey, newState.name());
});
返回结果示例:
{
"data": [],
"success": true,
"affectedRow": null,
"errorCode": 1,
"errorInfo": "成功",
"header": null,
"requestSQL": null,
"degradeRule": "myDegrade1",
"degradeState": "open",
"page": {
"totalPage": 0,
"pageSize": 20,
"totalCount": 0,
"currentPage": 1
},
"space": 5503,
"jdbcQuerySpace": 355,
"timestamp": null
}
7)降级
当熔断器抛出BlockException并且该api设置了降级内容时,将返回结果json完全重写成用户设定的返回内容并进行返回。
用户保存api时设置降级内容必须为json格式,会进行校验。
8)熔断过滤器的执行顺序
由于熔断规则的设定依赖于apiId、isTest、isGroup等参数,这些参数是通过解析url获取到的,所以熔断过滤器的执行顺序会晚于解析url的动作,如果解析url报错则不会触发熔断器的监听,这部分内容需要注意。
目前过滤器的执行顺序如下:
通过源码分析熔断规则
Sentinel通过不同的slot去判断不同的限流规则能否通过,且采用责任链模式,依次判断。我们这里主要是在DegradeSlot中进行熔断规则的判断
通过策略名称找到具体的CircuitBreaker,这里CircuitBreaker实现类主要为ExceptionCircuitBreaker(异常数和异常比例)、ResponseTimeCircuitBreaker(慢调用比例)。
void performChecking(Context context, ResourceWrapper r) throws BlockException {
List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());
if (circuitBreakers == null || circuitBreakers.isEmpty()) {
return;
}
for (CircuitBreaker cb : circuitBreakers) {
if (!cb.tryPass(context)) {
throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule());
}
}
}
接下来跟进cb.tryPass()可以看到主要通过熔断器状态来判断是否可以请求通过,那这里主要看下如何变更状态。抽象类AbstractCircuitBreaker里主要是进行状态变更,变更条件还是在具体的实现类里。
创建流程
创建熔断策略包括策略名称、策略类型、创建人、修改时间及操作字段列表支持按策略名称进行全模糊搜索;
慢调用比例
- 策略名称:,可编辑策略的名称
- 策略类型:慢调用比例,异常比例,异常数
- 单位统计时长(必填):指定了判断是否要开启熔断的单位监测时长,数值文本框,单位秒,可填(0,99999999]之内的整数和小数;
- 响应时长阈值(必填):指定了定义为慢查询的响应时长,响应时间超过此阈值的所有查询为慢查询,数值文本框,单位毫秒,可填(0,99999999]之内的整数和小数;
- 慢调用占比阈值(必填):指定了慢调用占比阈值百分比超过多少时开启熔断,计算方式为单位统计时长内“慢调用请求数/总请求数”,数值文本框,单位%,可填(0,100]之内的整数和小数;
- 熔断时长(必填):指定了开启熔断后的持续时长,数值文本框,单位秒,可填(0,99999999]之内的整数和小数;
- 允许通过的最小请求数(必填):指定了单位统计时长内允许正常通过的最小请求数,此数量内不触发熔断,默认值为0,可填[0,99999999]之内的整数;
异常比例
- 策略名称(必填):同慢调用比例;
- 策略类型(必填):同慢调用比例;
- 单位统计时长(必填):同慢调用比例;
- 异常占比阈值(必填):指定了异常调用占比阈值超过多少时开启熔断,计算方式为单位统计时长内“发生异常的请求数/总请求数”,数值文本框,单位%,可填(0,100]之内的整数和小数;
- 熔断时长(必填):同慢调用比例;
- 允许通过的最小请求数(必填):同慢调用比例;
异常数
- 策略名称(必填):同慢调用比例;
- 策略类型(必填):同慢调用比例;
- 单位统计时长(必填):同慢调用比例;
- 异常占比阈值(必填):指定了异常调用占比阈值超过多少时开启熔断,计算方式为单位统计时长内“发生异常的请求数/总请求数”,数值文本框,单位%,可填(0,100]之内的整数和小数;
- 熔断时长(必填):同慢调用比例;
- 允许通过的最小请求数(必填):同慢调用比例;
创建API时,熔断策略关联所创建的熔断策略.关联后,当调用时,慢调用比例或异常数达到所设定的阈值,将会自动熔断.到达所设定的熔断时间后,自动恢复.注:熔断策略不可支持服务分组所创建的API.