涉及抢购、秒杀、抽奖、抢票等活动时,为了不超卖,那么库存数量是有限的,可是若是同时下单人数超过了库存数量,就会致使商品超卖问题。那么咱们怎么来解决这个问题呢,个人思路以下(伪代码):php
sql1:查询商品库存 if(库存数量 > 0) { //生成订单... sql2:同时库存-1 }
当没有并发时,上面的流程看起来是再正常不过了,假设同时两我的下单,而库存只有1个了,在sql1阶段两我的查询到的库存都是>0的,因而最终都执行了sql2,库存最后变为-1,超售了,这不是咱们想要的结果吧。mysql
解决这个问题比较流行的思路我总结了下:redis
用额外的单进程处理一个队列,下单请求放到队列里,一个个处理,就不会有并发的问题了,可是要额外的开启后台进程以及延迟问题,这里暂不予考虑。这里我可以使用消息队列,咱们经常使用到Memcacheq、Radis。 好比:有100张票可供用户抢,那么就能够把这100张票放到缓存中,读写时不要加锁。 当并发量大的时候,可能有500人左右抢票成功,这样对于500后面的请求能够直接转到活动结束的静态页面。进去的500我的中有400我的是不可能得到商品的。因此能够根据进入队列的前后顺序只能前100我的购买成功。后面400我的就直接转到活动结束页面。固然进去500我的只是举个例子,至于多少能够本身调整。而活动结束页面必定要用静态页面,不要用数据库。这样就减轻了数据库的压力。sql
mysql乐观锁,意思是好比总库存是2,抢购事件提交时,立马将库存+1,那么此时库存是3,而后订单生成后,在更新库存前再查询一次库存(由于订单生成理所固然库存-1,可是先不急,再查一次库存返回结果是3),看看跟预期的库存数量(这里预期的库存是3)是否保持一致,不一致就回滚,提示用户库存不足。这里说道悲观锁,可能有朋友会问,那必定有乐观锁了吧??这里我就浅谈下我所了解的悲观与乐观锁了数据库
悲观锁与乐观锁是两种常见的资源并发锁设计思路,也是并发编程中一个很是基础的概念。本文将对这两种常见的锁机制在数据库数据上的实现进行比较系统的介绍。编程
悲观锁的特色是先获取锁,再进行业务操做,即“悲观”的认为获取锁是很是有可能失败的,所以要先确保获取锁成功再进行业务操做。**一般所说的“一锁二查三更新”即指的是使用悲观锁。一般来说在数据库上的悲观锁须要数据库自己提供支持,即经过经常使用的select … for update操做来实现悲观锁。**当数据库执行select for update时会获取被select中的数据行的行锁,所以其余并发执行的select for update若是试图选中同一行则会发生排斥(须要等待行锁被释放),所以达到锁的效果。select for update获取的行锁会在当前事务结束时自动释放,所以必须在事务中使用。缓存
这里须要注意的一点是不一样的数据库对select for update的实现和支持都是有所区别的,例如oracle支持select for update no wait,表示若是拿不到锁马上报错,而不是等待,mysql就没有no wait这个选项。另外mysql还有个问题是select for update语句执行中全部扫描过的行都会被锁上,这一点很容易形成问题。所以若是在mysql中用悲观锁务必要肯定走了索引,而不是全表扫描。服务器
乐观锁的特色先进行业务操做,不到万不得已不去拿锁。即“乐观”的认为拿锁多半是会成功的,所以在进行完业务操做须要实际更新数据的最后一步再去拿一下锁就好。架构
乐观锁在数据库上的实现彻底是逻辑的,不须要数据库提供特殊的支持。通常的作法是在须要锁的数据上增长一个版本号,或者时间戳,而后按照以下方式实现:并发
1. SELECT data AS old_data, version AS old_version FROM …; 2. 根据获取的数据进行业务操做,获得new_data和new_version 3. UPDATE SET data = new_data, version = new_version WHERE version = old_version if (updated row > 0) { // 乐观锁获取成功,操做完成 } else { // 乐观锁获取失败,回滚并重试 }
乐观锁是否在事务中其实都是无所谓的,其底层机制是这样:在数据库内部update同一行的时候是不容许并发的,即数据库每次执行一条update语句时会获取被update行的写锁,直到这一行被成功更新后才释放。所以在业务操做进行前获取须要锁的数据的当前版本号,而后实际更新数据时再次对比版本号确认与以前获取的相同,并更新版本号,便可确认这之间没有发生并发的修改。若是更新失败便可认为老版本的数据已经被并发修改掉而不存在了,此时认为获取锁失败,须要回滚整个业务操做并可根据须要重试整个过程。好吧,在此唠叨总结下这两个锁:
总结
乐观锁在不发生取锁失败的状况下开销比悲观锁小,可是一旦发生失败回滚开销则比较大,所以适合用在取锁失败几率比较小的场景,能够提高系统并发性能
乐观锁还适用于一些比较特殊的场景,例如在业务操做过程当中没法和数据库保持链接等悲观锁没法适用的地方
根据update结果来判断,咱们能够在sql2的时候加一个判断条件update table set 库存=xxx where 库存>0,若是返回false,则说明库存不足,并回滚事务。
借助文件排他锁,在处理下单请求的时候,用flock锁定一个文件,若是锁定失败说明有其余订单正在处理,此时要么等待要么直接提示用户"服务器繁忙"
大体代码以下:
阻塞(等待)模式
<?php $fp = fopen("lock.txt", "w+"); if(flock($fp,LOCK_EX)) //锁定当前指针,,, { //..处理订单 flock($fp,LOCK_UN); } fclose($fp); ?>
非阻塞模式
<?php $fp = fopen("lock.txt", "w+"); if(flock($fp,LOCK_EX | LOCK_NB)) { //..处理订单 flock($fp,LOCK_UN); } else { echo "系统繁忙,请稍后再试"; } fclose($fp); ?>
.若是是分布式集群服务器,就须要一个或多个队列服务器 小米和淘宝的抢购仍是有稍许不一样的,小米重在抢的那瞬间,抢到了名额,就是你的,你就能够下单结算。而淘宝则重在付款的时候的过滤,作了多层过滤,好比要卖10件商品,他会让大于10的用户抢到,在付款的时候再进行并发过滤,一层层的减小一瞬间的并发量。
使用redis锁 product_lock_key 为票锁key 当product_key存在于redis中时,全部用户均可以进入下单流程。 当进入支付流程时,首先往redis存放sadd(product_lock_key, “1″),若是返回成功,进入支付流程。若是不成,则说明已经有人进入支付流程,则线程等待N秒,递归执行sadd操做。
固然相似于淘宝双11的疯抢架构远远比我说滴这些复杂多啦....更多解决方案须要不停滴去实战中获取心得....你们有好的解决思路清随时共享留言哈