订单重复问题已经是老生常谈的问题了,我们熟悉的淘宝购物流程,购物车-->生成订单--》提交订单--》订单确认--》支付订单--》备货--》发货,这里可能会有一些问题,例如,有人恶意或者无意的重复提交订单,从而导致数据库保存的订单数量与实际不符,重复提交订单导致用户体验不佳,甚至更为严重的是,用户重复支付了同一个订单。所以,涉及到订单,应该首先想清楚如何设计才能保证系统不会出现这些问题
如何防止订单重复提交
首先说两个我们购物时经常有过的体验或者说购物网站的网页提醒
- 你提交的动作过快,请稍后尝试
- 你的订单已经超时,请刷新页面后重新提交
看到这些提示,说明该购物网站做了订单提交的限制,一方面是防止有人恶意无限制提交订单,所以限制了一定时间内最大可操作次数,另一方面是为了保证订单无重复提交。那么这是怎么做到的呢?
第一个应该比较简单,限制某个时间内的最大操作次数只需要有一个计数器就可以,计数器可以用redis实现,设置一个带有有效时间的值作为计数器,如果值不存在则自动创建,超过某一个值就认为操作次数用完即可以实现。
第二个可以使用token机制,token即令牌,学过spring security的相信对这个词不会陌生。我们可以使用类似spring security的机制在页面上生成一个token,当提交订单时,根据该token的有效时间和允许的使用次数来判断订单是否允许提交,从而规避重复提交的问题。当然,有人会问,在高并发的情况下,如果是判断token有效之前有很多同一个用户的提交线程过来(用户正常使用一般不会出现这种情况,一般是压力测试工具导致的),那么还是会重复提交,所以,这里需要用到锁机制,访问同一个用户的token同一时间只能有一个线程,token使用之后失效了就会被清掉,之后的线程就找不到该token,从而认为订单不能提交。
订单确认支付
如支付宝和微信等,支付宝和微信本身是怎么限制订单只能支付一次的呢?订单怎么保证只会被支付一次呢?这个问题相对来说就简单很多了,同一订单的状态更新的SQL只需要带上条件,利用的是数据库的行锁。当然,如果是分布式系统,这里涉及到的问题会更多。
update table item set item.status=:newstatus where item.id = :id and item.status = oldstatus.
对比案例
-
美团GTIS
主要看GTIS的流程图以及想想其交易ID的作用,交易之前,后台会返回一个交易ID给前端,前端在点击交易按钮时需将该交易ID和其他交易信息同时返回给后台进行处理,通过全局的交易ID实现“该次交易的”幂等性
美团GTIS.png
参考资料和研究
- 分布式锁的对比分析:非常好的一篇文章,包含下面的关键词:可重入、阻塞、公平、排他、乐观、悲观、单点、死锁发生、连接池状况、实现种类、实现方式
网友评论