Sharding-JDBC分庫分表使用例項
Sharding-JDBC是噹噹開源的用於分庫分表的基礎類庫。定位輕量級java框架,可以通過客戶端直接連線資料庫,只需要在增加額外的資料來源配置就可以輕鬆實現完整的分庫分表功能。
Sharding-JDBC是一個開源的適用於微服務的分散式資料訪問基礎類庫,它始終以雲原生的基礎開發套件為目標。
目前Sharding-JDBC已經實現的功能包括(最新穩定版本為2.0.0.M1
):
- 分庫分表
- 讀寫分離
- 分散式主鍵
- 柔性事務
- 分散式治理能力(2.0新功能),具體包括:
- 配置集中化與動態化,可支援資料來源、表與分片策略的動態切換
- 客戶端的資料庫治理,資料來源失效自動切換
- 基於Open Tracing協議的APM資訊輸出
下面的例子都是基於Spring Boot開發:Spring Boot + Mybatis + Druid + Sharding-Jdbc
工程結構
Application
是專案的啟動入口
DataSourceConfig
是資料來源配置,包括如何結合Sharding-Jdbc設定分庫分表
algorithm
下面是設定的分庫分表策略,比如當city_id % 2
為偶數放在庫A,否則放在庫B
UserMapper
是Mybatis的介面,由於採用了全註解配置,所以沒有Mapper檔案
druid
下面是druid的監控頁面配置
pom配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-boot-learning-examples</artifactId>
<groupId >com.rhwayfun</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-boot-mybatis-sharding-jdbc</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>sharding-jdbc-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
使用Sharding-Jdbc需要依賴sharding-jdbc-core
,其他都是Spring Boot相關的依賴。
詳解資料來源配置
/**
* 資料來源配置
*
* @author happyxiaofan
* @since 0.0.1
*/
@Configuration
@ConfigurationProperties(prefix = DataSourceConstants.DATASOURCE_PREFIX)
@MapperScan(basePackages = { DataSourceConstants.MAPPER_PACKAGE }, sqlSessionFactoryRef = "mybatisSqlSessionFactory")
public class DataSourceConfig {
private String url;
private String username;
private String password;
@Bean(name = "mybatisDataSource")
public DataSource getDataSource() throws SQLException {
//設定分庫對映
Map<String, DataSource> dataSourceMap = new HashMap<>(2);
dataSourceMap.put("springboot_0", mybatisDataSource("springboot"));
dataSourceMap.put("springboot_1", mybatisDataSource("springboot2"));
//設定預設庫,兩個庫以上時必須設定預設庫。預設庫的資料來源名稱必須是dataSourceMap的key之一
DataSourceRule dataSourceRule = new DataSourceRule(dataSourceMap, "springboot_0");
//設定分表對映
TableRule userTableRule = TableRule.builder("user")
.generateKeyColumn("user_id") //將user_id作為分散式主鍵
.actualTables(Arrays.asList("user_0", "user_1"))
.dataSourceRule(dataSourceRule)
.build();
//具體分庫分表策略
ShardingRule shardingRule = ShardingRule.builder()
.dataSourceRule(dataSourceRule)
.tableRules(Collections.singletonList(userTableRule))
.databaseShardingStrategy(new DatabaseShardingStrategy("city_id", new ModuloDatabaseShardingAlgorithm()))
.tableShardingStrategy(new TableShardingStrategy("user_id", new ModuloTableShardingAlgorithm())).build();
DataSource dataSource = ShardingDataSourceFactory.createDataSource(shardingRule);
//return new ShardingDataSource(shardingRule);
return dataSource;
}
private DataSource mybatisDataSource(final String dataSourceName) throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(DataSourceConstants.DRIVER_CLASS);
dataSource.setUrl(String.format(url, dataSourceName));
dataSource.setUsername(username);
dataSource.setPassword(password);
/* 配置初始化大小、最小、最大 */
dataSource.setInitialSize(1);
dataSource.setMinIdle(1);
dataSource.setMaxActive(20);
/* 配置獲取連線等待超時的時間 */
dataSource.setMaxWait(60000);
/* 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒 */
dataSource.setTimeBetweenEvictionRunsMillis(60000);
/* 配置一個連線在池中最小生存的時間,單位是毫秒 */
dataSource.setMinEvictableIdleTimeMillis(300000);
dataSource.setValidationQuery("SELECT 'x'");
dataSource.setTestWhileIdle(true);
dataSource.setTestOnBorrow(false);
dataSource.setTestOnReturn(false);
/* 開啟PSCache,並且指定每個連線上PSCache的大小。
如果用Oracle,則把poolPreparedStatements配置為true,
mysql可以配置為false。分庫分表較多的資料庫,建議配置為false */
dataSource.setPoolPreparedStatements(false);
dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
/* 配置監控統計攔截的filters */
dataSource.setFilters("stat,wall,log4j");
return dataSource;
}
/**
* Sharding-jdbc的事務支援
*
* @return
*/
@Bean(name = "mybatisTransactionManager")
public DataSourceTransactionManager mybatisTransactionManager(@Qualifier("mybatisDataSource") DataSource dataSource) throws SQLException {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "mybatisSqlSessionFactory")
public SqlSessionFactory mybatisSqlSessionFactory(@Qualifier("mybatisDataSource") DataSource mybatisDataSource)
throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(mybatisDataSource);
return sessionFactory.getObject();
}
// 省略setter、getter
}
分庫規則,按照city_id
分庫:
如果cityId mod 2 為0,則落在springboot_0,也即是springboot,
如果cityId mod 2 為1,則落在springboot_1,也即是springboot2
分表規則,按照user_id
分表:
如果userId mod 2 為0,則落在表user_0,
如果userId mod 2 為1,則落在表user_1
上面指定了兩個資料庫springboot
和springboot2
,對應的key分別是springboot_0
和springboot_1
,在具體執行資料庫寫入的時候會先根據分庫演算法確定寫入到哪個庫(springboot或者springboot2),再根據分表演算法確定寫入最終哪個表(user_0或user_1
)。所以這裡兩個資料庫都有兩個表,這裡以user
表作為示例,表結構如下:
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL COMMENT '使用者id',
`city_id` int(11) DEFAULT NULL COMMENT '城市id',
`user_name` varchar(15) DEFAULT NULL,
`age` int(11) DEFAULT NULL COMMENT '年齡',
`birth` date DEFAULT NULL COMMENT '生日',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
在設定分表對映的時候,我們將user_id
作為分散式主鍵,但是我們在建立表的時候卻將id作為了自增主鍵,這兩個是什麼關係呢,用MySQL的自增主鍵不行麼。因為同一個邏輯表(這裡的邏輯表是user
)內的不同實際表(這裡的實際表是user_0
和user_1
)之間的自增鍵是無法互相感知的,這樣會造成重複Id的生成。而Sharding-Jdbc的分散式主鍵保證了資料庫進行分庫分表後主鍵(這裡的user_id
)一定是唯一不重複的,這樣就解決了生成重複Id的問題。
注意下面這段程式碼:
ShardingRule shardingRule = ShardingRule.builder()
.dataSourceRule(dataSourceRule)
.tableRules(Collections.singletonList(userTableRule))
.databaseShardingStrategy(new DatabaseShardingStrategy("city_id", new ModuloDatabaseShardingAlgorithm()))
.tableShardingStrategy(new TableShardingStrategy("user_id", new ModuloTableShardingAlgorithm())).build();
這裡指定了具體的分庫分表策略,我們要實現的是根據city_id
分庫,根據user_id
分表。ModuloDatabaseShardingAlgorithm
表示具體分庫的演算法,TableShardingStrategy
表示具體的分表的演算法。
測試
我們可以先假設下,如果插入下面這條資料,按照之前確定的分庫分表規則,肯定是落在springboot2
庫,但是無法實現確定落在哪個表,因為我們將user_id
作為了分散式主鍵,主鍵是由Sharding-Jdbc內部先生成的,所以可能會落在user_0
或user_1
:
@Test
public void getOneSlave() throws Exception {
UserEntity user = new UserEntity();
user.setCityId(1);//1 mod 2 = 1,所以會落在springboot2庫中
user.setUserName("insertTest");
user.setAge(10);
user.setBirth(new Date());
assertTrue(userMapper.insertSlave(user) > 0);
Long userId = user.getUserId();
System.out.println("Generated Key--userId:" + userId + "mod:" + 1 % 2);
UserEntity one = userMapper.getOne(userId);
System.out.println("Generated User:" + one);
assertEquals("insertTest", one.getUserName());
//assertTrue(userMapper.delete(userId) > 0);
}
通過user.getUserId可以獲取Sharding-Jdbc的生成的主鍵,執行這個測試用例,可以看到生成主鍵為131004387576250368
,通過計算131004387576250368 mod 2 = 0,所以這條記錄在表user_0
。
驗證如下: