[017][web模块]基于计数器的接口幂等性与访问限流设计实战

简介: 本文介绍基于Redis计数器的轻量级接口幂等性与访问限流方案,通过自定义注解(@Idempotent/@AccessLimited)、拦截器及抽象缓存模板,实现声明式、分布式一致的重复提交防护与频率控制,代码简洁、易集成、可扩展。(239字)

[017][web模块]基于计数器的接口幂等性与访问限流设计实战

本项目代码:https://gitee.com/yunjiao-source/tutorials4j/tree/master/framework

在分布式系统中,接口的幂等性保障和访问频率限制是两个常见且重要的需求。本文介绍一套轻量级、基于 Redis 计数器的实现方案,通过自定义注解、拦截器和缓存模板,为 Spring MVC 应用提供声明式的幂等控制与限流能力。

一、功能概览

本方案提供两个核心能力:

  1. 接口幂等性:确保同一请求在有效期内只被成功处理一次,防止重复提交。
  2. 访问频率限制:限制同一请求方在单位时间内的访问次数,防止恶意刷接口或超出配额调用。

两者均基于“计数”逻辑:幂等要求最大计数为 1(首次成功,后续拒绝),限流则允许配置更大的上限(例如 10 次/分钟)。计数器的存储依托 Redis,实现高性能与分布式一致性。

二、核心设计思想

2.1 统一计数器抽象

三个核心类构成计数器体系:

  • AbstractCounterCacheTemplate:抽象模板,封装 Redis 计数器的通用逻辑(自增、上限校验、键值处理等)。
  • IdempotentCacheTemplate:专用于幂等性,构造时 maxTimes = 1
  • AccessLimitedCacheTemplate:专用于访问限制,maxTimes 由注解动态指定。

这样的继承层次使得通用逻辑只需实现一次,不同业务仅需调整上限参数。

2.2 注解 + 拦截器实现声明式控制

  • @Idempotent:标记需要幂等校验的方法。
  • @AccessLimited:标记需要限流的方法,可通过 maxTimes() 指定最大访问次数。

两个拦截器分别处理上述注解,在 preHandle 阶段生成请求的唯一标识,并调用对应的计数器模板进行计数。若计数失败(超过上限),则抛出特定业务异常,由全局异常处理器返回适当响应。

2.3 请求唯一键生成

SessionUtils.generateRequestKey(request) 负责生成标识一个请求的字符串。典型的组成方式包括:

  • 用户 session ID 或 JWT 中的用户 ID
  • 请求 URI
  • 请求参数(JSON 或 form 参数可序列化后取摘要)
  • IP 地址等

该键保证了不同用户、不同接口、不同参数的请求能够被正确区分。

2.4 键 MD5 摘要(可选)

AbstractCounterCacheTemplate.counting 提供了一个 useMd5 参数。当原始键可能过长或含有特殊字符时,可使用 MD5 缩短并规范化键名,节省 Redis 内存。

三、关键代码解析

3.1 抽象计数器模板

public abstract class AbstractCounterCacheTemplate extends AbstractRedisCacheTemplate<String, Integer> {
   
    private int maxTimes = 1;

    public int counting(String key, int maxTimes, boolean useMd5) {
   
        // 1. 校验与键值转换
        String newKey = useMd5 ? SecureUtil.md5(key) : key;
        Integer current = get(newKey);
        if (current == null) current = 0;

        // 2. 首次访问则创建缓存值为1
        if (current == 0) {
   
            create(newKey);
        } else {
   
            // 3. 已达上限则抛异常
            if (current >= maxTimes) {
   
                throw new CounterOverflowException(maxTimes);
            }
            put(newKey, current + 1);
        }
        return current + 1;
    }
}

注意:create(newKey) 会调用 valueGenerator(key) 返回初始值 1,因此首次计数后缓存值为 1。后续每次调用加 1,直到达到 maxTimes

3.2 幂等拦截器

public class IdempotentHandlerInterceptor implements HandlerInterceptor {
   
    private final IdempotentCacheTemplate idempotentCacheTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, ...) {
   
        // 获取方法上的 @Idempotent 注解
        Idempotent idempotent = method.getAnnotation(Idempotent.class);
        if (idempotent != null) {
   
            String key = SessionUtils.generateRequestKey(request);
            idempotentCacheTemplate.counting(key);  // maxTimes 固定为1
        }
        return true;
    }
}

