当前位置: 首页 > news >正文

Redis 限流

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {

    /**
     * 限制次数
     */
    int count() default 15;

    /**
     * 时间窗口,单位为秒
     */
    int seconds() default 60;
}

@Aspect
@Component
public class AccessLimitAspect {

    private static final String LUA_SCRIPT =
            "local key = KEYS[1] " +
                    "local limit = tonumber(ARGV[1]) " +
                    "local current = tonumber(redis.call('get', key) or '0') " +
                    "if current + 1 > limit then " +
                    "   return 0 " +
                    "else " +
                    "   redis.call('INCR', key) " +
                    "   redis.call('EXPIRE', key, ARGV[2]) " +
                    "   return 1 " +
                    "end";

    private static final RedisScript<Long> SCRIPT_INSTANCE = new DefaultRedisScript<>(LUA_SCRIPT, Long.class);

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Before("@annotation(accessLimit)")
    public void checkAccessLimit(JoinPoint joinPoint, AccessLimit accessLimit) throws Throwable {
        validateAccessLimitParams(accessLimit);
        
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        String ipAddr = IpUtils.getIpAddr(request);
        String cacheKey = generateCacheKey(joinPoint, ipAddr);

        Long result = redisTemplate.execute(SCRIPT_INSTANCE,
                Collections.singletonList(cacheKey),
                accessLimit.count(),
                accessLimit.seconds());

        if (result != null && result == 0) {
            throw new RateLimitExceededException("操作过于频繁,请稍后再试");
        }
    }

    private void validateAccessLimitParams(AccessLimit accessLimit) {
        if (accessLimit.count() <= 0 || accessLimit.seconds() <= 0) {
            throw new IllegalArgumentException("Invalid Access Limit parameters");
        }
    }

    private String generateCacheKey(JoinPoint joinPoint, String ipAddr) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String methodName = signature.getMethod().getName();
        return TradeCachePrefix.ACCESS_LIMIT_PREFIX + methodName + "_" + ipAddr;
    }

}

代码逻辑

  1. 定义和初始化变量
    • key = KEYS[1]:从传入的键列表中获取第一个键,作为 Redis 中存储计数器的键。
    • limit = tonumber(ARGV[1]):将传入参数中的第一个值转换为数字,这个值代表允许的最大请求次数(限制)。
    • current = tonumber(redis.call('get', key) or '0'):尝试从 Redis 中获取当前计数器的值。如果键不存在,使用默认值 '0'
  2. 判断是否超出限制
    • if current + 1 > limit then: 检查当前计数加一是否超过限制。
      • 如果超过限制,则返回 0,表示请求被拒绝。
  3. 更新计数器和设置过期时间
    • else: 如果没有超过限制:
      • redis.call('INCR', key): 将计数器加一。
      • redis.call('EXPIRE', key, ARGV[2]): 设置该键的过期时间为传入参数中的第二个值(秒)。
      • 返回 1,表示请求被接受。

应用场景

  • 限流机制:这个脚本通常用于实现基于 Redis 的限流功能。例如,在一定时间窗口内,只允许某个操作执行一定次数,以防止滥用。
  • API 请求限制:可以用于限制 API 的调用频率,每个用户或每个 IP 地址在特定时间内只能调用 API 一定次数。

使用方法

  • 执行脚本时需要传递两个参数
    • 第一个参数是允许的最大请求次数(limit)。
    • 第二个参数是键的过期时间(单位为秒),即在多长时间内重置计数器。

通过这种方式,可以有效地控制访问频率,保护系统资源不被滥用。

相关文章:

  • unity获取指定麦克风的分贝(deepseek)
  • DeepSeek 点燃关键技术突破的科技引擎,驶向未来新航道
  • 下拉框的数据置为危险的‘删除‘状态时弹窗确认
  • Jenkins 给任务分配 节点(Node)、设置工作空间目录
  • 2025最新高维多目标优化:基于城市场景下无人机三维路径规划的导航变量的多目标粒子群优化算法(NMOPSO),MATLAB代码
  • 自动化合约生成与管理:AI与Python的完美结合
  • 阿里云通过docker安装skywalking及elasticsearch操作流程
  • `AdminAdminDTO` 和 `userSession` 对象中的字段对应起来的表格
  • 使用最广泛的Web应用架构
  • linux中的查用命令
  • 万字长文解析:深入理解服务端渲染(SSR)架构与全栈实践指南
  • 基于 JavaWeb 的 Spring Boot 网上商城系统设计和实现(源码+文档+部署讲解)
  • [深度学习][python]yolov12+bytetrack+pyqt5实现目标追踪
  • springboot整合 xxl-job
  • Transformer解析——(五)代码解析及拓展
  • Spark 性能优化(四):Cache
  • 【含文档+PPT+源码】基于Django的新闻推荐系统的设计与实现
  • 【Python爬虫(36)】深挖多进程爬虫性能优化:从通信到负载均衡
  • RPA-实例(UiPath )
  • 一文讲解Redis的持久化方式及各自的区别
  • 晋级四强!WTA1000罗马站:郑钦文2比0萨巴伦卡
  • 消息人士称泽连斯基已启程前往土耳其
  • 万科再获深铁集团借款,今年已累计获股东借款近120亿元
  • 125%→10%、24%税率暂停90天,对美关税开始调整
  • 沙县小吃中东首店在沙特首都利雅得开业,首天营业额超5万元
  • 巴基斯坦全面恢复领空开放