Sentinel实现动态配置的集群流控的方法

2023-10-30

这篇文章主要介绍了Sentinel实现动态配置的集群流控,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

介绍

为什么要使用集群流控呢?

相对于单机流控而言,我们给每台机器设置单机限流阈值,在理想情况下整个集群的限流阈值为机器数量✖️单机阈值。不过实际情况下流量到每台机器可能会不均匀,会导致总量没有到的情况下某些机器就开始限流。因此仅靠单机维度去限制的话会无法精确地限制总体流量。而集群流控可以精确地控制整个集群的调用总量,结合单机限流兜底,可以更好地发挥流量控制的效果。

基于单机流量不均的问题以及如何设置集群整体的QPS的问题,我们需要创建一种集群限流的模式,这时候我们很自然地就想到,可以找一个 server 来专门统计总的调用量,其它的实例都与这台 server 通信来判断是否可以调用。这就是最基础的集群流控的方式。

原理

集群限流的原理很简单,和单机限流一样,都需要对 qps 等数据进行统计,区别就在于单机版是在每个实例中进行统计,而集群版是有一个专门的实例进行统计。

这个专门的用来统计数据的称为 Sentinel 的 token server,其他的实例作为 Sentinel 的 token client 会向 token server 去请求 token,如果能获取到 token,则说明当前的 qps 还未达到总的阈值,否则就说明已经达到集群的总阈值,当前实例需要被 block,如下图所示:

和单机流控相比,集群流控中共有两种身份:

  • Token Client:集群流控客户端,用于向所属 Token Server 通信请求 token。集群限流服务端会返回给客户端结果,决定是否限流。
  • Token Server:即集群流控服务端,处理来自 Token Client 的请求,根据配置的集群规则判断是否应该发放 token(是否允许通过)。

而单机流控中只有一种身份,每个 sentinel 都是一个 token server。

注意,集群限流中的 token server 是单点的,一旦 token server 挂掉,那么集群限流就会退化成单机限流的模式。

Sentinel 集群流控支持限流规则和热点规则两种规则,并支持两种形式的阈值计算方式:

  • 集群总体模式:即限制整个集群内的某个资源的总体 qps 不超过此阈值。
  • 单机均摊模式:单机均摊模式下配置的阈值等同于单机能够承受的限额,token server 会根据连接数来计算总的阈值(比如独立模式下有 3 个 client 连接到了 token server,然后配的单机均摊阈值为 10,则计算出的集群总量就为 30),按照计算出的总的阈值来进行限制。这种方式根据当前的连接数实时计算总的阈值,对于机器经常进行变更的环境非常适合。

部署方式

token server 有两种部署方式:

一种是独立部署,就是单独启动一个 token server 服务来处理 token client 的请求,如下图所示:

如果独立部署的 token server 服务挂掉的话,那其他的 token client 就会退化成本地流控的模式,也就是单机版的流控,所以这种方式的集群限流需要保证 token server 的高可用性。

一种是嵌入部署,即作为内置的 token server 与服务在同一进程中启动。在此模式下,集群中各个实例都是对等的,token server 和 client 可以随时进行转变,如下图所示:

嵌入式部署的模式中,如果 token server 服务挂掉的话,我们可以将另外一个 token client 升级为token server来,当然啦如果我们不想使用当前的 token server 的话,也可以选择另外一个 token client 来承担这个责任,并且将当前 token server 切换为 token client。Sentinel 为我们提供了一个 api 来进行 token server 与 token client 的切换:

1

http://<ip>:<port>/setClusterMode?mode=<xxx>

其中 mode 为 0 代表 client,1 代表 server,-1 代表关闭。

PS:注意应用端需要引入集群限流客户端或服务端的相应依赖。

集群限流控制台

sentinel为用户提供集群限流控制台功能,能够通过控制台配置集群的限流规则以及配置集群的Server与Client。

集群限流客户端

要想使用集群限流功能,必须引入集群限流 client 相关依赖:

1

2

3

4

5

<dependency>

    <groupId>com.alibaba.csp</groupId>

    <artifactId>sentinel-cluster-client-default</artifactId>

    <version>1.8.0</version>

</dependency>

集群限流服务端

要想使用集群限流服务端,必须引入集群限流 server 相关依赖:

1

2

3

4

5

<dependency>

    <groupId>com.alibaba.csp</groupId>

    <artifactId>sentinel-cluster-server-default</artifactId>

    <version>1.8.0</version>

</dependency>

我们结合server和client实现一个嵌入式模式。在pom中同时引入上面的两个依赖,并配置sentinel控制台地址,实现一个查询订单的接口。

pom

1

2

3

4

5

6

7

8

9

<dependency>

    <groupId>com.alibaba.csp</groupId>

    <artifactId>sentinel-cluster-server-default</artifactId>

</dependency>

<dependency>

    <groupId>com.alibaba.csp</groupId>

    <artifactId>sentinel-cluster-client-default</artifactId>

</dependency>

application.yml

1

2

3

4

5

6

7

8

9

10

11

12

server:

  port: 9091

