当前位置: 代码网 > it编程>数据库>Redis > 基于Redis实现API接口访问次数限制

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

2024年11月13日 Redis 我要评论
一,概述日常开发中会有一个常见的需求,需要限制接口在单位时间内的访问次数,比如说某个免费的接口限制单个ip一分钟内只能访问5次。该怎么实现呢,通常大家都会想到用redis,确实通过redis可以实现这

一,概述

日常开发中会有一个常见的需求,需要限制接口在单位时间内的访问次数,比如说某个免费的接口限制单个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接口访问限制内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

  • Redis过期键的删除策略分享

    redis过期键删除策略redis是内存型数据库,可对键设置过期时间,当键过期时时怎么淘汰这些键的呢?我们先来想一想,如果让我们设计,我们会想到哪些过期删除策略呢?定时器,创建一个…

    2024年11月08日 数据库
  • redis分布式锁实现示例

    redis分布式锁实现示例

    1.需求我们公司想实现一个简单的分布式锁,用于服务启动初始化执行init方法的时候,只执行一次,避免重复执行加载缓存规则的代码,还有预防高并发流程发起部分,产品... [阅读全文]
  • redis事务如何解决超卖问题

    redis事务如何解决超卖问题

    redis事务解决超卖问题redis的事务提供了一种将多个命令请求打包,然后一次性、按顺序性地执行多个命令的机制。在事务执行期间,服务器不会中断事务而去执行其它... [阅读全文]
  • 关于Redis库存超卖问题的分析

    一、分析问题刚刚秒杀优惠券购买测试的时候是我们自己在页面上点击进行测试的,这跟真实的秒杀场景还是有很大差异的,因为真实的秒杀场景下肯定有无数的用户一起来抢购,一起来点购这个按钮,因…

    2024年11月05日 数据库
  • ELK配置转存redis缓存采集nginx访问日志的操作方法

    在136服务器上部署mysql启动mysql服务可通过以下命令查找安装的软件包怎么查找安装软件的日志文件位置rpm -qc mysql-server,即可显示mysql.log位置…

    2024年11月05日 数据库
  • 如何解决Redis缓存穿透(缓存空对象、布隆过滤器)

    如何解决Redis缓存穿透(缓存空对象、布隆过滤器)

    背景缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库常见的解决方案有两种,分别是缓存空对象和布隆过滤器1.缓... [阅读全文]

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com