分布式系统接口,如何避免重复提交
在分布式系统中,
避免表单重复提交是一项重要的任务,
尤其是在处理涉及资金交易、订单创建等敏感业务时。
以下是一些常见的解决方案:
1、基于Token的幂等设计
原理
-
在接口请求前,服务器生成一个唯一的 Token(可以是 UUID 或其他唯一标识),并将其下发到客户端。
-
客户端在请求接口时携带该 Token,服务器校验该 Token 是否已经使用。
a.如果已使用,则拒绝重复请求。 b.如果未使用,则处理请求并将 Token 标记为已使用。
实现步骤
-
生成 Token
a.在用户访问接口时,生成一个唯一的 Token,存储到 Redis 或数据库中,并返回给客户端。
-
校验 Token
a.接口请求时,服务器接收到 Token,与存储的 Token 对比,确保 Token 的唯一性。
-
标识 Token
a.请求处理完成后,立即将 Token 标记为已使用,避免再次使用。
技术选型
- 使用 Redis 可以高效处理 Token
- 可以为 Token 设置 TTL(如 5 分钟),避免长时间占用资源。
2、基于Token的幂等设计
原理
- 针对业务中存在唯一性约束的字段(如订单编号、交易流水号),在数据库中添加唯一索引。
- 即使分布式系统中有重复的请求,由于唯一索引的存在,数据库会拒绝重复写入。
实现步骤
- 客户端生成一个唯一的业务编号(如订单编号)。
- 服务端在数据库写入时,依赖唯一约束来防止重复提交。
- 如果数据库插入失败(因唯一约束),则返回错误提示。
适用场景
- 适用于有明确唯一性标识的业务场景,例如订单号、交易号等。
3、幂等性设计
原理
- 确保请求的接口是幂等的,即多个提交的结果一致
- 常用手段包括状态机和更新操作。
实现方式
- 状态机
- 更新操作
4、分布式锁
原理
- 针对同一用户或同一操作,使用分布式锁来限制并发请求的处理。
实现方式
-
获取锁
a.使用 Redis 或 Zookeeper 创建分布式锁。锁的 Key 可以是用户 ID 或业务 ID。
-
处理请求
a.如果获取到锁,则处理请求;如果未获取到锁,则直接返回错误提示。
-
释放锁
a.请求处理完成后,释放分布式锁。
适用场景
- 避免高并发场景下的重复提交。
5、请求去重
原理
- 对于每个请求,计算唯一的签名(如 MD5 或 SHA256),用于标识请求内容。
- 服务端根据签名判断请求是否已处理。
实现方式
1. 生成签名
a. 基于接口字段内容生成签名,如:
String signature = MD5Utils.generateSignature("接口参数");
2. 存储签名
a. 服务端存储签名,一般是放在 Redis 里边
3. 校验签名
a. 适用于接口内容较复杂且需要精确去重的场景。
6.前端防护
原理
- 利用前端技术手段,减少重复提交的可能性。
实现方式
1.按钮防重复点击:
- 在用户提交表单后,立即禁用提交按钮,避免多次点击。
- 示例:
document.getElementById("submit").disabled = true;
2.表单防回退提交:
- 提交成功后,清空或销毁表单数据。
适用场景
- 适用于用户误操作导致的重复提交,但无法解决恶意重复提交。
7.延迟队列
原理
- 将接口提交请求放入消息队列中进行处理,系统只接受队列中的唯一消息。
实现方式
1、消息入队
- 接口提交时,将请求放入消息队列(如 Kafka、RabbitMQ)。
2、消息去重
- 在消息处理阶段,服务端检查消息 ID 是否已处理。
3、延迟处理
- 设置延迟队列,确保请求在短时间内不会重复处理。