(本篇博客已於2019-08-28優化更新)mysql
序言:噹噹開發的一箇中間件,sharding-jdbc官網API,據說能用它也能讀寫分離,但這一篇博客主要講述的是分庫分表的功能。算法
初版的分庫分表並非現有的Sharding-JDBC,而是噹噹的一個內部框架ddframe的數據庫模塊,
dd-rdb的其中一項功能就是分庫,沒有分表功能,當時只是作了簡單的SQL解析。後來隨着ddframe被各個
團隊採用,只分庫的需求漸漸不夠用了,而dd-rdb裏面有大量的數據庫ORM相關的東西,爲了使分庫分表這
一核心需求更加純粹,咱們纔將其中的分片的部分單獨提煉出來並命名爲Sharding-JDBC,用於在Java的
JDBC層面提供一層驅動,無縫的處理這方面的需求。
對於關係型數據庫數據量很大的狀況,須要進行水平拆庫和拆表,這種場景很適合使用Sharding-JDBC。
舉例說明:假設有一億數據的用戶庫,放在 MySQL 數據庫裏查詢性能會比較低,而採用水平拆庫,將其分爲10
個庫,根據用戶的ID模10,這樣數據就能比較平均的分在10個庫中,每一個庫只有1000w記錄,查詢性能會大大提
升。分片策略類型很是多,大體分爲Hash + Mod、Range、Tag 等。Sharding-JDBC還提供了讀寫分離的能
力,用於減輕寫庫的壓力。此外,Sharding-JDBC 能夠用在 JPA 場景中,如 JPA、Hibernate、Mybatis,
Spring JDBC Template 等任何 Java 的 ORM 框架。Java 的 ORM 框架也都是採用 JDBC 與數據庫交互。
這也是咱們選擇在JDBC層,而非選擇一個ORM框架進行開發的緣由。咱們但願 Sharding-JDBC能夠儘可能的兼容
全部的Java數據庫訪問層,而且無縫的接入業務應用
1.3.1 JDBC 相關的核心功能,包括分庫分表、讀寫分離、分佈式主鍵等。
1.3.2 和數據庫相關,但不屬於 JDBC 範疇的,將以插件的形式提供,包括柔性事務、數據庫的 HA、
數據庫 Metadata 管理、配置動態化等。
1.3.3 業務或使用友好度相關的,包括多租戶、帳戶安全、Spring 自定義命名空間、Yaml 配置等
勾選Web,Mysql,Mybatis三個模塊。spring
com.dangdang sharding-jdbc-core 1.5.4 org.projectlombok lombok, 此外,還須要安裝lombok插件,而後從新啓動,因此提早安裝。sql
mybatis.config-locations=classpath:mybatis/mybatis-config.xml #datasource spring.devtools.remote.restart.enabled=false #data source1 spring.datasource.test1.driverClassName=com.mysql.jdbc.Driver spring.datasource.test1.jdbc-url=jdbc:mysql://127.0.0.1:3306/test_msg1 spring.datasource.test1.username=root spring.datasource.test1.password=root #data source2 spring.datasource.test2.driverClassName=com.mysql.jdbc.Driver spring.datasource.test2.jdbc-url=jdbc:mysql://127.0.0.1:3306/test_msg2 spring.datasource.test2.username=root spring.datasource.test2.password=root
import com.dangdang.ddframe.rdb.sharding.api.ShardingDataSourceFactory; import com.dangdang.ddframe.rdb.sharding.api.rule.BindingTableRule; import com.dangdang.ddframe.rdb.sharding.api.rule.DataSourceRule; import com.dangdang.ddframe.rdb.sharding.api.rule.ShardingRule; import com.dangdang.ddframe.rdb.sharding.api.rule.TableRule; import com.dangdang.ddframe.rdb.sharding.api.strategy.database.DatabaseShardingStrategy; import com.dangdang.ddframe.rdb.sharding.api.strategy.table.TableShardingStrategy; import com.springboot2.mjt08.strategy.ModuloDatabaseShardingAlgorithm; import com.springboot2.mjt08.strategy.ModuloTableShardingAlgorithm; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; //import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import javax.sql.DataSource; import java.sql.SQLException; import java.util.*; @Configuration @MapperScan(basePackages = "com.springboot2.mjt08.mapper", sqlSessionTemplateRef = "test1SqlSessionTemplate") public class DataSourceConfig { /** * 配置數據源0,數據源的名稱最好要有必定的規則,方便配置分庫的計算規則 * * @return */ @Bean(name = "dataSource0") @ConfigurationProperties(prefix = "spring.datasource.test1") public DataSource dataSource0() { return DataSourceBuilder.create().build(); } /** * 配置數據源1,數據源的名稱最好要有必定的規則,方便配置分庫的計算規則 * * @return */ @Bean(name = "dataSource1") @ConfigurationProperties(prefix = "spring.datasource.test2") public DataSource dataSource1() { return DataSourceBuilder.create().build(); } /** * 配置數據源規則,即將多個數據源交給sharding-jdbc管理,而且能夠設置默認的數據源, * 當表沒有配置分庫規則時會使用默認的數據源 * * @param dataSource0 * @param dataSource1 * @return */ @Bean public DataSourceRule dataSourceRule(@Qualifier("dataSource0") DataSource dataSource0, @Qualifier("dataSource1") DataSource dataSource1) { Map<String, DataSource> dataSourceMap = new HashMap<>(); //設置分庫映射 dataSourceMap.put("dataSource0", dataSource0); dataSourceMap.put("dataSource1", dataSource1); return new DataSourceRule(dataSourceMap, "dataSource0");//設置默認庫,兩個庫以上時必須設置默認庫。默認庫的數據源名稱必須是dataSourceMap的key之一 } /** * 配置數據源策略和表策略,具體策略須要本身實現 * * @param dataSourceRule * @return */ @Bean public ShardingRule shardingRule(DataSourceRule dataSourceRule) { //具體分庫分表策略 TableRule orderTableRule = TableRule.builder("t_order") .actualTables(Arrays.asList("t_order_0", "t_order_1")) .tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm())) .dataSourceRule(dataSourceRule) .build(); //綁定表策略,在查詢時會使用主表策略計算路由的數據源,所以須要約定綁定表策略的表的規則須要一致,能夠必定程度提升效率 List<BindingTableRule> bindingTableRules = new ArrayList<BindingTableRule>(); bindingTableRules.add(new BindingTableRule(Arrays.asList(orderTableRule))); return ShardingRule.builder() .dataSourceRule(dataSourceRule) .tableRules(Arrays.asList(orderTableRule)) .bindingTableRules(bindingTableRules) .databaseShardingStrategy(new DatabaseShardingStrategy("user_id", new ModuloDatabaseShardingAlgorithm())) .tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm())) .build(); } /** * 建立sharding-jdbc的數據源DataSource,MybatisAutoConfiguration會使用此數據源 * * @param shardingRule * @return * @throws SQLException */ @Bean(name = "dataSource") public DataSource shardingDataSource(ShardingRule shardingRule) throws SQLException { return ShardingDataSourceFactory.createDataSource(shardingRule); } /** * 須要手動配置事務管理器 * * @param dataSource * @return */ @Bean public DataSourceTransactionManager transactitonManager(@Qualifier("dataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "test1SqlSessionFactory") @Primary public SqlSessionFactory testSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml")); return bean.getObject(); } @Bean(name = "test1SqlSessionTemplate") @Primary public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }
import com.dangdang.ddframe.rdb.sharding.api.ShardingValue; import com.dangdang.ddframe.rdb.sharding.api.strategy.database.SingleKeyDatabaseShardingAlgorithm; import com.google.common.collect.Range; import java.util.Collection; import java.util.LinkedHashSet; public class ModuloDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<Long> { @Override public String doEqualSharding(Collection<String> databaseNames, ShardingValue<Long> shardingValue) { for (String each : databaseNames) { if (each.endsWith(Long.parseLong(shardingValue.getValue().toString()) % 2 + "")) { return each; } } throw new IllegalArgumentException(); } @Override public Collection<String> doInSharding(Collection<String> databaseNames, ShardingValue<Long> shardingValue) { Collection<String> result = new LinkedHashSet<>(databaseNames.size()); for (Long value : shardingValue.getValues()) { for (String tableName : databaseNames) { if (tableName.endsWith(value % 2 + "")) { result.add(tableName); } } } return result; } @Override public Collection<String> doBetweenSharding(Collection<String> databaseNames, ShardingValue<Long> shardingValue) { Collection<String> result = new LinkedHashSet<>(databaseNames.size()); Range<Long> range = (Range<Long>) shardingValue.getValueRange(); for (Long i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) { for (String each : databaseNames) { if (each.endsWith(i % 2 + "")) { result.add(each); } } } return result; } }
import com.dangdang.ddframe.rdb.sharding.api.ShardingValue; import com.dangdang.ddframe.rdb.sharding.api.strategy.table.SingleKeyTableShardingAlgorithm; import com.google.common.collect.Range; import java.util.Collection; import java.util.LinkedHashSet; public class ModuloTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Long> { @Override public String doEqualSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) { for (String each : tableNames) { if (each.endsWith(shardingValue.getValue() % 2 + "")) { return each; } } throw new IllegalArgumentException(); } @Override public Collection<String> doInSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) { Collection<String> result = new LinkedHashSet<>(tableNames.size()); for (Long value : shardingValue.getValues()) { for (String tableName : tableNames) { if (tableName.endsWith(value % 2 + "")) { result.add(tableName); } } } return result; } @Override public Collection<String> doBetweenSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) { Collection<String> result = new LinkedHashSet<>(tableNames.size()); Range<Long> range = (Range<Long>) shardingValue.getValueRange(); for (Long i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) { for (String each : tableNames) { if (each.endsWith(i % 2 + "")) { result.add(each); } } } return result; } }
package com.springboot2.mjt08.entity; import com.springboot2.mjt08.enums.UserSexEnum; import java.io.Serializable; public class UserEntity implements Serializable { private static final long serialVersionUID = 1L; private Long id; private Long order_id; private Long user_id; private String userName; private String passWord; private UserSexEnum userSex; private String nickName; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Long getOrder_id() { return order_id; } public void setOrder_id(Long order_id) { this.order_id = order_id; } public Long getUser_id() { return user_id; } public void setUser_id(Long user_id) { this.user_id = user_id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassWord() { return passWord; } public void setPassWord(String passWord) { this.passWord = passWord; } public UserSexEnum getUserSex() { return userSex; } public void setUserSex(UserSexEnum userSex) { this.userSex = userSex; } public String getNickName() { return nickName; } public void setNickName(String nickName) { this.nickName = nickName; } }
@Mapper public interface User1Mapper { List<UserEntity> getAll(); void update(UserEntity user); void insert(UserEntity userEntity); }
public enum UserSexEnum { MAN, WOMAN }
@Slf4j @Service public class User1Service { @Autowired private User1Mapper user1Mapper; public List<UserEntity> getUsers() { List<UserEntity> users = user1Mapper.getAll(); return users; } //@Transactional(value="test1TransactionManager",rollbackFor = Exception.class,timeout=36000) //說明針對Exception異常也進行回滾,若是不標註,則Spring 默認只有拋出 RuntimeException纔會回滾事務 public void updateTransactional(UserEntity user) { try { user1Mapper.insert(user); log.error(String.valueOf(user)); } catch (Exception e) { log.error("find exception!"); throw e; // 事物方法中,若是使用trycatch捕獲異常後,須要將異常拋出,不然事物不回滾。 } } }
@RestController public class UserController { @Autowired private User1Service user1Service; @RequestMapping("/getUsers") public List<UserEntity> getUsers() { List<UserEntity> users = user1Service.getUsers(); return users; } //測試 @RequestMapping(value = "/update1") public String updateTransactional() { UserEntity user2 = new UserEntity(); user2.setId(16L); user2.setUser_id(53L); user2.setOrder_id(5L); user2.setNickName("chengjian"); user2.setPassWord("123445"); user2.setUserName("jj"); user2.setUserSex(UserSexEnum.WOMAN); user1Service.updateTransactional(user2); return "test"; } }
上面這張圖意思是user_id或者order_id 除以2,整除的話去那個庫或者去那個表數據庫
test_msg1 ===t_order_0 ===t_order_1 ===user test_msg2 ===t_order_0 ===t_order_1 ===user
建立兩個庫,個人是test_msg1 test_msg2,而後分別創建兩張數據結構相同的表,可是表名不一樣 ,而後在建立一張user表express
CREATE TABLE `t_order_0` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵id', `order_id` varchar(32) DEFAULT NULL COMMENT '順序編號', `user_id` varchar(32) DEFAULT NULL COMMENT '用戶編號', `userName` varchar(32) DEFAULT NULL COMMENT '用戶名', `passWord` varchar(32) DEFAULT NULL COMMENT '密碼', `user_sex` varchar(32) DEFAULT NULL, `nick_name` varchar(32) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8,分別建立四張表, CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID', `name` varchar(255) DEFAULT NULL COMMENT '名字', `age` int(11) NOT NULL COMMENT '年齡', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='用戶表'
最終,經過user_id 決定分庫,order_id 決定那張表apache
數據分片的數據源建立工廠。api
名稱 | 數據類型 | 說明 |
---|---|---|
dataSourceMap | Map<String, DataSource> | 數據源配置 |
shardingRuleConfig | ShardingRuleConfiguration | 數據分片配置規則 |
props (?) | Properties | 屬性配置 |
configMap (?) | Map<String, Object> | 用戶自定義配置 |
表分片規則配置對象。安全
名稱 | 數據類型 | 說明 |
---|---|---|
logicTable | String | 邏輯表名稱 |
actualDataNodes (?) | String | 由數據源名 + 表名組成,以小數點分隔。多個表以逗號分隔,支持inline表達式。缺省表示使用已知數據源與邏輯表名稱生成數據節點。用於廣播表(即每一個庫中都須要一個一樣的表用於關聯查詢,多爲字典表)或只分庫不分表且全部庫的表結構徹底一致的狀況 |
databaseShardingStrategyConfig (?) | ShardingStrategyConfiguration | 分庫策略,缺省表示使用默認分庫策略 |
tableShardingStrategyConfig (?) | ShardingStrategyConfiguration | 分表策略,缺省表示使用默認分表策略 |
logicIndex (?) | String | 邏輯索引名稱,對於分表的Oracle/PostgreSQL數據庫中DROP INDEX XXX語句,須要經過配置邏輯索引名稱定位所執行SQL的真實分表 |
keyGeneratorColumnName (?) | String | 自增列名稱,缺省表示不使用自增主鍵生成器 |
keyGenerator (?) | KeyGenerator | 自增列值生成器,缺省表示使用默認自增主鍵生成器 |
ShardingStrategyConfiguration的實現類,用於單分片鍵的標準分片場景。
名稱 | 數據類型 | 說明 |
---|---|---|
shardingColumn | String | 分片列名稱 |
preciseShardingAlgorithm | PreciseShardingAlgorithm | 精確分片算法,用於=和IN |
rangeShardingAlgorithm (?) | RangeShardingAlgorithm | 範圍分片算法,用於BETWEEN |
ShardingStrategyConfiguration的實現類,用於多分片鍵的複合分片場景。
名稱 | 數據類型 | 說明 |
---|---|---|
shardingColumns | String | 分片列名稱,多個列以逗號分隔 |
shardingAlgorithm | ComplexKeysShardingAlgorithm | 複合分片算法 |
ShardingStrategyConfiguration的實現類,用於配置行表達式分片策略。
名稱 | 數據類型 | 說明 |
---|---|---|
shardingColumn | String | 分片列名稱 |
algorithmExpression | String | 分片算法行表達式,需符合groovy語法,詳情請參考行表達式 |
ShardingStrategyConfiguration的實現類,用於配置Hint方式分片策略。
名稱 | 數據類型 | 說明 |
---|---|---|
shardingAlgorithm | HintShardingAlgorithm | Hint分片算法 |
屬性配置項,能夠爲如下屬性。
名稱 | 數據類型 | 說明 |
---|---|---|
sql.show (?) | boolean | 是否開啓SQL顯示,默認值: false |
executor.size (?) | int | 工做線程數量,默認值: CPU核數 |
上面配置說明均來自官網Sharding-Sphere