天天看點

1.冷熱分離:表資料量大讀寫緩慢如何優化?                                            冷熱分離:表資料量大讀寫緩慢如何優化?什麼是冷熱分離?什麼情況下使用冷熱分離?

                                            冷熱分離:表資料量大讀寫緩慢如何優化?

業務場景

我曾經做過供應鍊相關的架構優化,當時我們平台有一個訂單功能,裡面的主表有幾千萬的資料量,加上關聯表,資料量達到上億。

這麼龐大的資料量,讓平台的查詢訂單變得格外遲緩,查詢一次都要二三十秒,而且多點選幾次就會出現當機。比如業務員多次查詢時,資料庫的 CPU 會立馬狂飙,伺服器線程也降不下來。

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

考慮到我們手頭上還有其他優先級高的需求需要處理,為此,我們跟業務方回報:“這功能以後你們能不用就不用,暫時先忍受一下。”可經過一段時間後,業務方實在受不了了,直接跟我們放狠話,無奈之下我們屈服了。

最終,我們決定采用一個成本效益高的解決方案,簡單友善地解決了這個問題。在處理資料時,我們将資料庫分成了冷庫和熱庫 2 個庫,不常用資料放冷庫,常用資料放熱庫。

通過這樣的方法處理後,因為業務員查詢的基本是近期常用的資料,常用的資料量大大減少了,就再也不會出現當機的情況了,也大大提升了資料庫響應速度。

其實上面這個方法,就是“冷熱分離”。

比如賬單、文章等

什麼是冷熱分離?

冷熱分離就是在處理資料時将資料庫分成冷庫和熱庫 2 個庫,冷庫指存放那些走到了終态的資料的資料庫,熱庫指存放還需要修改的資料的資料庫。

什麼情況下使用冷熱分離?

假設你的業務需求出現了如下情況,就可以考慮使用冷熱分離的解決方案:

資料走到終态後,隻有讀沒有寫的需求,比如訂單完結狀态;

使用者能接受新舊資料分開查詢,比如有些電商網站預設隻讓查詢 3 個月内的訂單,如果你要查詢 3 個月前的訂單,還需要通路另外的單獨頁面。

冷熱分離實作思路

在實際操作過程中,冷熱分離整體實作思路如下:

(一)如何判斷一個資料到底是冷資料還是熱資料?

(二)如何觸發冷熱資料分離?

(三)如何實作冷熱資料分離?

(四)如何使用冷熱資料?

(一)如何判斷一個資料到底是冷資料還是熱資料?

一般而言,在判斷一個資料到底是冷資料還是熱資料時,我們主要采用主表裡的 1 個或多個字段組合的方式作為區分辨別。其中,這個字段可以是時間次元,比如“下單時間”這個字段,我們可以把 3 個月前的訂單資料當作冷資料,3 個月内的當作熱資料。

當然,這個字段也可以是狀态次元,比如根據“訂單狀态”字段來區分,已完結的訂單當作冷資料,未完結的訂單當作熱資料。

我們還可以采用組合字段的方式來區分,比如我們把下單時間 > 3 個月且狀态為“已完結”的訂單辨別為冷資料,其他的當作熱資料。

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

關于判斷冷熱資料的邏輯,這裡還有 2 個注意要點必須說明:

如果一個資料被辨別為冷資料,業務代碼不會再對它進行寫操作;

不會同時存在讀冷/熱資料的需求。

(二)如何觸發冷熱資料分離?

了解了冷熱資料的判斷邏輯後,我們就要開始考慮如何觸發冷熱資料分離了。一般來說,冷熱資料分離的觸發邏輯分 3 種。

1.直接修改業務代碼,每次修改資料時觸發冷熱分離(比如每次更新了訂單的狀态,就去觸發這個邏輯),如下圖所示:

1.冷熱分離:表資料量大讀寫緩慢如何優化?                                            冷熱分離:表資料量大讀寫緩慢如何優化?什麼是冷熱分離?什麼情況下使用冷熱分離?

                                                                        觸發邏輯一:直接修改業務代碼

2.如果不想修改原來的業務代碼,可通過監聽資料庫變更日志 binlog 的方式來觸發,如下圖所示:

1.冷熱分離:表資料量大讀寫緩慢如何優化?                                            冷熱分離:表資料量大讀寫緩慢如何優化?什麼是冷熱分離?什麼情況下使用冷熱分離?

3.通過定時掃描資料庫的方式來觸發,如下圖所示:

