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

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

滑动窗口算法

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

基本概念

  • 窗口大小:滑动窗口有一个固定的尺寸,表示你关心的数据的时间范围或数据数量。例如,如果你关注的是过去5分钟内的数据,那么窗口大小就是5分钟。
  • 滑动/移动:随着时间的推移或新数据的到来,窗口会不断向前移动,丢弃最旧的数据点,同时纳入最新的数据点,始终保持窗口内数据的新鲜度。
  • 数据处理:在窗口内的数据会被用来进行各种计算,比如求平均值、最大值、最小值、计数等,具体取决于应用场景。

应用实例

  • 网络流量控制:在网络传输中,滑动窗口常用来控制发送速率,避免拥塞。TCP协议中的拥塞控制就采用了类似滑动窗口的机制来调整数据包的发送速率。
  • 速率限制(Rate Limiting):在Web服务中,滑动窗口算法可以用来实现对API调用或其他请求的速率限制,确保服务不会因为过多的请求而过载。通过控制窗口期内的请求总数或特定时间段内的请求频率,可以平滑系统负载。
  • 交易监控:在金融系统中,滑动窗口可用于监控交易活动,比如检测是否存在异常交易模式,通过分析一段时间内的交易频次和金额分布。

实现要点

  • 数据结构选择:为了高效实现滑动窗口,通常使用队列或哈希表等数据结构来存储窗口内的数据,便于快速插入和删除元素。
  • 窗口边界处理:需要准确地管理窗口的边界,确保当新数据到来时,能及时移除窗口最左边(或最旧)的数据点,同时加入新数据点。
  • 时间复杂度:理想情况下,滑动窗口算法的操作(如添加元素、移除元素、计算窗口内统计量)应该能在常数时间内完成,以保证算法的高效性。

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

要实现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滑动窗口限流器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java版AI五子棋游戏

    Java版AI五子棋游戏

    这篇文章主要为大家详细介绍了Java版AI五子棋游戏,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-09-09
  • Java中遍历ConcurrentHashMap的四种方式详解

    Java中遍历ConcurrentHashMap的四种方式详解

    这篇文章主要介绍了Java中遍历ConcurrentHashMap的四种方式详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • Spring Boot整合ELK实现日志采集与监控

    Spring Boot整合ELK实现日志采集与监控

    这篇文章主要介绍了Spring Boot整合ELK实现日志采集与监控,需要的朋友可以参考下
    2022-06-06
  • SpringBoot集成kafka全面实战记录

    SpringBoot集成kafka全面实战记录

    在实际开发中,我们可能有这样的需求,应用A从TopicA获取到消息,经过处理后转发到TopicB,再由应用B监听处理消息,即一个应用处理完成后将该消息转发至其他应用,完成消息的转发,这篇文章主要介绍了SpringBoot集成kafka全面实战,需要的朋友可以参考下
    2021-11-11
  • 如何在Maven项目中运行JUnit5测试用例实现

    如何在Maven项目中运行JUnit5测试用例实现

    这篇文章主要介绍了如何在Maven项目中运行JUnit5测试用例实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-04-04
  • java中字符串与日期的转换实例

    java中字符串与日期的转换实例

    java中字符串与日期的转换实例,需要的朋友可以参考一下
    2013-05-05
  • springboot+redis 实现分布式限流令牌桶的示例代码

    springboot+redis 实现分布式限流令牌桶的示例代码

    这篇文章主要介绍了springboot+redis 实现分布式限流令牌桶 ,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • springboot+redis+lua实现分布式锁的脚本

    springboot+redis+lua实现分布式锁的脚本

    本文介绍了如何使用Spring Boot、Redis和Lua脚本实现分布式锁,包括实现原理、代码实现和存在的问题,感兴趣的朋友跟随小编一起看看吧
    2024-11-11
  • 解决RedisTemplate调用increment报错问题

    解决RedisTemplate调用increment报错问题

    这篇文章主要介绍了解决RedisTemplate调用increment报错问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-11-11
  • java网络编程之识别示例 获取主机网络接口列表

    java网络编程之识别示例 获取主机网络接口列表

    一个客户端想要发起一次通信,先决条件就是需要知道运行着服务器端程序的主机的IP地址是多少。然后我们才能够通过这个地址向服务器发送信息。
    2014-01-01

最新评论