spring:

  application:

    name: cloudalibaba-sentinel-clusterServer

  cloud:

    sentinel:

      transport:

        #配置sentinel dashboard地址

        dashboard: localhost:8080

        port: 8719 #默认8719端口

OrderController

1

2

3

4

5

6

7

8

9

10

11

12

13

@RestController

public class OrderController {

    /**

     * 查询订单

     * @return

     */

    @GetMapping("/order/{id}")

    public CommonResult<Order> getOrder(@PathVariable("id") Long id){

        Order order = new Order(id, "212121");

        return CommonResult.success(order.toString());

    }

}

代码示例如cloudalibaba-sentinel-cluster-embedded9091

修改VM options配置,启动三个不同端口的实例,即可。

1

2

3

-Dserver.port=9091 -Dproject.name=cloudalibaba-sentinel-clusterServer -Dcsp.sentinel.log.use.pid=true

-Dserver.port=9092 -Dproject.name=cloudalibaba-sentinel-clusterServer -Dcsp.sentinel.log.use.pid=true

-Dserver.port=9093 -Dproject.name=cloudalibaba-sentinel-clusterServer -Dcsp.sentinel.log.use.pid=true

控制台配置

登录sentinel的控制台,并有访问量后,我们就可以在 Sentinel上面看到集群流控,如下图所示:

点击添加Token Server。

从实例列表中选择一个作为Server端,其他作为Client端,并选中到右侧Client列表,配置token sever端的最大允许的QPS,用于对 Token Server 的资源使用进行限制,防止在嵌入模式下影响应用本身。

配置完成之后的Token Server列表,如下图所示

使用控制台配置token Server、token Client以及限流规则,有很多的缺点:

1、限流规则,不能持久化,应用重启之后,规则丢失。

2、token Server 、token Client配置也会丢失。

官方推荐给集群限流服务端注册动态配置源来动态地进行配置。我们使用nacos作为配置中心,动态配置客户端与服务端属性以及限流规则,实现动态集群限流。

sentinel结合nacos实现集群限流

我们使用Nacos对cloudalibaba-sentinel-cluster-embedded9091进行改造,实现动态配置源来动态进行配置。

配置源注册的相关逻辑可以置于 InitFunc 实现类中,并通过 SPI 注册,在 Sentinel 初始化时即可自动进行配置源加载监听。

嵌入模式部署

添加ClusterInitFunc类

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

public class ClusterInitFunc implements InitFunc {

    //应用名称

    private static final String APP_NAME = AppNameUtil.getAppName();

    //nacos集群地址

    private final String remoteAddress = "localhost:8848";

    //nacos配置的分组名称

    private final String groupId = "SENTINEL_GROUP";

    //配置的dataId

    private final String flowDataId = APP_NAME + Constants.FLOW_POSTFIX;

    private final String paramDataId = APP_NAME + Constants.PARAM_FLOW_POSTFIX;

    private final String configDataId = APP_NAME + Constants.CLIENT_CONFIG_POSTFIX;

    private final String clusterMapDataId = APP_NAME + Constants.CLUSTER_MAP_POSTFIX;

    private static final String SEPARATOR = "@";

    @Override

    public void init() {

        // Register client dynamic rule data source.

        //动态数据源的方式配置sentinel的流量控制和热点参数限流的规则。

        initDynamicRuleProperty();

        // Register token client related data source.

        // Token client common config

        // 集群限流客户端的配置属性

        initClientConfigProperty();

        // Token client assign config (e.g. target token server) retrieved from assign map:

        //初始化Token客户端

        initClientServerAssignProperty();

        // Register token server related data source.

        // Register dynamic rule data source supplier for token server:

        //集群的流控规则,比如限制整个集群的流控阀值,启动的时候需要添加-Dproject.name=项目名

        registerClusterRuleSupplier();

        // Token server transport config extracted from assign map:

        //初始化server的端口配置

        initServerTransportConfigProperty();

        // Init cluster state property for extracting mode from cluster map data source.

        //初始化集群中服务是客户端还是服务端

        initStateProperty();

    }

    private void initDynamicRuleProperty() {

        //流量控制的DataId分别是APP_NAME + Constants.FLOW_POSTFIX;热点参数限流规则的DataId是APP_NAME + Constants.PARAM_FLOW_POSTFIX;

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

    }

    private void initClientConfigProperty() {

        ReadableDataSource<String, ClusterClientConfig> clientConfigDs = new NacosDataSource<>(remoteAddress, groupId,

            configDataId, source -> JSON.parseObject(source, new TypeReference<ClusterClientConfig>() {}));

        ClusterClientConfigManager.registerClientConfigProperty(clientConfigDs.getProperty());

    }

