分布式接口限流与防重复提交实现方案

一、背景

在分布式系统架构下,接口的稳定性与安全性是系统设计的核心挑战之一。随着业务规模的扩大和高并发场景的增多,接口面临的​​流量洪峰​ ​、​​恶意刷单​ ​、​​重复提交​​等问题日益突出:

  • ​流量过载风险​:突发流量(如活动促销、热点事件)可能导致核心接口超出系统承载能力,引发服务雪崩;
  • ​恶意请求攻击​:未授权用户通过脚本高频调用接口,消耗服务器资源(如CPU、数据库连接),影响正常业务;
  • ​重复提交隐患​:用户因网络延迟重复点击按钮,或前端未做防重校验时,可能导致业务逻辑重复执行(如重复下单、重复扣款)。

传统单机限流方案(如Guava RateLimiter)仅能控制单个应用实例的流量,无法满足分布式系统多实例间的协同限流需求。而基于Redis的分布式限流方案,凭借其​​全局状态共享​ ​、​​高并发支持​ ​和​​原子性操作​​的特性,成为解决分布式限流问题的首选。

Redisson作为Redis的Java客户端增强工具,内置了RRateLimiter(分布式限流器)组件,支持基于时间窗口的令牌桶算法,能够轻松实现跨实例的分布式限流。本文将结合Spring Boot框架,通过自定义注解与AOP切面,演示如何基于Redisson快速实现一套​​低侵入、可配置、分布式​​的接口限流与防重复提交方案。

二、依赖引入

在Spring Boot项目中,通过redisson-spring-boot-starter快速集成Redisson,只需添加以下Maven依赖(版本号根据实际情况调整):

XML 复制代码
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>${redisson.version}</version>
</dependency>

三、限流注解定义

为了实现无侵入式的限流控制,我们定义一个自定义注解@RateLimit,用于标记需要限流的方法。该注解支持配置​​时间窗口​ ​(单位:秒)和​​限流键前缀​​,灵活适配不同业务场景的限流需求。

java 复制代码
import java.lang.annotation.*;

/**
 * 分布式限流注解(基于 Redisson 实现)
 * 用于标记需要限制调用频率的方法,防止接口被高频调用或重复提交
 */
@Target(ElementType.METHOD)       // 作用于方法级别
@Retention(RetentionPolicy.RUNTIME)  // 运行时保留,可通过反射获取
@Documented                     // 文档可见
@Inherited                      // 支持子类继承
public @interface RateLimit {
    /**
     * 限流时间窗口(单位:秒)
     * 表示在多长时间内允许的最大请求次数(默认60秒)
     */
    int seconds() default 60;

    /**
     * 限流键前缀
     * 用于生成Redis中限流规则的唯一键,建议根据业务场景命名(如"order:submit")
     */
    String key_pre();
}

四、分布式限流切面实现

通过Spring AOP的环绕通知(@Around),拦截所有标记了@RateLimit的方法,在方法执行前进行限流校验。核心逻辑包括:

  1. ​生成唯一限流键​:结合限流键前缀与请求参数的SHA256摘要,确保不同参数的请求独立计数;
  2. ​初始化限流器​ :通过Redisson的RRateLimiter创建分布式限流器,设置时间窗口内的最大请求次数;
  3. ​执行限流校验​:若当前请求超出限流阈值,则拦截请求并返回提示;否则放行请求,执行原方法。
  4. **更换限流规则:**标记为3处的代码可与标记为4处的代码置换位置,实现两种限流逻辑
java 复制代码
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import zyy.oa.framework.common.annotations.RateLimit;
import zyy.oa.framework.common.utils.Result;
import cn.hutool.core.lang.Assert;

import java.lang.reflect.Method;
import java.time.Duration;

@Aspect
@Component
@AllArgsConstructor
public class CommonAspect {

    private final RedissonClient redissonClient;  // Redisson客户端注入

