本文采用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限流方案改成一个注解方式,在需要的方法上加注解更灵活和方便,将在下一篇文章中展示。