表數據量大讀寫緩慢如何優化(1)【冷熱分離】

今天討論的內容是冷熱分離,也許概念並不陌生,對其使用場景也比較熟悉,但涉及鎖的內容時仍然須要認真思考,這部份內容在咱們實際開發中的「坑」仍是很多的。數據庫

業務場景一

曾經經歷過供應鏈相關的架構優化,當時平臺上有一個訂單功能,裏面的主表有幾千萬數據量,加上關聯表,數據量達到上億。
這麼龐大的數據量,讓平臺的查詢訂單變得格外遲緩,查詢一次都要二三十秒,並且多點擊幾回就會出現宕機。好比業務員屢次查詢時,數據庫的 CPU 會立馬狂飆,服務器線程也降不下來。服務器

當時,咱們嘗試了優化表結構、業務代碼、索引、SQL 語句等辦法來提升響應速度,但這些方法治標不治本,查詢速度仍是很慢。多線程

考慮到咱們手頭上還有其餘優先級高的需求須要處理,爲此,咱們跟業務方反饋:「這功能之後大家能不用就不用,暫時先忍受一下。」可通過一段時間後,業務方實在受不了了,直接跟咱們放狠話,無奈之下咱們屈服了。架構

最終,咱們決定採用一個性價比高的解決方案,簡單方便地解決了這個問題。在處理數據時,咱們將數據庫分紅了冷庫和熱庫 2 個庫,不經常使用數據放冷庫,經常使用數據放熱庫。併發

經過這樣的方法處理後,由於業務員查詢的基本是近期經常使用的數據,經常使用的數據量大大減小了,就不再會出現宕機的狀況了,也大大提高了數據庫響應速度。ide

其實上面這個方法,就是「冷熱分離」。優化

1、什麼是冷熱分離

冷熱分離就是在處理數據時將數據庫分紅冷庫和熱庫 2 個庫,冷庫指存放那些走到了終態的數據的數據庫,熱庫指存放還須要修改的數據的數據庫。網站

2、什麼狀況下使用冷熱分離?

假設業務需求出現了以下狀況,就能夠考慮使用冷熱分離的解決方案:線程

  • 數據走到終態後,只有讀沒有寫的需求,好比訂單完結狀態;
  • 用戶能接受新舊數據分開查詢,好比有些電商網站默認只讓查詢3個月內的訂單,若是你要查詢3個月前的訂單,還須要訪問另外的單獨頁面。

3、冷熱分離實現思路

在實際操做過程當中,冷熱分離總體實現思路以下:設計

一、如何判斷一個數據究竟是冷數據仍是熱數據?

二、如何觸發冷熱數據分離?

三、如何實現冷熱數據分離?

四、如何使用冷熱數據?

接下來,咱們針對以上4個問題進行詳細的分析。

(一)如何判斷一個數據究竟是冷數據仍是熱數據?

通常而言,在判斷一個數據究竟是冷數據仍是熱數據時,咱們主要採用主表裏的 1 個或多個字段組合的方式做爲區分標識。其中,這個字段能夠是時間維度,好比「下單時間」這個字段,咱們能夠把 3 個月前的訂單數據看成冷數據,3 個月內的看成熱數據。

固然,這個字段也能夠是狀態維度,好比根據「訂單狀態」字段來區分,已完結的訂單看成冷數據,未完結的訂單看成熱數據。

咱們還能夠採用組合字段的方式來區分,好比咱們把下單時間 > 3 個月且狀態爲「已完結」的訂單標識爲冷數據,其餘的看成熱數據。

而在實際工做中,最終究竟使用哪一種字段來判斷,仍是須要根據你的實際業務來定。

關於判斷冷熱數據的邏輯,這裏還有 2 個注意要點必須說明:

  • 若是一個數據被標識爲冷數據,業務代碼不會再對它進行寫操做;
  • 不會同時存在讀冷/熱數據的需求。

(二)如何觸發冷熱數據分離?

瞭解了冷熱數據的判斷邏輯後,咱們就要開始考慮如何觸發冷熱數據分離了。通常來講,冷熱數據分離的觸發邏輯分3種。

一、直接修改業務代碼,每次修改數據時觸發冷熱分離(好比每次更新了訂單的狀態,就去觸發這個邏輯);

