目錄;
(一) 拆分實(shí)施策略和示例演示
(二) 全局主鍵生成策略
(三) 關(guān)于使用框架還是自主開發(fā)以及sharding實(shí)現(xiàn)層面的考量
(四) 多數(shù)據(jù)源的事務(wù)處理
(五) 一種支持自由規(guī)劃無須數(shù)據(jù)遷移和修改路由代碼的Sharding擴(kuò)容方案
(一) 拆分實(shí)施策略和示例演示
第一部分:實(shí)施策略
圖1.數(shù)據(jù)庫分庫分表(sharding)實(shí)施策略圖解
1.準(zhǔn)備階段
對(duì)數(shù)據(jù)庫進(jìn)行分庫分表(Sharding化)前,需要開發(fā)人員充分了解系統(tǒng)業(yè)務(wù)邏輯和數(shù)據(jù)庫schema.一個(gè)好的建議是繪制一張數(shù)據(jù)庫ER圖或領(lǐng)域模型圖处嫌,以這類圖為基礎(chǔ)劃分shard,直觀易行笨篷,可以確保開發(fā)人員始終保持清醒思路。對(duì)于是選擇數(shù)據(jù)庫ER圖還是領(lǐng)域模型圖要根據(jù)項(xiàng)目自身情況進(jìn)行選擇联予。如果項(xiàng)目使用數(shù)據(jù)驅(qū)動(dòng)的開發(fā)方式啼县,團(tuán)隊(duì)以數(shù)據(jù)庫ER圖作為業(yè)務(wù)交流的基礎(chǔ),則自然會(huì)選擇數(shù)據(jù)庫ER圖沸久,如果項(xiàng)目使用的是領(lǐng)域驅(qū)動(dòng)的開發(fā)方式季眷,并通過OR-Mapping構(gòu)建了一個(gè)良好的領(lǐng)域模型,那么領(lǐng)域模型圖無疑是最好的選擇卷胯。就我個(gè)人來說子刮,更加傾向使用領(lǐng)域模型圖,因?yàn)檫M(jìn)行切分時(shí)更多的是以業(yè)務(wù)為依據(jù)進(jìn)行分析判斷,領(lǐng)域模型無疑更加清晰和直觀挺峡。
2.分析階段
- 垂直切分
垂直切分的依據(jù)原則是:將業(yè)務(wù)緊密葵孤,表間關(guān)聯(lián)密切的表劃分在一起,例如同一模塊的表橱赠。結(jié)合已經(jīng)準(zhǔn)備好的數(shù)據(jù)庫ER圖或領(lǐng)域模型圖尤仍,仿照活動(dòng)圖中的泳道概念,一個(gè)泳道代表一個(gè)shard狭姨,把所有表格劃分到不同的泳道中宰啦。下面的分析示例會(huì)展示這種做法。當(dāng)然送挑,你也可以在打印出的ER圖或模型圖上直接用鉛筆圈绑莺,一切取決于你自己的喜好。 - 水平切分
垂直切分后惕耕,需要對(duì)shard內(nèi)表格的數(shù)據(jù)量和增速進(jìn)一步分析纺裁,以確定是否需要進(jìn)行水平切分。
2.1若劃分到一起的表格數(shù)據(jù)增長(zhǎng)緩慢司澎,在產(chǎn)品上線后可遇見的足夠長(zhǎng)的時(shí)期內(nèi)均可以由單一數(shù)據(jù)庫承載欺缘,則不需要進(jìn)行水平切分,所有表格駐留同一shard,所有表間關(guān)聯(lián)關(guān)系會(huì)得到最大限度的保留挤安,同時(shí)保證了書寫SQL的自由度谚殊,不易受join、group by蛤铜、order by等子句限制嫩絮。
2.2 若劃分到一起的表格數(shù)據(jù)量巨大,增速迅猛围肥,需要進(jìn)一步進(jìn)行水平分割剿干。進(jìn)一步的水平分割就這樣進(jìn)行:
2.2.1.結(jié)合業(yè)務(wù)邏輯和表間關(guān)系,將當(dāng)前shard劃分成多個(gè)更小的shard,通常情況下穆刻,這些更小的shard每一個(gè)都只包含一個(gè)主表(將以該表ID進(jìn)行散列的表)和多個(gè)與其關(guān)聯(lián)或間接關(guān)聯(lián)的次表置尔。這種一個(gè)shard一張主表多張次表的狀況是水平切分的必然結(jié)果。這樣切分下來氢伟,shard數(shù)量就會(huì)迅速增多榜轿。如果每一個(gè)shard代表一個(gè)獨(dú)立的數(shù)據(jù)庫,那么管理和維護(hù)數(shù)據(jù)庫將會(huì)非常麻煩朵锣,而且這些小shard往往只有兩三張表谬盐,為此而建立一個(gè)新庫,利用率并不高猪勇,因此设褐,在水平切分完成后可再進(jìn)行一次“反向的Merge”,即:將業(yè)務(wù)上相近,并且具有相近數(shù)據(jù)增長(zhǎng)速率(主表數(shù)據(jù)量在同一數(shù)量級(jí)上)的兩個(gè)或多個(gè)shard放到同一個(gè)數(shù)據(jù)庫上,在邏輯上它們依然是獨(dú)立的shard助析,有各自的主表犀被,并依據(jù)各自主表的ID進(jìn)行散列,不同的只是它們的散列取模(即節(jié)點(diǎn)數(shù)量)必需是一致的外冀。這樣寡键,每個(gè)數(shù)據(jù)庫結(jié)點(diǎn)上的表格數(shù)量就相對(duì)平均了。
2.2.2. 所有表格均劃分到合適的shard之后雪隧,所有跨越shard的表間關(guān)聯(lián)都必須打斷西轩,在書寫sql時(shí),跨shard的join脑沿、group by藕畔、order by都將被禁止,需要在應(yīng)用程序?qū)用鎱f(xié)調(diào)解決這些問題庄拇。
特別想提一點(diǎn):經(jīng)水平切分后注服,shard的粒度往往要比只做垂直切割的粒度要小,原單一垂直shard會(huì)被細(xì)分為一到多個(gè)以一個(gè)主表為中心關(guān)聯(lián)或間接關(guān)聯(lián)多個(gè)次表的shard措近,此時(shí)的shard粒度與領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中的“聚合”概念不謀而合溶弟,甚至可以說是完全一致,每個(gè)shard的主表正是一個(gè)聚合中的聚合根瞭郑!
3.實(shí)施階段
如果項(xiàng)目在開發(fā)伊始就決定進(jìn)行分庫分表辜御,則嚴(yán)格按照分析設(shè)計(jì)方案推進(jìn)即可。如果是在中期架構(gòu)演進(jìn)中實(shí)施屈张,除搭建實(shí)現(xiàn)sharding邏輯的基礎(chǔ)設(shè)施外(關(guān)于該話題會(huì)在下篇文章中進(jìn)行闡述)擒权,還需要對(duì)原有SQL逐一過濾分析,修改那些因?yàn)閟harding而受到影響的sql.
第二部分:示例演示
本文選擇一個(gè)人盡皆知的應(yīng)用:jpetstore來演示如何進(jìn)行分庫分表(sharding)在分析階段的工作阁谆。由于一些個(gè)人原因菜拓,演示使用的jpetstore來自原ibatis官方的一個(gè)Demo版本,SVN地址為:http://mybatis.googlecode.com/svn/tags/java_release_2.3.4-726/jpetstore-5笛厦。關(guān)于jpetstore的業(yè)務(wù)邏輯這里不再介紹,這是一個(gè)非常簡(jiǎn)單的電商系統(tǒng)原型俺夕,其領(lǐng)域模型如下圖:
圖2. jpetstore領(lǐng)域模型
由于系統(tǒng)較簡(jiǎn)單裳凸,我們很容易從模型上看出,其主要由三個(gè)模塊組成:用戶劝贸,產(chǎn)品和訂單姨谷。那么垂直切分的方案也就出來了。接下來看水平切分映九,如果我們從一個(gè)實(shí)際的寵物店出發(fā)考慮梦湘,可能出現(xiàn)數(shù)據(jù)激增的單表應(yīng)該是Account和Order,因此這兩張表需要進(jìn)行水平切分。對(duì)于Product模塊來說,如果是一個(gè)實(shí)際的系統(tǒng)捌议,Product和Item的數(shù)量都不會(huì)很大哼拔,因此只做垂直切分就足夠了,也就是(Product瓣颅,Category倦逐,Item,Iventory宫补,Supplier)五張表在一個(gè)數(shù)據(jù)庫結(jié)點(diǎn)上(沒有水平切分檬姥,不會(huì)存在兩個(gè)以上的數(shù)據(jù)庫結(jié)點(diǎn))。但是作為一個(gè)演示粉怕,我們假設(shè)產(chǎn)品模塊也有大量的數(shù)據(jù)需要我們做水平切分健民,那么分析來看,這個(gè)模塊要拆分出兩個(gè)shard:一個(gè)是(Product(主)贫贝,Category)秉犹,另一個(gè)是(Item(主),Iventory平酿,Supplier)凤优,同時(shí),我們認(rèn)為:這兩個(gè)shard在數(shù)據(jù)增速上應(yīng)該是相近的蜈彼,且在業(yè)務(wù)上也很緊密筑辨,那么我們可以把這兩個(gè)shard放在同一個(gè)數(shù)據(jù)庫節(jié)點(diǎn)上,Item和Product數(shù)據(jù)在散列時(shí)取一樣的模幸逆。根據(jù)前文介紹的圖紙繪制方法棍辕,我們得到下面這張sharding示意圖:
圖3. jpetstore sharding示意圖
對(duì)于這張圖再說明幾點(diǎn):
1.使用泳道表示物理shard(一個(gè)數(shù)據(jù)庫結(jié)點(diǎn))
2.若垂直切分出的shard進(jìn)行了進(jìn)一步的水平切分,但公用一個(gè)物理shard的話还绘,則用虛線框住楚昭,表示其在邏輯上是一個(gè)獨(dú)立的shard。
3.深色實(shí)體表示主表
4.X表示需要打斷的表間關(guān)聯(lián)
(二) 全局主鍵生成策略
第一部分:一些常見的主鍵生成策略
一旦數(shù)據(jù)庫被切分到多個(gè)物理結(jié)點(diǎn)上拍顷,我們將不能再依賴數(shù)據(jù)庫自身的主鍵生成機(jī)制抚太。一方面,某個(gè)分區(qū)數(shù)據(jù)庫自生成的ID無法保證在全局上是唯一的昔案;另一方面尿贫,應(yīng)用程序在插入數(shù)據(jù)之前需要先獲得ID,以便進(jìn)行SQL路由。目前幾種可行的主鍵生成策略有: - UUID:使用UUID作主鍵是最簡(jiǎn)單的方案踏揣,但是缺點(diǎn)也是非常明顯的庆亡。由于UUID非常的長(zhǎng),除占用大量存儲(chǔ)空間外捞稿,最主要的問題是在索引上又谋,在建立索引和基于索引進(jìn)行查詢時(shí)都存在性能問題拼缝。
- 結(jié)合數(shù)據(jù)庫維護(hù)一個(gè)Sequence表:此方案的思路也很簡(jiǎn)單,在數(shù)據(jù)庫中建立一個(gè)Sequence表彰亥,表的結(jié)構(gòu)類似于:
[sql] view plaincopy
01.CREATE TABLESEQUENCE
( -
tablename
varchar(30) NOT NULL, -
nextid
bigint(20) NOT NULL, - PRIMARY KEY (
tablename
)
05.) ENGINE=InnoDB
每當(dāng)需要為某個(gè)表的新紀(jì)錄生成ID時(shí)就從Sequence表中取出對(duì)應(yīng)表的nextid,并將nextid的值加1后更新到數(shù)據(jù)庫中以備下次使用咧七。此方案也較簡(jiǎn)單,但缺點(diǎn)同樣明顯:由于所有插入任何都需要訪問該表剩愧,該表很容易成為系統(tǒng)性能瓶頸猪叙,同時(shí)它也存在單點(diǎn)問題,一旦該表數(shù)據(jù)庫失效仁卷,整個(gè)應(yīng)用程序?qū)o法工作穴翩。有人提出使用Master-Slave進(jìn)行主從同步,但這也只能解決單點(diǎn)問題锦积,并不能解決讀寫比為1:1的訪問壓力問題芒帕。
除此之外,還有一些方案丰介,像對(duì)每個(gè)數(shù)據(jù)庫結(jié)點(diǎn)分區(qū)段劃分ID,以及網(wǎng)上的一些ID生成算法背蟆,因?yàn)槿鄙倏刹僮餍院蛯?shí)踐檢驗(yàn),本文并不推薦哮幢。實(shí)際上带膀,接下來,我們要介紹的是Fickr使用的一種主鍵生成方案,這個(gè)方案是目前我所知道的最優(yōu)秀的一個(gè)方案,并且經(jīng)受了實(shí)踐的檢驗(yàn)装处,可以為大多數(shù)應(yīng)用系統(tǒng)所借鑒雕欺。
第二部分:一種極為優(yōu)秀的主鍵生成策略
flickr開發(fā)團(tuán)隊(duì)在2010年撰文介紹了flickr使用的一種主鍵生成測(cè)策略漠魏,同時(shí)表示該方案在flickr上的實(shí)際運(yùn)行效果也非常令人滿意,原文連接:Ticket Servers: Distributed Unique Primary Keys on the Cheap 這個(gè)方案是我目前知道的最好的方案,它與一般Sequence表方案有些類似,但卻很好地解決了性能瓶頸和單點(diǎn)問題剂癌,是一種非常可靠而高效的全局主鍵生成方案翰绊。
圖1. flickr采用的sharding主鍵生成方案示意圖
flickr這一方案的整體思想是:建立兩臺(tái)以上的數(shù)據(jù)庫ID生成服務(wù)器佩谷,每個(gè)服務(wù)器都有一張記錄各表當(dāng)前ID的Sequence表,但是Sequence中ID增長(zhǎng)的步長(zhǎng)是服務(wù)器的數(shù)量监嗜,起始值依次錯(cuò)開琳要,這樣相當(dāng)于把ID的生成散列到了每個(gè)服務(wù)器節(jié)點(diǎn)上。例如:如果我們?cè)O(shè)置兩臺(tái)數(shù)據(jù)庫ID生成服務(wù)器秤茅,那么就讓一臺(tái)的Sequence表的ID起始值為1,每次增長(zhǎng)步長(zhǎng)為2,另一臺(tái)的Sequence表的ID起始值為2,每次增長(zhǎng)步長(zhǎng)也為2,那么結(jié)果就是奇數(shù)的ID都將從第一臺(tái)服務(wù)器上生成童叠,偶數(shù)的ID都從第二臺(tái)服務(wù)器上生成框喳,這樣就將生成ID的壓力均勻分散到兩臺(tái)服務(wù)器上课幕,同時(shí)配合應(yīng)用程序的控制,當(dāng)一個(gè)服務(wù)器失效后五垮,系統(tǒng)能自動(dòng)切換到另一個(gè)服務(wù)器上獲取ID乍惊,從而保證了系統(tǒng)的容錯(cuò)放仗。
關(guān)于這個(gè)方案润绎,有幾點(diǎn)細(xì)節(jié)這里再說明一下:
flickr的數(shù)據(jù)庫ID生成服務(wù)器是專用服務(wù)器,服務(wù)器上只有一個(gè)數(shù)據(jù)庫诞挨,數(shù)據(jù)庫中表都是用于生成Sequence的莉撇,這也是因?yàn)閍uto-increment-offset和auto-increment-increment這兩個(gè)數(shù)據(jù)庫變量是數(shù)據(jù)庫實(shí)例級(jí)別的變量。
flickr的方案中表格中的stub字段只是一個(gè)char(1) NOT NULL存根字段惶傻,并非表名棍郎,因此,一般來說银室,一個(gè)Sequence表只有一條紀(jì)錄涂佃,可以同時(shí)為多張表生成ID,如果需要表的ID是有連續(xù)的蜈敢,需要為該表單獨(dú)建立Sequence表辜荠。
方案使用了mysql的LAST_INSERT_ID()函數(shù),這也決定了Sequence表只能有一條記錄抓狭。
使用REPLACE INTO插入數(shù)據(jù)伯病,這是很討巧的作法,主要是希望利用mysql自身的機(jī)制生成ID,不僅是因?yàn)檫@樣簡(jiǎn)單辐宾,更是因?yàn)槲覀冃枰狪D按照我們?cè)O(shè)定的方式(初值和步長(zhǎng))來生成狱从。
SELECT LAST_INSERT_ID()必須要于REPLACE INTO語句在同一個(gè)數(shù)據(jù)庫連接下才能得到剛剛插入的新ID,否則返回的值總是0
該方案中Sequence表使用的是MyISAM引擎叠纹,以獲取更高的性能季研,注意:MyISAM引擎使用的是表級(jí)別的鎖,MyISAM對(duì)表的讀寫是串行的誉察,因此不必?fù)?dān)心在并發(fā)時(shí)兩次讀取會(huì)得到同一個(gè)ID(另外与涡,應(yīng)該程序也不需要同步,每個(gè)請(qǐng)求的線程都會(huì)得到一個(gè)新的connection,不存在需要同步的共享資源)持偏。經(jīng)過實(shí)際對(duì)比測(cè)試驼卖,使用一樣的Sequence表進(jìn)行ID生成,MyISAM引擎要比InnoDB表現(xiàn)高出很多鸿秆!
可使用純JDBC實(shí)現(xiàn)對(duì)Sequence表的操作酌畜,以便獲得更高的效率,實(shí)驗(yàn)表明卿叽,即使只使用Spring JDBC性能也不及純JDBC來得快
實(shí)現(xiàn)該方案桥胞,應(yīng)用程序同樣需要做一些處理恳守,主要是兩方面的工作:
- 自動(dòng)均衡數(shù)據(jù)庫ID生成服務(wù)器的訪問
-
確保在某個(gè)數(shù)據(jù)庫ID生成服務(wù)器失效的情況下,能將請(qǐng)求轉(zhuǎn)發(fā)到其他服務(wù)器上執(zhí)行贩虾。
(三) 關(guān)于使用框架還是自主開發(fā)以及sharding實(shí)現(xiàn)層面的考量
一催烘、sharding邏輯的實(shí)現(xiàn)層面
從一個(gè)系統(tǒng)的程序架構(gòu)層面來看,sharding邏輯可以在DAO層缎罢、JDBC API層伊群、介于DAO與JDBC之間的Spring數(shù)據(jù)訪問封裝層(各種spring的template)以及介于應(yīng)用服務(wù)器與數(shù)據(jù)庫之間的sharding代理服務(wù)器四個(gè)層面上實(shí)現(xiàn)。
圖1. Sharding實(shí)現(xiàn)層面與相關(guān)框架/產(chǎn)品
在DAO層實(shí)現(xiàn)
當(dāng)團(tuán)隊(duì)決定自行實(shí)現(xiàn)sharding的時(shí)候策精,DAO層可能是嵌入sharding邏輯的首選位置舰始,因?yàn)樵谶@個(gè)層面上,每一個(gè)DAO的方法都明確地知道需要訪問的數(shù)據(jù)表以及查詢參數(shù)蛮寂,借助這些信息可以直接定位到目標(biāo)shard上蔽午,而不必像框架那樣需要對(duì)SQL進(jìn)行解析然后再依據(jù)配置的規(guī)則進(jìn)行路由。另一個(gè)優(yōu)勢(shì)是不會(huì)受ORM框架的制約酬蹋。由于現(xiàn)在的大多數(shù)應(yīng)用在數(shù)據(jù)訪問層上會(huì)依賴某種ORM框架及老,而多數(shù)的shrading框架往往無法支持或只能支持一種orm框架,這使得在選擇和應(yīng)用框架時(shí)受到了很大的制約范抓,而自行實(shí)現(xiàn)sharding完全沒有這方面的問題骄恶,甚至不同的shard使用不同的orm框架都可以在一起協(xié)調(diào)工作。比如現(xiàn)在的java應(yīng)用大多使用hibernate匕垫,但是當(dāng)下還沒有非常令人滿意的基于hibernate的sharding框架僧鲁,(關(guān)于hibernate hards會(huì)在下文介紹),因此很多團(tuán)隊(duì)會(huì)選擇自行實(shí)現(xiàn)sharding象泵。
簡(jiǎn)單總結(jié)一下寞秃,在DAO層自行實(shí)現(xiàn)sharding的優(yōu)勢(shì)在于:不受ORM框架的制約、實(shí)現(xiàn)起來較為簡(jiǎn)單偶惠、易于根據(jù)系統(tǒng)特點(diǎn)進(jìn)行靈活的定制春寿、無需SQL解析和路由規(guī)則匹配,性能上表現(xiàn)會(huì)稍好一些;劣勢(shì)在于:有一定的技術(shù)門檻忽孽,工作量比依靠框架實(shí)現(xiàn)要大(反過來看绑改,框架會(huì)有學(xué)習(xí)成本)、不通用兄一,只能在特定系統(tǒng)里工作厘线。當(dāng)然,在DAO層同樣可以通過XML配置或是注解將sharding邏輯抽離到“外部”出革,形成一套通用的框架. 不過目前還沒有出現(xiàn)此類的框架造壮。
在ORM框架層實(shí)現(xiàn)
在ORM框架層實(shí)現(xiàn)sharding有兩個(gè)方向,一個(gè)是在實(shí)現(xiàn)O-R Mapping的前提下同時(shí)提供sharding支持骂束,從而定位為一種分布式的數(shù)據(jù)訪問框架耳璧,這一類類型的框架代表就是guzz另一個(gè)方向是通過對(duì)既有ORM框架進(jìn)行修改增強(qiáng)來加入sharding機(jī)制硝全。此類型的代表產(chǎn)品是hibernate shard. 應(yīng)該說以hibernate這樣主流的地位,行業(yè)對(duì)于一款面向hibernate的sharding框架的需求是非常迫切的楞抡,但是就目前的hibernate shards來看,表現(xiàn)還算不上令人滿意析藕,主要是它對(duì)使用hibernate的限制過多召廷,比如它對(duì)HQL的支持就非常有限。在mybatis方面账胧,目前還沒有成熟的相關(guān)框架產(chǎn)生竞慢。有人提出利用mybatis的插件機(jī)制實(shí)現(xiàn)sharding,但是遺憾的是,mybatis的插件機(jī)制控制不到多數(shù)據(jù)源的連接層面治泥,另一方面筹煮,離開插件層又失去了對(duì)sql進(jìn)行集中解析和路由的機(jī)會(huì),因此在mybatis框架上居夹,目前還沒有可供借鑒的框架败潦,團(tuán)隊(duì)可能要在DAO層或Spring模板類上下功夫了。
在JDBC API層實(shí)現(xiàn)
JDBC API層是很多人都會(huì)想到的一個(gè)實(shí)現(xiàn)sharding的絕佳場(chǎng)所准脂,如果我們能提供一個(gè)實(shí)現(xiàn)了sharding邏輯的JDBC API實(shí)現(xiàn)劫扒,那么sharding對(duì)于整個(gè)應(yīng)用程序來說就是完全透明的,而這樣的實(shí)現(xiàn)可以直接作為通用的sharding產(chǎn)品了狸膏。但是這種方案的技術(shù)門檻和工作量顯然不是一般團(tuán)隊(duì)能做得來的沟饥,因此基本上沒有團(tuán)隊(duì)會(huì)在這一層面上實(shí)現(xiàn)sharding,甚至也沒有此類的開源產(chǎn)品。筆者知道的只有一款商業(yè)產(chǎn)品dbShards采用的是這一方案湾戳。
在介于DAO與JDBC之間的Spring數(shù)據(jù)訪問封裝層實(shí)現(xiàn)
在springd大行其道的今天贤旷,幾乎沒有哪個(gè)java平臺(tái)上構(gòu)建的應(yīng)用不使用spring,在DAO與JDBC之間砾脑,spring提供了各種template來管理資源的創(chuàng)建與釋放以及與事務(wù)的同步幼驶,大多數(shù)基于spring的應(yīng)用都會(huì)使用template類做為數(shù)據(jù)訪問的入口,這給了我們另一個(gè)嵌入sharding邏輯的機(jī)會(huì)拦止,就是通過提供一個(gè)嵌入了sharding邏輯的template類來完成sharding工作.這一方案在效果上與基于JDBC API實(shí)現(xiàn)的方案基本一致县遣,同樣是對(duì)上層代碼透明,在進(jìn)行sharding改造時(shí)可以平滑地過度汹族,但它的實(shí)現(xiàn)卻比基于JDBC API的方式簡(jiǎn)單萧求,因此成為了不少框架的選擇,阿里集團(tuán)研究院開源的Cobar Client就是這類方案的一種實(shí)現(xiàn)顶瞒。
在應(yīng)用服務(wù)器與數(shù)據(jù)庫之間通過代理實(shí)現(xiàn)
在應(yīng)用服務(wù)器與數(shù)據(jù)庫之間加入一個(gè)代理夸政,應(yīng)用程序向數(shù)據(jù)發(fā)出的數(shù)據(jù)請(qǐng)求會(huì)先通過代理,代理會(huì)根據(jù)配置的路由規(guī)則榴徐,對(duì)SQL進(jìn)行解析后路由到目標(biāo)shard守问,因?yàn)檫@種方案對(duì)應(yīng)用程序完全透明匀归,通用性好,所以成為了很多sharding產(chǎn)品的選擇耗帕。在這方面較為知名的產(chǎn)品是mysql官方的代理工具:Mysql Proxy和一款國人開發(fā)的產(chǎn)品:amoeba穆端。mysql proxy本身并沒有實(shí)現(xiàn)任何sharding邏輯,它只是作為一種面向mysql數(shù)據(jù)庫的代理仿便,給開發(fā)人員提供了一個(gè)嵌入sharding邏輯的場(chǎng)所体啰,它使用lua作為編程語言,這對(duì)很多團(tuán)隊(duì)來說是需要考慮的一個(gè)問題嗽仪。amoeba則是專門實(shí)現(xiàn)讀寫分離與sharding的代理產(chǎn)品荒勇,它使用非常簡(jiǎn)單,不使用任何編程語言闻坚,只需要通過xml進(jìn)行配置沽翔。不過amoeba不支持事務(wù)(從應(yīng)用程序發(fā)出的包含事務(wù)信息的請(qǐng)求到達(dá)amoeba時(shí),事務(wù)信息會(huì)被抹去窿凤,因此仅偎,即使是單點(diǎn)數(shù)據(jù)訪問也不會(huì)有事務(wù)存在)一直是個(gè)硬傷。當(dāng)然卷玉,這要看產(chǎn)品的定位和設(shè)計(jì)理念哨颂,我們只能說對(duì)于那些對(duì)事務(wù)要求非常高的系統(tǒng),amoeba是不適合的相种。
二威恼、使用框架還是自主開發(fā)?
前面的討論中已經(jīng)羅列了很多開源框架與產(chǎn)品寝并,這里再整理一下:基于代理方式的有MySQL Proxy和Amoeba箫措,基于Hibernate框架的是Hibernate Shards,通過重寫spring的ibatis template類是Cobar Client衬潦,這些框架各有各的優(yōu)勢(shì)與短板斤蔓,架構(gòu)師可以在深入調(diào)研之后結(jié)合項(xiàng)目的實(shí)際情況進(jìn)行選擇,但是總的來說镀岛,我個(gè)人對(duì)于框架的選擇是持謹(jǐn)慎態(tài)度的弦牡。一方面多數(shù)框架缺乏成功案例的驗(yàn)證,其成熟性與穩(wěn)定性值得懷疑漂羊。另一方面驾锰,一些從成功商業(yè)產(chǎn)品開源出框架(如阿里和淘寶的一些開源項(xiàng)目)是否適合你的項(xiàng)目是需要架構(gòu)師深入調(diào)研分析的。當(dāng)然走越,最終的選擇一定是基于項(xiàng)目特點(diǎn)椭豫、團(tuán)隊(duì)狀況、技術(shù)門檻和學(xué)習(xí)成本等綜合因素考量確定的。
(四) 多數(shù)據(jù)源的事務(wù)處理
分布式事務(wù)
這是最為人們所熟知的多數(shù)據(jù)源事務(wù)處理機(jī)制赏酥。本文并不打算對(duì)分布式事務(wù)做過多介紹喳整,讀者可參考此文:關(guān)于分布式事務(wù)、兩階段提交裸扶、一階段提交框都、Best Efforts 1PC模式和事務(wù)補(bǔ)償機(jī)制的研究 。在這里只想對(duì)分布式事務(wù)的利弊作一下分析呵晨。
優(yōu)勢(shì): - 基于兩階段提交瞬项,最大限度地保證了跨數(shù)據(jù)庫操作的“原子性”,是分布式系統(tǒng)下最嚴(yán)格的事務(wù)實(shí)現(xiàn)方式何荚。
- 實(shí)現(xiàn)簡(jiǎn)單,工作量小猪杭。由于多數(shù)應(yīng)用服務(wù)器以及一些獨(dú)立的分布式事務(wù)協(xié)調(diào)器做了大量的封裝工作餐塘,使得項(xiàng)目中引入分布式事務(wù)的難度和工作量基本上可以忽略不計(jì)。
劣勢(shì):
系統(tǒng)“水平”伸縮的死敵皂吮〗渖担基于兩階段提交的分布式事務(wù)在提交事務(wù)時(shí)需要在多個(gè)節(jié)點(diǎn)之間進(jìn)行協(xié)調(diào),最大限度地推后了提交事務(wù)的時(shí)間點(diǎn),客觀上延長(zhǎng)了事務(wù)的執(zhí)行時(shí)間蜂筹,這會(huì)導(dǎo)致事務(wù)在訪問共享資源時(shí)發(fā)生沖突和死鎖的概率增高需纳,隨著數(shù)據(jù)庫節(jié)點(diǎn)的增多,這種趨勢(shì)會(huì)越來越嚴(yán)重艺挪,從而成為系統(tǒng)在數(shù)據(jù)庫層面上水平伸縮的"枷鎖"不翩, 這是很多Sharding系統(tǒng)不采用分布式事務(wù)的主要原因。
基于Best Efforts 1PC模式的事務(wù)
與分布式事務(wù)采用的兩階段提交不同麻裳,Best Efforts 1PC模式采用的是一階段端提交口蝠,犧牲了事務(wù)在某些特殊情況(當(dāng)機(jī)、網(wǎng)絡(luò)中斷等)下的安全性津坑,卻獲得了良好的性能妙蔗,特別是消除了對(duì)水平伸縮的桎酷。Distributed transactions in Spring, with and without XA一文對(duì)Best Efforts 1PC模式進(jìn)行了詳細(xì)的說明疆瑰,該文提供的Demo代碼更是直接給出了在Spring環(huán)境下實(shí)現(xiàn)一階段提交的多數(shù)據(jù)源事務(wù)管理示例眉反。不過需要注意的是,原示例是基于spring 3.0之前的版本穆役,如果你使用spring 3.0+,會(huì)得到如下錯(cuò)誤:java.lang.IllegalStateException: Cannot activate transaction synchronization - already active寸五,如果使用spring 3.0+,你需要參考spring-data-neo4j的實(shí)現(xiàn)孵睬。鑒于Best Efforts 1PC模式的性能優(yōu)勢(shì)播歼,以及相對(duì)簡(jiǎn)單的實(shí)現(xiàn)方式,它被大多數(shù)的sharding框架和項(xiàng)目采用。
事務(wù)補(bǔ)償機(jī)制
對(duì)于那些對(duì)性能要求很高秘狞,但對(duì)一致性要求并不高的系統(tǒng)叭莫,往往并不苛求系統(tǒng)的實(shí)時(shí)一致性,只要在一個(gè)允許的時(shí)間周期內(nèi)達(dá)到最終一致性即可烁试,這使得事務(wù)補(bǔ)償機(jī)制成為一種可行的方案雇初。事務(wù)補(bǔ)償機(jī)制最初被提出是在“長(zhǎng)事務(wù)”的處理中,但是對(duì)于分布式系統(tǒng)確保一致性也有很好的參考意義减响【甘籠統(tǒng)地講,與事務(wù)在執(zhí)行中發(fā)生錯(cuò)誤后立即回滾的方式不同支示,事務(wù)補(bǔ)償是一種事后檢查并補(bǔ)救的措施刊橘,它只期望在一個(gè)容許時(shí)間周期內(nèi)得到最終一致的結(jié)果就可以了。事務(wù)補(bǔ)償?shù)膶?shí)現(xiàn)與系統(tǒng)業(yè)務(wù)緊密相關(guān)颂鸿,并沒有一種標(biāo)準(zhǔn)的處理方式促绵。一些常見的實(shí)現(xiàn)方式有:對(duì)數(shù)據(jù)進(jìn)行對(duì)帳檢查;基于日志進(jìn)行比對(duì);定期同標(biāo)準(zhǔn)數(shù)據(jù)來源進(jìn)行同步,等等嘴纺。
小結(jié)
分布式事務(wù)败晴,最嚴(yán)格的事務(wù)實(shí)現(xiàn),但性能是個(gè)大問題;Best Efforts 1PC模式栽渴,性能與事務(wù)可靠性的平衡尖坤,支持系統(tǒng)水平伸縮,大多數(shù)情況下是最合適的選擇;事務(wù)補(bǔ)償機(jī)制闲擦,只能適用于對(duì)事務(wù)性要求不高慢味,允許數(shù)據(jù)“最終一致”即可的系統(tǒng),犧牲實(shí)時(shí)一致性墅冷,獲得最大的性能回報(bào)贮缕。
(五) 一種支持自由規(guī)劃無須數(shù)據(jù)遷移和修改路由代碼的Sharding擴(kuò)容方案
本文將重點(diǎn)圍繞“數(shù)據(jù)庫擴(kuò)容”進(jìn)行深入討論,并提出一種允許自由規(guī)劃并能避免數(shù)據(jù)遷移和修改路由代碼的Sharding擴(kuò)容方案
Sharding擴(kuò)容——系統(tǒng)維護(hù)不能承受之重
任何Sharding系統(tǒng)俺榆,在上線運(yùn)行一段時(shí)間后感昼,數(shù)據(jù)就會(huì)積累到當(dāng)前節(jié)點(diǎn)規(guī)模所能承載的上限,此時(shí)就需要對(duì)數(shù)據(jù)庫進(jìn)行擴(kuò)容了罐脊,也就是增加新的物理結(jié)點(diǎn)來分?jǐn)倲?shù)據(jù)定嗓。如果系統(tǒng)使用的是基于ID進(jìn)行散列的路由方式,那么團(tuán)隊(duì)需要根據(jù)新的節(jié)點(diǎn)規(guī)模重新計(jì)算所有數(shù)據(jù)應(yīng)處的目標(biāo)Shard萍桌,并將其遷移過去宵溅,這對(duì)團(tuán)隊(duì)來說無疑是一個(gè)巨大的維護(hù)負(fù)擔(dān);而如果系統(tǒng)是按增量區(qū)間進(jìn)行路由(如每1千萬條數(shù)據(jù)或是每一個(gè)月的數(shù)據(jù)存放在一個(gè)節(jié)點(diǎn)上 )上炎,雖然可以避免數(shù)據(jù)的遷移恃逻,卻有可能帶來“熱點(diǎn)”問題雏搂,也就是近期系統(tǒng)的讀寫都集中在最新創(chuàng)建的節(jié)點(diǎn)上(很多系統(tǒng)都有此類特點(diǎn):新生數(shù)據(jù)的讀寫頻率明顯高于舊有數(shù)據(jù)),從而影響了系統(tǒng)性能寇损。面對(duì)這種兩難的處境凸郑,Sharding擴(kuò)容顯得異常困難。
一般來說矛市,“理想”的擴(kuò)容方案應(yīng)該努力滿足以下幾個(gè)要求:
最好不遷移數(shù)據(jù) (無論如何芙沥,數(shù)據(jù)遷移都是一個(gè)讓團(tuán)隊(duì)壓力山大的問題)
允許根據(jù)硬件資源自由規(guī)劃擴(kuò)容規(guī)模和節(jié)點(diǎn)存儲(chǔ)負(fù)載
能均勻的分布數(shù)據(jù)讀寫,避免“熱點(diǎn)”問題
保證對(duì)已經(jīng)達(dá)到存儲(chǔ)上限的節(jié)點(diǎn)不再寫入數(shù)據(jù)
目前浊吏,能夠避免數(shù)據(jù)遷移的優(yōu)秀方案并不多而昨,相對(duì)可行的有兩種,一種是維護(hù)一張記錄數(shù)據(jù)ID和目標(biāo)Shard對(duì)應(yīng)關(guān)系的映射表找田,寫入時(shí)歌憨,數(shù)據(jù)都寫入新擴(kuò)容的Shard,同時(shí)將ID和目標(biāo)節(jié)點(diǎn)寫入映射表墩衙,讀取時(shí)躺孝,先查映射表,找到目標(biāo)Shard后再執(zhí)行查詢底桂。該方案簡(jiǎn)單有效,但是讀寫數(shù)據(jù)都需要訪問兩次數(shù)據(jù)庫惧眠,且映射表本身也極易成為性能瓶頸籽懦。為此系統(tǒng)不得不引入分布式緩存來緩存映射表數(shù)據(jù),但是這樣也無法避免在寫入時(shí)訪問兩次數(shù)據(jù)庫氛魁,同時(shí)大量映射數(shù)據(jù)對(duì)緩存資源的消耗以及專門為此而引入分布式緩存的代價(jià)都是需要權(quán)衡的問題暮顺。另一種方案來自淘寶綜合業(yè)務(wù)平臺(tái)團(tuán)隊(duì),它利用對(duì)2的倍數(shù)取余具有向前兼容的特性(如對(duì)4取余得1的數(shù)對(duì)2取余也是1)來分配數(shù)據(jù)秀存,避免了行級(jí)別的數(shù)據(jù)遷移捶码,但是依然需要進(jìn)行表級(jí)別的遷移,同時(shí)對(duì)擴(kuò)容規(guī)模和分表數(shù)量都有限制或链”鼓眨總得來說,這些方案都不是十分的理想澳盐,多多少少都存在一些缺點(diǎn)祈纯,這也從一個(gè)側(cè)面反映出了Sharding擴(kuò)容的難度。
取長(zhǎng)補(bǔ)短叼耙,兼容并包——一種理想的Sharding擴(kuò)容方案
如前文所述腕窥,Sharding擴(kuò)容與系統(tǒng)采用的路由規(guī)則密切相關(guān):基于散列的路由能均勻地分布數(shù)據(jù),但卻需要數(shù)據(jù)遷移筛婉,同時(shí)也無法避免對(duì)達(dá)到上限的節(jié)點(diǎn)不再寫入新數(shù)據(jù)簇爆;基于增量區(qū)間的路由天然不存在數(shù)據(jù)遷移和向某一節(jié)點(diǎn)無上限寫入數(shù)據(jù)的問題,但卻存在“熱點(diǎn)”困擾。我們?cè)O(shè)計(jì)方案的初衷就是希望能結(jié)合兩種路由規(guī)則的優(yōu)勢(shì)入蛆,摒棄各自的劣勢(shì)响蓉,創(chuàng)造出一種接近“理想”狀態(tài)的擴(kuò)容方式,而這種方式簡(jiǎn)單概括起來就是:全局按增量區(qū)間分布數(shù)據(jù)安寺,使用增量擴(kuò)容厕妖,無數(shù)據(jù)遷移,局部使用散列方式分散數(shù)據(jù)讀寫挑庶,解決“熱點(diǎn)”問題言秸,同時(shí)對(duì)Sharding拓?fù)浣Y(jié)構(gòu)進(jìn)行建模,使用一致的路由算法迎捺,擴(kuò)容時(shí)只需追加節(jié)點(diǎn)數(shù)據(jù)举畸,不再修改散列邏輯代碼。
原理
首先凳枝,作為方案的基石抄沮,為了能使系統(tǒng)感知到Shard并基于Shard的分布進(jìn)行路由計(jì)算,我們需要建立一個(gè)可以描述Sharding拓?fù)浣Y(jié)構(gòu)的編程模型岖瑰。按照一般的切分原則叛买,一個(gè)單一的數(shù)據(jù)庫會(huì)首先進(jìn)行垂直切分,垂直切分只是將關(guān)系密切的表劃分在一起蹋订,我們把這樣分出的一組表稱為一個(gè)Partition率挣。 接下來,如果Partition里的表數(shù)據(jù)量很大且增速迅猛露戒,就再進(jìn)行水平切分椒功,水平切分會(huì)將一張表的數(shù)據(jù)按增量區(qū)間或散列方式分散到多個(gè)Shard上存儲(chǔ)。在我們的方案里智什,我們使用增量區(qū)間與散列相結(jié)合的方式动漾,全局上,數(shù)據(jù)按增量區(qū)間分布荠锭,但是每個(gè)增量區(qū)間并不是按照某個(gè)Shard的存儲(chǔ)規(guī)模劃分的旱眯,而是根據(jù)一組Shard的存儲(chǔ)總量來確定的,我們把這樣的一組Shard稱為一個(gè)ShardGroup证九,局部上键思,也就是一個(gè)ShardGroup內(nèi),記錄會(huì)再按散列方式均勻分布到組內(nèi)各Shard上甫贯。這樣吼鳞,一條數(shù)據(jù)的路由會(huì)先根據(jù)其ID所處的區(qū)間確定ShardGroup,然后再通過散列命中ShardGroup內(nèi)的某個(gè)目標(biāo)Shard叫搁。在每次擴(kuò)容時(shí)赔桌,我們會(huì)引入一組新的Shard供炎,組成一個(gè)新的ShardGroup,為其分配增量區(qū)間并標(biāo)記為“可寫入”疾党,同時(shí)將原有ShardGroup標(biāo)記為“不可寫入”音诫,于是新生數(shù)據(jù)就會(huì)寫入新的ShardGroup,舊有數(shù)據(jù)不需要遷移雪位。同時(shí)竭钝,在ShardGroup內(nèi)部各Shard之間使用散列方式分布數(shù)據(jù)讀寫,進(jìn)而又避免了“熱點(diǎn)”問題雹洗。最后香罐,在Shard內(nèi)部,當(dāng)單表數(shù)據(jù)達(dá)到一定上限時(shí)时肿,表的讀寫性能就開始大幅下滑庇茫,但是整個(gè)數(shù)據(jù)庫并沒有達(dá)到存儲(chǔ)和負(fù)載的上限,為了充分發(fā)揮服務(wù)器的性能螃成,我們通常會(huì)新建多張結(jié)構(gòu)一樣的表旦签,并在新表上繼續(xù)寫入數(shù)據(jù),我們把這樣的表稱為“分段表”(Fragment Table)寸宏。不過宁炫,引入分段表后所有的SQL在執(zhí)行前都需要根據(jù)ID將其中的表名替換成真正的分段表名,這無疑增加了實(shí)現(xiàn)Sharding的難度氮凝,如果系統(tǒng)再使用了某種ORM框架羔巢,那么替換起來可能會(huì)更加困難。目前很多數(shù)據(jù)庫提供一種與分段表類似的“分區(qū)”機(jī)制覆醇,但沒有分段表的副作用,團(tuán)隊(duì)可以根據(jù)系統(tǒng)的實(shí)現(xiàn)情況在分段表和分區(qū)機(jī)制中靈活選擇炭臭∮琅В總之,基于上述切分原理鞋仍,我們將得到如下Sharding拓?fù)浣Y(jié)構(gòu)的領(lǐng)域模型:
圖1. Sharding拓?fù)浣Y(jié)構(gòu)領(lǐng)域模型
在這個(gè)模型中常摧,有幾個(gè)細(xì)節(jié)需要注意:ShardGroup的writable屬性用于標(biāo)識(shí)該ShardGroup是否可以寫入數(shù)據(jù),一個(gè)Partition在任何時(shí)候只能有一個(gè)ShardGroup是可寫的威创,這個(gè)ShardGroup往往是最近一次擴(kuò)容引入的落午;startId和endId屬性用于標(biāo)識(shí)該ShardGroup的ID增量區(qū)間;Shard的hashValue屬性用于標(biāo)識(shí)該Shard節(jié)點(diǎn)接受哪些散列值的數(shù)據(jù)肚豺;FragmentTable的startId和endId是用于標(biāo)識(shí)該分段表儲(chǔ)存數(shù)據(jù)的ID區(qū)間溃斋。
確立上述模型后,我們需要通過配置文件或是在數(shù)據(jù)庫中建立與之對(duì)應(yīng)的表來存儲(chǔ)節(jié)點(diǎn)元數(shù)據(jù)吸申,這樣梗劫,整個(gè)存儲(chǔ)系統(tǒng)的拓?fù)浣Y(jié)構(gòu)就可以被持久化起來享甸,系統(tǒng)啟動(dòng)時(shí)就能從配置文件或數(shù)據(jù)庫中加載出當(dāng)前的Sharding拓?fù)浣Y(jié)構(gòu)進(jìn)行路由計(jì)算了,擴(kuò)容時(shí)只需要向?qū)?yīng)的文件或表中加入相關(guān)的節(jié)點(diǎn)信息重啟系統(tǒng)即可梳侨,不需要修改任何路由邏輯代碼蛉威。
示例
讓我們通過示例來了解這套方案是如何工作的。
階段一:初始上線
假設(shè)某系統(tǒng)初始上線走哺,規(guī)劃為某表提供4000W條記錄的存儲(chǔ)能力蚯嫌,若單表存儲(chǔ)上限為1000W條,單庫存儲(chǔ)上限為2000W條丙躏,共需2個(gè)Shard择示,每個(gè)Shard包含兩個(gè)分段表,ShardGroup增量區(qū)間為0-4000W彼哼,按2取余分散到2個(gè)Shard上对妄,具體規(guī)劃方案如下:
圖2. 初始4000W存儲(chǔ)規(guī)模的規(guī)劃方案
與之相適應(yīng),Sharding拓?fù)浣Y(jié)構(gòu)的元數(shù)據(jù)如下:
圖3. 對(duì)應(yīng)Sharding元數(shù)據(jù)
階段二:系統(tǒng)擴(kuò)容
經(jīng)過一段時(shí)間的運(yùn)行敢朱,當(dāng)原表總數(shù)據(jù)逼近4000W條上限時(shí)剪菱,系統(tǒng)就需要擴(kuò)容了。為了演示方案的靈活性拴签,我們假設(shè)現(xiàn)在有三臺(tái)服務(wù)器Shard2孝常、Shard3、Shard4蚓哩,其性能和存儲(chǔ)能力表現(xiàn)依次為Shard2<Shard3<Shard4构灸,我們安排Shard2儲(chǔ)存1000W條記錄,Shard3儲(chǔ)存2000W條記錄岸梨,Shard4儲(chǔ)存3000W條記錄喜颁,這樣,該表的總存儲(chǔ)能力將由擴(kuò)容前的4000W條提升到10000W條曹阔,以下是詳細(xì)的規(guī)劃方案:
圖4. 二次擴(kuò)容6000W存儲(chǔ)規(guī)模的規(guī)劃方案
相應(yīng)拓?fù)浣Y(jié)構(gòu)表數(shù)據(jù)下:
圖5. 對(duì)應(yīng)Sharding元數(shù)據(jù)
從這個(gè)擴(kuò)容案例中我們可以看出該方案允許根據(jù)硬件情況進(jìn)行靈活規(guī)劃半开,對(duì)擴(kuò)容規(guī)模和節(jié)點(diǎn)數(shù)量沒有硬性規(guī)定,是一種非常自由的擴(kuò)容方案赃份。
增強(qiáng)
接下來讓我們討論一個(gè)高級(jí)話題:對(duì)“再生”存儲(chǔ)空間的利用寂拆。對(duì)于大多數(shù)系統(tǒng)來說,歷史數(shù)據(jù)較為穩(wěn)定抓韩,被更新或是刪除的概率并不高纠永,反映到數(shù)據(jù)庫上就是歷史Shard的數(shù)據(jù)量基本保持恒定,但也不排除某些系統(tǒng)其數(shù)據(jù)有同等的刪除概率谒拴,甚至是越老的數(shù)據(jù)被刪除的可能性越大尝江,這樣反映到數(shù)據(jù)庫上就是歷史Shard隨著時(shí)間的推移,數(shù)據(jù)量會(huì)持續(xù)下降英上,在經(jīng)歷了一段時(shí)間后茂装,節(jié)點(diǎn)就會(huì)騰出很大一部分存儲(chǔ)空間怠蹂,我們把這樣的存儲(chǔ)空間叫“再生”存儲(chǔ)空間,如何有效利用再生存儲(chǔ)空間是這些系統(tǒng)在設(shè)計(jì)擴(kuò)容方案時(shí)需要特別考慮的少态〕遣啵回到我們的方案,實(shí)際上我們只需要在現(xiàn)有基礎(chǔ)上進(jìn)行一個(gè)簡(jiǎn)單的升級(jí)就可以實(shí)現(xiàn)對(duì)再生存儲(chǔ)空間的利用彼妻,升級(jí)的關(guān)鍵就是將過去ShardGroup和FragmentTable的單一的ID區(qū)間提升為多重ID區(qū)間嫌佑。為此我們把ShardGroup和FragmentTable的ID區(qū)間屬性抽離出來,分別用ShardGroupInterval和FragmentTableIdInterval表示侨歉,并和它們保持一對(duì)多關(guān)系屋摇。
[圖片上傳中。幽邓。炮温。(11)]
圖6. 增強(qiáng)后的Sharding拓?fù)浣Y(jié)構(gòu)領(lǐng)域模型
讓我們還是通過一個(gè)示例來了解升級(jí)后的方案是如何工作的。
階段三:不擴(kuò)容牵舵,重復(fù)利用再生存儲(chǔ)空間
假設(shè)系統(tǒng)又經(jīng)過一段時(shí)間的運(yùn)行之后柒啤,二次擴(kuò)容的6000W條存儲(chǔ)空間即將耗盡,但是由于系統(tǒng)自身的特點(diǎn)畸颅,早期的很多數(shù)據(jù)被刪除担巩,Shard0和Shard1又各自騰出了一半的存儲(chǔ)空間,于是ShardGroup0總計(jì)有2000W條的存儲(chǔ)空間可以重新利用没炒。為此涛癌,我們重新將ShardGroup0標(biāo)記為writable=true,并給它追加一段ID區(qū)間:10000W-12000W送火,進(jìn)而得到如下規(guī)劃方案:
圖7. 重復(fù)利用2000W再生存儲(chǔ)空間的規(guī)劃方案
相應(yīng)拓?fù)浣Y(jié)構(gòu)的元數(shù)據(jù)如下:
圖8. 對(duì)應(yīng)Sharding元數(shù)據(jù)
小結(jié)
這套方案綜合利用了增量區(qū)間和散列兩種路由方式的優(yōu)勢(shì)拳话,避免了數(shù)據(jù)遷移和“熱點(diǎn)”問題,同時(shí)种吸,它對(duì)Sharding拓?fù)浣Y(jié)構(gòu)建模弃衍,使用了一致的路由算法,從而避免了擴(kuò)容時(shí)修改路由代碼骨稿,是一種理想的Sharding擴(kuò)容方案笨鸡。