Spring Alibaba Sentinel实现集群限流demo

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已完成,如有疑问请在评论区评论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值