Sentinel - 分布式系统的流量哨兵

Posted by KANG's BLOG on Tuesday, March 15, 2022

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); // 修改授权规则