电商订单技术方案梳理-续

原来简单的整理一下订单业务,但是下面的评论网友望我参看
http://jblog.top/article/details/255453

看了之后我又突发感想,再作一篇续。作续的目的,是将之前没有理清楚的再理一理。

对于高并发情况下的电商下订单,需要解决的技术难点,主要集中在如下几个方面:
1.如何扣减库存;
2.如何写入订单;
3.如何完成支付,这个太复杂,不展开了

下面我们来回答上面的1,2两个问题。

1.如何扣减库存

方法1.update Product set availableNum = availableNum - ?2 , reserveNum = reserveNum + ?2 where id = ?1

这种方法的缺陷是不安全,可能会出现库存扣错,并且方法不幂等。举例说明:库存有2件,A,B两个用户同时发出请求,购买量都为2,分别获取了剩余库存量2,然后执行相应的扣减库存,并且顺利完成,最终库存为了-2,这个是不对的。另一种场景就是调用扣减库存方法,数据库没有顺利的返回(原因有很多,并发,网络,断电),那是否再重试?最终库存变成了-2,-4,-6都有可能。

方法2.update Product set availableNum = availableNum - ?2 , reserveNum = reserveNum + ?2 where id = ?1 and availableNum - ?2 >= 0

这种方法它的缺陷是不幂等。举例说明:商品库存有4件,A用户发出请求,购买量为1,获取剩余库存量为4,然后执行扣减库存。数据库执行成功,由于网络原因延迟,库存扣减系统重试然后成功,此时剩余库存量为2.而购买量只有1,即库存消失了1个。

方法3.redis分布式锁

这种方法它的缺陷将多线程并行处理变成串行处理,所以对于高并发,高吞吐量的电商系统是不行。

方法4.redis的increment原子操作

这种方法是使用乐观锁解决并发时资源抢占的问题。是一个好方法,只是需要解决redis的内存易失性问题。对于秒杀订单短时间内可以不考虑这个问题。

方法5.update Product set availableNum=${newValue} where id=${id} and availableNum=${oldValue}

这个是我们现在使用的普通下单的扣库存方法。oldValue为库存扣减前最初的库存剩余量,newValue=oldValue-{用户购买量}。这个扣减库存的sql最多成功执行一次,所以不会出现多扣或者少扣的情况。

2.如何写入订单

电商订单系统,在用户下单请求之后,支付之前都会生成物理单并将其落地,即将其写入数据库。因为从业务流程来看,用户下单到用户订单支付之间会有时间间隙,即留给用户的支付时间。在电商订单系统中,普通单时间为24小时,秒杀单时间不等或者由于商品的特殊性时间也不固定。比如12306留给用户的支付时间只有30分钟。我们的系统留给用户的时间也是30分钟,即30分钟之后如果用户不付款,则取消订单。这里面涉及到何时扣减库存的问题。

那么何时扣减库存呢?

对于这个问题的争议点,是下单时扣减,还是支付时扣减。
下单时扣减:对于普通订单用户下单,此时扣减库存,用户迟迟不支付,则库存被占用24小时,想买的买不了。
支付时扣减:对于秒杀单,在支付时扣减库存,则由于秒杀周期以及秒杀商品cx对于用户的吸引度等影响,如果此时告知库存不足,无法完成支付,则会造成用户的不好的体验。
综上,我们的作法是对于秒杀单,采用下单时扣减库存,库存最多被占用30分钟;普通订单,支付时扣减库存,不支付的订单不占用库存。

在高并发的情况下,如何写入订单呢?

直接入库,这样能行吗?思路可行,只要有足够的数据库instance提供写入服务,并且订单系统支持多库的写入服务,则可以满足订单的写入需求。比如并发量50000,每台数据库并发量为500,则需要100台数据库。如果没有足够的数据库,则只能换一条路,使用消息队列来抗订单的高写入。对于订单写入消息队列之后到持久化数据库之前这期间凡是对于当前订单的查询与操作的api均需要重构,对于重构的逻辑需要根据写入消息队列的订单数据结构进行重构。具体业务,具体分析。
举例说明:在我们的系统中,用户下完单之后,没有支付。随后再想支付,只能通过我的订单,查看没有支付的订单。对于查看该用户的没有支付的订单,对于该api入参只有 customerId,原始的逻辑根据customerId查询数据库中对应的用户订单即可。但是现在不行,还需要到消息队列中去查找是否有该用户的订单,然后作结果集合并,返回前端。为了支持这种api业务重构,所以我们使用的消息队列订单模型为:

3. 订单及订单详情序列化写入redis订单kv结构中,k为订单id;v为订单数据;
4. 用户与订单id的关系反表写入redis kv结构,k为用户id;v为订单id;为了解决用户的订单列表查询无法查到最新订单的问题;
5. 订单id写入redis列表,即消息队列;用于消息的发送;

关于使用消息队列抗写入,可参见上一篇博文电商订单技术方案梳理

猜你喜欢

转载自blog.csdn.net/zhurhyme/article/details/77992222