選擇開源核心組件的一個很是重要的考慮一般是社區活躍性,一旦項目團隊沒法進行本身後續維護和擴展的狀況下更是如此。php
至於爲何選擇sharding-jdbc而不是Mycat,能夠參考知乎討論帖子https://www.zhihu.com/question/64709787。html
還能夠參考https://blog.csdn.net/u013898617/article/details/79615427。java
關於分庫分表和讀寫分離、主從
通常來講,須要分庫分表的系統是流量比較大的,並且比較容易出現峯值的好比說打折/活動的時候;其次,當單機扛不住業務流量的時候,分庫分表必定不是第一選擇,在分庫分表以前,應該先保證垂直拆分完成了,子系統內都是高內聚的,其次基於Master-Slave的讀寫分離或者模糊查詢不少的,可能NoSQL好比elastic就引流去很大一部分了。當讀寫分離也作完了,主庫只剩下關鍵業務邏輯以後,流量仍是很高,這個時候纔開始考慮分庫分表。由於相對於讀寫分離、垂直拆分,分庫分表對開發和運維的要求多得多,若是肯定業務一兩年內不會劇增的,盲目引入只會致使成本高昂(尤爲是各類SQL限制)。node
其次,分庫分表會增長N倍的數據庫服務器,通常來講是4的倍數,若是某個應用說要作分庫分表,又只有兩臺機器,那徹底就是湊熱鬧。mysql
讀寫分離和分庫分表應該來講是先後的兩件事比較合理,很多人將這兩個事情混到一塊兒去講準確的說不合理的。分庫分表一般更多的是用於純粹的OLTP的事情,讓系統能夠水平擴展。而讀寫分離更多的是爲了把一部分能夠容忍短時延遲/不保證100%(但應該在99%以上)準確的查詢路由到查詢庫,準確的說是對業務根據優先級作個歸類,這些查詢從資源消耗的角度來講相對邏輯上的PK查詢要高几倍到數百倍,好比說查詢某我的過去3個月的交易狀況,但他從業務角度並不算是DSS概念,好比說查詢已經T-N的訂單/針對這些訂單進行導出,並針對這個單子可能會進行一些操做,甚至人工修改狀態。經過把這些功能從核心的訂單/交易/資金OLTP中拆分出去,能夠保證核心業務系統不會由於一個異常操做好比SQL不合理致使系統出現業務負載增長外的額外抖動因素。linux
從系統設計角度來講,讀寫分離雖然從邏輯表結構角度來講是相同的,都具備相同的字段定義,可是物理實現上是必定是不相同(要是徹底相同,說明沒有領會讀寫分離的初衷)的,尤爲是在索引上,寫庫可能除了PK、惟一索引(任何表都應該有惟一索引,分佈式鎖只能做爲緩衝器)外,最多還有一兩個簡單的字段索引以最大化性能,任何SQL符合條件的數據通常不會超過幾十條(極端客戶除外),可是讀庫根據業務不一樣,可能會有不少的索引以知足各類查詢以及簡單的統計彙總報表的需求。算法
那寫庫是否是必定就比讀庫重要呢?是,又不是。是是絕對的,不是是相對的。由於讀庫不是DSS庫,是交易庫中相對來講不是特別重要的業務功能。因此,寫庫一旦掛了,就會致使業務下不去,讀庫掛了,可能會致使作業務的人決策錯誤。好比沒有沒有查到作過某交易,又從新交易一次。spring
考慮分庫分表一個很重要的決策就是是否容許跨多庫操做以及有沒有必要。TODO。。。。待補充。。。。。。sql
其次,分庫和分表是兩件事,是到底選擇分庫仍是分表,若是量沒有那麼大的話並且是虛擬機的話,估計分庫就夠了。數據庫
sharding-jdbc的版本及其架構差別
目前最新版本的sharding-jdbc是v3.0.0.M1,應該來講還不穩定,包名又改爲了io.shardingsphere,jar包名是sharding-jdbc。
1.5.4.1是目前最新版,也多是終版,1.x的,座標是com.dangdang,jar包名是sharding-jdbc。
2.0.3是目前最新版,包名和座標統一改爲了io.shardingjdbc,jar包名是sharding-jdbc-core。
說明規劃不是特別好,仍是有些亂。
由於2.0以後基本上純粹分庫分表的核心特性的加強就很少了,主要往治理和代理方向去了,因此若是沒有特別的需求好比須要相似mycat的獨立服務代理模式,使用1.x(注:1.x版本官網文檔好像下線了)就能夠了,不過若是是大規模的部署,同時已經使用了微服務架構中的註冊中心或者基於spring boot,能夠考慮使用2.0,由於2.0增長了基於註冊中心的配置管理以及spring boot starter。因此2.0的架構是這樣的:
3.0以後,增長了相似mycat角色的sharding-proxy無狀態服務器(代理層能夠有,可是不該該做爲應用直接訪問的入口,以下圖所示),以及相似於Service Mesh的Database Mesh。不過核心沒有變化,對SQL語法增長了部分新的支持。因此3.0的架構以下:
就分庫分表核心來講,咱們就只要關心sharding-jdbc就能夠了,不須要關心sharding-sphere的其餘部分。
sharding-jdbc/Sharding-Proxy/Sharding-Sidecar三者的對好比下:
核心特性
事務
對任何重要的系統來講,事務一致性都是關鍵的。對分佈式系統來講更是如此,最重要的就是事務一致性,從這個層面來講,分庫分表自己並無引入另一個層次的複雜性。由於它在jdbc驅動層包了一層,因此咱們有必要理解它對事務的支持性以及相關的限制。事務
sharding jdbc 2.x不支持強一致性的分佈式事務,通常如今的系統設計也不追求強一致性,而是最終一致性。因此sharding jdbc 2.x支持2中事務:弱XA(同mycat)和最大努力投遞事務(官方簡稱BED)(算是BASE的一種),具體選擇哪種須要根據業務和開發進行權衡,若是架構規範和設計作得好,是能夠作到不跨庫分佈式事務的。
弱XA事務是默認的模式(即只要dml期間沒有拋出異常,commit期間有機器斷網或者宕機,是沒法保證一致性的),沒有特別的要求。
BED則在必定上增長了短時容忍,將執行的語句另做中心化存儲,而後輪詢commit期間失敗的事務重試,因此BED的架構以下:
可是沒有免費的午飯,BED對開發和維護有着必定的額外要求,並且這些要求都涉及面很廣,絕對算得上傷筋動骨。開發層面包括:
- INSERT語句的主鍵不能自增
- UPDATE必須可重複執行,好比不支持UPDATE xxx SET x=x+1,對於更新餘額類,這就至關於要求必須樂觀鎖了
運維層面包括:
- 須要存儲事務日誌的數據庫
- 用於異步做業使用的zookeeper
- 解壓sharding-jdbc-transaction-async-job-$VERSION.tar,經過start.sh腳本啓動異步做業
咱們選擇了從設計層面避免強一致性的分佈式事務。
分片靈活性
對於分庫分表來講,很重要的一個特性是分片的靈活性,好比單個字段、多個字段的=、IN、>=、<=。爲何多個字段很重要的,這裏涉及到一個特殊的考慮
sharding-jdbc目前提供4種分片算法。
因爲分片算法和業務實現緊密相關,所以並未提供內置分片算法,而是經過分片策略將各類場景提煉出來,提供更高層級的抽象,並提供接口讓應用開發者自行實現分片算法。
- 精確分片算法
對應PreciseShardingAlgorithm,用於處理使用單一鍵做爲分片鍵的=與IN進行分片的場景。須要配合StandardShardingStrategy使用。
- 範圍分片算法
對應RangeShardingAlgorithm,用於處理使用單一鍵做爲分片鍵的BETWEEN AND進行分片的場景。須要配合StandardShardingStrategy使用。
- 複合分片算法
對應ComplexKeysShardingAlgorithm,用於處理使用多鍵做爲分片鍵進行分片的場景,多分片鍵邏輯較複雜,須要應用開發者自行處理其中的複雜度。須要配合ComplexShardingStrategy使用。
- Hint分片算法(Hint分片指的是對於分片字段非SQL決定,而由其餘外置條件決定的場景,可以使用SQL Hint靈活的注入分片字段。例:內部系統,按照員工登陸ID分庫,而數據庫中並沒有此字段。SQL Hint支持經過Java API和SQL註釋(待實現)兩種方式使用。)
對應HintShardingAlgorithm,用於處理使用Hint行分片的場景。須要配合HintShardingStrategy使用。
由於算法的靈活性,標準的方式是經過實現具體的java接口是實現具體的分片算法好比SingleKeyDatabaseShardingAlgorithm,有很多的狀況下,分片是比較簡單的,好比說純粹是客戶編號,此時提供了行內表達式分片策略,使用Groovy的表達式,提供對SQL語句中的=和IN的分片操做支持,不過這隻支持單分片鍵。好比,t_user_${u_id % 8}
表示t_user表按照u_id按8取模分紅8個表,表名稱爲t_user_0
到t_user_7
。
分片鍵+分片算法=真正可用的分片策略。
算法和分片鍵的選擇是分庫分表的關鍵,其直接決定了各個分庫的負載是否均衡,以及擴展是否容易。在設計上的考慮一節筆者會詳細闡述,訂單和委託業務、用戶在使用分庫分表時設計上的考慮以及緣由。
SQL語法限制
對於分庫分表來講,還須要知道有哪些SQL的限制,尤爲是涉及到須要二次處理的,好比排序,去重,聚合等。
這裏筆者就列下那些經常使用但沒有被支持的。好比:
- case when
- distinct
- union
不過好在這些在java/js中處理都比較方便。
若是有很複雜的SQL,那最大的可能就是設計上有問題,應該採用讀寫分離解決。
sharding-jdbc對SQL的限制完整能夠參考http://shardingsphere.io/document/current/cn/features/sharding/usage-standard/sql/
設計上的考慮
哪些表要分庫分表
首先從設計上要區分清楚哪些是廣播表/哪些是分庫表/哪些是隻在一個庫的全局表,由於是公用數據源的,因此無論是否是分庫的表,都須要配置,不配置分片規則Sharding-JDB即沒法精確的判定應該路由至哪一個數據源。可是通常分庫分表組件包括Sharding-JDBC都會提供簡化配置的方法。對於不分片的表:
方法1:sharding-jdbc能夠在<sharding:sharding-rule />配置default-data-source-name,這樣未配置分片規則的表將經過默認數據源定位。
方法2:將不參與分庫分表的數據源獨立於Sharding-JDBC以外,在應用中使用多個數據源分別處理分片和不分片的狀況。
分庫仍是分表
通常來講應該選擇分庫(準確的說是分schema),不該該使用分表, 由於oracle能夠包含n個schema,mysql能夠包含多個database,並且就算真的須要,schema之間也是能夠關聯查詢的,因此感受就算是爲了擴展性問題,也沒有必要使用分表,分表反而在擴展的時候更加麻煩。就算數據量多了一點,感受稍微偏慢,能夠先採用分區擋一擋。
分片鍵的選擇
其中最重要的是分片鍵不能是自增字段,不然insert就不知道去哪裏了。因此對於ID生成,須要一個ID生成中心。
分佈式主鍵
分佈式系統的主鍵生成能夠經過設置增量或者也經過ID生成中心來生成,不過話說回來,既然使用ID生成中心了,就不要再使用數據庫機制的ID了,這不必定須要經過代碼所有重寫,能夠在dao層經過aop判斷是否insert,是Insert的動態從ID中心獲取,這樣就避免了分庫分表和非分庫分表在開發商的差異。
Sharding-JDBC使用說明
對於只有一個分片鍵的使用=和IN進行分片的SQL,建議使用行表達式代替Java類的配置。假設咱們不使用弱性事務(若是使用柔性事務,則還須要引入sharding-jdbc-transaction以及sharding-jdbc-transaction-async-job),這樣就只要引入sharding-jdbc-core這個jar包就能夠了,(由於sharding-jdbc的配置支持java、yaml、spring boot以及spring命名空間(相似dubbo),因此建議使用spring 命名空間方式)以下:
<dependency> <groupId>io.shardingjdbc</groupId> <artifactId>sharding-jdbc-core</artifactId> <version>2.0.3</version> </dependency> <!-- for sharding-jdbc spring namespace --> <dependency> <groupId>io.shardingjdbc</groupId> <artifactId>sharding-jdbc-core-spring-namespace</artifactId> <version>2.0.3</version> </dependency>
由於sharding jdbc能夠支持dbcp、druid,因此就不用改動其餘的依賴項了。
接下去配置數據源、數據源分片策略和表分片策略。
咱們假設分庫結構(此處2個庫僅作演示目的,實際應該至少4個分庫)以下:
order和order_item經過order_id關聯,且分片規則相同。
分庫1和分庫2經過user_id做爲分片字段,採用取模(採用取模而不是區間的好處是負載從理論上最均衡,用區間實際上是不均衡的,好比假設客戶比較穩定,就1000萬,0-100萬第一個分片,以此類推,實際上仍是可能很不均衡,由於某段時間增長的客戶可能會特別活躍,尤爲是在互聯網應用中)
配置三個數據源:
一、globalDataSource,指向全局庫,非分庫分表和廣播的表,好比系統參數;
二、dataSource_0、dataSource_1,指向分庫1和分庫2;
<bean name="dataSource_0" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc.url_0}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <!-- 配置初始化大小、最小、最大 --> <property name="initialSize" value="${jdbc.initialSize}"/> <property name="minIdle" value="${jdbc.minIdle}"/> <property name="maxActive" value="${jdbc.maxActive}"/> <!-- 配置獲取鏈接等待超時的時間 --> <property name="maxWait" value="${jdbc.maxWait}"/> <!-- 打開PSCache,而且指定每一個鏈接上PSCache的大小 --> <property name="poolPreparedStatements" value="${jdbc.pps}"/> <property name="maxPoolPreparedStatementPerConnectionSize" value="${jdbc.mpps}"/> <!-- 配置間隔多久才進行一次檢測,檢測須要關閉的空閒鏈接,單位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/> <!-- 配置一個鏈接在池中最小生存的時間,單位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/> <property name="removeAbandoned" value="${jdbc.removeAbandoned}"/> <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/> <property name="logAbandoned" value="${jdbc.logAbandoned}"/> <!-- 配置監控統計攔截的filters --> <property name="filters" value="${jdbc.filters}"/> </bean> <bean name="dataSource_1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc.url_1}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <!-- 配置初始化大小、最小、最大 --> <property name="initialSize" value="${jdbc.initialSize}"/> <property name="minIdle" value="${jdbc.minIdle}"/> <property name="maxActive" value="${jdbc.maxActive}"/> <!-- 配置獲取鏈接等待超時的時間 --> <property name="maxWait" value="${jdbc.maxWait}"/> <!-- 打開PSCache,而且指定每一個鏈接上PSCache的大小 --> <property name="poolPreparedStatements" value="${jdbc.pps}"/> <property name="maxPoolPreparedStatementPerConnectionSize" value="${jdbc.mpps}"/> <!-- 配置間隔多久才進行一次檢測,檢測須要關閉的空閒鏈接,單位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/> <!-- 配置一個鏈接在池中最小生存的時間,單位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/> <property name="removeAbandoned" value="${jdbc.removeAbandoned}"/> <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/> <property name="logAbandoned" value="${jdbc.logAbandoned}"/> <!-- 配置監控統計攔截的filters --> <property name="filters" value="${jdbc.filters}"/> </bean> <bean name="globalDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <!-- 配置初始化大小、最小、最大 --> <property name="initialSize" value="${jdbc.initialSize}"/> <property name="minIdle" value="${jdbc.minIdle}"/> <property name="maxActive" value="${jdbc.maxActive}"/> <!-- 配置獲取鏈接等待超時的時間 --> <property name="maxWait" value="${jdbc.maxWait}"/> <!-- 打開PSCache,而且指定每一個鏈接上PSCache的大小 --> <property name="poolPreparedStatements" value="${jdbc.pps}"/> <property name="maxPoolPreparedStatementPerConnectionSize" value="${jdbc.mpps}"/> <!-- 配置間隔多久才進行一次檢測,檢測須要關閉的空閒鏈接,單位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/> <!-- 配置一個鏈接在池中最小生存的時間,單位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/> <property name="removeAbandoned" value="${jdbc.removeAbandoned}"/> <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/> <property name="logAbandoned" value="${jdbc.logAbandoned}"/> <!-- 配置監控統計攔截的filters --> <property name="filters" value="${jdbc.filters}"/> </bean>
全局表、分庫不分表、分庫分表、廣播表、主、子表綁定的各自規則配置(下面使用行內策略,注:https://blog.csdn.net/shijiemozujiejie/article/details/80786231提到行表達式的策略不利於數據庫和表的橫向擴展,這是有道理的,由於java代碼咱們能夠隨時從新加載分庫和分表的基數,作到不停機擴展,可是行內表達式不行)以下:
<!-- 必須加上ignore-unresolvable,不然極可能會出現java.lang.IllegalArgumentException: Could not resolve placeholder以及cannot invoke method mod() on null object-->
<context:property-placeholder location="classpath:jrescloud.properties" ignore-unresolvable="true" order="1"/>
<!-- 分庫策略,儘可能使用sharding:standard-strategy而不是inline-stragegy --> <sharding:inline-strategy id="databaseStrategy" sharding-column="user_id" algorithm-expression="dataSource_${user_id % 2}" /> <!-- 分表策略 --> <sharding:inline-strategy id="orderTableStrategy" sharding-column="order_id" algorithm-expression="t_order_${order_id % 2}" /> <sharding:inline-strategy id="orderItemTableStrategy" sharding-column="order_id" algorithm-expression="t_order_item_${order_id % 2}" /> <sharding:data-source id="shardingDataSource"> <!-- configDataSource爲不參數分庫分表的全局表的默認數據源,好比系統參數 --> <sharding:sharding-rule data-source-names="dataSource_0,dataSource_1,globalDataSource" default-data-source-name="globalDataSource"> <sharding:table-rules> <!-- 分庫+分表 --> <sharding:table-rule logic-table="t_order" actual-data-nodes="dataSource_${0..1}.t_order_${0..1}" database-strategy-ref="databaseStrategy" table-strategy-ref="orderTableStrategy" /> <sharding:table-rule logic-table="t_order_item" actual-data-nodes="dataSource_${0..1}.t_order_item_${0..1}" database-strategy-ref="databaseStrategy" table-strategy-ref="orderItemTableStrategy" />
<!-- 分庫不分表 --> <sharding:table-rule logic-table="t_order" database-strategy-ref="databaseStrategy"/> <!-- 廣播表 --> <sharding:table-rule logic-table="t_dict"/> </sharding:table-rules> <!-- 綁定表規則列表,表示分庫分表的規則相同,這樣萬一涉及到多個分片的查詢,sharding-jdbc就能夠肯定分庫之間不須要沒必要要的二次關聯,全部的查詢都應該如此 --> <sharding:binding-table-rules> <sharding:binding-table-rule logic-tables="t_order,t_order_item"/> </sharding:binding-table-rules> </sharding:sharding-rule> </sharding:data-source> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="shardingDataSource" /> </bean> <!-- 使用annotation 自動註冊bean, 並保證@Required、@Autowired的屬性被注入 --> <tx:annotation-driven transaction-manager="transactionManager"/> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:mybatis.xml"/> <property name="dataSource" ref="shardingDataSource"/> <property name="mapperLocations"> <list> <value>classpath*:sqlmap/com/yidoo/k3c/**/*.xml</value> </list> </property> </bean>
HINT分片策略的使用
Hint分片指的是對於分片字段非SQL決定,而由其餘外置條件決定的場景,可以使用SQL Hint靈活的注入分片字段。舉個實例的例子,咱們都知道mysql insert values相對於循環插入來講,性能差距基本上能夠說是網絡延時的倍數,好比說插入1000條記錄,網絡來回1ms,1000條就得1m,若是是跨網段的,延時就更長了。而sharding-jdbc和Mycat都不支持多values(sharding-jdbc 3.x支持,但尚未正式發佈),有些SQL語句比較複雜好比說有(a = var or b = var) and member_id = N,這個時候druid sqlparser並不支持的時候,雖然能夠經過多數據源解決,可是咱們不但願架構搞得太複雜,這種狀況下,能夠經過sharding-jdbc的Hint分片策略來實現各類sharding-jdbc不支持的語法的限制。由於Hint分片策略是繞過SQL解析的,因此對於這些比較複雜的須要分片的非DSS查詢,採用Hint分片策略性能可能會更好。一樣,咱們仍是使用mybatis做爲數據庫訪問層做爲例子,對於Hint分片策略,帖子大都是基於1.x版本進行源碼分析路由,順帶說起,基本上都是瞎扯,同時在用法上sharding-jdbc-example沒有說起。由於咱們實際要用,因此筆者基於2.0.3版本進行了完整的測試確保結果符合咱們的預期。
首先定義一個hint策略,hint策略須要實現io.shardingjdbc.core.api.algorithm.sharding.hint.HintShardingAlgorithm接口。
<sharding:hint-strategy id="hintDatabaseStrategy" algorithm-class="com.yidoo.common.route.hint.HintShardingAlgorithm"/>
package com.yidoo.common.route.hint; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.druid.util.StringUtils; import com.yidoo.utils.JacksonHelper; import io.shardingjdbc.core.api.algorithm.sharding.ListShardingValue; import io.shardingjdbc.core.api.algorithm.sharding.ShardingValue; public class HintShardingAlgorithm implements io.shardingjdbc.core.api.algorithm.sharding.hint.HintShardingAlgorithm { private static final Logger logger = LoggerFactory.getLogger(HintShardingAlgorithm.class); @Override public Collection<String> doSharding(Collection<String> availableTargetNames, ShardingValue shardingValue) { logger.debug("shardingValue=" + JacksonHelper.toJSON(shardingValue)); logger.debug("availableTargetNames=" + JacksonHelper.toJSON(availableTargetNames)); List<String> shardingResult = new ArrayList<>(); availableTargetNames.forEach(targetName -> { String suffix = targetName.substring(targetName.lastIndexOf("_")+1); if(StringUtils.isNumber(suffix)) {
// hint分片算法的ShardingValue有兩種具體類型: ListShardingValue和RangeShardingValue,取決於io.shardingjdbc.core.api.HintManager.addDatabaseShardingValue(String, String, ShardingOperator, Comparable<?>...)的時候,ShardingOperator的類型。見下文。 ListShardingValue<Integer> tmpSharding = (ListShardingValue<Integer>) shardingValue; tmpSharding.getValues().forEach(value -> { if (value % 2 == Integer.parseInt(suffix)) { shardingResult.add(targetName); } }); } }); return shardingResult; } }
@SuppressWarnings("unchecked") private ShardingValue getShardingValue(final String logicTable, final String shardingColumn, final ShardingOperator operator, final Comparable<?>[] values) { Preconditions.checkArgument(null != values && values.length > 0); switch (operator) { case EQUAL: case IN: return new ListShardingValue(logicTable, shardingColumn, Arrays.asList(values)); case BETWEEN: return new RangeShardingValue(logicTable, shardingColumn, Range.range(values[0], BoundType.CLOSED, values[1], BoundType.CLOSED)); default: throw new UnsupportedOperationException(operator.getExpression()); } }
sharding:sharding-rule節點上定義屬性default-database-strategy-ref爲hintDatabaseStrategy,以下:
<sharding:sharding-rule data-source-names="dataSource_0,dataSource_1,globalDataSource" default-database-strategy-ref="hintDatabaseStrategy" default-data-source-name="globalDataSource">
不用定義<sharding:table-rules>。
接下去在執行sql語句前設置分片值便可,以下:
UnitQuery treq = new UnitQuery(); Unit record1 = new Unit(); record1.setUnitName("強制指定Hint-1"); record1.setRemark1(" "); record1.setInputMan("系統管理員"); Unit record2 = new Unit(); record2.setUnitName("強制指定Hint-3"); record2.setRemark1(" "); record2.setInputMan("系統管理員"); List<Unit> records = new ArrayList<>(); records.add(record1); records.add(record2); HintManager hintManager = HintManager.getInstance(); hintManager.setDatabaseShardingValue(1); // hintManager.addDatabaseShardingValue("t_Unit","user_id",1); unitMapper.insertValues(records); hintManager.close(); hintManager = HintManager.getInstance(); // 必須先clear,從新拿到instance hintManager.setDatabaseShardingValue(1); List<Unit> unitList = unitMapper.selectByHint(treq); logger.info("強制指定Hint-1提示" + JacksonHelper.toJSON(unitList)); hintManager.close(); Unit record3 = new Unit(); record3.setUnitName("強制指定Hint-2"); record3.setRemark1(" "); record3.setInputMan("系統管理員"); Unit record4 = new Unit(); record4.setUnitName("強制指定Hint-4"); record4.setRemark1(" "); record4.setInputMan("系統管理員"); records.clear(); records.add(record3); records.add(record4); hintManager = HintManager.getInstance(); hintManager.setDatabaseShardingValue(2); // hintManager.addDatabaseShardingValue("t_Unit","user_id",2); unitMapper.insertValues(records); hintManager.close(); hintManager = HintManager.getInstance(); unitList = unitMapper.selectByHint(treq); logger.info("強制指定Hint-2提示" + JacksonHelper.toJSON(unitList)); hintManager.close();
mapper中以下:
<insert id="insertValues"> insert into t_Unit( unit_name, remark1, input_man, input_date ) values <foreach collection="list" item="item" index="index" separator=","> ( #{item.unitName}, #{item.remark1}, #{item.inputMan}, now() ) </foreach> </insert> <select id="selectByHint" resultType="com.yidoo.k3c.global.model.pojo.Unit"> select * from t_Unit </select>
執行前:
執行後:
日誌:
[] 2018-07-05 10:10:21 [61125] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) Logic SQL: insert into t_Unit( unit_name, remark1, input_man, input_date ) values ( ?, ?, ?, now() ) , ( ?, ?, ?, now() ) [] 2018-07-05 10:10:21 [61125] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) SQLStatement: DMLStatement(super=AbstractSQLStatement(type=DML, tables=Tables(tables=[]), conditions=Conditions(conditions={}), sqlTokens=[], parametersIndex=0)) [] 2018-07-05 10:10:21 [61125] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) Actual SQL: dataSource_1 ::: insert into t_Unit( unit_name, remark1, input_man, input_date ) values ( ?, ?, ?, now() ) , ( ?, ?, ?, now() ) ::: [強制指定Hint-1, , 系統管理員, 強制指定Hint-3, , 系統管理員] [] 2018-07-05 10:10:21 [61514] [c.a.d.p.PreparedStatementPool]-[DEBUG] localhost-startStop-1 com.alibaba.druid.pool.PreparedStatementPool.put(PreparedStatementPool.java:129) {conn-110005, pstmt-120000} enter cache [] 2018-07-05 10:10:21 [61515] [o.m.s.SqlSessionUtils]-[DEBUG] localhost-startStop-1 org.mybatis.spring.SqlSessionUtils.closeSqlSession(SqlSessionUtils.java:168) Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2b3a6ec5] [] 2018-07-05 10:10:21 [61515] [o.s.j.d.DataSourceUtils]-[DEBUG] localhost-startStop-1 org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection(DataSourceUtils.java:329) Returning JDBC Connection to DataSource [] 2018-07-05 10:10:21 [61519] [o.m.s.SqlSessionUtils]-[DEBUG] localhost-startStop-1 org.mybatis.spring.SqlSessionUtils.getSqlSession(SqlSessionUtils.java:104) Creating a new SqlSession [] 2018-07-05 10:10:21 [61519] [o.m.s.SqlSessionUtils]-[DEBUG] localhost-startStop-1 org.mybatis.spring.SqlSessionUtils.getSqlSession(SqlSessionUtils.java:140) SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@41aefec2] was not registered for synchronization because synchronization is not active [] 2018-07-05 10:10:21 [61523] [o.s.j.d.DataSourceUtils]-[DEBUG] localhost-startStop-1 org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:110) Fetching JDBC Connection from DataSource [] 2018-07-05 10:10:21 [61523] [o.m.s.t.SpringManagedTransaction]-[DEBUG] localhost-startStop-1 org.mybatis.spring.transaction.SpringManagedTransaction.openConnection(SpringManagedTransaction.java:86) JDBC Connection [io.shardingjdbc.core.jdbc.core.connection.ShardingConnection@5e996b5d] will not be managed by Spring [] 2018-07-05 10:10:21 [61528] [i.s.c.r.t.h.DatabaseHintRoutingEngine]-[DEBUG] localhost-startStop-1 io.shardingjdbc.core.routing.type.hint.DatabaseHintRoutingEngine.route(DatabaseHintRoutingEngine.java:55) Before database sharding only db:[dataSource_0, dataSource_1, globalDataSource] sharding values: ListShardingValue(logicTableName=DB_TABLE_NAME, columnName=DB_COLUMN_NAME, values=[1]) [] 2018-07-05 10:10:21 [61529] [i.s.c.r.t.h.DatabaseHintRoutingEngine]-[DEBUG] localhost-startStop-1 io.shardingjdbc.core.routing.type.hint.DatabaseHintRoutingEngine.route(DatabaseHintRoutingEngine.java:59) After database sharding only result: [dataSource_1] [] 2018-07-05 10:10:21 [61530] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) Logic SQL: select * from t_Unit [] 2018-07-05 10:10:21 [61530] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) SQLStatement: SelectStatement(super=DQLStatement(super=AbstractSQLStatement(type=DQL, tables=Tables(tables=[]), conditions=Conditions(conditions={}), sqlTokens=[], parametersIndex=0)), containStar=false, selectListLastPosition=0, groupByLastPosition=0, items=[], groupByItems=[], orderByItems=[], limit=null, subQueryStatement=null) [] 2018-07-05 10:10:21 [61530] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) Actual SQL: dataSource_1 ::: select * from t_Unit [] 2018-07-05 10:10:21 [61576] [c.a.d.p.PreparedStatementPool]-[DEBUG] localhost-startStop-1 com.alibaba.druid.pool.PreparedStatementPool.put(PreparedStatementPool.java:129) {conn-110005, pstmt-120001} enter cache [] 2018-07-05 10:10:21 [61577] [o.m.s.SqlSessionUtils]-[DEBUG] localhost-startStop-1 org.mybatis.spring.SqlSessionUtils.closeSqlSession(SqlSessionUtils.java:168) Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@41aefec2] [] 2018-07-05 10:10:21 [61578] [o.s.j.d.DataSourceUtils]-[DEBUG] localhost-startStop-1 org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection(DataSourceUtils.java:329) Returning JDBC Connection to DataSource [] 2018-07-05 10:10:21 [61582] [c.y.c.s.ParamexService]-[INFO] localhost-startStop-1 com.yidoo.common.service.ParamexService.afterPropertiesSet(ParamexService.java:79) 強制指定Hint-1提示[{"unitName":"體積","remark1":" ","inputMan":"系統管理員","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"容量","remark1":" ","inputMan":"系統管理員","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"廣播","remark1":" ","inputMan":"系統管理員","inputDate":1529984218000,"updateMan":null,"updateDate":1529984218000},{"unitName":"強制指定Hint-1","remark1":" ","inputMan":"系統管理員","inputDate":1530756621000,"updateMan":null,"updateDate":1530756621000},{"unitName":"強制指定Hint-3","remark1":" ","inputMan":"系統管理員","inputDate":1530756621000,"updateMan":null,"updateDate":1530756621000},{"unitName":"數量","remark1":" ","inputMan":"系統管理員","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"重量","remark1":" ","inputMan":"系統管理員","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"長度","remark1":" ","inputMan":"系統管理員","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"面積","remark1":" ","inputMan":"系統管理員","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000}]
[] 2018-07-05 10:10:21 [61585] [i.s.c.r.t.h.DatabaseHintRoutingEngine]-[DEBUG] localhost-startStop-1 io.shardingjdbc.core.routing.type.hint.DatabaseHintRoutingEngine.route(DatabaseHintRoutingEngine.java:55) Before database sharding only db:[dataSource_0, dataSource_1, globalDataSource] sharding values: ListShardingValue(logicTableName=DB_TABLE_NAME, columnName=DB_COLUMN_NAME, values=[2]) [] 2018-07-05 10:10:21 [61586] [i.s.c.r.t.h.DatabaseHintRoutingEngine]-[DEBUG] localhost-startStop-1 io.shardingjdbc.core.routing.type.hint.DatabaseHintRoutingEngine.route(DatabaseHintRoutingEngine.java:59) After database sharding only result: [dataSource_0] [] 2018-07-05 10:10:21 [61587] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) Logic SQL: insert into t_Unit( unit_name, remark1, input_man, input_date ) values ( ?, ?, ?, now() ) , ( ?, ?, ?, now() ) [] 2018-07-05 10:10:21 [61587] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) SQLStatement: DMLStatement(super=AbstractSQLStatement(type=DML, tables=Tables(tables=[]), conditions=Conditions(conditions={}), sqlTokens=[], parametersIndex=0)) [] 2018-07-05 10:10:21 [61587] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) Actual SQL: dataSource_0 ::: insert into t_Unit( unit_name, remark1, input_man, input_date ) values ( ?, ?, ?, now() ) , ( ?, ?, ?, now() ) ::: [強制指定Hint-2, , 系統管理員, 強制指定Hint-4, , 系統管理員] [] 2018-07-05 10:10:21 [61624] [c.a.d.p.PreparedStatementPool]-[DEBUG] localhost-startStop-1 com.alibaba.druid.pool.PreparedStatementPool.put(PreparedStatementPool.java:129) {conn-10005, pstmt-20000} enter cache [] 2018-07-05 10:10:21 [61625] [o.m.s.SqlSessionUtils]-[DEBUG] localhost-startStop-1 org.mybatis.spring.SqlSessionUtils.closeSqlSession(SqlSessionUtils.java:168) Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@767f7324] [] 2018-07-05 10:10:21 [61625] [o.s.j.d.DataSourceUtils]-[DEBUG] localhost-startStop-1 org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection(DataSourceUtils.java:329) Returning JDBC Connection to DataSource [] 2018-07-05 10:10:21 [61625] [o.m.s.SqlSessionUtils]-[DEBUG] localhost-startStop-1 org.mybatis.spring.SqlSessionUtils.getSqlSession(SqlSessionUtils.java:104) Creating a new SqlSession [] 2018-07-05 10:10:21 [61626] [o.m.s.SqlSessionUtils]-[DEBUG] localhost-startStop-1 org.mybatis.spring.SqlSessionUtils.getSqlSession(SqlSessionUtils.java:140) SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4c582b4c] was not registered for synchronization because synchronization is not active [] 2018-07-05 10:10:21 [61626] [o.s.j.d.DataSourceUtils]-[DEBUG] localhost-startStop-1 org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:110) Fetching JDBC Connection from DataSource [] 2018-07-05 10:10:21 [61627] [o.m.s.t.SpringManagedTransaction]-[DEBUG] localhost-startStop-1 org.mybatis.spring.transaction.SpringManagedTransaction.openConnection(SpringManagedTransaction.java:86) JDBC Connection [io.shardingjdbc.core.jdbc.core.connection.ShardingConnection@b551009] will not be managed by Spring [] 2018-07-05 10:10:21 [61769] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) Logic SQL: select * from t_Unit [] 2018-07-05 10:10:21 [61770] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) SQLStatement: SelectStatement(super=DQLStatement(super=AbstractSQLStatement(type=DQL, tables=Tables(tables=[Table(name=t_Unit, alias=Optional.absent())]), conditions=Conditions(conditions={}), sqlTokens=[TableToken(beginPosition=14, originalLiterals=t_Unit)], parametersIndex=0)), containStar=true, selectListLastPosition=9, groupByLastPosition=0, items=[StarSelectItem(owner=Optional.absent())], groupByItems=[], orderByItems=[], limit=null, subQueryStatement=null) [] 2018-07-05 10:10:21 [61771] [Sharding-JDBC-SQL]-[INFO] localhost-startStop-1 io.shardingjdbc.core.util.SQLLogger.log(SQLLogger.java:59) Actual SQL: globalDataSource ::: select * from t_Unit [] 2018-07-05 10:10:22 [61782] [c.a.d.p.PreparedStatementPool]-[DEBUG] localhost-startStop-1 com.alibaba.druid.pool.PreparedStatementPool.put(PreparedStatementPool.java:129) {conn-210005, pstmt-220000} enter cache [] 2018-07-05 10:10:22 [61782] [o.m.s.SqlSessionUtils]-[DEBUG] localhost-startStop-1 org.mybatis.spring.SqlSessionUtils.closeSqlSession(SqlSessionUtils.java:168) Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4c582b4c] [] 2018-07-05 10:10:22 [61783] [o.s.j.d.DataSourceUtils]-[DEBUG] localhost-startStop-1 org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection(DataSourceUtils.java:329) Returning JDBC Connection to DataSource [] 2018-07-05 10:10:22 [61785] [c.y.c.s.ParamexService]-[INFO] localhost-startStop-1 com.yidoo.common.service.ParamexService.afterPropertiesSet(ParamexService.java:102) 強制指定Hint-2提示[{"unitName":"體積","remark1":" ","inputMan":"系統管理員","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"容量","remark1":" ","inputMan":"系統管理員","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"廣播","remark1":" ","inputMan":"系統管理員","inputDate":1529984218000,"updateMan":null,"updateDate":1529984218000},{"unitName":"強制指定Hint-1","remark1":" ","inputMan":"系統管理員","inputDate":1530701874000,"updateMan":null,"updateDate":1530701874000},{"unitName":"強制指定Hint-2","remark1":" ","inputMan":"系統管理員","inputDate":1530701874000,"updateMan":null,"updateDate":1530701874000},{"unitName":"強制指定Hint-3","remark1":" ","inputMan":"系統管理員","inputDate":1530701874000,"updateMan":null,"updateDate":1530701874000},{"unitName":"強制指定Hint-4","remark1":" ","inputMan":"系統管理員","inputDate":1530701874000,"updateMan":null,"updateDate":1530701874000},{"unitName":"數量","remark1":" ","inputMan":"系統管理員","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"重量","remark1":" ","inputMan":"系統管理員","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"長度","remark1":" ","inputMan":"系統管理員","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000},{"unitName":"面積","remark1":" ","inputMan":"系統管理員","inputDate":1528166814000,"updateMan":null,"updateDate":1528166814000}]
注意點
一、使用了sharding-jdbc以後,select from TableName會被轉換爲select from tablename,也就是轉小寫,這是在代碼中處理的,若是不但願轉換(sql標準是大小寫不敏感的,主要是mysql linux下lower-case-table-names=1這個特例會區分大小寫,並且mysql 8.0不容許數據庫初始化和啓動的值不一致,5.7以前是能夠的,https://bugs.mysql.com/bug.php?id=90695),則須要本身編譯sharding-jdbc-core(咱們編譯了一版sharding-jdbc-core-2.0.3.zip),並更改getTableTokens中的轉小寫的代碼(這應該算是個bug),2.0.3版本代碼位置爲:
// io.shardingjdbc.core.rewrite.SQLRewriteEngine.getTableTokens(TableUnit)中對sql語句中的表名作了toLowerCase()致使的,以下: private Map<String, String> getTableTokens(final TableUnit tableUnit) { String logicTableName = tableUnit.getLogicTableName().toLowerCase(); Map<String, String> tableTokens = new HashMap<>(); tableTokens.put(logicTableName, tableUnit.getActualTableName()); Optional<BindingTableRule> bindingTableRule = shardingRule.findBindingTableRule(logicTableName); if (bindingTableRule.isPresent()) { tableTokens.putAll(getBindingTableTokens(tableUnit, bindingTableRule.get())); } return tableTokens; }
二、必定要設置context:property-placeholder的ignore-unresolvable屬性爲true,即<context:property-placeholder location="classpath:property/*.properties" ignore-unresolvable="true" />,不然會報沒法解析佔位符。
三、咱們在調試的時候遇到個問題,若是eclipse中同時把sharding-jdbc的源代碼引入進來,war啓動的時候就會報sharding.xsd找不到,而後啓動出錯。
參考:
各非行內ShardingStrategy的用法能夠參考https://www.cnblogs.com/mr-yang-localhost/p/8313360.html
其餘:
https://wenku.baidu.com/view/ddeaed18910ef12d2bf9e74a.html
https://www.oschina.net/question/2918182_2280300
https://www.oschina.net/news/88860/sharding-jdbc-1-5-4-released
http://www.infoq.com/cn/news/2017/12/Sharding-JDBC-2
http://cmsblogs.com/?p=2542
http://shardingjdbc.io/document/legacy/2.x/en/00-overview/
https://www.oschina.net/question/2356021_2264290
https://blog.csdn.net/vkingnew/article/details/80613043
https://www.jianshu.com/p/dd47dd3b1f6b
https://www.zhihu.com/question/64709787
sharding-jdbc不支持SQL
https://blog.csdn.net/Farrell_zeng/article/details/52958189
mycat不支持SQL
https://blog.csdn.net/educast/article/details/50013355