    /**
     * 环绕通知:拦截所有标记了@RateLimit的方法,执行限流校验
     * @param pjp 切入点对象
     * @param rateLimit 限流注解参数
     * @return 原方法执行结果或限流提示
     * @throws Throwable 异常
     */
    @Around("@annotation(rateLimit)")
    public Object around(ProceedingJoinPoint pjp, RateLimit rateLimit) throws Throwable {
        // 1. 解析方法参数,生成唯一限流键
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        Object[] args = pjp.getArgs();  // 获取方法入参
        String paramsJson = JSONObject.toJSONString(args);  // 参数转JSON字符串
        String requestKey = DigestUtil.sha256Hex(paramsJson);  // 参数SHA256摘要(防重复关键)
        String limitKey = "rate_limit:" + rateLimit.key_pre() + ":" + requestKey;  // 完整限流键

        // 2. 初始化Redisson限流器(仅首次请求时设置)
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(limitKey);
        boolean isFirstRequest = rateLimiter.trySetRate(
            RateType.OVERALL,  // 限流类型:全局(所有节点共享)
            1,                 // 时间窗口内最大请求数(此处设为1次/窗口)
            Duration.ofSeconds(rateLimit.seconds())  // 时间窗口长度
        );

        // 3.确保键过期时间与窗口一致
        rateLimiter.expire(Duration.ofSeconds(rateLimit.seconds()));  

        // 4. 执行限流校验:尝试获取令牌(无令牌则拦截),非首次请求触发限流(首次请求可能因初始化延迟未生效,不拦截)
        // 可根据需要与3处代码位置置换实现不计入限流请求
        Assert.isTrue(rateLimiter.tryAcquire()&&firstTime, "限定时间内重复请求,已忽略");

        // 5. 限流通过,执行原方法
        return pjp.proceed();
    }
}

五、使用示例

在需要限流的业务方法上添加@RateLimit注解,即可快速启用分布式限流功能。以下是一个典型的接口限流场景:

java 复制代码
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import zyy.oa.framework.common.utils.Result;
import java.util.Map;

@RestController
public class FlowController {

    private final FlowDefinitionService flowDefinitionService;

    // 示例:限制"自动发起流程"接口的调用频率
    @Operation(summary = "自动发起流程")
    @PostMapping("/autoStartFlow")
    @RateLimit(
        key_pre = "flow:autoStart",  // 限流键前缀(业务相关,可以根据接口自定义)
        seconds = 60                  // 时间窗口60秒(1分钟内仅允许1次请求)
    )
    public Result autoStartFlow(
        @Parameter(description = "流程分类") @RequestParam String category,
        @Parameter(description = "流程名称") @RequestParam(required = false) String name,
        @Parameter(description = "变量集合") @RequestBody Map<String, Object> variables
    ) {
        return flowDefinitionService.autoStartFlow(category, name, variables);
    }
}

六、方案特性与应用场景

方案特性

特性 说明
​分布式支持​ 基于Redis存储限流状态,天然支持多实例集群环境,各节点限流规则同步
​低侵入性​ 通过注解+AOP实现,业务代码无需修改,仅需标记需要限流的方法
​参数敏感性​ 结合请求参数生成SHA256摘要,不同参数的请求视为独立操作,避免误限
​灵活配置​ 支持自定义时间窗口(seconds)和键前缀(key_pre),适配不同业务场景
​高并发友好​ Redisson的RRateLimiter基于Lua脚本实现原子操作,保证高并发下的准确性

应用场景

  • ​核心接口防刷​:如支付接口、短信发送接口,限制单位时间内的调用次数;
  • ​防重复提交​:表单提交、订单确认等操作,防止用户重复点击导致业务重复执行;
  • ​资源消耗保护​:批量任务接口、数据导出接口,限制高频调用对服务器资源的消耗;
  • ​业务风控​:配合用户行为分析,对异常高频请求(如短时间内大量不同参数调用)进行拦截。

总结

本文基于Redisson实现了分布式接口限流与防重复提交方案,通过自定义注解和AOP切面,以低侵入的方式解决了分布式系统中的流量控制问题。该方案兼顾了灵活性、准确性和可维护性,适用于大多数需要限制接口调用频率的业务场景。实际使用中,可根据业务需求调整限流时间窗口、最大请求数等参数,或扩展注解功能(如支持动态限流策略),进一步适配复杂业务场景。