前言
在现行高并发场景下,通常会采用微服务架构,可以更好的对服务接口进行水平扩展、接口限流等有效保障服务的可用性和稳定性。其中为了保障服务的高可用,同时也为了防止故障蔓延,通常会选择断路器,防止故障的服务还一直占用CPU资源,可能导致其他服务不能及时处理数据请求。
由于服务器所能承受的压力是有限的,为了保证可用性,一般会做服务限流,减少服务压力。
那么常见的断路器有Sentinel、Hystrix,而常见的微服务限流组件有RateLimiter、Hystrix、Sentinel等。其中Hystrix也在2022年官方发布说明停止维护,且Sentinel是阿里研发,专门为流量控制而生的限流框架。
故在微服务中,服务限流熔断建议使用Sentinel。
介绍
Sentinel 是一个高可用、高扩展、高稳定性的开源流量控制和熔断降级框架,可以在分布式系统中实现实时的流量控制,防止系统因流量过大导致系统崩溃和服务降级。
Sentinel 提供了以下功能:
- 流量控制:通过配置不同的规则,对请求流量进行限制。
- **熔断降级:**当系统异常情况发生时,可以自动熔断系统,保证系统的可用性。
- **系统负载保护:**在系统负载高峰期间,可以限制请求流量,避免系统资源耗尽。
- 实时监控:可以实时监控系统的请求流量、响应时间、错误率等指标。
Sentinel 面向所有的 Java 应用,可以支持基于 Spring Cloud、Dubbo、gRPC 等服务框架的应用,也可以集成到基于 Tomcat、Jetty 等 Web 容器的应用中。
什么是流量控制?
流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。
任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的,由此需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状。
流量控制设计理念
**1)资源的调用关系。**例如资源的调用链路,资源和资源之间的关系;
**2)运行指标。**例如 QPS、线程池、系统负载等;
**3)控制的效果。**例如直接限流、冷启动、排队等
什么是熔断降级?
除了流量控制以外,降低调用链路中的不稳定资源也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积。
Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。
设计理念
**Hystrix 通过线程池的方式,来对依赖资源进行了隔离。**这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本,还需要预先给各个资源做线程池大小的分配。
Sentinel的处理方式:
**1)通过并发线程数进行限制。**和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。
**2)通过响应时间对资源进行降级。**当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
**3)系统负载保护。**Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
工作机制
**对于定义需要保护的资源,提供设施对资源进行实时统计和调用链路分析。并根据预设的规则,结合对资源的实时统计信息,对流量进行控制。同时,Sentinel 提供开放的接口,方便定义及改变规则。**另外Sentinel 提供实时的监控系统,可直观了解目前系统的状态。
使用说明
1)在Maven中引入Sentinel的依赖:spring-cloud-starter-alibaba-sentinel
2)配置Sentinel的启动参数:
yml
# Sentinel 控制台的地址
spring.cloud.sentinel.transport.dashboard=http://localhost:8080
# Sentinel 的启动端口
spring.cloud.sentinel.transport.port=8719
# Sentinel 控制台连接超时时间(ms) spring.cloud.sentinel.transport.dashboard.request-timeout=5000
# 配置资源的默认规则
spring.cloud.sentinel.rules.defaults[0].grade=QPS spring.cloud.sentinel.rules.defaults[0].count=10
3)若是需要流量控制,添加流量控制注解。
java
@SentinelResource(value = "demoMethod", blockHandler = "handleBlock")
public String demoMethod() {
return "Hello World";
}
// 自定义的响应结果 也可以写成通用类
public String handleBlock(BlockException ex) {
return "请求被拦截: " + ex.getClass().getSimpleName();
}
4)若是需要熔断控制,在@SentinelResource注解中,需要指定fallback属性。
java
@SentinelResource(value = "demoMethod", blockHandler = "handleBlock", fallbackClass = DemoServiceFallback.class, fallback = "fallback")
public String demoMethod() {
return "Hello World";
}
public String handleBlock(BlockException ex) {
return "请求被拦截: " + ex.getClass().getSimpleName();
}
5)Sentinel 支持多种多样的流控规则和热点参数限流策略,可以根据业务场景进行灵活配置。
5-1)QPS 流量控制。
@RateLimiter(10)
5-2)线程数流控。
@ThreadPool(name = "demoMethod", coreSize = 5, maxQueueSize = 10)
**5-3)热点参数限流。**可以有效避免因某个参数的恶意使用而导致整个系统崩溃的情况。
@HotParam(value = "skuId", mode = ParamFlowItem.FlowControlMode.QPS, threshold = 100)
优缺点
优点:
1)功能丰富,简单便于使用。
2)提供了多种SPI扩展点,可自定义扩展。
3)开源免费
缺点:
1)仅支持Java
2)文档略显简单,实践中存在一些坑点需要注意。。
坑点,回头补充...
基本概念
1、资源
**资源是 Sentinel 的关键概念。只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。**大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
2、规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
底层实现原理
Sentinel 实现流量控制和熔断降级的原理是通过对应用程序进行拦截,然后根据预定义的规则,来判断该请求是否被允许或者需要进行降级处理。
Sentinel 的拦截器会在应用程序中建立一个责任链,对请求进行逐一拦截。在拦截过程中,Sentinel 会对 Request、Response、Exception 等参数进行统计,根据统计信息来对请求进行熔断或者限流等操作。
衡量系统稳定性主要有以下三个指标:
- TPS(Transactions Per Second):每秒钟处理的事务数。
- RT(Response Time):响应时间,即从发送请求到接收到响应的时间。
- Error Rate:错误率,即发生错误的请求次数占总请求数的比例。
Sentinel 根据这三个指标来评估应用程序的健康状况,当这些指标达到某个阈值时,Sentinel 会自动触发相应的流量控制和熔断降级操作。
@SentinelResource
通常使用@SentinelResource来标记一个方法,这个在Sentinel服务中被看成一个Sentinel资源。可以以此切面,查看拦截之后所做的处理。
进入SentinelResource切面后,首先会先获取资源的名称、类型,执行SphU.entry方法,并把资源包装成Entry。其在SphU.entry中做限流和熔断的逻辑处理。
在SphU.entry方法中的执行流程:
1)**获取Sentinel上下文(Context)。**就是Sentinel熔断限流执行的上下文,包含资源调用的节点和Entry信息。Context是线程持有的,利用ThreadLocal与当前线程绑定。而Context主要包含Conetxt名称,Node,Entry,这三个类是Sentinel的核心类,提供了资源调用路径、资源调用统计等信息。
Context是当前线程所持有的Sentinel上下文。进入Sentinel的逻辑时,会首先获取当前线程的Context,如果没有则新建。当任务执行完毕后,会清除当前线程的context。Context 代表调用链路上下文,贯穿一次调用链路中的所有 Entry。
Context 维持着入口节点(entranceNode)、本次调用链路的 当前节点(curNode)、调用来源(origin)等信息。Context 名称即为调用链路入口名称。
**Node是对一个@SentinelResource标记的资源的统计包装。**可以通过入口节点的childList,可以追溯资源的调用情况。而每个节点都对应一个@SentinelResource标记的资源及其统计数据,例如:passQps,blockQps,rt等数据。
Entry是Sentinel中用来表示是否通过限流的一个凭证,如果能正常返回,则说明你可以访问被Sentinel保护的后方服务,否则Sentinel会抛出一个BlockException。
Entry保存了本次执行entry()方法的一些基本信息,包括资源的Context、Node、对应的责任链等信息,后续完成资源调用后,还需要更具获得的这个Entry去执行一些善后操作,包括退出Entry对应的责任链,完成节点的一些统计信息更新,清除当前线程的Context信息等。
2)获取资源对应的责任链。
默认的责任链中的处理节点包括NodeSelectorSlot、ClusterBuilderSlot、StatisticSlot、FlowSlot、DegradeSlot等。调用链(ProcessorSlotChain)和其中包含的所有Slot都实现了ProcessorSlot接口,采用责任链的模式执行各个节点的处理逻辑,并调用下一个节点。其中,每个资源对应一条单独的责任链。
责任链中获取资源,源码中采用双重双检锁方式获取资源。
3)生成资源调用凭证(Entry)。
生成的Entry是CtEntry。其构造参数包括资源包装(ResourceWrapper)、资源对应的责任链以及当前线程的Context。CtEntry是一个双向链表,构建了Sentinel资源的调用链路。
4)执行责任链中各个节点。
责任链和其中的Slot都实现了ProcessorSlot,责任链的entry方法会依次执行责任链各个slot。
1)**NodeSelectorSlot -- 获取当前资源对应Node,构建节点调用树。**这个Node被用于后续资源调用的统计及限流和熔断条件的判断。
2)ClusterBuilderSlot -- 聚合相同资源不同Context的Node,以供后续限流判断使用。默认的限流条件判断就是依据ClusterNode中的统计信息来进行的。
3)**StatisticSlot -- 资源调用统计。**与之前slot不同的是,StatisticSlot的执行时先触发下一个slot的执行,等下面的slot执行完才会执行自己的逻辑。
4)FlowSlot -- 限流判断。之前在StatisticSlot对相关资源调用做的统计,在当前环节使用。默认情况下,限流使用的节点是当前节点的cluster node。主要分析的限流方式是QPS限流。
限流操作的核心逻辑--限流规则检查器(FlowRuleChecker):
获取资源对应的限流规则
根据限流规则检查是否被限流
如果被限流,则抛出限流异常FlowException。FlowException继承自BlockException
限流的关键代码(DefaultController):
获取节点的当前qps计数;
判断获取新的计数后是否超过阈值
超过阈值单返回false,表示被限流,后面会抛出FlowException。否则返回true,不被限流。
5)Entry.exit()方法,清空资源。执行流程:
- 判断要退出的entry是否是当前context的当前entry;
- 如果要退出的entry不是当前context的当前entry,则不退出此entry,而是退出context的的当前entry及其所有父entry,并抛出异常;
- 如果要退出的entry是当前context的当前entry(这种是正常情况),先退出当前entry对应的责任链的所有slot。在这一步,StatisticSlot会更新node的success计数和RT计数;
- 将context的当前entry置为被退出的entry的父entry;
- 如果被退出entry的父entry为空,且context为默认context,自动退出默认context(清除ThreadLocal)。
- 清除被退出entry的context引用
如果触发熔断和限流,会抛出BlockException,我们可以指定blockHandler方法来处理BlockException。而对于业务上的异常,我们也可以配置fallback方法来处理被拦截方法调用产生的异常。
NodeSelectorSlot的的作用是:
- 在资源对应的调用链执行时,获取当前context对应的Node,这个Node代表着这个资源的调用情况。
- 将获取到的node设为当前node,添加到之前的node后面,形成树状的调用路径。(通过Context中的当前Entry进行)
- 触发下一个Slot的执行。
小结
三大组件Context、Entry、Node,是Sentinel的核心组件,各类信息及资源调用情况都由这三大类持有。
- 采用责任链模式完成Sentinel的信息统计、熔断、限流等操作;
- 责任链中NodeSelectSlot负责选择当前资源对应的Node,同时构建node调用树;
- 责任链中ClusterBuilderSlot负责构建当前Node对应的ClusterNode,用于聚合同一资源对应不同Context的Node;
- 责任链中的StatisticSlot用于统计当前资源的调用情况,更新Node与其对用的ClusterNode的各种统计数据;
- 责任链中的FlowSlot根据当前Node对应的ClusterNode(默认)的统计信息进行限流;
- 资源调用统计数据(例如PassQps)使用滑动时间窗口进行统计;
- 所有工作执行完毕后,执行退出流程,补充一些统计数据,清理Context。
Sentinel 高级特性
1、同时支持同步和异步调用
Sentinel 可以方便地支持同步和异步调用。 对于同步调用,可以使用 @SentinelResource 注解,在注解中指定需要进行保护的方法,并设置相应的熔断降级、流控规则等限制条件。
对于异步调用,则需要使用 Sentinel 提供的异步 Entry 类实现保护。 在使用异步 Entry 进行保护时,需要在异步调用过程中插入 Sentinel 的拦截器,并在异步操作完成后手动释放相应的资源,以便 Sentinel 统计并记录相应的数据。
java
CompletableFuture.supplyAsync(() -> {
Entry entry = null;
try {
entry = SphU.asyncEntry("demoMethod");
// 异步逻辑
return "Hello World";
} catch (BlockException ex) {
return "blocked by Sentinel: " + ex.getClass().getSimpleName();
} finally {
//释放资源
if (entry != null) {
entry.exit();
}
}
}).thenAccept(result -> System.out.println("result: " + result));
2、支持多种限流模式
Sentinel 支持多种限流模式,可以根据实际需求选择不同的限流算法。
- **直接模式:**直接对资源进行限制,超出阈值即触发限流。
- **关联模式:**通过关联资源来进行限流,例如对某个 API 进行流量控制时,可以通过关联访问该 API 的数据库连接池来实现流量控制。通过 setRefResource 和 setStrategy 来关联数据库连接池资源,并设置限流策略为关联模式。
- **链路模式:**通过对整个链路进行流量控制来保障系统的稳定性。
注意:关联模式和链路模式时,需要在规则中设置相关的关联链接和链路信息。
java
// 关联模式示例:
@SentinelResource(value = "demoMethod", blockHandler = "handleBlock")
public void demoMethod(@RequestParam("id") Long id) {
System.out.println("request id: " + id);
}
@Bean
public RequestOriginParser requestOriginParser() {
return new DemoRequestOriginParser();
}
public static class DemoRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
String origin = request.getParameter("origin");
if (StringUtils.isEmpty(origin)) {
return "unknown";
}
return origin;
}
}
@Configuration
public class SentinelConfig {
@Autowired
private RequestOriginParser requestOriginParser;
@PostConstruct
public void init() {
FlowRuleManager.register2(Arrays.asList(
new FlowRule("demoMethod").setCount(5)
.setGrade(RuleConstant.FLOW_GRADE_QPS)
.setLimitApp("default")
.as(FlowRule.class)
.setStrategy(RuleConstant.STRATEGY_RELATE)
.setRefResource("demoDatabase")));
}
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
@Bean
public SentinelServletRequestAspect sentinelServletRequestAspect() {
return new SentinelServletRequestAspect();
}
}
3、支持多种规则匹配方式
Sentinel 支持多种规则匹配方式,可以根据实际需求选择不同的规则匹配策略。
- **精确匹配:**精确匹配是最常用的匹配方式,可以根据资源名称、限流参数等精确匹配规则。
- 子串匹配:通常用于对资源名称进行模糊匹配,例如对某个 API 的所有请求进行限流。
- **正则匹配:**可以根据正则表达式进行规则匹配,提供更高级别的灵活性。使用 setResourceRegex 方法设置了一个正则匹配规则
java
//正则匹配规则的示例
@SentinelResource(value = "demoMethod", blockHandler = "handleBlock")
public void demoMethod(@RequestBody Map<String, Object> data) {
System.out.println("request data: " + data);
}
@Configuration
public class SentinelConfig {
@PostConstruct
public void init() {
SystemRuleManager.loadRules(Collections.singletonList(
new SystemRule()
.setHighestSystemLoad(1.0)
.setAvgLoad(0.8)
.setQps(200))));
ParamFlowRuleManager.loadRules(Collections.singletonList(
new ParamFlowRule()
.setParamIdx(0)
.setGrade(RuleConstant.FLOW_GRADE_QPS)
.setCount(5)
.setDurationInSec(1)
.setParamFlowItemList(Collections.singletonList(
new ParamFlowItem().setObject("special_object")
.setCount(2)))));
DegradeRuleManager.loadRules(Collections.singletonList(
new DegradeRule("demoMethod")
.setCount(100)
.setTimeWindow(10)
.setGrade(RuleConstant.DEGRADE_GRADE_RT)
.setCount(20)
.setMinRequestAmount(10))));
FlowRuleManager.loadRules(Collections.singletonList(
new FlowRule()
.setGrade(RuleConstant.FLOW_GRADE_QPS)
.setResourceRegex("/api/.*")
.setCount(10)
.setLimitApp("default")
.as(FlowRule.class))));
}
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}
其他
以后补充...
总结
Sentinel是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。是一个非常不错的流量控制中间件。