小編的世界 優質文選 資料
字體大小:
2021年6月11日 -
:
計算機java編程
優質科技領域創作者
1 文章概述
在業務發展初期單表完全可以滿足業務需求,在阿裏巴巴開發手冊也建議:單表行數超過500萬行或者單表容量超過2GB才推薦進行分庫分表,如果預計三年後數據量根本達不到這個級別,請不要在創建表時就分庫分表。
但是隨著業務的發展和深入,單表數據量不斷增加,逐漸成為業務系統的瓶頸。這是為什麼呢?
從宏觀層面分析任何物體都必然有其物理極限。1965年英特爾創始人摩爾預測:集成電路上可容納的元器件的數目,約每隔24個月增加一倍,性能提升一倍,即計算機性能每兩年翻一番。
但是摩爾定律會有終點嗎?有些科學家認為摩爾定律是有終點的:半導體芯片單位面積可集成的元件數量是有極限的,因為半導體芯片制程工藝的物理極限為2到3納米。當然也有科學家不支持這種說法,但是我們可以從中看出物理極限是很難突破的,當單表數據量達到一定規模時必然也達到極限。
從細節層面分析我們將數據保存在數據庫,實際上是保存在磁盤中,一次磁盤IO操作需要經曆尋道、旋轉延時、數據傳輸三個步驟,那麼一次磁盤IO耗時公式如下:
單次IO時間 = 尋道時間 + 旋轉延遲 + 傳送時間
總體來說上述操作都較為耗時,速度和內存相比有著數量級的差距,當數據量過大磁盤這一瓶頸更加明顯。那麼應該怎麼辦?處理單表數據量過大有以下六字口訣:刪、換、分、拆、異、熱。
刪是指刪除歷史數據並進行歸檔。換是指不要只使用數據庫資源,有些數據可以存儲至其它替代資源。分是指讀寫分離,增加多個讀實例應對讀多寫少的互聯網場景。拆是指分庫分表,將數據分散至不同的庫表中減輕壓力。異指數據異構,將一份數據根據不同業務需求保存多份。熱是指熱點數據,這是一個非常值得注意的問題。
2 刪
我們分析這樣一個場景:消費者會經常查詢一年之前的訂單記錄嗎?答案是一般不會,或者說這種查詢需求量很小。根據上述分析那麼一年前的數據我們就沒有必要放在單表這張業務主表,可以將一年前的數據遷移到歷史歸檔表。
在查詢歷史數據表時,可以限制查詢條件如必須選擇日期範圍,日期範圍不能超過X個月等等從而減輕查詢壓力。
處理歷史存量數據比較簡單,因為存量數據一般是靜態的,此時狀態已經不再改變了。數據處理一般分為以下兩個步驟:
(1) 遷移一年前數據至歷史歸檔表
(2) 根據主鍵分批刪除主表數據
不能一次性刪除所有數據,因為數據量太大可能會引發超時,而是應該根據ID分批刪除,例如每次刪除500條數據。
第一步查詢一年前主鍵最大值和最小值,這是我們需要刪除的數據範圍:
第二步刪除數據時不能一次性全部刪掉,因為很可能會超時,我們可以通過代碼動態更新endId進行批量刪除:
3 換
換是指換一個存儲介質,當然並不是說完全替換,而是用其它存儲介質對數據庫做一個補充。例如海量流水記錄,這類數據量級是巨量的,根本不適合存儲在MySQL數據庫中,那麼這些數據可以存在哪裏呢?
現在互聯網公司一般都具備與之規模相對應的大數據服務或者平台,那麼作為業務開發者要善於應用公司大數據能力,減輕業務數據庫壓力。
3.1 消息隊列
這些海量數據可以存儲至Kafka,因為其本質上就是分布式的流數據存儲系統。使用Kafka有如下優點:
第一個優點是Kafka社區活躍功能強大,已經成為了一種事實上的工業標准。大數據很多組件都提供了Kafka接入組件,經過生產驗證並且對接成本較小,可以為下遊業務提供更多選擇。
第二個優點是Kafka具有消息隊列本身的優點例如解耦、異步和削峰。
假設這些海量數據都已經存儲在Kafka,現在我們希望這些數據可以產生業務價值,這涉及到兩種數據分析任務:離線任務和實時任務。
離線任務對實時性要求不高,例如每天、每周、每月的數據報表統計分析,我們可以使用基於MapReduce數據倉庫工具Hive進行報表統計。
實時任務對實時性要求高,例如根據用戶相關行為推薦用戶感興趣的商品,提高用戶購買體驗和效率,可以使用Flink進行流處理分析。例如運營後台查詢分析,可以將數據同步至ES進行檢索。
還有一種分類方式是將任務分為批處理任務和流處理任務,我們可以這麼理解:離線任務一般使用批處理技術,實時任務一般使用流處理技術。
3.2 API
上一個章節我們使用了Kafka進行海量數據存儲,由於其強大兼容性和集成度,可以作為數據中介將數據進行中轉和解耦。
當然我們並不是必須使用Kafka進行中轉,例如我們直接可以使用相關Java API將數據存入Hive、ES、HBASE等。
但是我並不推薦這種做法,因為將保存流水這樣操作耦合進業務代碼並不合適,違反了高內聚低耦合的原則,盡量不要使用。
3.3 緩存
從廣義上理解換這個字,我們還可以引入Redis遠程緩存,把Redis放在MySQL前面,攔下一些高頻讀請求,但是要注意緩存穿透和擊穿問題。
緩存穿透和擊穿從最終結果上來說都是流量繞過緩存打到了數據庫,可能會導致數據庫掛掉或者系統雪崩,但是仔細區分還是有一些不同,我們分析一張業務讀取緩存一般流程圖。
我們用文字簡要描述這張圖:
(1) 業務查詢數據時首先查詢緩存,如果緩存存在數據則返回,流程結束
(2) 如果緩存不存在數據則查詢數據庫,如果數據庫不存在數據則返回空數據,流程結束
(3) 如果數據庫存在數據則將數據寫入緩存並返回數據給業務,流程結束
假設業務方要查詢A數據,緩存穿透是指數據庫根本不存在A數據,所以根本沒有數據可以寫入緩存,導致緩存層失去意義,大量請求會頻繁訪問數據庫。
緩存擊穿是指請求在查詢數據庫前,首先查緩存看看是否存在,這是沒有問題的。但是並發量太大,導致第一個請求還沒有來得及將數據寫入緩存,後續大量請求已經開始訪問緩存,這是數據在緩存中還是不存在的,所以瞬時大量請求會打到數據庫。
我們可以使用分布式鎖加上自旋解決這個問題,本文給出一段示例代碼,具體原理和代碼實現請參看我之前的文章:流程圖+源碼深入分析:緩存穿透和擊穿問題原理以及解決方案(https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fwww.toutiao.com%2Fi6902543979116511748%2F%3Fgroup_id%3D6902543979116511748)
4 分
我們首先看一個概念:讀寫比。互聯網場景中一般是讀多寫少,例如瀏覽20次訂單列表信息才會進行1次確認收貨,此時讀寫比例就是20:1。面對讀多寫少這種情況我們可以做什麼呢?
我們可以部署多台MySQL讀庫專門用來接收讀請求,主庫接收寫請求並通過binlog實時同步的方式將數據同步至讀庫。MySQL官方即提供這種能力,進行簡單配置即可。
那麼客戶端怎麼知道訪問讀庫還是寫庫呢?推薦使用ShardingSphere組件,通過配置將讀寫請求分別路由至讀庫或者寫庫。
5 拆
如果刪除了歷史數據並采用了其它存儲介質,也用了讀寫分離,但是單表壓力還是太大怎麼辦?這時我們只能拆分數據表,即把單庫單表數據遷移到多庫多張表中。
假設有一個電商數據庫存放訂單、商品、支付三張業務表。隨著業務量越來越大,這三張業務數據表也越來越大,我們就以這個例子進行分析。
5.1 垂直拆分
垂直拆分就是按照業務拆分,我們將電商數據庫拆分成三個庫,訂單庫、商品庫。支付庫,訂單表在訂單庫,商品表在商品庫,支付表在支付庫。這樣每個庫只需要存儲本業務數據,物理隔離不會互相影響。
5.2 水平拆分
按照垂直拆分方案,現在我們已經有三個庫了,平穩運行了一段時間。但是隨著業務增長,每個單庫單表的數據量也越來越大,逐漸到達瓶頸。
這時我們就要對數據表進行水平拆分,所謂水平拆分就是根據某種規則將單庫單表數據分散到多庫多表,從而減小單庫單表的壓力。
水平拆分策略有很多方案,最重要的一點是選好ShardingKey,也就是按照哪一列進行拆分,怎麼分取決於我們訪問數據的方式。
5.2.1 範圍分片
現在我們要對訂單庫進行水平拆分,我們選擇的ShardingKey是訂單創建時間,拆分策略如下:
(1) 拆分為四個數據庫,分別存儲每個季度的數據(2) 每個庫三張表,分別存儲每個月的數據
上述方法優點是對範圍查詢比較友好,例如我們需要統計第一季度的相關數據,查詢條件直接輸入時間範圍即可。
但是這個方案問題是容易產生熱點數據。例如雙11當天下單量特別大,就會導致11月這張表數據量特別大從而造成訪問壓力。
5.2.2 查表分片
查表法是根據一張路由表決定ShardingKey路由到哪一張表,每次路由時首先到路由表裏查到分片信息,再到這個分片去取數據。
我們分析一個查表法實際案例。Redis官方在3.0版本之後提供了集群方案Redis Cluster,其中引入了哈希槽(slot)這個概念。
一個集群固定有16384個槽,在集群初始化時這些槽會平均分配到Redis集群節點上。每個key請求最終落到哪個槽計算公式是固定的:
SLOT = CRC16(key) mod 16384
那麼問題來了:一個key請求過來怎麼知道去哪台Redis節點獲取數據?這就要用到查表法思想。
(1) 客戶端連接任意一台Redis節點,假設隨機訪問到為節點A
(2) 節點A根據key計算出slot值
(3) 每個節點都維護著slot和節點映射關系表
(4) 如果節點A查表發現該slot在本節點則直接返回數據給客戶端
(5) 如果節點A查表發現該slot不在本節點則返回給客戶端一個重定向命令,告訴客戶端應該去哪個節點上請求這個key的數據
(6) 客戶端再向正確節點發起連接請求
查表法優點是可以靈活制定路由策略,如果我們發現有的分片已經成為熱點則修改路由策略。缺點是多一次查詢路由表操作增加耗時,而且路由表如果是單點也可能會有單點問題。
5.2.3 哈希分片
現在比較流行的分片方法是哈希分片,相較於範圍分片,哈希分片可以較為均勻將數據分散在數據庫中。
我們現在將訂單庫拆分為4個庫編號為<0,3>,每個庫4張表編號為<0,3>,如下圖如所示:
現在使用orderId作為ShardingKey,那麼orderId=100的訂單會保存在哪張表?我們來計算一下:由於是分庫分表,首先確定路由到哪一個庫,取模計算得到序號為0表示路由到db<0>
db_index=100%4=0
庫確定了接著在db<0>進行取模表路由
table_index=100%4=0
最終這條數據應該路由至下表
db<0>_table<0>
最終計算結果如下圖所示:
在實際開發中最終路由到哪張表,並不需要我們自己算,因為有許多開源框架就可以完成路由功能,例如ShardingSphere、TDDL等等。
6 異
現在數據已經使用哈希分片方法完成了水平拆分,我們選擇的ShardingKey是orderId。這時客戶端需要查詢orderId=111的數據,查詢語句很簡單如下:
SELECT * FROMorderWHERE orderId = 111
這個語句沒有問題,因為查詢條件包含orderId,可以路由到具體的數據表。
現在如果業務想要查詢用戶維度的數據,希望查詢userId=222的數據,現在問題來了:以下這個語句可以查出數據嗎?
SELECT * FROMorderWHERE userId = 222
答案是可以,但是需要掃描所有庫的所有表,因為無法根據userId路由到具體某一張表,這樣時間成本會非常高,這種場景怎麼辦呢?
這就要用到數據異構的思想。數據異構核心是用空間換時間,簡單一句話就是一份數據按照不同業務需求保存多份,這樣做是因為存儲硬件成本不是很高,而互聯網場景對響應速度要求很高。
對於上述需要使用userId進行查詢的場景,我們完全可以新建庫和表,數量和結構與訂單庫表完全一致,唯一不同點是ShardingKey改用userId,這樣就可以使用userId查詢了。
現在又引出一個新問題,業務不可能每次都將數據寫入多個數據源,這樣會帶來性能問題和數據一致行為。怎麼解決老庫和新庫數據同步問題?我們可以使用阿裏開源的canal組件解決這個問題,看一張官網介紹canal架構圖:
canal組件的主要用途是基於MySQL數據庫增量日志解析,提供增量數據訂閱和消費服務,工作原理如下:
(1) canal偽裝成為MySQL slave模擬交互協議向master發送dump協議
(2) master收到canal發送的dump請求,開始推送binlog給canal
(3) canal解析binlog並發送到存儲目的地,例如MySQL、Kafka、Elasticsearch
canal組件下遊可以對接很多其它數據源,這樣給業務提供了更多選擇。我們可以像上述實例中新建用戶維度訂單表,也可以將數據存在ES中提供運營檢索能力等等。
7 熱
我們來分析這樣一個場景:社交業務有一張用戶關系表,主要記錄誰關注了誰。其中有一個明星粉絲特別多,如果以userId作為分片,那麼其所在分片數據量就會特別大。
不僅分片數據量特別大,而且可以預見這個分片訪問頻率也會非常高。此時數據量大並且訪問頻繁,很有可能造成系統壓力。
7.1 熱點概念
我們將訪問行為稱為熱點行為,將訪問對應的數據稱為熱點數據。我們通過實例來分析。
在電商雙11活動中百分之八十的訪問量會集中在百分之二十的商品上。用戶刷新、添加購物車、下單被稱為熱點行為,相應商品數據就被稱為熱點數據。
在微博場景中大V發布一條消息會獲得大量訪問。用戶對這條消息的瀏覽、點贊、轉發、評論被稱為熱點行為,這條消息數據被稱為熱點數據。
在秒殺場景中參與秒殺的商品會獲得極大的瞬時訪問量。用戶對這個商品的頻繁刷新、點擊、下單被稱為熱點行為,參與秒殺的商品數據被稱為熱點數據。
我們必須將熱點數據進行一些處理,使得熱點訪問更加流暢,更是為了保護系統免於崩潰。我們從發現熱點數據、處理熱點數據來展開分析。
7.2 發現熱點數據
我們把發現熱點數據分為兩種方式:靜態發現和動態發現。
靜態發現:在開始秒殺活動之前,參與商家一定知道哪些商品參與秒殺,那麼他們可以提前將這些商品報備告知平台。
在微博場景中,具有影響力的大V一般都很知名,網站運營同學可以提前知道。技術同學還可以通過分析歷史數據找出TOP N數據。對於這些可以提前預判的數據,完全可以通過後台系統上報,這樣系統可以提前做出預處理。
動態發現:有些商品可能並沒有上報為熱點商品,但是在實際銷售中卻非常搶手。在微博場景中,有些話題熱度突然升溫。這些數據成為事實上的熱點數據。對於這些無法提前預判的數據,需要動態進行判斷。
我們需要一個熱點發現系統去主動發現熱點數據。大體思路是首先異步收集訪問日志,再統計單位時間內訪問頻次,當超過一定閾值時可以判斷為熱點數據。
7.3 處理熱點問題
(1) 熱點行為
熱點行為可以采取高頻檢測方式,如果發現頻率過高則進行限制。或者采用內存隊列實現的生產者與消費者這種異步化方式,消費者根據能力處理請求。
(2) 熱點數據
處理熱點數據核心主要是根據業務形態來進行處理,我一般采用以下方案配合執行:
(1) 選擇合適ShardingKey進行分庫分表
(2) 異構數據至其它適合檢索的數據源例如ES
(3) 在MySQL之前設置緩存層(4) 盡量不在MySQL進行耗時操作(例如聚合)
8 文章總結
本文我們詳細介紹處理單表數據量過大的六字口訣:刪、換、分、拆、異、熱。這並不是意味這每次遇到單表數據量過大情況六種方案全部都要使用,例如拆分數據表成本確實比較高,會帶來分布式事務、數據難以聚合等問題,如果不分表可以解決那麼就不要分表,核心還是根據自身業務情況選擇合適的方案。