二、若是不想修改原來業務代碼,可經過監聽數據庫變動日誌binlog的方式來觸發(數據庫觸發器也可);

三、經過定時掃描數據的方式來觸發(數據庫定時任務或經過程序定時任務來觸發);

針對以上三種觸發邏輯,選擇哪一種比較好呢?看完如下表格的分析,你內心就有答案了。

修改寫操做的業務代碼 監聽數據庫變動日誌 定時掃描數據庫
優勢 一、代碼靈活可控。二、保證明時性 一、與業務代碼解耦。二、能夠作到低延時。 一、與業務代碼解耦。二、能夠覆蓋根據時間區分冷熱數據的場景。
缺點 一、不能按照時間區分冷熱,當數據變爲冷數據,期間可能沒有進行任何操做。二、須要修改全部數據寫操做的代碼。 一、沒法按照時間區分冷熱,當數據變爲冷數據,期間沒有進行任何操做。二、須要考慮數據併發操做的問題,即業務代碼與冷熱變動代碼同時操做同一數據。 一、不能作到實時性

根據表格內容對比,咱們能夠得出每種出發邏輯的建議場景。

  1. 修改寫操做的業務代碼:建議在業務代碼比較簡單,而且不按照時間區分冷熱數據時使用。
  2. 監聽數據庫變動日誌:建議在業務代碼比較複雜,不能隨意變動,而且不按照時間區分冷熱數據時使用。
  3. 定時掃描數據庫:建議在按照時間區分冷熱數據時使用。

(三)如何分離冷熱數據?

分離冷熱數據的基本邏輯以下:

一、判斷數據是冷是熱;

二、將要分離的數據插入冷數據中;

三、再從熱數據庫中刪除分離的數據。

這個邏輯看起來簡單,而實際作方案時,如下三點咱們都得考慮在內,這一點就不簡單了。

(1)一致性:同時修改過個數據庫,如何保證數據的一致性

​ 這裏提到的一致性要求,指咱們如何保證任何一步出錯後數據仍是一致的,解決方案爲只要保證每一步均可以重試且操做都有冪等性就行,具體邏輯分爲四步。

  • 在熱數據庫中,給要搬的數據加個標識: flag=1。(1表明冷數據,0表明熱數據)

  • 找出全部待搬的數據(flag=1):這步是爲了確保前面有些線程由於部分緣由失敗,出現有些待搬的數據沒有搬的狀況。

  • 在冷數據庫中保存一份數據,但在保存邏輯中需加個判斷以此保證冪等性(這裏須要用事務包圍起來),通俗點說就是假如咱們保存的數據在冷數據庫已經存在了,也要確保這個邏輯能夠繼續進行。

  • 從熱數據庫中刪除對應的數據。

(2)數據量大:假設數據量大,一次性處理不完,該怎麼辦?是否須要使用批量處理?

​ 前面說的3種冷熱分離的觸發邏輯,前 2 種基本不會出現數據量大的問題,由於每次只須要操做那一瞬間變動的數據,但若是採用定時掃描的邏輯就須要考慮數據量這個問題了。

​ 這個實現邏輯也很簡單,在搬數據的地方咱們加個批量邏輯就能夠了。爲方便理解,咱們來看一個示例。

​ 假設咱們每次能夠搬 50 條數據:

​ a. 在熱數據庫中給要搬的數據加個標識:flag=1;

​ b. 找出前 50 條待搬的數據(flag=1);

​ c. 在冷數據庫中保存一份數據;

​ d. 從熱數據庫中刪除對應的數據;

​ e. 循環執行 b。

(3)併發性:假設數據量大到要分到多個地方並行處理,該怎麼辦?

​ 在定時搬運冷熱數據的場景裏(好比天天),假設天天處理的數據量大到連單線程批量處理都來不及,咱們該怎麼辦?這時咱們就能夠開多個線程併發處理了。(雖然大部分狀況下多線程較快,但我曾碰到過這種狀況:當單線程 batch size 到必定數值時效率特別高,比多線程任何 batch size 都快。因此,須要留意:若是遇到多線程速度不快,咱們就考慮控制單線程。)

​ 當多線程同時搬運冷熱數據,咱們須要考慮以下實現邏輯。

