幂等(Idempotence)性也就是我要保证我任意多次的调用产生的结果应与一次执行的结果相同,也就是落库的操作只能执行一次,接口需要具备识别并处理误操作或恶意重复调用的能力,以确保数据的一致性和系统的稳定性。
什么情况下会产生幂等问题
客户端:客户端是发起请求的源头,由于用户行为或客户端逻辑,可能导致重复请求,从而引发幂等问题。典型场景:
- 用户重复操作:用户在前端页面多次点击按钮(如提交表单、支付按钮),导致短时间内发送多个相同请求。例如:用户多次点击“提交订单”按钮,生成多个订单。
- 客户端重试机制:客户端在未收到服务端响应时,自动重试请求。例如:支付接口超时,客户端重试支付请求,导致重复扣款。
- 定时任务触发:客户端定时任务可能因为调度问题被多次触发,导致重复调用接口。例如:定时任务每天凌晨统计用户数据,任务被多次触发,导致数据重复计算。
网络不可靠:网络是客户端和服务端之间的桥梁,由于网络不可靠性,可能导致请求丢失、重复或延迟,从而引发幂等问题。常见场景:
- 网络超时或抖动:客户端发送请求后,由于网络问题(如超时、抖动),未能及时收到服务端响应,导致客户端重试。
- 消息队列重复投递:在网络通信中,消息队列可能因为消费者处理失败而重新投递消息,导致重复处理。
- 负载均衡重试:在分布式系统中,负载均衡器可能因为某个服务节点不可用,将请求重新路由到其他节点,导致重复请求。
服务端不可靠:服务端是请求的处理者,由于服务端逻辑或分布式系统的复杂性,可能导致请求被重复处理,场景如:
- 并发请求导致的重复操作,在高并发场景下,多个请求可能同时到达服务端,导致对同一资源的重复操作。例如,多个线程或进程同时处理同一个订单的支付或状态更新,如果没有适当的并发控制机制,就可能引发幂等问题。
- 分布式系统的复杂性,在分布式系统中,多个服务节点可能同时处理相同的业务逻辑,缺乏有效的协调机制会导致重复操作。例如,订单生成接口可能在多个节点上被重复调用,而没有全局的幂等性控制。
常见的解决方案:
场景的处理方案有:唯一索引、token机制、悲观锁、乐观锁、分布式锁、状态机
数据库唯一索引
当插入数据时,如果违反唯一索引约束,数据库会抛异常来保证相同的数据不会被重复插入。如在订单表中为订单号字段添加唯一索引。插入订单时,如果订单号已存在,数据库会抛出唯一约束冲突异常。
-- 创建订单表,并为订单号添加唯一索引
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(64) UNIQUE, -- 唯一索引
user_id BIGINT,
amount DECIMAL(10, 2),
status VARCHAR(32)
);
-- 插入订单
INSERT INTO orders (order_no, user_id, amount, status)
VALUES ('202310100001', 1, 100.00, '待支付');
-- 如果重复插入相同订单号,会抛出唯一约束冲突异常
INSERT INTO orders (order_no, user_id, amount, status)
VALUES ('202310100001', 1, 100.00, '待支付'); -- 报错:Duplicate entry '202310100001' for key 'order_no'
Token机制
客户端在首次请求API时,服务器生成一个唯一的token返回客户端,客户端在后续对同一操作的重复请求时,必须携带这个token。服务器端维护一个已处理token的集合或数据库记录,当收到带有token的请求时,检查Token是否已经被处理过。如果已处理,则直接返回之前的结果,不再执行重复的操作
主要流程如下:
1)服务端生成token
// 生成Token并存储到Redis
public String generateToken() {
String token = UUID.randomUUID().toString(); // 生成唯一Token
redisTemplate.opsForValue().set(token, "unused", 5, TimeUnit.MINUTES); // 存储Token,有效期5分钟
return token;
}
2)客户端获取token
3)客户端获取token之后,在提交订单时带上token
4) 服务端,验证Token并创建订单
// 创建订单接口
public String createOrder(OrderRequest request) {
String token = request.getToken();
String orderNo =