成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

別再問“分庫分表”了,再問就崩潰了!

運(yùn)維 數(shù)據(jù)庫運(yùn)維
在談?wù)摂?shù)據(jù)庫架構(gòu)和數(shù)據(jù)庫優(yōu)化的時(shí)候,我們經(jīng)常會(huì)聽到分庫分表,分庫分表其實(shí)涉及到很多難題,今天我們來匯總一下數(shù)據(jù)庫分庫分表解決方案。

 在談?wù)摂?shù)據(jù)庫架構(gòu)和數(shù)據(jù)庫優(yōu)化的時(shí)候,我們經(jīng)常會(huì)聽到分庫分表,分庫分表其實(shí)涉及到很多難題,今天我們來匯總一下數(shù)據(jù)庫分庫分表解決方案。 

[[285748]]

圖片來自 Pexels

數(shù)據(jù)切分

關(guān)系型數(shù)據(jù)庫本身比較容易成為系統(tǒng)瓶頸,單機(jī)存儲(chǔ)容量、連接數(shù)、處理能力都有限。

當(dāng)單表的數(shù)據(jù)量達(dá)到 1000W 或 100G 以后,由于查詢維度較多,即使添加從庫、優(yōu)化索引,做很多操作時(shí)性能仍下降嚴(yán)重。

此時(shí)就要考慮對(duì)其進(jìn)行切分了,切分的目的就在于減少數(shù)據(jù)庫的負(fù)擔(dān),縮短查詢時(shí)間。

數(shù)據(jù)庫分布式核心內(nèi)容無非就是數(shù)據(jù)切分(Sharding),以及切分后對(duì)數(shù)據(jù)的定位、整合。

數(shù)據(jù)切分就是將數(shù)據(jù)分散存儲(chǔ)到多個(gè)數(shù)據(jù)庫中,使得單一數(shù)據(jù)庫中的數(shù)據(jù)量變小,通過擴(kuò)充主機(jī)的數(shù)量緩解單一數(shù)據(jù)庫的性能問題,從而達(dá)到提升數(shù)據(jù)庫操作性能的目的。

數(shù)據(jù)切分根據(jù)其切分類型,可以分為兩種方式:

  • 垂直(縱向)切分
  • 水平(橫向)切分

垂直(縱向)切分

垂直切分常見有垂直分庫和垂直分表兩種。

垂直分庫就是根據(jù)業(yè)務(wù)耦合性,將關(guān)聯(lián)度低的不同表存儲(chǔ)在不同的數(shù)據(jù)庫。做法與大系統(tǒng)拆分為多個(gè)小系統(tǒng)類似,按業(yè)務(wù)分類進(jìn)行獨(dú)立劃分。

與"微服務(wù)治理"的做法相似,每個(gè)微服務(wù)使用單獨(dú)的一個(gè)數(shù)據(jù)庫,如下圖:

 

垂直分表是基于數(shù)據(jù)庫中的"列"進(jìn)行,某個(gè)表字段較多,可以新建一張擴(kuò)展表,將不經(jīng)常用或字段長度較大的字段拆分出去到擴(kuò)展表中。

在字段很多的情況下(例如一個(gè)大表有 100 多個(gè)字段),通過"大表拆小表",更便于開發(fā)與維護(hù),也能避免跨頁問題,MySQL 底層是通過數(shù)據(jù)頁存儲(chǔ)的,一條記錄占用空間過大會(huì)導(dǎo)致跨頁,造成額外的性能開銷。

另外數(shù)據(jù)庫以行為單位將數(shù)據(jù)加載到內(nèi)存中,這樣表中字段長度較短且訪問頻率較高,內(nèi)存能加載更多的數(shù)據(jù),命中率更高,減少了磁盤 IO,從而提升了數(shù)據(jù)庫性能。

 

垂直切分的優(yōu)點(diǎn)如下:

  • 解決業(yè)務(wù)系統(tǒng)層面的耦合,業(yè)務(wù)清晰
  • 與微服務(wù)的治理類似,也能對(duì)不同業(yè)務(wù)的數(shù)據(jù)進(jìn)行分級(jí)管理、維護(hù)、監(jiān)控、擴(kuò)展等
  • 高并發(fā)場景下,垂直切分一定程度的提升 IO、數(shù)據(jù)庫連接數(shù)、單機(jī)硬件資源的瓶頸

垂直切分的缺點(diǎn)如下:

  • 部分表無法 join,只能通過接口聚合方式解決,提升了開發(fā)的復(fù)雜度
  • 分布式事務(wù)處理復(fù)雜
  • 依然存在單表數(shù)據(jù)量過大的問題(需要水平切分)

水平(橫向)切分

當(dāng)一個(gè)應(yīng)用難以再細(xì)粒度的垂直切分,或切分后數(shù)據(jù)量行數(shù)巨大,存在單庫讀寫、存儲(chǔ)性能瓶頸,這時(shí)候就需要進(jìn)行水平切分了。

