滑动窗口限流算法:基于Redis有序集合的实现与优化

滑动窗口限流算法是一种基于时间窗口的流量控制策略,它将时间划分为固定大小的窗口,并在每个窗口内记录请求次数。通过动态滑动窗口,算法能够灵活调整限流速率,以应对流量的波动。

算法核心步骤

  1. 统计窗口内的请求数量:记录当前时间窗口内的请求次数。
  2. 应用限流规则:根据预设的阈值判断是否允许当前请求通过。

Redis有序集合的应用

Redis的有序集合(Sorted Set)为滑动窗口限流提供了理想的实现方式。每个有序集合的成员都有一个分数(score),我们可以利用分数来定义时间窗口。每当有请求进入时,将当前时间戳作为分数,并将请求的唯一标识作为成员添加到集合中。这样,通过统计窗口内的成员数量,即可实现限流。

实现细节

Redis命令简化

通过Redis的ZADD命令,我们可以将请求的时间戳和唯一标识添加到有序集合中:

ZADD 资源标识 时间戳 请求标识
Java代码实现

以下是基于Java和Redis的滑动窗口限流实现:

public boolean isAllow(String key) {
    ZSetOperations<String, String> zSetOperations = stringRedisTemplate.opsForZSet();
    long currentTime = System.currentTimeMillis();
    long windowStart = currentTime - period;
    zSetOperations.removeRangeByScore(key, 0, windowStart);
    Long count = zSetOperations.zCard(key);
    if (count >= threshold) {
        return false;
    }
    String value = "请求唯一标识(如:请求流水号、哈希值、MD5值等)";
    zSetOperations.add(key, value, currentTime);
    stringRedisTemplate.expire(key, period, TimeUnit.MILLISECONDS);
    return true;
}
Lua脚本优化

为了确保在高并发场景下的原子性操作,我们可以将上述逻辑封装为Lua脚本:

local key = KEYS[1]
local current_time = tonumber(ARGV[1])
local window_size = tonumber(ARGV[2])
local threshold = tonumber(ARGV[3])
redis.call('ZREMRANGEBYSCORE', key, 0, current_time - window_size)
local count = redis.call('ZCARD', key)
if count >= threshold then
    return tostring(0)
else
    redis.call('ZADD', key, tostring(current_time), current_time)
    return tostring(1)
end
完整Java代码
package com.example.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

@Service
public class SlidingWindowRatelimiter {
    private long period = 60 * 1000; // 1分钟
    private int threshold = 3; // 3次

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public boolean isAllow(String key) {
        ZSetOperations<String, String> zSetOperations = stringRedisTemplate.opsForZSet();
        long currentTime = System.currentTimeMillis();
        long windowStart = currentTime - period;
        zSetOperations.removeRangeByScore(key, 0, windowStart);
        Long count = zSetOperations.zCard(key);
        if (count >= threshold) {
            return false;
        }
        String value = "请求唯一标识(如:请求流水号、哈希值、MD5值等)";
        zSetOperations.add(key, value, currentTime);
        stringRedisTemplate.expire(key, period, TimeUnit.MILLISECONDS);
        return true;
    }

    public boolean isAllow2(String key) {
        String luaScript = "local key = KEYS[1]\n" +
                "local current_time = tonumber(ARGV[1])\n" +
                "local window_size = tonumber(ARGV[2])\n" +
                "local threshold = tonumber(ARGV[3])\n" +
                "redis.call('ZREMRANGEBYSCORE', key, 0, current_time - window_size)\n" +
                "local count = redis.call('ZCARD', key)\n" +
                "if count >= threshold then\n" +
                " return tostring(0)\n" +
                "else\n" +
                " redis.call('ZADD', key, tostring(current_time), current_time)\n" +
                " return tostring(1)\n" +
                "end";
        long currentTime = System.currentTimeMillis();
        DefaultRedisScript<String> redisScript = new DefaultRedisScript<>(luaScript, String.class);
        String result = stringRedisTemplate.execute(redisScript, Collections.singletonList(key), String.valueOf(currentTime), String.valueOf(period), String.valueOf(threshold));
        return "1".equals(result);
    }
}

AOP实现限流

为了更方便地应用限流策略,我们可以通过AOP(面向切面编程)来拦截请求并应用限流规则。

自定义注解

首先,定义一个限流注解:

package com.example.demo.controller;

import java.lang.annotation.*;

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    long period() default 60; // 窗口大小(默认:60秒)
    long threshold() default 3; // 阈值(默认:3次)
}
切面实现

然后,实现一个切面来拦截带有@RateLimit注解的方法:

package com.example.demo.controller;

