sentinel集群流控实践

嵌入模式

代码示例

示例可以参见sentinel-demo-cluster-embedded

代码说明

通过spi初始化

在resources文件夹下创建META-INF/services文件夹,然后创建一个叫做com.alibaba.csp.sentinel.init.InitFunc的文件,在文件中指名实现InitFunc接口的类全路径,比如com.alibaba.csp.sentinel.demo.cluster.init.DemoClusterInitFunc

InitFunc实现类

initDynamicRuleProperty 初始化动态规则

这个主要目的是通过动态数据源的方式配置sentinel的流量控制和热点参数限流的规则。flowDataId和paramDataId的命名是appB-flow-rules和appB-param-rules(这个需要和registerClusterRuleSupplier的parser对应,appB看作是namespace)
代码

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());
 }

动态配置
打开nacos的控制台,新建一个配置,group填SENTINEL_GROUP, dataId填写appB-flow-rules
,配置内容选择json格式,示例如下。其实就是上面代码的FlowRule的各项属性值写成json格式,因为解析的是List<FlowRule>,所以配置是个json数组

[
    {
        "resource" : "resource-1", //资源名 这里只是为了注释实际json格式不允许写注释
        "grade" : 1, //具体见RuleConstant 线程数限流0,qps限流1,默认1
        "count" : 3, //限流阀值                  
        "clusterMode" :  true, //是否是集群模式
        "clusterConfig" : { //下面是集群模式的配置 见ClusterRuleConstant
            "flowId" : 112, //规则id 唯一,github官网wiki建议管控端生成或者db生成
            "thresholdType" : 1,//0是单机均摊,1是全局阀值
            "fallbackToLocalWhenFail" : true //如果没引入依赖或者通信失败退化为单机流控
        }
    }
]

initClientConfigProperty

代码

private void initClientConfigProperty() {
    ReadableDataSource<String, ClusterClientConfig> clientConfigDs = new NacosDataSource<>(remoteAddress, groupId,
        configDataId, source -> JSON.parseObject(source, new TypeReference<ClusterClientConfig>() {}));
    ClusterClientConfigManager.registerClientConfigProperty(clientConfigDs.getProperty());
}

动态配置
打开nacos的控制台,新建一个配置,group填SENTINEL_GROUP, dataId填写appB-cluster-client-config
,配置内容选择json格式,示例如下。对应代码的ClusterClientConfig

{
    "requestTimeout": 20
}

initClientServerAssignProperty

代码

private void initClientServerAssignProperty() {
    ReadableDataSource<String, ClusterClientAssignConfig> clientAssignDs = new NacosDataSource<>(remoteAddress, groupId,
        clusterMapDataId, source -> {
        List<ClusterGroupEntity> groupList = JSON.parseObject(source, new TypeReference<List<ClusterGroupEntity>>() {});
        return Optional.ofNullable(groupList)
            .flatMap(this::extractClientAssignment)
            .orElse(null);
    });
    ClusterClientConfigManager.registerServerAssignProperty(clientAssignDs.getProperty());
}

动态配置
打开nacos的控制台,新建一个配置,group填SENTINEL_GROUP, dataId填写appB-cluster-map
,配置内容选择json格式,示例如下。对应代码的ClusterGroupEntity

[
    {
        "clientSet": [
            "10.32.35.18@8729",
            "10.32.35.18@8727"
        ],
        "ip": "10.32.35.18",
        "machineId": "10.32.35.18@8728",
        "port": 18730 //这个是token通信端口,和csp.sentinel.api.port区分开
    }
]

registerClusterRuleSupplier

代码

