为什么要进行集群流控
假设集群中有 10 台机器,我们给每台机器设置单机限流阈值为 10 QPS,理想情况下整个集群的限流阈值就为 100 QPS。不过实际情况下流量到每台机器可能会不均匀,会导致总量没有到的情况下某些机器就开始限流。因此仅靠单机维度去限制的话会无法精确地限制总体流量。而集群流控可以精确地控制整个集群的调用总量,结合单机限流兜底,可以更好地发挥流量控制的效果。
集群流控中共有两种身份
- Token Client:集群流控客户端,用于向所属 Token Server 通信请求 token。集群限流服务端会返回给客户端结果,决定是否限流。
- Token Server:即集群流控服务端,处理来自 Token Client 的请求,根据配置的集群规则判断是否应该发放 token(是否允许通过)。
Token Server 集群限流服务端
两种启动方式
-
独立模式(Alone),即作为独立的 token server 进程启动,独立部署,隔离性好,但是需要额外的部署操作。独立模式适合作为 Global Rate Limiter 给集群提供流控服务。
在独立模式下,我们可以直接创建对应的 ClusterTokenServer 实例并在 main 函数中通过 start 方法启动 Token Server。 -
嵌入模式(Embedded),即作为内置的 token server 与服务在同一进程中启动。在此模式下,集群中各个实例都是对等的,token server 和 client 可以随时进行转变,因此无需单独部署,灵活性比较好。但是隔离性不佳,需要限制 token server 的总 QPS,防止影响应用本身。嵌入模式适合某个应用集群内部的流控。
在 embedded 模式下通过API转换集群流控身份:
http://:/setClusterMode?mode=<xxx>
其中 mode 为 0 代表 client,1 代表 server,-1 代表关闭。注意应用端需要引入集群限流客户端或服务端的相应依赖。
独立模式示例
本文只介绍独立模式示例。我们的示例使用SPI方式初始化流控规则等。
- 添加 maven 依赖
<!-- Token Server 依赖 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-cluster-server-default</artifactId>
<version>${sentinel.version}</version>
</dependency>
<!-- 支持Nacos数据源 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>${sentinel.version}</version>
</dependency>
- 添加SPI配置
com.alibaba.csp.sentinel.init.InitFunc 文件配置如下
com.yyoo.sentinel.server.func.MyClusterServerInitFunc
注:此处配置遵循Java的SPI规则即可,不做过多详解。
- MyClusterServerInitFunc 代码
package com.yyoo.sentinel.server.func;
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.csp.sentinel.init.InitFunc;
import com.alibaba.csp.sentinel.init.InitOrder;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.List;
import java.util.Properties;
import java.util.Set;
/**
* 集群流控,服务端 规则 + Namespace + ServerTransportConfig 使用 Nacos 动态推送
*/
@InitOrder(1) // 用于定义执行顺序
public class MyClusterServerInitFunc implements InitFunc {
/**
* Nacos 地址
*/
public static final String NACOS_ADDRS = "localhost:8848";
/**
* 对应 Nacos 的命名空间 ID
*
* fd315d16-12ef-4d67-b8e2-8dfe2e6667b5
* 855e91a2-60e7-48c3-aa3f-27aa2f8a0f73
*/
public static final String NACOS_SENTINEL_NAMESPACE = "fd315d16-12ef-4d67-b8e2-8dfe2e6667b5";
/**
* Nacos group id
*/
public static final String NACOS_SENTINEL_GROUPID = "Sentinel_Demo_Group";
/**
* 集群限流规则 dataID
* 获取到的值为规则列表,如: List<FlowRule>
*/
public static final String NACOS_SENTINEL_CLUSTER_RULES_DATAID = "Cluster-Flow-Rule";
/**
* 集群流控作用域(默认对应集群应用的 project.name 应用名)
* 获取到的值为 Set<String> 类型,即为应用名列表
*/
public static final String NACOS_SENTINEL_NAMESPACES_DATAID = "Cluster-Name-Space";
/**
* 集群流控 Server 端配置
* 获取到的值为 ServerTransportConfig 类型
*/
public static final String NACOS_SENTINEL_SERVER_TRANSPORT_CONFIG_DATAID = "Cluster-Server-Config";
@Override
public void init() throws Exception {
Properties nacosPro = new Properties();
nacosPro.put(PropertyKeyConst.SERVER_ADDR,NACOS_ADDRS);
nacosPro.put(PropertyKeyConst.NAMESPACE,NACOS_SENTINEL_NAMESPACE);
// 配置 Nacos 动态规则数据源 (此处只定义了限流规则,其他规则如:热点参数规则可按此示例配置即可)
ClusterFlowRuleManager.setPropertySupplier(namespace -> {
ReadableDataSource<String, List<FlowRule>> ds = new NacosDataSource<>(nacosPro, NACOS_SENTINEL_GROUPID, NACOS_SENTINEL_CLUSTER_RULES_DATAID,
source -> JsonUtil.toGenericBean(source, new TypeReference<List<FlowRule>>(){}));
return ds.getProperty();
});
// 配置从 Nacos 动态获取应用集群作用域NameSpace(即默认为集群的project.name)
ReadableDataSource<String, Set<String>> nameSpaceDS = new NacosDataSource<>(nacosPro, NACOS_SENTINEL_GROUPID, NACOS_SENTINEL_NAMESPACES_DATAID,
source -> JsonUtil.toGenericBean(source, new TypeReference<Set<String>>(){}));
ClusterServerConfigManager.registerNamespaceSetProperty(nameSpaceDS.getProperty());
// 配置从 Nacos 动态获取 Token Server 的配置
ReadableDataSource<String, ServerTransportConfig> serverTransportConfigDS = new NacosDataSource<>(nacosPro, NACOS_SENTINEL_GROUPID, NACOS_SENTINEL_SERVER_TRANSPORT_CONFIG_DATAID,
source -> JsonUtil.toGenericBean(source, new TypeReference<ServerTransportConfig>(){}));
ClusterServerConfigManager.registerServerTransportProperty(serverTransportConfigDS.getProperty());
}
}
服务端还可以通过 ClusterServerConfigManager.setMaxAllowedQps(); 设置 token server 最大允许的总 QPS(maxAllowedQps),来对 token server 的资源使用进行限制,防止在嵌入模式下影响应用本身
- 服务端启动类
package com.yyoo.sentinel.server;
import com.alibaba.csp.sentinel.cluster.server.ClusterTokenServer;
import com.alibaba.csp.sentinel.cluster.server.SentinelDefaultTokenServer;
public class Main {
public static void main(String[] args) throws Exception {
ClusterTokenServer tokenServer = new SentinelDefaultTokenServer();
tokenServer.start();
}
}
直接执行main函数启动即可
注:Server启动后,日志可以在~/logs/csp/ 文件夹下查看 sentinel-record.log。如需配置日志路径等,请查阅 Spring Cloud Alibaba Sentinel 控制台 一文中关于客户端配置文件部分。
客户端SPI配置类
package com.yyoo.sentinel.config;
import com.alibaba.csp.sentinel.cluster.ClusterStateManager;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientAssignConfig;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.csp.sentinel.init.InitFunc;
import com.alibaba.csp.sentinel.init.InitOrder;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.List;
import java.util.Properties;
/**
* 集群流控,服务端 规则 + Namespace + ServerTransportConfig 使用 Nacos 动态推送
*/
@InitOrder(1) // 用于定义执行顺序
public class MyClusterServerInitFunc implements InitFunc {
/**
* Nacos 地址
*/
public static final String NACOS_ADDRS = "localhost:8848";
/**
* 对应 Nacos 的命名空间 ID
*
* fd315d16-12ef-4d67-b8e2-8dfe2e6667b5
* 855e91a2-60e7-48c3-aa3f-27aa2f8a0f73
*/
public static final String NACOS_SENTINEL_NAMESPACE = "fd315d16-12ef-4d67-b8e2-8dfe2e6667b5";
/**
* Nacos group id
*/
public static final String NACOS_SENTINEL_GROUPID = "Sentinel_Demo_Group";
/**
* 集群限流规则 dataID
* 获取到的值为规则列表,如: List<FlowRule>
* 和 Server 端一致
*/
public static final String NACOS_SENTINEL_CLUSTER_RULES_DATAID = "Cluster-Flow-Rule";
/**
* 集群流控 Client 端配置
* 获取到的值为 ClusterClientConfig 类型
*/
public static final String NACOS_SENTINEL_CLIENT_TRANSPORT_CONFIG_DATAID = "Cluster-Client-Config";
@Override
public void init() throws Exception {
// Nacos 配置
Properties nacosPro = new Properties();
nacosPro.put(PropertyKeyConst.SERVER_ADDR,NACOS_ADDRS);
nacosPro.put(PropertyKeyConst.NAMESPACE,NACOS_SENTINEL_NAMESPACE);
// 集群客户端获取限流规则,此处和非集群模式一样
ReadableDataSource<String, List<FlowRule>> ds = new NacosDataSource<>(nacosPro, NACOS_SENTINEL_GROUPID, NACOS_SENTINEL_CLUSTER_RULES_DATAID,
source -> JsonUtil.toGenericBean(source, new TypeReference<List<FlowRule>>(){}));
FlowRuleManager.register2Property(ds.getProperty());
// 获取集群客户端配置(客户端还有一个 ClusterClientConfig 配置需要请自行配置)
/**
* serverHost: token server host
* serverPort: token server 端口
* requestTimeout: 请求的超时时间(默认为 20 ms)
*/
ReadableDataSource<String, ClusterClientAssignConfig> clientConfigDs = new NacosDataSource<>(nacosPro, NACOS_SENTINEL_GROUPID, NACOS_SENTINEL_CLIENT_TRANSPORT_CONFIG_DATAID,
source -> JsonUtil.toGenericBean(source, new TypeReference<ClusterClientAssignConfig>(){}));
ClusterClientConfigManager.registerServerAssignProperty(clientConfigDs.getProperty());
// 设置当前应用为 Token Client。
/**
* 此处推荐使用 API http://<ip>:<port>/setClusterMode?mode=<xxx> 进行设置
* 我们采用独立的 Token Server 模式,所以此处初始化直接写死的。
* 0:表示 Token Client
* 1:表示 Token Server
* -:表示未设置,默认 -1
*/
ClusterStateManager.applyState(0);
}
}
客户端如果使用了Spring Cloud Alibaba ,可以使用 application.yml 配置动态数据源的
spring:
application:
name: mySentinelDemo # 此为客户端应用名(注意同集群流控的namespace 作用域相对应)
cloud:
sentinel:
enabled: true
datasource: # 如果使用了 InitFunc 此可以不用配置
myFlowRules:
server-addr: ${my.nacos.server-addr}
namespace: ${my.nacos.sentinel-namespace}
group-id: Sentinel_Demo_Group
data-id: Cluster-Flow-Rule
data-type: json
rule-type: flow
关于使用 application.yml 配置动态数据源请查阅我们 Spring Cloud Alibaba Sentinel 动态规则扩展 一章
我们此处还是使用的 SPI InitFunc 方式,因为使用 application.yml 配置方式只能配置动态规则,我们此处的其他配置还是得使用 InitFunc 的方式。
集群限流配置规则
FlowRule
集群流控的配置依然使用FlowRule
private boolean clusterMode; // 标识是否为集群限流配置
private ClusterFlowConfig clusterConfig; // 集群限流相关配置项
private double count; // 限流的阀值(注意:根据均摊模式或全局模式的不同,阀值表示的意义也不一样)
private String resource; // 资源名称(Spring Boot / Spring Cloud alibaba 下会自动定义资源,资源名称根据url生成,可通过控制台查看自动定义的资源)
以上 clusterMode、count、resource为必须配置的字段,否则集群流控规则不会生效
在集群Token Client 无法与 Token Server 正常通信时,如果设置了可以降级为本地流控,那么FlowRule的其他参数也可以进行相应配置,请参考我们 Spring Cloud Alibaba Sentinel 流量控制 一章
集群流控规则配置类 ClusterFlowConfig 字段说明
/**
* 代表全局唯一的规则 ID,Sentinel 集群限流服务端通过此 ID 来区分各个规则,因此务必保持全局唯一。
* 一般 flowId 由统一的管控端进行分配,或写入至 DB 时生成。
* 此字段必须
*/
private Long flowId;
/**
* 0:单机均摊模式:配置的阈值等同于单机能够承受的限额.
* 比如:集群应用名称为myCloud,有3个应用,单机均摊阀值为10,那么集群的阀值为30
* 1:全局模式:配置的阈值等同于整个集群的总阈值
* 默认 0
*/
private int thresholdType = ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL;
/**
* 在 client 连接失败或通信失败时,是否退化到本地的限流模式
* 默认为 true
*/
private boolean fallbackToLocalWhenFail = true;
/**
* 0: normal.
*/
private int strategy = ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL;
private int sampleCount = ClusterRuleConstant.DEFAULT_CLUSTER_SAMPLE_COUNT;
/**
* 统计滑动窗口的时间间隔长度(毫秒)
* 默认:1000ms
*/
private int windowIntervalMs = RuleConstant.DEFAULT_WINDOW_INTERVAL_MS;
/**
* 如果客户端保留令牌的时间超过resourceTimeout,则resourceTimeoutStrategy将起作用。
*/
private long resourceTimeout = 2000;
/**
* 0:忽略
* 1:释放token
*/
private int resourceTimeoutStrategy = RuleConstant.DEFAULT_RESOURCE_TIMEOUT_STRATEGY;
/**
* 如果请求(prioritized=true,优先级为true)被阻止,则acquireReuseStrategy将起作用。。
* 0:忽略并阻止。
* 1:再试一次。
* 2:努力直到成功。
*/
private int acquireRefuseStrategy = RuleConstant.DEFAULT_BLOCK_STRATEGY;
/**
* 如果客户端脱机,服务器将删除客户端在clientOfflineTime之后持有的所有令牌。
*/
private long clientOfflineTime = 2000;
Nacos 中的规则配置、namespace 规则作用域配置、Server和Client端的基础配置如下
注:以下配置都是根据我们的示例设置的 Nacos 的 namespace 、groupId、dataId
以上的配置总览如下
集群流控规则
[
{
"resource": "/test/flow1",
"limitApp": "default",
"grade": 1,
"count": 0,
"strategy": 0,
"refResource": null,
"controlBehavior": 0,
"warmUpPeriodSec": 10,
"maxQueueingTimeMs": 500,
"clusterMode": true,
"clusterConfig": {
"flowId": 1,
"thresholdType": 0,
"fallbackToLocalWhenFail": true,
"strategy": 0,
"sampleCount": 10,
"windowIntervalMs": 1000,
"resourceTimeout": 2000,
"resourceTimeoutStrategy": 0,
"acquireRefuseStrategy": 0,
"clientOfflineTime": 2000
}
}
]
注意:此处我们设置的 count 为 0 。这是为了方便首次验证
集群流控作用域 namespace
Token Server 配置
Token Client 配置
示例验证
在客户端编写Controller url 为 /test/flow1 ,并访问即可。