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