水平切分分為庫內(nèi)分表和分庫分表,是根據(jù)表內(nèi)數(shù)據(jù)內(nèi)在的邏輯關(guān)系,將同一個(gè)表按不同的條件分散到多個(gè)數(shù)據(jù)庫或多個(gè)表中,每個(gè)表中只包含一部分?jǐn)?shù)據(jù),從而使得單個(gè)表的數(shù)據(jù)量變小,達(dá)到分布式的效果。

如圖所示:

 

庫內(nèi)分表只解決了單一表數(shù)據(jù)量過大的問題,但沒有將表分布到不同機(jī)器的庫上。

因此對(duì)于減輕 MySQL 數(shù)據(jù)庫的壓力來說,幫助不是很大,大家還是競爭同一個(gè)物理機(jī)的 CPU、內(nèi)存、網(wǎng)絡(luò) IO,最好通過分庫分表來解決。

水平切分的優(yōu)點(diǎn)如下:

  • 不存在單庫數(shù)據(jù)量過大、高并發(fā)的性能瓶頸,提升系統(tǒng)穩(wěn)定性和負(fù)載能力
  • 應(yīng)用端改造較小,不需要拆分業(yè)務(wù)模塊

水平切分的缺點(diǎn):

  • 跨分片的事務(wù)一致性難以保證
  • 跨庫的 join 關(guān)聯(lián)查詢性能較差
  • 數(shù)據(jù)多次擴(kuò)展難度和維護(hù)量極大

水平切分后同一張表會(huì)出現(xiàn)在多個(gè)數(shù)據(jù)庫/表中,每個(gè)庫/表的內(nèi)容不同。幾種典型的數(shù)據(jù)分片規(guī)則為:

①根據(jù)數(shù)值范圍:按照時(shí)間區(qū)間或 ID 區(qū)間來切分。

例如:按日期將不同月甚至是日的數(shù)據(jù)分散到不同的庫中;將 userId 為 1~9999 的記錄分到第一個(gè)庫,10000~20000 的分到第二個(gè)庫,以此類推。

某種意義上,某些系統(tǒng)中使用的"冷熱數(shù)據(jù)分離",將一些使用較少的歷史數(shù)據(jù)遷移到其他庫中,業(yè)務(wù)功能上只提供熱點(diǎn)數(shù)據(jù)的查詢,也是類似的實(shí)踐。

這樣的優(yōu)點(diǎn)在于:

  • 單表大小可控
  • 天然便于水平擴(kuò)展,后期如果想對(duì)整個(gè)分片集群擴(kuò)容時(shí),只需要添加節(jié)點(diǎn)即可,無需對(duì)其他分片的數(shù)據(jù)進(jìn)行遷移
  • 使用分片字段進(jìn)行范圍查找時(shí),連續(xù)分片可快速定位分片進(jìn)行快速查詢,有效避免跨分片查詢的問題。

缺點(diǎn)在于:熱點(diǎn)數(shù)據(jù)成為性能瓶頸。連續(xù)分片可能存在數(shù)據(jù)熱點(diǎn),例如按時(shí)間字段分片,有些分片存儲(chǔ)最近時(shí)間段內(nèi)的數(shù)據(jù),可能會(huì)被頻繁的讀寫,而有些分片存儲(chǔ)的歷史數(shù)據(jù),則很少被查詢。

 

②根據(jù)數(shù)值取模:一般采用 hash 取模 mod 的切分方式。

例如:將 Customer 表根據(jù) cusno 字段切分到 4 個(gè)庫中,余數(shù)為 0 的放到第一個(gè)庫,余數(shù)為 1 的放到第二個(gè)庫,以此類推。

這樣同一個(gè)用戶的數(shù)據(jù)會(huì)分散到同一個(gè)庫中,如果查詢條件帶有 cusno 字段,則可明確定位到相應(yīng)庫去查詢。

優(yōu)點(diǎn):數(shù)據(jù)分片相對(duì)比較均勻,不容易出現(xiàn)熱點(diǎn)和并發(fā)訪問的瓶頸。

缺點(diǎn)如下:

  • 后期分片集群擴(kuò)容時(shí),需要遷移舊的數(shù)據(jù)(使用一致性 hash 算法能較好的避免這個(gè)問題)
  • 容易面臨跨分片查詢的復(fù)雜問題。比如上例中,如果頻繁用到的查詢條件中不帶 cusno 時(shí),將會(huì)導(dǎo)致無法定位數(shù)據(jù)庫,從而需要同時(shí)向 4 個(gè)庫發(fā)起查詢,再在內(nèi)存中合并數(shù)據(jù),取最小集返回給應(yīng)用,分庫反而成為拖累。

 

分庫分表帶來的問題

分庫分表能有效的緩解單機(jī)和單庫帶來的性能瓶頸和壓力,突破網(wǎng)絡(luò) IO、硬件資源、連接數(shù)的瓶頸,同時(shí)也帶來了一些問題。下面將描述這些技術(shù)挑戰(zhàn)以及對(duì)應(yīng)的解決思路。

事務(wù)一致性問題

①分布式事務(wù)

當(dāng)更新內(nèi)容同時(shí)分布在不同庫中,不可避免會(huì)帶來跨庫事務(wù)問題。跨分片事務(wù)也是分布式事務(wù),沒有簡單的方案,一般可使用"XA 協(xié)議"和"兩階段提交"處理。

