時序資料從分表到分庫
這裡的時序資料泛指一切隨時間推移而不斷增長的資料,比如通話記錄、銀行交易記錄等。
對於資料庫來講,時序資料並沒有什麼特殊性,可以和普通資料一樣放在資料表中。不過,因為不斷增長,積累時間較長後,這種資料的量常常都會很大。一個物理表的資料量太大時,就會影響查詢和計算的效能。
現代資料庫一般都提供有表分割槽(PARTITION)的機制,就是把一個大表縱向(按行)分成若干區段,分割槽規則由資料庫管理員來設定,對應用程式員來講是透明的,可以和不分割槽的表一樣訪問,資料庫會自動根據查詢條件決定讀取哪些分割槽的資料,這樣的介面體驗非常好。
不過,在實戰中,分割槽表的效果在某些場景下並不好,而且使用時也有些約束條件,並不總好用且能用的。結果,在實際業務中,我們常常會看到對於這種大資料採用手工物理分表的方案。
所謂物理分表,就是人為將一個大表分成若干較小的物理資料表。因為時序資料的結構中一定會有一個欄位來表示事件發生的時刻,而事件發生的數量一般來講也會按時間段相對平均分佈(大多數情況會緩慢增長,但討論時可以忽略),所以最常用的方案就是按時間段來做分表,比如一個月資料對應一個分表,這種方式在金融、電信行業比較普遍。
物理分表並不是資料庫自動支援的方案,不能對應用程式做到透明,需要應用程式自己處理。在查詢資料時一般都會有時間段引數,應用程式可以根據這個引數計算出該查詢涉及哪些分表,然後將這些分表UNION起來拼到SQL語句的FROM後面。查詢不涉及的時間段對應的分表不會被拼進來,這樣就可以有效減少資料遍歷的範圍,從而提高效能。
這個方案在單個數據庫時沒啥毛病,但是不是能推廣到多個數據庫的情況呢?
資料量再大下去,一個數據庫也無法承受了,而某些場景下又不允許我們上一套分散式資料庫系統,畢竟分散式資料庫是個沉重的工程,不僅造價高,而且維護管理都要複雜不少。這時候,我們可以擺多個數據庫分別儲存資料,類似物理分表的方案,也按時間段把資料分拆到各個資料庫中,比如一年資料放入一個數據庫中(一般來講多個庫會部署到多臺機器上),這樣就能分攤查詢壓力了。
這首先會有一個查詢範圍的問題,如果查詢的時間跨度超過了一個物理分庫時,這時候就不能象分表時那樣用UNION拼起來了,資料庫無法執行跨庫的SQL語句。不過,這個問題還不算嚴重,只是查詢明細資料時,要把各個分庫的返回資料拼接起來,這並不算困難。甚至,要求前端查詢範圍必須落在一個分庫內也不為過(比如必須先選擇查詢年份),因為一個分庫的資料量並不算少,這樣使用者體驗略有損失,但也可以容忍。
這種方案還會有壓力不平衡的問題。
對於時序資料,近期資料的查詢頻繁度遠遠高於遠期資料,大多數查詢都集中在最近一段時間中,存放近期資料的分庫上任務就很重,併發較多時仍然會有效能瓶頸,而存放遠期資料的分庫卻幾乎沒事幹,並不能有效分攤查詢壓力。
還有別的辦法嗎?
可以採用蛇形分佈。比如將多年資料分拆到10個分庫中,可以按日期拆分,所有年份中1月1日的資料放到1號分庫中,1月2日的放到2號分庫,…,1月10號的放到10號分庫,1月11號的再從1號分庫輪迴,…;其它情況的具體分法也可以根據時序資料的時刻欄位的分佈情況來決定。
這樣分下來,每個分庫儲存的資料量差不多也就是1/n,相對比較平均,還可以規避前面說的資料緩慢增長導致的不平衡;而且,無論近期資料還是遠期資料的查詢都會被分攤到各個分庫中,看起來能夠充分利用硬體資源了。
還有點注意事項!
蛇形分佈時,每個分庫中都有所有年份的資料,幾乎每個查詢都會涉及到所有分庫的資料,不能只挑出某些分庫來執行運算,這和前面說的分表方案的優化原理並不一樣了。我們需要在分庫中繼續做分表,查詢確實會涉及所有分庫,但只涉及分庫中的某些分表,這樣仍然可以有效的減少查詢範圍,同時利用分庫並行的優勢。
第二個問題:每個分庫都可能返回資料,應用程式需要把這些資料再做一次彙總,而不能象單庫分表那樣用UNION推給資料庫去完成。對於常見的明細查詢,那隻要簡單拼接再排序就可以了,開發起來並不難;但如果涉及到分組彙總就會麻煩很多,應用程式設計師並不擅長編寫這種運算,這時候最好藉助集算器這類外部計算引擎來協助實現跨庫彙總運算。
當然,成本和條件允許時直接上分散式資料庫就更簡單,分散式資料庫採用HASH方案基本上可以被理解成是蛇形分佈的。
原文釋出時間為:2018-10-30
本文作者:蔣步星