關於秒殺
隨著雙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數據庫上層的線程依然需要等待喚醒。