在分布式系统开发中,”幂等性”(Idempotency)是一个经常被提及但容易被忽视的概念。简单来说,幂等性是指同一个操作执行多次,结果与执行一次完全相同。对于站长和后端开发者来说,理解并正确实现幂等性,是构建可靠系统的必备技能。
为什么幂等性很重要
在现实的网络环境中,请求重复是不可避免的:
- 网络超时重试:用户点击”支付”后网络超时,客户端自动重试,服务端可能收到两次支付请求
- 消息队列重复投递:消息队列(如 Kafka、RabbitMQ)在消费者处理失败时会重复投递消息
- 负载均衡器重试:Nginx 等负载均衡器在后端超时时会将请求转发到另一个节点
- 用户手抖:用户快速双击提交按钮
如果系统不具备幂等性,这些重复请求可能导致:重复扣款、重复发货、数据不一致等严重问题。
常见的幂等性实现方案
方案一:唯一请求 ID(推荐)
最通用的方案。客户端在发起请求时生成一个唯一的请求 ID(通常是 UUID),服务端在处理前先检查这个 ID 是否已经处理过。
// 客户端生成唯一 ID
const requestId = crypto.randomUUID();
// 服务端处理逻辑
async function handlePayment(requestId, amount) {
// 检查是否已处理
const existing = await db.query(
'SELECT result FROM processed_requests WHERE request_id = ?',
[requestId]
);
if (existing) {
return existing.result; // 直接返回之前的结果
}
// 处理业务逻辑
const result = await processPayment(amount);
// 记录已处理
await db.insert('processed_requests', {
request_id: requestId,
result: result
});
return result;
}
这个方案的关键点:
- 请求 ID 必须由客户端生成,因为同一个请求重试时 ID 不变
- 需要一个存储来记录已处理的请求 ID(数据库表、Redis 等)
- 检查和记录必须是原子操作(使用数据库事务或 Redis 的 SETNX)
方案二:数据库唯一约束
利用数据库的唯一约束来防止重复插入:
-- 创建带唯一约束的订单表
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(64) UNIQUE NOT NULL, -- 唯一约束
user_id BIGINT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status VARCHAR(20) DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入时如果 order_no 已存在会报错
INSERT INTO orders (order_no, user_id, amount)
VALUES ('ORD-20260511-001', 1001, 99.00);
-- 捕获唯一约束冲突,视为重复请求处理
-- 在应用层捕获 DuplicateKeyException 并返回之前的结果
方案三:乐观锁 / 版本号
适用于更新操作。通过版本号确保只有最新的更新被应用:
UPDATE accounts
SET balance = balance - 100, version = version + 1
WHERE user_id = 1001 AND version = 5;
-- 如果 affected_rows = 0,说明版本已变更,需要重试或报错
方案四:状态机
对于有明确状态流转的业务(如订单状态),通过状态机来保证幂等:
-- 只有 'pending' 状态的订单才能变为 'paid'
UPDATE orders
SET status = 'paid'
WHERE order_no = 'ORD-001' AND status = 'pending';
-- 如果 affected_rows = 0,说明订单已经是其他状态,忽略本次操作
HTTP 方法的幂等性
RESTful API 设计中,不同 HTTP 方法有天然的幂等性差异:
- GET:天然幂等,读取操作不会改变状态
- PUT:应该是幂等的,多次更新到同一状态结果相同
- DELETE:应该是幂等的,删除一个已删除的资源结果相同
- POST:天然不幂等,每次 POST 通常创建新资源
- PATCH:不一定幂等,取决于具体实现
对于 POST 接口(如创建订单、发起支付),必须通过请求 ID 等机制实现幂等性。
实际项目中的注意事项
- 请求 ID 的存储需要定期清理:已处理的请求 ID 会持续增长,建议设置 TTL(如 24 小时后自动清理)
- 并发场景下的竞态条件:两个相同请求同时到达时,需要使用分布式锁或数据库事务来保证只有一个被处理
- 不同层级的幂等性:接口幂等、消息消费幂等、支付回调幂等可能需要分别实现
- 第三方接口的幂等性:对接支付宝、微信支付等第三方接口时,要注意它们是否支持幂等以及如何传递幂等键
小结
幂等性不是可选的”加分项”,而是分布式系统的基本要求。特别是在涉及金钱交易、库存变更、状态流转等关键业务时,忽略幂等性可能导致严重的数据不一致问题。建议在系统设计阶段就将幂等性纳入考量,而不是事后补救。
来源:
© 版权声明
THE END
















暂无评论内容