    private void initServerTransportConfigProperty() {

        ReadableDataSource<String, ServerTransportConfig> serverTransportDs = new NacosDataSource<>(remoteAddress, groupId,

            clusterMapDataId, source -> {

            List<ClusterGroupEntity> groupList = new Gson().fromJson(source, new TypeToken<List<ClusterGroupEntity>>(){}.getType());

            return Optional.ofNullable(groupList)

                .flatMap(this::extractServerTransportConfig)

                .orElse(null);

        });

        ClusterServerConfigManager.registerServerTransportProperty(serverTransportDs.getProperty());

    }

    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 + Constants.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 + Constants.PARAM_FLOW_POSTFIX, source -> JSON.parseObject(source, new TypeReference<List<ParamFlowRule>>() {}));

            return ds.getProperty();

        });

    }

    private void initClientServerAssignProperty() {

        // Cluster map format:

        // [{"clientSet":["112.12.88.66@8729","112.12.88.67@8727"],"ip":"112.12.88.68","serverId":"112.12.88.68@8728","port":11111}]

        // serverId: <ip@commandPort>, commandPort for port exposed to Sentinel dashboard (transport module)

        ReadableDataSource<String, ClusterClientAssignConfig> clientAssignDs = new NacosDataSource<>(remoteAddress, groupId,

            clusterMapDataId, source -> {

            List<ClusterGroupEntity> groupList = new Gson().fromJson(source, new TypeToken<List<ClusterGroupEntity>>(){}.getType());

            return Optional.ofNullable(groupList)

                .flatMap(this::extractClientAssignment)

                .orElse(null);

        });

        ClusterClientConfigManager.registerServerAssignProperty(clientAssignDs.getProperty());

    }

    private void initStateProperty() {

        // Cluster map format:

        // [{"clientSet":["112.12.88.66@8729","112.12.88.67@8727"],"ip":"112.12.88.68","serverId":"112.12.88.68@8728","port":11111}]

        // serverId: <ip@commandPort>, commandPort for port exposed to Sentinel dashboard (transport module)

        ReadableDataSource<String, Integer> clusterModeDs = new NacosDataSource<>(remoteAddress, groupId,

            clusterMapDataId, source -> {

            List<ClusterGroupEntity> groupList = new Gson().fromJson(source, new TypeToken<List<ClusterGroupEntity>>(){}.getType());

            return Optional.ofNullable(groupList)

                .map(this::extractMode)

                .orElse(ClusterStateManager.CLUSTER_NOT_STARTED);

        });

        ClusterStateManager.registerProperty(clusterModeDs.getProperty());

    }

    private int extractMode(List<ClusterGroupEntity> groupList) {

        // If any server group serverId matches current, then it's token server.

        if (groupList.stream().anyMatch(this::machineEqual)) {

            return ClusterStateManager.CLUSTER_SERVER;

        }

        // If current machine belongs to any of the token server group, then it's token client.

        // Otherwise it's unassigned, should be set to NOT_STARTED.

        boolean canBeClient = groupList.stream()

            .flatMap(e -> e.getClientSet().stream())

            .filter(Objects::nonNull)

            .anyMatch(e -> e.equals(getCurrentMachineId()));

        return canBeClient ? ClusterStateManager.CLUSTER_CLIENT : ClusterStateManager.CLUSTER_NOT_STARTED;

    }

    private Optional<ServerTransportConfig> extractServerTransportConfig(List<ClusterGroupEntity> groupList) {

        return groupList.stream()

            .filter(this::machineEqual)

            .findAny()

            .map(e -> new ServerTransportConfig().setPort(e.getPort()).setIdleSeconds(600));

    }

    private Optional<ClusterClientAssignConfig> extractClientAssignment(List<ClusterGroupEntity> groupList) {

        if (groupList.stream().anyMatch(this::machineEqual)) {

            return Optional.empty();

        }

        // Build client assign config from the client set of target server group.

        for (ClusterGroupEntity group : groupList) {

            if (group.getClientSet().contains(getCurrentMachineId())) {

                String ip = group.getIp();

                Integer port = group.getPort();

                return Optional.of(new ClusterClientAssignConfig(ip, port));

            }

        }

        return Optional.empty();

    }

    private boolean machineEqual(/*@Valid*/ ClusterGroupEntity group) {

        return getCurrentMachineId().equals(group.getServerId());

    }

    private String getCurrentMachineId() {

        // Note: this may not work well for container-based env.

        return HostNameUtil.getIp() + SEPARATOR + TransportConfig.getRuntimePort();

    }

}

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

1

com.liang.springcloud.alibaba.init.ClusterInitFunc

添加配置的解析类:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

public class ClusterGroupEntity implements Serializable {

    private String serverId;

    private String ip;

    private Integer port;

    private Set<String> clientSet;

    public String getServerId() {

        return serverId;

    }

    public void setServerId(String serverId) {

        this.serverId = serverId;

    }

    public String getIp() {

        return ip;

    }

    public void setIp(String ip) {

        this.ip = ip;

    }

    public Integer getPort() {

        return port;

    }

    public void setPort(Integer port) {

        this.port = port;

    }

    public Set<String> getClientSet() {

        return clientSet;

    }

    public void setClientSet(Set<String> clientSet) {

        this.clientSet = clientSet;

    }

    @Override

    public String toString() {

        return "ClusterGroupEntity{" +

                "serverId='" + serverId + '\'' +

                ", ip='" + ip + '\'' +

                ", port=" + port +

                ", clientSet=" + clientSet +

                '}';

    }

}

在Nacos中添加动态规则配置,以及token server与token client的配置:

DataId:cloudalibaba-sentinel-clusterServer-flow-rules Group:SENTINEL_GROUP 配置内容(json格式):

