億級數據存儲實現 + 分庫分表優化方案

前言

前幾個月公司要做一個發電站的項目,需要實時採集各個機器的發電用電情況,進行統計展示。初步估計一天產生的數據爲 4000W 左右。於是和組長一起討論設計了數據庫方案。下面說下我們的解決思路,如果有不足,需要改進的地方,歡迎提出(因爲這也是我第一次做分庫分表 ╮( ̄▽ ̄")╭)。

一 基本情況

數據是從 2000 多個節點分別採集輸入到系統中,大約每 5s 採集一次,一天數據量在 4000W 到 5000W 之間。
數據從採集系統採集後,放入 Kafka 隊列,我們的消費服務不停的從隊列中取出數據,推送給系統主體。
最少數據庫中要存放
保留上個月的歷史數據,再往前的歷史數據基本很少會用到,直接備份起來就好。也就是說最多數據庫會存在 24 億的數據。


二 建索引

數據主要用來統計,因此將關鍵字段加上索引。使用的每條查詢 SQL,都最好 explain 一下,確保使用了索引。

三 分表

MySQL 的推薦單表不超過 500W 條數據(含 char 等字符),或者不超過 1000W 數據(純 int 等數字),當然網上有很多操作讓單表達到幾千萬級、億級。但是我們既然都提前知道了數據量大,而且鬼知道發電廠會不會加新設備進來(我賭 5 毛,肯定會加),那麼最好把單表數據量不要超過 1000W,因爲單表數據量超過 1000W 以後會出現明顯的瓶頸。

四 分庫

5s 採集一次數據,平均 Kafka 裏每秒會增加 400 多個數據,我們可以每秒批量寫一次數據,併發量並不大。(分庫主要爲了解決併發量大的問題)

但是考慮到數據量大了以後,對機器的 CPU 和內存會造成很大的壓力,並且邏輯上也是兩套發電機組,因此分了兩個庫。

再加上一個存放其他數據的數據庫,一共 3 個寫庫。
現在是頻繁寫入的場景,因此又加了 3 個讀庫,做主從分離,並且 3 個讀庫也能當做備份庫使用。

一共使用了 6 個庫。

五 分表分庫

經過大概的估算,分成 2 個庫,每個庫分成 160 張表,一共 320 張表。一張表數據最多時會達到 750W(考慮到我們的表字段數不超過 10 個,並且對查詢操作的實時性要求不是特別高,沒有嚴格準守不超過 500W 條)

六 分表分庫實踐

分表分庫中間件,我們選擇了張亮大大的 Sharding-JDBC,原因很簡單,其他中間件的很多功能我們都不需要,而且 Sharding-JDBC 的使用非常簡單。

根據 ShardingSphere 使用手冊,配置好分表分庫策略,分庫策略根據機組 ID(之前說過總共有兩套機組),分表策略是自定義的策略,首先根據設備的編號進行劃分範圍,如每個設備劃分 10 張表,那麼根據設備編號 hash 出來值爲 1,則設備放在表 1 到表 10 之間,然後根據主鍵 ID 再一次 hash,落到具體的某張表上。這樣比較方便後續的查詢操作。

分庫分表有很多算法,最最實用的就是 hash 法,就是 hash 後取模。

並且用 Sharding-JDBC 自帶的分佈式 ID 生成算法,workId 用了主機名 hostname。分佈式 ID 生成算法有興趣的 key 參看我的文章——數據庫主鍵生成策略選擇

Sharding-JDBC 還有強大的柔性事務功能,改天我研究下分佈式事務,又可以水一篇文章了。

七 統計功能實現

因爲主要是做統計系統,那麼必然需要統計各種報表,主要有小時報表,日報表,週報表,月報表,年報表。目前我們的做法是將各種統計表的信息放在單獨的庫中,並且新建了幾十個表用來保存統計出的結果。
後臺起一個線程,每次入庫成功後,都會把數據傳入該線程。該線程會取出對應的本小時報表,然後重新計算一遍。
並且在數據庫裏寫了幾個定時任務,如每天凌晨統計前一天小時報表生成日報表,每月生成月報表......

八 緩存實現實時查詢

要提高查詢速度緩存是必不可少的,除了將用戶查詢過的數據緩存,我們還將近幾個小時的所有數據都緩存在 RedisSorted Set 裏。因爲用戶需要精確查看的數據,往往也就是最近幾個小時產生的。通過 Sorted Set 以時間戳爲 Score,可以迅速的查出來一段範圍內的數據進行展示。

九 其他的優化方案

主要有三種方案

  1. 映射表
  2. 基因法
  3. 數據冗餘法

推薦基因法和數據冗餘法,下面來看看使用場景。

9.1 多維度查詢

一張用戶表,我們一般會按照 user_id 來分庫分表,但是用戶查詢的時候經常是按照 user_name 或者 email 來查詢的。這時候如果直接進行全庫掃描肯定是很慢的。

  1. 映射表
    建立一張映射表,映射了 user_name 到 user_id 的關係,查詢時直接查一次映射表即可。因爲映射表很小,還可以提前加載到緩存中。
    缺點:多了一次查詢。

  2. 基因法
    網上看到大神分享的一個方法,一般分庫分表都是取模算法,如:user_id%8,可以看出實際的取模結果完全取決於 user_id 的後 3 個 bit(因爲 8 的二進制爲 1000),那麼我們可以改用 user_name 來生成 user_id 的後 3 個 bit,即 user_id=隨機數 +f(user_name)。這樣我們在分庫分表查找時,只需要重新計算一遍搜索 user_name 的哈希值即可定位到具體庫或表
    缺點:基因只能關聯一個字段。

9.2 一對多查詢

常見的一個場景:根據用戶信息,查詢該用戶的消費記錄。用戶 ID 與消費記錄 ID 是明顯的一對多關係。
這時候怎麼通過用戶信息查到對應被分庫分表的消費記錄呢——很明顯!還是採用映射表或者基因法。

9.3 多對多查詢

QQ 上任何人之間都可能存在好友關係,那麼這就是明顯的多對多關係了。正常情況下我們存一條好友關係只需要一行,如:我和張強是好友,則插入一條數據,上面包含了我的 user_id 和張強的 user_id。但是考慮到分庫分表的情況下,只能使用一個信息進行分庫分表,即以我的 user_id 爲值進行分庫分表,則在張強查詢自己的好友時,需要去全庫查找。

  1. 數據冗餘法
    既然只能以一個字段爲值去分庫分表,那麼我們就做一次數據冗餘。在以我的 user_id 爲值分庫分表插入後,再以張強的 user_id 爲值分庫分表插入一次。
    在查詢的時候不管是使用我的 user_id 還是張強的 user_id 都能順利的查到對應庫和表。
    數據冗餘法爲了保證系統的可用性和實時性。最好使用異步的冗餘插入,並且用分佈式事務的最終一致性保證異步插入成功。


9.4 多對多查詢 + 主鍵查詢

後端開發中利用主鍵查詢數據是必不可少的,比如典型的訂單表,有 order_id,buyer_id,seller_id。怎麼辦呢,很簡單結合基因法與數據冗餘法即可。
以 buyer_id 爲基因生成 order_id 插入一次,再以 seller_id 爲基因生成 order_id 插入一次。那麼不管從哪個維度查詢,都能直接定位到庫和表。

總結

對於大數據量的查詢存儲,往往就是以下幾個步驟:

  1. 優化表和索引,提高 SQL 速度
  2. 使用緩存,推薦 Redis
  3. 讀寫分離提高查詢速度
  4. 分表提高單表查寫性能
  5. 分庫提高併發量,並且降低 CPU,內存的壓力。業務耦合度不高時使用。
  6. 分庫分表時要考慮到多種可能的查詢。

聽別人提起這種業務好像可以直接上 ELK 一套的,但是無奈我對這個不太瞭解,沒辦法說服組裏其他人。有時間再去研究下。畢竟這個場景其實就是另類的日誌。



作者:EricTao2
鏈接:https://ld246.com/article/1562809046570
來源:鏈滴
協議:CC BY-SA 4.0 https://creativecommons.org/licenses/by-sa/4.0/




 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章