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

 更新时间:2024年11月12日 10:58:33   作者:东皋长歌  
日常开发中会有一个常见的需求,需要限制接口在单位时间内的访问次数,比如说某个免费的接口限制单个IP一分钟内只能访问5次,该怎么实现呢,本文小编给大家介绍了如何基于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 {
    @Autowired
    RedisTemplate 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();
        //设置客户端访问的key
        String 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,则取第一个ip
                ip = 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次访问,接口正常访问

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

到此这篇关于基于Redis实现API接口访问次数限制的文章就介绍到这了,更多相关Redis API接口访问限制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Redis哈希Hash键值对集合操作(查询增加修改)

    Redis哈希Hash键值对集合操作(查询增加修改)

    Redis中的Hash数据 是一个 键值对集合,本文主要介绍了Redis哈希Hash键值对集合操作(查询增加修改),具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01
  • redis数据结构之压缩列表

    redis数据结构之压缩列表

    这篇文章主要介绍了redis数据结构之压缩列表,压缩列表是列表list和hash数据结构的底层实现之一,是redis为了节约内存而开发的,由一系列特殊编码的连续内存块组成的顺序型数据结构,下面详细内容需要的小伙伴可以参考一下
    2022-03-03
  • Redis HyperLogLog数据量统计的实现实例

    Redis HyperLogLog数据量统计的实现实例

    在大数据时代,统计海量数据中的唯一值是一个常见的需求,但同时也是极具挑战性的任务,传统的统计方法可能会消耗大量内存或计算资源,而 Redis 的 HyperLogLog 数据结构 则提供了一种高效、轻量的解决方案,下面就来详细介绍一下HyperLogLog的使用,感兴趣的可以了解一下
    2025-09-09
  • 5分钟搭建redis集群(redis5.0.5)

    5分钟搭建redis集群(redis5.0.5)

    本文主要介绍了5分钟搭建redis集群,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • 在ssm项目中使用redis缓存查询数据的方法

    在ssm项目中使用redis缓存查询数据的方法

    本文主要简单的使用Java代码进行redis缓存,即在查询的时候先在service层从redis缓存中获取数据。如果大家对在ssm项目中使用redis缓存查询数据的相关知识感兴趣的朋友跟随脚本之家小编一起看看吧
    2018-03-03
  • Redis分片集群的实现示例

    Redis分片集群的实现示例

    本文介绍了搭建Redis分片集群,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-12-12
  • mac下设置redis开机启动方法步骤

    mac下设置redis开机启动方法步骤

    这篇文章主要介绍了mac下设置redis开机启动,本文详细的给出了操作步骤,需要的朋友可以参考下
    2015-07-07
  • Redis slowlog使用和实现

    Redis slowlog使用和实现

    slowlog是redis server记录查询命令执行时间的日志模块, 本文就来介绍一下slowlog使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-02-02
  • Redis RDB技术底层原理详解

    Redis RDB技术底层原理详解

    为了使Redis在重启之后仍能保证数据不丢失,需要将数据从内存中以某种形式同步到硬盘中,这一过程就是持久化,本文重点给大家介绍Redis RDB技术底层原理实现方法,一起看看吧
    2021-09-09
  • redis 集群批量操作实现

    redis 集群批量操作实现

    这篇文章主要介绍了redis 集群批量操作,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04

最新评论