1

2

3

4

5

6

7

8

9

10

11

12

13

[

    {

        "resource" : "/order/{id}",     // 限流的资源名称

        "grade" : 1,                         // 限流模式为:qps,线程数限流0,qps限流1

        "count" : 20,                        // 阈值为:20

        "clusterMode" true,               // 是否是集群模式,集群模式为:true

        "clusterConfig" : {

            "flowId" : 111,                  // 全局唯一id

            "thresholdType" : 1,             // 阈值模式为:全局阈值,0是单机均摊,1是全局阀值

            "fallbackToLocalWhenFail" : true // 在 client 连接失败或通信失败时,是否退化到本地的限流模式

        }

    }

]

DataId:cloudalibaba-sentinel-clusterServer-cluster-client-config Group:SENTINEL_GROUP 配置内容(json格式):

1

2

3

{

    "requestTimeout": 20

}

DataId:cloudalibaba-sentinel-clusterServer-cluster-map Group:SENTINEL_GROUP 配置内容(json格式):

1

2

3

4

5

6

[{

    "clientSet": ["10.133.40.30@8721", "10.133.40.30@8722"],

    "ip": "10.133.40.30",

    "serverId": "10.133.40.30@8720",

    "port": 18730   //这个端口是token server通信的端口

}]

重新启动服务,并访问接口,我们可以看到流控规则与集群流控都自动配置完成。我们需要测试,我们集群流控是否已经生效。

不断执行以下命令:

1

2

3

ab -n 100 -c 50 http://localhost:9091/order/1

ab -n 100 -c 50 http://localhost:9092/order/3

ab -n 100 -c 50 http://localhost:9093/order/1

测试效果图:

我们从实时监控图上可以看出,资源名为/order/{id},整个集群的QPS为20,跟我们的配置是一样的。当作为token server的机器挂掉后,集群限流会退化到 local 模式的限流,即在本地按照单机阈值执行限流检查。

Token Server 分配配置:

上面这张图可以很好帮忙我们解释嵌入模式的具体实现。通过配置信息解析,管理我们的token server与token client。

适用范围:

嵌入模式适合某个应用集群内部的流控。由于隔离性不佳,token server会影响应用本身,需要限制 token server 的总QPS。

独立模式部署

独立模式相对于嵌入模式而言就是将token server与应用隔离,进行独立部署。将嵌入模式中token server和token client分离,分别进行配置。我们只需要将 InitFunc 实现类进行拆分。

token server的nacos配置

server的名称空间配置,(集群的namespace或客户端项目名)如下:

DataId:cluster-server-namespace-set Group:SENTINEL_ALONE_GROUP 配置内容(json格式):

1

2

3

[

    "cloudalibaba-sentinel-cluster-client-alone"

]

server的通信端口配置,如下:

DataId:cluster-server-transport-config Group:SENTINEL_ALONE_GROUP 配置内容(json格式):

1

2

3

4

{

 "idleSecods":600,

 "port": 18730

}

Token sever的流控限制配置,如下:

DataId:cluster-server-flow-config Group:SENTINEL_ALONE_GROUP 配置内容(json格式):

1

2

3

4

5

{

    "exceedCount":1.0,

    "maxAllowedQps":20000,

    "namespace":"cloudalibaba-sentinel-cluster-client-alone"

}

token server的host地址与端口号配置,如下:

DataId: cluster-server-config Group:SENTINEL_ALONE_GROUP 配置内容(json格式):

1

2

3

4

{

    "serverHost": "10.133.40.30",

    "serverPort": 18730

}

token server的InitFunc类:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

/**

 * @PROJECT_NAME: SpringCloud-Learning

 * @USER: yuliang

 * @DESCRIPTION:

 * @DATE: 2021-04-01 10:01

 */

public class ClusterServerInitFunc implements InitFunc {

    //nacos集群地址

    private final String remoteAddress = "localhost:8848";

    //配置的分组名称

    private final String groupId = "SENTINEL_ALONE_GROUP";

    //配置的dataId

    private final String namespaceSetDataId = "cluster-server-namespace-set";

    private final String serverTransportDataId = "cluster-server-transport-config";

    private final String serverFlowDataId = "cluster-server-flow-config";

    @Override

    public void init() {

        //监听特定namespace(集群的namespace或客户端项目名)下的集群限流规则

        initPropertySupplier();

        // 设置tokenServer管辖的作用域(即管理哪些应用)

        initTokenServerNameSpaces();

        // Server transport configuration data source.

        //Server端配置

        initServerTransportConfig();

        // 初始化最大qps

        initServerFlowConfig();

        //初始化服务器状态

        initStateProperty();

    }

