秒殺場景下MySQL的低效緣由和改進

在「中國數據庫技術大會」上,淘寶 share了「秒殺場景下MySQL的低效」,詳細分析了秒殺的技術難點及改進措施,簡而言之,主要就是在高併發事務請求的狀況下,數據庫性能因爲死鎖檢測等因素直線降低,在這種場景下,單純的關閉死鎖檢測雖然能夠提高必定的性能,但這頂可能是治標而已,如何治本?數據庫


淘寶給出來兩個改進方法併發

請求排隊:若是請求一股腦的涌入數據庫,勢必會因爲爭搶資源形成性能降低,經過排隊,讓請求從混沌到有序,從而避免數據庫在協調大量請求時過載。
請求合併:甲買了一個商品,乙也買了同一個商品,與其把甲乙當作當作單獨的請求分別執行一次商品庫存減一的操做,不如把他們合併後統一執行一次商品庫存減二的操做,請求合併的越多,效率提高的就越大。
異步


惋惜的是淘寶的這些改進方法都是經過修改MySQL源代碼在數據層實現的,對芸芸衆生的咱們而言,簡直是一個沒法逾越的技術門檻!那麼是否能夠在應用層實現呢?高併發


請求排隊
經過Redis實現隊列是一件很簡單的事情,應用LIST或者ZSET就能夠搞定,若是沒有優先級之類需求的話,一般LIST是一個更好的選擇,由於它的時間複雜度更低,固然,若是處理隊列的速度足夠快,那麼ZSET也不錯。

把請求保存到隊列裏以後,能夠經過Gearman實現Worker來消費隊列,請求的生產和消費是異步的,因此不會出現併發擁堵,可是可能發生延遲,若是出現這種狀況,能夠經過增長Worker的數量能夠加快消費隊列的速度。

讓咱們從頭捋捋:程序收到請求,而後把請求保存到Redis隊列裏,Gearman經過Worker處理隊列裏的請求,但是處理完以後如何通知程序呢?由於整個過程是異步的,因此除非程序支持某種形式的回調,不然很難通知。

最容易想到的解決辦法是在程序裏經過輪詢來查詢請求是否已經處理完成,但這無疑會增長數據庫的負載,同時程序的實時性也會大打折扣。好在咱們有其它的方法,好比說Redis提供了名爲BLPOP和BRPOP的方法,它嘗試從一個LIST裏取元素,若是LIST爲空則會堵塞鏈接,利用這個特性咱們能夠實現一個簡易的通知功能:程序把請求保存到Redis隊列裏,而後調用BLPOP或BRPOP方法等通知,由於此時LIST爲空,因此會堵塞鏈接,與此同時Gearman的Work處理完隊列裏的請求後,往LIST裏保存一個狀態碼,程序感知到這個狀態碼,並經過狀態碼判斷出請求是成功仍是失敗。

整個過程當中有一些須要注意的地方,好比說由於BLPOP和BRPOP都屬於堵塞性質的操做,因此一旦隊列處理速度跟不上,程序就會堆積大量鏈接,這可能會引發不少連鎖問題:一方面可能致使內存不足,以PHP爲例,一個鏈接一般佔用10M左右,堆積一千個鏈接的話,10G內存就沒有了;另外一方面大量的鏈接可能耗盡端口資源,具體取決於內核參數「net.ipv4.ip_local_port_range」。此時提升處理隊列的速度是惟一的出路。
性能


其實Gearman的Jobserver自己就實現了一個隊列,並且還能夠將這個隊列用MYSQL來代替來持久化,保證隊列請求不會丟失,客戶端的請求先到Jobserver隊列,而後Worker是真正的鏈接數據的程序,Jobserver根據Worker的閒忙將隊列裏的任務指派給Worker去處理,Worker鏈接數據是有限的,這樣請求就不會一下擁入數據庫。spa


請求合併
orm

把相似的請求合併起來是一件既簡單又複雜的事情,介於本文的標題是笨法玩秒殺,咱們就挑簡單的說,當咱們經過Gearman的Work去處理隊列裏的請求時,一般是彈出一個請求處理一個請求,下面咱們作出一些調整,每次再也不只從隊列裏彈出一個請求,取而代之,咱們一次性從隊列裏取出多個請求,而後在程序裏完成合並後再執行。固然這裏有不少細節問題,因爲篇幅關係就很少說了。server

相關文章
相關標籤/搜索