分布式事務(wù)能最大限度保證了數(shù)據(jù)庫操作的原子性。但在提交事務(wù)時(shí)需要協(xié)調(diào)多個(gè)節(jié)點(diǎn),推后了提交事務(wù)的時(shí)間點(diǎn),延長了事務(wù)的執(zhí)行時(shí)間。導(dǎo)致事務(wù)在訪問共享資源時(shí)發(fā)生沖突或死鎖的概率增高。

隨著數(shù)據(jù)庫節(jié)點(diǎn)的增多,這種趨勢(shì)會(huì)越來越嚴(yán)重,從而成為系統(tǒng)在數(shù)據(jù)庫層面上水平擴(kuò)展的枷鎖。

②最終一致性

對(duì)于那些性能要求很高,但對(duì)一致性要求不高的系統(tǒng),往往不苛求系統(tǒng)的實(shí)時(shí)一致性,只要在允許的時(shí)間段內(nèi)達(dá)到最終一致性即可,可采用事務(wù)補(bǔ)償?shù)姆绞健?/p>

與事務(wù)在執(zhí)行中發(fā)生錯(cuò)誤后立即回滾的方式不同,事務(wù)補(bǔ)償是一種事后檢查補(bǔ)救的措施。

一些常見的實(shí)現(xiàn)方法有:對(duì)數(shù)據(jù)進(jìn)行對(duì)賬檢查,基于日志進(jìn)行對(duì)比,定期同標(biāo)準(zhǔn)數(shù)據(jù)來源進(jìn)行同步等等。事務(wù)補(bǔ)償還要結(jié)合業(yè)務(wù)系統(tǒng)來考慮。

跨節(jié)點(diǎn)關(guān)聯(lián)查詢 join 問題

切分之前,系統(tǒng)中很多列表和詳情頁所需的數(shù)據(jù)可以通過 sql join 來完成。

而切分之后,數(shù)據(jù)可能分布在不同的節(jié)點(diǎn)上,此時(shí) join 帶來的問題就比較麻煩了,考慮到性能,盡量避免使用 join 查詢。

解決這個(gè)問題的一些方法:

①全局表:全局表,也可看做是"數(shù)據(jù)字典表",就是系統(tǒng)中所有模塊都可能依賴的一些表,為了避免跨庫 join 查詢,可以將這類表在每個(gè)數(shù)據(jù)庫中都保存一份。這些數(shù)據(jù)通常很少會(huì)進(jìn)行修改,所以也不擔(dān)心一致性的問題。

②字段冗余:一種典型的反范式設(shè)計(jì),利用空間換時(shí)間,為了性能而避免 join 查詢。

例如:訂單表保存 userId 時(shí)候,也將 userName 冗余保存一份,這樣查詢訂單詳情時(shí)就不需要再去查詢"買家 user 表"了。

但這種方法適用場景也有限,比較適用于依賴字段比較少的情況。而冗余字段的數(shù)據(jù)一致性也較難保證,就像上面訂單表的例子,買家修改了 userName 后,是否需要在歷史訂單中同步更新呢?這也要結(jié)合實(shí)際業(yè)務(wù)場景進(jìn)行考慮。

③數(shù)據(jù)組裝:在系統(tǒng)層面,分兩次查詢,第一次查詢的結(jié)果集中找出關(guān)聯(lián)數(shù)據(jù) id,然后根據(jù) id 發(fā)起第二次請(qǐng)求得到關(guān)聯(lián)數(shù)據(jù)。最后將獲得到的數(shù)據(jù)進(jìn)行字段拼裝。

④ER 分片:關(guān)系型數(shù)據(jù)庫中,如果可以先確定表之間的關(guān)聯(lián)關(guān)系,并將那些存在關(guān)聯(lián)關(guān)系的表記錄存放在同一個(gè)分片上,那么就能較好的避免跨分片 join 問題。在 1:1 或 1:n 的情況下,通常按照主表的 ID 主鍵切分。

如下圖所示:

這樣一來,Data Node1 上面的 order 訂單表與 orderdetail 訂單詳情表就可以通過 orderId 進(jìn)行局部的關(guān)聯(lián)查詢了,Data Node2 上也一樣。

跨節(jié)點(diǎn)分頁、排序、函數(shù)問題

跨節(jié)點(diǎn)多庫進(jìn)行查詢時(shí),會(huì)出現(xiàn) limit 分頁、order by 排序等問題。分頁需要按照指定字段進(jìn)行排序,當(dāng)排序字段就是分片字段時(shí),通過分片規(guī)則就比較容易定位到指定的分片;當(dāng)排序字段非分片字段時(shí),就變得比較復(fù)雜了。

需要先在不同的分片節(jié)點(diǎn)中將數(shù)據(jù)進(jìn)行排序并返回,然后將不同分片返回的結(jié)果集進(jìn)行匯總和再次排序,最終返回給用戶。

如圖所示:

上圖中只是取第一頁的數(shù)據(jù),對(duì)性能影響還不是很大。但是如果取得頁數(shù)很大,情況則變得復(fù)雜很多。