    private  void initPropertySupplier(){

        // 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 + Constants.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 + Constants.PARAM_FLOW_POSTFIX, source -> JSON.parseObject(source, new TypeReference<List<ParamFlowRule>>() {}));

            return ds.getProperty();

        });

    }

    private void initTokenServerNameSpaces(){

        // Server namespace set (scope) data source.

        ReadableDataSource<String, Set<String>> namespaceDs = new NacosDataSource<>(remoteAddress, groupId,

                namespaceSetDataId, source -> JSON.parseObject(source, new TypeReference<Set<String>>() {}));

        ClusterServerConfigManager.registerNamespaceSetProperty(namespaceDs.getProperty());

    }

    private void initServerTransportConfig(){

        // Server transport configuration data source.

        ReadableDataSource<String, ServerTransportConfig> transportConfigDs = new NacosDataSource<>(remoteAddress,

                groupId, serverTransportDataId,

                source -> JSON.parseObject(source, new TypeReference<ServerTransportConfig>() {}));

        ClusterServerConfigManager.registerServerTransportProperty(transportConfigDs.getProperty());

    }

    private void initServerFlowConfig(){

        // Server namespace set (scope) data source.

        ReadableDataSource<String, ServerFlowConfig> serverFlowConfig = new NacosDataSource<>(remoteAddress, groupId,

                serverFlowDataId, source -> JSON.parseObject(source, new TypeReference<ServerFlowConfig>() {}));

        ClusterServerConfigManager.registerGlobalServerFlowProperty(serverFlowConfig.getProperty());

    }

    private void initStateProperty() {

        ClusterStateManager.applyState(ClusterStateManager.CLUSTER_SERVER);

    }

}

token client的nacos配置

客户端请求超时配置,如下:

DataId:cluster-client-config Group:SENTINEL_ALONE_GROUP 配置内容(json格式):

1

2

3

{

    "requestTimeout": 20

}

流控限流配置,如下:

DataId: cloudalibaba-sentinel-cluster-client-alone-flow-rules Group:SENTINEL_ALONE_GROUP 配置内容(json格式):

1

2

3

4

5

6

7

8

9

10

11

12

13

[

    {

        "resource" : "/order/{id}",     // 限流的资源名称

        "grade" : 1,                         // 限流模式为:qps

        "count" : 30,                        // 阈值为:30

        "clusterMode" true,               // 集群模式为:true

        "clusterConfig" : {

            "flowId" : 111,                  // 全局唯一id

            "thresholdType" : 1,             // 阈值模式为:全局阈值

            "fallbackToLocalWhenFail" : true // 在 client 连接失败或通信失败时,是否退化到本地的限流模式

        }

    }

]

热点限流配置,如下:

DataId:cloudalibaba-sentinel-cluster-client-alone-param-rules Group:SENTINEL_ALONE_GROUP 配置内容(json格式):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

[

    {

        "resource" : "order",          // 限流的资源名称

        "paramIdx" : 1,                      //参数索引

        "grade" : 1,                         // 限流模式为:qps

        "count" : 10,                        // 阈值为:10

        "clusterMode" true,               // 集群模式为:true

        "clusterConfig" : {

            "flowId" : 121,                  // 全局唯一id

            "thresholdType" : 1,             // 阈值模式为:全局阈值

            "fallbackToLocalWhenFail" : true // 在 client 连接失败或通信失败时,是否退化到本地的限流模式

        },

        "paramFlowItemList":[      //索引为1的参数值为hot时,接口阈值为50,其他值均为10

            {

                object: "hot",

                count: 50,

                classType: "java.lang.String"

            }

        ]

    }

]

Token client的InitFunc类:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

/**

 * @PROJECT_NAME: SpringCloud-Learning

 * @USER: yuliang

 * @DESCRIPTION:

 * @DATE: 2021-04-01 17:47

 */

public class ClusterClientInitFunc implements InitFunc {

    //项目名称

    private static final String APP_NAME = AppNameUtil.getAppName();

    //nacos集群地址

    private final String remoteAddress = "localhost:8848";

    //nacos配置的分组名称

    private final String groupId = "SENTINEL_ALONE_GROUP";

    //项目名称 + Constants的配置名称,组成配置的dataID

    private final String flowDataId = APP_NAME + Constants.FLOW_POSTFIX;

    private final String paramDataId = APP_NAME + Constants.PARAM_FLOW_POSTFIX;

    private final String configDataId = "cluster-client-config";

    private final String serverDataId =  "cluster-server-config";

    @Override

    public void init() throws Exception {

        // Register client dynamic rule data source.

        //客户端,动态数据源的方式配置sentinel的流量控制和热点参数限流的规则。

        initDynamicRuleProperty();

        // Register token client related data source.

        // Token client common config

        // 集群限流客户端的配置属性

        initClientConfigProperty();

        // Token client assign config (e.g. target token server) retrieved from assign map:

        //初始化Token客户端

        initClientServerAssignProperty();

        //初始化客户端状态

        initStateProperty();

    }

    private void initDynamicRuleProperty() {

        //流量控制的DataId分别是APP_NAME + Constants.FLOW_POSTFIX;热点参数限流规则的DataId是APP_NAME + Constants.PARAM_FLOW_POSTFIX;

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

    }

    private void initClientConfigProperty() {

        ReadableDataSource<String, ClusterClientConfig> clientConfigDs = new NacosDataSource<>(remoteAddress, groupId,

                configDataId, source -> JSON.parseObject(source, new TypeReference<ClusterClientConfig>() {}));

        ClusterClientConfigManager.registerClientConfigProperty(clientConfigDs.getProperty());

    }

