个人技术分享

一、自定义注解结合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博客