springmvc限流解决方案

本文采用3中限流方案:

1,谷歌的guava框架
2,使用redis技术
3,使用lua + redis 技术

限流方案类型

1,令牌桶限流(guava) 2,计数器限流(redis)

各位看官可根据自己的项目情况选择方案!!!

package com.example.webtest.controller;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.example.webtest.service.Isacquire;
import com.google.common.util.concurrent.RateLimiter;

import redis.clients.jedis.Jedis;

/**
 * 令牌桶限流
 * @author zack
 * QQ群:167350653
 */
@Controller
public class TokenBucketLimitController {
	
	final String SUCCESS = "success";
	final String FAIL = "fail";
	
	
	/**
	 * 定义一个令牌桶,每秒钟放两个令牌
	 */
	final RateLimiter rateLimiter = RateLimiter.create(2);
	
	/**
	 * 普通限流测试(令牌桶限流)(缺点:不适合分布式)
	 * <dependency>
	*	    <groupId>com.google.guava</groupId>
	*	    <artifactId>guava</artifactId>
	*	    <version>23.0</version>
	*	</dependency>
	 * @param name
	 * @return
	 */
	@RequestMapping("/limiterTest")
	@ResponseBody
	public String limiterTest(String name) {
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
		
		boolean acquire = false;
		//vip用户,2秒返回一次,一次获取4个令牌
		if(name!=null && "vip".equals(name)) {
			//尝试获取令牌
			acquire = rateLimiter.tryAcquire(4);
		}else {
			//尝试获取令牌
			acquire = rateLimiter.tryAcquire();
		}
		//判断获取令牌的结果
		if(acquire) {
			System.out.println("success获取令牌成功,当前时间:"+format.format(new Date()));
			return SUCCESS;
		}else {
			//当前的一秒钟之内已经没有令牌,返回失败,当前请求被限流
			System.out.println("fail获取令牌失败(被限流)");
			return FAIL+"(被限流)";
		}
	}
	
	/**
	 * 限流阻塞测试(令牌桶限流)(缺点:不适合分布式)
	 * @param name
	 * @return
	 */
	@RequestMapping("/limiterTest2")
	@ResponseBody
	public String limiterTest2(String name) {
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		速率是每秒5个许可
		RateLimiter rateLimiter = RateLimiter.create(5);
        for(int i = 0;i<100;i++){
            //开启限流阻塞
            rateLimiter.acquire();
            System.out.println("下标:"+i+"  success获取令牌成功,当前时间:"+format.format(new Date()));
        }
        return SUCCESS;
	}
	
	/**
	 * 计数器分布式限流,使用redis保持原子性(计数器限流)
	 * pom文件引入jedis
	 *<dependency>
     *		<groupId>redis.clients</groupId>
     *		<artifactId>jedis</artifactId>
     *		<version>3.0.1</version>
     *</dependency>
	 * @param name
	 * @return
	 */
	@RequestMapping("/limiterTest3")
	@ResponseBody
	public String limiterTest3(String name) {
		String result = SUCCESS;
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		//获取redis连接
		Jedis jedis = new Jedis("127.0.0.1", 6379);
		jedis.auth("123456"); 
		jedis.connect();//连接


		//限流的key
		String key = "limiterTest3";
		//对key的value进行+1操作,aferValue就是+1后的值
		long aferValue = jedis.incr(key);
		if(aferValue == 1) {
			System.out.println("第一次");
			//设置key 60秒后失效
			jedis.expire(key, 60);
		}else {
			//判断是否超过限制10次
			if(aferValue > 10) {
				result = FAIL;
			}
		}
		System.out.println("请求次数:"+aferValue+"  当前时间:"+format.format(new Date())+"  能否成功连接:"+result);
		return result;
	}
	
	
	
	
	@Autowired
	private Isacquire isacquire;
	
	/**
	 * 使用 redis + lua 进行分布式限流(计数器限流)
	 * @return
	 */
	@RequestMapping("/limiterTest4")
	@ResponseBody
	public String limiterTest4() {
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
		//得到限流判断结果  ,15秒最多10次请求
		if(isacquire.acquire("limiterTest4",15,10)) {
			System.out.println("success获取令牌成功,当前时间:"+format.format(new Date()));
			return SUCCESS;
		}else {
			//当前的一秒钟之内已经没有令牌,返回失败,当前请求被限流
			System.out.println("fail获取令牌失败(被限流)");
			return FAIL+"(被限流)";
		}
	}
	
}

下面为limiterTest4方法所用到的service类

package com.example.webtest.service;

import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;

import redis.clients.jedis.Jedis;

@Service
public class Isacquire {
	
private DefaultRedisScript<Long> redisScript;
	
	/**
	 * 是否被限流
	 * @param limitKey 关键字
	 * @param second 限制秒数
	 * @param limitCount 限制次数
	 * @return
	 * 
	 * pom文件需导入
	 * <dependency>
		    <groupId>org.springframework.data</groupId>
		    <artifactId>spring-data-redis</artifactId>
		</dependency>
	 * 
	 */
	public boolean acquire(String limitKey,Integer second,Integer limitCount) {
		//获取redis连接
		Jedis jedis = new Jedis("127.0.0.1",6379);
		jedis.auth("123456"); 
		jedis.connect();//连接
		
		redisScript = new DefaultRedisScript<>();
		//设置lua的返回值为lua
		redisScript.setResultType(Long.class);
		//加载我们自己写的lua脚本
		redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("rateLimiter.lua")));
		//执行lua脚本   参数说明:(lua脚本,key的数量,限制10次,60秒)
		Long result = (Long) jedis.eval(redisScript.getScriptAsString(),1,limitKey,String.valueOf(limitCount),String.valueOf(second));
		
		if(result == 0) {
			System.out.println("被限流");
			return false;
		}
		return true;
	}
	
}

rateLimiter.lua文件

--获取传入的参数key
local key = KEYS[1];
--限制参数
local limitCount = ARGV[1];
--限制周期
local expire = ARGV[2];
--对指定的key进行+1的操作
local afterValue = redis.call('incr',key);
--第一次,设置失效时间, 备注:tonumber转为number
if afterValue == 1 then
	redis.call("expire",key,tonumber(expire));
	return 1;
end
--判断次数是否超过限制,超过返回0
if afterValue > tonumber(limitCount) then
	return 0;
end
	return 1;

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.6.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>webtest</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>webtest</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<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>
		
		<!-- 引入Guava实现:令牌桶限流 -->
		<dependency>
		    <groupId>com.google.guava</groupId>
		    <artifactId>guava</artifactId>
		    <version>23.0</version>
		</dependency>


		<dependency>
		    <groupId>redis.clients</groupId>
		    <artifactId>jedis</artifactId>
		</dependency>
		
		<dependency>
		    <groupId>org.springframework.data</groupId>
		    <artifactId>spring-data-redis</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

备注:如需下载项目源码请在QQ群中167350653下载

另外更优雅的方式是将redis+lua限流方案改成一个注解方式,在需要的方法上加注解更灵活和方便,将在下一篇文章中展示。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值