3.3 限流拦截器

与之类似,但传入注解中配置的 maxTimes

AccessLimited limited = method.getAnnotation(AccessLimited.class);
if (limited != null) {
   
    accessLimitedCacheTemplate.counting(key, limited.maxTimes());
}

3.4 自动配置

SecurityConfiguration 实现 WebMvcConfigurer,将两个拦截器注册到 Spring MVC 的拦截器链中。由于使用了 @Configuration(proxyBeanMethods = false),该配置会被 Spring Boot 自动扫描加载(通常通过 META-INF/spring.factories@Import 控制)。

四、使用示例

4.1 添加依赖与配置

确保项目中已引入 Redis 相关依赖以及本框架。在 application.yml 中配置 Redis 连接信息(继承自 AbstractRedisCacheTemplate 的底层配置即可)。

4.2 编写 Controller 并添加注解

@RestController
public class OrderController {
   

    @PostMapping("/order")
    @Idempotent   // 防止重复下单
    public Result createOrder(@RequestBody OrderReq req) {
   
        // 业务逻辑...
    }

    @GetMapping("/search")
    @AccessLimited(maxTimes = 5)  // 每分钟最多5次
    public Result search(String keyword) {
   
        // 搜索逻辑...
    }
}

4.3 异常处理

当重复请求或超出限流阈值时,拦截器会分别抛出 IdempotentExceptionAccessLimitedException。可定义 @RestControllerAdvice 统一返回错误码与提示信息,例如:

@ExceptionHandler(IdempotentException.class)
public ResponseEntity<String> handleIdempotent() {
   
    return ResponseEntity.status(409).body("重复请求,请勿重试");
}

五、扩展与注意事项

5.1 缓存有效期

当前的计数缓存没有设置过期时间。这意味着一旦计数达到上限,将永久拒绝后续请求。实际生产环境中,通常需要配合过期策略:

  • 幂等性:建议对缓存设置合理的过期时间(如 5 分钟、1 小时),由 AbstractRedisCacheTemplateexpire 方法支持。
  • 访问限制:典型的限流是滑动窗口或固定窗口计数器,应设置窗口过期时间(如 1 分钟)。

可以在创建缓存时调用 redisTemplate.expire(key, timeout, unit) 来添加 TTL。

5.2 分布式环境下的原子性

AbstractCounterCacheTemplate 中的 getcreateput 操作并非严格原子。极端并发下可能造成计数不准确。改进方案是使用 Redis 的 INCR 命令,该命令原生支持原子递增,并且可以检查返回值是否超过上限。当前实现适合并发不高的场景,若需高并发精确限流,建议改用 RedisAtomicLong 或 Lua 脚本。

5.3 键的生成策略

SessionUtils.generateRequestKey 的实现决定了安全性与覆盖面。例如:

  • 仅根据用户 ID + URI 可以防止同一用户的重复提交,但无法区分不同参数。
  • 加入请求体摘要可区分不同内容,但会降低缓存命中率。

开发者应根据业务需要重写该方法,或通过配置声明键的组成规则。

5.4 与 Spring 生态的整合

本方案拦截器默认对所有请求路径生效。可以通过配置 addInterceptor 时指定 addPathPatternsexcludePathPatterns 来缩小范围,避免不必要的性能损耗。

六、总结

本文介绍的基于计数器的幂等与限流方案,具有以下优点:

  • 实现简洁:核心逻辑集中在抽象模板中,易于理解和维护。
  • 声明式使用:通过注解即可快速为接口增加保护,对业务代码无侵入。
  • 扩展性好:可轻松调整最大次数、键生成方式、缓存有效期等。

同时也存在改进空间:原子计数、过期策略的完善以及更灵活的限流算法(令牌桶、漏桶)。开发者可根据实际场景在此基础上进行二次开发。

该方案已经应用于多个内部项目,稳定地保障了接口的正确性与系统的可用性。希望本文能为您的分布式接口治理提供参考。

