使用幂等 避免重复生成订单
避免重复生成订单的思路
-
前端下单按钮点击之后,应该立即设置为禁用状态,避免网络卡时多次点击下单
-
在技术方面,这是一个分布式一致性的问题,即客户端和服务器端对某个订单是否成功/失败达成一致。防止重单的关键是使用一个由客户端生成的,可用于避免重复的key,俗称dedup key(deduplicate key之意)。这个key可以用任意可以保证全局唯一性的方式生成,比如uuid。客户端和服务器需要使用这个dedup key作为串联条件,一起解决去重问题。
后端表设计
订单 order表里 增加dedup_key 字段 并设置成 唯一约束
create table order( # ... dedup_key varchar(60) not null comment 'key to pretend order duplication', # ... unique uniq_dedup_key(dedup_key) );
下单的实现
在实现下单逻辑时,基于该dedup_key实现一个"create-or-get"语义的下单接口——简单说就是
如果带有指定dedup_key的订单已经存在,则直接返回;否则,用该dedup_key下单。
用伪代码表示大概是:
@Transactional
Order createOrder(Integer userId, String prodCode, Decimal amount, String dedupKey) {
try {
String orderId = createOrder(userId, prodCode, amount, deupKey); // insert a new order
Order order = getOrderById(orderId); // read order from db
order.setDuplicated(false);
return order;
} catch(UniqueKeyViolationException e) {
// if duplicated order has existed
Order order = getOrderByDedupKey(dedupKey);
order.setDuplicated(true);
return order;
} catch (Exception e) {
// hanlde other errors and rollback transaction ...
}
}
这时,这段下单代码总是能返回一个订单(除非发生一些DB挂了之类的错误),要么是新创建的,要么就是一个已经存在的单。注意,最好在订单里增加一个属性(比如例子中用“duplicated”)来表示这个订单是这次新生成的,还是因为幂等而直接返回的。这样前端可以有针对性的对这两种情况提示不同的文案。