    private void initClientServerAssignProperty() {

        ReadableDataSource<String, ClusterClientAssignConfig> clientAssignDs = new NacosDataSource<>(remoteAddress, groupId,

                serverDataId, source -> JSON.parseObject(source, new TypeReference<ClusterClientAssignConfig>() {}));

        ClusterClientConfigManager.registerServerAssignProperty(clientAssignDs.getProperty());

    }

    private void initStateProperty() {

        ClusterStateManager.applyState(ClusterStateManager.CLUSTER_CLIENT);

    }

}

核心的代码与配置,如上所示,其他代码,可以访问:

1

2

<module>cloudalibaba-sentinel-cluster-server-alone9092</module>

<module>cloudalibaba-sentinel-cluster-client-alone9093</module>

测试:

启动cloudalibaba-sentinel-cluster-server-alone9092,我们启动两个实例,模拟集群(可以启动多个):

1

2

-Dserver.port=9092 -Dcsp.sentinel.log.use.pid=true

-Dserver.port=9094 -Dcsp.sentinel.log.use.pid=true

启动cloudalibaba-sentinel-cluster-client-alone9093,我们启动1个实例,模拟server(实现master选举之后,可以启动多个):

1

-Dserver.port=9093 -Dcsp.sentinel.log.use.pid=true

不断执行以下命令,进行接口访问测试:

1

2

ab -n 100 -c 50 http://localhost:9092/order/1

ab -n 100 -c 50 http://localhost:9094/order/3

我们从实时监控图上可以看出,资源名为/order/{id},整个集群的QPS为30,跟我们的配置是一样的。当作为token server的机器挂掉后,集群限流会退化到 local 模式的限流,即在本地按照单机阈值执行限流检查。

热点限流已经为大家实现了,大家可以自行测试,比较简单,不再累述。

1

2

3

4

5

ab -n 100 -c 50  http://localhost:9092/hot_order/1/hot

ab -n 100 -c 50  http://localhost:9094/hot_order/1/hot

ab -n 100 -c 50  http://localhost:9092/hot_order/1/nothot

ab -n 100 -c 50  http://localhost:9094/hot_order/1/nothot

其它

若在生产环境使用集群限流,管控端还需要关注以下的问题:

  • Token Server 自动管理、调度(分配/选举 Token Server)
  • Token Server 高可用,在某个 server 不可用时自动 failover 到其它机器

 总结

集群流控,有两种模式,嵌入模式和独立模式,个人不建议在业务系统使用集群流控,集群流控可以在网关层做,业务层的话可以使用单机流控,相对来说简单好上手。token server目前存在单点问题,需要个人实现master选举,并修改 cluster-server-config的IP即可。

代码示例

本文示例读者可以通过查看下面仓库中的项目,如下所示:

1

<module>cloudalibaba-sentinel-cluster</module>

Github:GitHub - jiuqiyuliang/SpringCloud-Learning

到此这篇关于Sentinel实现动态配置的集群流控的文章就介绍到这了,更多相关Sentinel集群流控内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

