如何解決業務系統中的熱點問題
我們在做各種業務研發的時候經常會碰到熱點問題影響系統穩定性和性能瓶頸,例如支付系統中的熱點賬戶進出款,電商系統中的熱點商品參與秒殺,金融系統中的熱點理財產品搶購等,那今天就讓我帶大家來一起看下我們如何解決熱點問題。
首先我們要搞清楚的是熱點問題必須包含兩個字,一個是點,一個是熱,點表示我們在系統的業務路徑上有一個地方存在性能的瓶頸,比如資料庫,文件系統,網路,甚至於內存等,這個點一般有io,鎖等問題構成。熱表示其被訪問的頻率很高,就是說一個被訪問頻率很高的io或鎖自然而然就編程了我們系統業務路徑上的性能瓶頸。
其次我們需要弄清楚我們的熱點問題是屬於讀熱點問題還是寫熱點問題,兩種熱點問題的處理方案完全不一樣,比如我們對一個熱門的秒殺商品詳情頁的訪問就屬於是讀熱點問題,對一個秒殺商品的庫存搶操作就是一個寫熱點問題。雖然分離了讀熱點問題和寫熱點問題,但是往往在讀熱點問題中也需要處理寫熱點問題的解決方案,比如我對一個熱門的秒殺商品詳情的讀熱點問題使用了緩存解決方案,但因為商家對商品做了更新價格的東西,立馬需要對寫熱點而造成的緩存臟數據做清理的操作,因此就變成了一個讀寫混合的熱點問題。
然後我們開始先討論比較容易解決的讀熱點問題的解決方案。一般我們做系統之出使用資料庫,直接對用戶的請求做sql的select操作,那對於此類的熱點問題我們首先想到的是需要優化資料庫的讀操作,我們對應的查詢是否走了索引,走的是否是唯一索引甚至於主健效果最佳,優化了sql性能後我們可以藉助於mysql innodb的buffer做一些文章,在資料庫層面就提供足夠的緩衝區,加速對應的性能,實驗證明,只要走的是主健或唯一索引,在innodb緩衝區足夠大的情況下,mysql抗上億的數據也是沒有任何問題的,真正出問題的不是點而是熱,由於訪問頻次太高,mysql的cpu扛不住了,這個時候我們考慮到的是將對應的讀熱點放到例如redis的緩存中用於卸載壓力,由於redis4版本以後就可以支持cluster的集群模式,其藉助分片集群的效果理論上可以擴展到1000個左右的節點,如此一來我們可以依靠緩存去解決讀熱點問題,一旦商家變更了讀熱點的數據,我們可以在業務應用中使用提交後非同步清除緩存的方式將redis的數據清除,這樣在下一次的請求中可以依靠資料庫的回源更新redis數據。
那既然我們討論的是讀熱點問題,就和redis的水平擴展能力無關,因為是個熱點數據,則必定會被分片路由到一個redis節點上,當熱度大到連redis節點都無法承受的時候,我們可以考慮將原本的一個熱點做三分拷貝,比如我們的熱點key叫miaosha_item_1,我們可以考慮隨機的生成三個key分別叫miaosha_item_1_key_1,miaosha_item_1_key_2,miaosha_item_1_key_3,對應的value都是這個商品value本身,這樣當用戶請求過來後我們可以隨機的生成1-3的數字以決定這次請求我們訪問哪個key,這樣人為的將一個熱點的tps降到了原來的三分之一,以空間換時間,另外我們還可以考慮在應用伺服器上做本地的cache內存,由於應用伺服器本身容量有限,內存中不能放太多數據,也不能存很長時間,我們推薦使用google研發的guava cache包,提供給我們很好的lru cache隊列的能力,一般本地的緩存不要設置太長時間,一是出於內存容量考慮,二是出於清理本地緩存不像清理redis,需要我們的每台應用服務感知到數據的變更,一般可以用廣播型的mq消息解決,推薦rocketmq 的廣播型消息,使得訂閱對應商品信息變更的所有應用伺服器都有機會清理本地緩存。
接下來我們來解決更有挑戰的寫熱點問題,為什麼寫熱點問題處理起來比讀熱點更難的,因為讀操作可以並發,我可以做到無鎖操作的解決,但是寫操作不行啊,從來沒有說寫操作可以真正意義上並發的,都需要加鎖以防並發的方式寫入資料庫或者文件存儲中,那我們怎麼優化呢?
首先我們還是一樣先解決點的問題再解決熱的問題,針對點我們一般寫操作會選用資料庫之類的文件存儲設備,mysql對數據存儲有比較好的優化,其基於寫事務日誌,也就是redo,undo log,然後等系統空閑的時候將數據刷入磁碟的,由於事務日誌的存在,即使系統掛了再啟動的時候也可以根據redo log恢複數據,那為啥寫log比寫數據快那麼多的,因為寫日誌是一個順序追加寫的方式,磁碟的磁頭不需要隨機的移動尋找寫入點,只要順序的寫下去即可,配合ssd固態硬碟,整個寫入性能可以做到很高。但是磁碟操作終究是磁碟操作,我們試著可以將寫入的目標點移到緩存中,比如我們將秒殺的庫存移到redis中,這麼一來,點的瓶頸的天花板瞬間就提升到了很多倍,但是一旦將數據落到沒有辦法保證磁碟落地能力的緩存中就需要依靠一些機制去保證可靠性,不至於在緩存丟失的情況下造成超賣等災難,我們可以依靠rocketmq非同步事務型的消息保持redis和資料庫之間的數據同步,解決緩存異常情況下我們可以依靠資料庫恢復對應的數據。
那非同步化是解決問題的最終方案嗎?顯然不是,非同步化只是將對應的寫熱點問題延遲到後面去解決,不至於卡住前端的用戶體驗,但是一旦這個點熱了起來,後端伺服器和磁碟的壓力還在,那我們還有什麼方法去解決呢?我們都知道寫入操作之所以在熱點問題的情況下那麼難解決,是因為寫同一份數據的操作不能並發,必須得要通過競爭鎖的機制去競爭以獲得線程的寫入許可權,我們突然可以想到,鎖這個東西本身就是一個耗性能的來源,試想兩個人要搶同一個食堂阿姨拿出來的飯,你爭我搶,我搶到了吃晚了再給後面的其他人在爭,在競爭的過程中所有人,所有線程的資源都被白白消耗掉了,最終還是只有一個人在那個時刻可以吃到飯。那針對這種情況我們是否可以有更好的解決方案呢?還是考慮搶飯吃這個場景,在現實生活中最高效的方式是什麼,就是排隊,大家都不要競爭,按照先到先得的方式將所有對熱點的寫入訪問操作隊列化,使用單線程的方式去隊列中取得下一個寫入操作,然後寫完後再取下一個,這樣可以避免掉寫鎖競爭的無謂cpu和內存消耗,也可以使用單線程的方式解決,沒有cpu調度切換的開銷,這就是我們常說的在無鎖的情況下,單線程排隊比多線程更高效,我們把這種解決寫熱點的方式叫做緩衝入賬~
以上講了那麼多其實還有一些比較重要的點,無論是讀熱點還是寫熱點問題的解決方案,如果流量超過我的系統能力的上限,不好意思,拒絕服務,將內部的等待隊列先消化完再來服務您,保證我們可以高效的做完這單再接下單。
※為什麼Flink會成為下一代大數據處理框架的標準?
※監控信息推與拉
TAG:千鋒JAVA開發學院 |