目录
相关文章
|
28天前
|
云安全 人工智能 运维
阿里云ACP认证是什么?含金量高吗?有必要考吗?ACP高级工程师认证费用、题库及问题解答FAQ
阿里云ACP云计算高级工程师认证含金量高,面向架构/开发/运维人员,线下考试(120分钟,100分制,80分及格),官方费用1200元(活动价840元)。涵盖云架构、网络、高可用、安全等核心能力,助力职业发展与企业提效。阿里云认证官网链接:https://t.aliyun.com/U/cLcbll
396 0
|
1月前
|
运维 Java 开发者
[015][web模块]基于Spring Boot的HTTP客户端日志与默认配置实战
本文详解基于Spring Boot的HTTP客户端统一配置方案,支持RestTemplate、RestClient与WebClient三种客户端,实现无侵入的日志记录(请求/响应头、状态码)、默认请求头注入(如X-Request-Id)、非2xx异常自动转换及链路追踪支持,全部通过Customizer与Filter机制自动装配,开箱即用,提升微服务调用可观测性与开发效率。(239字)
187 5
[015][web模块]基于Spring Boot的HTTP客户端日志与默认配置实战
|
2天前
|
机器学习/深度学习 算法 自动驾驶
图解强化学习 |手算DDPG
DDPG(深度确定性策略梯度)是一种面向连续动作空间的Actor-Critic强化学习算法。它采用4网络结构(Actor/Critic及其对应目标网络),结合经验回放与软更新,通过确定性策略梯度优化策略,广泛应用于机器人控制、自动驾驶等场景。(239字)
174 1
|
1月前
|
消息中间件 缓存 NoSQL
【Redis】Redis缓存核心问题:缓存与数据库双写一致性问题、延迟双删、更新策略
本文系统梳理Redis缓存双写一致性核心问题,涵盖强/最终/弱一致性分级、Cache Aside等主流更新策略、延迟双删原理与落地要点,并深入解析binlog CDC兜底方案及高并发优化实践,兼顾理论深度与工业落地。
|
30天前
|
存储 算法 Java
[016][web模块]基于 MDC 的分布式追踪框架设计与实现
本项目基于SLF4J MDC实现轻量级分布式追踪框架,支持Servlet/WebFlux、同步/异步调用,通过过滤器、拦截器与任务装饰器自动传递TraceId,无需引入重量级APM即可实现全链路日志关联。开箱即用,低侵入、易集成。(239字)
208 0
|
1月前
|
人工智能 数据可视化 前端开发
DeepSeek 本地部署落地难:传统 RAG 为何难以支撑
企业AI转型中,本地部署DeepSeek+传统RAG常陷“能建不能用”困局:仅被动检索、无推理链、执行不透明、难对接业务。向量空间JBoltAI推出AgentRAG(V4.3),融合ReAct推理机制与步骤可视化,实现问题拆解、多步验证、可信输出,让私有大模型真正成为可推理、可执行、可信任的企业级智能体。(239字)
123 0
|
2月前
|
Kubernetes Cloud Native 微服务
【微服务与云原生架构】 云原生核心:Docker、K8s架构、核心资源(Pod/Deployment/Service/Ingress)、Pod生命周期、健康检查、滚动更新、自动扩缩容HPA
本文系统梳理微服务与云原生架构的知识体系:以Docker实现环境一致与轻量交付,K8s提供容器编排底座;涵盖Pod、Deployment、Service、Ingress四大核心资源,以及健康检查、滚动更新、HPA自动扩缩容等关键能力,构建高可用、可弹性、可观测的现代分布式应用架构闭环。
|
2月前
|
消息中间件 负载均衡 API
【微服务】微服务通信模式:同步(REST/gRPC)、异步(消息队列)
本文系统梳理微服务通信全体系:涵盖同步(REST/gRPC)与异步(消息队列)两大范式,深入解析原理、选型对比、治理实践及演进趋势,助你构建高可靠、松耦合、可观测的分布式通信架构。
|
2月前
|
JSON 前端开发 Java
【注解】@RequestBody与@ResponseBody 全方位对比全解
本文全方位解析Spring中`@RequestBody`与`@ResponseBody`:从`HttpMessageConverter`底层机制、数据流向(入站反序列化 vs 出站序列化)、使用限制、组合实践(`@RestController`)、配置技巧到高频踩坑,助你深入掌握RESTful接口开发核心。
|
2月前
|
缓存 Java API
从原理到落地:RAG 技术深度拆解与 Java 实战全攻略
RAG(检索增强生成)是解决大模型知识时效性差、易幻觉的核心方案:通过实时检索私有/外部数据,将其作为上下文输入大模型,实现准确、可更新的回答。相比微调,RAG成本低、上线快、维护简,是企业落地LLM的首选架构。
190 1