因?yàn)楦鞣制?jié)點(diǎn)中的數(shù)據(jù)可能是隨機(jī)的,為了排序的準(zhǔn)確性,需要將所有節(jié)點(diǎn)的前 N 頁數(shù)據(jù)都排序好做合并,最后再進(jìn)行整體的排序,這樣的操作是很耗費(fèi) CPU 和內(nèi)存資源的,所以頁數(shù)越大,系統(tǒng)的性能也會(huì)越差。

在使用 Max、Min、Sum、Count 之類的函數(shù)進(jìn)行計(jì)算的時(shí)候,也需要先在每個(gè)分片上執(zhí)行相應(yīng)的函數(shù),然后將各個(gè)分片的結(jié)果集進(jìn)行匯總和再次計(jì)算,最終將結(jié)果返回。

如圖所示:

全局主鍵避重問題

在分庫分表環(huán)境中,由于表中數(shù)據(jù)同時(shí)存在不同數(shù)據(jù)庫中,主鍵值平時(shí)使用的自增長將無用武之地,某個(gè)分區(qū)數(shù)據(jù)庫自生成的 ID 無法保證全局唯一。

因此需要單獨(dú)設(shè)計(jì)全局主鍵,以避免跨庫主鍵重復(fù)問題。有一些常見的主鍵生成策略:

①UUID

UUID 標(biāo)準(zhǔn)形式包含 32 個(gè) 16 進(jìn)制數(shù)字,分為 5 段,形式為 8-4-4-4-12 的 36 個(gè)字符,例如:550e8400-e29b-41d4-a716-446655440000。

UUID 是主鍵是最簡單的方案,本地生成,性能高,沒有網(wǎng)絡(luò)耗時(shí)。但缺點(diǎn)也很明顯,由于 UUID 非常長,會(huì)占用大量的存儲(chǔ)空間。

另外,作為主鍵建立索引和基于索引進(jìn)行查詢時(shí)都會(huì)存在性能問題,在 InnoDB 下,UUID 的無序性會(huì)引起數(shù)據(jù)位置頻繁變動(dòng),導(dǎo)致分頁。

②結(jié)合數(shù)據(jù)庫維護(hù)主鍵 ID 表

在數(shù)據(jù)庫中建立 sequence 表:

  1. CREATE TABLE `sequence` (   
  2.   `id` bigint(20) unsigned NOT NULL auto_increment,   
  3.   `stub` char(1) NOT NULL default '',   
  4.   PRIMARY KEY  (`id`),   
  5.   UNIQUE KEY `stub` (`stub`)   
  6. ) ENGINE=MyISAM; 

stub 字段設(shè)置為唯一索引,同一 stub 值在 sequence 表中只有一條記錄,可以同時(shí)為多張表生成全局 ID。

sequence 表的內(nèi)容,如下所示:

  1. +-------------------+------+   
  2. | id                | stub |   
  3. +-------------------+------+   
  4. | 72157623227190423 |    a |   
  5. +-------------------+------+   

使用 MyISAM 存儲(chǔ)引擎而不是 InnoDB,以獲取更高的性能。MyISAM 使用的是表級(jí)別的鎖,對(duì)表的讀寫是串行的,所以不用擔(dān)心在并發(fā)時(shí)兩次讀取同一個(gè) ID 值。

當(dāng)需要全局唯一的 64 位 ID 時(shí),執(zhí)行:

  1. REPLACE INTO sequence (stub) VALUES ('a');   
  2. SELECT LAST_INSERT_ID();   

這兩條語句是 Connection 級(jí)別的,select last_insert_id() 必須與 replace into 在同一數(shù)據(jù)庫連接下才能得到剛剛插入的新 ID。

使用 replace into 代替 insert into 好處是避免了表行數(shù)過大,不需要另外定期清理。

此方案較為簡單,但缺點(diǎn)也明顯:存在單點(diǎn)問題,強(qiáng)依賴 DB,當(dāng) DB 異常時(shí),整個(gè)系統(tǒng)都不可用。

配置主從可以增加可用性,但當(dāng)主庫掛了,主從切換時(shí),數(shù)據(jù)一致性在特殊情況下難以保證。另外性能瓶頸限制在單臺(tái) MySQL 的讀寫性能。

flickr 團(tuán)隊(duì)使用的一種主鍵生成策略,與上面的 sequence 表方案類似,但更好的解決了單點(diǎn)和性能瓶頸的問題。

這一方案的整體思想是:建立 2 個(gè)以上的全局 ID 生成的服務(wù)器,每個(gè)服務(wù)器上只部署一個(gè)數(shù)據(jù)庫,每個(gè)庫有一張 sequence 表用于記錄當(dāng)前全局 ID。

表中 ID 增長的步長是庫的數(shù)量,起始值依次錯(cuò)開,這樣能將 ID 的生成散列到各個(gè)數(shù)據(jù)庫上。

如下圖所示:

由兩個(gè)數(shù)據(jù)庫服務(wù)器生成 ID,設(shè)置不同的 auto_increment 值。第一臺(tái) sequence 的起始值為 1,每次步長增長 2,另一臺(tái)的 sequence 起始值為 2,每次步長增長也是 2。 

