涉及搶購、秒殺、抽獎、搶票等活動時,爲了不超賣,那麼庫存數量是有限的,可是若是同時下單人數超過了庫存數量,就會致使商品超賣問題。那麼咱們怎麼來解決這個問題呢,個人思路以下(僞代碼):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的瘋搶架構遠遠比我說滴這些複雜多啦....更多解決方案須要不停滴去實戰中獲取心得....你們有好的解決思路清隨時共享留言哈