Sentinel——熔断规则
熔断规则
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。[1]
Sentinel 提供以下几种熔断策略:
-
慢调用比例 (
SLOW_REQUEST_RATIO
):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN
状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。 -
异常比例 (
ERROR_RATIO
):当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN
状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是[0.0, 1.0]
,代表 0% - 100%。 -
异常数 (
ERROR_COUNT
):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN
状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
慢调用比例
设置说明:1s内的请求数量大于100,且有超过30%的响应时间大于2000ms,服务熔断10s后进入HALF-OPEN
状态,如果下一次请求的响应时间超过2000ms,服务会再次熔断。
发生熔断:
如果是设置的是方法签名的熔断,代表如果发送熔断,处理的方法为自定义的方法。如下面方法,执行getFallBack
方法。
/**
* 根据id查询部门
*/
@SentinelResource(fallback = "getFallBack")
@GetMapping("/get/{id}")
public Depart get(@PathVariable Long id) {
return restTemplate.getForObject(PROVIDER_URL + "/get/" + id, Depart.class);
}
/**
* 服务降级使用的方法
*/
public Depart getFallBack(Long id, Throwable t) {
log.info("id = " + id);
log.info("throwable = " + t.getMessage());
Depart depart = new Depart();
depart.setId(id);
depart.setName("no this depart");
return depart;
}
也可以给需要熔断的方法设置别名,如我们给get/{id}
,设置资源别名为value = "get"
。fallback一定要设置,不让会出现500空白页
。
/**
* 根据id查询部门
*/
@SentinelResource(value = "get", fallback = "getFallBack")
@GetMapping("/get/{id}")
public Depart get(@PathVariable Long id) {
return restTemplate.getForObject(PROVIDER_URL + "/get/" + id, Depart.class);
}
资源名为我们设置的别名。
需要注意的是,在控制台配置的熔断规则在服务重启后会丢失,但是我们可以在代码中进行设置,这样重启服务配置依然在。
慢比例调用代码实现
可以利用sentinel提供的api在代码中进行设置,或者自定义配置类在配置类中定义。
package com.zjw;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.ArrayList;
import java.util.List;
@SpringBootApplication
public class ConsumerSentinel8080Application {
public static void main(String[] args) {
SpringApplication.run(ConsumerSentinel8080Application.class, args);
//初始化熔断规则
initDegradeRule();
}
/**
* 初始化熔断规则
*/
private static void initDegradeRule() {
DegradeRuleManager.loadRules(configDegradeRule());
}
private static List<DegradeRule> configDegradeRule(){
List<DegradeRule> degradeRuleList = new ArrayList<>();
DegradeRule rule = new DegradeRule();
// sentinel资源名称,这里填入value值,@SentinelResource(value = "get", fallback = "getFallBack")
rule.setResource("get");
// 熔断策略,默认是慢调用比例,0: average RT, 异常比例 1: exception ratio, 异常数 2: exception count
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);//慢调用比例
// rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);//异常比例
// rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);//异常数
// 慢调用比例策略下为最大RT,响应时间,单位ms;异常比例策略下为比例阈值[0.0, 1.0];异常数策略下为异常数,int
rule.setCount(800);
// 比例阈值,默认为1.0,即100%。慢调用比例策略才需要设置
rule.setSlowRatioThreshold(0.5D);
// 熔断时长,单位s
rule.setTimeWindow(10);
// 最小请求数
rule.setMinRequestAmount(200);
// 统计时长,单位ms,默认为1000ms
rule.setStatIntervalMs(1000);
degradeRuleList.add(rule);
return degradeRuleList;
}
}
自定义异常处理器(返回响应流)
默认的降级响应在DefaultBlockExceptionHandler
定义,该类继承了BlockExceptionHandler
,如果我们自定义异常处理器,可以继承BlockExceptionHandler
,并且交给Spring管理。
自定义异常处理类
package com.zjw.handler;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import java.io.PrintWriter;
/**
* @since 2023/12/03 23:22
*/
@Component
public class CustomBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
// Return 429 (Too Many Requests) by default.
response.setStatus(429);
String msg = "";
if (e instanceof AuthorityException) {
msg = "Blocked by Sentinel (authority limiting)";
} else if (e instanceof DegradeException) {
msg = "Blocked by Sentinel (degrade limiting)";
} else if (e instanceof FlowException) {
msg = "Blocked by Sentinel (flow limiting)";
} else if (e instanceof ParamFlowException) {
msg = "Blocked by Sentinel (param flow limiting)";
} else if (e instanceof SystemBlockException) {
msg = "Blocked by Sentinel (system limiting)";
} else {
msg = "Blocked by Sentinel";
}
PrintWriter out = response.getWriter();
out.print(msg);
out.flush();
out.close();
}
}
BlockException有五个子类。
如果定义的fallback处理方法是不会走到自定义异常类的。否则会进入自定义异常类处理。
测试
添加熔断规则
controller方法
@GetMapping("/list")
public List<Depart> list() {
return (List<Depart>)restTemplate.getForObject(PROVIDER_URL + "/list", List.class);
}
多次请求返回结果
自定义异常处理器(返回页面)
异常处理器
package com.zjw.handler;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
/**
* 自定义异常处理器 返回页面
* @since 2023/12/03 23:22
*/
@Component
public class CustomBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
String page = "/default.html";
if (e instanceof AuthorityException) {
page = "/authorityException.html";
} else if (e instanceof DegradeException) {
page = "/degradeException.html";
} else if (e instanceof FlowException) {
page = "/flowException.html";
} else if (e instanceof ParamFlowException) {
page = "/paramFlowException.html";
} else if (e instanceof SystemBlockException) {
page = "/systemBlockException.html";
}
request.getRequestDispatcher(page).forward(request,response);
}
}
如果需要重定向,可以这样写:
/**
* 自定义异常处理器 重定向 适用前后端分离
* @since 2023/12/03 23:22
*/
@Component
public class CustomBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
String page = "https://www.taobao.com";
if (e instanceof AuthorityException) {
page = "https://www.baidu.com";
} else if (e instanceof DegradeException) {
page = "https://www.baidu.com";
} else if (e instanceof FlowException) {
page = "https://www.baidu.com";
} else if (e instanceof ParamFlowException) {
page = "https://www.baidu.com";
} else if (e instanceof SystemBlockException) {
page = "https://www.baidu.com";
}
response.sendRedirect(page);
}
}
定义页面
/resource/METE-INF/resource/degradeException.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>熔断</title>
</head>
<body>
系统发生熔断。
</body>
</html>
测试
测试方法同上。