小伙伴们或许遇到过下图这样的限流配置
又或者是这样的Nacos动态配置限流规则:
以上这些是什么限流?没错,就是单机限流,那么单机限流有什么弊端呢?
假设我们集群部署3台机器(NodeA/NodeB/NodeC),在某时刻,同时有25个请求进来,假设NodeA收到12个请求,NodeB 8个,NodeC 5个,并且每个单机限流qps=10,那么这个时候,NodeA将会触发限流,有两个请求BLOCK掉,这是由于可能发生的流量不均导致NodeA节点流量过高导致限流,这是因为每个机器都是自己管自己,有没有一种方法能够统筹调度呢?这就得提到集群限流了
集群限流就是,弄一台Token Server,每个客户端机器作为Token Client,有请求进来,Token Client就会向Token Server拿令牌,拿到令牌则不限流,反正则限流。其中Token Server可以作为独立运行的项目,也可以内嵌式内嵌至每个节点中(需将其中一台机器设置为Token Server,如果Token Server节点所在机器宕机,可以将其他Client节点设置成server,sentinel有相关api提供该操作)
例如上面的例子,集群内有3个节点,如果每个节点能承受10的请求,那么加起来就是3x10=30个请求。也就是说只要qps不超过30个请求都不会触发限流。
pom.xml
4.0.0
com.lee.sentinel.tokenserver
sentinel-token-server
0.0.1-SNAPSHOT
sentinel-token-server
Demo project for Spring Boot
1.8
2.3.7.RELEASE
org.springframework.boot
spring-boot-starter
com.alibaba.spring
spring-context-support
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.slf4j
slf4j-log4j12
com.alibaba.boot
nacos-config-spring-boot-starter
0.2.12
com.alibaba.spring
spring-context-support
com.alibaba.spring
spring-context-support
1.0.11
com.alibaba.csp
sentinel-cluster-server-default
1.8.5
com.alibaba.csp
sentinel-transport-simple-http
1.8.5
com.alibaba.csp
sentinel-datasource-nacos
1.8.5
org.springframework.boot
spring-boot-dependencies
${spring-boot.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
application.properties
server.port=9009
#应用名称
spring.application.name=sentinel-token-server
#nacos config center
nacos.config.server-addr=192.168.1.105:8848
ClusterServer启动类
import java.util.HashSet;
import java.util.Set;
import com.alibaba.csp.sentinel.cluster.server.ClusterTokenServer;
import com.alibaba.csp.sentinel.cluster.server.SentinelDefaultTokenServer;
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig;
public class ClusterServer {
private static final int TOKEN_SERVER_PORT = 7777;
private static final int IDLE_SECONDS = 600;
public static void main(String[] args) throws Exception {
ClusterTokenServer tokenServer = new SentinelDefaultTokenServer();
ServerTransportConfig serverConfig = new ServerTransportConfig(TOKEN_SERVER_PORT, IDLE_SECONDS);
ClusterServerConfigManager.loadGlobalTransportConfig( serverConfig );
//可以设置多个namespace
Set namespaceSet = new HashSet();
namespaceSet.add("user-service"); //dataId=user-service-flow-rules
ClusterServerConfigManager.loadServerNamespaceSet( namespaceSet );
tokenServer.start();
}
}
SpringBoot启动类:
基于spring-boot SPI机制初始化限流规则:
ClusterTokenInitFunc:从
import java.util.List;
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
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.slots.block.flow.FlowRule;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
public class ClusterTokenInitFunc implements InitFunc {
private String remoteAddress = "192.168.1.105:8848";// nacos配置中心地址
private String groupId = "SENTINEL_GROUP";
private String dataId_postfix = "-flow-rules";
@Override
public void init() throws Exception {
// TODO Auto-generated method stub
loadFlowRuleByNacos();
}
private void loadFlowRuleByNacos() {
// TODO Auto-generated method stub
// 从Nacos上获取配置进行加载
ClusterFlowRuleManager.setPropertySupplier(namespace -> {
// namespace在ClusterServer.java中已配置
String dataId = namespace + dataId_postfix; // user-service-flow-rules、 coupon-service-flow-rules
ReadableDataSource> readableDataSource = new NacosDataSource<>(remoteAddress,
groupId, dataId, source -> JSON.parseObject(source, new TypeReference>() {
}));
return readableDataSource.getProperty();
});
}
}
其中,我的Nacos配置(dataId=user-service-flow-rules)设置如下:
[
{
"resource":"com.lee.demo.dubbo.demo.user.ISentinelService",
"grade":1,
"count":2,
"clusterMode":true,
"clusterConfig":{
"flowId":"1001",
"thresholdType":1,
"fallbackToLocalWhenFail":true
}
},{
"resource":"com.lee.demo.dubbo.demo.user.IHelloService",
"grade":1,
"count":30,
"clusterMode":true,
"clusterConfig":{
"flowId":"1002",
"thresholdType":1,
"fallbackToLocalWhenFail":true
}
}
]
至此sentinel-token-server搭建完成,启动服务
导包
com.alibaba.boot
nacos-config-spring-boot-starter
0.2.12
com.alibaba.spring
spring-context-support
com.alibaba.spring
spring-context-support
1.0.11
com.alibaba.csp
sentinel-apache-dubbo-adapter
1.8.5
com.alibaba.csp
sentinel-transport-simple-http
1.8.5
com.alibaba.csp
sentinel-datasource-nacos
1.8.5
com.alibaba.csp
sentinel-cluster-client-default
1.8.5
加载集群流控规则:
ClusterFlowRuleInitFunc.java
import java.util.List;
import com.alibaba.csp.sentinel.cluster.ClusterStateManager;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientAssignConfig;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager;
import com.alibaba.csp.sentinel.datasource.Converter;
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.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
public class ClusterFlowRuleInitFunc implements InitFunc{
private static final String CLUSTER_SERVER_HOST = "localhost";
private static final int CLUSTER_SERVER_PORT = 7777;
private static final int REQUEST_TIME_OUT = 20000;
private static final String remoteAddress = "192.168.1.105:8848";
private static final String groupId = "SENTINEL_GROUP";
private static final String FLOW_POSTFIX="-flow-rules";
private static final String APP_NAME="user-service";
@Override
public void init() throws Exception {
// TODO Auto-generated method stub
//声明为Token Client
ClusterStateManager.applyState(ClusterStateManager.CLUSTER_CLIENT);
//加载集群限流Token Server
loadClusterClientConfig();
//加载单机限流规则(如果Token Server不可用,退化到单机限流)
initFlowRulesWithDatasource();
}
/**
* 集群限流规则
* */
private void loadClusterClientConfig() {
ClusterClientAssignConfig assignConfig = new ClusterClientAssignConfig();
assignConfig.setServerHost(CLUSTER_SERVER_HOST);
assignConfig.setServerPort(CLUSTER_SERVER_PORT);
ClusterClientConfigManager.applyNewAssignConfig(assignConfig);
ClusterClientConfig clientConfig = new ClusterClientConfig();
clientConfig.setRequestTimeout(REQUEST_TIME_OUT);
ClusterClientConfigManager.applyNewConfig(clientConfig);
}
/**
* 单机限流规则
* */
private void initFlowRulesWithDatasource() {
String dataId = APP_NAME + FLOW_POSTFIX;
// ReadableDataSource> readableDataSource = new NacosDataSource<>(
// remoteAddress, groupId, dataId
// ,source->JSON.parseObject(source,new TypeReference>() {}));
ReadableDataSource> readableDataSource = new NacosDataSource<>(
remoteAddress, groupId, dataId, new Converter>() {
@Override
public List convert(String source) {
// TODO Auto-generated method stub
System.out.println("source:"+source);
List rules = JSON.parseObject(source,new TypeReference>() {});
return rules;
}
});
FlowRuleManager.register2Property(readableDataSource.getProperty());
}
}
Controller:
至此,Token Client配置完成。
接下来启动3个客户端,模拟集群:
Sentinel-Dashboard上可以看到user-service有三台集群机器:
使用jmeter压测工具进行压测:
压测结果如下,可以看到com.lee.demo.dubbo.demo.user.ISentinelService.testSentinel() 接口的qps一直不会超过6个请求,这个峰值是怎么计算的来的呢?因为我上面提到的Nacos集群限流配置dataId=user-service-flow-rules中配置com.lee.demo.dubbo.demo.user.ISentinelService的qps=2,而我们总共有3台机器,因此集群限流max qps:2x3=6
至此,sentinel上线集群限流demo已完成,如有疑问请在评论区评论。