1.冷熱分離:表資料量大讀寫緩慢如何優化?                                            冷熱分離:表資料量大讀寫緩慢如何優化?什麼是冷熱分離?什麼情況下使用冷熱分離?

針對以上 3 種觸發邏輯,我們到底選哪種比較好呢?待我分析完各自優缺點後,你心裡就會有答案了。

1.冷熱分離:表資料量大讀寫緩慢如何優化?                                            冷熱分離:表資料量大讀寫緩慢如何優化?什麼是冷熱分離?什麼情況下使用冷熱分離?

根據以上表格内容對比,我們可以得出每種觸發邏輯的建議場景。

修改寫操作的業務代碼:建議在業務代碼比較簡單,并且不按照時間區分冷熱資料時使用。

監聽資料庫變更日志:建議在業務代碼比較複雜,不敢随意變更,并且不按照時間區分冷熱資料時使用。

定時掃描資料庫:建議在按照時間區分冷熱資料時使用。

我做架構那會兒就是按照時間區分冷熱資料,是以選用了定時掃描資料庫的觸發方式。是以,到底選擇哪種觸發方式,還是需要根據你的具體業務需求決定。

(三)如何分離冷熱資料?

在講解如何分離冷熱資料之前,我們先來了解下分離冷熱資料的基本邏輯,隻有掌握了基本原理,才能真正了解事物本質。

分離冷熱資料的基本邏輯如下:

判斷資料是冷是熱;

将要分離的資料插入冷資料庫中;

再從熱資料庫中删除分離的資料。

1.冷熱分離:表資料量大讀寫緩慢如何優化?                                            冷熱分離:表資料量大讀寫緩慢如何優化?什麼是冷熱分離?什麼情況下使用冷熱分離?

(1)一緻性:同時修改多個資料庫,如何保證資料的一緻性?     

這裡提到的一緻性要求,指我們如何保證任何一步出錯後資料還是一緻的,解決方案為隻要保證每一步都可以重試且操作都有幂等性就行,具體邏輯分為四步。

  1. 在熱資料庫中,給要搬的資料加個辨別: ColdFlag=WaittingForMove。(實際進行中辨別字段的值用數字就可以,這裡是為了友善了解。)
  2. 找出所有待搬的資料(ColdFlag=WaittingForMove):這步是為了確定前面有些線程因為部分原因失敗,出現有些待搬的資料沒有搬的情況。
  3. 在冷資料庫中儲存一份資料,但在儲存邏輯中需加個判斷以此保證幂等性(這裡需要用事務包圍起來),通俗點說就是假如我們儲存的資料在冷資料庫已經存在了,也要確定這個邏輯可以繼續進行。
  4. 從熱資料庫中删除對應的資料。

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

前面講了 3 種冷熱分離的觸發邏輯,前 2 種基本不會出現資料量大的問題,因為每次隻需要操作那一瞬間變更的資料,但如果采用定時掃描的邏輯就需要考慮資料量這個問題了。

這個實作邏輯也很簡單,在搬資料的地方我們加個批量邏輯就可以了。為友善了解,我們來看一個示例。

假設我們每次可以搬 50 條資料:

a. 在熱資料庫中給要搬的資料加個辨別:ColdFlag=WaittingForMove;

b. 找出前 50 條待搬的資料(ColdFlag=WaittingForMove);

c. 在冷資料庫中儲存一份資料;

d. 從熱資料庫中删除對應的資料;

e. 循環執行 b。

(3)并發性:假設資料量大到要分到多個地方并行處理,該怎麼辦?

在定時搬運冷熱資料的場景裡(比如每天),假設每天處理的資料量大到連單線程批量處理都來不及,我們該怎麼辦?這時我們就可以開多個線程并發處理了。(雖然大部分情況下多線程較快,但我曾碰到過這種情況:當單線程 batch size 到一定數值時效率特别高,比多線程任何 batch size 都快。是以,希望同學們到時多留意下,如果遇到多線程速度不快,我們就考慮控制單線程。)

當多線程同時搬運冷熱資料,我們需要考慮如下實作邏輯。

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

因為我們采用的是定時器觸發邏輯,這種觸發邏輯成本效益最高的方式是設定多個定時器,并讓每個定時器之間的間隔短一些,然後每次定時啟動一個線程就開始搬運資料。

還有一個比較合适的方式是自建一個線程池,然後定時觸發後面的操作:先計算待搬動的熱資料的數量,再計算要同時啟動的線程數,如果大于線程池的數量就取線程池的線程數,假設這個數量為 N,最後循環 N 次啟動線程池的線程搬運冷熱資料。

