一、簡介
Cobar是一個對數據進行拆分後進行分布式存儲的產品,可以支持使用後台的 MySQL或者Oracle數據庫,通過配置,將數據按照一定規則存儲入不同的數據庫中。即用分布式數據庫代替了集中式數據庫。傳統的集中式數據庫系統有如下不足:集中式處理,勢必造成性能瓶頸;應用程序集中在一台計算機上運行,一旦該計算機發生故障,則整個系統受到影響,可靠性不高;集中式處理引起系統的規模和配置都不夠靈活,系統的可擴充性差。在這種形勢下,集中式數據庫將向分布式數據庫發展。
分布式數據庫系統的優點:降低費用。分布式數據庫在地理上可以是分布的。其系統的結構符合這種分布的要求。允許用戶在自己的本地錄用、查詢、維護等操作,實行局部控制,降低通信代價,避免集中式需要更高要求的硬件設備。而且分布式數據庫在單台機器上面數據量較少,其響應速度明顯提升;提高系統整體可用性。避免了因為單台數據庫的故障而造成全部癱瘓的後果;易於擴展處理能力和系統規模。分布式數據庫系統的結構可以很容易地擴展系統,在分布式數據庫中增加一個新的節點,不影響現有系統的正常運行。這種方式比擴大集中式系統要靈活經濟。在集中式系統中擴大系統和系統升級,由於有硬件不兼容和軟件改變困難等缺點,升級的代價常常是昂貴和不可行的。
//優點: // 配置簡單, 可以很方便的實現數據的分布式存儲。 // 使用透明, 客戶端幾乎不需要為此做任何的特殊設定。 // 擴展方便, Cobar服務端可以通過負載均衡進行擴展, 數據庫可以根據不同的壓力進行擴張。 // //缺點: // 查詢限制, 如果提交的請求不包含分表字段的限制, 則可能在多個分區執行, 效率和可行性都大打折扣 (碰到過一個普通查詢拋出異常的情況, 具體場景還需要驗證) // 聯合查詢限制, 對於在不同分區的數據, 無法進行聯合查詢。 // 擴展, 在初始時的分區數目如果無法應對後續需求, 需要增加分區的話 (如, 初始設計分為 64 個分區表, 因為單表通常限制數據量在 20G, // 後期發現無法滿足容量需求, 需要擴展成 128/256 個分區), 沒有現成的解決方案, 只能對數據進行人工拆分
Cobar在分布式數據庫領域將致力解決數據切分,應付客戶端"集中式"處理分布式數據。這兒集中式是一個相對概念,客戶端不需要知道某種數據的物理存儲地。避免這種邏輯出現在業務端,大大簡化了客戶端操作分布式數據的復雜程度。 專注分布式數據庫proxy開發。其架設在Client、DB Server(s)之間、對客戶端透明、具有負載均衡、高可用性、sql過濾、讀寫分離、可路由相關的query到目標數據庫、可並發請求多台數據庫合並結果。
二、Cobar的使用驗證
目前, 我在開發環境 10.20.130.119 上部署了 Cobar 和 MySQL 的測試環境:MySQL 分為 128 個 DataBase, 分別是 cobar_1 至 cobar_128, 端口是 3306, 用戶名/密碼: root/password。Cobar 的訪問方式為使用 MySQL 連接, URL 為 jdbc:mysql://10.20.130.119:8066/cobar, 用戶名/密碼: root/12345。測試數據庫表為 q_reportkeywordsum, 分區所用字段為 custid, int 型。如果使用JDBC方式連接Cobar, 則只需要使用 MySQL的JDBC驅動(注意,不能使用最新的 5.1.13 版本, 需要使用 Cobar 自帶的 5.1.6 版 MySQL Connector),正常連接即可。示例代碼如下:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class CobarHelloWorld { public void mian(String[] args) { String url = "jdbc:mysql://10.20.130.119:8066/cobar"; String driver = "com.mysql.jdbc.Driver"; String user = "root"; String pwd = "12345"; Connection con = null; try { Class.forName(driver).newInstance(); con = DriverManager.getConnection(url, user, pwd); Statement stmt = con.createStatement(); // TODO stmt.close(); } catch (Exception e) { // ... } finally { if (con != null) { try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }
如果使用 Spring + iBatis 訪問, 則需要對配置文件作少許改動, 將原org.springframework.orm.ibatis.SqlMapClientTemplate 替換為 Cobar Client 提供的com.alibaba.cobar.client.CobarSqlMapClientTemplate。同時, 將TransactionManager從原 org.springframework.jdbc.datasource.DataSourceTransactionManager替換為 com.alibaba.cobar.client.transaction.MultipleDataSourcesTransactionManager,其他部分不需要改動, 修改後的配置代碼類似以下示例:
<bean id="sqlMapClientTemplate" class="com.alibaba.cobar.client.CobarSqlMapClientTemplate"> <property name="sqlMapClient" ref="sqlMapClient"/> ... </bean> <bean id="transactionManager" class="com.alibaba.cobar.client.transaction.MultipleDataSourcesTransactionManager"> ... </bean> <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:META-INF/ibatis/sqlmap-config.xml" /> </bean> <bean id="dataSource" ...> ... </bean>
開發注意點:如果查詢條件不包含分區字段條件, 則會將請求在所有的分區執行後返回全部結果集, 如執行:
select count(*) from q_reportkeywordsum
則返回所有分區中的查詢結果 (如分區為 128 個, 則結果集中包含 128 個記錄). 而且, 在這樣的情況下, 效率會很差, 需要等待 128 個分區全部執行完後才會返回結果集.解決: 所有查詢都 必須 限定在某一指定的分區字段, 如
select count(*) from q_reportkeywordsum where custid=1
但是,要注意:select count(*) from q_reportkeyworsum where custid < 10; 代碼是不能正確返回結果的。同樣的,如果插入記錄時不指定分區字段, 則會在所有分區表內均插入記錄, 如執行:
insert into q_reportkeywordsum (id, keywordid) values (2, ...)
將會在全部 128 個分區內均插入本條記錄。解決: 對於用作分區規則的字段 (如示例中所用 custid) 必須設置為非空 (NOT NULL) 字段,以避免此類問題。對於不同的分區表, 數據庫主鍵可以重復, 這一點從上一段即可看出, id 作為主鍵字段, 插入過程會在所有分區中均插入一條 id 為 2 的記錄, 因此, 需要額外使用主鍵生成機制保障在不同表內的主鍵不會發生重復。
三、Cobar使用約束
Cobar表的水平拆分,上圖中,數據庫表被水平拆分成2份,分別放到兩個庫中。F( x )是拆分函數,它根據每一條記錄的拆分字段的取值,決定將這條記錄拆分到哪個庫裡。拆分函數可以是多元函數,即 F(x1,x2,..xn)。但是對任意一個拆分函數,不存在該函數的兩個自變量xi,xj,使得 xi 和 xj 是來自不同拆分表的拆分字段。
左圖所示,cobar位於應用和數據庫之間,cobar與應用通過mysql protocol交互。若應用發來的sql語句中包含拆分字段(拆分函數的自變量),以左圖方式工作:否則,根據配置,以右圖方式工作,或直接報錯。
// 如果應用和cobar之間通過F5連接,那麼F5與cobar的連接保持時間設置為1小時。 // 應用必須通過mysql-connector-5.0.4連接cobar,拆分函數值域元素數最大1024 // 不支持DDL,不支持事務,不允許跨庫的join、跨庫的子查詢、跨庫的分頁排序 // 對於操作拆分表的sql語句,需包含拆分字段,方式如下: // 1.對select, delete, update語句,拆分字段存在於where中,以c[i]=xx的形式存在(運算符必須是=、xx必須是某個具體值,不能是字段名或子查詢結果) // 2.insert語句(insert into table (c1, c2 ...) values (value1,value2); insert into table set // c1=value1,c2=....)中拆分字段出現在紅色部分中,同時拆分字段對應的valueXX也必須是某個具體值。 // 建議: // 1. 記錄數小於1千萬或表空間小於10G的,不做拆分。 // 2. F(拆分字段取值)服從均勻分布,其中F是拆分函數。
備注:
利用Redis解決MySQL分表自增ID的問題。采用redis的ID自增生成器 就行,每次插入之前,都去調用一個自增長的函數,返回的數值永遠是唯一,全局唯一的。