文章目录
1、为什么要引入Sentinel?
分布式系统遇到的问题
在一个高度服务化的系统中,我们实现的一个业务通常会依赖多个服务;
如果其中的下单服务不可用,就会出现线程池里所有线程都因等待响应而被阻塞,从而造成整个服务链路不可用,进而导致整个系统的服务雪崩,如图所示:
服务雪崩效应:因服务提供者的不可用导致服务调用者的不可用,并将不可用逐渐放大的过程,就叫做服务雪崩效应。
导致服务不可用的原因:
- 激增流量导致系统CUP / Load 飙高,无法正常处理请求
- 激增流量打垮冷系统(数据库连接未创建,缓存未预热)
- 消息投递速度过快,导致消息处理积压
- 慢SQL查询卡爆连接池
- 第三方服务不响应,卡满线程池
- 业务调用持续出现异常,产生大量的副作用
在服务提供者不可用的时候,会出现大量重试的情况:用户重试、代码逻辑重试,这些重试最终导致:进一步加大请求流量。所以归根结底导致雪崩效应的最根本原因是:大量请求线程同步等待造成的资源耗尽。当服务调用者使用同步调用时, 会产生大量的等待线程占用系统资源。一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态, 于是服务雪崩效应产生了。
解决方案
常见的容错机制:
-
超时机制
在不做任何处理的情况下,服务提供者不可用会导致消费者请求线程强制等待,而造成系统资源耗尽。加入超时机制,一旦超时,就释放资源。由于释放资源速度较快,一定程度上可以抑制资源耗尽的问题。 -
服务限流
-
隔离
原理:用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,则会进行降级处理,用户的请求不会被阻塞,至少可以看到一个执行结果(例如返回友好的提示信息),而不是无休止的等待或者看到系统崩溃。
-
服务熔断
远程服务不稳定或网络抖动时暂时关闭,就叫服务熔断。
现实世界的断路器大家肯定都很了解,断路器实时监控电路的情况,如果发现电路电流异常,就会跳闸,从而防止电路被烧毁。
软件世界的断路器可以这样理解:实时监测应用,如果发现在一定时间内失败次数/失败率达到一定阈值,就“跳闸”,断路器打开——此时,请求直接返回,而不去调用原本调用的逻辑。跳闸一段时间后(例如10秒),断路器会进入半开状态,这是一个瞬间态,此时允许一次请求调用该调的逻辑,如果成功,则断路器关闭,应用正常调用;如果调用依然不成功,断路器继续回到打开状态,过段时间再进入半开状态尝试——通过”跳闸“,应用可以保护自己,而且避免浪费资源;而通过半开的设计,可实现应用的“自我修复“。
所以,同样的道理,当依赖的服务有大量超时时,在让新的请求去访问根本没有意义,只会无畏的消耗现有资源。比如我们设置了超时时间为1s,如果短时间内有大量请求在1s内都得不到响应,就意味着这个服务出现了异常,此时就没有必要再让其他的请求去访问这个依赖了,这个时候就应该使用断路器避免资源浪费。 -
服务降级
有服务熔断,必然要有服务降级。
所谓降级,就是当某个服务熔断之后,服务将不再被调用,此时客户端可以自己准备一个本地的fallback(回退)回调,返回一个缺省值。 例如:(备用接口/缓存/mock数据) 。这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强,当然这也要看适合的业务场景。
2、Alibaba Sentinel
Sentinel是什么
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
源码地址:https://github.com/alibaba/Sentinel
官网文档:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
Sentinel 具有以下特征:
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
- 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
- 完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
Sentinel 的主要特性:
Sentinel 分为两个部分:
- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
Sentinel和Hystrix对比
Sentinel | Hystrix | |
---|---|---|
隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口(基于RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于QPS,支持基于调用关系的限流 | 有限的支持 |
流量整形 | 支持慢启动、匀速器形式 | 不支持 |
系统负载保护 | 支持 | 不支持 |
控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC等 | Servlet、Spring Cloud Netflix |
Sentinel快速开始
在官网文档中,定义的Sentinel 进行资源保护的几个步骤:
- 定义资源
- 定义规则
- 检查规则是否生效
Entry entry = null;
// 务必保证 finally 会被执行
try {
// 资源名可使用任意有业务语义的字符串,注意数目不能太多(超过 1K),超出几千请作为参数传入而不要直接作为资源名
// EntryType 代表流量类型(inbound/outbound),其中系统规则只对 IN 类型的埋点生效
entry = SphU.entry("自定义资源名");
// 被保护的业务逻辑
// do something...
} catch (BlockException ex) {
// 资源访问阻止,被限流或被降级
// 进行相应的处理操作
} catch (Exception ex) {
// 若需要配置降级规则,需要通过这种方式记录业务异常
Tracer.traceEntry(ex, entry);
} finally {
// 务必保证 exit,务必保证每个 entry 与 exit 配对
if (entry != null) {
entry.exit();
}
}
流控规则重要属性:
Field | 说明 | 默认 |
---|---|---|
resource | 资源名,资源名是限流规则的作用对象 | |
count | 限流阈值 | |
grade | 限流阈值类型,QPS模式(1)或并发线程数模式(0) | QPS模式 |
limitApp | 流控针对的调用来源 | default,代表不区分调用来源 |
strategy | 调用关系限流策略:直接、链路、关联 | 根据资源本身(直接) |
controlBehavior | 流控鲜果(直接拒接/WaramUp/匀速+排队等待),不支持按调用关系限流 | 直接拒绝 |
clusterMode | 是否集群限流 | 否 |
熔断降级规则重要属性:
Field | 说明 | 默认 |
---|---|---|
resource | 资源名,即规则的作用对象 | |
grade | 熔断策略,支持慢调用比例/异常比例/异常数策略 | 慢调用比例 |
count | 慢调用比例模式下为慢调用临界RT(超出该值为慢调用);异常比例/异常数模式下为对应的阈值 | |
timeWindow | 熔断时长,单位为s | |
minRequestAmount | 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0引入) | 5 |
statIntervalMs | 统计时长(单位为ms),如60*1000代表分钟级(1.8.0引入) | 1000ms |
slowRatioThreshold | 慢调用比例阈值,仅慢调用比例模式有效(1.8.0引入) |
Sentinel资源保护的方式
API实现
- 引入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.0</version>
</dependency>
- 编写测试逻辑
@RestController
@Slf4j
public class HelloController {
private static final String RESOURCE_NAME = "hello";
// 进行sentinel流控
@RequestMapping(value = "/hello")
public String hello() {
Entry entry = null;
try {
// sentinel针对资源进行限制的
entry = SphU.entry(RESOURCE_NAME);
// 被保护的业务逻辑
String str = "hello world";
log.info("======"+str+"======");
return str;
} catch (BlockException e) {
// 资源访问阻止,被限流或被降级
log.info("Block!");
return "被流控了!";
} catch (Exception ex){
// 若需要配置降级规则,需要通过这种方式的记录业务异常
Tracer.traceEntry(ex,entry);
} finally {
if (entry != null) {
entry.exit();
}
}
return null;
}
/**
* 定义流控规则
*/
@PostConstruct
private static void initFlowRules() {
// 流控规则
List<FlowRule> rules = new ArrayList<>();
// 流控
FlowRule rule = new FlowRule();
// 为那个资源进行流控
rule.setResource(RESOURCE_NAME);
// 设置流控规则 QPS
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置受保护的资源阈值
rule.setCount(1);
rules.add(rule);
// 加载配置好的规则
FlowRuleManager.loadRules(rules);
}
}
- 测试效果
缺点:
- 业务侵入性很强,需要在controller中写入非业务代码
- 配置不灵活 若需要添加新的受保护资源 需要手动添加 init方法来添加流控规则
@SentinelResource注解实现
@SentinelResource 注解用来标识资源是否被限流、降级。
- blockHandler:设置 流控降级后的处理方法(默认该方法必须声明在同一类)
- 如果不想在同一个类中,需要使用blockHandlerClass指定类,并且方法必须用static修饰
- fallback:当接口出现了异常,就可以交给 fallback 指定的方法进行处理
- 如果不想在同一个类中,需要使用 fallbackClass指定类,并且方法必须用static修饰
- blockHandler 和 fallback 如果同时制定了,则blockHandler优先级更高
- exceptionsToIgnore:排除那些异常不处理
源码入口:com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect
- 引入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.8.0</version>
</dependency>
- 配置切面支持
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
- 编写测试逻辑,添加
@SentinelResouce
,并配置blockhandler
和fallback
private static final String USER_RESOURCE_NAME = "user";
@RequestMapping("/user")
@SentinelResource(value = USER_RESOURCE_NAME,
fallbackClass = ExceptionUtil.class, fallback = "fallbackForGetUser",
blockHandlerClass = ExceptionUtil.class, blockHandler = "blockHandlerForGetUser")
public User getUser(String id) {
return new User("zhangsan");
}
- 编写ExceptionUtil,注意:如果指定了class,方法必须是static 方法
/**
* 注意:
* 1. 一定要public
* 2. 返回值一定要和源方法保持一致,包含源方法的参数
* 3. 可以在参数最后添加BlockException,可以区分是什么规则的处理方法
*/
public static User blockHandlerForGetUser(String id, BlockException ex) {
ex.printStackTrace();
return new User("流控!!!!");
}
public static User fallbackForGetUser(String id, BlockException ex) {
ex.printStackTrace();
return new User("被异常降级!!!!");
}
启动Sentinel控制台
下载控制台 jar 包并在本地启动:下载地址:https://github.com/alibaba/Sentinel/releases
以 sentinel‐dashboard‐1.8.0.jar
为例:
#启动控制台命令
java ‐jar sentinel‐dashboard‐1.8.0.jar
访问 http://localhost:8080/#/login
,默认用户名密码: sentinel/sentinel
登录后,默认控制台是空的。
为了方便快捷启动可以在桌面创建 .bat
文件
java ‐Dserver.port=8858 ‐Dsentinel.dashboard.auth.username=starsea ‐Dsentinel.dashboard.auth.password=123456 ‐jar D:\server\sentinel‐dashboard‐1.8.0.jar
pause
客户端需要引入 Transport 模块来与 Sentinel 控制台进行通信。您可以通过 pom.xml 引入 JAR 包:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.0</version>
</dependency>
注意:由于我们将Sentinel的端口修改为8858
,在启动时需加入JVM参数:-Dcsp.sentinel.dashboard.server=consoleIp:port
来指定控制台地址和端口;这里我们使用IDEA 可以直接添加,操作如下:
此时启动项目访问上面 localhost:8888/hello
,就可以看到实时监控图
注意:Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包,所以要确保客户端有访问量;
SpringCloud Alibaba 整合 Sentinel
- 引入依赖
<!--SpringBoot基本的场景启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Sentinel启动器-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- 添加yml配置
server:
port: 8010
spring:
application:
name: order-sentinel
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8080
- 编写业务
@RestController
@RequestMapping("/order")
public class OrderController {
@RequestMapping("/test")
public String test() {
return "Hello word";
}
@RequestMapping("/flow")
@SentinelResource(value = "flow",blockHandler = "flowBlockHandler")
public String flow() {
return "正常访问";
}
public String flowBlockHandler(BlockException e) {
return "流控";
}
}
- 访问测试
先访问 http://localhost:8010/order/test
增加访问量,在访问 http://localhost:8080/
。效果如图:
控制台功能介绍
1、实时监控
可以实时监控接口通过的QPS和拒绝QPS
2、簇点链路
用来显示微服务的所监控的API
BlockException异常统一处理
SpringMVC接口资源限流入口在HandlerInterceptor
的实现类 AbstractSentinelInterceptor
的 preHandler
方法中,对异常的处理是BlockExceptionHandler
的实现类。
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, BlockException e) throws Exception {
// getRule() 资源规则的详细信息
logger.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(102, "热点参数限流了");
} else if (e instanceof SystemBlockException) {
r = Result.error(103, "触发系统保护规则了");
} else if (e instanceof AuthorityException) {
r = Result.error(104, "授权规则不通过");
}
//返回json数据
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getWriter(), r);
}
}
public class Result<T> {
private Integer code;
private String msg;
private T data;
//省略getter、settter方法
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);
}
}
3、流控规则
流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,
以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
应用场景:
- 应对洪峰流量:秒杀、大促、下单、订单回流处理
- 消息型场景:削峰填谷,冷热启动
- 付费系统:根据使用流量付费
- API Gateway:精准控制API流量
1. 阈值类型
QPS(Query Per Second):每秒请求数,就是说服务器在一秒的时间内处理了多少个请求。
QPS:进入簇点链路选择具体的访问API,然后点击 flow 的流控按钮,输入如下:
测试用例:
@RequestMapping("/flow")
//@SentinelResource(value = "flow",blockHandler = "flowBlockHandler")
public String flow() {
return "正常访问";
}
测试访问http://localhost:8010/order/flow
,鼠标1秒钟点击一次,则显示访问;如果鼠标1秒钟点击超过两下,则就会出现如图所示:
这里因为我们使用 @SentinelResource
注解自定义异常了,如果没有设置此注解则显示Bloacked by Sentinel (flow limiting)
同时我们可以看到 流控规则 处有我们新添加的流控规则。
注意:这里如果我们重启服务后,刚才设置的流控规则就没有了,因为它保存在内存中,没有被持久化。
并发线程数:并发数控制用于保护业务线程池不被慢调用耗尽。
例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。
测试用例:
@RequestMapping("/flowThread")
@SentinelResource(value = "flowThread",blockHandler = "flowBlockHandler")
public String flowThread() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
return "正常访问线程";
}
public String flowBlockHandler(BlockException e) {
return "流控";
}
2. 流控模式
基于调用关系的流量控制。调用关系包括调用方、被调用方;一个方法可能会调用其它方法,形成一个调用链路的层次
关系。
直接
资源调用达到设置的阈值后直接被流控抛出异常
关联
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写
操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本
身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db 和
write_db 这两个资源分别代表数据库读写,我们可以给 read_db
设置限流规则来达到写优先的目的:设置 strategy
为
RuleConstant.STRATEGY_RELATE
同时设置 refResource
为 write_db
。这样当写库操作过于频繁时,读数据的请求会被限流。
需求:为查询订单限流;由生成订单触发查询订单的限流。
测试用例:
@RequestMapping("/add")
public String add() {
return "生成订单";
}
@RequestMapping("/get")
public String get() {
return "查询订单";
}
测试:我们选择使用 Jmeter
工具辅助测试【JMeter下载安装及入门教程】,再次访问http://localhost:8010/order/get
,可以看到如下:
链路
根据调用链路入口限流。
下面中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一棵调用树。这棵树的根节点是一个名字为
getUser 的虚拟节点,调用链的入口都是这个虚节点的子节点。
一棵典型的调用树如下图所示:
getUser
/ \
/ \
/order/test1 /order/test2
上图中来自入口 /order/test1 和 /order/test2的请求都调用到了资源 getUser,Sentinel 允许只根据某个入口的统计信息对
资源限流。
测试用例:
@Autowired
private OrderService orderService;
@RequestMapping("/test1")
public String test1() {
return orderService.getUser();
}
@RequestMapping("/test2")
public String test2() {
return orderService.getUser();
}
在test1的getUser中点击流控按钮进行添加如下:
测试会发生链路规则不生效。
注意:高版本此功能直接使用不生效,如何解决?
从1.6.3 版本开始,Sentinel Web filter 默认收敛所有URL的入口context,因此链路限流不生效。
1.7.0 版本开始(对应SCA的2.1.1.RELEASE),官方在CommonFilter 引入了 WEB_CONTEXT_UNIFY 参数,用于控制是否收敛context。将其配置为 false 即可根据不同的URL 进行链路限流。SCA 2.1.1.RELEASE之后的版本,可以通过配置spring.cloud.sentinel.web-context-unify=false
即可关闭收敛
测试,此场景拦截不到BlockException,对应@SentinelResource指定的资源必须在@SentinelResource注解中指定
blockHandler处理BlockException
再次测试访问http://localhost:8010/order/test2
,如下:
2. 流控效果
快速失败
(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
新增 快速失败
流控效果如下:
测试用例:
@RequestMapping("/flow")
public String flow() {
return "正常访问";
}
Jmeter测试:1秒,中线程数10,循环4次,定时器5000,请求/order/flow
:
Warm Up(激增流量)
Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
冷加载因子:codeFactor 默认是3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
通常冷启动的过程系统允许通过的 QPS 曲线如下图所示:
新增 Warm Up
流控效果如下:
测试用例:
@RequestMapping("/flow")
public String flow() {
return "正常访问";
}
Jmeter测试:
查看实时监控,可以看到通过QPS存在缓慢增加的过程。
匀速排队(脉冲流量)
匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER
)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。
该方式的作用如下图所示:
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
新增 快速排队
流控效果如下:
测试用例:
@RequestMapping("/flow")
public String flow() {
return "正常访问";
}
Jmeter 测试如下:
4、降级规则
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
1. 熔断策略
慢调用比例
慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALFOPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
在 /order/flowThread
上点击 降级 按钮 如下:
分析:单位ms,当前允许的最大响应时间是1000ms,大于当前值就是慢调用;单位时间内请求数目大于10,并且慢调用比例大于0.2,在接下来的10s内会自动熔断。
测试用例:
@RequestMapping("/flowThread")
@SentinelResource(value = "flowThread",blockHandler = "flowBlockHandler")
public String flowThread() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
return "正常访问线程";
}
Jmeter压力测试:1秒,线程数10,循环1次;
再次访问 http://localhost:8010/order/flowThread
会出现服务降级结果如图:
异常比例
异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALFOPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
在 /order/err
上点击 降级 按钮 如下:
分析:1s内请求数大于5次,并且异常比例大于0.1,接下来熔断时长内请求就会自动熔断。
测试用例:
@RequestMapping("/err")
public String err() {
int i = 1/0;
return "hello";
}
Jmeter测试:再次在浏览器上访问,会出现服务降级结果如图:
异常数
异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALFOPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。
分析:当单位统计时长1s内的异常数目超过阈值2,之后就会自动进行熔断。
Jmeter测试:
5、热点规则
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的数据,并对其访问进行限
制。比如:热点商品访问/操作控制,用户/IP 防刷。
实现原理:热点淘汰策略(LRU)+ Token Bucket 流控
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
在 getById
上点击新增按钮进行添加;
注意: 资源名必须是@SentinelResource(value=“资源名”)中 配置的资源名,热点规则依赖于注解
在点击修改按钮才可以添加高级选项;具体到参数值,配置参数值为1,限流阈值为2。
测试案例:
@RequestMapping("/getById/{id}")
@SentinelResource(value = "getById",blockHandler = "HotBlockHandler")
public String getById(@PathVariable("id") Integer id) throws InterruptedException {
return "正常访问";
}
public String HotBlockHandler(@PathVariable("id") Integer id, BlockException e) throws InterruptedException {
return "热点异常处理";
}
浏览器测试访问:
http://localhost:8010/order/getById/2,使用Jmeter测试线程数为11的时候,就会出现 热点异常处理。
http://localhost:8010/order/getById/1
6、系统规则
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
- Load 自适应(仅对 Linux/Unixlike 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是
CPU cores * 2.5
。
理解linux下的load:https://www.cnblogs.com/gentlemanhai/p/8484839.html - CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.01.0),比较灵敏。
- 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
入口QPS
测试:访问http://localhost:8010/order/getById/2
7、授权规则
很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制(黑白名单控制)的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。
来源访问控制规则(AuthorityRule)非常简单,主要有以下配置项:
- resource:资源名,即限流规则的作用对象。
- limitApp:对应的黑名单/白名单,不同 origin 用 , 分隔,如 appA,appB。
- strategy:限制模式,
AUTHORITY_WHITE
为白名单模式,AUTHORITY_BLACK
为黑名单模式,默认为白名单模式。
首先要实现 com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser
接口,在 parseOrigin
方法中区分来源,并交给spring管理
@Component
public class MyRequestOriginParser implements RequestOriginParser {
// 通过request获取来源标识,交给授权规则进行匹配
@Override
public String parseOrigin(HttpServletRequest request) {
// 标识字段名称可以自定义
String origin = request.getParameter("serviceName");
if (StringUtil.isBlank(origin)){
throw new IllegalArgumentException("serviceName参数未指定");
}
return origin;
}
}
测试:origin是order的请求不通过。
8、集群规则
为什么要使用集群流控呢?假设我们希望给某个用户限制调用某个 API 的总 QPS 为 50,但机器数可能很多(比如有100 台)。这时候我们很自然地就想到,找一个 server 来专门来统计总的调用量,其它的实例都与这台 server 通信来判断是否可以调用。这就是最基础的集群流控的方式。
另外集群流控还可以解决流量不均匀导致总体限流效果不佳的问题。假设集群中有 10 台机器,我们给每台机器设置单机限流阈值为 10 QPS,理想情况下整个集群的限流阈值就为 100 QPS。不过实际情况下流量到每台机器可能会不均匀,会导致总量没有到的情况下某些机器就开始限流。因此仅靠单机维度去限制的话会无法精确地限制总体流量。而集群流控可以精确地控制整个集群的调用总量,结合单机限流兜底,可以更好地发挥流量控制的效果。
集群流控中共有两种身份:
- Token Client:集群流控客户端,用于向所属 Token Server 通信请求 token。集群限流服务端会返回给客户端结果,决定是否限流。
- Token Server:即集群流控服务端,处理来自 Token Client 的请求,根据配置的集群规则判断是否应该发放token(是否允许通过)。
Sentinel 集群流控支持限流规则和热点规则两种规则,并支持两种形式的阈值计算方式:
- 集群总体模式:即限制整个集群内的某个资源的总体 qps 不超过此阈值。
- 单机均摊模式:单机均摊模式下配置的阈值等同于单机能够承受的限额,token server 会根据连接数来计算总的阈值(比如独立模式下有 3 个 client 连接到了 token server,然后配的单机均摊阈值为 10,则计算出的集群总量就为 30),按照计算出的总的阈值来进行限制。这种方式根据当前的连接数实时计算总的阈值,对于机器经常进行变更的环境非常适合。
Sentinel 集群限流服务端有两种启动方式:
- 独立模式(Alone),即作为独立的 token server 进程启动,独立部署,隔离性好,但是需要额外的部署操作。独立模式适合作为 Global Rate Limiter 给集群提供流控服务。
- 嵌入模式(Embedded),即作为内置的 token server 与服务在同一进程中启动。在此模式下,集群中各个实例都是对等的,token server 和 client 可以随时进行转变,因此无需单独部署,灵活性比较好。但是隔离性不佳,需要限制 token server 的总 QPS,防止影响应用本身。嵌入模式适合某个应用集群内部的流控。
云上版本 AHAS Sentinel 提供开箱即用的全自动托管集群流控能力,无需手动指定/分配 token server 以及管理连接状态,同时支持分钟小时级别流控、大流量低延时场景流控场景,同时支持 Istio/Envoy 场景的 Mesh 流控能力。
整合openfeign进行降级
- 引入依赖
<!--nacos注册发现-->
<dependency>
<groupId>org.alibaba.cloud</groupId>
<artifactId>spring‐cloud‐starter‐alibaba-nacos-discovery</artifactId>
</dependency>
<!--openfegin依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring‐cloud‐starter‐openfeign</artifactId>
</dependency>
<!--sentinel熔断降级依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring‐cloud‐starter‐alibaba‐sentinel</artifactId>
</dependency>
- 添加application.yml
server:
port: 8041
spring:
application:
name: order-service
cloud:
nacos:
server-addr: 127.0.0.1:8848
discovery:
username: nacos
password: nacos
namespace: public
feign:
sentinel:
enabled: true
- openfegin接口
@FeignClient(value = "nacos‐payment‐provider",fallback = ConsumerFallBackService.class )
public interface ConsumerService {
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
}
- openfegin的fallback实现类
@Component
public class ConsumerFallBackService implements ConsumerService {
@Override
public CommonResult<Payment> paymentSQL(Long id) {
return new CommonResult<Payment>("500","进入兜底方法‐‐‐ConsumerFallBackService",null);
}
}
- 测试就可以看到降级成功。
Sentinel持久化
1、原始模式
如果不做任何修改,Dashboard 的推送规则方式是通过 API 将规则推送至客户端并直接更新到内存中:
这种做法的好处是简单,无依赖;坏处是应用重启规则就会消失,仅用于简单测试,不能用于生产环境。
2、拉模式
pull 模式的数据源(如本地文件、RDBMS 等)一般是可写入的。使用时需要在客户端注册
数据源:将对应的读数据源注册至对应的 RuleManager,将写数据源注册至 transport 的 WritableDataSourceRegistry
中。
3、推模式
生产环境下一般更常用的是 push 模式的数据源。对于 push 模式的数据源,如远程配置中心(ZooKeeper, Nacos, Apollo等等),推送的操作不应由 Sentinel 客户端进行,而应该经控制台统一进行管理,直接进行推送,数据源仅负责获取配置中心推送的配置并更新到本地。因此推送规则正确做法应该是 配置中心控制台/Sentinel 控制台 → 配置中心 → Sentinel 数据源 → Sentinel,而不是经 Sentinel 数据源推送至配置中心。
如果有收获!!! 希望老铁们来个三连,点赞、收藏、转发。
创作不易,别忘点个赞,可以让更多的人看到这篇文章,顺便鼓励我写出更好的博客