關于秒殺
隨著雙11活動的不斷發展,小米饑餓營銷模式的興起,“秒殺”已經成為一個熱點詞匯。在一些活動中,熱銷商品會以驚人的速度售罄,比如最近本人在搶購美圖M4手機,12點開賣,1分鐘之內就被售罄。
秒殺的實現
對于關注數據庫的本人來說,更關心的是如何高效的實現秒殺應用。之前淘寶在2013年的數據庫大會上分享過他們的秒殺方案,修改MySQL數據庫源碼來實現高效的秒殺應用。但是,那篇分享過于高大上,沒有給出具體的實現過程。另外,從其他渠道打聽到的是這個方案并沒有在生產環境上線,不知道有沒有其他知道內幕的小伙伴,具體來說說淘寶的方案是否有上線。
當然,有多種方法來優化秒殺應用,比如使用memcached的CAS功能,但是這些方法都不能實現事務的特性。對于深受Jim Gray事務處理教育長大的一代,本人覺得任何事情都應該事務的,不支持事務只不過能取得暫時的勝利,整個世界的哲學應該就是事務,即要么全做,要么全不做,不要處于一個中間狀態。本人的為人哲學就是,要么不去設定一個目標,否則這個目標一定會去實現。比如,本人決定去讀博,那么一定會完成這個學業。
本人感覺雖然淘寶沒有給出具體的實現方式,但是拋出了秒殺應用對于數據庫壓力的問題所在,即大并發量下更新同一行數據的壓力。例如并發執行如下的SQL語句模擬秒殺場景:
BEGIN;
INSERT INTO stock_log VALUES
SELECT count FROM stock WHERE id=1 AND count>0 FOR UPDATE;
UPDATE stock SET count = count -1 WHERE id=1 AND count > 0;
COMMIT;
在做秒殺時,最主要是對庫存表進行操作,在操作前可能需要插入一些其他操作,比如日志等,然后就是對庫存表進行更新。下圖顯示增大并發量的情況下,事務處理的性能:
顯而易見的是隨著并發量的增大,事務處理的性能越差。這和淘寶之前分享的數據基本一致。導致其中的原因就是秒殺是對同一件商品進行更新,需要對同一行記錄加鎖,因此秒殺操作雖然是并行的,但是在數據庫層面是串行的。
隨著并發的不斷增大,不斷發生事務的鎖等待與喚醒操作,導致性能的急劇下降。如果通過perf工具來觀察的話,應該可以觀察到類似如下的內容:
#
59.06% mysqld mysqld [.] lock_deadlock_recursive
16.63% mysqld libc-2.13.so [.] 0x115171
3.09% mysqld mysqld [.] lock_rec_get_prev
2.96% mysqld mysqld [.] my_strnncollsp_utf8
......
可以發現鎖的死鎖檢測占據了大部分的CPU時間,究其原因,就是因為鎖等待。
innodb_thread_concurrency
有小伙伴或許會知道可以通過innodb_thread_concurrency參數來控制InnoDB存儲引擎層的并發量。的確,通過這個參數可以限制進入InnoDB引擎層的事務數量,對比測試的話,性能上的確會有一定的提升:
可以發現,將innodb_thread_concurrency設置為16,性能的確會有一定的提升。并發線程數在128的時候,TPS從原有的4300提升為了7200,將近有65%的性能提升。但是在256線程之后,性能依舊堪憂。
導致上述的原因是雖然在InnoDB存儲引擎層做了“限流”,但是MySQL數據庫上層的線程依然需要等待喚醒。
線程池技術
業界提供了很多關于秒殺MySQL的解決方案,然而非常的定制化,并且需要應用修改相信的程序,比如通過在SQL語句中寫hint來進行排隊,而這種的排隊機制在我看來在低并發量下性能反而又會變差。因此,一個通用的解決方案是采用線程池技術。
線程池可以在MySQL上層限制住同時運行的MySQL的事務數,這樣就解決了由秒殺而導致的資源競爭問題。例如,通過前面的測試,已經得知并發16線程時,秒殺可以有最好的性能,那么這時用戶將線程池的大小設置為16,這樣就能獲得用戶預期想要的性能:
可以發現即使在4096個并發線程下,秒殺依然可以有近10000的TPS。通過線程池技術,秒殺就是這么簡單,無需任何應用端的修改。
但是線程池這里有個參數thread_pool_oversubscribe,這個參數其實有點類似云計算中“超售”概念,即MySQL的線程池允許有額外的線程運行。該參數默認是3,之前thread_pool_size設置為16,那么總共允許16*(1+3)=64個線程同時運行。這個參數的默認值本身沒有問題,但是對于秒殺應用來說確是不需要的,因為之前已經討論過,秒殺應用是串行的。所以將參數thread_pool_oversubscribe設置為1,秒殺應用還能有進一步的提升:
可以發現在大并發的線程下,性能還能有10%~30%的提升。
總結
其實秒殺應用的數據庫層優化非常簡單,各個層面做好排隊即可,如:
-
應用層做好對于單個商品搶購的數量限制
-
MySQL數據庫層使用線程池技術來保證大并發量下的性能
-
調整參數thread_pool_oversubscribe用來進一步提升性能
MySQL企業版提供了線程池插件,但是需要額外的費用。小伙伴們可以使用開源的MySQL版本InnoSQL,其免費提供了線程池,可以保證應用在大并發量下依舊保證應用的穩定性,特別是對于秒殺類的應用。
文章列表