使用幂等 避免重复生成订单
避免重复生成订单的思路
-
前端下单按钮点击之后,应该立即设置为禁用状态,避免网络卡时多次点击下单
-
在技术方面,这是一个分布式一致性的问题,即客户端和服务器端对某个订单是否成功/失败达成一致。防止重单的关键是使用一个由客户端生成的,可用于避免重复的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”)来表示这个订单是这次新生成的,还是因为幂等而直接返回的。这样前端可以有针对性的对这两种情况提示不同的文案。