一、实现水平分库
需求说明
水平分库是把同一个表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上。
接下来咱们继续对快速入门中的例子进行完善。
实现步骤
将原有order_db库拆分为order_db_1、order_db_2
CREATE DATABASE order_db_1 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
CREATE DATABASE order_db_2 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
再分别对每个库建立同样的表结构
DROP TABLE IF EXISTS t_order_1;
CREATE TABLE t_order_1 (
`order_id` BIGINT ( 20 ) NOT NULL COMMENT '订单id',
`price` DECIMAL ( 10, 2 ) NOT NULL COMMENT '订单价格',
`user_id` BIGINT ( 20 ) NOT NULL COMMENT '下单用户id',
`status` VARCHAR ( 50 ) CHARACTER
SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单状态',
PRIMARY KEY ( `order_id` ) USING BTREE
) ENGINE = INNODB CHARACTER
SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
DROP TABLE IF EXISTS t_order_2;
CREATE TABLE t_order_2 (
`order_id` BIGINT ( 20 ) NOT NULL COMMENT '订单id',
`price` DECIMAL ( 10, 2 ) NOT NULL COMMENT '订单价格',
`user_id` BIGINT ( 20 ) NOT NULL COMMENT '下单用户id',
`status` VARCHAR ( 50 ) CHARACTER
SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单状态',
PRIMARY KEY ( `order_id` ) USING BTREE
) ENGINE = INNODB CHARACTER
SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
在application.properties中配置分片分库规则
# 端口号
server.port=56000
# 应用名称
spring.application.name=sharding_shuiping_dbsharding
# 表示spring中发现的bean会覆盖之前相同名称的bean
spring.main.allow-bean-definition-overriding=true
# 该配置项,将数据库中带有下划线的字段名称映射成驼峰命名
mybatis.configuration.map-underscore-to-camel-case=true
# 以下配置为sharding-jdbc分片规则配置
# 1.定义数据源
#定义数据源名称
spring.shardingsphere.datasource.names=m1,m2
#数据源连接数据库
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://localhost:3306/order_db_1?useUnicode=true
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=123456
spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://localhost:3306/order_db_2?useUnicode=true
spring.shardingsphere.datasource.m2.username=root
spring.shardingsphere.datasource.m2.password=123456
# 2.指定订单表的数据分布情况,配置数据节点 下面的't_order'是逻辑表,这个名字可以自己起一个 m1.t_order_$->{1..2}表示利用行表达式来实现动态表
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=m$->{1..2}.t_order_$->{1..2}
# 3.指定t_order表中的主键生成策略
#表示告诉sharding-jdbc数据库表中的那一个字段进行主键生成
spring.shardingsphere.sharding.tables.t_order.key-generator.column=order_id
#指定具体的主键生成策略 -> 雪花算法
spring.shardingsphere.sharding.tables.t_order.key-generator.type=SNOWFLAKE
# 4.指定t_order表的分片策略(分片策略=分片键+分片算法):主键为偶数路由到t_order_1中,否则插入到t_order_2中
#告诉分片键是哪一个字段
spring.shardingsphere.sharding.tables.t_order.tableStrategy.inline.shardingColumn=order_id
#确定采取什么样的分片算法
spring.shardingsphere.sharding.tables.t_order.tableStrategy.inline.algorithmExpression=t_order_$->{order_id % 2 + 1}
# 5.指定分库策略
#告诉分库需要采用的分片键是哪一个字段
spring.shardingsphere.sharding.tables.t_order.databaseStrategy.inline.shardingColumn=user_id
#确定采取什么样的分库算法
spring.shardingsphere.sharding.tables.t_order.databaseStrategy.inline.algorithmExpression=m$->{user_id % 2 + 1}
# 6.指定,打开sharding-jdbc提供的sql输出日志配置
spring.shardingsphere.props.sql.show=true
# 日志配置
logging.level.root=info
logging.level.org.springframework=info
logging.level.com.lcc=debug
logging.level.druid.sql=debug
mapper代码如下:
@Mapper
public interface OrderMapper {
/**
* 这里t_order就是sharding-jdbc中的逻辑表
*/
@Insert("insert into t_order(price,user_id,status) values(#{price},#{userId},#{status})")
public int insertOrder(Order order);
@Select("<script>" +
"select " +
"* " +
"from t_order t " +
"where t.order_id in " +
"<foreach collection='orderIds' item='id' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
" AND user_id = #{userId}" +
"</script>")
public List<Map> selectListByUserIdAndOrderIds(@Param("userId") Long userId,@Param("orderIds") List<Long> orderIds);
}
测试代码如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class ShardingShuipingDBShardingApplicationTests {
@Autowired
private OrderMapper orderMapper;
@Test
public void insertDBShardingTest(){
for (int i = 0; i < 10; i++) {
Order order = new Order();
order.setStatus("success_"+i);
order.setUserId(1L);
order.setPrice(new BigDecimal((i+1)*5));
int result = orderMapper.insertOrder(order);
System.out.println(result);
}
for (int i = 0; i < 10; i++) {
Order order = new Order();
order.setStatus("success_"+i);
order.setUserId(2L);
order.setPrice(new BigDecimal((i+1)*5));
int result = orderMapper.insertOrder(order);
System.out.println(result);
}
}
@Test
public void selectDBShardingTest(){
List<Long> orderIds = new ArrayList<>();
orderIds.add(1074752838191546368L);
orderIds.add(1074752838183157761L);
Long userId = 2L;
List<Map> maps = orderMapper.selectListByUserIdAndOrderIds(userId,orderIds);
for (Map map : maps) {
System.out.println(map);
}
}
}
二、实现垂直分库
需求说明
是指按照业务将表进行分类,分布到不同的数据库上面,每个库可以放在不同的服务器上,它的核心理念是专库专用。接下来看一下在单库中实现垂直分库操作。
实现步骤
新增user数据库
#创建数据库
CREATE DATABASE user_db CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
#创建表
DROP TABLE IF EXISTS t_user;
CREATE TABLE t_user (
user_id BIGINT ( 20 ) NOT NULL COMMENT '用户id',
fullname VARCHAR ( 255 ) CHARACTER
SET 'utf8' COLLATE 'utf8_general_ci' NOT NULL COMMENT '用户姓名',
user_type CHAR ( 1 ) DEFAULT NULL COMMENT '用户类型',
PRIMARY KEY ( user_id ) USING BTREE
) ENGINE = INNODB CHARACTER
SET = 'utf8' COLLATE = 'utf8_general_ci' ROW_FORMAT = Dynamic;
在application.properties中配置分片分库规则
# 端口号
server.port=56000
# 应用名称
spring.application.name=sharding_chuizhi_dbsharding
# 表示spring中发现的bean会覆盖之前相同名称的bean
spring.main.allow-bean-definition-overriding=true
# 该配置项,将数据库中带有下划线的字段名称映射成驼峰命名
mybatis.configuration.map-underscore-to-camel-case=true
# 以下配置为sharding-jdbc分片规则配置
# 1.定义数据源
#定义数据源名称
spring.shardingsphere.datasource.names=m1,m2
#order数据源连接数据库
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://localhost:3306/order_db?useUnicode=true
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=123456
#user数据源连接数据库
spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://localhost:3306/user_db?useUnicode=true
spring.shardingsphere.datasource.m2.username=root
spring.shardingsphere.datasource.m2.password=123456
# 2.指定订单表的数据分布情况,配置数据节点 下面的't_order'是逻辑表,这个名字可以自己起一个 m1.t_order_$->{1..2}表示利用行表达式来实现动态表
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=m1.t_order_$->{1..2}
# 2.1 指定t_order表中的主键生成策略
#表示告诉sharding-jdbc数据库表中的那一个字段进行主键生成
spring.shardingsphere.sharding.tables.t_order.key-generator.column=order_id
#指定具体的主键生成策略 -> 雪花算法
spring.shardingsphere.sharding.tables.t_order.key-generator.type=SNOWFLAKE
# 2.2 指定t_order表的分片策略(分片策略=分片键+分片算法):主键为偶数路由到t_order_1中,否则插入到t_order_2中
#告诉分片键是哪一个字段
spring.shardingsphere.sharding.tables.t_order.tableStrategy.inline.shardingColumn=order_id
#确定采取什么样的分片算法
spring.shardingsphere.sharding.tables.t_order.tableStrategy.inline.algorithmExpression=t_order_$->{order_id % 2 + 1}
# 3 指定订单表的数据分布情况,配置数据节点 下面的't_order'是逻辑表,这个名字可以自己起一个 m1.t_order_$->{1..2}表示利用行表达式来实现动态表
spring.shardingsphere.sharding.tables.t_user.actual-data-nodes=m2.t_user
# 3.1 指定t_user表中的主键生成策略
#表示告诉sharding-jdbc数据库表中的那一个字段进行主键生成
spring.shardingsphere.sharding.tables.t_user.key-generator.column=user_id
#指定具体的主键生成策略 -> 雪花算法
spring.shardingsphere.sharding.tables.t_user.key-generator.type=SNOWFLAKE
# 3.2 指定t_user表的分片策略(分片策略=分片键+分片算法):主键为偶数路由到t_order_1中,否则插入到t_order_2中
#告诉分片键是哪一个字段
spring.shardingsphere.sharding.tables.t_user.tableStrategy.inline.shardingColumn=user_id
#确定采取什么样的分片算法
spring.shardingsphere.sharding.tables.t_user.tableStrategy.inline.algorithmExpression=t_user
# 4.指定,打开sharding-jdbc提供的sql输出日志配置
spring.shardingsphere.props.sql.show=true
# 日志配置
logging.level.root=info
logging.level.org.springframework=info
logging.level.com.lcc=debug
logging.level.druid.sql=debug
添加userMapper接口
@Mapper
public interface UserMapper {
/**
* 这里t_order就是sharding-jdbc中的逻辑表
*/
@Insert("insert into t_user(fullname,user_type) values(#{fullname},#{userType})")
public int insertUser(@Param("fullname") String fullname,@Param("userType") String userType);
@Select("<script>" +
"select " +
"* " +
"from t_user t " +
"where t.user_id in " +
"<foreach collection='userIds' item='id' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
"</script>")
public List<Map> selectListByUserIds(@Param("userIds") List<Long> userIds);
}
添加orderMapper接口
@Mapper
public interface OrderMapper {
/**
* 这里t_order就是sharding-jdbc中的逻辑表
*/
@Insert("insert into t_order(price,user_id,status) values(#{price},#{userId},#{status})")
public int insertOrder(Order order);
@Select("<script>" +
"select " +
"* " +
"from t_order t " +
"where t.order_id in " +
"<foreach collection='orderIds' item='id' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
"</script>")
public List<Map> selectListByOrderIds(@Param("orderIds") List<Long> orderIds);
}
测试代码如下
@RunWith(SpringRunner.class)
@SpringBootTest
public class ShardingChuizhiDBShardingApplicationTests {
@Autowired
private OrderMapper orderMapper;
@Autowired
private UserMapper userMapper;
@Test
public void insertOrderTest(){
for (int i = 0; i < 10; i++) {
Order order = new Order();
order.setStatus("success_"+i);
order.setUserId(1L);
order.setPrice(new BigDecimal((i+1)*5));
int result = orderMapper.insertOrder(order);
System.out.println(result);
}
}
@Test
public void selectOrderTest(){
List<Long> orderIds = new ArrayList<>();
orderIds.add(1074752838191546368L);
orderIds.add(1074752838183157761L);
List<Map> maps = orderMapper.selectListByOrderIds(orderIds);
for (Map map : maps) {
System.out.println(map);
}
}
@Test
public void insertUserTest(){
for (int i = 0; i < 10; i++) {
userMapper.insertUser("lcc"+i,i+"");
}
}
@Test
public void selectUserTest(){
List<Long> userIds = new ArrayList<>();
userIds.add(1074769306459308033L);
userIds.add(1074769306564165632L);
List<Map> maps = userMapper.selectListByUserIds(userIds);
for (Map map : maps) {
System.out.println(map);
}
}
}
可以发现,不同的表会去到不同的数据库
三、实现读写分离
什么是读写分离
面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。 对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。
通过一主多从的配置方式,可以将查询请求均匀的分散到多个数据副本,能够进一步的提升系统的处理能力。 使用多主多从的方式,不但能够提升系统的吞吐量,还能够提升系统的可用性,可以达到在任何一个数据库宕机,甚至磁盘物理损坏的情况下仍然不影响系统的正常运行。
读写分离和分库分表有什么关系呢
读写分离的数据节点中的数据内容是一致的,而水平分片的每个数据节点的数据内容却并不相同。将水平分片和读写分离联合使用,能够更加有效的提升系统的性能。
Sharding-JDBC读写分离则是根据SQL语义的分析,将读操作和写操作分别路由至主库与从库。它提供透明化读写分离,让使用方尽量像使用一个数据库一样使用主从数据库集群。
所以,基于Sharding-jdbc的读写分离只是将写请求路由到一个节点上,读请求路由到另一个节点上,而其中这些数据库节点数据之间的同步由数据库内部或者其他中间件完成,这不是Sharding-jdbc需要完成的。
docker搭建mysql一主一从架构
我们将基于MySQL:5.7.41版本进行搭建
主节点环境搭建
主节点文件夹创建
mkdir /usr/local/mysql_zhu_cong/mysql_master/data
mkdir /usr/local/mysql_zhu_cong/mysql_master/log
mkdir /usr/local/mysql_zhu_cong/mysql_master/conf
主节点文件配置
vim /usr/local/mysql_zhu_cong/mysql_master/conf/my.cnf
文件内容如下:
[mysqld]
character_set_server=utf8
#开启日志
log-bin=mysql-bin
#设置服务id,主从服务不能一致
server-id=1
#设置需要同步的数据库
binlog-do-db=user_db
#屏蔽系统库同步
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
binlog-ignore-db=performance_schema
启动容器
docker run --name mysql_zhu_cong_master \
-e MYSQL_ROOT_PASSWORD=123456 \
-v /usr/local/mysql_zhu_cong/mysql_master/data:/var/lib/mysql \
-v /usr/local/mysql_zhu_cong/mysql_master/log:/var/log/mysql \
-v /usr/local/mysql_zhu_cong/mysql_master/conf:/etc/mysql/conf.d \
-dp 3316:3306 \
mysql:5.7.41
从节点环境搭建
从节点文件夹创建
mkdir /usr/local/mysql_zhu_cong/mysql_slave
#复制主节点信息
cp -r /usr/local/mysql_zhu_cong/mysql_master/data /usr/local/mysql_zhu_cong/mysql_slave/
cp -r /usr/local/mysql_zhu_cong/mysql_master/log /usr/local/mysql_zhu_cong/mysql_slave/
cp -r /usr/local/mysql_zhu_cong/mysql_master/conf /usr/local/mysql_zhu_cong/mysql_slave/
请注意,主从 MySQL下的数据(data)目录下有个文件auto.cnf,文件中定义了uuid,要保证主从数据库实例的 uuid不一样,建议直接删除掉,重启服务后将会重新生成。
rm /usr/local/mysql_zhu_cong/mysql_slave/data/auto.cnf
编辑从节点配置文件
vim /usr/local/mysql_zhu_cong/mysql_slave/conf/my.cnf
文件内容如下:
[mysql]
default_character_set=utf8
[mysqld]
character_set_server=utf8
#开启日志
log-bin=mysql-bin
#设置服务id,主从服务不能一致
server-id=2
#设置需要同步的数据库
replicate_wild_do_table=user_db.%
#屏蔽系统库同步
replicate_wild_ignore_table=mysql.%
replicate_wild_ignore_table=information_schema.%
replicate_wild_ignore_table=performance_schema.%
启动容器
docker run --name mysql_zhu_cong_slave \
-e MYSQL_ROOT_PASSWORD=123456 \
-v /usr/local/mysql_zhu_cong/mysql_slave/data:/var/lib/mysql \
-v /usr/local/mysql_zhu_cong/mysql_slave/log:/var/log/mysql \
-v /usr/local/mysql_zhu_cong/mysql_slave/conf:/etc/mysql/conf.d \
-dp 3326:3306 \
mysql:5.7.41
主从同步搭建
连接上主节点,执行以下命令
--授权主备复制专用账号
GRANT replication SLAVE ON *.* TO 'db_sync'@'%' IDENTIFIED by 'db_sync';
--刷新权限
FLUSH PRIVILEGES;
--确认位点 记录下文件名以及位点
SHOW MASTER STATUS;
得到以下结果:
连接上从节点,执行以下命令
--先停止同步
STOP SLAVE;
--修改从库指向主库,使用上一步记录的文件名以及位点
CHANGE MASTER TO master_host='192.168.149.128',master_port=3316, master_user='db_sync', master_password='db_sync', master_log_file='mysql-bin.000002', master_log_pos=584;
--启动同步
START SLAVE;
--查看从库状态Slave_IO_Running和Slave_SQL_Runing都为yes说明同步成功,如果不为yes,请 检查error_Log,然后排查相关异常
SHOW SLAVE STATUS
在上面的命令中, 第二条命令,其中的master_log_file='mysql-bin.000002', master_log_pos=584两个配置项就是根据上面标红部分叫你记下来的内容
验证
修改master中的user_db,如下:
打开slave中的user_db,我们发现,已经自动将修改后的结果同步过来了
至此,读写分离的MySQL环境已经搭建完毕
Sharding-jdbc代码实现
在application.properties中配置分片分库规则
# 端口号
server.port=56000
# 应用名称
spring.application.name=sharding_duxiefenli
# 表示spring中发现的bean会覆盖之前相同名称的bean
spring.main.allow-bean-definition-overriding=true
# 该配置项,将数据库中带有下划线的字段名称映射成驼峰命名
mybatis.configuration.map-underscore-to-camel-case=true
# 以下配置为sharding-jdbc分片规则配置
# 1.定义数据源
#定义数据源名称
spring.shardingsphere.datasource.names=m0,s0
#order数据源连接数据库
spring.shardingsphere.datasource.m0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m0.url=jdbc:mysql://192.168.149.128:3316/user_db?useUnicode=true
spring.shardingsphere.datasource.m0.username=root
spring.shardingsphere.datasource.m0.password=123456
#user数据源连接数据库
spring.shardingsphere.datasource.s0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.s0.url=jdbc:mysql://192.168.149.128:3326/user_db?useUnicode=true
spring.shardingsphere.datasource.s0.username=root
spring.shardingsphere.datasource.s0.password=123456
# 2.主库从库逻辑数据源定义ds0为user_db,指定谁是主库,谁是从库
spring.shardingsphere.sharding.master-slave-rules.ds0.masterDataSourceName=m0
spring.shardingsphere.sharding.master-slave-rules.ds0.slaveDataSourceNames=s0
# 3.配置分片策略
spring.shardingsphere.sharding.tables.t_user.actualDataNodes=ds0.t_user
#分片键
spring.shardingsphere.sharding.tables.t_user.tableStrategy.inline.shardingColumn=user_id
#分片算法
spring.shardingsphere.sharding.tables.t_user.tableStrategy.inline.algorithmExpression=t_user
# 4.配置主键生成策略
spring.shardingsphere.sharding.tables.t_user.keyGenerator.column=user_id
spring.shardingsphere.sharding.tables.t_user.keyGenerator.type=SNOWFLAKE
# 5.指定,打开sharding-jdbc提供的sql输出日志配置
spring.shardingsphere.props.sql.show=true
# 日志配置
logging.level.root=info
logging.level.org.springframework=info
logging.level.com.lcc=debug
logging.level.druid.sql=debug
userMapper代码如下:
@Mapper
public interface UserMapper {
/**
* 这里t_order就是sharding-jdbc中的逻辑表
*/
@Insert("insert into t_user(fullname,user_type) values(#{fullname},#{userType})")
public int insertUser(@Param("fullname") String fullname,@Param("userType") String userType);
@Select("<script>" +
"select " +
"* " +
"from t_user t " +
"where t.user_id in " +
"<foreach collection='userIds' item='id' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
"</script>")
public List<Map> selectListByUserIds(@Param("userIds") List<Long> userIds);
}
测试代码如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class ShardingDuxiefenliApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void insertUserTest(){
for (int i = 0; i < 10; i++) {
userMapper.insertUser("lcc_duxiefenli_"+i,i+"");
}
}
@Test
public void selectUserTest(){
List<Long> userIds = new ArrayList<>();
userIds.add(1074769306459308033L);
userIds.add(1074769306564165632L);
List<Map> maps = userMapper.selectListByUserIds(userIds);
for (Map map : maps) {
System.out.println(map);
}
}
}