結(jié)果第一臺(tái)生成的 ID 都是奇數(shù)(1, 3, 5, 7 ...),第二臺(tái)生成的 ID 都是偶數(shù)(2, 4, 6, 8 ...)。

這種方案將生成 ID 的壓力均勻分布在兩臺(tái)機(jī)器上。同時(shí)提供了系統(tǒng)容錯(cuò),第一臺(tái)出現(xiàn)了錯(cuò)誤,可以自動(dòng)切換到第二臺(tái)機(jī)器上獲取 ID。

但有以下幾個(gè)缺點(diǎn):系統(tǒng)添加機(jī)器,水平擴(kuò)展時(shí)較復(fù)雜;每次獲取 ID 都要讀寫一次 DB,DB 的壓力還是很大,只能靠堆機(jī)器來提升性能。

可以基于 flickr 的方案繼續(xù)優(yōu)化,使用批量的方式降低數(shù)據(jù)庫的寫壓力,每次獲取一段區(qū)間的 ID 號(hào)段,用完之后再去數(shù)據(jù)庫獲取,可以大大減輕數(shù)據(jù)庫的壓力。

如下圖所示:

 

還是使用兩臺(tái) DB 保證可用性,數(shù)據(jù)庫中只存儲(chǔ)當(dāng)前的最大 ID。ID 生成服務(wù)每次批量拉取 6 個(gè) ID,先將 max_id 修改為 5,當(dāng)應(yīng)用訪問 ID 生成服務(wù)時(shí),就不需要訪問數(shù)據(jù)庫,從號(hào)段緩存中依次派發(fā) 0~5 的 ID。

當(dāng)這些 ID 發(fā)完后,再將 max_id 修改為 11,下次就能派發(fā) 6~11 的 ID。于是,數(shù)據(jù)庫的壓力降低為原來的 1/6。

③Snowflake 分布式自增 ID 算法

Twitter 的 Snowflake 算法解決了分布式系統(tǒng)生成全局 ID 的需求,生成 64 位的 Long 型數(shù)字。

組成部分:

  • 第一位未使用。
  • 接下來 41 位是毫秒級(jí)時(shí)間,41 位的長度可以表示 69 年的時(shí)間。
  • 5 位 datacenterId,5 位 workerId。10 位的長度最多支持部署 1024 個(gè)節(jié)點(diǎn)。
  • 最后 12 位是毫秒內(nèi)的計(jì)數(shù),12 位的計(jì)數(shù)順序號(hào)支持每個(gè)節(jié)點(diǎn)每毫秒產(chǎn)生 4096 個(gè) ID 序列。

這樣的好處是:毫秒數(shù)在高位,生成的 ID 整體上按時(shí)間趨勢(shì)遞增;不依賴第三方系統(tǒng),穩(wěn)定性和效率較高。 

理論上 QPS 約為 409.6w/s(1000*2^12),并且整個(gè)分布式系統(tǒng)內(nèi)不會(huì)產(chǎn)生 ID 碰撞;可根據(jù)自身業(yè)務(wù)靈活分配 bit 位。

不足就在于:強(qiáng)依賴機(jī)器時(shí)鐘,如果時(shí)鐘回?fù)埽瑒t可能導(dǎo)致生成 ID 重復(fù)。

綜上結(jié)合數(shù)據(jù)庫和 Snowflake 的唯一 ID 方案,可以參考業(yè)界較為成熟的解法:Leaf——美團(tuán)點(diǎn)評(píng)分布式 ID 生成系統(tǒng),并考慮到了高可用、容災(zāi)、分布式下時(shí)鐘等問題:

  1. https://tech.meituan.com/2017/04/21/mt-leaf.html 

數(shù)據(jù)遷移、擴(kuò)容問題

當(dāng)業(yè)務(wù)高速發(fā)展,面臨性能和存儲(chǔ)的瓶頸時(shí),才會(huì)考慮分片設(shè)計(jì),此時(shí)就不可避免的需要考慮歷史數(shù)據(jù)遷移的問題。

一般做法是先讀出歷史數(shù)據(jù),然后按指定的分片規(guī)則再將數(shù)據(jù)寫入到各個(gè)分片節(jié)點(diǎn)中。

此外還需要根據(jù)當(dāng)前的數(shù)據(jù)量和 QPS,以及業(yè)務(wù)發(fā)展的速度,進(jìn)行容量規(guī)劃,推算出大概需要多少分片(一般建議單個(gè)分片上的單表數(shù)據(jù)量不超過 1000W)。

如果采用數(shù)值范圍分片,只需要添加節(jié)點(diǎn)就可以進(jìn)行擴(kuò)容了,不需要對(duì)分片數(shù)據(jù)遷移。如果采用的是數(shù)值取模分片,則考慮后期的擴(kuò)容問題就相對(duì)比較麻煩。

什么時(shí)候考慮切分

下面講述一下什么時(shí)候需要考慮做數(shù)據(jù)切分。

①能不切分盡量不要切分

