核心原理深入
本章深入探讨 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 会:
- 创建或获取资源的 Slot Chain
- 依次执行各个 Slot 的逻辑
- 如果某个 Slot 判定需要阻止请求,抛出 BlockException
- 如果所有 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);
}
}
}
限流算法:
- 直接拒绝:简单地判断 QPS 是否超过阈值
public boolean canPass(FlowRule rule, Context context, DefaultNode node, int count) {
int currentQps = node.passQps();
return currentQps + count <= rule.getCount();
}
- 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();
}
- 匀速排队:基于漏桶算法,严格控制请求间隔
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);
}
}
熔断策略:
-
慢调用比例:统计慢调用比例,超过阈值则熔断
-
异常比例:统计异常比例,超过阈值则熔断
-
异常数:统计异常数量,超过阈值则熔断
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 的核心原理可以总结为:
- Slot Chain 机制:通过责任链模式,将不同的功能封装到各个 Slot 中
- 滑动窗口统计:高性能的实时数据统计
- 多种限流算法:直接拒绝、Warm Up、匀速排队
- 熔断器模式:CLOSED → OPEN → HALF_OPEN 状态转换
- SPI 扩展:灵活的扩展机制
理解这些核心原理,可以帮助你更好地使用 Sentinel,也能在遇到问题时快速定位和解决。
下一步
- 速查表 - 查阅常用配置和命令