第 1 步:如何啓動多線程?

​ 由於咱們採用的是定時器觸發邏輯,這種觸發邏輯性價比最高的方式是設置多個定時器,並讓每一個定時器之間的間隔短一些,而後每次定時啓動一個線程就開始搬運數據。

​ 還有一個比較合適的方式是自建一個線程池,而後定時觸發後面的操做:先計算待搬動的熱數據的數量,再計算要同時啓動的線程數,若是大於線程池的數量就取線程池的線程數,假設這個數量爲 N,最後循環 N 次啓動線程池的線程搬運冷熱數據。

第 2 步:某線程宣佈某個數據正在操做,其餘線程不要動(鎖)。

​ 關於這個邏輯,咱們須要考慮 3 個特性。

  • 獲取鎖的原子性: 當一個線程發現某個待處理的數據沒有加鎖,而後給它加鎖,這 2 步操做必須是原子性的,即要麼一塊兒成功,要麼一塊兒失敗。實際操做爲先在表中加上 LockThread 和 LockTime 兩個字段,而後經過一條 SQL 語句找出待遷移的未加鎖或鎖超時的數據,再更新 LockThread=當前線程,LockTime=當前時間,最後利用 MySQL 的更新鎖機制實現原子性。

  • 獲取鎖必須與開始處理保證一致性: 當前線程開始處理這條數據時,須要再次檢查下操做的數據是否由當前線程鎖定成功,實際操做爲再次查詢一下 LockThread= 當前線程的數據,再處理查詢出來的數據。

  • 釋放鎖必須與處理完成保證一致性: 當前線程處理完數據後,必須保證鎖釋放出去。

    第 3 步:某線程正常處理完後,數據不在熱庫,直接跑到了冷庫,這是正常的邏輯,倒沒有什麼特別須要注意的點。

    第 4 步:某線程失敗退出了,結果鎖沒釋放怎麼辦(鎖超時)?

    鎖沒法釋放: 若是鎖定這個數據的線程異常退出了且來不及釋放鎖,致使其餘線程沒法處理這個數據,此時該怎麼辦?解決方案爲給鎖設置一個超時時間,若是鎖超時了還未釋放,其餘線程可正常處理該數據。

    設置超時時間時,咱們還應考慮若是正在處理的線程並未退出,因還在處理數據致使了超時,此時又該怎麼辦?解決方案爲儘可能給超時的時間設置成超過處理數據的合理時間,且處理冷熱數據的代碼裏必須保證是冪等性的。

    最後,咱們還得考慮一個極端狀況:若是當前線程還在處理數據,此時正在處理的數據的鎖超時了,另一個線程把正在處理的數據又進行了加鎖,此時該怎麼辦?咱們只須要在每一步加判斷容錯便可,由於搬運冷熱數據的代碼比較簡單,經過這樣的操做當前線程的處理就不會破壞數據的一致性。

(四)如何使用冷數據

​ 在功能設計的查詢界面上,通常都會有一個選項供咱們選擇須要查詢冷數據仍是熱數據,若是界面上沒有提供,咱們能夠直接在業務代碼裏區分。(說明:在判斷是冷數據仍是熱數據時,咱們必須確保用戶不容許有同時讀冷熱數據的需求。)

歷史數據如何遷移?
通常而言,只要跟持久化層有關的架構方案,咱們都須要考慮歷史數據的遷移問題,即如何讓舊架構的歷史數據適用於新的架構?

​ 由於前面的分離邏輯在考慮失敗重試的場景時,恰好覆蓋了這個問題,因此這個問題的解決方案也很簡單,咱們只須要給全部的歷史數據加上標識:flag=1 後,程序就會自動遷移了。

冷熱分離解決方案的不足

​ 不得不說,冷熱分離解決方案確實能解決寫操做慢和熱數據慢的問題,但仍然存在諸多不足。

不足一: 用戶查詢冷數據速度依舊很慢,若是查詢冷數據的用戶比例很低,好比只有 1%,那麼這個方案就沒問題。

不足二: 業務沒法再修改冷數據,由於冷數據多到必定程度時,系統承受不住。(這點能夠經過冷庫再分庫來解決,後面再來探討)

相關文章
相關標籤/搜索