本文采用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")@ResponseBodypublic 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")@ResponseBodypublic 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")@ResponseBodypublic 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();//连接//限流的keyString 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;}@Autowiredprivate Isacquire isacquire;/*** 使用 redis + lua 进行分布式限流(计数器限流)* @return*/@RequestMapping("/limiterTest4")@ResponseBodypublic 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的返回值为luaredisScript.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 thenredis.call("expire",key,tonumber(expire));return 1;
end
--判断次数是否超过限制,超过返回0
if afterValue > tonumber(limitCount) thenreturn 0;
endreturn 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限流方案改成一个注解方式,在需要的方法上加注解更灵活和方便,将在下一篇文章中展示。