跳到主要内容

核心原理深入

本章深入探讨 Sentinel 的核心原理,帮助你理解 Sentinel 的内部工作机制,以便更好地使用和扩展它。

整体架构

Sentinel 的整体架构可以分为两个部分:核心库和控制台。核心库负责流量控制、熔断降级等功能,控制台提供可视化的监控和规则管理。

核心库架构

Sentinel 核心库的工作流程基于 Slot Chain(插槽链)实现。每个资源都会对应一个 Slot Chain,请求进入资源时会依次经过各个 Slot 进行处理。

请求 → SphU.entry()


┌────────────────────────────────────────────┐
│ Slot Chain │
│ ┌──────────────────────────────────────┐ │
│ │ NodeSelectorSlot │ │
│ │ └─ 构建调用链路树 │ │
│ └──────────────────────────────────────┘ │
│ ┌──────────────────────────────────────┐ │
│ │ ClusterBuilderSlot │ │
│ │ └─ 构建集群统计节点 │ │
│ └──────────────────────────────────────┘ │
│ ┌──────────────────────────────────────┐ │
│ │ LogSlot │ │
│ │ └─ 记录异常日志 │ │
│ └──────────────────────────────────────┘ │
│ ┌──────────────────────────────────────┐ │
│ │ StatisticSlot │ │
│ │ └─ 实时统计数据 │ │
│ └──────────────────────────────────────┘ │
│ ┌──────────────────────────────────────┐ │
│ │ AuthoritySlot │ │
│ │ └─ 黑白名单控制 │ │
│ └──────────────────────────────────────┘ │
│ ┌──────────────────────────────────────┐ │
│ │ SystemSlot │ │
│ │ └─ 系统自适应保护 │ │
│ └──────────────────────────────────────┘ │
│ ┌──────────────────────────────────────┐ │
│ │ FlowSlot │ │
│ │ └─ 流量控制 │ │
│ └──────────────────────────────────────┘ │
│ ┌──────────────────────────────────────┐ │
│ │ DegradeSlot │ │
│ │ └─ 熔断降级 │ │
│ └──────────────────────────────────────┘ │
└────────────────────────────────────────────┘


业务逻辑

资源定义

资源是 Sentinel 的核心概念,可以是任何需要保护的代码片段。资源的定义方式:

// 方式一:API 方式
Entry entry = SphU.entry("resourceName");
try {
// 业务逻辑
} finally {
entry.exit();
}

// 方式二:注解方式
@SentinelResource(value = "resourceName")
public void doSomething() {
// 业务逻辑
}

当调用 SphU.entry() 时,Sentinel 会:

  1. 创建或获取资源的 Slot Chain
  2. 依次执行各个 Slot 的逻辑
  3. 如果某个 Slot 判定需要阻止请求,抛出 BlockException
  4. 如果所有 Slot 都通过,返回 Entry 对象

核心组件详解

Context 和 Entry

Context 是 Sentinel 的上下文环境,包含了当前调用链的信息:

public class Context {
// 上下文名称(入口名称)
private final String name;

// 调用来源
private String origin = "";

// 入口节点
private DefaultNode entranceNode;

// 当前 Entry
private Entry curEntry;

// 是否异步
private boolean async;
}

Entry 代表一次资源访问:

public abstract class Entry {
// 资源信息
protected ResourceWrapper resourceWrapper;

// 创建时间
private long createTime;

// 当前节点
private Node curNode;

// 来源节点
private Node originNode;
}

Context 存储在 ThreadLocal 中,因此:

  • 同一线程内的调用共享同一个 Context
  • 异步调用需要特殊处理来传递 Context

NodeSelectorSlot

NodeSelectorSlot 负责构建调用链路树,记录资源之间的调用关系。

调用链路树结构

                machine-root (机器根节点)

┌───────────┴───────────┐
│ │
EntranceNode1 EntranceNode2
(entrance1) (entrance2)
│ │
│ │
DefaultNode DefaultNode
(resourceA) (resourceA)

工作原理

public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode param, int count, boolean prioritized, Object... args) {
// 根据上下文名称获取或创建入口节点
DefaultNode node = map.get(context.getName());
if (node == null) {
node = new DefaultNode(resourceWrapper, null);
map.put(context.getName(), node);
// 添加到根节点
Constants.ROOT.addChild(node);
}

// 设置当前节点
context.setCurNode(node);

// 调用下一个 Slot
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

重要概念

  • EntranceNode:入口节点,代表一个调用链路的入口
  • DefaultNode:默认节点,代表一个资源在特定入口下的统计数据
  • 一个资源可以有多个 DefaultNode(对应不同的入口)

ClusterBuilderSlot

ClusterBuilderSlot 负责构建资源的集群统计节点(ClusterNode)。

ClusterNode 与 DefaultNode 的区别

  • DefaultNode:按入口区分,同一资源在不同入口下有不同的 DefaultNode
  • ClusterNode:资源维度,同一资源只有一个 ClusterNode,聚合所有入口的统计数据
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) {
// 获取或创建 ClusterNode
ClusterNode clusterNode = clusterNodeMap.get(resourceWrapper.getName());
if (clusterNode == null) {
clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType());
clusterNodeMap.put(resourceWrapper.getName(), clusterNode);
}

