订单并发处理--悲观锁和乐观锁、任务队列以及订单模块开发流程
订单模块开发流程:
前端提交购买商品信息
1.在商品详情页面点击购买按钮,到达提交订单页面,页面显示收货地址,商品信息,商品数量,总金额,支付方式等。然后点击提交订单按钮,前端页面将商品id、运费、总金额、总数量、支付方式等传递给后端的订单视图去创建订单。
2.在生成订单的时候需要去判断库存数量,如果库存数量不够,就返回商品库存不足,整个创建订单的流程,做成一个事务
3.在对库存数量进行操作的地方使用锁:乐观锁或者悲观锁
为什么要使用锁
加入甲乙同时下单购买商品A,下单前查询库存数量都是15,在下单的时候,甲下单更快,买走了10件。而乙下单时,还是以库存15来判断,这样就会出现数据错误。
在创建订单时候,假设有人和你同时对商品数量进行操作,要保证数据是安全的,可以使用乐观锁。在更新商品数量的时候,需要判断一下还是不是之前的数量,如果不是则说明数量被人改了,就会创建订单失败
解决思路
1.悲观锁 悲观锁是从读取记录那一刻就开始了
当查询某条记录时,就让数据库为该记录加锁,锁住之后其他人无法操作该数据,其他事务要想获取锁,必须等原事务结束
select stock from tb_sku where id=1 for update; SKU.objects.select_for_update().get(id=1) #悲观锁
2.乐观锁 乐观锁是从UPDATE那一刻开始
乐观锁并不真正的锁,而是在更新的时候判断此时的库存是否是之前查询出的库存,如果相同,表示数据没被修改,可以更新库存,否则不更新库存
update tb_sku set stock=2 where id=1 and stock=7; SKU.objects.filter(id=1, stock=7).update(stock=2) #更新之前做判断
3.任务队列
将下单的逻辑放到任务队列中(如celery),将并行转为串行,所有人排队下单。比如开启只有一个进程的Celery,一个订单一个订单的处理。
然后,我们现在解决了锁的问题,全部请求采用“先进先出”的队列方式来处理。那么新的问题来了,高并发的场景下,因为请求很多,很可能一瞬间将队列内存“撑爆”,然后系统又陷入到了异常状态。
或者设计一个极大的内存队列,也是一种方案,但是,系统处理完一个队列内请求的速度根本无法和疯狂涌入队列中的数目相比。
也就是说,队列内的请求会越积累越多,最终Web系统平均响应时候还是会大幅下降,系统还是陷入异常。
是选择悲观锁还是乐观锁
冲突比较少的时候,使用乐观锁。冲突比较多的时候,使用悲观锁
悲观锁的加锁和解锁都是需要消耗CPU资源的,所以在订单并发少的情况使用乐观锁会是更好的选择。
注意:在使用乐观锁的时候需要把MySQL数据库的事务隔离级别更换成不可重复读
MySQL数据库事务隔离级别是可重复读,这样读取的数据是一致的。改成不可重复读,别的事务提交了,需要是能感知到数据修改了,这样乐观锁才会起作用