本文转载自-方才coding的博客,原文链接:fangcaicoding.cn/course/15/9…
Hello 我是方才,8人后端研发leader、4年团队管理&架构经验。
专注于分享成体系的编程知识、职场经验、个人成长历程等!
文末,方才送你一份优质的技术资料,记得领取哟!
Hello,我是方才。不知道你是否也遇到过下面这些问题?
- 用户习惯性双击鼠标,导致表单重复提交,业务数据重复,甚至发生资金异常情况
- 搜索框输入时频繁触发联想词请求,拖慢页面性能
- 秒杀活动流量突增,服务直接被压垮
其实这些问题的背后,正是接口幂等性、防抖与限流三大核心技术的用武之地。
特别是接口的幂等性问题,非常重要,一不小心数据就异常了!然后就需要花很高的代价去修正数据!
ps:都是血泪史呀!这周连续加班三天,就是为了处理接口的幂等问题,我发现有不少伙伴,分不清接口幂等性、接口防抖和接口限流的区别,以及分别解决的问题是什么!所以今天方才和大家分享下,我相信对你也一定有所帮助!
方才将从定义、场景到解决方案,为你全面解析这三者的区别与应用。
一、接口幂等性:数据一致性的守护者
1. 定义与核心目标
接口幂等性指同一个请求无论执行多少次,其效果一致且不会产生额外副作用。
例如:用户重复提交订单时,系统仅生成一个订单,避免重复扣款。
ps:某些操作天生就是幂等的,比如说指定ID删除、查询操作等。
2. 典型场景
- 用户重复操作:用户可能会因为操作延迟或误操作而多次点击按钮。
- 双击提交按钮:用户可能会因为操作习惯而多次点击提交按钮。
- 浏览器后退或前进按钮:用户可能会通过浏览器的后退或前进按钮重复提交表单。
- 网络波动:网络不稳定可能导致请求被重复发送。
- 重试机制:应用层、RPC 或 Nginx 的重试机制可能导致请求被重复处理。
- 页面重复刷新:用户可能会因为操作延迟而刷新页面,导致表单重复提交。
- 定时任务重复执行:定时任务可能会因为配置错误或异常而重复执行。
- 消息队列消费:消息队列中的消息被重复消费,可能导致业务逻辑重复执行。
3. 解决方案
| 方案 | 原理 | 适用场景 |
|---|---|---|
| Token 机制 | 客户端请求前获取唯一Token,服务端校验Token后删除,防止重复使用。 | 表单提交、支付接口 |
| 唯一业务标识 | 基于业务字段(如订单号)做唯一性校验,拒绝重复请求。 | 订单创建、库存扣减 |
| 数据库唯一索引 | 在数据库层对关键字段(如订单ID)添加唯一约束,拦截重复数据插入。 | 数据写入场景(如用户注册) |
| 状态机控制 | 通过业务状态流转(如“已支付”状态不可修改),阻止非法操作。 | 订单状态变更、流程审批 |
| 分布式锁 | 使用Redis实现跨服务锁,确保同一请求仅被处理一次。 | 分布式系统下的并发控制 |
代码示例(Token机制):
// 生成Token并存入Redis
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set("order:token:" + userId, token, 5, TimeUnit.MINUTES);
// 接口校验Token
public ResponseEntity<?> createOrder(@RequestParam String token) {
// 删除Token成功,说明token有效,且能防止复用
if ( redisTemplate.delete("order:token:" + userId)) {
// 处理订单逻辑
return ResponseEntity.ok("订单创建成功");
}
return ResponseEntity.badRequest().body("请勿重复提交");
}
二、接口防抖:高频无效请求的拦截者
1. 定义与核心目标
接口防抖(Debounce)的目标是减少短时间内高频触发的无效请求,优化性能与用户体验。
例如:搜索框输入时,用户每输入一个字符就触发联想词请求,实际只需在输入停止后发送一次。
ps:接口防抖似乎在某种程度上能解决部分接口幂等性问题,**但这是最大的误解!**因为所在的环节都不一样(这也是最近方才遇到的问题,有同学认为做了防抖,就不用管接口的幂等性了)!先接着往下看,后续方才会对比说明。
2. 典型场景
- 搜索框联想词:输入过快导致频繁请求后端。
- 按钮重复点击:用户双击提交按钮,触发多次表单提交。
- 窗口Resize事件:窗口调整大小时频繁触发计算逻辑。
3. 解决方案
| 方案 | 原理 | 适用场景 |
|---|---|---|
| 防抖函数(前端) | 延迟执行请求,若在延迟时间内再次触发,则重置计时器。 | 搜索框输入、滚动加载 |
| 节流函数(前端) | 固定时间间隔内仅允许执行一次请求(如每秒最多一次)。 | 按钮点击、鼠标移动事件 |
| Redis分布式锁(后端,也是方才推荐的) | 后端通过Redis锁限制同一用户短时间内的重复请求(如3秒内仅允许一次),结合AOP很方便统一封装。 | 所有API均适用 |
代码示例(前端防抖函数):
function debounce(func, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// 搜索框输入防抖
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce(function() {
// 发送搜索请求
fetchResults(this.value);
}, 300));
三、接口限流:系统稳定性的最后防线
1. 定义与核心目标
接口限流(Rate Limiting)通过控制单位时间内的请求量,保护系统不被突发流量击垮。
例如:秒杀活动中限制每秒最多处理1000个请求,超出部分直接拒绝。
ps:接口限流一般只在特定场景或特定接口才会使用,毕竟是通过拒绝服务来保证核心系统的稳定!
2. 典型场景
- 秒杀活动:瞬时流量远超系统承载能力。
- API防爬虫:防止恶意爬虫高频请求数据。
- 微服务熔断:下游服务故障时,上游服务限流避免级联雪崩。
3. 解决方案
| 方案 | 原理 | 适用场景 |
|---|---|---|
| 计数器算法 | 固定时间窗口内统计请求数,超出阈值则拒绝(如每分钟100次)。 | 简单限流需求 |
| 滑动窗口算法 | 将时间窗口细分为多个小窗口,动态统计请求数,精度更高。 | 高精度限流(如API网关) |
| 令牌桶算法 | 以恒定速率生成令牌,请求需获取令牌才能执行,支持突发流量。 | 秒杀、突发流量缓冲 |
| 漏桶算法 | 请求以恒定速率流出,超出桶容量的请求被丢弃,平滑流量峰值。 | 流量整形、平滑处理 |
代码示例(令牌桶算法):
public class TokenBucket {
private final int capacity; // 桶容量
private double tokens; // 当前令牌数
private long lastRefillTime; // 上次补充时间
public TokenBucket(int capacity) {
this.capacity = capacity;
this.tokens = capacity;
this.lastRefillTime = System.nanoTime();
}
public synchronized boolean tryAcquire(int tokensNeeded) {
refill();
if (this.tokens >= tokensNeeded) {
this.tokens -= tokensNeeded;
return true;
}
return false;
}
private void refill() {
long now = System.nanoTime();
double elapsedTime = (now - lastRefillTime) / 1e9; // 转换为秒
double tokensToAdd = elapsedTime * capacity; // 每秒补充capacity个令牌
this.tokens = Math.min(capacity, this.tokens + tokensToAdd);
this.lastRefillTime = now;
}
}
四、三者的区别与协同关系
1. 核心目标对比
| 技术 | 核心目标 | 层级 |
|---|---|---|
| 幂等性 | 保证重复请求的数据一致性,确保不产生副作用 | 数据层 |
| 防抖 | 减少用户高频操作产生的无效请求 | 交互层 |
| 限流 | 控制系统整体流量,防止过载 | 系统层 |
2. 实际协同案例
以常规的电商系统为例:
- 前端防抖:用户点击“提交订单”按钮时,防抖函数确保仅最后一次点击有效。
- 网关限流:Nginx限制每秒最多处理5000个订单请求,超出部分返回“稍后重试”。
- 后端幂等:订单服务通过唯一订单号校验,防止重复创建订单和扣减库存。
五、总结
方才简单总结下:
- 幂等性是数据层的安全锁:所有涉及资金、库存、状态变更的接口必须设计幂等性。
- 防抖是交互层的优化器:用户高频操作的场景(如搜索、按钮点击)优先考虑防抖。
- 限流是系统层的保险丝:高并发场景(秒杀、API开放平台)必须配置限流策略。
最后,如果今天的分享对你有帮助,**记得给方才点个赞,点个爱心!**如果你曾经也因为接口的幂等性等问题,加过班、熬过夜,记得在评论区告诉方才,哈哈,有坑大家一起踩!
交流群
相遇即是缘分,方才送你一份优质的资料(包括方才自己输出的ES、前端、Mysql系列的知识图谱,软考架构师资料,方才阅读过的优质书籍等等资料)。
也可备注加群,方才拉你进入优质的技术交流群(日常分享高质量的技术文章、优质的资料、实时资讯共享等)。