數據庫分庫分表(sharding)(二) 全局主鍵生成策略

第一部分:一些常見的主鍵生成策略


一旦數據庫被切分到多個物理結點上,我們將不能再依賴數據庫自身的主鍵生成機制。一方面,某個分區數據庫自生成的ID無法保證在全局上是唯一的;另一方面,應用程序在插入數據之前需要先獲得ID,以便進行SQL路由。目前幾種可行的主鍵生成策略有:
1. UUID:使用UUID作主鍵是最簡單的方案,但是缺點也是非常明顯的。由於UUID非常的長,除佔用大量存儲空間外,最主要的問題是在索引上,在建立索引和基於索引進行查詢時都存在性能問題。
2. 結合數據庫維護一個Sequence表:此方案的思路也很簡單,在數據庫中建立一個Sequence表,表的結構類似於:

[sql] view plain copy
  1. CREATE TABLE `SEQUENCE` (  
  2.     `tablename` varchar(30) NOT NULL,  
  3.     `nextid` bigint(20) NOT NULL,  
  4.     PRIMARY KEY (`tablename`)  
  5. ) ENGINE=InnoDB   
每當需要爲某個表的新紀錄生成ID時就從Sequence表中取出對應表的nextid,並將nextid的值加1後更新到數據庫中以備下次使用。此方案也較簡單,但缺點同樣明顯:由於所有插入任何都需要訪問該表,該表很容易成爲系統性能瓶頸,同時它也存在單點問題,一旦該表數據庫失效,整個應用程序將無法工作。有人提出使用Master-Slave進行主從同步,但這也只能解決單點問題,並不能解決讀寫比爲1:1的訪問壓力問題。

除此之外,還有一些方案,像對每個數據庫結點分區段劃分ID,以及網上的一些ID生成算法,因爲缺少可操作性和實踐檢驗,本文並不推薦。實際上,接下來,我們要介紹的是Fickr使用的一種主鍵生成方案,這個方案是目前我所知道的最優秀的一個方案,並且經受了實踐的檢驗,可以爲大多數應用系統所借鑑。


第二部分:一種極爲優秀的主鍵生成策略


flickr開發團隊在2010年撰文介紹了flickr使用的一種主鍵生成測策略,同時表示該方案在flickr上的實際運行效果也非常令人滿意,原文連接:Ticket Servers: Distributed Unique Primary Keys on the Cheap 這個方案是我目前知道的最好的方案,它與一般Sequence表方案有些類似,但卻很好地解決了性能瓶頸和單點問題,是一種非常可靠而高效的全局主鍵生成方案。


圖1. flickr採用的sharding主鍵生成方案示意圖(點擊查看大圖)


flickr這一方案的整體思想是:建立兩臺以上的數據庫ID生成服務器,每個服務器都有一張記錄各表當前ID的Sequence表,但是Sequence中ID增長的步長是服務器的數量,起始值依次錯開,這樣相當於把ID的生成散列到了每個服務器節點上。例如:如果我們設置兩臺數據庫ID生成服務器,那麼就讓一臺的Sequence表的ID起始值爲1,每次增長步長爲2,另一臺的Sequence表的ID起始值爲2,每次增長步長也爲2,那麼結果就是奇數的ID都將從第一臺服務器上生成,偶數的ID都從第二臺服務器上生成,這樣就將生成ID的壓力均勻分散到兩臺服務器上,同時配合應用程序的控制,當一個服務器失效後,系統能自動切換到另一個服務器上獲取ID,從而保證了系統的容錯。

關於這個方案,有幾點細節這裏再說明一下:

1. flickr的數據庫ID生成服務器是專用服務器,服務器上只有一個數據庫,數據庫中表都是用於生成Sequence的,這也是因爲auto-increment-offset和auto-increment-increment這兩個數據庫變量是數據庫實例級別的變量。
2. flickr的方案中表格中的stub字段只是一個char(1) NOT NULL存根字段,並非表名,因此,一般來說,一個Sequence表只有一條紀錄,可以同時爲多張表生成ID,如果需要表的ID是有連續的,需要爲該表單獨建立Sequence表

