文章目录
- 前言
- 一、分布式接口幂等性(核心)
- 二、限流体系总览
- [三、Guava 单机限流(令牌桶)](#三、Guava 单机限流(令牌桶))
-
- [1. 流程图](#1. 流程图)
- [2. 引入依赖](#2. 引入依赖)
- [3. 完整代码案例](#3. 完整代码案例)
- [四、Nginx + Lua 分布式限流(生产常用)](#四、Nginx + Lua 分布式限流(生产常用))
-
- [1. 架构流程图](#1. 架构流程图)
- [2. 实现原理](#2. 实现原理)
- [3. Nginx + Lua 完整配置](#3. Nginx + Lua 完整配置)
-
- [1)Nginx 配置 `nginx.conf`](#1)Nginx 配置
nginx.conf) - [2)Lua脚本 `limit.lua`(Redis滑动窗口限流)](#2)Lua脚本
limit.lua(Redis滑动窗口限流))
- [1)Nginx 配置 `nginx.conf`](#1)Nginx 配置
- [4. 特点](#4. 特点)
- 五、三种方案横向对比(面试必背)
- 六、面试高频总结
前言
幂等性
在高并发场景的架构里,防止重复提交的幂等性是必须要保证的。
比如说支付功能,用户发起支付,如果后台没有做幂等校验,刚好用户手抖多点了几下,于是后台就可能多次受到同一个订单请求,不做幂等很容易就让用户重复支付了,这样用户体验是无法忍受的。
一、分布式接口幂等性(核心)
1. 什么是幂等
一个接口执行多次,结果和执行一次完全一致场景:重复提交、网络重试、超时重发、MQ重复消费
2. 常用4种幂等方案(流程图)
整体流转图

四种主流方案
-
全局Token令牌(前端拦截,最常用)
-
唯一业务主键(订单号、流水号 唯一索引)
-
Redis分布式锁 + 防重标记
-
MQ消费:消息唯一ID + 幂等表
3. 代码案例:Token令牌幂等(SpringBoot+Redis)
步骤
-
前端先获取唯一幂等Token
-
提交业务请求携带Token
-
接口拦截器校验Token,删除/标记
-
重复请求直接拦截
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种实现层级
-
应用层单机限流:Guava RateLimiter(单机、简单)
-
网关层分布式限流:Nginx+Lua(全局、高性能)
-
分布式中间件限流: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 微服务体系 |
六、面试高频总结
-
幂等核心:唯一标识 + 防重存储(Redis/DB)
-
Guava局限:只能单机,集群必须用Redis分布式限流
-
Nginx+Lua优势:网关层限流,拦截在最外层,保护后端
-
幂等+限流组合:先限流、后幂等,系统稳定性最强
本文的引用仅限自我学习如有侵权,请联系作者删除。
参考知识
分布式接口幂等性、分布式限流:Guava、nginx和lua限流