来源:Sentinel实现动态配置的集群流控的方法_java_脚本之家

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Sentinel实现动态配置的集群流控的方法 的相关文章

  • 如何使用 Java 和 Selenium WebDriver 在 C 目录中创建文件夹并需要将屏幕截图保存在该目录中?

    目前正在与硒网络驱动程序和代码Java 我有一种情况 我需要在 C 目录中创建一个文件夹 并在该文件夹中创建我通过 selenium Web 驱动程序代码拍摄的屏幕截图 它需要存储在带有时间戳的文件夹中 如果我每天按计划运行脚本 所有屏幕截
  • Spring Batch 多线程 - 如何使每个线程读取唯一的记录?

    这个问题在很多论坛上都被问过很多次了 但我没有看到适合我的答案 我正在尝试在我的 Spring Batch 实现中实现多线程步骤 有一个包含 100k 条记录的临时表 想要在 10 个线程中处理它 每个线程的提交间隔为 300 因此在任何时
  • 在画布上绘图

    我正在编写一个 Android 应用程序 它可以在视图的 onDraw 事件上直接绘制到画布上 我正在绘制一些涉及单独绘制每个像素的东西 为此我使用类似的东西 for int x 0 x lt xMax x for int y 0 y lt
  • Play框架运行应用程序问题

    每当我尝试运行使用以下命令创建的新 Web 应用程序时 我都会收到以下错误Play http www playframework org Error occurred during initialization of VM Could no
  • Java JDBC:更改表

    我希望对此表进行以下修改 添加 状态列 varchar 20 日期列 时间戳 我不确定该怎么做 String createTable Create table aircraft aircraftNumber int airLineCompa
  • 给定两个 SSH2 密钥,我如何检查它们是否属于 Java 中的同一密钥对?

    我正在尝试找到一种方法来验证两个 SSH2 密钥 一个私有密钥和一个公共密钥 是否属于同一密钥对 我用过JSch http www jcraft com jsch 用于加载和解析私钥 更新 可以显示如何从私钥 SSH2 RSA 重新生成公钥
  • Final字段的线程安全

    假设我有一个 JavaBeanUser这是从另一个线程更新的 如下所示 public class A private final User user public A User user this user user public void
  • Mockito when().thenReturn 不必要地调用该方法

    我正在研究继承的代码 我编写了一个应该捕获 NullPointerException 的测试 因为它试图从 null 对象调用方法 Test expected NullPointerException class public void c
  • 无法解析插件 Java Spring

    我正在使用 IntelliJ IDEA 并且我尝试通过 maven 安装依赖项 但它给了我这些错误 Cannot resolve plugin org apache maven plugins maven clean plugin 3 0
  • Java TestNG 与跨多个测试的数据驱动测试

    我正在电子商务平台中测试一系列商店 每个商店都有一系列属性 我正在考虑对其进行自动化测试 是否有可能有一个数据提供者在整个测试套件中提供数据 而不仅仅是 TestNG 中的测试 我尝试不使用 testNG xml 文件作为机制 因为这些属性
  • Eclipse Java 远程调试器通过 VPN 速度极慢

    我有时被迫离开办公室工作 这意味着我需要通过 VPN 进入我的实验室 我注意到在这种情况下使用 Eclipse 进行远程调试速度非常慢 速度慢到调试器需要 5 7 分钟才能连接到远程 jvm 连接后 每次单步执行断点 行可能需要 20 30
  • 在mockito中使用when进行模拟ContextLoader.getCurrentWebApplicationContext()调用。我该怎么做?

    我试图在使用 mockito 时模拟 ContextLoader getCurrentWebApplicationContext 调用 但它无法模拟 here is my source code Mock org springframewo
  • Java列表的线程安全

    我有一个列表 它将在线程安全上下文或非线程安全上下文中使用 究竟会是哪一个 无法提前确定 在这种特殊情况下 每当列表进入非线程安全上下文时 我都会使用它来包装它 Collections synchronizedList 但如果不进入非线程安
  • 如何在桌面浏览器上使用 webdriver 移动网络

    我正在使用 selenium webdriver 进行 AUT 被测应用程序 的功能测试自动化 AUT 是响应式网络 我几乎完成了桌面浏览器的不同测试用例 现在 相同的测试用例也适用于移动浏览器 因为可以从移动浏览器访问 AUT 由于它是响
  • 获取 JVM 上所有引导类的列表?

    有一种方法叫做findBootstrapClass对于一个类加载器 如果它是引导的 则返回一个类 有没有办法找到类已经加载了 您可以尝试首先通过例如获取引导类加载器呼叫 ClassLoader bootstrapLoader ClassLo
  • 在 Maven 依赖项中指定 jar 和 test-jar 类型

    我有一个名为 commons 的项目 其中包含运行时和测试的常见内容 在主项目中 我添加了公共资源的依赖项
  • 使用 JMF 创建 RTP 流时出现问题

    我正处于一个项目的早期阶段 需要使用 RTP 广播DataStream创建自MediaLocation 我正在遵循一些示例代码 该代码目前在rptManager initalize localAddress 出现错误 无法打开本地数据端口
  • 将 List 转换为 JSON

    Hi guys 有人可以帮助我 如何将我的 HQL 查询结果转换为带有对象列表的 JSON 并通过休息服务获取它 这是我的服务方法 它返回查询结果列表 Override public List
  • 按日期对 RecyclerView 进行排序

    我正在尝试按日期对 RecyclerView 进行排序 但我尝试了太多的事情 我不知道现在该尝试什么 问题就出在这条线上适配器 notifyDataSetChanged 因为如果我不放 不会显示错误 但也不会更新 recyclerview
  • 如何实现仅当可用内存较低时才将数据交换到磁盘的写缓存

    我想将应用程序生成的数据缓存在内存中 但如果内存变得稀缺 我想将数据交换到磁盘 理想情况下 我希望虚拟机通知它需要内存并将我的数据写入磁盘并以这种方式释放一些内存 但我没有看到任何方法以通知我的方式将自己挂接到虚拟机中before an O

