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