如何使用分庫分表支持海量數據的寫入
當前位置:點晴教程→知識管理交流
→『 技術文檔交流 』
本講將要介紹如何存儲這些海量數據,同時保證相對應的寫入和查詢的性能,以及業務流程不發生太大變化。 不管是打車的訂單、電商里的支付訂單,還是外賣或團購的支付訂單,都是后臺服務中最重要的一環,關乎公司的營收。因此,本講及本模塊都將以 訂單業務 作為案例進行分析。 是否真的要分庫?分庫當然能夠解決存儲的問題,假設原先單庫只能最多存儲 2 千萬的數據量。采用分庫之后,存儲架構變成下圖 1 所示的分庫架構,每個分庫都可以存儲 2 千萬數據量,容量的上限一下提升了。 容量提升了,但也帶來了很多其他問題。比如:
所以在解決容量問題上,可以根據業務場景選擇,不要一上來就要考慮分庫,分表也是一種選擇。 分表是指所有的數據均存在同一個數據庫實例中,只是將原先的一張大表按一定規則,劃分成多張行數較少的表。它與分庫的區別是,分表后的子表仍在原有庫中,而分庫則是子表移動到新的數據庫實例里并在物理上單獨部署。分表的拆分架構如下圖 2 所示: 以本模塊的訂單案例來說,假設訂單只是 單量多而每一單的數據量較小,這就適合采用分表。單條數據量小但行數多,會導致寫入(因為要構建索引)和查詢非常慢,但整體對于容量的占用是可控的。采用分表后,大表變成小表,寫入時構建索引的性能消耗會變小,其次小表的查詢性能也更好。如果采用了分庫,雖然解決了寫入和查詢的問題,但每張表所占有的磁盤空間很少,也會產生資源浪費。兩種方案的對比如下圖 3 所示: 在實際場景里,因為要詳細記錄用戶的提單信息,單個訂單記錄的數據量均較多,所以不存在行數多但單條數據量小的情況。但在其他寫入服務里,經常會出現上述場景,你可以優先采用分表的方案。因為 分表除了能解決容量問題,還能在一定程度上解決分庫所帶來的三個問題。
接下來將介紹如何應對行數多且單行數據量較大的場景。通過我們前面的分析,我想你已經知道答案了——采用分庫的方案。 如何實現分庫?在決定對數據庫進行分庫后,首先要解決的問題便是 如何選擇分庫維度。不同的分庫維度 決定了部分查詢是否能直接使用數據庫,以及是否存在數據傾斜的問題。 分庫維度選擇下面以訂單為案例,介紹兩種常見不同維度的分庫方式:按 直接滿足最重要的業務場景劃分和最細粒度隨機分。 首先我們來看按 直接滿足最重要的業務場景劃分。在業務上,所有的訂單數據都是隸屬于某一個用戶的。在選擇分庫維度時,可以按訂單歸屬的用戶 這個字段進行分庫。按此維度分庫后,同一個用戶的訂單都在某一個分庫里。分庫后的場景如下圖 4 所示: 訂單模塊除了提供提交訂單接口外,還會提供給售賣商家對自己店鋪的訂單進行查詢及修改等功能。這些維度的查詢和修改需求,在采用了按購買用戶進行分庫之后,均無法直接滿足了。 這里請你思考一個問題, 訂單模塊最重要的功能是什么? 答案是保證客戶(即買家)的各項訂單功能能夠正常使用,比如下單、下單后立刻(無延遲)查看已購的訂單信息、待支付、待發貨、待配送的訂單列表等。相對來說,訂單里的商品售賣方(即賣家)所使用的功能并不是優先級最高的。因為當我們要對賣家和買家的功能做取舍時,賣家是愿意降低優先級的,畢竟賣家是買賣的受益方。 按購買用戶劃分后,用戶的使用場景都可以直接通過分庫支持,而不需要通過異構數據(存在數據延遲)等手段解決,對用戶來說體驗較好。其次,在同一個分庫中,便于修改同一用戶的多條數據,因此也不存在分布式事務問題。 我們可以通過上述訂單案例抽象出一個分庫準則,即 在確定分庫字段時應該以直接滿足最重要的業務場景為準。很多其他的業務都參考了這一準則,比如:
上述劃分方法雖然直接滿足了最重要的場景,但可能會出現數據傾斜的問題,比如出現一個超級客戶(如企業客戶),購買的訂單量非常大,導致某一個分庫數據量巨多,就會重現分庫前的場景。這屬于最極端的情況之一。 對于傾斜的問題,可以采用 最細粒度的拆分,即按數據的唯一標示進行拆分,對于訂單來說唯一標示即為訂單號。采用訂單號進行分庫之后,用戶的訂單會按 Hash 隨機均勻地分散到某一個分庫里。這樣就解決了某一個分庫數據不均勻的問題。 對于上個小節里的案例,也可以用此手段進行處理。比如:
采用最細粒度分庫后,雖然解決了數據均衡的問題,但又帶來了其他問題。
上述兩種分庫的方式,在解決問題的同時又帶來一些新的問題。在架構中,沒有一種方案可以解決所有問題的,更多的是根據場景去選擇更適合自己的方案。 全局唯一標示不管采用何種維度的分庫方式,使用原有單庫的數據庫自增主鍵生產數據標示的方案已經不可以使用了。對于全局的數據唯一標示,有兩種常見的生成方式。 1. 使用算法隨機生成。 比如使用機器 IP、時間戳、隨機數等進行組合,生成一個唯一編號。業界成熟的有 Twitter 推出的雪花算法。需要注意的是,為了保證唯一性,雪花算法增加了很多隨機因子,導致計算出來的唯一標示特別長,達到 19 位。 在 JavaScript 里,數據精度和 Java 等語言不完全一致,太長的雪花 ID 在前端存在溢出的問題。因為雪花算法生成的 ID 為 Long 類型,可以采用類似 Base64 等算法,對原始 ID 進行壓縮轉換為 String 類型,降低長度并避免和 JavaScript 精度不統一導致的問題。 2. 基于數據庫主鍵構建一個 ID 生成服務。 雖然不能在插入的時候使用數據庫唯一主鍵,但可以在插入前通過一個服務獲取全局唯一的 ID。ID 生產服務可以基于一張單表實現,每一次外部請求時,均生產一個新的 ID。通過此方式,可以獲得長度較短且為數值類型的全局唯一編號。 但如果每次獲取 ID 時,ID 生成服務都需要從數據庫實時獲取,性能會比較差。為了解決性能問題,可以在生成 ID 的數據庫前置一個具備持久化功能的內存緩存,預生成一批 ID。具體架構如下圖 5 所示: 分庫中間件選擇現在開源提供分庫支持的中間件較多,如 MyCat 等, 整體上各類分庫中間件可以分為兩大類:一種是代理式、另外一種是內嵌式。 代理式分庫中間件 對于業務應用無任何侵入,業務應用和未分庫時一樣使用數據庫,分庫的選擇及分庫的維度對業務層完全隱藏,接入和使用成本極低。代理式的架構如下圖 6 所示: 代理式雖有使用成本低的好處,但也存在其他一些問題。
內嵌式分庫中間件 是將分庫中間件內置在業務應用中,它只負責分庫的選擇,并不會解析用戶的 SQL。在使用時,業務應用需將分庫字段傳遞給內嵌中間件去計算具體對應的分庫。它相比代理式性能更好。內嵌式的架構如下圖 7 所示: 除了性能優勢外,內嵌式同樣存在問題。
其他問題接下來,再來看幾個常見問題的應對策略。 1. 是否一定需要進行分表或者分庫呢? 不一定。雖然很多互聯網公司的體量很大,用戶非常多,但你千萬不要被這些現象迷惑了。實際上,90% 以上的系統能夠發展到上百萬、上千萬數據量已經很不錯了。對于千萬的數據量,開源的 MySQL 都可以很好地應對,更別說一些商業數據庫了。 另外,當數據增長到一定量級后,可以在業務層面做一些處理。比如根據業務特點,對無效數據、軟刪除數據,以及業務上不會再查詢的數據進行統一歸檔,這也是一個成本低、效果明顯的方式了。 2. 使用業務字段分庫后,如何處理數據傾斜? 如果數據量不是特別大,可以在分庫基礎上,再進行分表。針對數據量較大的場景,可以使用二次分庫的方式。對于訂單量較多的用戶,可以在用戶賬號基礎上再增加一個字段,做進一步的分庫,但此用戶的查詢就會有損了。 此外,還有另外兩個問題,由于需要用到暫未講解的知識,所以我將放在后面的章節結合相關知識詳細講解,今天僅做提及。 3. 如何滿足富查詢? 富查詢是一個無法回避的問題,即采用分庫分表之后,如何滿足跨越分庫的查詢?對于此問題,我將在“ 第 11 講”進行詳細講解。 4. 如何解決跨多庫的修改導致的分布式事務? 跨多庫的修改及多個微服務間的寫操作導致的分布式事務問題,我將在“ 第 19 講”里集中講解。 總結不斷進行分庫分表一定能解決容量問題,但“殺敵一千,自損八百”的事情少做為宜。使用分庫分表會將代碼和架構的復雜度變高,帶來資源成本上升等問題。另外,在使用系統時,用戶(不管是客戶還是管理員)的查詢體驗也存在一定的降級。 在使用分庫分表前,你需要確定這是否是最優選擇,是否能通過其他更簡單的手段處理無效數據清理?架構是通過最小代價解決問題,而不是技術工具的比拼。 最后,我再給你留一道討論題,你知道的分庫分表的問題還有哪些或者上述問題你還有哪些解決方案? 該文章在 2024/1/24 23:01:01 編輯過 |
關鍵字查詢
相關文章
正在查詢... |