初识Sentinel
雪崩问题及解决方案
常见方式:
- 超时处理:设置超时时间,超时返回错误,不会无休止等待
- 舱壁模式:规定每个业务线程数,避免耗尽tomcat资源,也叫线程隔离
- 熔断降级:断路器统计业务异常比例,超出阈值则熔断该业务
- 流量控制:限制业务访问QPS,避免因流量突增而故障
服务保护技术对比
Sentinel介绍和安装
Sentinel官网
启动Sentinel
java -jar sentinel-dashboard-1.8.2.jar
访问
修改配置
java -jar sentinel-dashboard-1.8.2.jar -Dserver.port=8090
微服务整合Sentinel
引入cloud-demo
结构
先启动nacos,修改yaml中mysql配置
startup.cmd -m standalone
- 引入sentinel依赖
- 配置控制台地址
- 访问微服务任意端点,触发sentinel监控
限流规则
簇点链路
默认sentinel监控SpringMVC的每一个端点(Controller中方法),因此SpringMVC的每个端点(Endpoint)就是调用链路中的一个资源。
Jmeter快速入门.md
启动Jmeter
运行Jmeter.bat
快速入门
流控模式
- 直接:统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式
- 关联:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流
链路:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流
直接
关联
使用场景:用户支付修改订单状态,和用户查询订单操作争抢数据库锁。有限支付修改订单业务,因此当修改业务触发阈值时,对查询业务限流。
Demo:新增两个端点:/order/query和/order/update
- 配置流控规则,当/update的QPS>5时,对/query限流
结果:用sentinel访问/update,网页端请求/query时被限流
链路
例如有两条请求:
- /test1 -> /common
- /test2 -> /common
只统计/test2 到 /common 的请求,配置如下
使用场景:查询订单和创建订单都需要查询商品。只对查询订单进入到查询商品的请求做统计限流
Demo:
- 在OrderService添加queryGoods方法
- 在OrderController中,/order/query端点,调用queryGoods方法
- 在OrderController中,/order/save端点,调用queryGoods方法
- 设置queryGoods限流,从/order/query进入的QPS<2
因为queryGoods不是Controller中的方法,需要做注解并在yml中关闭context整合
@SentinelResource("goods")
public void queryGoods(){
System.err.println("查询商品");
}
sentinel:
transport:
dashboard: localhost:8080 # sentinel控制台地址
web-context-unify: false # 关闭context整合
为goods配置流控规则
在JMeter中做测试,同时向save和query发送请求,可以发现后者被限流
流控效果
指达到流控阈值时采取的措施,包括
warm up
预热模式,是服务冷启动方案。请求阈值初始threshold/coldFactor,持续指定时长后,逐渐提高到threshold值,coldFactor默认值3。(人话:一开始只发挥1/3功效)
demo
使用JMeter测试
排队等待
请求超过QPS阈值时,快速失败和warm up会拒绝并抛异常。排队等待则是让所有请求进入队列,按照阈值允许的时间间隔依次执行。等待时间超出最大时长被拒绝。
demo:给/order/{orderId}设置限流,QPS为10,利用排队流控效果,超时时长5s
结果:起到流量整形作用,大部分请求被通过
热点参数限流
访问/goods/{id}的请求中,id参数值会有变化,热点参数限流会根据参数值分别统计QPS,统计结果:
当id=1的请求触发阈值被限流时,id值不为1的请求不受影响。
demo:给/order/{orderId}添加热点参数限流
- 默认热点参数规则每1秒请求不超过2
- 102参数设置例外,每1秒不超过4
- 103参数设置例外,每1秒不超过10
注意:热点参数限流对默认SpringMVC资源无效,添加注解
@SentinelResource("hot")
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
// 根据id查询订单并返回
return orderService.queryOrderById(orderId);
}
配置
JMeter测试
结果:不同参数结果不同
隔离和降级
限流只是避免高并发引起服务故障,若出现其他原因的故障,则需要通过隔离和降级,将故障控制在一定范围,防止雪崩。
线程隔离和熔断降级,都是对客户端(调用方)的保护。
实现方式👇
Feign整合Sentinel
- 修改OrderService的yml文件,开启Feign的Sentinel功能
- 给FeignClient编写失败的降级逻辑
- 方式一:FallbackClass,无法处理远程调用的异常
- 方式二:FallbackFactory,可处理…🆗
步骤一:在feign-api中定义类,实现FallbackFactory
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable throwable) {
return new UserClient() {
@Override
public User findById(Long id) {
log.error("查询用户异常", throwable);
return new User();
}
};
}
}
步骤二:将UserClientFallbackFactory注册为Bean
@Bean
public UserClientFallbackFactory userClientFallbackFactory(){
return new UserClientFallbackFactory();
}
步骤三:在UserClient接口中使用UserClientFallbackFactory
@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
注册成功后查到
线程隔离
两种实现方式:
- 线程池隔离
- 信号量隔离(Sentinel默认)
demo:给UserClient查询用户接口设置流控规则,线程<2。
JMeter测试
熔断降级
断路器统计异常比例,超出阈值会熔断,拦截请求;服务恢复时,断路器恢复放行。
熔断策略-慢调用
慢调用:业务响应时长(RT)大于指定时长被认定为慢调用。指定时间,慢调用比例大于阈值,触发熔断
demo:UserClient的查询用户接口设置降级规则,慢调用RT阈值50ms,统计时间1s,最小请求数5,失败阈值0.4,熔断时长5
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) throws InterruptedException {
if (id == 1) {
// 休眠,触发熔断
Thread.sleep(60);
}
return userService.queryById(id);
}
熔断策略-异常比例、异常数
demo:给UserClient查询用户接口设置降级规则,统计时间1秒,最小请求数5,失败阈值比例0.4,熔断时长5s
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id,
@RequestHeader(value = "Truth", required = false) String truth) throws InterruptedException {
if (id == 1) {
// 休眠,触发熔断
Thread.sleep(60);
}
else if (id == 2) {
throw new RuntimeException("故意出错,触发熔断");
}
return userService.queryById(id);
}
授权规则及规则持久化
授权规则
授权规则
- 白名单/黑名单
- 资源名:受保护的资源
- 流控应用:来源者名单
获取origin:定义RequestOriginParser实现类
@Component
public class HeaderOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
// 1.获取请求头
String origin = request.getHeader("origin");
// 2.非空判断
if (StringUtils.isEmpty(origin)) {
origin = "blank";
}
return origin;
}
}
给网关添加请求头:修改gateway服务的yml
spring:
cloud:
gateway:
default-filters:
- AddRequestHeader=origin,gateway
routes:
# ...略
作用:从gateway路由的请求带上origin头,值为gateway
配置授权规则
作用:跳过网关直接访问order-service服务被拦截,通过网关访问不受影响
自定义异常结果
BlockExceptionHandler接口
public interface BlockExceptionHandler {
/**
* 处理请求被限流、降级、授权拦截时抛出的异常:BlockException
*/
void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception;
}
三个参数:
- HttpServletRequest request:request对象
- HttpServletResponse response:response对象
- BlockException e:被sentinel拦截时抛出的异常
BlockException包含的子类:
异常 | 说明 |
---|---|
FlowException | 限流异常 |
ParamFlowException | 热点参数限流的异常 |
DegradeException | 降级异常 |
AuthorityException | 授权规则异常 |
SystemBlockException | 系统规则异常 |
在order-service中自定义异常处理类
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
String msg = "未知异常";
int status = 429;
if (e instanceof FlowException) {
msg = "请求被限流了";
} else if (e instanceof ParamFlowException) {
msg = "请求被热点参数限流";
} else if (e instanceof DegradeException) {
msg = "请求被降级了";
} else if (e instanceof AuthorityException) {
msg = "没有权限访问";
status = 401;
}
response.setContentType("application/json;charset=utf-8");
response.setStatus(status);
response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}");
}
}
规则持久化
因为sentinel的规则都是存储在内存中,重启后规则会丢失,生产环境下需要做持久化。
规则能否持久化取决于管理模式,sentinel支持的三种模式:
- 原始模式:默认模式,会丢失
- pull模式
- push模式
pull模式
push模式
实现push模式
sentinel规则持久化.md