初窥Sentinel集群流控:从源码分析到测试实现

说明:本文源码分析及测试使用的Sentinel版本为1.7.2,Nacos版本为1.3.1

参考:Sentinel集群流控官方文档

简介

主要流程

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()方法,并且判断当前内存中有流控规则后的逻辑,可以看出其实就是两部分:

  1. 流控规则是否为集群模式
  2. 向所属的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());
}
  1. 监听地址为remoteAddress的Nacos服务,通过groupIddataId找到配置文件。

dataId有特定的拼接规则,来实现nacos配置项与sentinel配置项的对应,比如Demo中用于保存普通流控规则配置的flowDataId为APP_NAME-flow-rules

  1. 使用特定的对象结构解析配置文件,并将解析出来的配置项更新到特定的配置中心,比如实例中的FlowRuleManager,一般会保存在静态属性中用于流控判断。

整个init()方法实现了Sentinel内几乎所有的配置的动态化,整理其转换关系如下:

image-20200730155009358

也就是说,后面只需要在Nacos上,使用特定的dataId及特定的对象结构,添加我们所需的规则配置,即可被应用实例监听并读取使用。

接下来,我将按照示例中的接入方式来测试一下集群流控的效果

测试

1. 准备

1.1 测试应用

  • 本地启动3个名为TestWebApp的springboot实例

  • csp.sentinel.api.port分别设置为8721、8722、8723(用于sentinel通信)

  • 本地启动NacosServer及SentinelDashboard

1.2 测试工具

使用Jmeter,添加3个线程组,分别请求3个实例的/echo/hello接口,并通过定时器保证请求每个实例的QPS为10。

启动线程组后,可以通过SentinelDashboard观察应用集群的总体QPS保持在30:

image-20200730173916430

2. 添加配置

这边我们参照相应的对象格式,添加两个配置,分别设定TokenServer-TokenClient的分配规则,及针对/echo/hello接口的集群模式流控规则:

image-20200730164321429

  1. 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"
        ]
    }
]
  1. 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中的两个配置项都已同步到应用:

  • image-20200730184139111

  • image-20200730165013157

3. 结果观察

再次启动Jmeter测试线程组,观察SentinelDashboard,可以看到在集群流控规则的约束下,整体QPS基本被限制在10,实现了预期的效果:

image-20200730181538545

总结和思考

虽然通过以上测试,Sentinel能实现集群流控的功能,但还是有很多问题需要解决:

  1. 通过Nacos来进行流控规则配置不够直观,需要提前编写好Json配置,容易出错。

而官方的SentinelDashboard虽然可以通过界面进行流控规则及集群分配的配置,但由于SentinelDashboard默认的配置发布方式是直接通知到相应的应用实例,储存在实例内存中,一旦应用重启就需要重新配置。

这方面可以通过改写SentinelDashboard的实现方式,将页面提交的配置发布到Nacos等配置中心,实现配置持久化。

可参考:Sentinel Dashboard中修改规则同步到Nacos

  1. 目前Sentinel Cluster中的Token Server 没有实现自动管理调度(分配/选举),无法满足高可用需求。而在容器环境下,应用的ip是动态的,按照测试中的配置方式,难以管理TokenServer及TokenClient集群。

因此,要想在生产环境下使用Sentinel的集群流控功能,还需要对源码进行一定的改造,才能满足实际使用需求。

猜你喜欢

转载自blog.csdn.net/wk52525/article/details/107707747