方案一:
收到抢购请求后,对商品执行库存 -1 操作。然后给用户生成一个订单。
这系列操作中,并发问题在于:如果库存为1,这时候有两个用户同一时间请求,那么两个进程同时select 到库存数为1,并同时进行update -1操作,那么相当于1件商品就卖出去了两次。
那么如何解决这个问题呢?很简单。我们只需要把select库存和update库存-1这两个操作放在1个事务里,并且在select商品库存的时候for update一下加上排他锁。那么就不存在两人同时读取到库存1的可能了。第一个人-1库存,+1订单,提交事务。第二人才能select到库存。这时候库存就是0了。自然而然就是抢购失败。
上面是理论。实际操作中,有更简单的办法。那就是直接update 商品表 库存-1 where id=商品id AND 库存!=0;然后获取更新数量,如果不为1,就是抢购失败。为1,就是抢购成功。这里面保证并发安全的是数据库的单条sql就是一个事务的特性保证的。
优化方案1:将 库存 字段 设为unsigned,当库存为0时,因为字段不能为负数,将会返回false
优化方案2:使用mysql的事务,锁住操作的行
https://blog.csdn.net/canot/article/details/53815294 (未完)
方案二:文件锁
方案三:Memcache锁
使用product_lock_key 为票锁key
当product_key存在于memcached中时,所有用户都可以进入下单流程。
当进入支付流程时,首先往memcached存放add(product_lock_key, “1″),如果返回成功,进入支付流程。如果不成,则说明已经有人进入支付流程,则线程等待N秒,递归执行add操作。
方案四:redis队列(rpoplpush的安全队列)
将商品加入redis队列中
一般情况下,我们可以借助List来实现消息队列,比如一个客户端通过命令LPUSH(BLPUSH)把消息入队,另一个客户端通过命令RPOP(BRPOP)获取消息。这种方式实现的队列是不安全的。
为什么是不安全的呢?因为RPOP命令的特性:会移除list的队尾元素(消息),并将这个元素(消息)返回给客户端。这意味着该元素就只存在于客户端的上下文中,redis服务器中没有这个元素了,如果客户端在处理这个返回元素的过程崩溃了,那么这个元素就永远丢失了。这种情况导致:客户端虽然成功收到了消息,但是却没有处理它。
那怎么来实现一个安全的队列呢?可以使用redis的 RPOPLPUSH (或者其阻塞版本的 BRPOPLPUSH)命令。
RPOPLPUSH命令格式:RPOPLPUSH source destination 。RPOPLPUSH命令原子性地返回并移除 source 列表的最后一个元素, 并把该元素放入 destination 列表的头部。使用这个命令就可以实现安全队列。
因为使用 RPOPLPUSH 获取消息时,RPOPLPUSH 会把消息返给客户端,同时把该消息放入一个备份消息列表,并且这个过程是原子的,可以保证消息的安全。当客户端成功的处理了消息后,就可以把此消息从备份列表中移除了。如果客户端因为崩溃的原因没有处理某个消息,那么就可以从备份列表destination中重新获取并处理这个消息。
网友评论