1.背景
有个项目会将本公司的下单接口通过Open API暴露到外网供第三方使用,最近收到商务反馈第三方公司,在重试的过程中多次使用同一个第三方订单号下单,导致我们业务系统出现了重复订单。我们系统并没有做幂等校验,数据库也没有对第三方订单号建立唯一索引。
2 .解决方法
因为数据库已经存在了重复的第三方订单号,所以无法对第三方订单号建立唯一索引。
当然可以考虑在数据库新增一张表,这张表上有第三方订单号的字段,并为此字段建立唯一索引, 每次下单前预先往这张表插入数据,如果此第三方订单号已存在,那么插入数据必然报错。这样可以利用唯一索引做幂等。但这种做法有点重,为了一个幂等校验单独引入一张新表。
Redis这种内存数据库做幂等会更轻量级一些,因为它可以设置过期时间,到期自动删除。
使用redis分布式锁,在锁过期后再次下单还是可以成功,所以在更长的时间维度内redis分布式锁也无法实现幂等。当然也可以给redis分布式锁设置一个很长很长的过期时间,但随着时间的推移下单累计量越来越多,就会造成Redis的内存占用越来越多。
不要忘记了我们数据库存储了第三方订单号,所以在redis分布式锁过期后,我们还可以根据第三方订单号去查数据,如果数据库存在此订单就直接忽略掉此次下单请求。
伪代码如下:
try{boolean success= redis.getLock(thirdOrderNo,30s);if(!success){throw new Exception("此订单已在处理中");}Order order= orderReposity.queryOrder(thirdOrderNo);if(order !=null ){return order.getOrderNo();}return doProccessOrder(orderReq);//...}finally{redis.release(thirdOrderNo);}
上面的代码中会有两次检查订单是否存在。
第一次是通过分布式锁检测,这是在高并发情况下做幂等,有可能两次下单请求相隔时间很短(几乎同时发起),此时下单流程还没执行完,数据还未保存到数据库,此时直接去查数据库时看不到订单的。
第二次是通过数据库检测,这是在更长时间维度里(数据已保存)去检测订单,解决单纯的分布式锁在锁过期后无法判定订单数据是否已存在的问题。