// 设置 ClusterNode
node.setClusterNode(clusterNode);

// 如果有调用来源,创建来源节点
if (!"".equals(context.getOrigin())) {
Node originNode = clusterNode.getOrCreateOriginNode(context.getOrigin());
context.getCurEntry().setOriginNode(originNode);
}

fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

StatisticSlot

StatisticSlot 是 Sentinel 的核心统计插槽,负责实时统计请求数据。

统计数据结构

public class ClusterNode {
// 统计数组(滑动窗口)
private Metric rollingCounterInSecond; // 秒级统计
private Metric rollingCounterInMinute; // 分钟级统计
}

统计的指标

  • 通过请求数(pass)
  • 阻止请求数(block)
  • 成功请求数(success)
  • 异常数(exception)
  • 响应时间(rt)
  • 并发线程数(thread)

工作原理

public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) {
// 增加线程数
node.increaseThreadNum();
// 增加通过数
node.addPassRequest(count);

try {
// 调用下一个 Slot
fireEntry(context, resourceWrapper, node, count, prioritized, args);
} catch (BlockException e) {
// 被阻止,增加阻止数
node.increaseBlockQps(count);
throw e;
}
}

public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
// 获取响应时间
long rt = TimeUtil.currentTimeMillis() - context.getCurEntry().getCreateTime();

// 减少线程数
node.decreaseThreadNum();
// 增加成功数
node.addSuccessRequest(count);
// 记录响应时间
node.addRtAndSuccess(rt, count);

fireExit(context, resourceWrapper, count, args);
}

滑动窗口算法

Sentinel 使用滑动窗口算法来实现实时统计,核心数据结构是 LeapArray。

LeapArray 结构

时间轴:  ─────────────────────────────────────►
│ │ │ │
Window0 Window1 Window2 Window3
│ │ │ │
[统计] [统计] [统计] [统计]
│ │ │
└────────┴────────┘
滑动窗口

实现原理

public class LeapArray<T> {
// 窗口大小
protected int windowLengthInMs;
// 窗口数量
protected int sampleCount;
// 窗口数组
protected final AtomicReferenceArray<WindowWrap<T>> array;

public WindowWrap<T> currentWindow(long timeMillis) {
// 计算当前时间对应的窗口下标
int idx = calculateTimeIdx(timeMillis);
// 计算窗口开始时间
long windowStart = calculateWindowStart(timeMillis);

while (true) {
WindowWrap<T> old = array.get(idx);

if (old == null) {
// 创建新窗口
WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket());
if (array.compareAndSet(idx, null, window)) {
return window;
}
} else if (windowStart == old.windowStart()) {
// 当前窗口
return old;
} else {
// 窗口过期,重置
old.resetWindowTo(old.windowStart());
return old;
}
}
}
}

优势

  • 高性能:使用环形数组,避免频繁创建对象
  • 低内存:固定大小的数组
  • 高精度:支持多个时间窗口

FlowSlot

FlowSlot 负责流量控制,根据预设的规则判断是否允许请求通过。

流量控制流程

public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) {
// 检查所有流控规则
checkFlow(resourceWrapper, context, node, count, prioritized);

fireEntry(context, resourceWrapper, node, count, prioritized, args);
}

void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized) {
// 遍历规则
for (FlowRule rule : rules) {
if (!rule.passCheck(context, node, count, prioritized)) {
throw new FlowException(rule);
}
}
}

限流算法

  1. 直接拒绝:简单地判断 QPS 是否超过阈值
public boolean canPass(FlowRule rule, Context context, DefaultNode node, int count) {
int currentQps = node.passQps();
return currentQps + count <= rule.getCount();
}
  1. Warm Up(预热):基于令牌桶算法,冷启动时阈值较低,逐渐增加
public boolean canPass(FlowRule rule, Context context, DefaultNode node, int count) {
// 令牌桶算法
double currentQps = node.passQps();
double warningToken = rule.getCount() / 3; // 预警令牌数

if (currentQps < warningToken) {
// 冷启动阶段,阈值较低
return true;
}
// 正常阶段
return currentQps + count <= rule.getCount();
}
  1. 匀速排队:基于漏桶算法,严格控制请求间隔
public boolean canPass(FlowRule rule, Context context, DefaultNode node, int count) {
// 计算期望的通过时间
long expectedTime = lastPassedTime.get() + costTime;

if (expectedTime <= currentTime) {
// 可以立即通过
lastPassedTime.set(currentTime);
return true;
}

// 需要等待
long waitTime = expectedTime - currentTime;
if (waitTime > maxQueueingTimeMs) {
return false; // 超过最大等待时间
}

// 排队等待
Thread.sleep(waitTime);
return true;
}

DegradeSlot

DegradeSlot 负责熔断降级,根据资源的运行状态决定是否熔断。

熔断器实现

