首頁  >  文章  >  後端開發  >  php高並發如何解決

php高並發如何解決

小云云
小云云原創
2018-03-26 10:39:506058瀏覽

本文主要和大家分享php高並發如何解決,涉及搶購、秒殺、抽獎、搶票等活動時,為了避免超賣,那麼庫存數量是有限的,但是如果同時下單人數超過了庫存數量,就會導致商品超賣問題。那我們要怎麼解決這個問題呢,我的思路如下(偽代碼): 

sql1:查詢商品庫存

if(库存数量 > 0)
{
  //生成订单...
  sql2:同时库存-1
}

當沒有並發時,上面的流程看起來是再正常不過了,假設同時兩個人下單,而庫存只有1個了,在sql1階段兩個人查詢到的庫存都是>0的,於是最終都執行了sql2,庫存最後變為-1,超售了,這不是我們想要的結果吧。

解決這個問題比較流行的思路我總結了下:
1.用額外的單進程處理一個隊列,下單請求放到隊列裡,一個個處理,就不會有並發的問題了,但是要額外的開啟後台進程以及延遲問題,這裡暫不予考慮。這裡我可使用訊息隊列,我們常用到Memcacheq、Radis。 例如:有100張票可供用戶搶,那麼就可以把這100張票放到快取中,讀寫時不要加鎖。 當並發量大的時候,可能有500人左右搶票成功,這樣對於500後面的請求可以直接轉到活動結束的靜態頁面。進去的500個人中有400個人是不可能取得商品的。所以可以根據進入隊列的先後順序只能前100個人購買成功。後面400個人就直接轉到活動結束頁面。當然進去500個人只是舉個例子,至於多少可以自己調整。而活動結束頁面一定要用靜態頁面,不要用資料庫。這樣就減輕了資料庫的壓力。
2.mysql樂觀鎖,意思是例如總庫存是2,搶購事件提交時,立刻將庫存+1,那麼此時庫存是3,然後訂單生成後,在更新庫存前再查詢一次庫存(因為訂單生成理所當然庫存-1,但是先不急,再查一次庫存返回結果是3),看看跟預期的庫存數量(這裡預期的庫存是3)是否保持一致,不一致就回滾,提示用戶庫存不足。這裡說道悲觀鎖,可能有朋友會問,那一定有樂觀鎖了吧??這裡我就淺談下我所了解的悲觀與樂觀鎖了

悲觀鎖與樂觀鎖是兩種常見的資源並發鎖設計思路,​​也是並發程式設計中非常基礎的概念。本文將對這兩種常見的鎖定機制在資料庫資料上的實作進行比較系統的介紹。

悲觀鎖(Pessimistic Lock)

悲觀鎖的特點是先取得鎖,再進行業務操作,即「悲觀」的認為獲取鎖是非常有可能失敗的,因此要先確保獲取鎖定成功再進行業務操作。通常所說的「一鎖二查三更新」即指的是使用悲觀鎖。通常來講在資料庫上的悲觀鎖需要資料庫本身提供支持,即透過常用的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中用悲觀鎖務必要確定走了索引,而不是全表掃描。

樂觀鎖(Optimistic Lock)

樂觀鎖的特性先進行業務操作,不到萬不得已不去拿鎖。即「樂觀」的認為拿鎖多半是會成功的,因此在進行完業務作業需要實際更新資料的最後一步再去拿一下鎖就好。

樂觀鎖在資料庫上的實作完全是邏輯的,不需要資料庫提供特殊的支援。一般的做法是在需要鎖定的資料上增加一個版本號,或是時間戳,然後按照以下方式實作:

1. SELECT data AS old_data, version AS old_version FROM …;2. 根据获取的数据进行业务操作,得到new_data和new_version3. UPDATE SET data = new_data, version = new_version WHERE version = old_versionif (updated row > 0) {    // 乐观锁获取成功,操作完成
} else {    // 乐观锁获取失败,回滚并重试
}

乐观锁是否在事务中其实都是无所谓的,其底层机制是这样:在数据库内部update同一行的时候是不允许并发的,即数据库每次执行一条update语句时会获取被update行的写锁,直到这一行被成功更新后才释放。因此在业务操作进行前获取需要锁的数据的当前版本号,然后实际更新数据时再次对比版本号确认与之前获取的相同,并更新版本号,即可确认这之间没有发生并发的修改。如果更新失败即可认为老版本的数据已经被并发修改掉而不存在了,此时认为获取锁失败,需要回滚整个业务操作并可根据需要重试整个过程。好吧,在此唠叨总结下这两个锁:

  • 乐观锁在不发生取锁失败的情况下开销比悲观锁小,但是一旦发生失败回滚开销则比较大,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能

  • 乐观锁还适用于一些比较特殊的场景,例如在业务操作过程中无法和数据库保持连接等悲观锁无法适用的地方

3.根据update结果来判断,我们可以在sql2的时候加一个判断条件update table set 库存=xxx where 库存>0,如果返回false,则说明库存不足,并回滚事务。
4.借助文件排他锁,在处理下单请求的时候,用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);?>

5.如果是分布式集群服务器,就需要一个或多个队列服务器 小米和淘宝的抢购还是有稍许不同的,小米重在抢的那瞬间,抢到了名额,就是你的,你就可以下单结算。而淘宝则重在付款的时候的过滤,做了多层过滤,比如要卖10件商品,他会让大于10的用户抢到,在付款的时候再进行并发过滤,一层层的减少一瞬间的并发量。

6.使用redis锁 product_lock_key 为票锁key 当product_key存在于redis中时,所有用户都可以进入下单流程。 当进入支付流程时,首先往redis存放sadd(product_lock_key, “1″),如果返回成功,进入支付流程。如果不成,则说明已经有人进入支付流程,则线程等待N秒,递归执行sadd操作。

当然类似于淘宝双11的疯抢架构远远比我说滴这些复杂多啦....更多解决方案需要不停滴去实战中获取心得....大家有好的解决思路清随时共享留言哈。

相关推荐:

php高并发应当用Apache还是IIS

php高并发访问写文件

php高并发下的疑问。

以上是php高並發如何解決的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn