如何保证交易中的订单不会重复处理?
如何保证交易中的订单不会重复处理?
在分布式系统中,保证交易中的订单不会重复处理是一个常见问题。为了解决这个问题,
通常需要结合幂等性设计、流水号唯一性、数据库锁等机制。
幂等性是指一个操作无论被执行多少次,最终结果都是相同的。对于交易系统来说,幂等
性设计尤为重要,特别是在网络重试或分布式环境下,避免同一个操作多次执行。
首先在用户提交订单时,会生成一个唯一的订单号。上游系统在每次请求时,带上这个唯
一的流水号。下游系统通过检测这个流水号,确保同一个流水号的请求不会被重复处理。
然后可以通过在数据库中为流水号设置唯一索引,确保同一个流水号的订单或交易在数据
库中只能插入一次。
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(255) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status VARCHAR(50) NOT NULL,
UNIQUE KEY (order_no) -- order_no 唯一索引,确保流水号唯一
);
在处理订单或交易时,先向数据库中插入流水号。
INSERT INTO orders (order_no, amount, status) VALUES ('202401010001',
100.00, 'pending');
如果插入成功,则执行后续处理逻辑。如果插入失败(因为同一个流水号已经存在),则
说明该订单已经处理过了,不再重复执行。
如果 order_no 是唯一索引,当重复插入相同 order_no 时会抛出异常,可以捕获异常并返
回订单已存在或已处理过的响应。
try {
// 尝试插入订单,如果失败则说明订单已存在
jdbcTemplate.update("INSERT INTO orders (order_no, amount, status) VALUES
(?, ?, ?)",
orderNo, amount, "pending");
// 执行后续业务逻辑,如订单处理
} catch (DuplicateKeyException e) {
// 订单已存在,返回重复处理的响应
System.out.println("订单已经处理过了,流水号: " + orderNo);
}
如果想要在查询或更新时加锁,可以使用 SELECT FOR UPDATE,通过对同一个流水号的
记录加行锁,确保并发请求时,只有一个事务可以处理,其他并发事务会等待锁释放。
START TRANSACTION;
-- 查找订单并加行锁
SELECT * FROM orders WHERE order_no = '202401010001' FOR UPDATE;
-- 判断订单状态,如果未处理,执行处理逻辑
UPDATE orders SET status = 'completed' WHERE order_no = '202401010001';
COMMIT;
同一时刻只能有一个事务处理特定的流水号,其他事务在锁释放前会被阻塞。这就保证了
并发请求时,订单只会被处理一次。
在分布式系统中,需要使用分布式锁来保证多个节点不会重复处理同一个订单。
在处理订单之前,尝试获取基于流水号的分布式锁。如果成功获取锁,执行订单处理逻
辑。处理完成后,释放锁.如果无法获取锁,则说明该流水号正在被处理,可以返回“订单
处理中”的提示。
比如说使用 Redisson 实现分布式锁:
RLock lock = redissonClient.getLock("lock:order:" + orderNo);
try {
// 尝试加锁,最多等待 10 秒,超过 10 秒返回加锁失败
if (lock.tryLock(10, TimeUnit.SECONDS)) {
// 执行订单处理逻辑
processOrder(orderNo);
} else {
System.out.println("订单正在处理中,流水号: " + orderNo);
}
} finally {
// 释放锁
lock.unlock();
}
在小规模系统中,数据库锁和唯一索引通常就足够保证交易订单不会被重复处理了;但在
高并发的分布式系统中,还需要配合分布式锁来进一步保证。