10. 软件设计&架构-经典架构问题-幂等+限流

文章目录


前言

幂等性

在高并发场景的架构里,防止重复提交的幂等性是必须要保证的。

比如说支付功能,用户发起支付,如果后台没有做幂等校验,刚好用户手抖多点了几下,于是后台就可能多次受到同一个订单请求,不做幂等很容易就让用户重复支付了,这样用户体验是无法忍受的。


一、分布式接口幂等性(核心)

1. 什么是幂等

一个接口执行多次,结果和执行一次完全一致场景:重复提交、网络重试、超时重发、MQ重复消费

2. 常用4种幂等方案(流程图)

整体流转图

四种主流方案

  1. 全局Token令牌(前端拦截,最常用)

  2. 唯一业务主键(订单号、流水号 唯一索引)

  3. Redis分布式锁 + 防重标记

  4. MQ消费:消息唯一ID + 幂等表

3. 代码案例:Token令牌幂等(SpringBoot+Redis)

步骤

  1. 前端先获取唯一幂等Token

  2. 提交业务请求携带Token

  3. 接口拦截器校验Token,删除/标记

  4. 重复请求直接拦截

java 复制代码
// 1. 生成幂等Token
@RestController
@RequestMapping("/idempotent")
publicclass IdempotentController {

    @Autowired
    private StringRedisTemplate redisTemplate;

    // 获取幂等Token
    @GetMapping("/getToken")
    public R getToken() {
        String token = UUID.randomUUID().toString();
        // 过期时间10分钟
        redisTemplate.opsForValue().set("idempotent:token:" + token, "1", 10, TimeUnit.MINUTES);
        return R.ok(token);
    }
}

自定义注解 + 拦截器

java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
}
java 复制代码
@Component
publicclass IdempotentInterceptor implements HandlerInterceptor {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 非注解方法直接放行
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod method = (HandlerMethod) handler;
        if (!method.hasMethodAnnotation(Idempotent.class)) {
            return true;
        }

        // 获取请求头Token
        String token = request.getHeader("idempotent-token");
        if (StringUtils.isEmpty(token)) {
            throw new RuntimeException("非法请求,缺少幂等令牌");
        }
        String key = "idempotent:token:" + token;
        // 原子操作:删除成功=第一次请求;删除失败=重复请求
        Boolean delete = redisTemplate.delete(key);
        if (Boolean.FALSE.equals(delete)) {
            throw new RuntimeException("请求重复提交,禁止重复操作");
        }
        return true;
    }
}

业务接口使用

java 复制代码
@PostMapping("/submit")
@Idempotent
public R submitOrder() {
    // 正常下单业务
    return R.ok("下单成功");
}

二、限流体系总览

限流3种实现层级

  1. 应用层单机限流:Guava RateLimiter(单机、简单)

  2. 网关层分布式限流:Nginx+Lua(全局、高性能)

  3. 分布式中间件限流:Sentinel/Redis+Lua(微服务常用)

限流核心算法

  • 令牌桶 :Guava 默认,突发流量友好

  • 漏桶:固定流出速率,抗突发差

  • 滑动窗口:平滑限流,Sentinel常用


三、Guava 单机限流(令牌桶)

1. 流程图

2. 引入依赖

xml 复制代码
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version>
</dependency>

3. 完整代码案例

java 复制代码
@RestController
@RequestMapping("/limit")
publicclass GuavaLimitController {

    // 每秒放行 5 个请求:令牌桶容量=5
    privatestaticfinal RateLimiter RATE_LIMITER = RateLimiter.create(5.0);

    @GetMapping("/guava")
    public R guavaLimit() {
        // 尝试获取令牌,非阻塞
        boolean acquire = RATE_LIMITER.tryAcquire();
        if (!acquire) {
            return R.error("系统繁忙,请求限流,请稍后重试");
        }
        return R.ok("请求正常处理");
    }
}

特点

  • ✅ 轻量、零配置、代码极简

  • 单机限流,集群环境失效

  • ✅ 令牌桶,支持突发流量


四、Nginx + Lua 分布式限流(生产常用)