public class CircuitBreaker {
// 熔断器状态
private volatile State state = State.CLOSED;
// 探测请求
private final AtomicInteger retryTimeout = new AtomicInteger(0);

public boolean tryPass(Context context) {
if (state == State.CLOSED) {
return true; // 关闭状态,允许通过
}

if (state == State.OPEN) {
// 开启状态,检查是否到达探测时间
return retryTimeout.get() <= 0;
}

return true; // 半开启状态,允许探测请求通过
}

public void onClose() {
state = State.OPEN;
// 设置探测时间
retryTimeout.set(rule.getTimeWindow());
}

public void onHalfOpen() {
state = State.HALF_OPEN;
}

public void onClose() {
state = State.CLOSED;
retryTimeout.set(0);
}
}

熔断策略

  1. 慢调用比例:统计慢调用比例,超过阈值则熔断

  2. 异常比例:统计异常比例,超过阈值则熔断

  3. 异常数:统计异常数量,超过阈值则熔断

SystemSlot

SystemSlot 负责系统自适应保护,从系统整体维度进行保护。

系统指标获取

public class SystemStatusListener {
// CPU 使用率
private double cpuUsage;
// 系统 Load
private double load;

public void run() {
// 定时获取系统指标
cpuUsage = getCpuUsage();
load = getSystemLoad();
}
}

BBR 算法实现

public boolean checkSystem(SystemRule rule, int currentThreadNum, double currentQps) {
// 检查 CPU 使用率
if (rule.getHighestCpuUsage() > 0 && currentCpuUsage > rule.getHighestCpuUsage()) {
return false;
}

// 检查 Load
if (rule.getHighestSystemLoad() > 0 && currentLoad > rule.getHighestSystemLoad()) {
// BBR 检查:当前线程数是否超过系统容量
if (currentThreadNum > systemCapacity) {
return false;
}
}

// 其他检查...
return true;
}

SPI 扩展机制

Sentinel 提供了 SPI 机制来扩展功能。

自定义 Slot

可以通过 SPI 添加自定义的 Slot:

public class CustomSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode param, int count, boolean prioritized, Object... args) {
// 自定义逻辑
System.out.println("CustomSlot entry: " + resourceWrapper.getName());

fireEntry(context, resourceWrapper, param, count, prioritized, args);
}

@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
// 自定义逻辑
System.out.println("CustomSlot exit: " + resourceWrapper.getName());

fireExit(context, resourceWrapper, count, args);
}
}

注册自定义 SlotChainBuilder

public class CustomSlotChainBuilder implements SlotChainBuilder {

@Override
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();

// 添加自定义 Slot
chain.addLast(new CustomSlot());

// 添加默认 Slot
chain.addLast(new NodeSelectorSlot());
chain.addLast(new ClusterBuilderSlot());
chain.addLast(new StatisticSlot());
chain.addLast(new FlowSlot());
chain.addLast(new DegradeSlot());

return chain;
}
}

META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder 文件中注册:

com.example.CustomSlotChainBuilder

数据结构总结

数据结构说明作用
Context调用上下文存储当前调用链信息
Entry资源访问代表一次资源调用
DefaultNode默认节点按入口区分的资源统计
ClusterNode集群节点资源维度的聚合统计
EntranceNode入口节点调用链入口的统计
LeapArray滑动窗口实时统计数据结构

关键流程图

请求处理流程

请求进入


SphU.entry()


获取/创建 Context


获取/创建 Slot Chain


NodeSelectorSlot ─→ 构建调用链路


ClusterBuilderSlot ─→ 构建集群节点


StatisticSlot ─→ 记录统计数据


AuthoritySlot ─→ 黑白名单检查


SystemSlot ─→ 系统保护检查


FlowSlot ─→ 流量控制检查


DegradeSlot ─→ 熔断降级检查

├─→ 抛出 BlockException (被阻止)


返回 Entry (通过)


执行业务逻辑


Entry.exit()


更新统计数据

性能优化

Sentinel 在设计上做了很多性能优化:

1. 无锁设计

滑动窗口使用 AtomicReferenceArray,避免锁竞争:

if (array.compareAndSet(idx, null, window)) {
return window;
}

2. 内存复用

窗口对象复用,避免频繁 GC:

// 重置窗口而不是创建新对象
old.resetWindowTo(windowStart);

3. 延迟初始化

资源按需创建,避免启动时开销:

// 只有第一次访问时才创建 Slot Chain
ProcessorSlotChain chain = slotChainMap.get(resourceWrapper);
if (chain == null) {
chain = slotChainBuilder.build();
slotChainMap.put(resourceWrapper, chain);
}

总结

Sentinel 的核心原理可以总结为:

  1. Slot Chain 机制:通过责任链模式,将不同的功能封装到各个 Slot 中
  2. 滑动窗口统计:高性能的实时数据统计
  3. 多种限流算法:直接拒绝、Warm Up、匀速排队
  4. 熔断器模式:CLOSED → OPEN → HALF_OPEN 状态转换
  5. SPI 扩展:灵活的扩展机制

理解这些核心原理,可以帮助你更好地使用 Sentinel,也能在遇到问题时快速定位和解决。

下一步