[轉]秒殺系統優化方案之緩存、隊列、鎖設計思路

本文轉自:http://www.javashuo.com/article/p-ooiioipc-my.htmlphp

1、爲何難

    秒殺系統難作的緣由:庫存只有一份,全部人會在集中的時間讀和寫這些數據。例如小米手機每週二的秒殺,可能手機只有1萬部,但瞬時進入的流量多是幾百幾千萬。又例如12306搶票,亦與秒殺相似,瞬時流量更甚。html

主要須要解決的問題有兩個:mysql

  1. 高併發對數據庫產生的壓力
  2. 競爭狀態下如何解決庫存的正確減小(超賣問題)

    對於第一個問題,已經很容易想到用緩存來處理搶購,避免直接操做數據庫,例如使用Redis。重點在於第二個問題,常規寫法:
    查詢出對應商品的庫存,看是否大於0,而後執行生成訂單等操做,可是在判斷庫存是否大於0處,若是在高併發下就會有問題,致使庫存量出現負數程序員

2、常見架構

 

流量到了億級別,常見站點架構如上:redis

  1. 瀏覽器端,最上層,會執行到一些JS代碼
  2. 站點層,這一層會訪問後端數據,拼html頁面返回給瀏覽器
  3. 服務層,向上遊屏蔽底層數據細節
  4. 數據層,最終的庫存是存在這裏的,mysql是一個典型

3、優化方向

1)將請求儘可能攔截在系統上游:傳統秒殺系統之因此掛,請求都壓倒了後端數據層,數據讀寫鎖衝突嚴重,併發高響應慢,幾乎全部請求都超時,流量雖大,下單成功的有效流量甚小【一趟火車其實只有2000張票,200w我的來買,基本沒有人能買成功,請求有效率爲0】 
2)充分利用緩存:這是一個典型的讀多寫少的應用場景【一趟火車其實只有2000張票,200w我的來買,最多2000我的下單成功,其餘人都是查詢庫存,寫比例只有0.1%,讀比例佔99.9%】,很是適合使用緩存。sql

4、優化細節

4.1)瀏覽器層請求攔截數據庫

點擊了「查詢」按鈕以後,系統那個卡呀,進度條漲的慢呀,做爲用戶,我會不自覺的再去點擊「查詢」,繼續點,繼續點,點點點。。。有用麼?無緣無故的增長了系統負載(一個用戶點5次,80%的請求是這麼多出來的),怎麼整?segmentfault

a 產品層面,用戶點擊「查詢」或者「購票」後,按鈕置灰,禁止用戶重複提交請求 
b JS層面,限制用戶在x秒以內只能提交一次請求後端

如此限流,80%流量已攔。瀏覽器

4.2)站點層請求攔截與頁面緩存

瀏覽器層的請求攔截,只能攔住小白用戶(不過這是99%的用戶喲),高端的程序員根本不吃這一套,寫個for循環,直接調用你後端的http請求,怎麼整?

a 同一個uid,限制訪問頻度,作頁面緩存,x秒內到達站點層的請求,均返回同一頁面 
b 同一個item的查詢,例如手機車次,作頁面緩存,x秒內到達站點層的請求,均返回同一頁面

如此限流,又有99%的流量會被攔截在站點層

4.3)服務層請求攔截與數據緩存
站點層的請求攔截,只能攔住普通程序員,高級黑客,假設他控制了10w臺肉雞(而且假設買票不須要實名認證),這下uid的限制不行了吧?怎麼整?

a 大哥,我是服務層,我清楚的知道小米只有1萬部手機,我清楚的知道一列火車只有2000張車票,我透10w個請求去數據庫有什麼意義呢?對於寫請求,作請求隊列,每次只透有限的寫請求去數據層,若是均成功再放下一批,若是庫存不夠則隊列裏的寫請求所有返回「已售完」

 

對於讀請求,還要我說麼?cache抗,不論是memcached仍是redis,單機抗個每秒10w應該都是沒什麼問題的

如此限流,只有很是少的寫請求,和很是少的讀緩存mis的請求會透到數據層去,又有99.9%的請求被攔住了

4.4)數據層閒庭信步
到了數據這一層,幾乎就沒有什麼請求了,單機也能扛得住,仍是那句話,庫存是有限的,小米的產能有限,透這麼多請求來數據庫沒有意義。

4.5)mysql批量入庫提升INSERT效率

5、Redis

    使用redis隊列(list),pushpop操做保證了原子性的實現。即便有不少用戶同時到達,也是依次執行。(mysql事務在高併發下性能降低很厲害)

先將商品庫存存入隊列:

<?php $store=1000; //商品庫存 $redis=new Redis(); $result=$redis->connect('127.0.0.1',6379); $res=$redis->llen('goods_store'); for($i=0; $i<$store; $i++){ $redis->lpush('goods_store',1); } echo $redis->llen('goods_store'); ?> 

客戶執行下單操做:

$redis=new Redis(); $result=$redis->connect('127.0.0.1',6379); $count = $redis->lpop('goods_store'); if(!$count){ echo '搶購失敗!'; return; } 

    緩存也是能夠應對寫請求的,好比咱們就能夠把數據庫中的庫存數據轉移到Redis緩存中,全部減庫存操做都在Redis中進行,而後再經過後臺進程把Redis中的用戶秒殺請求同步到數據庫中

6、總結

沒什麼總結了,上文應該描述的很是清楚了,對於秒殺系統,再次重複下兩個架構優化思路:
1)儘可能將請求攔截在系統上游 
2)讀多寫少經量多使用緩存
3) redis隊列緩存 + mysql 批量入庫

參考文檔1:秒殺系統架構優化思路
參考文檔2:【高併發簡單解決方案】redis隊列緩存 + mysql 批量入庫 + php離線整合 
參考文檔3:PHP+Mysql高併發解決
參考文檔4:php結合redis實現高併發下的搶購、秒殺功能

相關文章
相關標籤/搜索