在上一篇(
資料庫表資料量大讀寫緩慢如何最佳化(1)「冷熱分離」
)聊到過,冷熱分離解決方案的價效比高,但它並不是一個最優的方案,仍然存在諸多不足,比如:查詢冷資料慢、業務無法再修改冷資料、冷資料多到一定程度系統依舊扛不住,我們如果想把這些問題一一解決掉,可以用另外一種解決方案——
查詢分離
。(
注意:查詢分離與讀寫分離還是有區別的。
)
業務場景二
某 SaaS 客服系統,系統裡有一個工單查詢功能,工單表中存放了幾千萬條資料,且查詢工單表資料時需要關聯十幾個子表,每個子表的資料也是超億條。
面對如此龐大的資料量,跟前面的冷熱分離一樣,每次客戶查詢資料時幾十秒才能返回結果,即便我們使用了索引、SQL 等資料庫最佳化技巧,效果依然不明顯。
加上工單表中有些資料是幾年前的,但是這些資料涉及訴訟問題,需要繼續保持更新,因此無法將這些舊資料封存到別的地方,也就沒法透過前面的冷熱分離方案來解決。
最終採用了查詢分離的解決方案,才得以將這個問題順利解決:將更新的資料放在一個數據庫裡,而查詢的資料放在另外一個系統裡。因為資料的更新都是單表更新,不需要關聯也沒有外來鍵,所以更新速度立馬得到提升,資料的查詢則透過一個專門處理大資料量的查詢引擎來解決,也快速地滿足了實際的查詢需求。
透過這種解決方案處理後,每次查詢資料時,500ms 內就可得到返回結果,客戶再也不抱怨了。
透過上面這個例子,大家對查詢分離的業務場景已經有了一定認知,但如果想掌握整個業務場景,繼續往下看吧。
什麼是查詢分離?
關於查詢分離的概念,從簡單的字面意思上也好理解,即每次寫資料時儲存一份資料到另外的儲存系統裡,使用者查詢資料時直接從另外的儲存系統裡獲取資料。示意圖如下:
何種場景下使用查詢分離?
當在實際業務中遇到以下情形,則可以考慮使用查詢分離解決方案。
資料量大;
所有寫資料的請求效率尚可;
查詢資料的請求效率很低;
所有的資料任何時候都可能被修改;
業務希望最佳化查詢資料的效率;
大家對查詢分離這個概念特別熟悉,但是對於查詢分離的使用場景一無所知,這可不行,只有瞭解了查詢分離的真正使用場景,才能在遇到實際問題時採取最正確的解決方案。
查詢分離實現思路
在實際工作中,如果業務要求必須使用查詢分離的解決方案,我們就務必掌握查詢分離的實現思路。也只有這樣,我們真正遇到問題時才能有條不紊地開展工作。
查詢分離解決方案的實現思路如下:
如何觸發查詢分離?
如何實現查詢分離?
查詢資料如何儲存?
查詢資料如何使用?
針對以上問題,我們一點一點來討論。
(一)如何觸發查詢分離?
這個問題說明的是我們應該在什麼時候儲存一份資料到查詢資料中,即什麼時候觸發查詢分離這個動作。
一般來說,查詢分離的觸發邏輯分為3種。
(1)修改業務程式碼:在寫入常規資料後,同步建立查詢資料。
(2)修改業務程式碼:在寫入常規資料後,非同步建立查詢資料。
(3)監控資料庫日誌:如有資料變更,更新查詢資料。
透過觀察以上3種觸發邏輯示意圖,發現了什麼嗎?3種觸發邏輯的優缺點對比表如下:
為方便理解表中的內容,我們來一起聊一下其中的幾個概念。
什麼叫業務靈活邏輯可控?
舉個例子:一般來說,寫業務程式碼的人能從業務邏輯中快速判斷出何種情況下更新查詢資料,而監控資料庫日誌的人並不能將全部的資料庫變更分支窮舉,再把所有的可能性關聯到對應的更新查詢資料邏輯中,最終導致任何資料的變更都需要重新建立查詢資料。
什麼叫減緩寫操作速度?
建立查詢資料的一個動作能減緩多少寫操作速度?答案:很多。舉個例子:當你只是簡單更新了訂單的一個標識,本來查詢資料時間只需要 2ms,而在查詢資料時可能會涉及重建(比如使用 ES 查詢資料時會涉及索引、分片、主從備份,其中每個動作又細分為很多子動作,這些內容後面文章會聊到),這時建立查詢資料的過程可能就需要 1s 了,從 2ms 變成 1s,你說減緩幅度大不大?
查詢資料更新前,使用者可能查詢到過時資料。
這裡我們結合第 2 種觸發邏輯來講,比如某個操作正處於訂單更新狀態,狀態更新時會透過非同步更新查詢資料,更新完後訂單才從“待稽核”狀態變為“已稽核”狀態。假設查詢資料的更新時間需要 1 秒,這 1 秒中如果使用者正在查詢訂單狀態,這時主資料雖然已變為“已稽核”狀態,但最終查詢的結果還是顯示“待稽核”狀態。
根據前面的對比表,總結每種觸發邏輯的適用場景如下:
這裡,結合實戰案例說明下:在一個真實業務場景中,雖然我們對業務的程式碼比較熟悉,但是業務要求每次修改工單請求時響應速度快,我們最終就選擇了修改業務程式碼非同步建立查詢資料這種觸發邏輯。
(二)如何實現查詢分離?
以上共談到 3 種觸發邏輯,第 1 種是同步建立查詢資料的過程比較簡單,這裡就不展開說明,第 3 種監控資料庫日誌我會在 13 講具體講解,所以這部分內容我們主要圍繞第 2 種討論。
關於第 2 種觸發方案:修改業務程式碼非同步建立查詢資料,最基本的實現方式是單獨起一個執行緒建立查詢資料,不過這種做法會出現如下情況:
寫操作較多且執行緒太多,最終撐爆JVM。
建查詢資料的執行緒出錯了,如何自動重試。
多執行緒併發時,很多併發場景需要解決。
面對以上三種情況,我們該如何處理?此時使用MQ管理這些這些執行緒即可解決。
MQ的具體操作思路為每次主資料寫操作請求處理時,都會發一個通知給MQ,MQ收到通知後喚醒一個執行緒更新查詢資料,示意圖如下:
瞭解了MQ的具體操作思路後,我們還應該考慮以下5大問題。
問題一:MQ如何選型?
如果公司已使用 MQ,那選型問題也就不存在了,畢竟技術部門不會同時維護 2 套 MQ 中介軟體,而如果公司還沒使用 MQ,這就需要考慮選型問題了。
這裡我分享兩點選型原則,希望對你有幫助。
(1)召集技術中心所有能做技術決策的人共同投票選型。
(2)不管我們選擇哪個 MQ ,最終都能實現想要的功能,只不過是易用不易用、多寫少寫業務程式碼的問題,因此我們從易用性和程式碼工作量角度考量即可。
問題二:MQ宕機了怎麼辦?
如果 MQ 宕機了,我們只需要保證主流程正常進行,且 MQ 恢復後資料正常處理即可,具體方案分為三大步驟。
每次寫操作時,在主資料中加個標識:NeedUpdateQueryData=true,這樣發到 MQ 的訊息就很簡單,只是一個簡單的訊號告知更新資料,並不包含更新的資料 id。
MQ 的消費者獲取訊號後,先批次查詢待更新的主資料,然後批次更新查詢資料,更新完後查詢資料的主資料標識 NeedUpdateQueryData 就更新成 false 了。
當然還存在多個消費者同時搬運動作的情況,這就涉及併發性的問題,因此問題與上一篇聊的冷熱分離中的併發性處理邏輯類似,這裡就不細聊了(有興趣的同學可以去看看)。
問題三:更新查詢資料的執行緒失敗了怎麼辦?
如果更新的執行緒失敗了,NeedUpdateQueryData 的標識就不會更新,後面的消費者會再次將有 NeedUpdateQueryData 標識的資料拿出來處理。但如果一直失敗,我們可以在主資料中多新增一個嘗試搬運次數,比如每次嘗試搬運時 +1,成功後就清零,以此監控那些嘗試搬運次數過多的資料。
問題四:訊息的冪等消費
在程式設計中,一個冪等操作的特點是多次執行某個操作均與執行一次操作的影響相同。
舉個例子,比如主資料的訂單 A 更新後,我們在查詢資料中插入了 A,可是此時系統出問題了,系統誤以為查詢資料沒更新,又把訂單 A 插入更新了一次。
所謂冪等,就是不管更新查詢資料的邏輯執行幾次,結果都是我們想要的結果。因此,考慮消費端併發性的問題時,我們需要保證更新查詢資料冪等。
問題五:訊息的時序性問題
比如某個訂單 A 更新了 1 次資料變成 A1,執行緒甲將 A1 的資料搬到查詢資料中。不一會兒,後臺訂單 A 又更新了 1 次資料變成 A2,執行緒乙也啟動工作,將 A2 的資料搬到查詢資料中。
所謂的時序性就是如果執行緒甲啟動比乙早,但搬運資料動作比執行緒乙還晚完成,就有可能出現查詢資料最終變成過期的 A1。如下圖(動作前面的序號代表實際動作的先後順序):
此時解決方案為主資料每次更新時,都更新上次更新時間 last_update_time,然後每個執行緒更新查詢資料後,檢查當前訂單 A 的 last_update_time 是否跟執行緒剛開始獲得的時間一樣,且 NeedUpdateQueryData 是否等於 false,如果都滿足的話,我們就將 NeedUpdateQueryData 改為 true,然後再做一次搬運。
看到這,你心中可能有個疑問:MQ 在這裡的作用只是一個觸發訊號的工具,如果不用 MQ 好像也沒啥問題啊,這你就大錯特錯了,MQ 的作用還不少呢,不信你往下看。
服務的解耦
:這樣主業務邏輯就不會依賴更新查詢資料這個服務了。
控制更新查詢資料服務的併發量
:如果我們直接呼叫更新查詢資料服務,因寫操作速度快,更新查詢資料速度慢,寫操作一旦併發量高,會給更新查詢資料服務造成超負荷壓力。如果透過訊息觸發更新查詢資料服務,我們就可以透過控制訊息消費者的執行緒數來控制負載。
(三)查詢資料如何儲存?
我們應該使用什麼技術儲存查詢資料呢?目前,市面上主要使用 Elasticsearch 實現大資料量的搜尋查詢,當然還可能會使用到MongoDB、HBase 這些技術,這就需要我們對各種技術的特性瞭如指掌,再進行技術選型。
關於技術選型這個問題,我覺得很多時候我們不能單單隻考慮業務功能的需求,還需要考慮組織結構。團隊最熟悉哪款中介軟體,花費的成本最小,優先考慮的就應該是這種。
(四)查詢資料如何使用?
因 ES 自帶 API,所以使用查詢資料時,我們在查詢業務程式碼中直接呼叫 ES 的 API 就行。
不過,這個辦法會出現一個問題:
資料查詢更新完前,查詢資料不一致怎麼辦?
這裡分享 2 種解決思路。
在查詢資料更新到最新前,不允許使用者查詢。(我們沒用過這種設計,但我確實見過市面上有這樣的設計。
給使用者提示:您目前查詢到的資料可能是 1 秒前的資料,如果發現數據不準確,可以嘗試重新整理一下,這種提示使用者一般比較容易接受。
整體方案
以上,我們已經把四個問題都討論完了,我們再一起看看查詢分離的整體方案,如下圖所示:
總結一下,本篇關於查詢分離的架構主要分為四個部分:如何觸發查詢分離?如何實現查詢分離?查詢資料如何儲存?查詢資料如何使用?
歷史資料遷移
新的架構方案上線後,舊的資料如何適用新的架構方案?這是實際業務中需要我們考慮的問題。
在這個方案裡,我們只需要把所有的歷史資料加上這個標識:NeedUpdateQueryData=true,程式就會自動處理了。
查詢分離解決方案的不足
查詢分離這個解決方案雖然能解決一些問題,但我們也要清醒地認識到它的不足。
不足一: 使用 Elasticsearch 儲存查詢資料時,注意事項是什麼(此方案並未詳細展開)?
不足二: 主資料量越來越大後,寫操作還是慢,到時還是會出問題。
不足三: 主資料和查詢資料不一致時,假設業務邏輯需要查詢資料保持一致性呢?
接下來的文章將會來聊使用elastic search做查詢資料的儲存系統時需要注意哪些問題,這個問題不管是面試還是實際工作中,我們都會碰到。一個技術使用起來並不難,難的是使用這個技術時你會碰到什麼問題,你又是如何解決的?