3. 方案使用了MySQL的LAST_INSERT_ID()函數,這也決定了Sequence表只能有一條記錄。
4. 使用REPLACE INTO插入數據,這是很討巧的作法,主要是希望利用mysql自身的機制生成ID,不僅是因爲這樣簡單,更是因爲我們需要ID按照我們設定的方式(初值和步長)來生成。

5. SELECT LAST_INSERT_ID()必須要於REPLACE INTO語句在同一個數據庫連接下才能得到剛剛插入的新ID,否則返回的值總是0
6. 該方案中Sequence表使用的是MyISAM引擎,以獲取更高的性能,注意:MyISAM引擎使用的是表級別的鎖,MyISAM對錶的讀寫是串行的,因此不必擔心在併發時兩次讀取會得到同一個ID(另外,應該程序也不需要同步,每個請求的線程都會得到一個新的connection,不存在需要同步的共享資源)。經過實際對比測試,使用一樣的Sequence表進行ID生成,MyISAM引擎要比InnoDB表現高出很多!

7. 可使用純JDBC實現對Sequence表的操作,以便獲得更高的效率,實驗表明,即使只使用spring JDBC性能也不及純JDBC來得快!


實現該方案,應用程序同樣需要做一些處理,主要是兩方面的工作:


1. 自動均衡數據庫ID生成服務器的訪問
2. 確保在某個數據庫ID生成服務器失效的情況下,能將請求轉發到其他服務器上執行。



一、sharding邏輯的實現層面


從一個系統的程序架構層面來看,sharding邏輯可以在DAO層、JDBC API層、介於DAO與JDBC之間的spring數據訪問封裝層(各種spring的template)以及介於應用服務器與數據庫之間的sharding代理服務器四個層面上實現。



圖1. Sharding實現層面與相關框架/產品

  • 在DAO層實現

當團隊決定自行實現sharding的時候,DAO層可能是嵌入sharding邏輯的首選位置,因爲在這個層面上,每一個DAO的方法都明確地知道需要訪問的數據表以及查詢參數,藉助這些信息可以直接定位到目標shard上,而不必像框架那樣需要對SQL進行解析然後再依據配置的規則進行路由。另一個優勢是不會受ORM框架的制約。由於現在的大多數應用在數據訪問層上會依賴某種ORM框架,而多數的shrading框架往往無法支持或只能支持一種orm框架,這使得在選擇和應用框架時受到了很大的制約,而自行實現sharding完全沒有這方面的問題,甚至不同的shard使用不同的orm框架都可以在一起協調工作。比如現在的Java應用大多使用hibernate,但是當下還沒有非常令人滿意的基於hibernate的sharding框架,(關於hibernate hards會在下文介紹),因此很多團隊會選擇自行實現sharding。

 

簡單總結一下,在DAO層自行實現sharding的優勢在於:不受ORM框架的制約、實現起來較爲簡單、易於根據系統特點進行靈活的定製、無需SQL解析和路由規則匹配,性能上表現會稍好一些;劣勢在於:有一定的技術門檻,工作量比依靠框架實現要大(反過來看,框架會有學習成本)、不通用,只能在特定系統裏工作。當然,在DAO層同樣可以通過XML配置或是註解將sharding邏輯抽離到“外部”,形成一套通用的框架. 不過目前還沒有出現此類的框架。

  • 在ORM框架層實現
