之因此編寫Kratos其實存在一個小插曲,當筆者滿山遍野尋找成熟、穩定、高性能的Sharding中間件時,確實是翻山越嶺,只不過始終沒有找到一款合適筆者項目場景的中間件產品。依稀記得當年第一款使用的Sharding中間件就是淘寶的TDDL3.0,只惋惜如今拿不到源碼。而其它的中間件,大多都是基於Proxy的,相信作過度布式系統的人都知道,拋開網絡消耗所帶來的性能問題不談,多一個外圍系統依賴就意味着須要多增長和承擔一份風險,所以與應用集成的方式,則成爲筆者選擇的首要條件。固然不是說基於Proxy方式的Sharding中間件很差,只能說我我的並不看重通用型需求,僅此而已。其實目前社區很是活躍的MyCat,筆者在這裏要批評一下,既然開源,那麼就請在GitHub上貼上使用手冊,而不是配置手冊,由於對於一個任何一個DVP而言,他要的是迅速上手的幫助文檔,而不是要使用大家的開源產品還須要在淘寶上購買幾十塊一本的網絡書,對於這一點,我很是鄙視和厭惡。java
許多開發人員動不動就大談分庫分表來顯示本身的成就感,那麼筆者須要作的事情就是將其拉下神壇,讓你們切切實實感覺到一款平民化技術所帶來的親民性。做爲一款數據路由工具,Kratos採用與應用集成的方式爲開發人員帶來超強的易用性。就目前而言,筆者測試環境上,物理環境選用爲32庫和1024表的庫內分片模式,所以筆者就先沒必要王婆賣瓜,自賣自詡其所謂的高性能和高穩定。至於你是否可以從Kratos中獲益,取決於你是否想用最簡單,最易上手的方式來解決分庫分表場景下的數據路由工做。mysql
筆者此篇博文,並非宣導和普及分庫分表理論,而是做爲Kratos貨真價實的Sharding權威指南手冊呈現給你們。目前Kratos1.3.0版本已經在Github上開源,地址爲:https://github.com/gaoxianglong/kratos。git
目錄github
1、Kratos簡介;正則表達式
2、互聯網當下的數據拆分過程;算法
3、目前市面上常見的一些Sharding中間件產品對比;sql
4、Kratos的架構原型;數據庫
5、動態數據源層的Master/Slave讀寫分離;express
6、Kratos的Sharding模型;網絡
7、Sharding之庫內分片;
8、Sharding之一庫一片;
9、自動生成全局惟一的sequenceId;
10、自動生成kratos分庫分表配置文件;
11、注意事項;
1、Kratos簡介
由於找不到合適的Shading中間件,其次一些開源的Shading中間件動不動就幾萬行的代碼真有必要嗎?所以誕生了編寫本身中間件的想發。Kratos這個名字來源於筆者以前在PS3平臺玩的一款ACT遊戲《戰神3》中嗜血殺神的主角奎爺Kratos,儘管筆者的Kratos並無展示出秒殺其餘Sharding中間件的霸氣,但Kratos要作的事情很純粹,僅僅就只是處理分庫分表場景下的數據路由工做,它處於數據路由層,介於持久層與JDBC之間,所以沒有其它看似有用實則花哨的雞肋功能,而且與應用層集成的方式註定了Kratos必然擁有更好的易用性。
對於開發人員而言,在使用Kratos的時候,就好像是在操做同一個數據源同樣,也就是說,以前你的sql怎麼寫,換上Kratos以後,業務邏輯無需作出變更,你只須要關心你的邏輯便可,數據路由工做,你徹底能夠放心的交由Kratos去處理。Kratos站在巨人的肩膀上,必然擁有更大的施展空間。首先Kratos依賴於Spring JDBC,那麼若是你習慣使用JdbcTemplate,那就都不是問題,反之你能夠先看一下筆者的博文《筆者帶你剖析Spring3.x JDBC》。其次Kratos目前僅僅只支持Mysql數據庫,對於其餘RDBMS類型的數據庫,基本上之後也不會支持,簡單來講,通用型需求並非Kratos的目標,作好一件事纔是真正的實惠。
kratos的優勢:
一、動態數據源的無縫切換;
二、master/slave一主一從讀寫分離;
三、單線程讀重試(取決於的數據庫鏈接池是否支持);
四、單獨且友好支持Mysql數據庫,不支持其它RDBMS庫;
五、非Proxy架構,與應用集成,應用直連數據庫,下降外圍系統依賴帶來的down機風險;
六、使用簡單,侵入型低,站在巨人的肩膀上,依賴於Spring JDBC;
七、不作真正意義上的Sql解析任務,規避Sql解析過程當中由詞法解析、語法解析、語義解析等操做所帶來的
性能延遲,僅用正則表達式解析 片名和Route條件,解析過程僅耗時約<=1ms;
八、分庫分表路由算法支持2類4種分片模式,庫內分片/一庫一片;
九、提供自動生成sequenceId的API支持;
十、提供自動生成配置文件的支持,下降配置出錯率;
十一、目標和職責定位明確,僅專一於Sharding,不支持其它多餘或雞肋功能、無需兼容通用性,所以核心代
碼量少、易讀易維護
2、互聯網當下的數據拆分過程
對於一個剛上線的互聯網項目來講,因爲前期活躍度並不大,併發量相對較小,所以企業通常都會選擇將全部數據存放在一個物理DB中進行讀寫操做。但隨着後續的市場推廣力度不斷增強,活躍度不斷提高,這時若是僅靠一個DB來支撐全部讀寫壓力,就會顯得力不從心。因此通常到了這個階段,大部分Mysql DBA就會將數據庫設置爲讀寫分離狀態(一主一從/一主多從),Master負責寫操做,而Slave負責讀操做。按照二八定律,80%的操做更可能是讀操做,那麼剩下的20%則爲寫操做,通過讀寫分離後,大大提高了單庫沒法支撐的負載壓力。不過光靠讀寫分離並不會一勞永逸,若是活躍度再次提高,相信又會再次碰見數據庫的讀寫瓶頸。所以到了這個階段,就須要實現垂直分庫。
所謂垂直分庫,簡單來講就是根據業務的不一樣將本來冗餘在單庫中的業務表拆散,分佈到不一樣的業務庫中,實現分而治之的讀寫訪問操做。固然咱們都知道,傳統RDBMS數據庫中的數據表隨着數據量的暴增,從維護性和高響應的角度去看,不管任何CRUD操做,對於數據庫而言,都是一件極其傷腦筋的事情。即使設置了索引,檢索效率依然低下,由於隨着數據量的暴增,RDBMS數據庫的性能瓶頸就會逐漸顯露出來。這一點,Nosql數據庫卻是作得很好,固然架構不一樣,因此不予比較。那麼既然已經到了這個節骨眼上了,惟一的殺手鐗就是在垂直分庫的基礎之上進行水平分區,也就是咱們常說的Sharding。
簡單來講,水平分區要作的事情,就是將本來冗餘在單庫中的業務表分散爲N個子表(好比tab_000一、tab_000二、tab_000三、tab_0004...)分別存放在不一樣的子庫中。理論上來說,子表之間經過某種契約關聯在一塊兒,每一張子表均按段位進行數據存儲,好比tab_0000存儲1-10000的數據,而tab_0001存儲10001-20000的數據。通過水平分區後,必然可以將本來由一張業務表維護的海量數據分配給N個子表進行讀寫操做和維護,大大提高了數據庫的讀寫性能,下降了性能瓶頸。基於分庫分表的設計,目前在國內一些大型網站中應用的很是廣泛。
固然一旦實現分庫分表後,將會牽扯到5個很是關鍵的問題,以下所示:
一、單機ACID被打破,分佈式事務一致性難以保證;
二、數據查詢須要進行跨庫跨表的數據路由;
三、多表之間的關聯查詢會有影響;
四、單庫中依賴於主鍵序列自增時生成的惟一ID會有影響;
五、強外鍵(外鍵約束)難以支持,僅能考慮弱外鍵(約定);
3、目前市面上常見的一些Sharding中間件產品對比
其實目前市面上的分庫分表產品不在少數,可是這類產品,更多的是基於Proxy架構的方式,在對於不看重通用性的前提下,基於應用集成架構的中間件則只剩下淘寶的TDDL和Mysql官方的Fabric。其實筆者仍是很是喜歡TDDL的,Kratos中所支持的庫內分片就是效仿的TDDL,相信你們也看得出來筆者對TDDL的感情。可是TDDL並不是是絕對完美的,其弊端一樣明顯,好比:社區推動力度緩慢、文檔資料匱乏、過多的功能、外圍系統依賴,再加上致命傷非開源,所以註定了TDDL沒法爲欣賞它的非淘寶系用戶服務。而Fabric,筆者接觸的太少了,而且正式版發行時間仍是過短了,所以就不想當小白鼠去試用,避免出現hold不住的狀況。目前常見的一些Shardig中間件產品對比,如圖A-1所示:
圖A-1 常見的Shading中間件對比
在基於Proxy架構的Sharding中間件中,大部分的產品基本上都是衍生子Cobar,而且這類產品對分片算法的支持都僅限於一庫一片的分片方式。對於庫內分片和一庫一片等各自的優缺點,筆者稍後會進行講解。具體使用什麼樣的中間件產品,還須要根據具體的應用場景而定,固然若是是你正在愁找不到完善的使用手冊、配置手冊之類的文檔資料,Kratos你能夠優先考慮。
4、Kratos的架構原型
簡單來講,分庫分表中間件無非就是根據Sharding算法對持有的多數據源進行動態切換,這是任何Sharding中間件最核心的部分。一旦在程序中使用Kratos後,應用層將會持有N個數據源,Kratos經過路由條件進行運算,而後經過Route技術對數據庫和數據表進行讀寫操做。在此你們須要注意,Kratos內部並無實現本身的ConnectionPool,這也就意味着,給了開發人員極大的自由,使之能夠隨意切換任意的ConnectionPool產品,好比你以爲C3P0沒有BonePC性能高,那麼你能夠切換爲BonePC。
對於開發人員而言,你並不須要關心底層的數據庫和表的劃分規則,程序中任何的CRUD操做,都像是在操做同一個數據庫同樣,而且讀寫效率還不可以比以前低太多(好比幾毫秒或者實際毫秒以內完成),而Kratos就承擔着這樣的一個任務。Kratos所處的領域模型定位,如圖A-2所示:
圖A-2 Kratos的領域模型定位
如圖A-2所示,Kratos的領域模型定位處於持久層和JDBC之間。以前筆者曾經說起過,Kratos是站在巨人的肩膀上,這個巨人正是Spring。簡單來講,Kratos重寫了JdbcTemplate,並使用了Spring提供的AbstractRoutingDataSource做爲動態數據源層。所以從另一個側面反應了Kratos的源碼註定是簡單、輕量、易閱讀、易維護的,由於Kratos更多的關注點只會停留在Master/Slave讀寫分離層和分庫分表層。咱們知道通常的Shading中間件,動不動就幾萬行代碼,其中得「貓膩」有不少,不只數據庫鏈接池要本身寫、動態數據源要本身寫,再加上一些雜七雜八的功能,好比:通用性支持、多種類型的RDBMS或者Nosql支持,那麼代碼天然冗餘,可讀性極差。在Kratos中,這些都問題徹底會「滾犢子」,由於Kratos只會考慮如何經過Sharding規則實現數據路由。Kratos的3層架構,如圖A-3所示:
圖A-3 Kratos的3層架構
既然Kratos只考慮最核心的功能,同時也就意味着它的性能恆定指標還須要結合其餘第三方產品,好比Kratos的動態數據源層所使用的ConnectionPool爲C3P0,儘管很是穩定的,可是性能遠遠比不上使用BonePC,所以你們徹底能夠將Kratos看作一個高效的黏合劑,它的核心任務就是數據路由,你別期望Kratos還能爲你處理邊邊角角的零碎雜事,想要什麼效果,自行組合配置,這就是Kratos,一個簡單、輕量級的Sharding中間件。Kratos的應用整體架構,如圖A-4所示:
圖A-4 Kratos的應用整體架構
5、動態數據源層的Master/Slave讀寫分離
當你們對Kratos有了一個基本的瞭解後,那麼接下來咱們就來看看如何在程序中使用Kratos。com.gxl.kratos.jdbc.core.KratosJdbcTemplate是Kratos提供的一個Jdbc模板,它繼承自Spring的JdbcTemplate。簡單來講,KratosJdbcTemplate幾乎支持JdbcTemplate的全部方法(除批量操做外)。對於開發人員而言,只須要將JdbcTemplate替換爲KratosJdbcTemplate便可,除此以外,程序中沒有其餘任何須要進行修改的地方,這種低侵入性相信你們都應該可以接受。
通常來講,數據庫的主從配置,既能夠一主一從,也能夠一主多從,但目前Kratos僅支持一主一從。接下來咱們再來看看如何在配置文件中配置一主一從的讀寫分離操做,以下所示:
上述程序實例中,com.gxl.kratos.jdbc.datasource.config.KratosDatasourceGroup就是一個用於管理多數據源的Group,它繼承自Spring提供的AbstractRoutingDataSource,並充當了動態數據源層的角色,由此基礎之上實現DBRoute。咱們能夠看見,在<entry/>標籤中,key屬性指定了數據源索引,而value-ref屬性指定了數據源,經過這種簡單的鍵值對關係就能夠明確的切換到具體的目標數據源上。
在com.gxl.kratos.jdbc.core.KratosJdbcTemplate中,isShard屬性實際上就是一個Sharding開關,缺省爲false,也就意味着缺省是沒有開啓分庫分表的,那麼在不Sharding的狀況下,咱們依然可使用Kratos來完成讀寫分離操做。在wr_weight屬性中定義了讀寫分離的權重索引,也就是說,咱們有多少個主庫,就必定須要有多少個從庫,好比主庫有1個,從庫也應該是1個,所以KratosDatasourceGroup中持有的數據源個數就應該一共是2個,索引從0-1,若是主庫的索引爲0,那麼從庫的索引就應該爲1,也就是「r1w0」。當配置完成後,一旦Kratos監測到執行的操做爲寫操做時,就會自動切換爲主庫的數據源,而當操做爲讀操做的時候,就會自動切換爲從庫的數據源,從而實現一主一從的讀寫分離操做。
6、Kratos的Sharding模型
目前市面上的Sharding中間的分庫分表算法有2種最多見的類型,分別是庫內分片和一庫一片。筆者先從庫內分片開始談起,而且會在後續進行比較這2種分片算法的優劣勢,讓你們更好的進行選擇使用。
庫內分片是TDDL採用的一種分片算法,這種分片算法相對來講較爲複雜,由於不只須要根據路由條件計算出數據應該落盤到哪個庫,還須要計算須要落盤到哪個子表中。好比咱們生產上有32個庫,數據庫表有1024張,那麼平均每一個庫中存放的子表數量就應該是32個。庫內分片算法示例,如圖A-5所示:
圖A-5 庫內分片
一庫一片目前是很是常見的一種分片算法,它同時具有了簡單性和易維護性等特色。簡單來講,一旦經過路由算法計算出數據須要落盤到哪個庫後,就等於間接的計算出了須要落盤到哪個子表下。假設咱們有1024個子表,那麼對應的數據庫也應該是1024個,當計算出數據須要落盤到第105個庫的時候,天然子表也就是第105個。一庫一片算法示例,如圖A-6所示:
圖A-6 一庫一片
那麼咱們究竟在生產中應該選擇庫內分片仍是一庫一片呢?儘管Kratos這2種分片算法都同時支持,但生產上所使用的分片算法最好是統一的,千萬別一個子系統的分片算法使用的庫內分片,而另一個子系統使用的是一庫一片,由於這樣對於DBA來講將會極其痛苦,不只維護極其困難,數據遷移也是一個頭痛的問題。筆者建議優先考慮一庫一片這種分片方式,由於這種分片方式維護更簡單,而且在後期數據庫擴容時,數據遷移工做更加容易和簡單,畢竟算出來庫就等於算出來表,遷移越簡單就意味着生產上停應用的時間更短。固然究竟應該選擇哪種分片算法,還須要你自行考慮抉擇。
7、Sharding之庫內分片
以前筆者已經說起過,Kratos的低侵入性設計只須要將本來的JdbcTemplate替換爲KratosJdbcTemplate便可,除此以外,程序要不須要修改任何地方,由於讀寫分離操做、分庫分表操做都僅僅只是在配置文件中進行配置的,無需耦合在業務邏輯中。庫內分片的配置,以下所示:
上述程序示例中,筆者使用的是一主一從讀寫分離+庫內分片模式。主庫一共是32個(1024個子表,每一個庫包含子表數爲32個),那麼天然從庫也就是32個,在KratosDatasourceGroup中一共會持有64個數據源,數據源索引爲0-63。那麼在KratosJdbcTemplate中首先要作的事情是將分庫分片開關打開,而後讀寫權重索引wr_weight屬性的比例是「r32w0」,這也就意味着0-31都是主庫,而32-63都是從庫,Kratos會根據這個權重索引來自動根據所執行的操做切換到對應的主從數據源上。屬性shardMode其實就是指明瞭須要Kratos使用哪種分片算法,true爲一庫一片,而false則爲庫內分片。
屬性dbRuleArray指明瞭分庫規則,「#userinfo_id|email_hash# % 1024 / 32」指明瞭路由條件可能包括兩個,分別爲userinfo_id和email_hash。而後根據路由條件先%tbSize,最後在/dbSize,便可計算出具體的數據源。在此你們須要注意,庫的倍數必定要是表的數量,不然數據將沒法均勻分佈到全部的子表上。或許你們有個疑問,爲何路由條件會有多個呢?這是由於在實際的開發過程當中,咱們全部的查詢條件都須要根據路由條件來,而且實際狀況不可能只有一個理由條件,甚至有可能更多(好比反向索引表)。所以經過符號「|」分隔開多個路由條件。當一條sql執行時,Kratos會匹配sql條件中的第一個數據庫參數字段是不是分庫分表條件,若是不是則會拋出異常com.gxl.kratos.jdbc.exception.ShardException。分表規則「#userinfo_id|email_hash# % 1024 % 32」其實大體和分庫規則同樣,只不過最後並不是是/dbSize,而是%dbSize。通過分庫分表算法後,一條sql就會被解析並落盤到指定的庫和指定的表中。
8、Sharding之一庫一片
一庫一片相似於庫內分片的配置,但又細微的不一樣,以前筆者曾經說起過,使用一庫一片算法後,根據路由條件計算出庫後,就等於間接計算出表,那麼配置文件中就只需配置分庫規則便可。一庫一片的配置,以下所示:
上述程序示例中,筆者使用的是一主一從讀寫分離+一庫一片模式。主庫一共是32個(32個子表,每一個庫包含子表數爲1個),那麼天然從庫也就是32個,在KratosDatasourceGroup中一共會持有64個數據源,數據源索引爲0-63。權重索引wr_weight屬性的比例是「r32w0」,這也就意味着0-31都是主庫,而32-63都是從庫,Kratos會根據這個權重索引來自動根據所執行的操做切換到對應的主從數據源上。屬性shardMode其實就是指明瞭須要Kratos使用哪種分片算法,true爲一庫一片,而false則爲庫內分片。
屬性dbRuleArray指明瞭分庫規則,「#userinfo_id|email_hash# % 32」指明瞭路由條件可能包括兩個,分別爲userinfo_id和email_hash。而後根據路由條件%dbSize便可計算出數據究竟應該落盤到哪個庫的哪個子表下。
9、自動生成全局惟一的sequenceId
一旦咱們分庫分表後,本來單庫中使用的序列自增Id將沒法再繼續使用,那麼這應該怎麼辦呢?其實解決這個問題並不困難,目前有2種方案,第一種是全部的應用統一調用一個集中式的Id生成器,另一種則是每一個應用集成一個Id生成器。不管選擇哪種方案,Id生成器所持有的DB都只有一個,也就是說,經過一個通用DB去管理和生成全局覺得的sequenceId。
目前市面上幾乎全部的Sharding中間件都沒有提供sequenceId的支持,而Kratos的工具包中卻爲你們提供了支持。Kratos選擇的是每一個應用都集成一個Id生成器,而沒有采用集中式Id生成器,由於在分佈式場景下,多一個外圍系統依賴就意味着多一分風險,相信這個道理你們都應該懂。那麼究竟應該如何使用Kratos提供的Id生成器呢?首先咱們須要單獨準備一個全局DB出來,而後使用Kratos的建表語句,以下所示:
當成功創建好生成sequenceId所須要的數據庫表後,接下來要作的事情就是進行調用。生成sequenceId,以下所示:
上述程序示例中,首先須要調用com.gxl.kratos.utils.sequence.DbConnectionManager類的init()方法對數據源進行初始化,而後調用com.gxl.kratos.utils.sequence.DbConnectionManager類的getSequenceId()方法便可成功獲取全局惟一的sequenceId。在此你們須要注意,Kratos生成的sequenceId是一個17-19位之間的整型,在getSequenceId()方法中,第一個參數爲IDC機房編碼,第二個參數爲類型操做碼,而最後一個參數很是關鍵,就是須要向DB中申請的內存佔位數量(自增碼)。
簡單來講,相信你們都知道,既然業務庫都分庫分表了,就是爲了緩解數據庫讀寫瓶頸,當併發較高時,一個生成sequenceId的通用數據庫能扛得住嗎?筆者告訴你,扛得住!就是由於內存佔位。其實原理很簡單,當第一次應用從Id生成器中去拿sequenceId時,Id生成器會鎖表並將數據庫字段k_useData更新爲5000,那麼第二次應用從Id生成器中去拿sequenceId時,將不會與DB建議物理會話連接,而是直接在內存中去消耗着5000內存佔位數,直至消耗殆盡時,纔會從新去數據庫中申請下一次的內存佔位。
那麼問題又來了,若是併發訪問會不會有問題呢?其實保證全局惟一性有3點,第一是程序中會加鎖,其次數據庫會for update,最後每個操做碼都是惟一的,都會管理本身旗下的內存佔位數(經過Max()函數比較,累加下一個5000)。或許你會在想,如何提高Id生成器的性能,儘量的避免與數據庫創建物理會話,沒錯,這麼想是對的,每次假設從數據庫申請的佔位數量是50000,那麼性能確定比只申請5000好,可是這也有弊端,一旦程序出現down機,內存中的內存數量就會丟失,只能從新申請,這會形成資源浪費。
10、自動生成kratos分庫分表配置文件
Kratos的工具包中除了提供有自動生成sequenceId的功能外,還提供有自動生成分庫分表配置文件等功能。筆者本身是很是喜歡這個功能。由於這很明顯能夠減小配置出錯率。好比咱們採用庫內分片模式,32個庫1024個表,數據源配置文件中,須要配置的數據源信息會有32個,固然這經過手工的方式也何嘗不可,無非就是一個kratos分庫分表配置文件+一個dataSource文件(若是主從的話,還須要一個從庫的dataSource文件),dataSource文件中須要編寫32個數據源信息的<bean/>標籤。可是若是咱們使用的是一庫一片這種分片方式,使用的庫的數量是1024個的時候呢?dataSource文件中須要定義的數據源將會是1024個?寫不死你嗎?你能保證配置不會出問題?
既然手動編寫配置文件可能會出現錯誤,那麼究竟應該如何使用Kratos提供的自動生成配置文件功能來下降出錯率呢?Kratos自動生成配置文件,以下所示:
11、注意事項
一旦在程序中使用Kratos進行Sharding後,sql的編寫必定要注意,不然將沒法進行路由。sql規則以下所示:
一、暫時不支持分佈式事物,所以沒法保證事務一致性;
二、不支持多表查詢,全部多表查詢sql,務必所有打散爲單條sql分開查詢;
三、不建議使用一些數據庫統計函數、Order by語句等;
四、sql的參數第一個必須是路由條件;
五、不支持數據庫別名;
六、路由條件必須是整型;
七、採用連續分片時,子表後綴爲符號"_"+4位整型,好比「tb_0001」——"tb_1024";
注意:
目前Kratos還處於1.2階段,將來還有很長的路要走,如今筆者測試環境上已經在大規模的使用,後續生產環境上將會開始投放使用,所以若是各位在使用過程當中有任何疑問,均可以經過企鵝羣:150445731進行交流,或者獲取Kratos的構件。