![图片[1]-Sentinel 集群限流深度解析(上):架构设计与配置实战 - 速优课-速优课](https://www.suyouke.com/wp-content/uploads/2026/08/cover-2115.png)
本文导读
在前面的文章中,我们学习的都是单机限流。但在微服务架构下,一个服务通常会部署多个节点。由于负载均衡算法的不确定性,流量不可能均匀地分发到每个节点上,这就导致单机限流存在一个痛点:
总流量还没到阈值,有些节点就已经开始限流了。
举个例子:
- 服务 A 部署了 3 个节点
- 单机限流阈值配置为 200 QPS
- 理想情况下集群总阈值是 600 QPS
- 但实际可能某个节点先到 200 QPS 开始限流,而其他节点只有 100 QPS
- 此时集群总 QPS 只有 400,远低于预期的 600
为了解决这个问题,Sentinel 从 1.4.0 版本开始引入了集群限流功能,可以精确控制整个集群的总 QPS。
本文作为集群限流系列的上篇,将从以下几个方面展开:
- 集群限流的整体架构:客户端、服务端、通信协议
- 服务端的两种部署模式:嵌入模式 vs 独立模式
- 集群限流规则配置:阈值类型、回退机制
- 客户端与服务端配置实战:从零开始搭建集群限流
- 动态配置与角色切换:如何动态调整节点角色
通过本文的学习,你将全面了解 Sentinel 集群限流的架构设计,并能够动手搭建一套完整的集群限流环境。
一、集群限流整体架构
1.1 为什么需要集群限流
在分布式环境下,单机限流存在以下问题:
| 问题 | 说明 |
| 流量不均 | 负载均衡无法保证绝对均匀,导致部分节点先限流 |
| 阈值不准 | 无法精确控制整个集群的总流量 |
| 资源浪费 | 有些节点限流了,有些节点还很空闲 |
集群限流的核心目标就是:精确控制整个集群的总 QPS,而不是每个节点各自为战。
1.2 模块划分
Sentinel 集群限流的代码在 sentinel-cluster 目录下,包含以下几个核心模块:
| 模块 | 作用 |
| sentinel-cluster-common-default | 公共模块,定义通信协议、编解码接口、请求响应实体 |
| sentinel-cluster-client-default | 客户端模块,基于 Netty 实现通信,支持自动重连 |
| sentinel-cluster-server-default | 服务端模块,基于 Netty 实现通信,提供 TokenService 扩展接口 |
设计亮点:
- 通信协议与底层框架解耦:公共模块只定义接口,不依赖具体的通信框架
- 目前默认实现基于 Netty,但理论上可以替换为其他通信框架
1.3 单机限流 vs 集群限流
我们先回顾一下单机限流的工作流程:
FlowSlot作为流量切入点,调用FlowRuleChecker#checkFlow判断是否限流FlowRuleChecker根据资源名获取限流规则,遍历规则- 根据规则的
clusterMode决定走本地限流还是集群限流 - 本地限流则调用流量效果控制器判断是否拒绝
集群限流就是在第 3 步做文章:当 clusterMode = true 时,不是本地判断,而是向集群限流服务端发起远程调用,由服务端统一判断。
整体流程如下图所示:
![图片[2]-Sentinel 集群限流深度解析(上):架构设计与配置实战 - 速优课-速优课](https://www.suyouke.com/wp-content/uploads/2026/08/9d4a3bd0-ed3c-11ea-b07d-070a5bf07ad2.png)
核心思想:
- 客户端发起 Token 请求
- 服务端统一计算,判断是否有剩余配额
- 服务端返回结果(允许通过 / 拒绝)
- 客户端根据结果决定是否放行
类比一下:单机限流就像每个人自己管自己的钱,花完了就不能花了;集群限流就像大家共用一个钱包,统一管理总预算。
二、集群限流服务端的两种模式
Sentinel 支持两种模式启动集群限流服务端:嵌入模式(Embedded) 和 独立模式(Alone)。
2.1 嵌入模式(Embedded)
嵌入模式下,集群限流服务端作为应用的内置服务,和应用在同一个进程中启动。可以动态挑选集群中的一个节点作为服务端。
![图片[3]-Sentinel 集群限流深度解析(上):架构设计与配置实战 - 速优课-速优课](https://www.suyouke.com/wp-content/uploads/2026/08/df52aee0-ed3c-11ea-be9c-f7616f01fc23.png)
优点:
- 无需单独部署,运维成本低
- 可动态切换服务端节点
缺点:
- 作为服务端的节点需要处理所有客户端的限流请求,会影响应用自身性能
- 没有主从自动切换功能,服务端挂了就只能退回本地限流
适用场景:
- 单个微服务内部实现集群限流
- 对性能要求不高的场景
- 服务节点数量不多的场景
2.2 独立模式(Alone)
独立模式下,集群限流服务端作为一个独立的应用部署,和业务应用完全隔离。
![图片[4]-Sentinel 集群限流深度解析(上):架构设计与配置实战 - 速优课-速优课](https://www.suyouke.com/wp-content/uploads/2026/08/c4e787b0-ed3c-11ea-ac04-0516e16318b7.png)
优点:
- 与业务应用隔离,不影响应用性能
- 可以为所有服务提供统一的集群限流服务
缺点:
- 需要单独部署,运维成本稍高
- 需要考虑服务端的高可用
适用场景:
- 多个服务都需要集群限流
- 对性能要求较高的场景
- 生产环境推荐使用
2.3 性能与可用性
性能方面:
- 客户端与服务端之间保持一个长连接
- 底层基于 Netty 实现,自定义通信协议
- 数据包设计得足够小,网络 IO 影响很低
- 服务端处理请求都是内存操作,计算量小,响应快
可以类比 Redis 的一次 hget 操作对应用性能的影响,集群限流的性能开销是类似的。
可用性方面:
- Sentinel 集群限流对服务端可用性要求不高
- 服务端挂掉时,客户端可以自动回退为本地限流
- 嵌入模式没有主从自动切换,服务端挂了客户端不能自动选举新的服务端
选择哪种模式,更多是考虑:嵌入模式是否会严重影响应用性能,以及业务是否严重依赖集群限流的精确性。
2.4 Kubernetes 环境下的考量
如果服务部署在 Kubernetes 集群上,使用嵌入模式会有很多问题:
- Pod IP 是动态变化的,静态配置服务端地址不现实
- 节点挂了重启后 IP 会变,客户端需要重新配置
- 半夜服务端节点挂了,新起的 Pod 是客户端角色,所有客户端都连不上
- 只能退回本地限流,失去了集群限流的意义
在 Kubernetes 环境下,更推荐使用独立模式,或者基于第三方组件(如 Redis)实现集群限流。
三、集群限流规则详解
3.1 规则配置
集群限流规则本质上还是 FlowRule,只是多了集群相关的配置。当 FlowRule 的 clusterMode 为 true 时,这就是一个集群限流规则。
集群限流的具体配置在 clusterConfig 字段中,类型为 ClusterFlowConfig:
public class ClusterFlowConfig {
private Long flowId;
private int thresholdType = ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL;
private boolean fallbackToLocalWhenFail = true;
// 当前版本未使用
private int strategy = ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL;
private int sampleCount = ClusterRuleConstant.DEFAULT_CLUSTER_SAMPLE_COUNT;
private int windowIntervalMs = RuleConstant.DEFAULT_WINDOW_INTERVAL_MS;
}
各字段说明:
| 字段 | 说明 |
| flowId | 集群限流规则的全局唯一 ID |
| thresholdType | 集群限流阈值类型(见下文) |
| fallbackToLocalWhenFail | 失败时是否回退为本地限流,默认 true |
| sampleCount | 滑动窗口的窗口数量 |
| windowIntervalMs | 滑动窗口的总时长(毫秒) |
滑动窗口的参数和单机限流类似:windowIntervalMs / sampleCount = 每个小窗口的时间长度。
3.2 两种阈值类型
当规则配置为集群模式时,限流规则的 grade(阈值类型)不再使用,改用 ClusterFlowConfig 的 thresholdType。
支持两种阈值类型:
| 阈值类型 | 常量名 | 说明 |
| 单机均摊 | FLOW_THRESHOLD_AVG_LOCAL |
集群总阈值 = 客户端节点数 × 规则配置的 count |
| 集群总阈值 | FLOW_THRESHOLD_GLOBAL |
规则配置的 count 就是整个集群的总阈值 |
举例说明:
假设规则配置的 count = 200,有 3 个客户端节点连接到服务端:
- 单机均摊模式:集群总阈值 = 3 × 200 = 600 QPS
- 集群总阈值模式:集群总阈值 = 200 QPS(所有节点加起来 200)
3.3 失败回退机制
fallbackToLocalWhenFail 参数控制当集群限流请求失败时的行为:
- true:失败时回退为本地限流(默认值,推荐)
- false:失败时直接放行(相当于没有限流,风险较高)
建议保持默认值 true,这样即使服务端挂了,也有单机限流作为兜底,保证系统安全。
四、集群限流的动态配置
4.1 规则配置的一致性
集群限流有一个重要原则:客户端和服务端必须使用同一份规则配置。
- 客户端需要规则来判断是否走集群限流模式
- 服务端需要规则来计算和分配 Token
- 两边规则不一致会导致各种奇怪的问题
为了保证一致性,最好的方式就是使用动态配置,让客户端和服务端从同一个数据源读取规则。
Sentinel 支持使用名称空间(namespace) 来隔离不同应用的集群限流规则。比如服务 A 和服务 B 的规则使用不同的 namespace 互相隔离。
4.2 简单动态数据源示例
为了便于理解和测试,我们来实现一个简单的动态数据源 SimpleLocalDataSource。
它继承 AbstractDataSource,构造方法传入 namespace,用于指定只加载某个名称空间的规则。
public class SimpleLocalDataSource
extends AbstractDataSource<String, List<FlowRule>>
implements Runnable {
public SimpleLocalDataSource(String namespace) {
super(new SimpleConverter<String, List<FlowRule>>() {});
// 模拟延迟加载规则(等注册完成后再加载)
new Thread(this).start();
}
@Override
public String readSource() throws Exception {
// 实际项目中从配置中心读取
return "";
}
@Override
public void close() throws Exception {
}
@Override
public void run() {
try {
// 休眠 6 秒,等注册完成后再模拟加载一次规则
Thread.sleep(6000);
getProperty().updateValue(loadConfig());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Converter 中构造一个测试用的集群限流规则:
public class SimpleConverter implements Converter<String, List<FlowRule>> {
@Override
public List<FlowRule> convert(String source) {
List<FlowRule> flowRules = new ArrayList<>();
FlowRule flowRule = new FlowRule();
flowRule.setCount(200);
flowRule.setResource("GET:/hello");
// 开启集群限流模式
flowRule.setClusterMode(true);
ClusterFlowConfig clusterFlowConfig = new ClusterFlowConfig();
clusterFlowConfig.setFlowId(10000L); // 全局唯一 ID
flowRule.setClusterConfig(clusterFlowConfig);
flowRules.add(flowRule);
return flowRules;
}
}
注意:这只是测试用例。实际项目中,动态数据源可以通过定时拉取配置中心、或监听配置变更事件来实现。
五、集群限流客户端配置
5.1 添加依赖
在需要使用集群限流的微服务项目中添加客户端依赖:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-cluster-client-default</artifactId>
<version>${sentinel.version}</version>
</dependency>
5.2 设置角色和基础配置
指定当前节点身份为集群限流客户端,并配置客户端参数:
@SpringBootApplication
public class WebMvcDemoApplication {
static {
// 指定当前身份为 Token Client(集群限流客户端)
ClusterStateManager.applyState(ClusterStateManager.CLUSTER_CLIENT);
// 集群限流客户端配置
ClusterClientConfig clientConfig = new ClusterClientConfig();
clientConfig.setRequestTimeout(1000); // 请求超时时间 1 秒
ClusterClientConfigManager.applyNewConfig(clientConfig);
}
}
5.3 注册动态数据源
在 Spring 容器启动完成后,创建动态数据源并注册到 FlowRuleManager:
@SpringBootApplication
public class WebMvcDemoApplication implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 指定名称空间为 serviceA,只加载这个 namespace 下的规则
SimpleLocalDataSource ruleSource = new SimpleLocalDataSource("serviceA");
FlowRuleManager.register2Property(ruleSource.getProperty());
}
}
5.4 配置服务端连接信息
最后,配置服务端的连接信息,让客户端知道去哪里连接服务端:
@SpringBootApplication
public class WebMvcDemoApplication {
static {
// 配置服务端地址
ClusterClientAssignConfig assignConfig = new ClusterClientAssignConfig();
assignConfig.setServerHost("127.0.0.1");
assignConfig.setServerPort(11111);
// 注册名称空间提供者(非常重要!)
ConfigSupplierRegistry.setNamespaceSupplier(() -> "serviceA");
// 应用配置,会触发连接建立
ClusterClientConfigManager.applyNewAssignConfig(assignConfig);
}
}
关键点解读:
- 调用
applyNewAssignConfig会触发连接建立:不需要手动启动客户端,配置变更时会自动初始化或重新连接 - 支持自动重连:客户端与服务端意外断开时,会自动重试重连
- namespace 必须在连接前设置:客户端连上服务端时会立即发一个 PING 消息,namespace 就携带在 PING 数据包中,服务端以此识别每个客户端属于哪个应用
namespace 的作用:服务端根据 namespace 找到对应的规则配置,实现多应用共享一个集群限流服务端。
六、集群限流服务端配置
6.1 添加依赖
- 嵌入模式:直接在微服务项目中添加服务端依赖
- 独立模式:单独创建一个项目,添加服务端依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-cluster-server-default</artifactId>
<version>${sentinel.version}</version>
</dependency>
6.2 启动服务端(独立模式)
独立模式下,需要手动创建并启动 ClusterTokenServer:
public class ClusterServerDemo {
public static void main(String[] args) throws Exception {
ClusterTokenServer tokenServer = new SentinelDefaultTokenServer();
// 配置服务端传输层参数
ClusterServerConfigManager.loadGlobalTransportConfig(
new ServerTransportConfig()
.setIdleSeconds(600) // 连接最大空闲时间 600 秒
.setPort(11111) // 监听端口 11111
);
// 启动服务
tokenServer.start();
}
}
6.3 注册规则动态数据源
服务端也需要加载限流规则,但方式和客户端不同。服务端通过 ClusterFlowRuleManager.setPropertySupplier 注册一个提供者函数:
ClusterFlowRuleManager.setPropertySupplier(new Function<String, SentinelProperty<List<FlowRule>>>() {
// ClusterFlowRuleManager 会给 apply 方法返回的 SentinelProperty 注册监听器
@Override
public SentinelProperty<List<FlowRule>> apply(String namespace) {
// 根据 namespace 创建对应的动态数据源
SimpleLocalDataSource source = new SimpleLocalDataSource(namespace);
// 返回数据源的 SentinelProperty
return source.getProperty();
}
});
这里注册的是一个 Java 8 的 Function 函数:
- 输入:namespace(名称空间)
- 输出:该 namespace 对应的
SentinelProperty
当注册 namespace 时,会触发 apply 方法创建对应的动态数据源。
6.4 注册名称空间
最后,为服务端注册名称空间,触发动态数据源的创建:
// 注册名称空间,多个应用对应多个名称空间
ClusterServerConfigManager.loadServerNamespaceSet(Collections.singleton("serviceA"));
工作流程:
- 注册 namespace “serviceA”
- 触发
setPropertySupplier注册的 Function - 创建 “serviceA” 对应的
SimpleLocalDataSource ClusterFlowRuleManager给数据源的SentinelProperty注册监听器- 延迟 6 秒后,
SimpleLocalDataSource加载规则并通知更新
如果有多个 namespace,就会多次调用 Function 的 apply 方法,为每个 namespace 创建独立的数据源。应用之间通过 namespace 完全隔离。
七、动态配置与角色切换
7.1 嵌入模式的痛点
如果使用嵌入模式,并且节点 IP 是动态变化的(比如 Kubernetes 环境),静态配置就行不通了。我们需要动态调整节点角色和连接配置。
Sentinel 提供了 HTTP API 来动态修改这些配置。
7.2 动态修改节点角色
HTTP API 格式:
http://<节点IP>:<节点Port>/setClusterMode?mode={state}
其中 state 的取值:
- 0:集群限流客户端(CLUSTER_CLIENT)
- 1:集群限流服务端(CLUSTER_SERVER)
当一个新节点被选为服务端后:
- 旧的服务端节点应该切换为客户端角色
- 其他所有客户端节点都需要更新连接配置,指向新的服务端
7.3 动态修改客户端配置
HTTP API 格式:
http://<节点IP>:<节点Port>/cluster/client/modifyConfig?data={body}
body 是 JSON 格式的字符串,支持的参数:
| 参数 | 说明 |
| serverHost | 集群限流服务端的 IP 地址 |
| serverPort | 集群限流服务端的端口 |
| requestTimeout | 请求超时时间 |
7.4 动态数据源方式
除了 HTTP API,Sentinel 还支持通过动态数据源方式修改配置。这也是更推荐的方式。
支持动态配置的项包括:
| 配置项 | 说明 | 动态变更效果 |
ClusterClientAssignConfig |
客户端连接服务端的配置 | 重新创建客户端连接 |
ServerTransportConfig |
服务端传输层配置(端口、空闲时间) | 重启服务端 |
ClusterClientConfig |
客户端配置(请求超时) | 动态生效 |
ClusterState |
节点状态(客户端/服务端) | 切换角色,关闭旧连接/启动新服务 |
嵌入模式下角色切换的完整流程:
- 动态配置改变了某个节点的 ClusterState(从客户端变为服务端)
- 该节点关闭客户端连接,启动服务端监听
- 其他节点监听到配置变更,更新服务端地址
- 其他节点重新连接到新的服务端
虽然不能自动选举主节点,但 Sentinel 通过动态数据源 + 观察者模式提供了灵活的切换能力。你可以基于这个能力自己实现自动选举逻辑。
总结与思考
本文作为集群限流系列的上篇,详细介绍了 Sentinel 集群限流的架构设计、部署模式、规则配置和使用方式。
核心要点回顾
1. 架构设计
- 客户端-服务端模式,基于 Netty 通信
- 公共模块定义协议,与具体通信框架解耦
- Token 请求模式:客户端申请 Token,服务端统一分配
2. 两种部署模式
| 模式 | 优点 | 缺点 | 适用场景 |
| 嵌入模式 | 无需单独部署 | 影响应用性能,无自动切换 | 单服务内、小规模 |
| 独立模式 | 性能隔离,统一服务 | 需要额外部署 | 多服务、生产环境 |
3. 两种阈值类型
- 单机均摊:总阈值 = 节点数 × 单机阈值
- 集群总阈值:配置值就是整个集群的总阈值
4. 配置三要素
- 客户端需要:规则配置 + 服务端地址 + namespace
- 服务端需要:规则配置 + namespace 注册 + 端口配置
- 规则必须一致,推荐使用动态配置
5. 失败回退
- 服务端挂了自动回退为本地限流(默认开启)
- 保证系统可用性,不因为限流组件故障导致服务不可用
实践建议
- 生产环境推荐独立模式:避免影响业务应用性能
- 务必开启失败回退:
fallbackToLocalWhenFail = true - 使用动态配置管理规则:保证客户端和服务端规则一致
- 合理设置超时时间:避免集群限流成为性能瓶颈
- K8s 环境谨慎使用嵌入模式:Pod 动态变化会导致配置管理困难
下篇文章我们将深入集群限流的源码,剖析客户端和服务端的实现细节,包括通信协议、请求处理流程、Token 分配算法等,敬请期待。












请登录后查看评论内容