第 2 步:某線程宣布某個資料正在操作,其他線程不要動(鎖)。

關于這個邏輯,我們需要考慮 3 個特性。 

  1. 擷取鎖的原子性: 當一個線程發現某個待處理的資料沒有加鎖,然後給它加鎖,這 2 步操作必須是原子性的,即要麼一起成功,要麼一起失敗。實際操作為先在表中加上 LockThread 和 LockTime 兩個字段,然後通過一條 SQL 語句找出待遷移的未加鎖或鎖逾時的資料,再更新 LockThread=目前線程,LockTime=目前時間,最後利用 MySQL 的更新鎖機制實作原子性。   
  2. 擷取鎖必須與開始處理保證一緻性: 目前線程開始處理這條資料時,需要再次檢查下操作的資料是否由目前線程鎖定成功,實際操作為再次查詢一下 LockThread= 目前線程的資料,再處理查詢出來的資料。
  3. 釋放鎖必須與處理完成保證一緻性: 目前線程處理完資料後,必須保證鎖釋放出去。                   

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

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

鎖無法釋放: 如果鎖定這個資料的線程異常退出了且來不及釋放鎖,導緻其他線程無法處理這個資料,此時該怎麼辦?解決方案為給鎖設定一個逾時時間,如果鎖逾時了還未釋放,其他線程可正常處理該資料。

設定逾時時間時,我們還應考慮如果正在處理的線程并未退出,因還在處理資料導緻了逾時,此時又該怎麼辦?解決方案為盡量給逾時的時間設定成超過處理資料的合理時間,且處理冷熱資料的代碼裡必須保證是幂等性的。

最後,我們還得考慮一個極端情況:如果目前線程還在處理資料,此時正在處理的資料的鎖逾時了,另外一個線程把正在處理的資料又進行了加鎖,此時該怎麼辦?我們隻需要在每一步加判斷容錯即可,因為搬運冷熱資料的代碼比較簡單,通過這樣的操作目前線程的處理就不會破壞資料的一緻性。

考慮到前面邏輯比較複雜,這裡我們特地畫了一個分離的流程圖,如下圖所示:

1.冷熱分離:表資料量大讀寫緩慢如何優化?                                            冷熱分離:表資料量大讀寫緩慢如何優化?什麼是冷熱分離?什麼情況下使用冷熱分離?

學到這,冷熱分離的 4 個問題,我們已經解決了 3 個,解決最後 1 個問題——如何使用冷熱資料,我們就算大功告成了。

(四)如何使用冷熱資料?

在功能設計的查詢界面上,一般都會有一個選項供我們選擇需要查詢冷資料還是熱資料,如果界面上沒有提供,我們可以直接在業務代碼裡區分。(說明:在判斷是冷資料還是熱資料時,我們必須確定使用者不允許有同時讀冷熱資料的需求。)

1.冷熱分離:表資料量大讀寫緩慢如何優化?                                            冷熱分離:表資料量大讀寫緩慢如何優化?什麼是冷熱分離?什麼情況下使用冷熱分離?

整體方案

課程講到這,我們再把 01 講整個知識點串起來下,串完後就形成了一個整體解決方案,如下圖所示:

1.冷熱分離:表資料量大讀寫緩慢如何優化?                                            冷熱分離:表資料量大讀寫緩慢如何優化?什麼是冷熱分離?什麼情況下使用冷熱分離?

一步一步學到這,思路是不是感覺瞬間清晰了很多?

曆史資料如何遷移?

一般而言,隻要跟持久化層有關的架構方案,我們都需要考慮曆史資料的遷移問題,即如何讓舊架構的曆史資料适用于新的架構?

因為前面的分離邏輯在考慮失敗重試的場景時,剛好覆寫了這個問題,是以這個問題的解決方案也很簡單,我們隻需要給所有的曆史資料加上辨別:ColdFlag=WaittingForMove 後,程式就會自動遷移了。

冷熱分離解決方案的不足

不得不說,冷熱分離解決方案确實能解決寫操作慢和熱資料慢的問題,但仍然存在諸多不足。

不足一: 使用者查詢冷資料速度依舊很慢,如果查詢冷資料的使用者比例很低,比如隻有 1%,那麼這個方案就沒問題。

不足二: 業務無法再修改冷資料,因為冷資料多到一定程度時,系統承受不住。

繼續閱讀