说明:本文源码分析及测试使用的Sentinel版本为1.7.2,Nacos版本为1.3.1
简介
主要流程
Sentinel从1.4.0 开始引入了集群流控模块,用于控制应用集群内某个 API 的总 QPS 。
在Sentinel集群流控中,主要有两种身份:Token Client和Token Server,实现集群流控的流程就是应用实例(Token Client)向Token Server请求令牌,TokenServer根据集群流控规则来控制总体的QPS。
这个过程可以从Sentinel核心流控组件的FlowRuleChecker
类的源码中体现:
public boolean canPassCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount, boolean prioritized) {
// 1.判断流控规则是否为集群模式
if (rule.isClusterMode()) {
return passClusterCheck(rule, context, node, acquireCount, prioritized);
}
// #使用默认单机流控
return passLocalCheck(rule, context, node, acquireCount, prioritized);
}
private static boolean passClusterCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount, boolean prioritized) {
try {
// 2.获取所属的TokenServer信息
TokenService clusterService = pickClusterService();
if (clusterService == null) {
// #退化为单机模式
return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized);
}
long flowId = rule.getClusterConfig().getFlowId();
// 3.向TokenServer发起请求
TokenResult result = clusterService.requestToken(flowId, acquireCount, prioritized);
// 4.根据响应判断此次流控的最终结果
return applyTokenResult(result, rule, context, node, acquireCount, prioritized);
} catch (Throwable ex) {
RecordLog.warn("[FlowRuleChecker] Request cluster token unexpected failed", ex);
}
// #退化为单机模式
return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized);
}
这段代码是在Sentinel拦截请求,经由FlowSlot
调用FlowRuleCheck.checkFlow()
方法,并且判断当前内存中有流控规则后的逻辑,可以看出其实就是两部分:
- 流控规则是否为集群模式
- 向所属的TokenServer请求相应的流量令牌
因此要实现集群流控有两个关键配置:
- 集群模式的资源流控规则
- TokenServer及TokenClient的分配规则
部署
另外SentinelCluster集群的部署方式有两种:
- 嵌入模式(embedded):在应用集群内选择某一个实例作为TokenServer
- 独立模式(server alone):在应用集群外独立部署一个TokenServer服务
根据部署方式的不同,应用实例需要分别引用client或server模块:
sentinel-cluster-client-default
: 默认集群流控 client 模块sentinel-cluster-server-default
: 默认集群流控 server 模块,提供扩展接口对接规则判断的具体实现(TokenService
),默认实现是复用sentinel-core
的相关逻辑
下面将通过Sentinel的官方示例,来了解下嵌入模式集群流控的实现方式。
官方示例解析
SentinelCluster嵌入模式官方示例:github:Sentinel Cluster Demo(Embedded Mode)
针对这个官方示例的代码进行讲解一下:
其核心就是DemoClusterInitFunc.java
类,这个类就是将一系列的配置项绑定到Nacos数据源上,实现动态配置监听:
@Override
public void init() throws Exception {
// (1)流控规则配置
initDynamicRuleProperty();
// (2)Token client请求配置
initClientConfigProperty();
// (3)Token client分配规则配置
initClientServerAssignProperty();
// (4)集群模式的流控规则配置
registerClusterRuleSupplier();
// (5)Token Server端的分配规则配置
initServerTransportConfigProperty();
// (6)应用身份状态配置(Client/Server)
initStateProperty();
}
这6个步骤内部逻辑相似,功能就是监听Nacos配置,配置更新时,将其更新到实例中的配置Manager的静态属性中,用于流控判断。以(1)为例:
private void initDynamicRuleProperty() {
// 普通流控配置
ReadableDataSource<String, List<FlowRule>> ruleSource = new NacosDataSource<>(
remoteAddress, groupId, flowDataId,
source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {})
);
FlowRuleManager.register2Property(ruleSource.getProperty());
// 热点规则配置
ReadableDataSource<String, List<ParamFlowRule>> paramRuleSource = new NacosDataSource<>(
remoteAddress, groupId, paramDataId,
source -> JSON.parseObject(source, new TypeReference<List<ParamFlowRule>>() {}));
ParamFlowRuleManager.register2Property(paramRuleSource.getProperty());
}
- 监听地址为
remoteAddress
的Nacos服务,通过groupId
、dataId
找到配置文件。
dataId
有特定的拼接规则,来实现nacos配置项与sentinel配置项的对应,比如Demo中用于保存普通流控规则配置的flowDataId为APP_NAME-flow-rules
- 使用特定的对象结构解析配置文件,并将解析出来的配置项更新到特定的配置中心,比如实例中的
FlowRuleManager
,一般会保存在静态属性中用于流控判断。
整个init()方法实现了Sentinel内几乎所有的配置的动态化,整理其转换关系如下:
也就是说,后面只需要在Nacos上,使用特定的dataId及特定的对象结构,添加我们所需的规则配置,即可被应用实例监听并读取使用。
接下来,我将按照示例中的接入方式来测试一下集群流控的效果
测试
1. 准备
1.1 测试应用
-
本地启动3个名为TestWebApp的springboot实例
- 参照官方示例接入Nacos数据源:github:Sentinel Cluster Demo(Embedded Mode)
- 提供/echo/hello接口用于测试(接口直接返回"Hello"字符串)
server.port
分别设置为8081、8082、8083(用于接口请求)
-
csp.sentinel.api.port
分别设置为8721、8722、8723(用于sentinel通信) -
本地启动NacosServer及SentinelDashboard
1.2 测试工具
使用Jmeter,添加3个线程组,分别请求3个实例的/echo/hello接口,并通过定时器保证请求每个实例的QPS为10。
启动线程组后,可以通过SentinelDashboard观察应用集群的总体QPS保持在30:
2. 添加配置
这边我们参照相应的对象格式,添加两个配置,分别设定TokenServer-TokenClient的分配规则,及针对/echo/hello接口的集群模式流控规则:
- dataId=
TestWebApp-cluster-map
,将8721端口的实例作为TokenServer,其他两个实例作为下属TokenClient,通信端口设置为18730,配置内容:
[
{
"ip": "127.0.0.1",
"machineId": "127.0.0.1@8721",
"port": 18730,
"clientSet":
[
"127.0.0.1@8722",
"127.0.0.1@8723"
]
}
]
- dataId=
TestWebApp-flow-rules
,针对/echo/hello接口,设置集群QPS限制为10,配置内容:
[
{
"app": "TestWebApp",
"clusterMode": true,
"controlBehavior": 0,
"count": 10.0,
"grade": 1,
"ip": "127.0.0.1",
"limitApp": "default",
"port": 8721,
"resource": "/echo/hello",
"strategy": 0,
"clusterConfig": {
"flowId": 1,
"thresholdType": 1
}
}
]
Json中具体字段的含义,可以参考文档:Sentinel集群流控官方文档
配置发布后,打开Sentinel控制台,可以看到Nacos中的两个配置项都已同步到应用:
3. 结果观察
再次启动Jmeter测试线程组,观察SentinelDashboard,可以看到在集群流控规则的约束下,整体QPS基本被限制在10,实现了预期的效果:
总结和思考
虽然通过以上测试,Sentinel能实现集群流控的功能,但还是有很多问题需要解决:
- 通过Nacos来进行流控规则配置不够直观,需要提前编写好Json配置,容易出错。
而官方的SentinelDashboard虽然可以通过界面进行流控规则及集群分配的配置,但由于SentinelDashboard默认的配置发布方式是直接通知到相应的应用实例,储存在实例内存中,一旦应用重启就需要重新配置。
这方面可以通过改写SentinelDashboard的实现方式,将页面提交的配置发布到Nacos等配置中心,实现配置持久化。
- 目前Sentinel Cluster中的Token Server 没有实现自动管理调度(分配/选举),无法满足高可用需求。而在容器环境下,应用的ip是动态的,按照测试中的配置方式,难以管理TokenServer及TokenClient集群。
因此,要想在生产环境下使用Sentinel的集群流控功能,还需要对源码进行一定的改造,才能满足实际使用需求。
来源:oschina
链接:https://my.oschina.net/u/4284321/blog/4463382