一、自定义注解结合AOP的实现
1、新建注解接口类 【RequestLimit】
package com.joker.cloud.linserver.conf.annotation;
import com.joker.cloud.linserver.conf.annotation.requestlimit.RequestLimitType;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestLimit {
/**
* 限流方式 (固定窗口 、 滑动窗口 、 令牌桶、 漏桶算法)
*
* @return
*/
RequestLimitType type() default RequestLimitType.FIXED_WINDOW;
/**
* 限流访问次数
*
* @return
*/
int limitCount() default 100;
/**
* 限流时间范围
*
* @return
*/
int time() default 60;
/**
* 限流时间范围 时间单位
*
* @return
*/
TimeUnit unit() default TimeUnit.SECONDS;
/**
* 漏出或生成令牌时间间隔, 单位 毫秒 (当type为TOKEN、LEAKY_BUCKET时生效)
*
* @return
*/
long period() default 1000;
/**
* 每次生成令牌数或者漏出水滴数 (当type为TOKEN、LEAKY_BUCKET时生效)
*
* @return
*/
int limitPeriodCount() default 10;
}
2、新建限流类型类 【RequestLimitType】
package com.joker.cloud.linserver.conf.annotation.requestlimit;
/**
* RequestLimitType
*
* @author joker
* @version 1.0
* 2024/3/29 13:16
**/
public enum RequestLimitType {
/**
* 固定窗口算法
*/
FIXED_WINDOW,
/**
* 滑动窗口算法
*/
SLIDE_WINDOW,
/**
* 令牌桶算法
*/
TOKEN_BUCKET,
/**
* 漏铜算法
*/
LEAKY_BUCKET
}
3、新建AOP切面类【RequestLimitAop】
package com.joker.cloud.linserver.conf.annotation;
import com.joker.cloud.linserver.conf.annotation.requestlimit.RequestLimitFactory;
import com.joker.cloud.linserver.service.RequestLimitService;
import com.joker.cloud.linserver.vo.userinfo.RequestLimitDto;
import com.joker.common.exception.ServiceException;
import com.joker.common.message.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* RequestLimitAop
*
* @author joker
* @version 1.0
* 2024/3/29 13:33
**/
@Slf4j
@Aspect
@Component
public class RequestLimitAop {
@Autowired
private RequestLimitFactory factory;
// @Pointcut("execution(public * com.joker.cloud.linserver.controller.limit..*.*(..))")
@Pointcut("@annotation(com.joker.cloud.linserver.conf.annotation.RequestLimit)")
public void aspect() {
}
/**
* 方法执行前
*
* @param joinPoint
*/
@Before("aspect()")
public void doBefore(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
RequestLimit limit = method.getAnnotation(RequestLimit.class);
log.info("RequestLimitAop 【限流切面】 doBefore method:{}, requestLimitType:{}", signature.getName(), limit.type());
RequestLimitService requestLimitService = factory.build(limit.type());
if (requestLimitService == null) {
throw new RuntimeException("【限流类型错误】");
}
RequestLimitDto requestLimitDto = new RequestLimitDto();
requestLimitDto.setRequestLimit(limit);
requestLimitDto.setKey(signature.getName());
if(requestLimitService.checkRequestLimit(requestLimitDto)){
log.error("RequestLimitAop 【限流切面】 doBefore method:{}, requestLimitType:{} 流量控制", signature.getName(), limit.type());
throw new ServiceException(ResultCode.REQUESTS_ARE_TOO_FREQUENT);
}
}
/**
* 方法执行后
*
* @param joinPoint
*/
@Before("aspect()")
public void doAfter(JoinPoint joinPoint) {
}
}
二、固定窗口算法
算法说明:
固定窗口算法算是四种算法中最简单,最暴力的算法了,在单位时间的开始进行计数,当访问次数超过阈值,后续的所有访问均被丢弃,直到下一个单位时间开始,计数被置为0。
算法缺陷:
1.不能平滑限流:
假如我们设置一分钟只能访问N次,如果有一个用户在疯狂的恶意请求接口,在一分钟的第一秒疯狂请求,那这一分钟的接口访问额度将会在这一秒被耗光,导致其他正常的用户无法访问到接口。
2.存在临界值问题:
假如我们设置十分钟只能访问100次,如果有一个用户在第一秒访问了1次后,一直等到第十分钟的最后一秒访问99次,然后在下一秒再访问全部100次。此时耗时2秒内总请求为199次,未达到十分钟内限流100次的需求。
代码实现:
我们使用Spring中的RedisTemplate的原子整型来实现该功能,在接口第一次触发时,设置增加键值,并将键的有效期设置为单位时间,后续接口访问调用getAndIncrement()方法获取当前值,并将redis中值增加1,然后判断当前值是否超过访问阈值,如果超过则直接丢弃请求。
FixedWindowRequestLimitServiceImpl.class
package com.joker.cloud.linserver.service.impl;
import com.joker.cloud.linserver.conf.annotation.RequestLimit;
import com.joker.cloud.linserver.conf.annotation.requestlimit.RequestLimitType;
import com.joker.cloud.linserver.conf.redis.RedisUtils;
import com.joker.cloud.linserver.service.RequestLimitService;
import com.joker.cloud.linserver.vo.userinfo.RequestLimitDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalTime;
import java.util.Objects;
/**
* FixedWindowRequestLimitServiceImpl
*
* @author joker
* @version 1.0
* 2024/3/29 16:07
**/
@Slf4j
@Service
public class FixedWindowRequestLimitServiceImpl implements RequestLimitService {
@Resource
private RedisUtils redisUtils;
@Override
public boolean checkRequestLimit(RequestLimitDto requestLimitDto) {
String key = "fixed:window:" + requestLimitDto.getKey();
RequestLimit requestLimit = requestLimitDto.getRequestLimit();
log.info("限流配置:{} {} 内允许访问 {} 次", requestLimit.time(), requestLimit.unit(), requestLimit.limitCount());
log.info("访问时间【{}】", LocalTime.now());
Object obj = redisUtils.get(key);
// Redis 记录为空则设值为1
if (Objects.isNull(obj)) {
redisUtils.set(key, 1, requestLimit.time());
} else {
int numberOfTimes = Integer.parseInt(obj.toString());
if (numberOfTimes >= requestLimit.limitCount()) {
log.error("FixedWindowRequestLimitServiceImpl checkRequestLimit Number out of limit, request:{}", requestLimitDto);
return true;
} else {
redisUtils.incrBy(key, 1);
}
}
return false;
}
@Override
public RequestLimitType getType() {
return RequestLimitType.FIXED_WINDOW;
}
}
限流模拟:
aopFixedWindowTest()
编写接口模拟配置固定窗口调用(固定窗口方式,120秒内 最多调用 5次)
@RequestLimit(type = RequestLimitType.FIXED_WINDOW, limitCount = 5, time = 120)
@ApiOperation("测试AOP拦截 - 固定窗口限流")
@GetMapping("/aop-fixed-window-test")
public Result<String> aopFixedWindowTest() {
String str = "测试限流 aop拦截效果 - 固定窗口限流";
System.out.println(str);
return Result.success(str);
}
postman调用
使用postman进行aop-fixed-window-test 接口调用
redis查看
此时查看redis可见,该key下记录次数为1,。(未到达设置的limitCount个数前,每次调用都会+1,直到与limitCount相等,后续的调用均会被限流处理)
连续调用至与limitCount相等
再次调用时,抛出指定错误码,调用被限制
三、滑动窗口算法
算法说明:
滑动窗口算法我们以每次接口调用的当前时间,回推单位时间,计算这段时间接口调用的数量是否超出阈值,未超出则放行,超出则丢弃。在计算时,我们每次都以当前调用时间往回推单位时间,就像是一个固定长度的窗口一直在顺着时间滑动,所以叫做滑动窗口。
算法缺陷:
随着访问接口的增多,zset里面堆积的数据将会越来越多
代码实现:
我们这里使用redis的zset来实现,当每一次请求进来的时候,value保持唯一,可以用UUID生成,而score可以用当前时间戳表示,因为redis提供了range方法可以用score来计算区间值数量。
SlideWindowRequestLimitServiceImpl.class
package com.joker.cloud.linserver.service.impl;
import com.joker.cloud.linserver.conf.annotation.RequestLimit;
import com.joker.cloud.linserver.conf.annotation.requestlimit.RequestLimitType;
import com.joker.cloud.linserver.service.RequestLimitService;
import com.joker.cloud.linserver.vo.userinfo.RequestLimitDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalTime;
import java.util.Objects;
import java.util.UUID;
/**
* SlideWindowRequestLimitServiceImpl
*
* @author joker
* @version 1.0
* 2024/3/29 16:45
**/
@Slf4j
@Service
public class SlideWindowRequestLimitServiceImpl implements RequestLimitService {
@Resource
private RedisTemplate<Object, Object> redisTemplate;
@Override
public boolean checkRequestLimit(RequestLimitDto requestLimitDto) {
String key = "slide:window:" + requestLimitDto.getKey();
RequestLimit requestLimit = requestLimitDto.getRequestLimit();
long currentTimeMillis = System.currentTimeMillis();
long duringTime = requestLimit.unit().toMillis(requestLimit.time());
Long count = redisTemplate.opsForZSet().count(key, currentTimeMillis - duringTime, currentTimeMillis);
//清除有效期外的数据
redisTemplate.opsForZSet().removeRangeByScore(key, 0, currentTimeMillis - duringTime - 1f);
log.info("限流配置:{} {} 内允许访问 {} 次", requestLimit.time(), requestLimit.unit(), requestLimit.limitCount());
log.info("访问时间【{}】", LocalTime.now());
//检测是否达到限流值
if (Objects.nonNull(count) && count >= requestLimit.limitCount()) {
log.error("SlideWindowRequestLimitServiceImpl checkRequestLimit Number out of limit, request:{}", requestLimitDto);
return true;
}
redisTemplate.opsForZSet().add(key, UUID.randomUUID().toString(), currentTimeMillis);
return false;
}
@Override
public RequestLimitType getType() {
return RequestLimitType.SLIDE_WINDOW;
}
}
限流模拟:
aopSlideWindowTest()
编写接口模拟配置滑动窗口调用(滑动窗口方式,120秒内 滑动最多调用 5次)
@RequestLimit(type = RequestLimitType.SLIDE_WINDOW, limitCount = 5, time = 120)
@ApiOperation("测试AOP拦截 - 滑动窗口限流")
@GetMapping("/aop-slide-window-test")
public Result<String> aopSlideWindowTest() {
String str = "测试限流 aop拦截效果 - 滑动窗口限流";
System.out.println(str);
return Result.success(str);
}
postman调用
使用postman进行aop-slide-window-test 接口调用
redis查看
此时查看redis可见,该key下zset集合中数据为1条,。(未到达设置的limitCount个数前,每次调用都会增加1条数据,直到指定time时间段内(score)的条数与limitCount相等,后续的调用均会被限流处理)
连续调用至指定time时间段内(score)的条数与limitCount相等
再次调用时,抛出指定错误码,调用被限制
四、漏桶算法
算法说明:
漏桶算法刚好和令牌桶算法相逆,漏桶算法为以恒定的速度从桶中漏出水滴,而我们调用接口为向桶中加水滴,当桶中容量未满时,则表示接口调用量未达到阈值,放行,若桶中容量已满,无法增加水滴进入,则表示拦截请求。
算法缺陷:
漏桶算法会以固定的速率将请求输出到网络上,当网络拥塞时,由于上游发送的数据流量过大而无法承载,此时的漏桶算法对于域名下默认所有流量都进行等比例丢弃,该算法可能导致浪费网络资源并带来应用程序响应延迟甚至瘫痪。
代码实现:
LeakyBucketRequestLimitServiceImpl.class
package com.joker.cloud.linserver.service.impl;
import com.joker.cloud.linserver.conf.annotation.RequestLimit;
import com.joker.cloud.linserver.conf.annotation.requestlimit.RequestLimitType;
import com.joker.cloud.linserver.service.LimitUtilsService;
import com.joker.cloud.linserver.service.RequestLimitService;
import com.joker.cloud.linserver.vo.userinfo.RequestLimitDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalTime;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
/**
* LeakyBucketRequestLimitServiceImpl
*
* @author joker
* @version 1.0
* 2024/5/21 10:50
**/
@Slf4j
@Service
public class LeakyBucketRequestLimitServiceImpl implements RequestLimitService {
@Resource
private RedisTemplate<Object, Object> redisTemplate;
@Resource
private LimitUtilsService limitUtilsService;
@Value("${request-limit.scan-package}")
private String scanPackage;
@Autowired
private ResourcePatternResolver resourcePatternResolver;
@javax.annotation.Resource(name = "leakyBucketPopThreadPoolScheduler")
private ThreadPoolTaskScheduler scheduler;
@Override
public boolean checkRequestLimit(RequestLimitDto requestLimitDto) {
String key = "leaky:bucket:" + requestLimitDto.getKey();
RequestLimit requestLimit = requestLimitDto.getRequestLimit();
log.info("限流配置:{} {} 内允许访问 {} 次", requestLimit.time(), requestLimit.unit(), requestLimit.limitCount());
log.info("访问时间【{}】", LocalTime.now());
Long size = redisTemplate.opsForList().size(key);
if (Objects.nonNull(size) && size >= requestLimit.limitCount()) {
log.error("LeakyBucketRequestLimitServiceImpl checkRequestLimit Number out of limit, request:{}", requestLimitDto);
return true;
}
redisTemplate.opsForList().leftPush(key, UUID.randomUUID().toString());
return false;
}
/**
* 定数流出令牌
*/
@PostConstruct
public void popToken() {
List<RequestLimitDto> list = limitUtilsService.getTokenLimitList(resourcePatternResolver, RequestLimitType.LEAKY_BUCKET, scanPackage);
if (list.isEmpty()) {
log.error("未扫描到使用 漏桶限流 注解的方法,结束生成令牌线程");
return;
}
for (RequestLimitDto requestLimitDto : list) {
scheduler.scheduleAtFixedRate(() -> {
String key = "leaky:bucket:" + requestLimitDto.getKey();
redisTemplate.opsForList().trim(key, requestLimitDto.getRequestLimit().limitPeriodCount(), -1);
log.error("【{}】漏出 {} 个水滴", key, requestLimitDto.getRequestLimit().limitPeriodCount());
}, requestLimitDto.getRequestLimit().period());
}
}
@Override
public RequestLimitType getType() {
return RequestLimitType.LEAKY_BUCKET;
}
}
getTokenLimitList()
package com.joker.cloud.linserver.service.impl;
import com.google.common.collect.Lists;
import com.joker.cloud.linserver.conf.annotation.RequestLimit;
import com.joker.cloud.linserver.conf.annotation.requestlimit.RequestLimitType;
import com.joker.cloud.linserver.service.LimitUtilsService;
import com.joker.cloud.linserver.vo.userinfo.RequestLimitDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.List;
import java.util.Set;
/**
* LimitUtilsServiceImpl
*
* @author joker
* @version 1.0
* 2024/5/21 11:06
**/
@Slf4j
@Service
public class LimitUtilsServiceImpl implements LimitUtilsService {
@Override
public List<RequestLimitDto> getTokenLimitList(ResourcePatternResolver resourcePatternResolver, RequestLimitType requestLimitType, String scanPackage) {
List<RequestLimitDto> arrayList = Lists.newArrayList();
try {
Resource[] resources = resourcePatternResolver.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + org.springframework.util.ClassUtils.convertClassNameToResourcePath(scanPackage) + "/**/*.class");
MetadataReaderFactory metaReader = new CachingMetadataReaderFactory();
for (Resource resource : resources) {
MetadataReader metadataReader = metaReader.getMetadataReader(resource);
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
Set<MethodMetadata> annotatedMethods = annotationMetadata.getAnnotatedMethods(RequestLimit.class.getCanonicalName());
for (MethodMetadata annotatedMethod : annotatedMethods) {
RequestLimit requestLimit = annotatedMethod.getAnnotations().get(RequestLimit.class).synthesize();
if(!requestLimitType.equals(requestLimit.type())){
continue;
}
RequestLimitDto requestLimitDto = new RequestLimitDto();
requestLimitDto.setRequestLimit(requestLimit);
requestLimitDto.setKey(annotatedMethod.getMethodName());
arrayList.add(requestLimitDto);
}
}
return arrayList;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
限流模拟:
aopLeakyBucketTest()
编写接口模拟配置 漏桶限流 调用(漏桶方式,桶容量最大为5,每3s流出1个水滴)
@RequestLimit(type = RequestLimitType.LEAKY_BUCKET, limitCount = 5, limitPeriodCount = 1, period = 3000)
@ApiOperation("测试AOP拦截 - 漏桶-限流")
@GetMapping("/aop-leaky-bucket-test")
public Result<String> aopLeakyBucketTest() {
String str = "测试限流 aop拦截效果 - 漏桶-限流";
System.out.println(str);
return Result.success(str);
}
postman调用
使用postman进行aop-leaky-bucket-test 接口调用
redis查看
此时查看redis可见,该key下list集合中数据为1条,。(未到达设置的limitCount个数前,每次调用都会增加1条数据,直到与limitCount相等,后续的调用均会被限流处理,同时list集合内数据又会按照配置period时间,进行流出limitPeriodCount个,保持动态删减(流出))
连续调用至与limitCount相等
再次调用时,抛出指定错误码,调用被限制
但是每当时间过去配置的period时长,list则会流出limitPeriodCount个(如等待配置的几秒钟(period时长)后,肉眼可见这个key内的list条数减少,此时如果没超过limitCount(5)条,则又可继续请求,达到动态平衡)
五、令牌桶算法
算法说明:
令牌桶算法为我们以一个恒定的速度向一个桶内存放令牌,我们调用接口时从令牌桶内取令牌,如果能取到令牌,那么接口就放行, 如果没有取到令牌,则表示接口调用到达阈值,进行拦截
算法缺陷:
代码实现:
这里我们使用redis的list来实现,我们使用leftPush和rightPop即左进右出来对list数据进行操作,因为redis的这两个指令是具有原子性的,所以也不存在多线程下限流判断异常的问题
TokenBucketRequestLimitServiceImpl.class
package com.joker.cloud.linserver.service.impl;
import com.alibaba.nacos.common.utils.Objects;
import com.joker.cloud.linserver.conf.annotation.RequestLimit;
import com.joker.cloud.linserver.conf.annotation.requestlimit.RequestLimitType;
import com.joker.cloud.linserver.service.LimitUtilsService;
import com.joker.cloud.linserver.service.RequestLimitService;
import com.joker.cloud.linserver.vo.userinfo.RequestLimitDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* TokenBucketRequestLimitServiceImpl
* 令牌桶 限流
*
* @author joker
* @version 1.0
* 2024/5/21 16:14
**/
@Slf4j
@Service
public class TokenBucketRequestLimitServiceImpl implements RequestLimitService {
@Resource
private RedisTemplate<Object, Object> redisTemplate;
@Resource
private LimitUtilsService limitUtilsService;
@Value("${request-limit.scan-package}")
private String scanPackage;
@Autowired
private ResourcePatternResolver resourcePatternResolver;
@javax.annotation.Resource(name = "tokenBucketPushThreadPoolScheduler")
private ThreadPoolTaskScheduler scheduler;
@Override
public boolean checkRequestLimit(RequestLimitDto requestLimitDto) {
String key = "token:bucket:" + requestLimitDto.getKey();
RequestLimit requestLimit = requestLimitDto.getRequestLimit();
log.error("限流配置:每 {} 毫秒 生成 {} 个令牌,最大令牌数:{}", requestLimit.period(), requestLimit.limitPeriodCount(), requestLimit.limitCount());
Object rightPop = redisTemplate.opsForList().rightPop(key);
if (Objects.isNull(rightPop)) {
log.error("TokenBucketRequestLimitServiceImpl checkRequestLimit Number out of limit, request:{}", requestLimitDto);
return true;
}
return false;
}
@PostConstruct
public void pushToken() {
List<RequestLimitDto> list = limitUtilsService.getTokenLimitList(resourcePatternResolver, RequestLimitType.TOKEN_BUCKET, scanPackage);
if (list.isEmpty()) {
log.error("未扫描到使用 令牌限流 注解的方法,结束生成令牌线程");
return;
}
for (RequestLimitDto requestLimitDto : list) {
scheduler.scheduleAtFixedRate(() -> {
String key = "token:bucket:" + requestLimitDto.getKey();
Long tokenSize = redisTemplate.opsForList().size(key);
int size = tokenSize == null ? 0 : tokenSize.intValue();
if (size >= requestLimitDto.getRequestLimit().limitCount()) {
return;
}
int addSize = Math.min(requestLimitDto.getRequestLimit().limitPeriodCount(), requestLimitDto.getRequestLimit().limitCount() - size);
List<String> addList = new ArrayList<>(addSize);
for (int index = 0; index < addSize; index++) {
addList.add(UUID.randomUUID().toString());
}
redisTemplate.opsForList().leftPushAll(key, addList);
log.error("【{}】生成令牌丢入令牌桶,当前令牌数:{},令牌桶容量:{}", key, size + addSize, requestLimitDto.getRequestLimit().limitCount());
}, requestLimitDto.getRequestLimit().period());
}
}
@Override
public RequestLimitType getType() {
return RequestLimitType.TOKEN_BUCKET;
}
}
限流模拟:
aopLeakyBucketTest()
编写接口模拟配置 令牌桶限流 调用(令牌桶方式,桶容量最大为5,每3s生成一个令牌丢入其中)
@RequestLimit(type = RequestLimitType.TOKEN_BUCKET, limitCount = 5, limitPeriodCount = 1, period = 3000)
@ApiOperation("测试AOP拦截 - 令牌桶-限流")
@GetMapping("/aop-token-bucket-test")
public Result<String> aopTokenBucketTest() {
String str = "测试限流 aop拦截效果 - 令牌桶-限流";
System.out.println(str);
return Result.success(str);
}
redis查看
令牌会随着配置的时间period生成指令period个数放入rediskey的 list中最大只能存放limitCount个令牌再此集合中
无任何调用,令牌桶达到limitCount个数则停止生成
配置的period(3s)生成limitPeriodCount(1)个
postman调用
此时使用postman进行 aop-token-bucket-test 接口调用
此时查看redis可见,该key下list集合中数据为4条,随着接口的成功请求,会减少一条。
此时调用接口频繁请求,只要请求速度大于生产令牌速度,那么就会造成redis 中list集合的令牌被全部消耗(此时新令牌还未生成的间隙),请求便会被限流处理,无令牌可用
查看redis 中list集合的令牌被全部消耗(此时新令牌还未生成的间隙)
学习自:【限流】从0开始实现常见的四种限流算法,基于Redis结合AOP实现【固定窗口】、【滑动窗口】、【令牌桶算法】、【漏桶算法】_aop redis滑动时间窗口算法-CSDN博客