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> ruleSource = new NacosDataSource<>(remoteAddress, groupId,
        flowDataId, source -> JSON.parseObject(source, new TypeReference>() {}));
    FlowRuleManager.register2Property(ruleSource.getProperty());

    ReadableDataSource> paramRuleSource = new NacosDataSource<>(remoteAddress, groupId,
        paramDataId, source -> JSON.parseObject(source, new TypeReference>() {}));
    ParamFlowRuleManager.register2Property(paramRuleSource.getProperty());
 }

动态配置
打开nacos的控制台,新建一个配置,group填SENTINEL_GROUP, dataId填写appB-flow-rules
,配置内容选择json格式,示例如下。其实就是上面代码的FlowRule的各项属性值写成json格式,因为解析的是List,所以配置是个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 clientConfigDs = new NacosDataSource<>(remoteAddress, groupId,
        configDataId, source -> JSON.parseObject(source, new TypeReference() {}));
    ClusterClientConfigManager.registerClientConfigProperty(clientConfigDs.getProperty());
}

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

{
    "requestTimeout": 20
}

initClientServerAssignProperty

代码

private void initClientServerAssignProperty() {
    ReadableDataSource clientAssignDs = new NacosDataSource<>(remoteAddress, groupId,
        clusterMapDataId, source -> {
        List groupList = JSON.parseObject(source, new TypeReference>() {});
        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> ds = new NacosDataSource<>(remoteAddress, groupId,
            namespace + DemoConstants.FLOW_POSTFIX, source -> JSON.parseObject(source, new TypeReference>() {}));
        return ds.getProperty();
    });
        
    // Register cluster parameter flow rule property supplier which creates data source by namespace.
    ClusterParamFlowRuleManager.setPropertySupplier(namespace -> {
        ReadableDataSource> ds = new NacosDataSource<>(remoteAddress, groupId,
             namespace + DemoConstants.PARAM_FLOW_POSTFIX, source -> JSON.parseObject(source, new TypeReference>() {}));
        return ds.getProperty();
    });
}

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

initServerTransportConfigProperty

代码

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

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

initStateProperty

代码

private void initStateProperty() {
    ReadableDataSource clusterModeDs = new NacosDataSource<>(remoteAddress, groupId,
        clusterMapDataId, source -> {
        List groupList = JSON.parseObject(source, new TypeReference>() {});
        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
  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
  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.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

查看csp的metric日志输出


截屏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

相比较不请求内嵌的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

这种模式配置有个缺点就是不能持久化,重新启动就会失效,通过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> ds = new NacosDataSource<>(remoteAddress, groupId,
        namespace + DemoConstants.FLOW_POSTFIX,
        source -> JSON.parseObject(source, new TypeReference>() {}));
    return ds.getProperty();
});
 // Register cluster parameter flow rule property supplier.
 ClusterParamFlowRuleManager.setPropertySupplier(namespace -> {
      ReadableDataSource> ds = new NacosDataSource<>(remoteAddress, groupId,
      namespace + DemoConstants.PARAM_FLOW_POSTFIX,
        source -> JSON.parseObject(source, new TypeReference>() {}));
     return ds.getProperty();
});

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

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

_

namespace set

代码

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

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

["appA"]

initServerTransportConfigProperty

代码

ReadableDataSource transportConfigDs = new NacosDataSource<>(remoteAddress,
    groupId, serverTransportDataId,
    source -> JSON.parseObject(source, new TypeReference() {}));
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

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

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

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


截屏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.26.10.png

小结

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

参考资料

  • sentinel集群流控wiki
  • Sentinel实战:集群限流环境搭建

你可能感兴趣的:(sentinel集群流控实践)