在ORM框架層實現sharding有兩個方向,一個是在實現O-R Mapping的前提下同時提供sharding支持,從而定位爲一種分佈式的數據訪問框架,這一類類型的框架代表就是guzz另一個方向是通過對既有ORM框架進行修改增強來加入sharding機制。此類型的代表產品是hibernate shard. 應該說以hibernate這樣主流的地位,行業對於一款面向hibernate的sharding框架的需求是非常迫切的,但是就目前的hibernate shards來看,表現還算不上令人滿意,主要是它對使用hibernate的限制過多,比如它對HQL的支持就非常有限。在mybatis方面,目前還沒有成熟的相關框架產生。有人提出利用mybatis的插件機制實現sharding,但是遺憾的是,mybatis的插件機制控制不到多數據源的連接層面,另一方面,離開插件層又失去了對sql進行集中解析和路由的機會,因此在mybatis框架上,目前還沒有可供借鑑的框架,團隊可能要在DAO層或Spring模板類上下功夫了。
  • 在JDBC API層實現
JDBC API層是很多人都會想到的一個實現sharding的絕佳場所,如果我們能提供一個實現了sharding邏輯的JDBC API實現,那麼sharding對於整個應用程序來說就是完全透明的,而這樣的實現可以直接作爲通用的sharding產品了。但是這種方案的技術門檻和工作量顯然不是一般團隊能做得來的,因此基本上沒有團隊會在這一層面上實現sharding,甚至也沒有此類的開源產品。筆者知道的只有一款商業產品dbShards採用的是這一方案。
  • 在介於DAO與JDBC之間的Spring數據訪問封裝層實現
在springd大行其道的今天,幾乎沒有哪個java平臺上構建的應用不使用spring,在DAO與JDBC之間,spring提供了各種template來管理資源的創建與釋放以及與事務的同步,大多數基於spring的應用都會使用template類做爲數據訪問的入口,這給了我們另一個嵌入sharding邏輯的機會,就是通過提供一個嵌入了sharding邏輯的template類來完成sharding工作.這一方案在效果上與基於JDBC API實現的方案基本一致,同樣是對上層代碼透明,在進行sharding改造時可以平滑地過度,但它的實現卻比基於JDBC API的方式簡單,因此成爲了不少框架的選擇,阿里集團研究院開源的Cobar Client就是這類方案的一種實現。
  • 在應用服務器與數據庫之間通過代理實現
在應用服務器與數據庫之間加入一個代理,應用程序向數據發出的數據請求會先通過代理,代理會根據配置的路由規則,對SQL進行解析後路由到目標shard,因爲這種方案對應用程序完全透明,通用性好,所以成爲了很多sharding產品的選擇。在這方面較爲知名的產品是mysql官方的代理工具:Mysql Proxy和一款國人開發的產品:amoeba。mysql proxy本身並沒有實現任何sharding邏輯,它只是作爲一種面向mysql數據庫的代理,給開發人員提供了一個嵌入sharding邏輯的場所,它使用lua作爲編程語言,這對很多團隊來說是需要考慮的一個問題。amoeba則是專門實現讀寫分離與sharding的代理產品,它使用非常簡單,不使用任何編程語言,只需要通過xml進行配置。不過amoeba不支持事務(從應用程序發出的包含事務信息的請求到達amoeba時,事務信息會被抹去,因此,即使是單點數據訪問也不會有事務存在)一直是個硬傷。當然,這要看產品的定位和設計理念,我們只能說對於那些對事務要求非常高的系統,amoeba是不適合的。

二、使用框架還是自主開發?

前面的討論中已經羅列了很多開源框架與產品,這裏再整理一下:基於代理方式的有MySQL Proxy和Amoeba,基於Hibernate框架的是Hibernate Shards,通過重寫spring的ibatis template類是Cobar Client,這些框架各有各的優勢與短板,架構師可以在深入調研之後結合項目的實際情況進行選擇,但是總的來說,我個人對於框架的選擇是持謹慎態度的。一方面多數框架缺乏成功案例的驗證,其成熟性與穩定性值得懷疑。另一方面,一些從成功商業產品開源出框架(如阿里和淘寶的一些開源項目)是否適合你的項目是需要架構師深入調研分析的。當然,最終的選擇一定是基於項目特點、團隊狀況、技術門檻和學習成本等綜合因素考量確定的。
發佈了75 篇原創文章 · 獲贊 39 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章