java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java滑动窗口限流器

Java AOP实现自定义滑动窗口限流器方法详解

作者:柚几哥哥

这篇文章主要介绍了Java AOP实现自定义滑动窗口限流器方法,其中滑动窗口算法弥补了计数器算法的不足,滑动窗口算法把间隔时间划分成更小的粒度,当更小粒度的时间间隔过去后,把过去的间隔请求数减掉,再补充一个空的时间间隔,需要的朋友可以参考下

滑动窗口算法

滑动窗口算法是一种广泛应用于计算机科学和数据分析中的数据流算法,特别适用于处理具有时间序列特性的数据,如网络流量监控、速率限制、数据分析等领域。其核心思想是在一个固定大小的“窗口”内对数据进行统计分析,这个窗口会随着数据的流入而向前滑动,始终保持最新一段时间内的数据统计。

基本概念

应用实例

实现要点

滑动窗口算法因其灵活性和高效性,在众多领域中都有重要应用,是理解和处理时间序列数据的一个非常实用的工具。

要实现AOP结合滑动窗口算法来实现自定义规则的限流,我们可以在原有的基础上进一步扩展,以支持更灵活的配置和更复杂的规则。以下是一个基于Spring AOP和滑动窗口算法的简单示例,包括自定义注解来设置限流规则,以及如何在切面中应用这些规则。

定义缓存注解

首先,定义一个自定义注解来标记需要限流的方法,并允许传入限流的具体规则

package com.example.demo.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WindowRateLimit {
    // 允许的最大请求数
    int limit();
    // 窗口时间长度,单位毫秒
    long timeWindowMilliseconds();
}

滑动窗口限流器

接下来,实现滑动窗口限流器,这里简化处理,直接使用内存实现,实际应用中可能需要基于Redis等持久化存储以适应分布式场景:

核心思想:每次请求进来时,获取当前时间的时间戳,将每次请求的时间戳存储到LinkedList集合中,同时以当前时间为窗口期的结束点,删除往前一个窗口期内所有的请求时间戳,将LinkedList集合剩余数据的个数与自定义设置的窗口期请求峰值进行对比,若等于则直接限流。

package com.example.demo.uitls;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.LinkedList;
/**
 * SlidingWindowRateLimiter : 滑动窗口限流算法
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SlidingWindowRateLimiter implements Serializable {
    /**
     * 请求队列
     */
    private LinkedList<Long> requests = new LinkedList<>();
    /**
     * 最大请求数
     */
    private int maxRequests;
    /**
     * 窗口大小
     */
    private long windowSizeInMilliseconds;
    public SlidingWindowRateLimiter(int maxRequests, long windowSizeInMilliseconds) {
        this.maxRequests = maxRequests;
        this.windowSizeInMilliseconds = windowSizeInMilliseconds;
    }
    /**
     * 判断是否允许请求
     * @return
     */
    public synchronized boolean allowRequest() {
        // 获取当前时间
        long currentTime = System.currentTimeMillis();
        // 清除窗口之外的旧请求
        while (!requests.isEmpty() && currentTime - requests.peekFirst() > windowSizeInMilliseconds) {
            requests.removeFirst();
        }
        // 如果当前窗口请求未达到上限,则允许请求并记录
        if (requests.size() < maxRequests) {
            requests.addLast(currentTime);
            return true;
        } else {
            // 达到限流阈值,拒绝请求
            return false;
        }
    }
}

AOP切面实现

最后,创建AOP切面来应用限流逻辑:

将需要限流的方法所初始化的滑动窗口限流器缓存到Redis中,过期时间设置为对应的窗口时间。

一个窗口时间内,若没有新的请求进来,即存储的请求时间戳都为窗口期外的,因此可以直接清除掉已减少缓存占用空间。

package com.example.demo.aspect;
import com.example.demo.annotation.WindowRateLimit;
import com.example.demo.config.redis.RedisKeyEnum;
import com.example.demo.uitls.RedisUtil;
import com.example.demo.uitls.SlidingWindowRateLimiter;
import jakarta.annotation.Resource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
 * RateLimiterAspect :
 */
@Aspect
@Component
public class SlidingWindowRateLimiterAspect {
    @Resource
    private RedisUtil redisUtil;
    @Around("@annotation(rateLimit)")
    public Object applyRateLimit(ProceedingJoinPoint joinPoint, WindowRateLimit rateLimit) throws Throwable {
        // 获取调用的方法名
        String methodName = joinPoint.getSignature().getName();
        // 获取方法对应的缓存滑动窗口限流器KEY
        String key = RedisKeyEnum.WINDOW_CURRENT_LIMITING.getKey() + methodName;
        // 从缓存中获取滑动窗口限流器
        SlidingWindowRateLimiter rateLimiter = redisUtil.getCacheObject(key);
        // 如果滑动窗口限流器不存在,则创建一个新限流器
        if (rateLimiter == null) {
            rateLimiter = new SlidingWindowRateLimiter(rateLimit.limit(), rateLimit.timeWindowMilliseconds());
        }
        // 如果滑动窗口限流器存在,则判断是否允许请求
        if (!rateLimiter.allowRequest()) {
            throw new RuntimeException("Too many requests, please try again later.");
        }
        // 如果允许请求,则更新滑动窗口限流器,缓存过期时间设置为滑动窗口限流器时间窗口
        redisUtil.setCacheObject(key, rateLimiter, rateLimit.timeWindowMilliseconds(), TimeUnit.MILLISECONDS);
        // 允许执行方法
        return joinPoint.proceed();
    }
}

应用限流注解

在需要做限流的方法上加上注解,在注解参数中设定 允许的最大请求数 和 窗口时间长度(单位毫秒)

package com.example.demo.service.impl;
import com.example.demo.annotation.WindowRateLimit;
import com.example.demo.service.TestService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class TestServiceImpl implements TestService {
    @Override
    @WindowRateLimit(limit = 5, timeWindowMilliseconds = 60L*1000) // 每最多允许5次请求
    public String getContent() {
        return "Hello Word";
    }
}

首次请求时,初始化滑动窗口限流器,记录第一次请求的时间戳

窗口期内,记录了五次请求的时间戳后,已达到我们在注解中设置的窗口期最大请求量

此时接口限流

到此这篇关于Java AOP实现自定义滑动窗口限流器方法详解的文章就介绍到这了,更多相关Java滑动窗口限流器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文