随机推荐

  • Intel DDR布线之Tabbed Routing

    一 Overview Tabbed Routing是一种在相邻的平行走线上连接小的梯形凸片 以更积极地控制走线的电容 以管理走线阻抗并补偿结构的电感效应的方法 Tabbed Routing is a method of attaching
  • Ubuntu18.04安装OpenGL依赖库

    sudo apt get install build essential sudo apt get install libgl1 mesa dev sudo apt get install libglu1 mesa dev sudo apt
  • tabpanel页签的机制

    tabpanel页签的机制 页签展现渲染时 只会初始化渲染你所指定的activeTab这个子页签 其他的页签一律不渲染 所以也就不存在form的dom内容 如果没有指定activeTab页签不会初始化任何子页签 那么所有的form都不会得到
  • 【ARIMA-WOA-CNN-LSTM】合差分自回归移动平均方法-鲸鱼优化-卷积神经网络-长短期记忆神经网络研究(Python代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 1 1 ARIMA模型 1 2 鲸鱼优化算法 1 3 卷积神经网络 1 4 LSTM 模型 2 运行结果
  • uniapp Echart X轴Y轴文字被遮挡怎么办,或未能铺满整个容器

    有时候布局太小 使用echarts x轴y轴文字容易被遮挡 怎么解决这个问题呢 或者是未能铺满整个容器 方法1 直接设置 containLabel 字段 options grid containLabel true 方法2 间接设置 但是不
  • IDEA 【基础】 javaweb项目中 将maven的jar包,复制到web项目的 lib 文件夹

    自己在做小型javweb项目的时候经常遇到这种问题 java lang NoClassDefFoundError 明明maven已经添加依赖了 而且项目里面可以正常运行 但是启动tomcat运行的时候 却运行不了 博主琢磨半天 了解到 第三
  • 深度学习实战项目(三)-行人检测重识别yolov5+reid(跑通+界面设计)

    行人检测重识别yolov5 reid 跑通 界面设计 参考源代码 github 权重文件 根据github上面的网盘进行权重下载 检测 将 ReID resnet50 ibn a pth放在person search weights文件下
  • One-Stage Visual Grounding(单阶段语言指示的视觉定位)论文略读_2019-2020

    One Stage Visual Grounding 2019 2020年论文略读 1 Zero Shot Grounding of Objects from Natural Language Queries 2019 ICCV 改进工作
  • Linux 查看显卡型号

    输入以下命令 lspci grep i vga 可以查看显卡型号 但是是一串数字代码 可通过PCI devices网站进行查询 结果如下所示 GeForce RTX 3060 Lite Hash Rate 即为显卡信息
  • 浏览器刷新、关闭页面与统计在线人数

    项目中可能需要统计在线人数 也可能需要在用户在退出时进行用户注销登录 既为统计实时在线人数 也为及时清理暂时不再使用的session 节约资源提高性能 对于以上的情况 若用户使用页面的注销按钮退出登录 那一定万事大吉了 当实际中这种可能性很
  • Java面试题(1)-J2SE基础

    最近在为自己实习准备 看了网上各种面试经验贴 也和身边的小伙伴一起参加了不少牛逼IT企业的面试 这篇文章就将面试遇到的一些比较常见的问题整理一下 给大家一些参考 也为自己整理整理 J2SE基础 1 九种基本数据类型的大小 以及他们的封装类
  • 猿创征文

    猿创征文 国产数据库实战 使用docker部署PolarDB X云原生分布式开源数据库 一 PolarDB X介绍 1 PolarDB X简介 2 PolarDB X特点 二 检查docker版本 三 检查docker配置信息 四 下载Po
  • redis集群原理

    redis是单线程 但是一般的作为缓存使用的话 redis足够了 因为它的读写速度太快了 官方的一个简单测试 测试完成了50个并发执行100000个请求 设置和获取的值是一个256字节字符串 结果 读的速度是110000次 s 写的速度是8
  • MySQL高频面试题

    文章目录 1 什么是MySQL 2 关系型数据库和非关系型数据库 3 数据库三大范式是什么 4 一条 SQL 查询语句是如何执行的 5 引擎 MySQL存储引擎MyISAM与InnoDB区别 MyISAM索引与InnoDB索引的区别 Inn
  • 哈夫曼树带权路径长度

    一 长什么样 左边是普通树 右边是哈夫曼树 图a WPL 5 2 7 2 2 2 13 2 54 图b WPL 5 3 2 3 7 2 13 1 48 可见 图b的带权路径长度较小 我们可以证明图b就是哈夫曼树 也称为最优二叉树 二 怎么生
  • Vue实现swiper轮播组件

    目前市面上有很多轮播组件 但是有的不满足业务需求 因此也需要自己首先轮播组件 以下是一个用vue实现的轮播组件 带动画效果 可以自行设置轮播速度 选择是否需要分页器等 效果如下 思路 结构 一个轮播组件应该由三部分组成 一是轮播的元素 如图
  • 如何在内存中执行二进制代码之win平台

    大家可能会很好奇 我们的任意exe程序 不就是在内存中执行的二进制机器码吗 不 今天我要说的是 我们如何把实现指定功能的一段二进制机器码 放到我们的程序中 然后在需要的时候 直接调用它 当然 这段代码也有其他用途 故而有了shell cod
  • 公众号分享

    配置 gt 登录公众号 gt 设置与开发 gt 公众号设置 gt 功能设置 gt js安全域名域名 gt 网页授权域名 1 先拿集成微信 js 路径写自己的 import wxshare from common js wxShareModu
  • 【树莓派之旅】第01期:一根网线搞定树莓派可视化界面

    一次偶然的机会接触到了树莓派 于是就购了一个板子 入手快两个月了 由于一直忙于工作的事情 所以也就没折腾 今天拿出来的时候发现要想把树莓派运行起来还需要一些其他外接设备 一时也没去某宝或某东上采购 就利用手头现有的资源玩一下 于是就有了此文
  • Sentinel实现动态配置的集群流控的方法

    这篇文章主要介绍了Sentinel实现动态配置的集群流控 本文给大家介绍的非常详细 对大家的学习或工作具有一定的参考借鉴价值 需要的朋友可以参考下 介绍 为什么要使用集群流控呢 相对于单机流控而言 我们给每台机器设置单机限流阈值 在理想情况