前言mysql
在開始講解淘寶的 TDDL(Taobao Distribute Data Layer) 技術以前,請容許筆者先吐槽一番。首先要開噴的是淘寶的社區支持作的無比的爛, TaoCode 開源社區上面,幾乎歷來都是有人提問,無人響應。再者版本迭代速度也一樣差強人意 , 就目前而言 TDDL 的版本已經全線開源(Group、Atom、Matrix)你們能夠在Github上下載源碼 。web
目錄算法
1、互聯網當下的數據庫拆分過程spring
2、 TDDL 的架構原型sql
3、下載 TDDL 的 Atom 層和 Group 層源代碼數據庫
4、 Diamond 簡介tomcat
5、 Diamond 的安裝和使用架構
6、動態數據源層的 Master/Salve 讀寫分離 配置與實現併發
7、 Matrix 層的分庫分表配置與實現oracle
1、互聯網當下的數據庫拆分過程
對於一個剛上線的互聯網項目來講,因爲前期活躍用戶數量並很少,併發量也相對較小,因此此時企業通常都會選擇將全部數據存放在 一個數據庫 中進行訪問操做。但隨着後續的市場推廣力度不斷增強,用戶數量和併發量不斷上升,這時若是僅靠一個數據庫來支撐全部訪問壓力,幾乎是在 自尋死路 。因此一旦到了這個階段,大部分 Mysql DBA 就會將數據庫設置成 讀寫分離狀態 ,也就是一個 Master節點對應多個 Salve 節點。通過 Master/Salve 模式的設計後,徹底能夠應付單一數據庫沒法承受的負載壓力,並將訪問操做分攤至多個 Salve 節點上,實現真正意義上的讀寫分離。但你們有沒有想過,單一的 Master/Salve 模式又能抗得了多久呢?若是用戶數量和併發量出現 量級 上升,單一的 Master/Salve 模式照樣抗不了多久,畢竟一個 Master 節點的負載仍是相對比較高的。爲了解決這個難題,Mysql DBA 會在單一的 Master/Salve 模式的基礎之上進行數據庫的 垂直分區 (分庫)。所謂垂直分區指的是能夠根據業務自身的不一樣,將本來冗餘在一個數據庫內的業務表拆散,將數據分別存儲在不一樣的數據庫中,同時仍然保持 Master/Salve模式。通過垂直分區後的 Master/Salve 模式徹底能夠承受住不可思議的高併發訪問操做,可是否能夠永遠 高枕無憂 了?答案是否認的,一旦業務表中的數據量大了,從維護和性能角度來看,不管是任何的 CRUD 操做,對於數據庫而言都是一件極其耗費資源的事情。即使設置了索引, 仍然沒法掩蓋由於數據量過大從而致使的數據庫性能降低的事實 ,所以這個時候 Mysql DBA 或許就該對數據庫進行 水平分區 (分表, sharding ),所謂水平分區指的是將一個業務表拆分紅多個子表,好比 user_table0 、 user_table1 、 user_table2 。子表之間經過某種契約關聯在一塊兒,每一張子表均按段位進行數據存儲,好比 user_table0 存儲 1-10000 的數據,而 user_table1 存儲 10001-20000 的數據,最後 user_table3 存儲 20001-30000 的數據。通過水平分區設置後的業務表,必然可以將本來一張表維護的海量數據分配給 N 個子表進行存儲和維護,這樣的設計在國內一流的互聯網企業比較常見,如圖 1-1 所示:
圖 1-1 水平分區
上述筆者簡單的講解了數據庫的分庫分表原理。接下來請你們認真思考下。本來一個數據庫可以完成的訪問操做,如今若是按照分庫分表模式設計後,將會顯得很是麻煩,這種麻煩尤爲體如今 訪問操做 上。由於持久層須要判斷出對應的數據源,以及數據源上的水平分區,這種訪問方式咱們稱之爲訪問 「 路由 」 。按照常理來講,持久層不該該負責數據訪問層 (DAL) 的工做,它應該只關心 one to one 的操做形式,因此淘寶的 TDDL 框架誕生也就順其天然了。
2、 TDDL 的架構原型
淘寶根據自身業務需求研發了 TDDL ( Taobao Distributed Data Layer )框架,主要用於解決 分庫分表場景下的訪問路由(持久層與數據訪問層的配合)以及異構數據庫之間的數據同步 ,它是一個基於集中式配置的 JDBC DataSource 實現,具備分庫分表、 Master/Salve 、動態數據源配置等功能。
就目前而言,許多大廠也在出一些更加優秀和社區支持更普遍的 DAL 層產品,好比 Hibernate Shards 、 Ibatis-Sharding 等。若是你要問筆者還爲何還要對 TDDL進行講解,那麼筆者只能很 無奈 的表示公司要這麼幹,由於不少時候技術選型並非筆者說了算,而是客戶說了算。當筆者費勁全部努力在 google 上尋找 TDDL的相關使用說明和介紹時,內心一股莫名的火已經開始在蔓延,對於更新緩慢(差很少一年沒更新過 SVN ),幾乎沒社區支持(提問從不響應)的產品來講,除了蝸居在企業內部,一定走不了多遠,最後的結局註定是 悲哀 的。好了,既然抱怨了一番,不管如何仍是要堅持講解完。 TDDL 位於數據庫和持久層之間,它直接與數據庫創建交道,如圖 1-2 所示:
圖 1-2 TDDL 所處領域模型定位
傳說淘寶很早之前就已經對數據進行過度庫分表處理,應用層鏈接多個數據源,中間有一個叫作 DBRoute 的技術對數據庫進行 統一 的路由訪問。 DBRoute 對數據進行多庫的操做、數據的整合,讓應用層像操做一個數據源同樣操做多個數據庫。可是隨着數據量的增加,對於庫表的分法有了更高的要求,例如,你的商品數據到了百億級別的時候,任何一個庫都沒法存放了,因而分紅 2 個、 4 個、 8 個、 16個、 32 個 …… 直到 1024 個、 2048 個。好,分紅這麼多,數據可以存放了,那怎麼查詢它?這時候,數據查詢的中間件就要可以承擔這個重任了,它對上層來講,必須像查詢一個數據庫同樣來查詢數據,還要像查詢一個數據庫同樣快( 每條查詢要求在幾毫秒內完成 ), TDDL 就承擔了這樣一個工做( 其餘 DAL 產品作得更好 ),如圖 1-3 所示:
圖 1-3 TDDL 分庫分表查詢策略
上述筆者描述了 TDDL 在分庫分表環境下的查詢策略,那麼接下來筆者有必要從淘寶官方 copy 它們本身對 TDDL 優勢的一些描述,真實性不敢保證,畢竟沒徹底開源,和社區零支持,你們看一看就算了,別認真。
淘寶人自定的 TDDL 優勢:
1 、數據庫主備和動態切換;
2 、帶權重的讀寫分離;
3 、單線程讀重試;
4 、集中式數據源信息管理和動態變動;
5 、剝離的穩定 jboss 數據源;
6 、支持 mysql 和 oracle 數據庫;
7 、基於 jdbc 規範,很容易擴展支持實現 jdbc 規範的數據源;
8 、無 server,client-jar 形式存在,應用直連數據庫;
9 、讀寫次數 , 併發度流程控制,動態變動;
10 、可分析的日誌打印 , 日誌流控,動態變動;
注意 :
TDDL 必需要依賴 diamond 配置中心( diamond 是淘寶內部使用的一個管理持久配置的系統,目前淘寶內部絕大多數系統的配置)。
接下來,筆者將會帶領各位一塊兒分析 TDDL 的體系架構。 TDDL 其實主要能夠劃分爲 3 層架構,分別是 Matrix 層、 Group 層和 Atom 層。 Matrix 層用於實現分庫分表邏輯,底層持有多個 Group 實例。而 Group 層和 Atom 共同組成了 動態數據源 , Group 層實現了數據庫的 Master/Salve 模式的寫分離邏輯,底層持有多個Atom 實例。最後 Atom 層 (TAtomDataSource) 實現數據庫ip,port,password,connectionProperties 等信息的動態推送 , 以及持有原子的數據源分離的 JBOSS 數據源)。
圖 1-4 TDDL 體系結構
章節的最後,咱們還須要對 TDDL 的原理進行一次剖析。由於咱們知道持久層只關心對數據源的 CRUD 操做,而多數據源的訪問,並不該該由它來關心。也就是說 TDDL 透明給持久層的數據源接口應該是統一且 「 單一 」 的,至於數據庫 到底如何分庫分表,持久層無需知道,也無需 編寫對應的 SQL 去實行 應對策略 。這個時候對 TDDL 一些疑問就出現了, TDDL 須要對 SQL 進行二次解析和拼裝嗎?答案是 不解析僅拼裝 。說白了 TDDL 只須要從持久層拿到發出的 SQL
再按照一些分庫分表條件,進行特定的 SQL 擴充以此知足訪問路路由操做。
如下是淘寶團隊對 TDDL 的官方原理解釋:
1 、 TDDL 除了拿到分庫分表條件外,還須要拿到 order by 、 group by 、 limit 、join 等信息, SUM 、
MAX 、 MIN 等聚合函數信息, DISTINCT 信息。具備這些關鍵字的 SQL 將會在單庫和多庫狀況下進行 , 語義是不一樣的。 TDDL 必須對使用這些關鍵字的 SQL 返回的結果作出合適的處理;
2 、 TDDL 行復制須要從新拼寫 SQL, 帶上 sync_version 字段;
3 、不經過 sql 解析 , 由於 TDDL 遵照 JDBC 規範 , 它不可能去擴充 JDBC 規範裏面的接口 , 因此只能經過 SQL 中加額外的字符條件 ( 也就是 HINT 方式 ) 或者ThreadLocal 方式進行傳遞 , 前者使 SQL 過長 , 後者難以維護 , 開發 debug 時不容易跟蹤 , 並且須要斷定是在一條 SQL 執行後失效仍是 1 個鏈接關閉後才失效;
4 、 TDDL 如今也同時支持 Hint 方式和 ThreadLocal 方式傳遞這些信息;
3、下載 TDDL 的 Atom 層和 Group 層源代碼
前面咱們談及了 TDDL 的動態數據源主要由 2 部分構成,分別是 Atom 和Group 。 Group 用於實現數據庫的 Master/Salve 模式的寫分離邏輯,而 Atom 層則是持有數據源。很是遺憾的 TDDL 中還有一層叫作 Matrix ,該層是整個 TDDL 最爲核心的地方,淘寶也並無對這一層實現開源,而 Matrix 層主要是創建在動態數據源之上的分庫分表實現。換句話說, TDDL 是基於模塊化結構的,開發人員能夠選用 TDDL 中的部分子集。
你們能夠從淘寶的 TaoCode 上下載 TDDL 的源碼帶,而後進行構件的打包。TDDL 的項目主要是基於 Maven 進行管理的,因此建議你們若是不瞭解 Maven 的使用,仍是參考下筆者的博文《 Use Maven3.x 》。
你們下載好 TDDL 的源代碼後,經過 IDE 工具導入進來後能夠發現,開源的TDDL 的工程結構有以下幾部份組成:
tddl-all –
— tbdatasource
— tddl-atom-datasource
— tddl-common
— tddl-group-datasource
— tddl-interact
— tddl-sample
你們可使用 Maven 的命令「 mvn package 「將 TDDL 的源代碼打包成構件。若是你的電腦上並無安裝 Maven 的插件到不是沒有辦法實現構件打包,你可使用eclipse 的導出命令,將源代碼導出成構件形式也能夠。
4、 Diamond 簡介
使用任何一種框架都須要配置一些配置源信息,畢竟每一種框架都有本身的規範,使用者務必遵照這些規範來實現本身的業務與基礎框架的整合。天然 TDDL 也不例外,也是有配置信息須要顯式的進行配置,在 TDDL 中,配置能夠基於 2 種方式,一種是基於本地配置文件的形式,另一種則是基於 Diamond 的形式進行配置,在實際開發過程當中,因爲考慮到配置信息的集中管理所帶來的好處,大部分開發人員願意選擇將 TDDL 的配置信息託管給 Diamond ,因此本文仍是以Diamond 做爲 TDDL 的配置源。
diamond 是淘寶內部使用的一個管理持久配置的系統,它的特色是簡單、可靠、易用,目前淘寶內部絕大多數系統的配置,由 diamond 來進行統一管理。diamond 爲應用系統提供了獲取配置的服務,應用不只能夠在啓動時從 diamond獲取相關的配置,並且能夠在運行中對配置數據的變化進行感知並獲取變化後的配置數據。
5、 Diamond 的安裝和使用
Diamond 和 TDDL 不一樣,它已經實現了徹底意義上的開源。你們能夠從淘寶的TaoCode
上下載 Diamond 的源代碼, SVN 下載地址爲http://code.taobao.org/svn/diamond/trunk 。當你們成功下載好 Diamond 的源代碼後,咱們接下來就須要開始 Diamond 的環境搭建工做。
首先咱們須要安裝好 Mysql 數據庫,以 root 用戶登陸,創建用戶並賦予權限,創建數據庫,而後建表,語句分別以下:
create database diamond;
grant all on diamond.* to zh@’%’ identified by ‘abc’;
use diamond
create table config_info (
‘ id’ bigint(64) unsigned NOT NULL auto_increment,
‘ data_id’ varchar(255) NOT NULL default ’ ’,
‘ group_id’ varchar(128) NOT NULL default ’ ’,
‘ content’ longtext NOT NULL,
‘ md5 ′ varchar(32) NOT NULL default ’ ’ ,
‘ gmt_create ’ datetime NOT NULL default ’ 2010-05-05 00:00:00 ′ ,
‘ gmt_modified ’ datetime NOT NULL default ’ 2010-05-05 00:00:00 ′ ,
PRIMARY KEY (‘id’),
UNIQUE KEY ‘uk_config_datagroup’ (‘data_id’,'group_id’));
完成後,請將數據庫的配置信息( IP ,用戶名,密碼)添加到 diamond-server 工程的 src/resources/jdbc.properties 文件中的 db.url , db.user , db.password 屬性上面,這裏創建的庫名,用戶名和密碼,必須和 jdbc.properties 中對應的屬性相同。
tomcat 是 Damond 的運行容器,在 diamond-server 源代碼根目錄下,執行 mvn clean package -Dmaven.test.skip ,成功後會在 diamond-server/target 目錄下生成diamond-server.war 。打包完成後,將 diamond-server.war 放在 tomcat 的webapps 目錄下。最後啓動 tomcat ,即啓動了 Diamond 。
http server 用來存放 diamond server 等地址列表,能夠選用任何 http server ,這裏以 tomcat 爲例。通常來說, http server 和 diamond server 是部署在不一樣機器上的,這裏簡單起見,將兩者部署在同一個機器下的同一個 tomcat 的同一個應用中,注意,若是部署在不一樣的 tomcat 中,端口號必定是 8080 ,不能修改(因此必須部署在不一樣的機器上)。
在 tomcat 的 webapps 中的 diamond-server 中創建文件 diamond ,文件內容是diamond-server 的地址列表,一行一個地址,地址爲 IP ,例如 127.0.0.1 ,完成這些步驟後,就等於已經完成 Diamond 的安裝。
6、動態數據源層的 Master/Salve 讀寫分離 配置與實現
其實使用 TDDL 並不複雜,只要你會使用 JDBC ,那麼 TDDL 對於你來講無非就只須要將 JDBC 的操做鏈接替換爲 TDDL 的操做鏈接,剩餘操做如出一轍。而且因爲 TDDL 遵循了 JDBC 規範,因此你徹底還可使用 Spring JDBC 、 Hibernate等第三方持久層框架進行 ORM 操做。
咱們來看看如何 TDDL 中配置 TDDL 的讀寫分離, Atom+Group 組成了 TDDL 的動態數據源,這 2 層主要負責數據庫的讀寫分離。
TGroupDataSource 的配置
一、 配置讀寫分離權重:
KEY : com.taobao.tddl.jdbc.group_V2.4.1_ 「 groupKey 」 (Matrix 中爲「 dbKey 」 )
VALUE : dbKey:r10w0,dbKey2:r0w10
TAtomDataSource 的配置(由 3 部分組成, global 、 app 、 user )
一、 基本數據源信息 (global) :
KEY : com.taobao.tddl.atom.global. 「 dbKey 」
VALUE :(
ip= 數據庫 IP
port= 數據庫端口
dbName= 數據庫暱稱
dbType= 數據庫類型
dbStatus=RW )
二、 數據庫密碼信息 (user) :
KEY : com.taobao.tddl.atom.passwd. 「 dbKey 」 . 「 dbType 」 . 「 dbUserName 」
VALUE :數據庫密碼
三、 數據庫鏈接信息( app ,若是不配置時間單位,缺省爲分鐘):
KEY : com.taobao.tddl.atom.app. 「 appName 」 . 「 dbKey 」
VALUE :(
userName= 數據庫用戶
minPoolSize= 最小鏈接數
maxPoolSize= 最大鏈接數
idleTimeout= 鏈接的最大空閒時間
blockingTimeout= 等待鏈接的最大時間
checkValidConnectionSQL=select 1
connectionProperties=rewriteBatchedStatements=true&characterEncoding=UTF8&connectTimeout=1000&autoReconnect=true&socketTimeout=12000 )
應用層使用 TDDL 示例:
public class UseTDDL { private static final String APPNAME = "tddl_test"; private static final String GROUP_KEY = "tddltest"; private static TGroupDataSource tGroupDataSource ; /* 初始化動態數據源 */ static { tGroupDataSource = new TGroupDataSource(); tGroupDataSource .setAppName( APPNAME ); tGroupDataSource .setDbGroupKey( GROUP_KEY ); tGroupDataSource .init(); } @Test public void testQuery() { final String LOAD_USER = "SELECT userName FROM tddl_table WHERE userName=?"; Connection conn = null ; PreparedStatement pstmt = null ; ResultSet rs = null ; try { conn = tGroupDataSource .getConnection(); pstmt = conn.prepareStatement(LOAD_USER); pstmt.setString(1, "tddl-test2"); rs = pstmt.executeQuery(); while (rs.next()) System. out .println("data: " + rs.getString(1)); } catch (Exception e) { e.printStackTrace(); } finally { try { if ( null != rs) rs.close(); if ( null != pstmt) pstmt.close(); if ( null != conn) conn.close(); } catch (Exception e) { e.printStackTrace(); } } } }
|
7、 Matrix 層的分庫分表配置與實現
在上一章節中,筆者演示瞭如何在 Diamond 中配置數據庫的讀寫分離,那麼本章筆者則會演示若是配置 TDDL 的分庫分表。
TDDL 的 Matrix 層是創建在動態數據源之上的,因此分庫分表的配置和讀寫分離的基本配置也是同樣的,只不過咱們須要新添加 dbgroups 和 shardrule 項。dbgroups 項包含了咱們所須要配置的全部 AppName 選項,而 shardrule 則是具體的分庫分表規則。這裏有一點須要提醒各位,在開源版本的 TDDL 中,配置TGroupDataSource 讀寫分離是使用 dbKey ,然而在 Matrix 中則是使用appName 。
1 、配置 Group 組:
KEY : com.taobao.tddl.v1_ 「 appName 」 _dbgroups
VALUE : appName1 , appName2
2 、配置分庫分表規則:
KEY : com.taobao.tddl.v1_」appName」_shardrule
VALUE :(
<?xml version="1.0" encoding="gb2312"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="root" class="com.taobao.tddl.common.config.beans.AppRule" init-method="init">
<property name="readwriteRule" ref="readwriteRule" />
</bean>
<bean id="readwriteRule" class="com.taobao.tddl.common.config.beans.ShardRule">
<property name="dbtype" value="MYSQL" />
<property name="tableRules">
<map>
<entry key="tddl_table" value-ref="tddl_table" />
</map>
</property>
</bean>
<bean id="tddl_table" init-method="init"
class="com.taobao.tddl.common.config.beans.TableRule">
<!-- 數據庫組 index 號 -->
<property name="dbIndexes" value="tddl_test,tddl_test2" />
<!-- 分庫規則 -->
<property name="dbRuleArray" value="(#id#.longValue() % 4).intdiv(2)"/>
<!-- 分表規則 , 須要注意的是,由於 taobao 目前 dba 的要求是全部庫內的表名必須徹底不一樣,所以這裏多加了一個映射的關係
簡單來講,分表規則只會算表的 key.
倆庫 4 表 : db1(tab1+tab2) db2(tab3+tab4)
db1 == key: 0 value tab1
key: 1 value tab2
db2 == key: 0 value tab3
key: 1 value tab4
-->
<property name="tbRuleArray" value="#id#.longValue() % 4 % 2"/>
<property name="tbSuffix" value="throughAllDB:[_0-_3]" />
</bean>
</beans>
)
TDDL 的分庫分表配置形式徹底是採用 Spring 的配置形式,這一點你們應該是很是熟悉的。那麼接下來咱們一步一步的分析 TDDL 的分庫分表規則。
在元素 <map/> 中咱們能夠定義咱們所須要的分表,也就是說,當有多個表須要實現分表邏輯的時候,咱們能夠在集合中進行定義。固然咱們還須要外部引用<bean/> 標籤中定義的具體的表邏輯的分庫分表規則。
在分庫分表規則中,咱們須要定義 數據庫組 index 號,也就是說咱們須要定義咱們有多少的 appNames ,接下來咱們就能夠定義分庫和分表規則了。 TDDL的分庫分表規則徹底是採用取餘方式,好比 <property name="dbRuleArray" value="(#id#.longValue() % 4).intdiv(2)"/> , value 屬性中包含有具體的分庫規則,其中「 #id# 」做爲咱們的分庫分表條件,此值在數據庫中對應的類型必須是整類,而後進行取餘後再進行 intdiv 。或許有些朋友看不太明白這個是什麼意思,咱們用簡單的一點的話來講就是,「 #id#.longValue() % 4).intdiv(2) 」的含義是咱們須要分 2個庫和 4 個表,那麼咱們怎麼知道咱們的數據到底落盤到哪個庫呢?打個比方,若是咱們的 id 等於 10 ,首先 10%4 等於 2 ,而後 2/2 等於 1 , TDDL 分庫規則下標從 0 開始,那麼咱們的數據就是落盤到第 2 個庫。
當你們明白 TDDL 的分庫規則後,咱們接下來再來分析分表規則 <property name="tbRuleArray" value="#id#.longValue() % 4 % 2"/> 。和分庫規則相似的是,咱們都採用取餘算法首先進行運算,只不過度表尾運算也是使用取餘,而不是除算。打個比方,若是咱們的 id 等於 10 ,首先 10%4 等於 2 ,而後 2%2 等於 0 ,那麼咱們的數據就是落盤到第 2 個庫的第 1 張表。
應用層使用 TDDL 示例:
public class UseTDDL { private static final String APPNAME = "tddl_test"; private static final TDataSource dataSource ; /* 初始化動態數據源 */ static { dataSource = new TDataSource(); dataSource .setAppName( APPNAME ); dataSource .setUseLocalConfig( false ); dataSource .setDynamicRule( false ); dataSource .init(); } @Test public void query() { final String LOAD_USER = "SELECT userName FROM tddl_table WHERE id = ?"; Connection conn = null ; PreparedStatement pstmt = null ; ResultSet rs = null ; try { conn = dataSource .getConnection(); pstmt = conn.prepareStatement(LOAD_USER); pstmt.setLong(1, 3); rs = pstmt.executeQuery(); while (rs.next()) System. out .println("data: " + rs.getString(1)); } catch (Exception e) { e.printStackTrace(); } finally { try { if ( null != rs) rs.close(); if ( null != pstmt) pstmt.close(); if ( null != conn) conn.close(); } catch (Exception e) { e.printStackTrace(); } } } @Test public void insert() { final String LOAD_USER = "insert into tddl_table values(?, ?)"; Connection conn = null ; PreparedStatement pstmt = null ; try { conn = dataSource .getConnection(); pstmt = conn.prepareStatement(LOAD_USER); pstmt.setLong(1, 10); pstmt.setString(2, "JohnGao"); pstmt.execute(); System. out .println("insert success..."); } catch (Exception e) { e.printStackTrace(); } finally { try { if ( null != pstmt) pstmt.close(); if ( null != conn) conn.close(); } catch (Exception e) { e.printStackTrace(); } } } } |