概述
读写分离,简单来说,就是将DML交给主数据库去执行,将更新结果同步至各个从数据库保持主从数据一致,DQL分发给从数据库去查询,从数据库只提供读取查询操作。读写分离特别适用于读多写少的场景下,通过分散读写到不同的数据库实例上来提高性能,缓解单机数据库的压力:
Name | Remark |
---|---|
DQL | 数据查询语言,比如select查询语句 |
DML | 数据操纵语言,比如insert、delete、update更新语句 |
DDL | 数据定义语言,比如create/drop/alter等语句 |
DCL | 数据控制语言,比如grant/rollback/commit等语句 |
Sharding-JDBC是一个开源的分布式数据库中间件解决方案。它在Java的JDBC层以对业务应用零侵入的方式额外提供数据分片,读写分离,柔性事务和分布式治理能力。并在其基础上提供封装了MySQL协议的服务端版本,用于完成对异构语言的支持。
基于JDBC的客户端版本定位为轻量级Java框架,使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
封装了MySQL协议的服务端版本定位为透明化的MySQL代理端,可以使用任何兼容MySQL协议的访问客户端(如:MySQL Command Client, MySQL Workbench等)操作数据,对DBA更加友好。
以上内容摘抄至Sharding-JDBC官网 (http://shardingjdbc.io/docume...
本文主要探讨在SpringBoot环境下如何使用Sharding-JDBC提供的读写分离解决方案;
环境
SpringBoot: 1.5.7.RELEASE,
MybatisPlus: 2.1.4,
Sharding-JDBC: 2.0.0.M2
POM.xml
Sharding-JDBC现已提供相关的Starter, 集成起来非常简单;下面是完整的pom文件(springboot & mysql & mybatis-plus & sharding-jdbc)
4.0.0
com.example
sharding-jdbc-example-with-spring-boot
0.0.1-SNAPSHOT
jar
sharding-jdbc-example-with-spring-boot
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
1.5.7.RELEASE
UTF-8
1.7
1.7
1.0.5
2.1.4
2.7.2
1.2.39
1.4
5.1.30
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-jetty
com.h2database
h2
com.zaxxer
HikariCP
${HikariCP.version}
com.baomidou
mybatisplus-spring-boot-starter
${mybatisplus-spring-boot-starter.version}
com.baomidou
mybatis-plus
${mybatisplus.version}
org.springframework.boot
spring-boot-starter-test
com.alibaba
fastjson
${fastjson.version}
io.shardingjdbc
sharding-jdbc-spring-boot-starter
2.0.0.M2
commons-dbcp
commons-dbcp
${commons-dbcp.version}
mysql
mysql-connector-java
${mysql-connector-java.version}
org.projectlombok
lombok
true
cn.hutool
hutool-all
4.0.12
org.springframework.boot
spring-boot-maven-plugin
org.apache.maven.plugins
maven-compiler-plugin
8
多数据源配置 (application.yml)
server:
port: 10086
sharding:
jdbc:
datasource:
names: ds_master_0,ds_slave_0_1,ds_slave_0_2
ds_master_0:
type: org.apache.commons.dbcp.BasicDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.01:3306/ds_master?useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
ds_slave_0_1:
type: org.apache.commons.dbcp.BasicDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.01:3306/ds_slave_0?useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
ds_slave_0_2:
type: org.apache.commons.dbcp.BasicDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.01:3306/ds_slave_1?useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
config:
# 主从策略
masterslave:
load-balance-algorithm-type: round_robin # 负载策略
name: ds_m_1_s_2
master-data-source-name: ds_master_0
slave-data-source-names: ds_slave_0_1,ds_slave_0_2
sharding:
props:
sql:
show: true
#mybatis
mybatis-plus:
datasource: dataSource
mapper-locations: classpath:/mapper/*Mapper.xml
#实体扫描,多个package用逗号或者分号分隔
typeAliasesPackage: com.example.shardingjdbcexamplewithspringboot.entity
global-config:
#主键类型 0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID";
id-type: 0
#字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
field-strategy: 2
#驼峰下划线转换
db-column-underline: true
#刷新mapper 调试神器
refresh-mapper: true
#逻辑删除配置
logic-delete-value: 0
logic-not-delete-value: 1
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
实际上到这一步就完成了最简单的配置, 为了测试效果, 生成相关的数据库和实体吧:
init data.sql;
建立3个数据库, 分别为主, 从1, 从2, 为了区分数据来源, 在from中指定了节点名称;
CREATE SCHEMA IF NOT EXISTS `ds_master`;
DROP TABLE IF EXISTS `ds_master`.`tb_employee`;
CREATE TABLE `ds_master`.`tb_employee` (
`id` int NOT NULL AUTO_INCREMENT ,
`name` varchar(255) NULL ,
`from` varchar(255) NULL ,
PRIMARY KEY (`id`)
);
INSERT INTO `ds_master`.`tb_employee` VALUES(1,'name1', 'ds_master');
INSERT INTO `ds_master`.`tb_employee` VALUES(2,'name2', 'ds_master');
INSERT INTO `ds_master`.`tb_employee` VALUES(3,'name3', 'ds_master');
####
CREATE SCHEMA IF NOT EXISTS `ds_slave_0`;
DROP TABLE IF EXISTS `ds_slave_0`.`tb_employee`;
CREATE TABLE `ds_slave_0`.`tb_employee` (
`id` int NOT NULL AUTO_INCREMENT ,
`name` varchar(255) NULL ,
`from` varchar(255) NULL ,
PRIMARY KEY (`id`)
);
INSERT INTO `ds_slave_0`.`tb_employee` VALUES(1,'name1', 'ds_slave_0');
INSERT INTO `ds_slave_0`.`tb_employee` VALUES(2,'name2', 'ds_slave_0');
INSERT INTO `ds_slave_0`.`tb_employee` VALUES(3,'name3', 'ds_slave_0');
####
CREATE SCHEMA IF NOT EXISTS `ds_slave_1`;
DROP TABLE IF EXISTS `ds_slave_1`.`tb_employee`;
CREATE TABLE `ds_slave_1`.`tb_employee` (
`id` int NOT NULL AUTO_INCREMENT ,
`name` varchar(255) NULL ,
`from` varchar(255) NULL ,
PRIMARY KEY (`id`)
);
INSERT INTO `ds_slave_1`.`tb_employee` VALUES(1,'name1', 'ds_slave_1');
INSERT INTO `ds_slave_1`.`tb_employee` VALUES(2,'name2', 'ds_slave_1');
INSERT INTO `ds_slave_1`.`tb_employee` VALUES(3,'name3', 'ds_slave_1');
Entity / Mapper / Service:
@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_employee")
public class EmployeeEntity {
@TableId(type = IdType.AUTO)
private Integer id;
@TableField
private String name;
@TableField
private String from;
}
public interface EmployeeMapper extends BaseMapper {
}
单元测试
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ShardingJdbcExampleWithSpringBootApplication.class)
public class ShardingJdbcExampleWithSpringBootApplicationTests {
@Resource
EmployeeMapper employeeMapper;
@Test
public void testMasterSlave() {
// search slave db;
Console.log("search slave db:");
for (int i = 0; i < 4; i++) {
((Runnable) () -> {
Console.log(employeeMapper.selectById(1));
}).run();
}
Console.log("==========================================\n");
EmployeeEntity employeeEntity = EmployeeEntity.builder().name("test").from("write master db").build();
Integer insert = employeeMapper.insert(employeeEntity);
Console.log("write master db: {}", insert > 0); // true
Console.log("==========================================\n");
EmployeeEntity ret = employeeMapper.selectOne(employeeEntity);
Console.log("search by \"write master db\": {}", ret); // null
Console.log("==========================================\n");
// 强制路由,访问masterdb数据
HintManager hintManager = HintManager.getInstance();
hintManager.setMasterRouteOnly();
ret = employeeMapper.selectOne(employeeEntity); //(id=9, name=test, from=write master db)
Console.log("[HintManager]search by \"write master db\": {}", ret);
hintManager.close();
}
}
最终结果
search slave db:
EmployeeEntity(id=1, name=name1, from=ds_slave_0)
EmployeeEntity(id=1, name=name1, from=ds_slave_0)
EmployeeEntity(id=1, name=name1, from=ds_slave_1)
EmployeeEntity(id=1, name=name1, from=ds_slave_0)
==========================================
write master db: true
==========================================
search by "write master db": null
==========================================
[HintManager]search by "write master db": EmployeeEntity(id=11, name=test, from=write master db)
如测试效果一般, Sharding-JDBC可以帮你轻松的实现读写分离, 但是数据同步仍然是需要考虑的问题;