并不是所有表都需要進(jìn)行切分,主要還是看數(shù)據(jù)的增長速度。切分后會(huì)在某種程度上提升業(yè)務(wù)的復(fù)雜度,數(shù)據(jù)庫除了承載數(shù)據(jù)的存儲(chǔ)和查詢外,協(xié)助業(yè)務(wù)更好的實(shí)現(xiàn)需求也是其重要工作之一。

不到萬不得已不用輕易使用分庫分表這個(gè)大招,避免"過度設(shè)計(jì)"和"過早優(yōu)化"。

分庫分表之前,不要為分而分,先盡力去做力所能及的事情,例如:升級(jí)硬件、升級(jí)網(wǎng)絡(luò)、讀寫分離、索引優(yōu)化等等。當(dāng)數(shù)據(jù)量達(dá)到單表的瓶頸時(shí)候,再考慮分庫分表。

②數(shù)據(jù)量過大,正常運(yùn)維影響業(yè)務(wù)訪問

這里說的運(yùn)維指:

  • 對(duì)數(shù)據(jù)庫備份,如果單表太大,備份時(shí)需要大量的磁盤 IO 和網(wǎng)絡(luò) IO。例如 1T 的數(shù)據(jù),網(wǎng)絡(luò)傳輸占 50MB 時(shí)候,需要 20000 秒才能傳輸完畢,整個(gè)過程的風(fēng)險(xiǎn)都是比較高的。
  • 對(duì)一個(gè)很大的表進(jìn)行 DDL 修改時(shí),MySQL 會(huì)鎖住全表,這個(gè)時(shí)間會(huì)很長,這段時(shí)間業(yè)務(wù)不能訪問此表,影響很大。

如果使用 pt-online-schema-change,使用過程中會(huì)創(chuàng)建觸發(fā)器和影子表,也需要很長的時(shí)間。在此操作過程中,都算為風(fēng)險(xiǎn)時(shí)間。將數(shù)據(jù)表拆分,總量減少,有助于降低這個(gè)風(fēng)險(xiǎn)。

  • 大表會(huì)經(jīng)常訪問與更新,就更有可能出現(xiàn)鎖等待。將數(shù)據(jù)切分,用空間換時(shí)間,變相降低訪問壓力。

③隨著業(yè)務(wù)發(fā)展,需要對(duì)某些字段垂直拆分

舉個(gè)例子,假如項(xiàng)目一開始設(shè)計(jì)的用戶表如下:

id bigint #用戶的IDname varchar #用戶的名字last_login_time datetime #最近登錄時(shí)間personal_info text #私人信息..... #其他信息字段

在項(xiàng)目初始階段,這種設(shè)計(jì)是滿足簡單的業(yè)務(wù)需求的,也方便快速迭代開發(fā)。

而當(dāng)業(yè)務(wù)快速發(fā)展時(shí),用戶量從 10w 激增到 10 億,用戶非常的活躍,每次登錄會(huì)更新 last_login_name 字段,使得 user 表被不斷 update,壓力很大。

而其他字段:id,name,personal_info 是不變的或很少更新的,此時(shí)在業(yè)務(wù)角度,就要將 last_login_time 拆分出去,新建一個(gè) user_time 表。

personal_info 屬性是更新和查詢頻率較低的,并且 text 字段占據(jù)了太多的空間。這時(shí)候,就要對(duì)此垂直拆分出 user_ext 表了。

④數(shù)據(jù)量快速增長

隨著業(yè)務(wù)的快速發(fā)展,單表中的數(shù)據(jù)量會(huì)持續(xù)增長,當(dāng)性能接近瓶頸時(shí),就需要考慮水平切分,做分庫分表了。此時(shí)一定要選擇合適的切分規(guī)則,提前預(yù)估好數(shù)據(jù)容量。

⑤安全性和可用性

雞蛋不要放在一個(gè)籃子里。在業(yè)務(wù)層面上垂直切分,將不相關(guān)的業(yè)務(wù)的數(shù)據(jù)庫分隔,因?yàn)槊總€(gè)業(yè)務(wù)的數(shù)據(jù)量、訪問量都不同,不能因?yàn)橐粋€(gè)業(yè)務(wù)把數(shù)據(jù)庫搞掛而牽連到其他業(yè)務(wù)。

利用水平切分,當(dāng)一個(gè)數(shù)據(jù)庫出現(xiàn)問題時(shí),不會(huì)影響到 100% 的用戶,每個(gè)庫只承擔(dān)業(yè)務(wù)的一部分?jǐn)?shù)據(jù),這樣整體的可用性就能提高。

案例分析

用戶中心業(yè)務(wù)場景

用戶中心是一個(gè)非常常見的業(yè)務(wù),主要提供用戶注冊(cè)、登錄、查詢/修改等功能,其核心表為:

  1. User(uid, login_name, passwd, sex, age, nickname) 
  2.  
  3. uid為用戶ID,  主鍵 
  4. login_name, passwd, sex, age, nickname,  用戶屬性 

任何脫離業(yè)務(wù)的架構(gòu)設(shè)計(jì)都是耍流氓,在進(jìn)行分庫分表前,需要對(duì)業(yè)務(wù)場景需求進(jìn)行梳理:

用戶側(cè):前臺(tái)訪問,訪問量較大,需要保證高可用和高一致性。

主要有兩類需求:

  • 用戶登錄:通過 login_name/phone/email 查詢用戶信息,1% 請(qǐng)求屬于這種類型。
  • 用戶信息查詢:登錄之后,通過 uid 來查詢用戶信息,99% 請(qǐng)求屬這種類型。

運(yùn)營側(cè):后臺(tái)訪問,支持運(yùn)營需求,按照年齡、性別、登陸時(shí)間、注冊(cè)時(shí)間等進(jìn)行分頁的查詢。是內(nèi)部系統(tǒng),訪問量較低,對(duì)可用性、一致性的要求不高。

水平切分方法

當(dāng)數(shù)據(jù)量越來越大時(shí),需要對(duì)數(shù)據(jù)庫進(jìn)行水平切分,上文描述的切分方法有"根據(jù)數(shù)值范圍"和"根據(jù)數(shù)值取模"。

"根據(jù)數(shù)值范圍":以主鍵 uid 為劃分依據(jù),按 uid 的范圍將數(shù)據(jù)水平切分到多個(gè)數(shù)據(jù)庫上。

例如:user-db1 存儲(chǔ) uid 范圍為 0~1000w 的數(shù)據(jù),user-db2 存儲(chǔ) uid 范圍為 1000w~2000w uid 數(shù)據(jù)。

優(yōu)點(diǎn)是:擴(kuò)容簡單,如果容量不夠,只要增加新 DB 即可。

不足是:請(qǐng)求量不均勻,一般新注冊(cè)的用戶活躍度會(huì)比較高,所以新的 user-db2 會(huì)比 user-db1 負(fù)載高,導(dǎo)致服務(wù)器利用率不平衡。

"根據(jù)數(shù)值取模":也是以主鍵 uid 為劃分依據(jù),按 uid 取模的值將數(shù)據(jù)水平切分到多個(gè)數(shù)據(jù)庫上。

例如:user-db1 存儲(chǔ) uid 取模得 1 的數(shù)據(jù),user-db2 存儲(chǔ) uid 取模得 0 的 uid 數(shù)據(jù)。

優(yōu)點(diǎn)是:數(shù)據(jù)量和請(qǐng)求量分布均勻。

不足是:擴(kuò)容麻煩,當(dāng)容量不夠時(shí),新增加 DB,需要 rehash。需要考慮對(duì)數(shù)據(jù)進(jìn)行平滑的遷移。

非 uid 的查詢方法

水平切分后,對(duì)于按 uid 查詢的需求能很好的滿足,可以直接路由到具體數(shù)據(jù)庫。

而按非 uid 的查詢,例如 login_name,就不知道具體該訪問哪個(gè)庫了,此時(shí)需要遍歷所有庫,性能會(huì)降低很多。

對(duì)于用戶側(cè),可以采用"建立非 uid 屬性到 uid 的映射關(guān)系"的方案;對(duì)于運(yùn)營側(cè),可以采用"前臺(tái)與后臺(tái)分離"的方案。

①建立非 uid 屬性到 uid 的映射關(guān)系

映射關(guān)系:例如:login_name 不能直接定位到數(shù)據(jù)庫,可以建立 login_name→uid 的映射關(guān)系,用索引表或緩存來存儲(chǔ)。

當(dāng)訪問 login_name 時(shí),先通過映射表查詢出 login_name 對(duì)應(yīng)的 uid,再通過 uid 定位到具體的庫。

映射表只有兩列,可以承載很多數(shù)據(jù),當(dāng)數(shù)據(jù)量過大時(shí),也可以對(duì)映射表再做水平切分。

這類 kv 格式的索引結(jié)構(gòu),可以很好的使用 cache 來優(yōu)化查詢性能,而且映射關(guān)系不會(huì)頻繁變更,緩存命中率會(huì)很高。

基因法:分庫基因,假如通過 uid 分庫,分為 8 個(gè)庫,采用 uid%8 的方式進(jìn)行路由,此時(shí)是由 uid 的最后 3bit 來決定這行 User 數(shù)據(jù)具體落到哪個(gè)庫上,那么這 3bit 可以看為分庫基因。

上面的映射關(guān)系的方法需要額外存儲(chǔ)映射表,按非 uid 字段查詢時(shí),還需要多一次數(shù)據(jù)庫或 cache 的訪問。

如果想要消除多余的存儲(chǔ)和查詢,可以通過 f 函數(shù)取 login_name 的基因作為 uid 的分庫基因。

生成 uid 時(shí),參考上文所述的分布式唯一 ID 生成方案,再加上最后 3 位 bit 值=f(login_name)。

當(dāng)查詢 login_name 時(shí),只需計(jì)算 f(login_name)%8 的值,就可以定位到具體的庫。

不過這樣需要提前做好容量規(guī)劃,預(yù)估未來幾年的數(shù)據(jù)量需要分多少庫,要預(yù)留一定 bit 的分庫基因。

 

②前臺(tái)與后臺(tái)分離

對(duì)于用戶側(cè),主要需求是以單行查詢?yōu)橹鳎枰?login_name/phone/email 到 uid 的映射關(guān)系,可以解決這些字段的查詢問題。

而對(duì)于運(yùn)營側(cè),很多批量分頁且條件多樣的查詢,這類查詢計(jì)算量大,返回?cái)?shù)據(jù)量大,對(duì)數(shù)據(jù)庫的性能消耗較高。

此時(shí),如果和用戶側(cè)共用同一批服務(wù)或數(shù)據(jù)庫,可能因?yàn)楹笈_(tái)的少量請(qǐng)求,占用大量數(shù)據(jù)庫資源,而導(dǎo)致用戶側(cè)訪問性能降低或超時(shí)。

這類業(yè)務(wù)最好采用"前臺(tái)與后臺(tái)分離"的方案,運(yùn)營側(cè)后臺(tái)業(yè)務(wù)抽取獨(dú)立的 service 和 DB,解決和前臺(tái)業(yè)務(wù)系統(tǒng)的耦合。

由于運(yùn)營側(cè)對(duì)可用性、一致性的要求不高,可以不訪問實(shí)時(shí)庫,而是通過 binlog 異步同步數(shù)據(jù)到運(yùn)營庫進(jìn)行訪問。

在數(shù)據(jù)量很大的情況下,還可以使用 ES 搜索引擎或 Hive 來滿足后臺(tái)復(fù)雜的查詢方式。

支持分庫分表中間件

站在巨人的肩膀上能省力很多,目前分庫分表已經(jīng)有一些較為成熟的開源解決方案:

  • sharding-jdbc(當(dāng)當(dāng))

https://github.com/shardingjdbc

  • TSharding(蘑菇街)

https://github.com/baihui212/tsharding

  • Atlas(奇虎360)

https://github.com/Qihoo360/Atlas

  • Cobar(阿里巴巴)

https://github.com/alibaba/cobar

  • MyCAT(基于 Cobar)

http://www.mycat.io/

  • Oceanus(58 同城)

https://github.com/58code/Oceanus

  • Vitess(谷歌)

https://github.com/vitessio/vitess

參考文章:

  • 數(shù)據(jù)庫分布式架構(gòu)掃盲——分庫分表(及銀行核心系統(tǒng)適用性思考)
  • 分庫分表的思想
  • 水平分庫分表的關(guān)鍵步驟以及可能遇到的問題
  • 從原則、方案、策略及難點(diǎn)闡述分庫分表
  • Leaf——美團(tuán)點(diǎn)評(píng)分布式 ID 生成系統(tǒng)
  • 架構(gòu)師之路公眾號(hào)

 

責(zé)任編輯:武曉燕 來源: 博客園
相關(guān)推薦

2018-09-28 05:25:53

TopK算法代碼

2020-12-11 09:24:19

Elasticsear存儲(chǔ)數(shù)據(jù)

2020-04-22 11:19:07

貪心算法動(dòng)態(tài)規(guī)劃

2018-11-01 13:49:23

桶排序排序面試

2018-10-28 22:37:00

計(jì)數(shù)排序排序面試

2020-09-24 14:40:55

Python 開發(fā)編程語言

2015-02-13 10:42:31

前端工具Dreamweaver

2020-04-16 08:22:11

HTTPS加解密協(xié)議

2021-01-22 10:09:23

簡歷求職者面試

2023-03-28 08:58:47

分庫分表TiDB

2024-08-07 10:34:46

2025-02-17 10:30:01

2020-03-30 17:20:54

B+樹SQL索引

2019-07-08 10:00:52

Java內(nèi)存模型并發(fā)

2015-09-14 11:06:53

PYTHON運(yùn)維

2019-07-10 10:06:24

面試官三次握手四次揮手

2020-08-26 08:18:39

數(shù)據(jù)索引查詢

2018-11-06 11:40:19

時(shí)間復(fù)雜度面試算法

2020-09-02 08:04:59

多線程互聯(lián)網(wǎng)高并發(fā)

2019-04-16 13:30:05

表達(dá)式求值數(shù)據(jù)結(jié)構(gòu)算法
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 国产精品影视在线观看 | 美女国产 | av网址在线| 色综合99 | 久久久这里只有17精品 | .国产精品成人自产拍在线观看6 | 亚洲精品电影在线观看 | 国产高清视频一区二区 | av黄色在线 | 中文字幕在线一区 | 日韩成人免费 | 欧美视频1区 | 中文字幕在线一区二区三区 | 免费在线播放黄色 | 999久久久久久久久6666 | 最新国产视频 | 成人精品国产 | 日本中文字幕一区 | 久久亚洲国产 | 欧美日韩在线观看视频网站 | 我爱操 | 成人免费共享视频 | 久久夜夜 | 超碰在线人人 | 狠狠涩 | 久久久久久免费精品一区二区三区 | 国产欧美一区二区三区久久手机版 | 乳色吐息在线观看 | 亚洲一区二区在线播放 | 国产精品视频二区三区 | 日日操视频 | 男人天堂网址 | 国产69精品久久99不卡免费版 | 国产精品久久久久久久午夜片 | 毛片免费观看 | 91视频网址 | 国产91久久精品一区二区 | 色综合久久天天综合网 | 成人一区二区三区视频 | 欧美视频1区 | 成人免费三级电影 |