一、Sentinel 控制台部署
# 启动控制台命令
java -jar sentinel-dashboard-1.8.1.jar
用户可通过如下参数进行配置:
-Dsentinel.dashboard.auth.username=sentinel 用于指定控制台的登录用户名为 sentinel
-Dsentinel.dashboard.auth.password=123456 用于指定控制台登录密码为 123456,如果省略这两个参数,默认用户名和密码都为 sentinel
-Dserver.servlet.session.timeout=7200 用于指定 Spring Boot 服务端 session 的过期时间,单位为秒,默认为 30 分钟
java -Dserver.port=8858 -Dsentinel.dashboard.auth.username=hudu -Dsentinel.dashboard.auth.password=123456 -jar sentinel-dashboard-1.8.1.jar
二、SpringBoot 整合 Sentinel
项目引入依赖
<!-- 整合控制台 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.1</version>
</dependency>
项目设置 sentinel 控制台的服务地址和端口
-Dcsp.sentinel.dashboard.server=192.168.33.62:8858
随便请求一下项目中的接口
三、Spring Cloud Alibaba 整合 Sentinel
3.1、引入依赖
<!--sentinel 启动器-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
application.yml 配置
server:
port: 8070
spring:
application:
name: order-sentinel
cloud:
sentinel:
transport:
dashboard: 192.168.33.62:8858
# 防止实时监控不显示
clientIp: 192.168.33.27
3.2、流控规则
流量控制(flow-control):其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时流量高峰击垮,从而保障应用的高可用性
应用场景
应对洪峰流量:秒杀,大促,下单,订单回流处理
消息型场景:削峰填谷,冷热启动
付费系统:根据使用流量付费
API Gateway:精准控制 API 流量
任何应用:探测应用中运行的慢程序块,进行限制
3.2.1、QPS 流控
给当前资源设置流控 QPS 为 2
当接口请求请求频繁,出现被流控情况
自定义流控处理
由于没有持久化,服务重启之后,需要重新设置流控规则
再次测试,当请求频繁之后,返回的值为自定义流控处理后返回的值
3.2.2、并发线程数流控
并发数控制用于保护业务线程池不被慢调用耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的overhead比较大,特别是对低延时的调用有比较大的影响。`Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置`。
接口代码如下
@RequestMapping("/flowThread")
@SentinelResource(value = "flowThread",blockHandler = "flowBlockHandler")
public String flowThread() throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
return "正常访问";
}
可以看到,当前一个请求还在处理中时,如果有下个请求进入,会直接处理为流控
三、BlockException 异常统一处理
springwebmvc 接口资源限流流入在 HandlerInterceptor 的实现类 AbstractSentinelInterceptor 的 preHandle 方法中,对异常的处理时 BlcokExceptionHandler 的实现累,sentinel 1.7.1 引入 sentinel-spring-webmvc-adapter.jar
自定义 BlockException 的实现类统一处理 BlockException
注释掉@SentinelResource
注解
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
// getRule() 资源 规则的详细信息
log.info("BlockExceptionHandler BlockException =====" + e.getRule());
Result r = null;
if (e instanceof FlowException) {
r = Result.error(100, "接口限流了");
} else if (e instanceof DegradeException) {
r = Result.error(101, "服务降级了");
} else if (e instanceof ParamFlowException) {
r = Result.error(100, "热点参数限流了");
} else if (e instanceof SystemBlockException) {
r = Result.error(100, "触发系统保护规则了");
} else if (e instanceof AuthorityException) {
r = Result.error(100, "授权规则不通过");
}
// 返回 json
httpServletResponse.setStatus(500);
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(httpServletResponse.getWriter(), r);
}
}
public class Result<T> {
private Integer code;
private String msg;
private T data;
public Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Result(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static Result error(Integer code,String msg) {
return new Result(code,msg);
}
}
四、流控模式
4.1、直接
之前的例子都是使用的直接模式,资源达到设置的阈值之后直接被流控抛出异常
4.2、关联
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db
和write_db
这两个资源分别代表数据库读写,我们可以给 read_db
设置限流规则来达到写优先的目的:设置strategy
为RuleConstant.STRATEGY_RELATE
同时设置refResource
为rite_db
。 这样当写库操作过于频繁时,读数据的请求会被限流。
准备两个接口,实现效果为,当生成订单访问量比较大时,对查询订单进行一个限流
@RequestMapping("/add")
public String add() {
System.out.println("下单成功!");
return "生成订单";
}
@RequestMapping("/get")
public String get() {
return "查询订单";
}
使用 JMeter 进行辅助测试
然后启动,JMeter会进行每秒三次的请求,我们请求查询接口发现,查询接口被限流了
4.3、链路
根据调用链路入口限制
下面中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一棵调用树。这棵树的根节点是一个名字为 getUser 的虚拟节点(方法),调用链的入口都是这个虚节点的子节点。一棵典型的调用树如下图所示:
实现效果为,当 test2 调用查过阈值,只有 test2 会被限流,test1 不受影响
项目结构如下
@Autowired
private IOrderService orderService;
@RequestMapping("/test1")
public String test1() {
return orderService.getUser();
}
@RequestMapping("/test2")
public String test2() {
return orderService.getUser();
}
@Service
public class OrderServiceImpl implements IOrderService {
@Override
@SentinelResource(value = "getUser")
public String getUser() {
return "查询用户";
}
}
默认情况下 sentinel 没有给我们维护调用链路树,默认将调用链路收敛,需要我们设置spring.cloud.sentinel.web-context-unify=false
可以发现现在生成的资源变了,随便选择一个进行链路流控规则设置。
一旦我们使用了@SentinelResource
注解,就不会应用统一异常处理
修改 service 代码
@Service
public class OrderServiceImpl implements IOrderService {
@Override
@SentinelResource(value = "getUser",blockHandler = "getUserBlockHandler")
public String getUser() {
return "查询用户";
}
public String getUserBlockHandler(BlockException e) {
return "流控用户";
}
}
五、流控效果
5.1、快速失败
之前的例子都是快速失败,当请求的量超过设定的阈值之后,新的请求直接被立即拒绝,拒绝方式为抛出FlowException
。这种方式使用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
5.2、Warm Up(预热流控)
主要针对激增流量处理
Warm Up (RuleConstant.CONTROL BEHAVIOR WARM_UP)方式,即预热冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过”冷启动”,
让通过的流量缓慢增加,在一定时间内逐渐增加到阈值 上限,给冷系统一个预热的时间, 避免冷系统被压垮。
冷加载因子: codeFactor 默认是3,即请求 QPS 从 threshold/3 开始,经预热时长逐渐升至设定的 QPS 阈值。
表示在五秒钟之内,10 个请求慢慢递增,根据上面所说的冷加载因子的算法,默认开始会进入 3 个请求,然后再根据阈值除以 3,然后慢慢递增,最终达到阈值设定的值。
可以实时监控就可以明显看到,开始 QPS 超过 3 会进行限流,然后慢慢的 QPS 上限越来越高,直到达到阈值
5.3、排队等待(匀速排队)
主要针对脉冲流量处理
匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。该方式的作用如下图所示:
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来, 而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
注意:匀速排队模式暂时不支持 QPS > 100 的场景
先查看直接失败的效果
添加定时器,每次执行完暂停 5 秒
接口请求设置
可以看到当遇到脉冲流量,会触发限流策略,然后中间会有大概五秒的停顿,排队等待就时利用空闲的时间,对脉冲流量进行一个缓冲,提高服务器的利用率
修改策略为排队等待
当有脉冲流量时,对超出阈值的请求,在五秒内进行一个排队处理。
再次请求,可以看到所有的请求抖执行成功了,并且中间还有空闲时段,有余量可以接受更多的请求
再次调整为 20 个线程,现在可以看到空闲时间就被利用了起来
六、熔断策略
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。 我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
熔断降级规则(DegradeRule)包含下面几个重要的属性:
Field 说明 默认值 resource 资源名,即规则的作用对象 grade 熔断策略,支持满调用比例/异常比例/异常数策略 慢调用比例 count 慢调用比例模式下为慢调用临界RT(超出该阈值为慢调用);异常比例/异常数模式下为对应的阈值 timeWindow 熔断时长,单位为 s minRequestAmount 熔断触发的最小请求数,请求数小于该阈值时即使异常比例超出阈值也不会熔断(1.7.0引入) 5 statIntervalMs 统计时长(单位 ms),如 60x1000 代表 60 分钟 (1.8.0 引入) 1000ms slowRatioThreshold 慢调用比例阈值,仅慢调用比例模式有效(1.8.0引入)
6.1、慢调用比例
慢调用比例(SLOW REQUEST RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用RT (即最大的响应时间), 请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用RT则结束熔断,若大于设置的慢调用RT则会再次被熔断。
准备如下接口
@RequestMapping("/flowThread")
public String flowThread() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
return "正常访问";
}
设置如下降级规则,当请求处理时间大于 1000 ms,请求就为慢调用,这里的最小请求数代表和比例阈值表示,在 1000 ms 内,有 8 次请求,并且 50% 是慢请求,也就是 4 次慢调用,接口就会进行熔断处理,经过 10 s 后恢复半开状态,如果恢复后的第一次调用还是慢调用,直接熔断。
使用 JMeter 进行测试,设定每秒请求 10 次
可以看到当我们再次请求时,服务被降级了。
6.2、异常比例
异常比例(ERROR_RATIO): 当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会
进入探测恢复状态(HALF-OPEN 状态),若接下来的一一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是[0.0, 1.0], 代表0%~100%。
准备接口
@RequestMapping("/err")
public String err() {
int i = 1 / 0;
return "hello";
}
当一秒内,前五个请求中,异常的接口超过 10 %,也就是只要有一个接口异常,接口会熔断
同样用 JMeter 进行辅助测试,当再次请求接口,发现接口被熔断了
6.3、异常数
一秒内,前五个请求只要有一个异常,就会熔断