基于redis实现API接口访问次数限制

一,概述

日常开发中会有一个常见的需求,需要限制接口在单位时间内的访问次数,比如说某个免费的接口限制单个IP一分钟内只能访问5次。该怎么实现呢,通常大家都会想到用redis,确实通过redis可以实现这个功能,下面实现一下。

二,常见错误

固定时间窗口

有人设计了一个在每分钟内只允许访问1000次的限流方案,如下图01:00s-02:00s之间只允许访问1000次。这种设计的问题在于,请求可能在01:59s-02:00s之间被请求1000次,02:00s-02:01s之间被请求了1000次,这种情况下01:59s-02:01s间隔0.02s之间被请求2000次,很显然这种设计是错误的。

三, 实现

1,基于滑动事件窗口

在指定的时间窗口内次数是累积的,超过阈值,都会限制。

2,流程如下

3,代码实现

前提:pom文件引入redis,Spring AOP等

(1)添加注解RequestLimit
package com.xxx.demo.aspect;import java.lang.annotation.*;/*** 接口访问频率注解,默认一分钟只能访问10次*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestLimit {// 限制时间 单位:秒(默认值:一分钟)long period() default 60;// 允许请求的次数(默认值:10次)long count() default 10;
}
(2)添加切面实现注解的限制访问逻辑
package com.xxx.demo.aspect;import com.xgd.demo.commons.ErrorCode;
import com.xgd.demo.handler.BusinessException;
import com.xgd.demo.util.IpUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.concurrent.TimeUnit;/*** @date 2024/11/8 上午8:43*/
@Aspect
@Component
@Log4j2
public class RequestLimitAspect {@AutowiredRedisTemplate redisTemplate;@Pointcut("@annotation(requestLimit)")public void controllerAspect(RequestLimit requestLimit) {}@Around("controllerAspect(requestLimit)")public Object doAround(ProceedingJoinPoint joinPoint, RequestLimit requestLimit) throws Throwable {// 从注解中获取限制次数和窗口时间long period = requestLimit.period();long limitCount = requestLimit.count();// 请求ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();assert attributes != null;HttpServletRequest request = attributes.getRequest();String ip = IpUtil.getIpFromRequest(request);String uri = request.getRequestURI();//设置客户端访问的keyString key = "req_limit_".concat(uri).concat(ip);ZSetOperations zSetOperations = redisTemplate.opsForZSet();// 添加当前时间戳,分数为当前时间戳long currentMs = System.currentTimeMillis();zSetOperations.add(key, currentMs, currentMs);// 设置窗口时间作为过期时间redisTemplate.expire(key, period, TimeUnit.SECONDS);// 移除掉不在窗口里的数据zSetOperations.removeRangeByScore(key, 0, currentMs - period * 1000);// 查询窗口内已经访问过的次数Long count = zSetOperations.zCard(key);if (count > limitCount) {log.error("接口拦截:{} 请求超过限制频率【{}次/{}s】,IP为{}", uri, limitCount, period, ip);throw new BusinessException(ErrorCode.REQUEST_LIMITED.getCode(), ErrorCode.REQUEST_LIMITED.getMessage());}// 继续执行请求return  joinPoint.proceed();}
}

上面里面请求被拦截,是抛出了一个自定义的业务异常,大家可以根据自己的情况自己定义。

(3)同时附上上面中引用到自定义工具类
package com.xxx.demo.util;import jakarta.servlet.http.HttpServletRequest;
import java.util.Objects;/*** @date 2024/11/8 上午9:06*/
public class IpUtil {private static final String X_FORWARDED_FOR_HEADER = "X-Forwarded-For";private static final String X_REAL_IP_HEADER = "X-Real-IP";/*** 从请求中获取IP** @return IP;当获取不到时,返回null*/public static String getIpFromRequest(HttpServletRequest request ) {return getRealIp(request);}/*** 获取请求的真实IP,优先级从高到低为:<br/>* 1.从请求头X-Forwarded-For中获取ip,并且只获取第一个ip(从左到右) <br/>* 2.从请求头X-Real-IP中获取ip <br/>* 3.使用{@link HttpServletRequest#getRemoteAddr()}方法获取ip** @param request 请求对象,必须不能为null* @return ip*/private static String getRealIp(HttpServletRequest request) {Objects.requireNonNull(request, "request must be not null");String ip = request.getHeader(X_FORWARDED_FOR_HEADER);if (ip != null && !ip.isBlank()) {int delimiterIndex = ip.indexOf(',');if (delimiterIndex != -1) {// 如果存在多个ip,则取第一个ipip = ip.substring(0, delimiterIndex);}return ip;}ip = request.getHeader(X_REAL_IP_HEADER);if (ip != null && !ip.isBlank()) {return ip;} else {return request.getRemoteAddr();}}
}
(4)使用注解

这里限制为10秒内只允许访问3次,超过就抛出异常

(5)访问测试

前3次访问,接口正常访问

后面的访问,返回自定义异常的结果

如果对你有帮助,记得点赞关注哟!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uni-app小程序开发(1)

下载软件就不多赘述了。 直接上代码&#xff0c;写过wep端的vue看这个小程序就简单很多&#xff0c;不需要搞那么多麻烦事情&#xff0c;直接编译器就创建好了基础模版。 1、项目结构 暂时知道这么多&#xff0c;后续再补充 2、页面创建、导航栏设置、基础属性设置 在pages中…

【C++】哈希表封装 unordered_map 和 unordered_set 的实现过程

C语法相关知识点可以通过点击以下链接进行学习一起加油&#xff01;命名空间缺省参数与函数重载C相关特性类和对象-上篇类和对象-中篇类和对象-下篇日期类C/C内存管理模板初阶String使用String模拟实现Vector使用及其模拟实现List使用及其模拟实现容器适配器Stack与QueuePriori…

优化策略:揭秘钢条切割与饼干分发的算法艺术

引言 在生活中&#xff0c;钢条和饼干看似风马牛不相及&#xff0c;但它们的分割与分发却隐藏着惊人的数学魅力。如何最大化利润&#xff1f;如何用有限的资源最大程度满足需求&#xff1f;这便是算法世界中的艺术。今天&#xff0c;我们来揭秘钢条切割与饼干分发的算法设计。本…

SQL,力扣题目1709,访问日期之间最大的空档期

一、力扣链接 LeetCode_1709 二、题目描述 表&#xff1a; UserVisits ------------------- | Column Name | Type | ------------------- | user_id | int | | visit_date | date | ------------------- 该表没有主键&#xff0c;它可能有重复的行 该表包含用户访问…

第七篇: BigQuery中的复杂SQL查询

BigQuery中的复杂SQL查询 背景与目标 在数据分析中&#xff0c;我们通常需要从多个数据源中获取信息&#xff0c;以便进行深入的分析。这时&#xff0c;BigQuery提供的JOIN、UNION和子查询等复杂SQL语句非常实用。本文将以Google BigQuery的公共数据集为例&#xff0c;介绍如何…

【C++】条件变量condition_variable

文章目录 1. 条件变量定义及特点2. 代码示例3. wait方法4. wait_for方法5. notify_all和notify_one6. 思考 1. 条件变量定义及特点 条件变量 用于在线程之间协调共享资源的访问。它允许一个线程等待特定条件的满足(如某个值的变化)&#xff0c;而另一个线程在条件满足时通知(或…

C++ 线程初始化编译报错

这是一个很简单的开启一个线程, 用于演示一个线程和生命周期之间的错误,但是还没有把这个错误暴露出来, 就遇见了一个编译问题. 线程中执行指定逻辑的代码 线程的执行方法, 声明写在了ThreadRun.h 实现写在 ThreadRun.cpp中. class ThreadRun { public: void func(); };void T…

SPIRE: Semantic Prompt-Driven Image Restoration 论文阅读笔记

这是一篇港科大学生在google research 实习期间发在ECCV2024的语义引导生成式修复的文章&#xff0c;港科大陈启峰也挂了名字。从首页图看效果确实很惊艳&#xff0c;尤其是第三行能用文本调控修复结果牌上的字。不过看起来更倾向于生成&#xff0c;对原图内容并不是很复原&…

Backend - Python 爬取网页数据并保存在Excel文件中

目录 一、导入依赖 二、前端爬取页面数据 1. html 中 2. js 中 三、后端获取数据&#xff0c;并存储到 Excel 文件中 1. 后端接收数据 2. 后端存储数据 &#xff08;1&#xff09;系统代码固定存储位置 &#xff08;2&#xff09;用户可选择文件对话框的存储位置 一、…

Dubbo负载均衡

负载均衡策略与配置细节 Dubbo 内置了 client-based 负载均衡机制&#xff0c;如下是当前支持的负载均衡算法&#xff0c;结合上文提到的自动服务发现机制&#xff0c;消费端会自动使用 Weighted Random LoadBalance 加权随机负载均衡策略 选址调用。 如果要调整负载均衡算法…

软件工程中的创建型设计模式:工厂方法模式与抽象工厂模式

目录 1. 工厂方法模式&#xff08;Factory Method Pattern&#xff09; 1.1 核心概念 1.2 应用场景 1.3 优点 2. 抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09; 2.1 核心概念 2.2 应用场景 2.3 优点 3. 联系与区别 4. 拓展知识 5. 结语 在软件工程…

FFmpeg 4.3 音视频-多路H265监控录放C++开发十二:在屏幕上显示多路视频播放,可以有不同的分辨率,格式和帧率。

上图是在安防领域的要求&#xff0c;一般都是一个屏幕上有显示多个摄像头捕捉到的画面&#xff0c;这一节&#xff0c;我们是从文件中读取多个文件&#xff0c;显示在屏幕上。 一 改动UI文件 这里我们要添加两个label&#xff0c;为了区分我们设置一下背景色&#xff08;这个是…

java中线程与集合的面试题

在 Java 面试中&#xff0c;线程和集合相关的知识是非常常见的考察点。以下是几个典型的问题及答案&#xff1a; 线程相关面试题 什么是线程&#xff1f; 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中&#xff0c;是进程中的实际运作单位。一个进程可以有多…

前言2、VS(Visual Studio)-2022使用

早前用VS-2010编译平台&#xff0c;进行C语言编程学习。 现如今&#xff0c;为了适应未来发展趋势以及日新月异的新功能&#xff0c;就此转到VS-2022编译平台&#xff1b; 由于都是VS编译平台&#xff0c;大多数基础功能都类似&#xff0c;关于一些基础操作可参考前言1&#…

深入了解逻辑回归:机器学习中的经典算法

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

Tomcat(3) Tomcat是哪种类型的服务器?

Tomcat是一个开源的Java Servlet容器&#xff0c;同时也是一个轻量级的Web服务器。它主要用于实现Java Servlet和JavaServer Pages (JSP)规范&#xff0c;使得开发者能够部署和运行Java Web应用程序。 Tomcat的主要类型 1. Servlet容器 Tomcat的核心功能之一是作为Servlet容…

软件测试基础十四(python 类与对象)

类与对象的意义 面向对象编程的基础 模拟现实世界&#xff1a;在Python中&#xff0c;类和对象是面向对象编程&#xff08;OOP&#xff09;的核心概念。类就像是制造对象的蓝图或者模板&#xff0c;它定义了对象的属性&#xff08;数据成员&#xff09;和方法&#xff08;函数…

技术总结(二十二)

一、Redis缓存更新 在更新Redis缓存时&#xff0c;通常有两种策略&#xff1a; 更新缓存&#xff08;Cache-Aside Pattern&#xff09;&#xff1a;先从缓存获取数据&#xff0c;如果缓存中没有数据或数据已经过期&#xff0c;则从数据库中获取最新数据&#xff0c;并更新到缓…

[High Speed Serial ] Xilinx

Xilinx 高速串行数据接口 收发器产品涵盖了当今高速协议的方方面面。GTH 和 GTY 收发器提供要求苛刻的光互连所需的低抖动&#xff0c;并具有世界一流的自适应均衡功能&#xff0c;具有困难的背板操作所需的 PCS 功能。 Versal™ GTY &#xff08;32.75Gb/s&#xff09;&…

基于CNN-RNN的影像报告生成

项目源码获取方式见文章末尾&#xff01; 600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【PaddleNLP的FAQ问答机器人】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生成】 4.【CNN模型实现…