目录
一、前言
二、环境准备
2.1 部署sentinel管控台
2.1.1 官网下载sentinel的jar包
2.1.2 启动控制台
2.1.3 访问控制台
2.2 整合springcloud-alibaba
2.2.1 引入相关依赖
2.2.2 修改配置文件
2.2.3 增加一个测试接口
2.2.4 接口测试
三、sentinel 流控规则使用
3.1 实时监控
3.2 簇点链路
3.3 流控规则概述
3.3.1 流量控制原理
3.3.2 流量控制常用场景
3.3.3 流控规则的详细参数
3.4 常用流控规则
3.4.1 QPS限流
3.4.2 自定义限流返回结果
3.4.3 并发线程数限流
3.4.4 自定义异常
3.5 流控模式
3.5.1 流控模式概述
3.5.2 流控模式 —— 直接
3.5.3 流控模式 —— 关联
3.5.4 流控模式 —— 链路
3.6 流控效果
3.6.1 快速失败
3.6.2 Warm Up
3.6.3 排队等待
四、sentinel 降级规则
4.1 降级规则概述
4.1.1 为什么需要降级
4.1.2 降级与隔离
4.1.3 熔断后的处理逻辑
4.2 熔断策略
4.2.1 慢调用比例
4.2.2 异常比例
五、整合openfeign
5.1 服务提供方
5.2 服务消费方
5.2.1 添加pom依赖
5.2.2 添加配置文件
5.2.3 添加feign接口
5.2.4 提供降级的类
5.2.5 添加测试接口
5.2.6 接口测试与验证
六、热点参数限流
6.1 何为热点参数
6.1.1 使用场景
6.2 热点参数使用
6.2.1 新增一个测试接口
6.2.2 dashboard 如下配置
6.2.3 测试验证
6.3 系统规则使用
6.3.1 系统规则来源
6.3.2 sentinel系统规则概述
6.3.3 sentinel控制台配置
七、Sentinel配置规则持久化
7.1 三种规则持久化模式
7.1.1 原始模式
7.1.2 pull模式
7.1.3 push模式
7.2 整合nacos规则持久化
7.2.1 引入依赖
7.2.2 配置文件
7.2.3 naocos新增一个配置文件
7.2.4 请求接口
7.2.5 重启服务
八、写在文末
从前面的学习了解到sentinel是springcloud alibaba微服务生态下的一个重要的流量治理组件,在微服务架构中占据着非常重要的地位,本篇将详细介绍下springcloud alibaba中如何整合sentinel,以及结合sentinel控制台聊聊其深入的使用。
git下载地址下载地址:
注意版本的匹配
由于是一个springboot的服务jar包,直接使用java -jar命令即可启动
启动成功后,默认端口是8080,浏览器直接:localhost:8080,默认登录账户名和密码为:sentinel /sentinel,登录之后看到下面界面,首次进入为空页面;
sentinel与springcloud-alibaba整合比较简单,按照下面的几步操作即可。
导入以下基础依赖即可
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
true
com.alibaba.cloud
spring-cloud-alibaba-sentinel
这里暂时没有对接其他的外部依赖,只需配置与sentinel的控制台的连接信息
server:
port: 8032
spring:
application:
name: sentinel-order
#注册中心使用nacos
cloud:
sentinel:
transport:
dashboard: localhost:8080
这里添加一个测试接口,用于接下来的各种流控规则的测试
@RestController
@RequestMapping("/order")
public class OrderController {
@GetMapping("/add")
public String add(){
System.out.println("准备下单");
return "hello " ;
}
}
启动工程后,浏览器反复调用几次接口:localhost:8032/order/add,此后在sentinel的控制台就能看到如下相关的监控信息了;
通过上面的准备,在sentinel控制台左侧呈现出了详细的规则配置菜单,接下来,结合代码分别聊聊各种配置的详细使用。
监控接口的通过的QPS和拒绝的QPS,比如我们反复刷以下add接口,就能在监控中左侧看到该接口一段时间内的调用频率趋势图,右侧的表格则更直观的记录了在具体的时间点内QPS的通过和拒绝情况;
显示微服务的所有被监控的API资源信息,比如上面的add接口在图中就呈现了出来,同时这个页面也是后续设置API接口流控规则的主要入口。
还记得在上一篇中,我们通过使用sentinel的SDK演示了各种流控规则的使用,有了sentinel的控制台之后,就变得更简单了,接下来就来详细说明下如何使用控制台的各种流控规则的配置。
其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
同一个资源可以设置多条限流规则,FlowSlot 会对该资源的所有限流规则依次遍历,直到有规则触发限流或者所有规则遍历完毕,一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果;
Field
|
说明
|
默认值
|
resource
|
资源名,资源名是限流规则的作用对象
|
|
count
|
限流阈值
|
|
grade
|
限流阈值类型,QPS 模式(1)或并发线程数模式(0)
|
QPS 模式
|
limitApp
|
流控针对的调用来源
|
default ,代表不区分调用来源
|
strategy
|
调用关系限流策略:直接、链路、关联
|
根据资源本身(直接)
|
controlBehavior
|
流控效果(直接拒绝/WarmUp/匀速+排队等
待),不支持按调用关系限流
|
直接拒绝
|
clusterMode
|
是否集群限流
|
否 |
流控规则详细说明:git地址
QPS(Query Per Second)
每秒请求数,就是说服务器在一秒的时间内处理了多少个请求。
给order接口设置一个流控规则,现在每秒的请求数量上限为2;
设置完成后,界面再次快速访问时如果超过了QPS设置的阈值,将会看到下面的效果,说明配置规则生效了;
上面的规则生效后,快速刷接口时返回的提示信息是sentinel默认的一串值,如果要自定义返回信息,只需要通过@SentinelResource注解进行自定义返回值即可,如下:
@GetMapping("/add")
@SentinelResource(value = "add",blockHandler = "addBlockHandler")
public String add(){
System.out.println("准备下单");
return "hello " ;
}
public String addBlockHandler(BlockException b){
return "add 接口被流控了";
}
然后重新启动工程,再次快速刷接口时将会看到下面的效果;
并发线程数控制用于保护业务线程池不被慢调用耗尽。
例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。
Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。
新增一个接口
为了模拟出效果,给接口添加一个休眠时间,表示当前接口执行速度较慢,这样的话,当设置并发线程数时,如果第一个请求没有处理完,下一个请求过来时只能排队等待;
@GetMapping("/flow/thread")
@SentinelResource(value = "flowThread",blockHandler = "flowThreadBlockHandler")
public String flowThread(){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("准备flowThread");
return "flowThread " ;
}
public String flowThreadBlockHandler(BlockException b){
return "flowThread 接口被流控了";
}
界面上为该接口配置如下流控规则,这里设置为1
保存之后,第一次请求该接口,此时,再开一个窗口再次请求该接口,将会看到如下的效果;
为了统一处理流控接口的异常,可以通过自定义全局异常类,然后在需要使用的地方引用即可
public class MyBlockException {
public static Map handleException(BlockException e){
Map resultMap = new HashMap<>();
if(e instanceof FlowException){
resultMap.put("code",100);
resultMap.put("msg","被限流了:" + e.getMessage());
} else if (e instanceof DegradeException) {
resultMap.put("code",101);
resultMap.put("msg","服务被降级了:" + e.getMessage());
} else if (e instanceof ParamFlowException) {
resultMap.put("code",102);
resultMap.put("msg","热点参数限流:" + e.getMessage());
} else if (e instanceof SystemBlockException) {
resultMap.put("code",103);
resultMap.put("msg","触发系统保护规则:" + e.getMessage());
} else if (e instanceof AuthorityException){
resultMap.put("code",104);
resultMap.put("msg","授权规则不通过:" + e.getMessage());
}
return resultMap;
}
}
再在需要限流的接口上通过 @SentinelResource 引用即可
@GetMapping("/add")
//@SentinelResource(value = "add",blockHandler = "addBlockHandler")
@SentinelResource(value = "add",
blockHandlerClass = MyBlockException.class,
blockHandler = "handleException")
public Map add() {
System.out.println("准备下单");
Map resultMap = new HashMap<>();
resultMap.put("code", 200);
resultMap.put("msg", "下单成功");
return resultMap;
}
通过sentinel控制台配置一下限流规则后,再次测试以下add接口
在流控规则下面点开高级选项,可以看到流控模式这一选项,相当于是限流规则的补充;
1)基于调用关系的流量控制,调用关系包括调用方、被调用方;
2)一个方法可能会调用其它方法,形成一个调用链路的层次 关系;
从控制台可以看到提供了3钟方式,分别为:直接,关联和链路。
资源调用达到设置的阈值后直接被流控抛出异常
比如上文中设置的QPS限流,默认情况下即为直接的效果
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联,设置了关联之后,当一个资源被限流后,这个关联的资源也将会受到影响
比如有下面两个接口,一个生成订单的接口,一个查询订单的接口,假设生成订单接口会影响到查询订单,即它们两个之间存在关联;
@GetMapping("/create")
public String createOrder(){
System.out.println("生成订单");
return "生成订单";
}
@GetMapping("/get")
public String getOrder(){
System.out.println("查询订单");
return "查询订单";
}
在sentinel控制台做如下设置
根据调用链路入口限流
比如当微服务集群中,某个微服务调用了一系列其他的微服务时,相互之间可能就构成了一棵调用树,其根节点就是起始调用的那个接口,比如:这棵树的根节点是一个名字为 getUser 的虚拟节点,调用链的入口都是这个虚节点的子节点
getUser
/ \
/ \
/ order / test1 / order / test2
上图中来自入口 /order/test1 和 /order/test2的请求都调用到了资源 getUser,Sentinel 允许只根据某个入口的统计信息对 资源限流。
新增两个接口
@Autowired
private UserService userService;
@GetMapping("/user1")
public String user1(){
return userService.getUser();
}
@GetMapping("/user2")
public String user2(){
return userService.getUser();
}
业务实现
@Service
public class UserService {
@SentinelResource(value = "getUser")
public String getUser() {
return "查询用户";
}
}
sentinel控制台设置链路,仅对来源为user2的接口进行限流
流控效果即资源被流控之后展示出来的效果,主要包括3种:
默认的流控方式,上面展示的大多数流控后的效果就是快速识别,抛出流控异常,这里不再演示。
当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
即预热/冷启动方式。
当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过Warm Up的方式,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
sentinel控制台如下设置
测试发现,当接口刷的频率达到一定值时,将会看到被限流的效果,但不是立马被限流,而是能明显看到经历了一个过程;
也可以通过实时监控效果进一步确认当前接口的访问频率
这种方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,底层实现对应的是漏桶算法。
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
在sentinel的控制台做如下配置
通过压测工具访问接口,一段时间内当请求的QPS超过5个时,界面上会出现短暂的等待效果;
通过实时监控效果,也可以看出请求时间段内的QPS情况
除了流控以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。
我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
服务的熔断、降级与隔离是保护服务自身的一种有效手段,通常在consumer端组合使用,主要实施手段有:
1)并发控制(信号量隔离);
2)基于慢调用比例熔断;
3)基于异常比例熔断;
当消费端调用的服务进行熔断后,通常可采用下面的处理逻辑:
1)提供fallback结果(服务降级);
2)返回错误result;
3)读取缓存(降低DB访问量);
Field
|
说明
|
默认值
|
resource
|
资源名,即规则的作用对象
|
|
grade
|
熔断策略,支持慢调用比例/异常比例/异常数策略
|
慢调用比例
|
count
|
慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值
|
|
timeWindow
|
熔断时长,单位为 s
|
|
minRequestAmount
|
熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入)
|
5 |
statIntervalMs
|
统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入)
|
1000 ms
|
slowRatioThreshold
|
慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)
|
选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。
当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALFOPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断
新增一个接口,在该接口中处理需要3秒
@GetMapping("/flow/thread")
public String flowThread() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("准备flowThread");
return "flowThread ";
}
在sentinel控制台做如下配置,简单说下几个关键参数含义:
保存完毕后,可以直接在浏览器进行模拟调用,当调用的次数和频率符合了规则时,将看到下面的效果;
同时可以从实时监控效果可以看到QPS的数据;
当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。
经过熔断时长后熔断器会进入探测恢复状态(HALFOPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% 100%。
新增一个带有异常的接口
@GetMapping("/err")
public String err() {
int a = 1/0;
return "err ";
}
sentinel做如下配置
浏览器访问接口,当满足规则中的条件时将会触发熔断,出现下面的效果;
在使用openfeign进行服务调用时,对于客户端来说,被调用的服务接口出现异常时,为了消费端的服务接口不被拖垮,通常来说也需要做服务的熔断降级处理,以提升自身的稳定性,此时就可以考虑接入sentinel,接下来看看如何在openfeign中使用sentinel。
准备两个工程,第一个工程作为服务提供方,使用之前的一个扣减库存的工程,其中有一个异常接口
@GetMapping("/reduct2")
public String reduct2() {
int a=1/0;
return "扣减库存 : reduct2";
}
工程结构如下
org.springframework.boot
spring-boot-starter-web
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.cloud
spring-cloud-starter-openfeign
com.alibaba.cloud
spring-cloud-alibaba-sentinel
server:
port: 8040
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: localhost:8848 #服务注册中心地址
#config:
#server-addr: localhost:8848 #配置中心地址
sentinel:
transport:
dashboard: localhost:8080
feign:
sentinel:
enabled: true
@FeignClient(name = "stock-service",path = "/stock",fallback = FallBackService.class)
public interface StockFeignService {
@GetMapping("/reduct")
public String reduct();
@GetMapping("/reduct2")
public String reduct2();
@GetMapping("/get")
public String get(@RequestParam("id") Integer id);
}
@Component
public class FallBackService implements StockFeignService{
@Override
public String reduct() {
return null;
}
@Override
public String reduct2() {
return "服务被降级了";
}
@Override
public String get(Integer id) {
return null;
}
}
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private StockFeignService stockFeignService;
//localhost:8083/order/add
@GetMapping("/add")
public String add(){
System.out.println("下单成功");
String reduct = stockFeignService.reduct2();
return "add order :" + reduct;
}
}
启动nacos服务,启动两个工程,浏览器调用:localhost:8040/order/add,看到下面的效果
热点参数也可以理解为热点接口,即在一个系统中,那些被高频调用的接口以及某参数对应的接口,比如根据商品ID获取商品详情这样的接口在一个电商系统中一定是高频被调用的接口。
很多时候我们希望统计某个热点数据中访问频次最高的数据,并对其访问进行限制,以免被该来源的IP或应用占用太多的服务资源拖慢了整个系统的效率。
比如:热点商品的访问(访问控制),识别热点参数接口之后对来源用户/app/IP进行限制,避免防刷;
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
注意点
1)热点规则需要使用@SentinelResource("resourceName")注解,否则不生效;
2)参数必须是7种基本数据类型才会生效
@GetMapping("/get/{id}")
@SentinelResource(value = "getById",blockHandler = "hotBlockHandler")
public String getById(@PathVariable("id") Integer id){
System.out.println("正常访问");
return "正常访问" ;
}
public String hotBlockHandler(@PathVariable("id") Integer id,BlockException e){
return "热点异常处理结果" ;
}
新增完成后,在热点规则列表就有了一个数据,继续编辑
请求时设置参数为1 ,每秒1次,可以正常响应结果
每秒超过1次时,触发热点规则降级
在真实的生产环境下,对一个复杂的系统来说,引起系统故障或者突然崩溃的因素有很多,比如:
希望有个全局的兜底防护,即使一开始缺少容量评估也有一定的保护机制。简单来说就是,结合系统的指标和服务容量,能够自适应动态调整流量。
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
Load 自适应
仅对 LinuxUnixlike 机器生效,是系统的 load1 作为启发指标,进行自适应系统保护。当系统load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5。
CPU usage
当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.01.0),比较灵敏。
平均 RT
当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒
并发线程数
当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护
入口QPS
当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护
在控制台上,提供了多种系统保护规则,这里以其中的某一种为例进行说明,比如下面设置“入口QPS”,这里设置为1
删除之前配置的其他接口配置规则,快速刷新接口时,可以看到下面的效果,说明这种全局配置规则生效了。
在上面的操作中可以发现一个比较大的问题就是,一旦当工程重启,或者sentinel服务重启之后,之前界面上配置的规则就丢失了,这个并不奇怪,因为默认情况下,这些规则是动态的存储在内存中的,这在线上环境是肯定不允许的,因此为了安全起见,需要将sentine的配置规则进行持久化存储。
关于sentinel的规则持久化,官方主要提供了3种模式,分别做一一概述
API 将规则推送至客户端并直接更新到内存中,扩展写数据源(WritableDataSource),其优点就是简单无需任何依赖,缺点也很明显,重启就丢失。
扩展写数据源(WritableDataSource),客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件等,其优点就是简单无需任何依赖,缺点是不能保证一致性,实时性得不到保障,拉取过于频繁时可能存在性能问题。
pull 模式的数据源(如本地文件、RDBMS 等)一般是可写入的。使用时需要在客户端注册数据源:将对应的读数据源注册至对应的 RuleManager,将写数据源注册至 transport的WritableDataSourceRegistry 中。
扩展读数据源(ReadableDataSource),规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。生产环境下一般采用 push 模式的数据源,其优点是,规则持久化且能保持一致性,快捷,但是需要引入第三方依赖。
生产环境下一般更常用的是 push 模式的数据源。对于 push 模式的数据源,如远程配置中心(ZooKeeper, Nacos, Apollo等等),推送的操作不应由 Sentinel 客户端进行,而应该经控
制台统一进行管理,直接进行推送,数据源仅负责获取配置中心推送的配置并更新到本地。因此推送规则正确做法应该是 配置中心控制台/Sentinel 控制台 → 配置中心 → Sentinel 数据源 → Sentinel,而不是经 Sentinel 数据源推送至配置中心。这样的流程就非常清晰了:
com.alibaba.csp
sentinel-datasource-nacos
下面贴出完整的配置文件内容
server:
port: 8040
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: localhost:8848 #服务注册中心地址
#config:
#server-addr: localhost:8848 #配置中心地址
sentinel:
transport:
dashboard: localhost:8080
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: order-flow-rules
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
feign:
sentinel:
enabled: true
配置文件的名称即为yaml中的dataId对应的名称,选择json格式,内容如下:
[
{
"resource": "/testFlow",
"limitApp":"default",
"grade":1,
"count":1,
"strategy":0,
"controlBehavior":0,
"clusterMode":false
}
]
关于参数解释
resource:资源名称
limitApp:来源应用
grade:阀值类型,0:线程数,1:QPS
count:单机阀值
strategy:流控模式,0:直接,1:关联,2:链路
controlBehavior:流控效果,0:快速失败,1:warmUp,2:排队等待
clusterMode:是否集群
此时还未登录dashboard的情况下,快速刷接口时,可以看到如下效果,说明此时从nacos中读取到了配置规则
此时再看dashboard,可以看到这里针对该接口多了一个流控规则
重启服务,稍等一会之后,再次刷接口,再次进入dashboard进行查看流控规则,发现依然能够加载到之前从nacos中的流控规则,说明持久化规则生效了。
本文通过较大的篇幅,全方位讲述了sentinel的限流、熔断降级等规则的使用,以及如何整合springcloud进行跨服务访问使用,由于sentinel涉及到的点比较多,需要投入较多的时间和精力去研究,这对于后续在生产环境中合理使用sentinel的功能时也是一个很好的储备。