项目的源码地址
Spring Cloud Alibaba 工程搭建(1)
Spring Cloud Alibaba 工程搭建连接数据库(2)
Spring Cloud Alibaba 集成 nacos 以及整合 Ribbon 与 Feign 实现负载调用(3)
Spring Cloud Alibaba Ribbon 负载调用说明(4)
Spring Cloud Alibaba 核心理论 CAP与BASE理论简单理解(5)
Spring Cloud Alibaba Sentinel 集成与限流实战(6)
Spring Cloud Alibaba 网关 Gateway 集成(7)
什么是Sentinel?
- 阿里巴巴开源的分布式系统流控工具
- 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性
- 丰富的应用场景:消息削峰填谷、集群流量控制、实时熔断下游不可用应用等
- 完备的实时监控:Sentinel 同时提供实时的监控功能
- 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合
- 官网:home | Sentinel (sentinelguard.io)
核心概念:
- 资源:是 Sentinel 中的核心概念之一,可以是java程序中任何内容,可以是服务或者方法甚至代码,总结起来就是我们要保护的东西
- 规则:定义怎样的方式保护资源,主要包括流控规则、熔断降级规则等
官方详细介绍 introduction | Sentinel (sentinelguard.io)
Sentinel 服务
- Sentinel 分为两个部分
- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo、Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
Sentinel 控制台搭建
官方文档:https://github.com/alibaba/Sentinel/wiki/控制台
控制台包含如下功能:
- 查看机器列表以及健康情况:收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线。
- 监控 (单机和集群聚合)通过 Sentinel 客户端暴露的监控 API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控。
- 规则管理和推送:统一管理推送规则。
- 鉴权:生产环境中鉴权非常重要。这里每个开发者需要根据自己的实际情况进行定制。
//启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本,
//-Dserver.port=8858 用于指定 Sentinel 控制台端口为 8858
//默认用户名和密码都是 sentinel
java -Dserver.port=8858 -Dcsp.sentinel.dashboard.server=localhost:8858 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar
注意:
- 如果是在 linux 系统上面,需要记得打开对应的端口防火墙
- 如果是在 windows 上面,就要注意使用管理员模式运行命令行(另外需要使用 jdk 11),我本地是使用 jdk 17 的时候,启动报错
登录的用户名和密码:默认用户名和密码都是 sentinel
程序集成 Sentinel
在 demo-order、demo-video 的 pom 文件中增加依赖
<!--添加sentinel客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
修改对应的配置文件
#dashboard: 8080 控制台端口
#port: 9999 本地启的端口,随机选个不能被占用的,与dashboard进行数据交互,会在应用对应的机器上启动一个 Http Server,
# 该 Server 会与 Sentinel 控制台做交互, 若被占用,则开始+1一次扫描
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8858
port: 9999
微服务注册上去后,由于Sentinel是懒加载模式,所以需要访问微服务后才会在控制台出现
我们访问下: localhost:9000/api/v1/video_order/findById?videoId=30
然后就可以看到 sentinel 的控制台上面增加了对应的模块信息了,我们多请求几次,可以看到 实时监控下面是有对应的数据收集
基于QPS限流配置实战
我们是初步了解下怎么设置,先找到对应的模块的资源,然后设置流控规则,从这里可以看到,我们可以对指定的接口去设置流控,这个设置的颗粒度还是比较细的
新增流控规则,这里我们为了方便于查看效果,就给一个单击阈值为:2
新增完了之后,可以在流控规则
这里找到
再次访问 localhost:9000/api/v1/video_order/findById?videoId=30,这次我们点击刷新的时候点击快一点:
Sentinel流量控制功能
有了初步的操作之后,我们来简单的了解下 sentinel 的流控功能,首先我们先了解下什么是流量控制(flow control)?
原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
面板参数
参数说明
- 针对来源:Sentinel 可以针对调用者进行限流,填写具体微服务名时,指定对此微服务进行限流 (就是可以指定是哪个来源的进行限流,不是这个来源的不限流),默认值为 default(不区分来源,全部限制)。
- 阈值类型/单机阈值:用于限制和控制流量的一种度量标准的类型,可以为 QPS(Queries Per Second,每秒请求数)也可以为“并发线程数”。
- QPS:每秒请求达到此值开始限流。
- 并发线程数:请求此资源的线程达到某个值时限流。每个请求分配一个线程,当请求执行时间长时,很快就会触发限流,相反如果线程执行速度快,那么限流触发就会概率就会比较小。
- 流控模式:流量控制模式。
- 直接:接口达到限流条件时,直接限流。
- 关联:当关联的资源达到阈值时,就限流自己。
- 链路:指定资源从入口资源进来的流量,如果达到阈值,就进行限流。
- 流控效果:流量控制效果。
- 快速失败:该方式是默认的流量控制方式,比如 QPS 超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出 FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
- 排队等待(也叫匀速通过):排队等待会严格控制请求通过的间隔时间,让请求稳定且匀速的通过,可以用来处理间隔性突发的高流量。例如抢票软件,在某一秒或者一分钟内有大量的请求到来,而接下来的一段时间里处于空闲状态,我们希望系统能够在接下来的空余时间里也能出去这些请求,而不是直接拒绝。在设置排队等待时,需要填写超时时间。
- Warm Up:此项叫做预热或者冷启动方式,此模式主要是防止流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮,通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。当使用 Warm Up 模式时,我们还需要指定启动时开放的 QPS 比例(DEFAULT_COLD_FACTOR,默认值为 3,代表 30%),以及系统预热所需时长(warmUpPeriodSec,默认值是 10 秒)。
限流页面当“是否集群”选中之后,就会是这样的界面:
其中最后一项“失败退化”中的 Token Server 含义如下:Token Server 是 Sentinel 用于集群流量控制的关键组件,它负责分发令牌并进行流量控制。当 Sentinel 的应用程序配置为集群限流模式时,它会向 Token Server 请求令牌,然后根据令牌情况来进行流量控制。 如果 Token Server 不可用,可能是由于网络故障、Token Server 实例崩溃等原因,这时候无法从 Token Server 获取令牌。Token Server 配置的含义如下:
- 当配置选项为"是"时:表示当 Token Server 不可用时,Sentinel 会自动切换为单机限流模式。在单机限流模式中,Sentine 会从本地的限流规则进行流量控制,不再依赖 Token Server。这样可以保证即使 Token Server 不可用,也能够继续对流量进行限制。
- 当配置选项为"否"时:表示当 Token Server 不可用时,Sentinel 不会自动切换为单机限流模式,流量控制会被暂停,即无法进行限流,可能会导致服务负载过高。
基于并发线程数限流
首先,在 OrderController
增加测试代码
@RequestMapping("list")
public Map list() throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
return Map.of("title", "互联网架构之JAVA虚拟机JVM零基础到高级实战", "price", 199.00, "createTime", new Date());
}
然后测试访问下:http://localhost:9000/api/v1/video_order/list
接着,配置对应的流控规则:
这里我们选择线程数,单机阈值为 1
重新请求 http://localhost:9000/api/v1/video_order/list,浏览器刷新两次
流控效果-Warm Up 与 排队等待
Warm Up:冷启动/预热,如果系统在此之前⻓期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最⼤值。比如说,我们设置下面的值:就表示经过10s 达到最大阈值 90
结合一个图再理解下
匀速排队
严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法,主要用于处理间隔性突发的流量,如消息队列,想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求
注意:
- 匀速排队等待策略是 Leaky Bucket 算法结合虚拟队列等待机制实现的。
- 匀速排队模式暂时不支持 QPS > 1000 的场景
官方文档: 流量控制 · alibaba/Sentinel Wiki (github.com)
熔断与降级
上面我们说了下流控相关的,这里我们开始讲讲面试经常问到的熔断与降级,熔断降级(虽然是两个概念,基本都是互相配合):
- 对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一
- 对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩
- 熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置、
什么是 Sentinel 降级规则 ? 官方文档,点击这里查看 ,总的来说:就是配置一定规则,然后满足之后就对服务进行熔断降级
熔断策略
慢调用比例(响应时间):
选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。
- 比例阈值:
- 熔断时长:超过时间后会尝试恢复
- 最小请求数:熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断
这里对上面的规则进行说明,熔断触发的最小请求数为 5:
- 请求数 < 5,即使异常比率超出阈值也不会熔断
- 请求数 >= 5,就是表示只要有一个请求(比例阈值设置为 0.1 体现)的最大相应时间为 100毫秒(最大 RT 设置100体现),就熔断10s(熔断时长体现)
异常比例
当单位统计时长内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断
- 比例阈值
- 熔断时长:超过时间后会尝试恢复。
- 最小请求数:熔断触发的最小请求数,请求数小于该值时,即使异常比率超出阈值也不会熔断。
异常数
当单位统计时长内的异常数目超过阈值之后会自动进行熔断
- 异常数:
- 熔断时长:超过时间后会尝试恢复
- 最小请求数:熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断
熔断的状态
服务熔断一般有三种状态
- 熔断关闭(Closed):服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制
- 熔断开启(Open):后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法
- 半熔断(Half-Open):所谓半熔断就是尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率
熔断的恢复:
- 经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态)尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。
- 如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断状态
熔断降级的测试
第一步:增加测试源码
这里我们先修改下源码 OrderController
,将 list 接口修改下:
int temp = 0;
/**
* 用于测试熔断、降级
*
* @return map
*/
@RequestMapping("list")
private Map list(HttpServletRequest httpRequest) {
temp++;
if (temp % 3 == 0) {
throw new RuntimeException("服务异常");
}
String serverInfo = httpRequest.getServerName() + ":"+ httpRequest.getServerPort();
return Map.of("title", "测试返回数据", "name", "返回名称", "serverInfo", serverInfo);
}
代码比较简单,这样子就可以模拟出报错的情况,这里我就不贴报错的图片了
第二步:增加降级规则
这里我们为了测试方便,就选择异常比例的策略,阈值就选择0.1,点击新增,然后我们开始测试
第三步:测试
访问:http://localhost:9000/api/v1/video_order/list ,然后快速刷新,就可以看到发生报错
报错之后,继续刷新的话还是报错,因为我们设置的是 10s 左右,我们等一会,再来请求:
自定义流控异常
从上面的图,我们可以知道发生流控报错之后,就在页面上面显示了一个文字信息,但是我们实际生产中,更多的是使用 JSON 格式来传输数据,所以我们需要改造下。
具体的实现方式:
// 需要实现 BlockExceptionHandler 接口,然后在 handle 中写对应的业务逻辑
public class MyUrlBlockHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
//降级业务处理
}
}
sentinel 异常的种类
- FlowException 限流异常
- DegradeException 降级异常
- ParamFlowException 参数限流异常
- SystemBlockException 系统负载异常
- AuthorityException 授权异常
增加对应的源码
@Component
public class MyUrlBlockHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
Map<String,Object> backMap=new HashMap<>();
if (e instanceof FlowException){
backMap.put("code",-1);
backMap.put("msg","限流-异常啦");
}else if (e instanceof DegradeException){
backMap.put("code",-2);
backMap.put("msg","降级-异常啦");
}else if (e instanceof ParamFlowException){
backMap.put("code",-3);
backMap.put("msg","热点-异常啦");
}else if (e instanceof SystemBlockException){
backMap.put("code",-4);
backMap.put("msg","系统规则-异常啦");
}else if (e instanceof AuthorityException){
backMap.put("code",-5);
backMap.put("msg","认证-异常啦");
}
// 设置返回json数据
httpServletResponse.setStatus(200);
httpServletResponse.setHeader("content-Type","application/json;charset=UTF-8");
httpServletResponse.getWriter().write(JSON.toJSONString(backMap));
}
}
这里需要重新配置下限流或者降级的规则,不然是看不到效果的,这里我就随便配置一个降级策略,
多刷新几次就可以看到对应的效果了
Sentinel整合OpenFeign配置
我们这里调用是通过 order 服务
调用 video 服务
如果是说,在我们调用 video 服务的时候报错,或者说 video 服务现在不可用,我们让程序返回固定默认的数据,而不是直接报错返回。
整合的步骤
加入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
开启Feign对Sentinel的支持
feign:
sentinel:
enabled: true
创建容错类,实现对应的服务接口,记得加注解 @Service
@Service
public class VideoServiceFallback implements VideoService {
@Override
public Video findById(int videoId) {
Video video = new Video();
video.setTitle("熔断降级数据");
return video;
}
@Override
public Video saveVideo(Video video) {
return null;
}
}
配置feign容错类,准备兜底数据
@FeignClient(value = "xdclass-video-service", fallback = VideoServiceFallback.class)
这个时候,我们只启动一个服务,就是 Order 服务,然后访问下: http://localhost:9000/api/v1/video_order/findById?videoId=30,好了现在就可以看到效果了
到此,我们就搞完了 sentinel 的基础入门了,我暂时也只学到了这些。 狗头.png,其实整个搭建都不算特别难,因为我们没有说去读源码那些(面试太卷了点),如果是学习使用的,这些应该可以应对很多场景了。