private void registerClusterRuleSupplier() {
    // Register cluster flow rule property supplier which creates data source by namespace.
    // Flow rule dataId format: ${namespace}-flow-rules
    ClusterFlowRuleManager.setPropertySupplier(namespace -> {
        ReadableDataSource<String, List<FlowRule>> ds = new NacosDataSource<>(remoteAddress, groupId,
            namespace + DemoConstants.FLOW_POSTFIX, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
        return ds.getProperty();
    });
        
    // Register cluster parameter flow rule property supplier which creates data source by namespace.
    ClusterParamFlowRuleManager.setPropertySupplier(namespace -> {
        ReadableDataSource<String, List<ParamFlowRule>> ds = new NacosDataSource<>(remoteAddress, groupId,
             namespace + DemoConstants.PARAM_FLOW_POSTFIX, source -> JSON.parseObject(source, new TypeReference<List<ParamFlowRule>>() {}));
        return ds.getProperty();
    });
}

动态配置
具体的json配置和之前的appB-flow-rules一样,这个是集群的流控规则,比如限制整个集群的流控阀值是30qps。比如之前的叫做appB-flow-rules,那么namespace是appB, 启动的时候需要添加-Dproject.name=appB

initServerTransportConfigProperty

代码

private void initServerTransportConfigProperty() {
    ReadableDataSource<String, ServerTransportConfig> serverTransportDs = new NacosDataSource<>(remoteAddress, groupId,
        clusterMapDataId, source -> {
        List<ClusterGroupEntity> groupList = JSON.parseObject(source, new TypeReference<List<ClusterGroupEntity>>() {});
        return Optional.ofNullable(groupList)
            .flatMap(this::extractServerTransportConfig)
            .orElse(null);
    });
   ClusterServerConfigManager.registerServerTransportProperty(serverTransportDs.getProperty());
}

**
动态配置
具体的json配置同之前的appB-cluster-map一样

initStateProperty

代码

private void initStateProperty() {
    ReadableDataSource<String, Integer> clusterModeDs = new NacosDataSource<>(remoteAddress, groupId,
        clusterMapDataId, source -> {
        List<ClusterGroupEntity> groupList = JSON.parseObject(source, new TypeReference<List<ClusterGroupEntity>>() {});
        return Optional.ofNullable(groupList)
            .map(this::extractMode)
            .orElse(ClusterStateManager.CLUSTER_NOT_STARTED);
    });
    ClusterStateManager.registerProperty(clusterModeDs.getProperty());
}

动态配置
具体的json配置同之前的appB-cluster-map一样

启动参数

  • idea启动三个示例,可以通过copy configuration快捷键快速复制配置,添加vm参数

第一个实例

-Dproject.name=appB
-Dserver.port=8081
-Dcsp.sentinel.dashboard.server=127.0.0.1:8080
-Dcsp.sentinel.api.port=8727
-Dcsp.sentinel.log.use.pid=true

第二个实例

-Dproject.name=appB
-Dserver.port=8082
-Dcsp.sentinel.dashboard.server=127.0.0.1:8080
-Dcsp.sentinel.api.port=8728
-Dcsp.sentinel.log.use.pid=true

第三个实例

-Dproject.name=appB
-Dserver.port=8083
-Dcsp.sentinel.dashboard.server=127.0.0.1:8080
-Dcsp.sentinel.api.port=8729
-Dcsp.sentinel.log.use.pid=true

实验1 通过配置动态调整

配置内容见initFunc实现类中具体的json说明,分别是appB-flow-rules,appB-cluster-map,appB-cluster-client-config

  1. 首先登陆sentinel控制台,会发现没有看到对应的应用,主要原因是埋点没有触发
  2. 分别请求localhost:8081/hello/zihao,localhost:8082/hello/zihao,localhost:8083/zihao
  3. 然后重新刷新sentinel控制台,在机器列表可以看到对应的机器实例
截屏2020-12-31 下午11.11.45.png
截屏2020-12-31 下午11.11.45.png
  1. 在流控规则那里,可以看到自己配置的流控规则

这里对原先的代码示例DemoService的@SentinelResource注解指定了resource为resource-1

@SentinelResource(value = "resource-1",blockHandler = "sayHelloBlockHandler")
public String sayHello(String name) {
    return "Hello, " + name;
}
截屏2020-12-31 下午11.13.57.png
截屏2020-12-31 下午11.13.57.png
  1. 在集群流控那里,现在可能是空的,原因主要是因为我们的ip可能发生了变化,所以需要把ip调整为机器列表的配置,在nacos控制台把appB-cluster-map的配置调整下,然后重新发布
[
    {
        "clientSet": [
            "192.168.70.176@8729",
            "192.168.70.176@8727"
        ],
        "ip": "192.168.70.176",
        "machineId": "192.168.70.176@8728",
        "port": 18730 //这个端口是token server通信的端口
    }
]
  1. 然后刷新sentinel控制台,进入集群流控可以看到token server列表和token client列表
截屏2020-12-31 下午11.20.42.png
截屏2020-12-31 下午11.20.42.png

截屏2020-12-31 下午11.23.26.png
截屏2020-12-31 下午11.23.26.png
  1. 然后开始使用wrk对流量进行压力测试
  • 首先将流量阀值设置为90,然后使用wrk对两个token client节点请求
 wrk -c 5 -d 1000 -t 5 'http://localhost:8081/hello/zihao'

 wrk -c 5 -d 1000 -t 5 'http://localhost:8083/hello/zihao'
  1. 查看实时监控面板的qps情况
截屏2021-01-01 下午7.56.22.png
截屏2021-01-01 下午7.56.22.png

查看csp的metric日志输出


截屏2021-01-01 下午7.57.05.png
截屏2021-01-01 下午7.57.05.png
  1. 对内嵌的token server也开始请求
wrk -c 5 -d 1000 -t 5 'http://localhost:8082/hello/zihao'

查看metric日志可以看到单台的qps下降到30左右

截屏2021-01-01 下午8.00.20.png
截屏2021-01-01 下午8.00.20.png

相比较不请求内嵌的server token,可以发现qps波动非常大。

小结

  • 嵌入模式的统计感觉不准确
  • 特别是对嵌入的server token请求的时候,qps的值会很高,达不到目标的流控效果。所以个人建议内嵌模式的时候尽量不要server token在承担token分发外,还需要处理别的请求
  • 对于嵌入的server token自身也带来了流量压力, 不过可以设置qps阀值保护embeded token server

实验2 通过控制台指定token server和token client

首先移除原先的集群配置。
点击集群流控,点击右上角的新增token server。选择对应的token server和client,截图如下。

截屏2021-01-01 上午12.45.26.png
截屏2021-01-01 上午12.45.26.png

这种模式配置有个缺点就是不能持久化,重新启动就会失效,通过nacos配置的话就不会有这个问题。

独立模式

代码示例

示例可以参见sentinel-demo-cluster-server-alone

代码说明

服务端

注意点

sentinel-demo-cluster-server-alone示例下的ClusterServerDemo中手动指定了namespace set和server transport config,把这段代码注释掉,然后使用动态配置。如果不注释的话,不要添加cluster-server-namespace-set和cluster-server-transport-config的动态配置

public static void main(String[] args) throws Exception {
        // Not embedded mode by default (alone mode).
        ClusterTokenServer tokenServer = new SentinelDefaultTokenServer();

        /*ClusterServerConfigManager.loadGlobalTransportConfig(new ServerTransportConfig()
            .setIdleSeconds(600)
            .setPort(11111));
        ClusterServerConfigManager.loadServerNamespaceSet(Collections.singleton(DemoConstants.APP_NAME));*/

        // Start the server.
        tokenServer.start();
}

registerClusterRuleSupplier

主要目的是注册集群流控的规则,和嵌入模式的registerClusterRuleSupplier代码是一致的
代码

ClusterFlowRuleManager.setPropertySupplier(namespace -> {
    ReadableDataSource<String, List<FlowRule>> ds = new NacosDataSource<>(remoteAddress, groupId,
        namespace + DemoConstants.FLOW_POSTFIX,
        source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
    return ds.getProperty();
});
 // Register cluster parameter flow rule property supplier.
 ClusterParamFlowRuleManager.setPropertySupplier(namespace -> {
      ReadableDataSource<String, List<ParamFlowRule>> ds = new NacosDataSource<>(remoteAddress, groupId,
      namespace + DemoConstants.PARAM_FLOW_POSTFIX,
        source -> JSON.parseObject(source, new TypeReference<List<ParamFlowRule>>() {}));
     return ds.getProperty();
});

动态配置
打开nacos的控制台,新建一个配置,group填SENTINEL_GROUP, dataId填写appA-flow-rules
,配置内容选择json格式,示例如下。其实就是上面代码的FlowRule的各项属性值写成json格式,因为解析的是List<FlowRule>,所以配置是个json数组

[
    {
        "resource" : "cluster-resource",
        "grade" : 1,
        "count" : 30,                   
        "clusterMode" :  true,
        "clusterConfig" : {
            "flowId" : 111,
            "thresholdType" : 1,      
            "fallbackToLocalWhenFail" : true
        }
    }
]

_

namespace set

代码

ReadableDataSource<String, Set<String>> namespaceDs = new NacosDataSource<>(remoteAddress, groupId,
    namespaceSetDataId, source -> JSON.parseObject(source, new TypeReference<Set<String>>() {}));
ClusterServerConfigManager.registerNamespaceSetProperty(namespaceDs.getProperty());

动态配置
groupId填写SENTINEL_GROUP, dataId选择cluster-server-namespace-set

["appA"]

initServerTransportConfigProperty

代码

ReadableDataSource<String, ServerTransportConfig> transportConfigDs = new NacosDataSource<>(remoteAddress,
    groupId, serverTransportDataId,
    source -> JSON.parseObject(source, new TypeReference<ServerTransportConfig>() {}));
ClusterServerConfigManager.registerServerTransportProperty(transportConfigDs.getProperty());

动态配置
groupId填写SENTINEL_GROUP, dataId选择cluster-server-transport-config

{
    "port": 11111,
    "idleSeconds": 600
}

客户端

原先的代码示例没有client的demo,我这边仿照了embeded模式的写了一个
代码

@Override
public void init() throws Exception {
    //指定其为集群的客户端,因为不需要将其从客户端变成服务端
    ClusterStateManager.applyState(ClusterStateManager.CLUSTER_CLIENT);

    initDynamicRuleProperty();
    initClientServerAssignProperty();
    initClientConfigProperty();
}

动态配置

  • appA-flow-rules同上
  • appA-cluster-client-config
{
    "requestTimeout":20
}
  • appA-cluster-map
{
    "serverHost":"192.168.70.176", //独立的token server地址
    "serverPort":11111
}

启动参数

token server的启动参数

-Dcsp.sentinel.dashboard.server=127.0.0.1:8080
-Dcsp.sentinel.log.use.pid=true
-Dcsp.sentinel.api.port=8788
-Dproject.name=appA

token client的启动参数

-Dcsp.sentinel.dashboard.server=127.0.0.1:8080
-Dcsp.sentinel.log.use.pid=true
-Dcsp.sentinel.api.port=8721
-Dproject.name=appA

-Dcsp.sentinel.dashboard.server=127.0.0.1:8080
-Dcsp.sentinel.log.use.pid=true
-Dcsp.sentinel.api.port=8722
-Dproject.name=appA

-Dcsp.sentinel.dashboard.server=127.0.0.1:8080
-Dcsp.sentinel.log.use.pid=true
-Dcsp.sentinel.api.port=8723
-Dproject.name=appA

实验1 通过配置动态调整

配置内容见之前的说明,配置分别是appA-flow-rules,appA-cluster-map,appA-cluster-client-config,cluster-server-transport-config,
cluster-namespace-set

  1. 启动sentinel控制台,查看机器列表
截屏2021-01-01 下午3.28.34.png
截屏2021-01-01 下午3.28.34.png

前面三个是token client, 最后一个是token server

  1. 点击实时监控,查看qps情况,现在设置的qps是30
截屏2021-01-01 下午3.40.16.png
截屏2021-01-01 下午3.40.16.png

查看csp的其中一台日志看到qps是10,因为总的qps设置的是30,每台是10


截屏2021-01-01 下午3.41.22.png
截屏2021-01-01 下午3.41.22.png
  1. 然后在nacos控制台,将appA-flow-rules的总qps设置为90,可以发现sentinel控制台和日志都变为90了
截屏2021-01-01 下午7.25.43.png
截屏2021-01-01 下午7.25.43.png

截屏2021-01-01 下午7.26.10.png
截屏2021-01-01 下午7.26.10.png

小结

  1. 独立模式下,集群每台服务器的qps分布均匀,总的qps调控也均匀
  2. token server目前是单节点,所以存在高可用的问题,所以生产情况下需要使用token server集群,或者可以发生故障的时候转移token server。
  3. 对于集群流控,个人看法是有一定复杂度的,不建议在业务系统使用集群流控,集群流控可以在网关层做,业务层的话可以使用单机流控相对来说简单好上手。

参考资料

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容