1.背景
1.什么是单机限流?
小伙伴们或许遇到过下图这样的限流配置
又或者是这样的Nacos动态配置限流规则:
以上这些是什么限流?没错,就是单机限流,那么单机限流有什么弊端呢?
假设我们集群部署3台机器(NodeA/NodeB/NodeC),在某时刻,同时有25个请求进来,假设NodeA收到12个请求,NodeB 8个,NodeC 5个,并且每个单机限流qps=10,那么这个时候,NodeA将会触发限流,有两个请求BLOCK掉,这是由于可能发生的流量不均导致NodeA节点流量过高导致限流,这是因为每个机器都是自己管自己,有没有一种方法能够统筹调度呢?这就得提到集群限流了
2.什么是集群限流
集群限流就是,弄一台Token Server,每个客户端机器作为Token Client,有请求进来,Token Client就会向Token Server拿令牌,拿到令牌则不限流,反正则限流。其中Token Server可以作为独立运行的项目,也可以内嵌式内嵌至每个节点中(需将其中一台机器设置为Token Server,如果Token Server节点所在机器宕机,可以将其他Client节点设置成server,sentinel有相关api提供该操作)
例如上面的例子,集群内有3个节点,如果每个节点能承受10的请求,那么加起来就是3x10=30个请求。也就是说只要qps不超过30个请求都不会触发限流。
2.sentinel实现集群限流
1. 以spring-boot项目构建 sentinel-token-server
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lee.sentinel.tokenserver</groupId>
<artifactId>sentinel-token-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sentinel-token-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--slf4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<!--nacos配置中心-->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-config-spring-boot-starter</artifactId>
<version>0.2.12</version>
<!--由于jar包冲突,此处排除冲突jar,重新导入高版本jar-->
<!--nacos-spring-context-1.1.1.jar需要比spring-context-support-1.0.8.jar更高版本的jar-->
<exclusions>
<exclusion>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--重新引入jar包-->
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
<version>1.0.11</version>
</dependency>
<!--sentinel-cluster-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-cluster-server-default</artifactId>
<version>1.8.5</version>
</dependency>
<!--sentinel dashboard-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.5</version>
</dependency>
<!--sentinel dashboard -> nacos 配置动态感知-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.8.5</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
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<String> namespaceSet = new HashSet<String>();
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<String, List<FlowRule>> readableDataSource = new NacosDataSource<>(remoteAddress,
groupId, dataId, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
}));
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搭建完成,启动服务
2. 配置客户端Token Client
导包
<!--nacos配置中心-->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-config-spring-boot-starter</artifactId>
<version>0.2.12</version>
<!--由于jar包冲突,此处排除冲突jar,重新导入高版本jar-->
<!--nacos-spring-context-1.1.1.jar需要比spring-context-support-1.0.8.jar更高版本的jar-->
<exclusions>
<exclusion>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--重新引入jar包-->
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
<version>1.0.11</version>
</dependency>
<!--sentinel-dubbo-adapter -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-apache-dubbo-adapter</artifactId>
<version>1.8.5</version>
</dependency>
<!--sentinel dashboard-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.5</version>
</dependency>
<!--sentinel dashboard -> nacos 配置动态感知-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.8.5</version>
</dependency>
<!--sentinel-token-cluster-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-cluster-client-default</artifactId>
<version>1.8.5</version>
</dependency>
加载集群流控规则:
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<String, List<FlowRule>> readableDataSource = new NacosDataSource<>(
// remoteAddress, groupId, dataId
// ,source->JSON.parseObject(source,new TypeReference<List<FlowRule>>() {}));
ReadableDataSource<String, List<FlowRule>> readableDataSource = new NacosDataSource<>(
remoteAddress, groupId, dataId, new Converter<String, List<FlowRule>>() {
@Override
public List<FlowRule> convert(String source) {
// TODO Auto-generated method stub
System.out.println("source:"+source);
List<FlowRule> rules = JSON.parseObject(source,new TypeReference<List<FlowRule>>() {});
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已完成,如有疑问请在评论区评论。