《輕量級(jí)數(shù)據(jù)庫中間件利器Sharding-JDBC深度解析》要點(diǎn):
本文介紹了輕量級(jí)數(shù)據(jù)庫中間件利器Sharding-JDBC深度解析,希望對(duì)您有用。如果有疑問,可以聯(lián)系我們。
講師介紹
張亮
當(dāng)當(dāng)架構(gòu)部總監(jiān)
主題簡介:
1、關(guān)系型數(shù)據(jù)庫中間件核心功能介紹
2、Sharding-JDBC架構(gòu)及內(nèi)核解析
3、Sharding-JDBC未來展望
關(guān)系型數(shù)據(jù)庫憑借靈活查詢的SQL和穩(wěn)定的存儲(chǔ)及事務(wù)引擎,一直以來是業(yè)務(wù)存儲(chǔ)領(lǐng)域的首選.而在規(guī)模越來越大的互聯(lián)網(wǎng)年代,單一的關(guān)系型數(shù)據(jù)庫卻已難滿足需求.開發(fā)人員不愿放棄SQL查詢的靈活度及對(duì)之前代碼的兼容性,而又無法承受數(shù)據(jù)量過大時(shí)所帶來的性能瓶頸.因此NoSQL和NewSQL分別產(chǎn)生,而NoSQL的不兼容性和NewSQL的不成熟,也使得關(guān)系型數(shù)據(jù)庫仍將長期存在下去.所以,繼續(xù)使用關(guān)系型數(shù)據(jù)庫,并能解決互聯(lián)網(wǎng)規(guī)模所帶來的沖擊,數(shù)據(jù)庫中間層這個(gè)既不保守,也不激進(jìn)的平衡方案被大多數(shù)互聯(lián)網(wǎng)公司所接受.關(guān)系型數(shù)據(jù)庫中間件采用單體向分布式透明轉(zhuǎn)化的方案來解決數(shù)據(jù)量和訪問量巨大這兩個(gè)互聯(lián)網(wǎng)場景的核心問題.
關(guān)系型數(shù)據(jù)庫在大于自身數(shù)據(jù)量閥值的情況下,性能會(huì)急劇下降.在面對(duì)互聯(lián)網(wǎng)海量數(shù)據(jù)情況時(shí),所有數(shù)據(jù)都存于單表,顯然會(huì)輕易超過數(shù)據(jù)庫表可承受的范圍.這個(gè)單表可承受的數(shù)據(jù)量閥值,需根據(jù)數(shù)據(jù)庫和并發(fā)量的差異,通過實(shí)際測試獲得.
通過分庫和分表拆分?jǐn)?shù)據(jù)來使得各個(gè)表的數(shù)據(jù)量保持在閥值以下.拆分方式為垂直拆分和水平拆分.垂直拆分是根據(jù)業(yè)務(wù)將單庫(表)拆分為多庫(表).如:將常用的字段和不常用的字段拆分至不同的庫(表)中.但垂直拆分需要對(duì)架構(gòu)和設(shè)計(jì)進(jìn)行調(diào)整,往往來不及應(yīng)對(duì)互聯(lián)網(wǎng)業(yè)務(wù)需求的快速變化,而且也并不能真正的解決單點(diǎn)瓶頸.水平拆分則是根據(jù)分片算法將一個(gè)庫(表)拆分為多個(gè)庫(表).如:根據(jù)ID的最后一位以10取余,尾數(shù)是0的放入0庫(表),尾數(shù)是1的放入1庫(表).水平拆分從理論上突破了單機(jī)數(shù)據(jù)量處理的瓶頸,并且擴(kuò)展相對(duì)自由,是分庫分表的標(biāo)準(zhǔn)解決方案.
分庫和讀寫分離疏導(dǎo)流量是應(yīng)對(duì)高訪問量的常見手段.分表雖然可以解決海量數(shù)據(jù)導(dǎo)致的性能問題,但無法解決過多請求訪問同一數(shù)據(jù)庫,導(dǎo)致其響應(yīng)變慢的問題.所以水平拆分通常要采取分庫的方式,一并解決數(shù)據(jù)量和訪問量巨大的問題.讀寫分離是另一個(gè)疏導(dǎo)流量的辦法,但讀寫數(shù)據(jù)間的延遲是架構(gòu)設(shè)計(jì)時(shí)需要考慮的問題.
雖然分庫可以解決上述問題,但分布式架構(gòu)在獲得了收益的同時(shí),也帶來了新的問題.
跨庫事務(wù)是分布式數(shù)據(jù)庫要面對(duì)的棘手事情.合理采用分表,可以在降低單表數(shù)據(jù)量的情況下,盡量使用本地事務(wù),善于使用同庫不同表可有效避免分布式事務(wù)帶來的麻煩.在不能避免跨庫事務(wù)的場景,有些業(yè)務(wù)仍然需要保持事務(wù)的一致性.而基于XA的分布式事務(wù)由于性能低下,無法被互聯(lián)網(wǎng)公司所采納,它們大多采用最終一致性的柔性事務(wù)代替分布式事務(wù).
因此,最佳實(shí)踐是合理地配合使用分庫+分表.在實(shí)現(xiàn)上,分表的難度遠(yuǎn)大于分庫,它需要對(duì)SQL解析,并且對(duì)表名改寫,而分庫則不需要.
另一個(gè)分布式衍生的問題是主鍵生成.它必須可以保證分布式唯一.分布式主鍵的生成方式分為中心化和去中心化兩大類.中心化可以繼續(xù)采用數(shù)據(jù)庫生成自增主鍵的方式,為每個(gè)不同的分庫設(shè)置不同的初始值,并將步長設(shè)置為分片的個(gè)數(shù)即可,這種方式對(duì)分片個(gè)數(shù)有依賴,一旦再次水平擴(kuò)展,原有的分布式主鍵不易遷移.還有一種中心化生成分布式主鍵的方式,即采用Redis在內(nèi)存中生成自增序列,但此種方式新增加了一個(gè)外部組件的依賴,一旦Redis不可用,則整個(gè)數(shù)據(jù)庫將無法在插入,可用性會(huì)大大下降,另外Redis的單點(diǎn)問題也需要解決,部署復(fù)雜度較高.去中心化方式無需額外部署,可擴(kuò)展性也很好,因此更推薦使用.UUID是去中心化生成分布式主鍵較為常見的一種方式,但它的主鍵很長,而且無序,通過主鍵排序時(shí)對(duì)數(shù)據(jù)庫的性能影響較大,不建議使用.目前較為完美的方案是使用snowflake算法生成分布式唯一和基本有序的主鍵.
綜上所述,分布式數(shù)據(jù)庫中間件的功能模塊非常清晰,有其存在的必要和市場價(jià)值.但與旺盛的需求形成鮮明對(duì)比,成熟的關(guān)系型數(shù)據(jù)庫中間件鳳毛麟角.主要原因是各公司均開發(fā)各自的中間件,沒有形成統(tǒng)一的標(biāo)準(zhǔn)及規(guī)范,也沒有動(dòng)力從公司現(xiàn)有的系統(tǒng)中解耦并獨(dú)立開源.當(dāng)當(dāng)同樣自研,并決定將其開源,命名為Sharding-JDBC.從2016年開源至今,已發(fā)布了15個(gè)版本,其中包含5個(gè)里程碑版本升級(jí).在經(jīng)歷了整體架構(gòu)的數(shù)次精煉以及穩(wěn)定性打磨后,如今它已積累了足夠的底蘊(yùn),相信可以成為開發(fā)者選擇技術(shù)組件時(shí)的一個(gè)參考.
Sharding-JDBC完整的實(shí)現(xiàn)了分庫分表,讀寫分離和分布式主鍵功能,并初步實(shí)現(xiàn)了柔性事務(wù).
它直接實(shí)現(xiàn)JDBC接口,舊代碼遷移成本幾乎為零,可適用于任何基于Java的ORM框架,如:JPA、Hibernate、Mybatis、SpringJDBC Template等;理論上可以支持所有實(shí)現(xiàn)JDBC協(xié)議的數(shù)據(jù)庫,但由于各種數(shù)據(jù)庫的SQL方言差別較大,每種SQL都需要獨(dú)立的解析器,Sharding-JDBC目前僅支持MySQL、PostgreSQL、Oracle和SQL Server這4種最主流的數(shù)據(jù)庫.由于柔性事務(wù)與JDBC沒有直接關(guān)系,因此正在考慮將它拆分為一個(gè)獨(dú)立的項(xiàng)目.
Sharding-JDBC與基于MySQL等數(shù)據(jù)庫協(xié)議實(shí)現(xiàn)的Proxy中間層在部署架構(gòu)上差別很大,但在代碼的核心邏輯上差別并不大.Sharding-JDBC作為lib庫,是與業(yè)務(wù)代碼部署在一起的,而基于Proxy的中間層則是架在數(shù)據(jù)庫的前方,與應(yīng)用代碼在部署上隔絕.無論使用哪種架構(gòu),它們的核心邏輯均極為相似,都會(huì)分為分片規(guī)則配置、SQL解析、SQL路由、SQL改寫、SQL執(zhí)行以及結(jié)果歸并模塊,區(qū)別僅在于協(xié)議實(shí)現(xiàn)層的不同(JDBC或數(shù)據(jù)庫協(xié)議).
Sharding-JDBC架構(gòu)圖如下:
左邊部分是部署架構(gòu)圖,右邊部分則是核心邏輯架構(gòu)圖.
使用Sharding-JDBC,性能是大家最關(guān)心的問題.
在數(shù)據(jù)量一致的情況下,使用Sharding-JDBC和原生JDBC的性能測試報(bào)告如下:
將單表的數(shù)據(jù)拆分為二,放入兩個(gè)表中,使用Sharding-JDBC和原生JDBC的性能測試報(bào)告如下:
下面我將按照模塊深度剖析Sharding-JDBC的詳細(xì)功能和主要實(shí)現(xiàn),請大家和我一起探索與評(píng)估它的水有多深.
分片規(guī)則配置
Sharding-JDBC的分片策略配置是自定義的,因此可以通過編程的方式最大限度的靈活調(diào)整.它并不僅支持=運(yùn)算符分片,可支持BETWEEN和IN的運(yùn)算符分片,支持將一條邏輯SQL最終散落至多個(gè)數(shù)據(jù)節(jié)點(diǎn).同時(shí)支持多分片鍵,例如:根據(jù)用戶ID分庫,訂單ID分表這種分庫分表結(jié)合的分片策略;或根據(jù)年分庫,月份+用戶區(qū)域ID分表這樣的多片鍵分片.
通過編程的方式定制分片規(guī)則雖然靈活,但配置起來略顯繁瑣.因此Sharding-JDBC又提供了Inline表達(dá)式編寫分片策略的方式,用于配置集中化,以避免配置散落在配置文件和代碼中的情況.此外,它還提供了定制化的Spring命名空間和YAML進(jìn)一步簡化配置.
JDBC規(guī)范重寫
Sharding-JDBC對(duì)JDBC規(guī)范的重寫思路是針對(duì)DataSource、Connection、Statement、PreparedStatement和ResultSet這5個(gè)核心接口封裝,將多個(gè)實(shí)現(xiàn)類集合納入Sharding-JDBC實(shí)現(xiàn)類管理.分布式主鍵也屬于JDBC協(xié)議的一部分.
Sharding-JDBC盡量最大化實(shí)現(xiàn)JDBC協(xié)議,但分布式畢竟與原生JDBC不同,所以目前仍有未實(shí)現(xiàn)的接口,包括游標(biāo),存儲(chǔ)過程、SavePoint以及向前遍歷和修改ResultSet等不太常用的功能.此外,為了保證兼容性,并未實(shí)現(xiàn)JDBC 4.1及其后發(fā)布的接口(如:DBCP 1.x版本不支持JDBC 4.1).
SQL解析
SQL解析作為分庫分表類產(chǎn)品的核心,性能和兼容性是最重要的衡量指標(biāo).目前常見的SQL解析器主要有fdb,jsqlparser和Druid.Sharding-JDBC1.4.x之前的版本使用Druid作為SQL解析器,經(jīng)實(shí)際測試,它的性能遠(yuǎn)超其它解析器.
從1.5.x版本開始,Sharding-JDBC采用完全自研的SQL解析引擎.由于目的不同,它并不需要將SQL轉(zhuǎn)為AST語法樹,也無需通過Visitor的方式二次遍歷.它采用對(duì)SQL“半理解”的方式,僅提煉分片需要關(guān)注的上下文,因此SQL解析的性能和容錯(cuò)性得到了進(jìn)一步的提高.
SQL解析模塊由Lexer和Parser兩個(gè)模塊組成.Lexer用于將SQL拆解為Token,并將其歸類為關(guān)鍵詞,表達(dá)式,字面量和操作符.Parser則用于理解SQL和提煉分片上下文,并標(biāo)記可能需要改寫的位置.分片上下文包含SELECTItems、表信息、分片條件、自增主鍵信息、排序信息、分組信息和Limit信息.一次解析過程是不可逆的,一個(gè)個(gè)Token的依次解析,因此解析性能很高.由于各種數(shù)據(jù)庫的SQL差異很大,因此在解析模塊對(duì)每種數(shù)據(jù)庫提供方言的支持.
Sharding-JDBC支持各種連接、聚合、排序、分組以及分頁的解析,并且可以有限度的支持子查詢.
SQL路由
SQL路由是根據(jù)分片規(guī)則配置以及解析上下文中的分片條件,將SQL定位至真正的數(shù)據(jù)源.它又分為直接路由、簡單路由和笛卡爾積路由.
滿足直接路由的條件比較苛刻,如果通過Hint(通過HintAPI直接指定路由至庫表)方式分片,且僅分庫,則無需SQL解析和結(jié)果歸并.因此它的SQL兼容性最好,可以執(zhí)行包括子查詢、OR、UNION等復(fù)雜情況的任意SQL.
簡單路由是Sharding-JDBC最推薦使用的分片方式,它是指不包含JOIN或僅包含Binding表JOIN的SQL.Binding表是指使用同樣的分片鍵和分片規(guī)則的一組表,也就是說任何情況下,Binding表的分片結(jié)果應(yīng)與主表一致.例如:order表和order_item表,都根據(jù)order_id分片,結(jié)果應(yīng)是order_1與order_item_1成對(duì)出現(xiàn).這樣的關(guān)聯(lián)查詢和單表查詢復(fù)雜度和性能相當(dāng).如果分片條件不是等于,而是BETWEEN或IN,則路由結(jié)果不一定落入單庫(表),因此一條邏輯SQL最終可能拆分為多條SQL語句.
笛卡爾積查詢最為復(fù)雜,因?yàn)闊o法根據(jù)Binding關(guān)系定位分片規(guī)則的一致性,所以非Binding表的關(guān)聯(lián)查詢需要拆解為笛卡爾積組合執(zhí)行.查詢性能較低,而且數(shù)據(jù)庫連接數(shù)較高,需謹(jǐn)慎使用.
SQL改寫模塊的用途是將邏輯SQL改寫為可以分布式執(zhí)行的SQL.在Sharding-JDBC 1.5.x版本,SQL改寫進(jìn)行了調(diào)整和大量優(yōu)化.1.4.x及之前版本,SQL改寫是在SQL路由之前完成的,在1.5.x中調(diào)整為SQL路由之后,因?yàn)镾QL改寫可以根據(jù)路由至單庫表還是多庫表而進(jìn)行進(jìn)一步優(yōu)化.SQL改寫分為正確性改寫和優(yōu)化改寫兩部分.
正確性改寫包括將分表的邏輯表名稱替換為真實(shí)表名稱,修正分頁信息和增加補(bǔ)列.舉兩個(gè)例子:
優(yōu)化改寫是1.5.x重點(diǎn)提升的部分,實(shí)現(xiàn)的功能比較零散,這里同樣舉兩個(gè)例子:
路由至真實(shí)數(shù)據(jù)源后,Sharding -JDBC將采用多線程并發(fā)執(zhí)行SQL.它用3種執(zhí)行引擎分別對(duì)應(yīng)處理Statement,PreparedStatement和AddBatchPreparedStatement.Sharding-JDBC線程池放在一個(gè)名為ShardingContext的對(duì)象中,它的生命周期同ShardingDataSource保持一致.如果一個(gè)應(yīng)用中創(chuàng)建了多個(gè)Sharding-JDBC的數(shù)據(jù)源,它們將持有不同的線程池.
Sharding-JDBC支持的結(jié)果歸并從功能上分為遍歷、排序、分組和分頁4種類型,它們是組合而非互斥的關(guān)系.從結(jié)構(gòu)劃分,可分為流式歸并、內(nèi)存歸并和裝飾者歸并.流式歸并和內(nèi)存歸并是互斥的,裝飾者歸并可以在流式歸并和內(nèi)存歸并之上做進(jìn)一步的處理.
流式歸并是將數(shù)據(jù)游標(biāo)與結(jié)果集的游標(biāo)保持一致,順序的從結(jié)果集中一條條的獲取正確的數(shù)據(jù).遍歷和排序都是流式歸并,分組比較復(fù)雜,分為流式分組和內(nèi)存分組.內(nèi)存歸并則是需要將結(jié)果集的所有數(shù)據(jù)都遍歷并存儲(chǔ)在內(nèi)存中,再通過內(nèi)存歸并后,將內(nèi)存中的數(shù)據(jù)偽裝成結(jié)果集返回.
遍歷類型最為簡單,只需將多結(jié)果集組成鏈表,遍歷完成當(dāng)前結(jié)果集后,將鏈表位置后移,繼續(xù)遍歷下一個(gè)結(jié)果集即可.
排序類型稍微復(fù)雜,由于ORDER BY的原因,每個(gè)結(jié)果集自身數(shù)據(jù)是有序的,因此只需要將結(jié)果集當(dāng)前游標(biāo)指向的值排序即可.Sharding-JDBC在排序類型歸并時(shí),將每個(gè)結(jié)果集的當(dāng)前排序數(shù)據(jù)實(shí)現(xiàn)了比較器,并將其放入優(yōu)先級(jí)隊(duì)列.每次JDBC調(diào)用next時(shí),將隊(duì)列頂端的結(jié)果集出隊(duì)并next,然后獲取新的隊(duì)列頂端的結(jié)果集供JDBC獲取數(shù)據(jù).
分組類型最為復(fù)雜,分組歸并已經(jīng)不屬于OLTP范疇,而更面向OLAP,但由于遺留系統(tǒng)使用很多,因此Sharding-JDBC還是將其實(shí)現(xiàn).分組歸并分成流式分組歸并和內(nèi)存分組歸并.流式分組歸并節(jié)省內(nèi)存,但必須要求排序和分組的數(shù)據(jù)保持一致.如果GROUPBY和ORDER BY的內(nèi)容不一致,則必須使用內(nèi)存分組歸并.由于數(shù)據(jù)不是按照分組需要的順序取出,因此需要將結(jié)果集中的所有數(shù)據(jù)全部加載至內(nèi)存.在SQL改寫時(shí)提到的僅有GROUP BY的SQL,會(huì)優(yōu)化增加ORDER BY語句,即使將內(nèi)存分組歸并優(yōu)化為流式分組歸并的提升.
無論是流式分組還是內(nèi)存分組,對(duì)聚合的處理都是一致的.聚合分為比較、累加和平均值3種類型.比較聚合包括MAX和MIN,只返回最大(小)結(jié)果.累加聚合包括SUM和COUNT,需要將結(jié)果累加后返回.平均值聚合則是通過SQL改寫的SUM和COUNT計(jì)算,相關(guān)內(nèi)容已在SQL改寫涵蓋,不再贅述.
最后再聊一下裝飾者歸并,他是對(duì)所有的結(jié)果集歸并進(jìn)行統(tǒng)一的功能增強(qiáng),目前裝飾者歸并只有分頁一種類型.
上述的所有歸并類型,都可能分頁或不分頁,因此可以通過裝飾者模式來增加分頁的能力.分頁歸并會(huì)將改寫的LIMIT中,不需要獲取的數(shù)據(jù)過濾掉.Sharding-JDBC的分頁很容易產(chǎn)生誤解,很多人認(rèn)為分頁會(huì)占用大量內(nèi)存,因?yàn)镾harding-JDBC會(huì)因?yàn)榉植际秸_性的考量,將LIMIT 100000, 10改寫為LIMIT 0, 100010,產(chǎn)生Sharding-JDBC會(huì)將100010數(shù)據(jù)都加載到內(nèi)存的錯(cuò)覺.通過上面分析可知,會(huì)全部加載到內(nèi)存的只有內(nèi)存分組歸并這一種情況.其他情況都是通過流式獲取結(jié)果集數(shù)據(jù)的方式,因此Sharding-JDBC會(huì)通過結(jié)果集的next方法將無需取出的數(shù)據(jù)全部跳過,并不會(huì)將其存入內(nèi)存.
分布式主鍵在這里單獨(dú)提煉出一個(gè)章節(jié),因?yàn)樗秦灤┯赟harding-JDBC整個(gè)生命周期的.
分布式主鍵最獨(dú)立的部分是生成策略,Sharding-JDBC提供靈活的配置分布式主鍵生成策略方式.在分片規(guī)則配置模塊可配置每個(gè)表的主鍵生成策略,默認(rèn)使用snowflake.
通過策略生成的分布式主鍵可以無縫的融入JDBC協(xié)議,它實(shí)現(xiàn)了Statement的getGeneratedKeys方法,將其返回改寫后的Result和ResultMetaData,將Sharding-JDBC生成的分布式主鍵偽裝為數(shù)據(jù)庫生成的自增主鍵返回.
SQL解析時(shí),需要根據(jù)分布式主鍵配置策略判斷是否在邏輯SQL中已包含主鍵列,如果未包含則需要將INSERTItems和INSERT Values的最后位置寫入解析上下文.
SQL改寫時(shí),將根據(jù)解析上下文中的位置改寫SQL,增加未包含的主鍵列名稱和值.如果是Statement則在INSERT Values后追加生成后的分布式主鍵;如果是PreparedStatement則在INSERT Values后追加?,并在傳入的參數(shù)后追加生成后的分布式主鍵.
受限于篇幅,讀寫分離、柔性事務(wù)就不在此說明了.
首先,請和我一同回顧下Sharding-JDBC每個(gè)里程碑版本的歷程.
1.0.x:分庫分表
1.1.x:配置簡易化
1.2.x:柔性事務(wù)
1.3.x:讀寫分離
1.4.x:分布式主鍵
1.5.x:自研解析引擎 + 多數(shù)據(jù)庫支持
通過這5個(gè)版本的迭代可以看到,Sharding-JDBC的精力主要集中在透明化分布式數(shù)據(jù)庫這部分,因此經(jīng)常有人問Sharding-JDBC和基于Proxy的數(shù)據(jù)庫中間層有什么區(qū)別?和NewSQL數(shù)據(jù)庫又有什么區(qū)別?
盡管部署架構(gòu)不同,但功能上確實(shí)差異不明顯.不過結(jié)構(gòu)的不同終會(huì)將它們推向不同的方向.Sharding-JDBC與業(yè)務(wù)代碼部署在一起的架構(gòu),非常適合作為微服務(wù)的數(shù)據(jù)訪問層基礎(chǔ)開發(fā)組件.Proxy和NewSQL是面向運(yùn)維的數(shù)據(jù)庫,而Sharding-JDBC的定位與當(dāng)當(dāng)一并開源的DubboX、Elastic-Job一樣,是面向開發(fā)的微服務(wù)基礎(chǔ)類庫,它始終以云原生的基礎(chǔ)開發(fā)套件為目標(biāo).
Sharding-JDBC 1.6.x到來,將會(huì)愈加明顯的劃清界限.Sharding-JDBC 1.6.x的目標(biāo)是配置動(dòng)態(tài)化和數(shù)據(jù)庫治理,通過將配置存入注冊中心,達(dá)到治理分庫分表+讀寫分離的數(shù)據(jù)庫的目的.在應(yīng)用端進(jìn)行數(shù)據(jù)庫發(fā)現(xiàn)、流量疏導(dǎo)、故障轉(zhuǎn)移、熔斷等功能,向治理服務(wù)一樣治理數(shù)據(jù)庫.
Sharding-JDBC將作為面向OLTP在線業(yè)務(wù)的分片化的數(shù)據(jù)庫治理微服務(wù)基礎(chǔ)組件積極的發(fā)展下去.真誠邀請感興趣的人關(guān)注和參與.
點(diǎn)擊文末【閱讀原文】或登錄https://github.com/dangdangdotcom/sharding-jdbc 即可進(jìn)入Sharding-JDBC開源地址.
回聽直播請戳:https://m.qlchat.com/topic/details?topicId=260000426036664&isGuide=Y
密碼:123
文章來自微信公眾號(hào):DBAplus社群
轉(zhuǎn)載請注明本頁網(wǎng)址:
http://www.fzlkiss.com/jiaocheng/2198.html