1. 架构流程图

2. 实现原理

  • 基于 Lua+Redis 实现分布式计数器/滑动窗口

  • 限流统一在网关层,后端服务无感知

  • 高性能、高并发,适合全局接口限流

3. Nginx + Lua 完整配置

1)Nginx 配置 nginx.conf

nginx 复制代码
http {
    # 加载lua脚本
    lua_package_path "/usr/local/nginx/lua/?.lua;;";

    server {
        listen 80;
        server_name localhost;

        location /api/ {
            # 执行限流lua脚本
            access_by_lua_file /usr/local/nginx/lua/limit.lua;
            # 转发后端服务
            proxy_pass http://127.0.0.1:8080;
            proxy_set_header Host $host;
        }
    }
}

2)Lua脚本 limit.lua(Redis滑动窗口限流)

lua 复制代码
-- 连接redis
local redis = require"resty.redis"
local red = redis:new()
red:set_timeout(1000)
local ok, err = red:connect("127.0.0.1", 6379)
ifnot ok then
    ngx.log(ngx.ERR, "redis连接失败: ", err)
    return
end

-- 限流参数
local limit_key = "nginx:limit:api"
local now = ngx.time()
local window = 60          -- 时间窗口:60秒
local max_count = 100     -- 单窗口最大请求数

-- 1. 移除窗口外过期数据
red:zremrangebyscore(limit_key, 0, now - window)
-- 2. 统计当前窗口请求数
local count = red:zcard(limit_key)

iftonumber(count) >= max_count then
    -- 触发限流,直接返回
    ngx.status = 429
    ngx.say("{\"code\":429,\"msg\":\"请求过于频繁,已被限流\"}")
    ngx.exit(429)
end

-- 3. 记录当前请求
red:zadd(limit_key, now, now .. math.random())
red:expire(limit_key, window)

4. 特点

  • 分布式全局限流,集群生效

  • ✅ 网关层拦截,减轻后端压力

  • ✅ 高性能,适合高并发项目

  • ❌ Nginx+Lua 有一定运维成本


五、三种方案横向对比(面试必背)

限流方案 底层 范围 性能 适用场景
Guava RateLimiter 令牌桶 单机 极高 单体项目、简单内部接口
Nginx+Lua Redis+滑动窗口 分布式全局 超高 对外API、网关统一限流
Sentinel 滑动窗口 微服务分布式 SpringCloud 微服务体系

六、面试高频总结

  1. 幂等核心:唯一标识 + 防重存储(Redis/DB)

  2. Guava局限:只能单机,集群必须用Redis分布式限流

  3. Nginx+Lua优势:网关层限流,拦截在最外层,保护后端

  4. 幂等+限流组合:先限流、后幂等,系统稳定性最强


本文的引用仅限自我学习如有侵权,请联系作者删除。
参考知识
分布式接口幂等性、分布式限流:Guava、nginx和lua限流


相关推荐
天码-行空2 小时前
深入拆解Tomcat架构:多层容器设计原理
java·架构·tomcat
天空属于哈夫克32 小时前
企微自动化:API接口的私有化部署架构
运维·架构·自动化
星梦清河2 小时前
微服务-01
微服务·云原生·架构
LONGZETECH2 小时前
新能源汽车专业升级,仿真教学软件科学布局指南
人工智能·架构·汽车·汽车仿真教学软件·汽车故障诊断
帅次2 小时前
Android 高级工程师面试参考答案:架构设计、Jetpack 与 Compose
android·面试·职场和发展·架构·composer·jetpack
天涯明月19932 小时前
QClaw完全指南_AI代理网关架构与多代理管理实战
人工智能·架构·大模型·agent
陈天伟教授2 小时前
Gemma 4 模型-可变分辨率(令牌预算)
人工智能·安全·架构
zshs0002 小时前
重读《凤凰架构》,从分布式演进史看技术选型的本质
分布式·后端·架构
生活观察站2 小时前
地铁隧道5G工业专网规划:基于室内覆盖架构的Ranplan全场景解决方案
5g·架构