Sentinel - 分布式系统的流量哨兵
介绍
sentinel是阿里开源的分布式流控组件,支持Dubbo、Servlet、Spring Boot / Cloud、gRPC等扩展方式,提供了流量控制、熔断降级、系统负载保护等措施来保障服务稳定。
Sentinel极为轻量,仅有200k,且无过多依赖。性能方面,25wQPS下不会对性能产生显著影响。
GitHub: https://github.com/alibaba/Sentinel/wiki
核心概念
1. Context上下文
Sentinel 中所有资源的访问都会生成一个Context(请求上下文),其基于ThreadLocal。
Context在entry创建时生成和获取,在exit中清除,所以如果entry不为空,一定要在最后调用exit以清除上下文。
2. 插槽
每一资源对应一串插槽组成的插槽链,这些插槽用来统计执行情况和校验规则。
其资源和插槽链的对应关系存在于一个volitile修饰的chainMap中,其更新时使用DOUBLE-CHECK方式来保证读写并行、写写串行,更新逻辑为copyOnWrite,具体可参看获取插槽链的源码:
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
ProcessorSlotChain chain = chainMap.get(resourceWrapper);
if (chain == null) {
synchronized (LOCK) {
chain = chainMap.get(resourceWrapper);
if (chain == null) {
// 资源数限制最大为 MAX_SLOT_CHAIN_SIZE(6000)
if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
return null;
}
chain = SlotChainProvider.newSlotChain();
Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
chainMap.size() + 1);
newMap.putAll(chainMap);
newMap.put(resourceWrapper, chain);
chainMap = newMap;
}
}
}
return chain;
}
NodeSelectorSlot
负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;ClusterBuilderSlot
则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;StatisticsSlot
则用于记录,统计不同维度的 runtime 信息;SystemSlot
则通过系统的状态,例如 load1 等,来控制总的入口流量;AuthoritySlot
则根据黑白名单,来做黑白名单控制;FlowSlot
则用于根据预设的限流规则,以及前面 slot 统计的状态,来进行限流;DegradeSlot
则通过统计信息,以及预设的规则,来做熔断降级;
每个Slot执行完业务逻辑处理后,会调用fireEntry()
方法,该方法将会触发下一个节点的entry方法,下一个节点又会调用他的fireEntry,以此类推直到最后一个Slot,由此就形成了sentinel的责任链。
3. 限流模式
-
直接失败模式
超过限流设置阈值后,直接失败抛出BlockException异常
-
关联模式
Sentinel支持多个资源之间限流的彼此关联
比如,A资源设置了关联资源(RefResource)为B资源,那么当B资源被请求打满后,A资源同时也被限流。
-
预热模式
系统冷启动时,如果流量瞬间打满可能把系统打死,那么我们可以通过设置预热模式,并设置预热值(WarmUpPeriodSec),这样请求数超过预热值后,经预热时长慢慢提升到最大阈值。
-
排队等待模式
匀速排队模式内部采用漏桶算法,严格控制请求通过的间隔时间。
这种方式,主要应对突发情况:对于瞬间大量请求我们可能不希望直接抛弃,而是希望在接下来的系统空闲时间慢慢消化这些超额请求。
-
热点规则
对于热点数据,往往系统单独监控,比如下单场景,绝大数的非热点sku流量正常,但某个热点商品可能希望对其进行单独限流,以免其影响其他非热点sku的下单行为。对应到代码中,就是对方法参数的不同情况单独设置限流策略。
参数说明如下:
参数 说明 参数值举例 资源名 @SentinelResource(value = "getOrder")
中的value
值getOrder 参数索引 针对第 0 个参数 0 参数类型 索引位置是 0 的参数的类型是 long long 参数值 如果其值是 1 1 限流阈值 允许的 QPS 是 1 1 单机阈值 不是 1 的情况下,允许的 QPS 就是 10 10 统计时间窗口 统计的时间单位,一般都是 1s 1 这样设置后,对于被
@SentinelResource(value = "getOrder")
标注的接口:-
如果参数 id 带的值是 1,那么允许它的 QPS 就是 1
-
对于 id 值是其他的值的请求,允许其 QPS 就是 10
-
使用示例
引入maven
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
定义规则
private static void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("HelloWorld"); // 定义该规则的资源名
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(3); // 设置QPS为3
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
增加限流
通过注解@SentinelResource(String name)
修饰方法或者手动调用SphU.entry(String name)
方法开启流控
@Test
public void testRule() {
// 配置规则.
initFlowRules();
int count = 0;
while (true) {
try (Entry entry = SphU.entry("HelloWorld")) {
// 被保护的逻辑
System.out.println("run " + ++count + " times");
} catch (BlockException ex) {
// 处理被流控的逻辑
System.out.println("blocked after " + count);
break;
}
}
}
// 输出结果:
// run 1 times
// run 2 times
// run 3 times
// blocked after 3
流控规则设置
FlowRuleManager.loadRules(List<FlowRule> rules); // 修改流控规则
DegradeRuleManager.loadRules(List<DegradeRule> rules); // 修改降级规则
SystemRuleManager.loadRules(List<SystemRule> rules); // 修改系统规则
AuthorityRuleManager.loadRules(List<AuthorityRule> rules); // 修改授权规则