import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Slf4j
@Aspect
@Component
public class RateLimitAspect {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Before("@annotation(rateLimit)")
    public void doBefore(JoinPoint joinPoint, RateLimit rateLimit) {
        long period = rateLimit.period();
        long threshold = rateLimit.threshold();
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String uri = httpServletRequest.getRequestURI();
        Long userId = 123L; // 模拟获取用户ID
        String key = "limit:" + userId + ":" + uri;

        ZSetOperations<String, String> zSetOperations = stringRedisTemplate.opsForZSet();
        long currentTime = System.currentTimeMillis();
        long windowStart = currentTime - period * 1000;
        zSetOperations.removeRangeByScore(key, 0, windowStart);
        Long count = zSetOperations.zCard(key);
        if (count >= threshold) {
            throw new RuntimeException("请求过于频繁!");
        } else {
            zSetOperations.add(key, String.valueOf(currentTime), currentTime);
            stringRedisTemplate.expire(key, period, TimeUnit.SECONDS);
        }
    }
}
使用注解

最后,在需要限流的方法上添加@RateLimit注解:

@RestController
@RequestMapping("/hello")
public class HelloController {
    @RateLimit(period = 30, threshold = 2)
    @GetMapping("/sayHi")
    public void sayHi() {
    }
}

总结

通过Redis有序集合和Lua脚本,我们实现了一个高效且灵活的滑动窗口限流算法。结合AOP,我们可以轻松地将限流策略应用到具体的业务方法中。对于更复杂的流量控制需求,可以参考阿里巴巴的Sentinel框架。

参考链接:

  • Sentinel官方文档
  • AOP实现限流
  • Redis Lua脚本

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/951328.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java 如何传参xml调用接口获取数据

传参和返参的效果图如下&#xff1a; 传参&#xff1a; 返参&#xff1a; 代码实现&#xff1a; 1、最外层类 /*** 外层DATA类*/ XmlRootElement(name "DATA") public class PointsXmlData {private int rltFlag;private int failType;private String failMemo;p…

【C】编译与链接

在本文章里面&#xff0c;我们讲会讲解C语言程序是如何从我们写的代码一步步变成计算机可以执行的二进制指令&#xff0c;并最终执行的。C语言程序运行主要包括两大步骤 -- 编译和链接&#xff0c;接下来我们就来一一讲解。 目录 1 翻译环境和运行环境 2 翻译环境 1&#…

如何设计一个注册中心?以Zookeeper为例

这是小卷对分布式系统架构学习的第8篇文章&#xff0c;在写第2篇文章已经讲过服务发现了&#xff0c;现在就从组件工作原理入手&#xff0c;讲讲注册中心 以下是面试题&#xff1a; 某团面试官&#xff1a;你来说说怎么设计一个注册中心&#xff1f; 我&#xff1a;注册中心嘛&…

Vision Transformer模型详解(附pytorch实现)

写在前面 最近&#xff0c;我在学习Transformer模型在图像领域的应用。图像处理任务一直以来都是深度学习领域的重要研究方向&#xff0c;而传统的卷积神经网络已在许多任务中取得了显著的成绩。然而&#xff0c;近年来&#xff0c;Transformer模型由于其在自然语言处理中的成…

vue实现虚拟列表滚动

<template> <div class"cont"> //box 视图区域Y轴滚动 滚动的是box盒子 滚动条显示的也是因为box<div class"box">//itemBox。 一个空白的盒子 计算高度为所有数据的高度 固定每一条数据高度为50px<div class"itemBox" :st…

Vue指令(下)

Vue指令(下) 参考文献&#xff1a; Vue的快速上手 Vue指令上 文章目录 Vue指令(下)v-bindv-bind小案例v-forv-for小案例v-for中的keyv-model 结语 博客主页: He guolin-CSDN博客 关注我一起学习&#xff0c;一起进步&#xff0c;一起探索编程的无限可能吧&#xff01;让我们…

初学者关于对机器学习的理解

一、机器学习&#xff1a; 1、概念&#xff1a;是指从有限的观测数据中学习(或“猜 测”)出具有一般性的规律&#xff0c;并利用这些规律对未知数据进行预测的方法.机器学 习是人工智能的一个重要分支&#xff0c;并逐渐成为推动人工智能发展的关键因素。 2、使用机器学习模型…

Vue篇-05

5 vuex 5.1 vuex是什么 概念:专门在 Vue 中实现集中式状态(数据)管理的一个Vue 插件&#xff0c;对 vue 应用中多个组件的共享状态进行集中式的管理(读/写)&#xff0c;也是一种组件间通信的方式&#xff0c;且适用于任意组件间通信。Github 地址: https://github.com/vuejs/…

Vue3(elementPlus) el-table替换/隐藏行箭头,点击整行展开

element文档链接&#xff1a; https://element-plus.org/zh-CN/component/form.html 一、el-table表格行展开关闭箭头替换成加减号 注&#xff1a;Vue3在样式中修改箭头图标无效&#xff0c;可能我设置不对&#xff0c;欢迎各位来交流指导 转变思路&#xff1a;隐藏箭头&…

opencv的NLM去噪算法

NLM&#xff08;Non-Local Means&#xff09;去噪算法是一种基于图像块&#xff08;patch&#xff09;相似性的去噪方法。其基本原理是&#xff1a; 图像块相似性&#xff1a;算法首先定义了一个搜索窗口&#xff08;search window&#xff09;&#xff0c;然后在该窗口内寻找…

NineData云原生智能数据管理平台新功能发布|2024年12月版

本月发布 7 项更新&#xff0c;其中重点发布 2 项、功能优化 5 项。 重点发布 数据库 Devops - Oracle 非表对象支持可视化创建与管理 Oracle 非表对象&#xff0c;包括视图&#xff08;View&#xff09;、包&#xff08;Package&#xff09;、存储过程&#xff08;Procedur…

计算机网络 —— 网络编程(TCP)

计算机网络 —— 网络编程&#xff08;TCP&#xff09; TCP和UDP的区别TCP (Transmission Control Protocol)UDP (User Datagram Protocol) 前期准备listen &#xff08;服务端&#xff09;函数原型返回值使用示例注意事项 accpect &#xff08;服务端&#xff09;函数原型返回…

eNSP之家----ACL实验入门实例详解(Access Control List访问控制列表)(重要重要重要的事说三遍)

ACL实验&#xff08;Access Control List访问控制列表&#xff09;是一种基于包过滤的访问控制技术&#xff0c;它可以根据设定的条件对接口上的数据包进行过滤&#xff0c;允许其通过或丢弃。访问控制列表被广泛地应用于路由器和三层交换机。 准备工作 在eNSP里面部署设备&a…

PySide6基于QSlider实现QDoubleSlider

我在写小工具的时候&#xff0c;需要一个支持小数的滑动条。 我QSpinBox都找到了QDoubleSpinBox&#xff0c;QSlider愣是没找到对应的东西。 网上有好多对QSlider封装实现QDoubleSlider的文章。 似乎Qt真的没有这个东西&#xff0c;需要我们自行实现。 于是我也封装了一个&…

即插即用,无缝集成各种模型,港科大蚂蚁等发布Edicho:图像编辑一致性最新成果!

文章链接&#xff1a;https://arxiv.org/pdf/2412.21079 项目链接&#xff1a;https://ezioby.github.io/edicho/ 亮点直击 显式对应性引导一致性编辑&#xff1a;通过将显式图像对应性融入扩散模型的去噪过程&#xff0c;改进自注意力机制与分类器自由引导&#xff08;CFG&…

福建双色荷花提取颜色

提取指定颜色 HSV双色荷花代码验证 参照《OpenCV图像处理技术》 HSV 要用HSV的色调、饱和度和亮度来提取指定颜色。 双色荷花 农林大学金山校区观音湖 代码 import cv2 import numpy as npimgcv2.imread("./sucai6/hua.jpg") cv2.imshow("SRC",img) h…

关于重构一点简单想法

关于重构一点简单想法 当前工作的组内&#xff0c;由于业务开启的时间正好处于集团php-》go技术栈全面迁移的时间点&#xff0c;组内语言技术栈存在&#xff1a;php、go两套。 因此需求开发过程中通常要考虑两套技术栈的逻辑&#xff0c;一些基础的逻辑也没有办法复用。 在这…

【操作系统】课程 7设备管理 同步测练 章节测验

7.1知识点导图 它详细地展示了I/O系统的层次结构、I/O硬件和软件的组成以及它们的功能。下面是对图中内容的文字整理&#xff1a; I/O设备分类 按使用特性分类 输入设备&#xff1a;键盘、鼠标等输出设备&#xff1a;打印机、绘图仪等交互式设备&#xff1a;显示器等 按传输速率…

用 Python 绘制可爱的招财猫

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​​​​​ ​​​​​​​​​ ​​​​ 招财猫&#xff0c;也被称为“幸运猫”&#xff0c;是一种象征财富和好运的吉祥物&#xff0c;经常…

【Vue.js 组件化】高效组件管理与自动化实践指南

文章目录 摘要引言组件命名规范与组织结构命名规范目录组织 依赖管理工具自动化组件文档生成构建自动引入和文档生成的组件化体系代码结构自动引入组件配置使用 Storybook 展示组件文档自动生成 代码详解QA 环节总结参考资料 摘要 在现代前端开发中&#xff0c;组件化管理是 V…