赞
踩
沙丁斯菲尔 - 分片秋
读写分离原理,将数据库的读写操作分布在不同的节点上,写操作在主机上,读操作在从机上,主机执行完写操作后会报数据同步到从机上,完成数据同步
读写分离的基本实现:
根据 SQL 语义的分析
,将读操作和写操作分别路由至主库与从库
。一主多从
的配置方式,可以将查询请求均匀的分散到多个数据副本,能够进一步的提升系统的处理能力。多主多从
的方式,不但能够提升系统的吞吐量,还能够提升系统的可用性,可以达到在任何一个数据库宕机,甚至磁盘物理损坏的情况下仍然不影响系统的正常运行。读写分离会出现一个问题:读的时候可能会不能够及时的读取到最新的数据。因此引入 CAP理论
在一个分布式系统中
,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲。
(可能是丢包,也可能是连接中断,还可能是拥塞)
,系统能够继续“履行职责”(返回错误信息或者提示)在实际设计过程中,每个系统不可能只处理一种数据,而是包含多种类型的数据,有的数据必须选择 CP,有的数据必须选择 AP,分布式系统理论上不可能选择 CA 架构。
因为C要求能够读取到最新的数据,因此从机在未同步主机数据之前是不允许访问的,这与A互相矛盾。并且在实际设计中,P是肯定要满足的,因此设计分布式系统,主打 CP 或者 AP。
垂直分片是按照业务将不同业务的表放在不同的数据库,一个数据库只放一种业务的表,比如订单数据库、积分数据库、用户信息数据库
水平分片是将同一个业务的全部数据,按照某个字段(通常是主键)来放在不同的表(或库)中,比如订单主键奇数表、订单主键偶数表
如果特殊情况下,表内数据行并不多,但是依旧单表内存过大,可能考虑是一张表内字段过多且某些字段数据量巨大,可以再进行垂直分表,将一个表的字段拆分到两个表中存储,拆分出来的两个表拥有共同的主键。
而如果单表内行数据过大,比如超过1KW行数据,就考虑继续水平分片,将同一个表中的数据根据主键的奇偶来分布到两个或者多个表中。
本案例使用windows可视化工具 Docker Desktop 来进行
一主两从,先看常见问题再进行操作
setp1:下载镜像
安装后,windows的命令行就可以直接使用docker命令
直接搜索并下载镜像
setp2:先创建挂载目录,配置目录
conf 目录下先创建 配置文件 my.cnf
[client]
default_character_set=utf-8
[mysqld]
collation_server=utf8_general_ci
character_set_server=utf8
# 服务器唯一id,默认值1
server-id=1
# 设置日志格式,默认值ROW
binlog_format=STATEMENT
setp3:运行镜像,生成容器
命令:
docker run -p 3307:3306 # 因为本地已有3306,因此需要避开3306端口
--privileged=true
--name mysql # 取名
-v E:/Docker/mysql/log:/var/log/mysql ## 挂载日志目录
-v E:/Docker/mysql/data:/var/lib/mysql ## 挂载数据持久化目录
-v E:/Docker/mysql/conf:/etc/mysql/conf.d ## 挂载配置文件
-e MYSQL_ROOT_PASSWORD=root -d mysql ## 配置密码并指定镜像
docker run -p 3307:3306 --privileged=true --name mysql-master -v //e/Docker/mysql/log:/var/log/mysql -v //e/Docker/mysql/data:/var/lib/mysql -v //e/Docker/mysql/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=root -d mysql
启动成功,并使用可视化工具连接
进入到命令行,将密码编码方式修改一下,否则无法使用连接工具连接上,如下的错误:
使用如下命令:
# mysql -uroot -proot mysql: [Warning] World-writable config file '/etc/mysql/conf.d/my.cnf' is ignored. mysql: [Warning] Using a password on the command line interface can be insecure. Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 9 Server version: 8.0.27 MySQL Community Server - GPL Copyright (c) 2000, 2021, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. ## 更改密码编码 mysql> ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root'; Query OK, 0 rows affected (0.00 sec)
可以成功连接
连接成功,主机可以正常使用;
setp4:主机中创建从机用户
-- 创建slave用户
CREATE USER 'chen_slave'@'%';
-- 设置密码
ALTER USER 'chen_slave'@'%' IDENTIFIED WITH mysql_native_password BY 'root';
-- 授予复制权限
GRANT REPLICATION SLAVE ON *.* TO 'chen_slave'@'%' ;
-- 刷新权限
FLUSH PRIVILEGES;
执行完此步骤后不要再操作主服务器MYSQL
,防止主服务器状态值变化
SHOW MASTER STATUS;
记下File
和Position
的值。执行完此步骤后不要再操作主服务器MYSQL,防止主服务器状态值变化。
mysql> SHOW MASTER STATUS;
+---------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+---------------+----------+--------------+------------------+-------------------+
| binlog.000002 | 1347 | | | |
+---------------+----------+--------------+------------------+-------------------+
setp1:准备
从服务器的目录、挂载、配置文件、启动命令、修改密码方式都一样。按照上述进行即可
my.cnf 配置文件不生效,但是内容就是这么个内容
[client]
default_character_set=utf-8
[mysqld]
collation_server=utf8_general_ci
character_set_server=utf8
# 服务器唯一id,每台服务器的id必须不同,如果配置其他从机,注意修改id
server-id=2
# 中继日志名,默认xxxxxxxxxxxx-relay-bin
#relay-log=relay-bin
启动命令
docker run -p 3308:3306 --privileged=true --name mysql-slave1 -v //e/Docker/mysql-slave1/log:/var/log/mysql -v //e/Docker/mysql-slave1/data:/var/lib/mysql -v //e/Docker/mysql-slave1/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=root -d mysql
docker run -p 3309:3306 --privileged=true --name mysql-slave2 -v //e/Docker/mysql-slave2/log:/var/log/mysql -v //e/Docker/mysql-slave2/data:/var/lib/mysql -v //e/Docker/mysql-slave2/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=root -d mysql
setp2:配置主从关系
进入从机命令行
# 这里使用主机创建的 从机账号密码来配置
CHANGE MASTER TO MASTER_HOST='172.27.48.1',
MASTER_USER='chen_slave',MASTER_PASSWORD='root', MASTER_PORT=3307,
# 这里是主机显示的内容
MASTER_LOG_FILE='binlog.000002',MASTER_LOG_POS=1347;
CHANGE MASTER TO MASTER_HOST='172.27.48.1', MASTER_USER='chen_slave',MASTER_PASSWORD='root', MASTER_PORT=3307,MASTER_LOG_FILE='binlog.000002',MASTER_LOG_POS=1347;
-- 启动
START SLAVE;
-- 查看状态(不需要分号)
SHOW SLAVE STATUS\G
**两个关键进程:**下面两个参数都是Yes,则说明主从配置成功!
按照这个方法,必然会出问题,因为配置文件不生效
如果没有全部yes,可以删除掉配置关系从新配置
stop slave; #停止主从配置
reset slave; #删除原本的主从配置
# 问题一,连接不上主机,原因:docker上挂载的配置文件没有生效,两个都用的同一个服务id,出错
# 解决办法,每次手动修改服务id
show variables like 'server_id'; ## 查看当前服务的id
SET GLOBAL server_id=2; # 修改当前服务的id
# 问题二 同一个主机,要用 IPv4 地址 . . . . . . . . . . . . : 172.27.48.1
其他的ip不可以
主机:先查看主机的状态
mysql -uroot -proot
SHOW MASTER STATUS;
从机:然后改变从机的服务id,并重新删除掉原本的连接,再重新连接
mysql -uroot -proot
SET GLOBAL server_id=2;
stop slave; #停止主从配置
reset slave; #删除原本的主从配置
CHANGE MASTER TO MASTER_HOST='172.27.48.1', MASTER_USER='chen_slave',MASTER_PASSWORD='root', MASTER_PORT=3307,MASTER_LOG_FILE='binlog.000007',MASTER_LOG_POS=1686;
-- 启动
START SLAVE;
-- 查看状态(不需要分号)
SHOW SLAVE STATUS\G
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.1.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency> </dependencies>
@TableName("t_user")
@Data
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String uname;
}
# 应用名称 spring.application.name=sharging-jdbc-demo # 开发环境设置 spring.profiles.active=dev # 内存模式 spring.shardingsphere.mode.type=Memory # 配置真实数据源 spring.shardingsphere.datasource.names=master,slave1,slave2 # 配置第 1 个数据源 spring.shardingsphere.datasource.master.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.master.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.master.jdbc-url=jdbc:mysql://192.168.100.201:3306/db_user spring.shardingsphere.datasource.master.username=root spring.shardingsphere.datasource.master.password=123456 # 配置第 2 个数据源 spring.shardingsphere.datasource.slave1.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.slave1.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.slave1.jdbc-url=jdbc:mysql://192.168.100.201:3307/db_user spring.shardingsphere.datasource.slave1.username=root spring.shardingsphere.datasource.slave1.password=123456 # 配置第 3 个数据源 spring.shardingsphere.datasource.slave2.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.slave2.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.slave2.jdbc-url=jdbc:mysql://192.168.100.201:3308/db_user spring.shardingsphere.datasource.slave2.username=root spring.shardingsphere.datasource.slave2.password=123456 # 读写分离类型,如: Static,Dynamic spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.type=Static # 写数据源名称 spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.props.write-data-source-name=master # 读数据源名称,多个从数据源用逗号分隔 spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.props.read-data-source-names=slave1,slave2 # 负载均衡算法名称 spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.load-balancer-name=alg_round # 负载均衡算法配置 # 负载均衡算法类型 spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_round.type=ROUND_ROBIN spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_random.type=RANDOM spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.type=WEIGHT spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.props.slave1=1 spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.props.slave2=2 # 打印SQl spring.shardingsphere.props.sql-show=true
注意会报错,提示命令行过长,需要做一些修改:
然后就可以正常启动了
写入数据:
private final UserMapper userMapper;
/**
* 写入数据的测试
*/
@GetMapping("/testInsert")
public void testInsert() {
User user = new User();
user.setUname("张三丰");
userMapper.insert(user);
}
日志:
日志解析:
## 逻辑sql,表明本条sql语句是向配置文件中的逻辑数据源myds插入数据,
## 具体向哪个表插入,就交给了shardingSphere 框架去依据配置文件中配置的情况进行
Logic SQL: INSERT INTO t_user ( uname ) VALUES ( ? )
##
SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
## 实际的sql语句,经过shardingSohere框架 判断,实际插入的表的信息
Actual SQL: master ::: INSERT INTO t_user ( uname ) VALUES ( ? ) ::: [张三丰]
数据库:
数据都同步了
读取数据,负载均衡
/**
* 读取数据的测试
*/
@GetMapping("/testRead")
public void testRead() {
for (int i = 0; i < 5; i++) {
log.info("-第{}次-", i);
List<User> users = userMapper.selectList(Wrappers.query());
}
}
日志
因为是轮询,因此每次查询的数据库都不同,也就是做了负载均衡。
为了保证主从库间的事务一致性,避免跨服务的分布式事务,ShardingSphere-JDBC的主从模型中,事务中的数据读写均用主库
。
不添加 @Transactional 注解,就还是正常的读写分离,添加了 @Transactional 注解意味着开启了事务,就会把读写都锁定在主机;
开启事务,进行读写
/** * 事务测试, * //开启事务,测试读取 */ @Transactional(rollbackFor = Exception.class) @GetMapping("/testRollBack") public void testTrans() { User user = new User(); user.setUname("铁锤"); userMapper.insert(user); //查询三次 for (int i = 0; i < 3; i++) { log.info("-第{}次-", i); List<User> users = userMapper.selectList(null); log.info("-结果{}-", users); } }
日志
因为事务的开启,将读写两个操作都限定在了主库,而且一个线程内多次去查询相同的数据,会从缓存中拿取;
读取数据时,想要采取不同的负载均衡算法,只需要在配置文件中进行设置即可
轮询
跟上述例子一样
随机
权重
一共访问15次:
slave1 权重配的是1 , 因此出现了 5 次
slave2 权重配的是2 , 因此出现了 10 次
垂直分片就是把不同的业务的数据分到不同数据库的表中
因为引入了多个规则,因此这里将配置文件改为 yaml 格式,以方便读
tips:我用的ShardingSphere版本为 5.1.1 不能根据官网文档去配,会报错,按照下面方法去配:
上面的配置文件改为yaml格式的话是这样的:
spring: application: name: service-shardingsphere shardingsphere: mode: # ===一、模式配置=== type: Memory datasource: # ===二、数据源配置=== names: master_user,user_slave1,user_slave2,master_order dataSource: master_user: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3307/db_user username: root password: root user_slave1: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3308/db_user username: root password: root user_slave2: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3309/db_user username: root password: root rules: # ===三、规则配置=== readwrite-splitting: # --- 读写分离配置 --- # 1.配置数据库 dataSources: ds_user: type: Static props: write-data-source-name: master_user read-data-source-names: user_slave1,user_slave2 loadBalancerName: alg_round # 为读写分离设定规则名,自定义 load-balancers: # 给自定义规则名做解释 alg_round: type: ROUND_ROBIN alg_random: type: RANDOM alg_weight: type: WEIGHT props: user_slave1: 1 user_slave2: 2 props: # ===四、配置日志=== sql-show: true
1.用户业务数据库堆,有一主两从 的读写分离库,有业务表 t_user
CREATE DATABASE db_user;
USE db_user;
CREATE TABLE t_user (
id BIGINT AUTO_INCREMENT,
uname VARCHAR(30),
PRIMARY KEY (id)
);
INSERT INTO t_user(uname) VALUES('zhang3');
INSERT INTO t_user(uname) VALUES(@@hostname);
2.订单业务数据库堆,有一主数据库没有从数据库,有订单业务表 t_order
CREATE DATABASE db_order;
USE db_order;
CREATE TABLE t_order (
id BIGINT AUTO_INCREMENT,
order_no VARCHAR(30),
user_id BIGINT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);
@TableName("t_order") @Data public class Order { @TableId(type = IdType.AUTO) private Long id; private String orderNo; private Long userId; private BigDecimal amount; } @TableName("t_user") @Data public class User { @TableId(type = IdType.AUTO) private Long id; private String uname; }
server: port: 7021 spring: application: name: service-shardingsphere shardingsphere: # ===一、模式配置=== mode: type: Memory # ===二、数据源配置=== datasource: names: master_user,user_slave1,user_slave2,master_order dataSource: master_user: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3307/db_user username: root password: root user_slave1: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3308/db_user username: root password: root user_slave2: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3309/db_user username: root password: root master_order: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3310/db_order username: root password: root # ===三、规则配置=== rules: # --- 3.1 读写分离配置 --- readwrite-splitting: # 1.给每个大数据堆设置各种的读写分离的从库主库 dataSources: ds_user: # user业务数据库堆 type: Static props: write-data-source-name: master_user read-data-source-names: user_slave1,user_slave2 loadBalancerName: alg_round # 为读写分离设定规则名,自定义 ds_order: # order业务数据库堆 type: Static props: write-data-source-name: master_order read-data-source-names: master_order loadBalancerName: alg_round # 为读写分离设定规则名,自定义 # 2.给自定义规则名做解释 load-balancers: alg_round: type: ROUND_ROBIN alg_random: type: RANDOM alg_weight: type: WEIGHT props: user_slave1: 1 user_slave2: 2 # --- 3.2 垂直分片配置 --- sharding: tables: # 这里指定该表使用哪个数据堆的哪个表(数据堆用读写分离中配置的名称才可以) t_user: actualDataNodes: ds_user.t_user t_order: actualDataNodes: ds_order.t_order # ===四、配置日志=== props: sql-show: true
准备工作完成,开始测试
/** * 垂直分库的测试 * 需要两个不同业务的数据插入到不同的数据堆中 */ @GetMapping("/testShardingVertical") public void testInsert() { User user = new User(); user.setUname("强哥"); userMapper.insert(user); Order order = new Order(); order.setOrderNo("ATGUIGU001"); order.setUserId(user.getId()); order.setAmount(new BigDecimal(100)); orderMapper.insert(order); }
测试结果:
用户数据插入了用户业务数据库堆中,依旧是主从同步,依旧是读写分离
订单数据插入了订单表,数据正确
日志
## 用户数据业务
Logic SQL: INSERT INTO t_user ( uname ) VALUES ( ? )
SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
Actual SQL: master_user ::: INSERT INTO t_user ( uname ) VALUES (?) ::: [强哥]
## 用户数据业务
Logic SQL: INSERT INTO t_order ( order_no,user_id,amount ) VALUES ( ?,?,? )
SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
Actual SQL: master_order ::: INSERT INTO t_order ( order_no,user_id,amount ) VALUES (?, ?, ?) ::: [ATGUIGU001, 8, 100]
显然,从日志看不出来什么特别的,z只能看到sql分发到了哪个数据库堆中,╮(╯▽╰)╭
代码
/**
* 垂直分库的读测试
*/
@GetMapping("/testReadVertical")
public void testSelectFromOrderAndUser() {
User user = userMapper.selectById(1L);
Order order = orderMapper.selectById(1L);
}
日志
## 用户数据业务
Logic SQL: SELECT id,uname FROM t_user WHERE id=?
SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
Actual SQL: user_slave1 ::: SELECT id,uname FROM t_user WHERE id=? ::: [1]
## 用户数据业务
Logic SQL: SELECT id,order_no,user_id,amount FROM t_order WHERE id=?
SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
Actual SQL: master_order ::: SELECT id,order_no,user_id,amount FROM t_order WHERE id=? ::: [1]
依旧看不出处理的底层逻辑。
水平分片就是把同一个表中的数据分成两个表
水平分片又有水平分库+水平分表
server: port: 7021 spring: application: name: service-shardingsphere shardingsphere: # ===一、模式配置=== mode: type: Memory # ===二、数据源配置=== datasource: names: master_user,user_slave1,user_slave2,master_order0,master_order1 dataSource: master_user: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3307/db_user username: root password: root user_slave1: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3308/db_user username: root password: root user_slave2: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3309/db_user username: root password: root master_order0: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3310/db_order # 订单库0 username: root password: root master_order1: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3311/db_order # 订单库1 username: root password: root # ===三、规则配置=== rules: # --- 3.1 读写分离配置 --- readwrite-splitting: # 1.给每个大数据堆设置各种的读写分离的从库主库 dataSources: ds_user: # user业务数据库堆 type: Static props: write-data-source-name: master_user read-data-source-names: user_slave1,user_slave2 loadBalancerName: alg_round # 为读写分离设定规则名,自定义 ds_order0: # order业务数据库堆 type: Static props: write-data-source-name: master_order0 read-data-source-names: master_order0 loadBalancerName: alg_round # 为读写分离设定规则名,自定义 ds_order1: # order业务数据库堆 type: Static props: write-data-source-name: master_order1 read-data-source-names: master_order1 loadBalancerName: alg_round # 为读写分离设定规则名,自定义 # 2.给自定义规则名做解释 load-balancers: alg_round: type: ROUND_ROBIN alg_random: type: RANDOM alg_weight: type: WEIGHT props: user_slave1: 1 user_slave2: 2 # --- 3.2 垂直分片配置 --- sharding: tables: # 这里指定该表使用哪个数据堆的哪个表(数据堆用读写分离中配置的名称才可以) t_user: actualDataNodes: ds_user.t_user # 用户数据不做水平分库,保持原本配置即可 t_order: # --- 4 垂直分片后,还可以针对进一步的水平分片配置 --- # 因为这里直接指定向那个库中插入,因此不能使用数据库堆,要使用数据库名称 actualDataNodes: master_order${0..1}.t_order0 # 指定t_order相关的数据,需要在这些数据库的表中存储 databaseStrategy: standard: shardingColumn: user_id # 水平分片的依据字段 shardingAlgorithmName: t_user_inline # 规则名 sharding-algorithms: t_user_inline: # 规则解释 type: INLINE props: algorithm-expression: master_order$->{user_id % 2} ## 正好利用取模的出来值,确定是那个库 # ===四、配置日志=== props: sql-show: true
代码
/**
* 水平分库的写测试
*/
@GetMapping("/test22")
public void testInsert() {
for (long i = 0; i < 4; i++) {
Order order = new Order();
order.setOrderNo("ATGUIGU001");
order.setUserId(i + 1);
log.info("userId的:{}", order.getUserId());
order.setAmount(new BigDecimal(100));
orderMapper.insert(order);
}
}
结果
根据 user_id 的对 2 取模后的值,决定放在那个库中
水平分库后,空中如果只有一个表的话,也会有存储读取压力,因此再把数据放在多张表中,达到水平分表的目的
server: port: 7021 spring: application: name: service-shardingsphere shardingsphere: # ===一、模式配置=== mode: type: Memory # ===二、数据源配置=== datasource: names: master_user,user_slave1,user_slave2,master_order0,master_order1 dataSource: master_user: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3307/db_user username: root password: root user_slave1: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3308/db_user username: root password: root user_slave2: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3309/db_user username: root password: root master_order0: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3310/db_order username: root password: root master_order1: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3311/db_order username: root password: root # ===三、规则配置=== rules: # --- 3.1 读写分离配置 --- readwrite-splitting: # 1.给每个大数据堆设置各种的读写分离的从库主库 dataSources: ds_user: # user业务数据库堆 type: Static props: write-data-source-name: master_user read-data-source-names: user_slave1,user_slave2 loadBalancerName: alg_round # 为读写分离设定规则名,自定义 ds_order0: # order业务数据库堆 type: Static props: write-data-source-name: master_order0 read-data-source-names: master_order0 loadBalancerName: alg_round # 为读写分离设定规则名,自定义 ds_order1: # order业务数据库堆 type: Static props: write-data-source-name: master_order1 read-data-source-names: master_order1 loadBalancerName: alg_round # 为读写分离设定规则名,自定义 # 2.给自定义规则名做解释 load-balancers: alg_round: type: ROUND_ROBIN alg_random: type: RANDOM alg_weight: type: WEIGHT props: user_slave1: 1 user_slave2: 2 # --- 3.2 垂直分片配置 --- sharding: tables: # 这里指定该表使用哪个数据堆的哪个表(数据堆用读写分离中配置的名称才可以) t_user: actualDataNodes: ds_user.t_user t_order: # --- 4 垂直分片后,还可以针对进一步的水平分片配置 --- actualDataNodes: master_order${0..1}.t_order${0..1} # 指定t_order相关的数据,需要在这些数据库堆、表中存储${0..1} databaseStrategy: #水平分库的配置 standard: shardingColumn: user_id # 水平分片的依据字段 shardingAlgorithmName: inline # 规则名 tableStrategy: #水平分表的配置 standard: shardingColumn: order_no shardingAlgorithmName: hashmod sharding-algorithms: inline: # 规则解释,行表达式,适合字段是标准的数值的情况 type: INLINE props: algorithm-expression: master_order$->{user_id % 2} hashmod: # 规则解释,哈希取模,针对非数值的字段,使用哈希取模比较合适 type: HASH_MOD props: sharding-count: 2 # 这里有几个表就写几,并且表得以0序列号开始,比如 t_order0 这里表示字段进行hash运算后,对 2 进行取模,看落到那个表 # ===四、配置日志=== props: sql-show: true
/**
* 水平分表的写测试
*/
@GetMapping("/testTable")
public void testTable() {
for (long i = 100; i < 104; i++) {
Order order = new Order();
order.setOrderNo("ATGUIGU" + i);
order.setUserId(1L);//这里写死,让它全落在一个库中,方便测试
log.info("userId的:{}", order.getUserId());
order.setAmount(new BigDecimal(100));
orderMapper.insert(order);
}
}
结果:
对字段哈希计算并取模后,各自落在对应的表中;
水平分片的读取会把配置的该业务的全部数据都查出来
/**
* 水平分片的读测试
*/
@GetMapping("/testRead")
public void testRead() {
//查全部,并打印
List<Order> orders = orderMapper.selectList(null);
orders.forEach(System.out::println);
}
结果
全部数据都查询出来了
日志
Logic SQL: SELECT id,order_no,user_id,amount FROM t_order
SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
Actual SQL: master_order0 ::: SELECT id,order_no,user_id,amount FROM t_order0 UNION ALL SELECT id,order_no,user_id,amount FROM t_order1
Actual SQL: master_order1 ::: SELECT id,order_no,user_id,amount FROM t_order0 UNION ALL SELECT id,order_no,user_id,amount FROM t_order1
UNION ALL
可以看到,查询其实是把水平分片的每个分库都查了出来,并且每个库内,每个分表都查出来,然后用 UNION ALL 将结果汇合起来
/** * 水平分片的读测试 * 分别测试 分库字段、分表字段、普通字段 */ @GetMapping("/testRead2") public void testRead2() { System.out.println("-------------查分库字段-------------"); List<Order> orders = orderMapper.selectList(Wrappers.<Order>lambdaQuery() .eq(Order::getUserId, 1L)); System.out.println("-------------查分表字段-------------"); List<Order> orders2 = orderMapper.selectList(Wrappers.<Order>lambdaQuery() .eq(Order::getOrderNo, "ATGUIGU001")); System.out.println("-------------查分库字段+分表字段-------------"); List<Order> orders3 = orderMapper.selectList(Wrappers.<Order>lambdaQuery() .eq(Order::getOrderNo, "ATGUIGU001") .eq(Order::getUserId, 1L)); System.out.println("---------------查普通字段-----------"); List<Order> orders4 = orderMapper.selectList(Wrappers.<Order>lambdaQuery() .eq(Order::getId, "01111382569209186813f97a4e63f77b")); }
结果
-------------查分库字段------------- ## 可以看出,因为查询的是 用来分库的关键字段,因此虽然查询了两个表,但是只查了一个库(因为库是通过该字段确定了) Logic SQL: SELECT id,order_no,user_id,amount FROM t_order WHERE (user_id = ?) SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty) Actual SQL: master_order1 ::: SELECT id,order_no,user_id,amount FROM t_order0 WHERE (user_id = ?) UNION ALL SELECT id,order_no,user_id,amount FROM t_order1 WHERE (user_id = ?) ::: [1, 1] -------------查分表字段------------- ## 可以看出,因为查询的是 用来分表的关键字段,因此虽然查了两个库,但是只查了一个表(因为表是通过该字段确定了) Logic SQL: SELECT id,order_no,user_id,amount FROM t_order WHERE (order_no = ?) SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty) Actual SQL: master_order0 ::: SELECT id,order_no,user_id,amount FROM t_order1 WHERE (order_no = ?) ::: [ATGUIGU001] Actual SQL: master_order1 ::: SELECT id,order_no,user_id,amount FROM t_order1 WHERE (order_no = ?) ::: [ATGUIGU001] -------------查分库字段+分表字段------------- ## 可以看出,因为查询的是 分库字段+分表字段,因此查了一个库,并且只查了一个表(因为通过该字段确定了那个库那个表) Logic SQL: SELECT id,order_no,user_id,amount FROM t_order WHERE (order_no = ? AND user_id = ?) SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty) Actual SQL: master_order1 ::: SELECT id,order_no,user_id,amount FROM t_order1 WHERE (order_no = ? AND user_id = ?) ::: [ATGUIGU001, 1] ---------------查普通字段----------- ## 可以看出,因为查询的是 普通字段,因此查了两个库,并且查了两个表(因为无法通过该字段确定了) Logic SQL: SELECT id,order_no,user_id,amount FROM t_order WHERE (id = ?) SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty) Actual SQL: master_order0 ::: SELECT id,order_no,user_id,amount FROM t_order0 WHERE (id = ?) UNION ALL SELECT id,order_no,user_id,amount FROM t_order1 WHERE (id = ?) ::: [01111382569209186813f97a4e63f77b, 01111382569209186813f97a4e63f77b] Actual SQL: master_order1 ::: SELECT id,order_no,user_id,amount FROM t_order0 WHERE (id = ?) UNION ALL SELECT id,order_no,user_id,amount FROM t_order1 WHERE (id = ?) ::: [01111382569209186813f97a4e63f77b, 01111382569209186813f97a4e63f77b]
水平分片需要关注全局序列,因为不能简单的使用基于数据库的主键自增。
雪花算法的原理
这里有两种方案:一种是基于MyBatisPlus的id策略;一种是ShardingSphere-JDBC的全局序列配置。
基于MyBatisPlus的id策略:
将Order类的id设置成如下形式
@TableId(type = IdType.ASSIGN_ID)
private Long id;
基于ShardingSphere-JDBC的全局序列配置
:和前面的MyBatisPlus的策略二选一
sharding: tables: # 这里指定该表使用哪个数据堆的哪个表(数据堆用读写分离中配置的名称才可以) t_user: actualDataNodes: ds_user.t_user t_order: # --- 4 垂直分片后,还可以针对进一步的水平分片配置 --- actualDataNodes: master_order${0..1}.t_order${0..1} # 指定t_order相关的数据,需要在这些数据库堆、表中存储${0..1} databaseStrategy: #水平分库的配置 standard: shardingColumn: user_id # 水平分片的依据字段 shardingAlgorithmName: inline # 规则名 tableStrategy: #水平分表的配置 standard: shardingColumn: order_no shardingAlgorithmName: hashmod ## ---------------------雪花算法配置----------------------------- key-generate-strategy: column: id key-generator-name: alg_snowflake ## -------------------------------------------------- sharding-algorithms: inline: # 规则解释,行表达式,适合字段是标准的数值的情况 type: INLINE props: algorithm-expression: master_order$->{user_id % 2} hashmod: # 规则解释,哈希取模,针对非数值的字段,使用哈希取模比较合适 type: HASH_MOD props: sharding-count: 2 # 这里有几个表就写几,并且表得以0序列号开始,比如 t_order0 这里表示字段进行hash运算后,对 2 进行取模看落到那个表 ## -------------------------------------------------- key-generators: alg_snowflake: type: SNOWFLAKE ## --------------------------------------------------
此时,需要将实体类中的id策略修改成以下形式:
//当配置了shardingsphere-jdbc的分布式序列时,自动使用shardingsphere-jdbc的分布式序列
//当没有配置shardingsphere-jdbc的分布式序列时,自动依赖数据库的主键自增策略
@TableId(type = IdType.AUTO)
不推荐使用shardingSphere的分布式序列,建议使用mybatis-plus的。
业务中,会有两个表相关联的情况出现,比如订单主单 t_order 跟细单 t_item ,我们希望这种关联的数据,都存放在同一个数据源中,最好是同一个库中。像之前那种配置,就会有可能把一条关联的订单主细单数据水平分片到两个表甚至是两个库里面,为了避免这种情况,我们可以使用多表关联,这样关联的两条数据,会优先放在同一个数据源中;
多表关联没有额外的配置,只能通过代码实现,具体思路是:将分库关键字和分表关键字设置为主细单关联的外键,然后主单表跟细单表的分库分表配置都设成一样的,这样因为主细单的分库分表键都是相同的值,因此就会分配到同一个数据源中
表
## 订单详情表 CREATE TABLE t_order_item0( id BIGINT, order_no VARCHAR(30), user_id BIGINT, price DECIMAL(10,2), `count` INT, PRIMARY KEY(id) ); CREATE TABLE t_order_item1( id BIGINT, order_no VARCHAR(30), user_id BIGINT, price DECIMAL(10,2), `count` INT, PRIMARY KEY(id) );
实体类
@TableName("t_order_item")
@Data
public class OrderItem {
//当配置了shardingsphere-jdbc的分布式序列时,自动使用shardingsphere-jdbc的分布式序列
@TableId(type = IdType.AUTO)
private Long id;
private String orderNo;
private Long userId;
private BigDecimal price;
private Integer count;
}
# --- 3.2 垂直分片配置 --- sharding: tables: # 这里指定该表使用哪个数据堆的哪个表(数据堆用读写分离中配置的名称才可以) t_user: # 这个是实体上面 #TableName("值") 里面的值,使用到该实体,就会出现在这里 actualDataNodes: ds_user.t_user t_order: # --- 4 垂直分片后,还可以针对进一步的水平分片配置 --- actualDataNodes: master_order${0..1}.t_order${0..1} # 指定t_order相关的数据,需要在这些数据库堆、表中存储${0..1} databaseStrategy: #水平分库的配置 standard: shardingColumn: user_id # 水平分片的依据字段 shardingAlgorithmName: inline # 规则名 tableStrategy: #水平分表的配置 standard: shardingColumn: order_no shardingAlgorithmName: hashmod t_order_item: # --- 4 垂直分片后,还可以针对进一步的水平分片配置 --- actualDataNodes: master_order${0..1}.t_order_item${0..1} # 指定t_order相关的数据,需要在这些数据库堆、表中存储${0..1} databaseStrategy: #水平分库的配置 standard: shardingColumn: user_id # 水平分片的依据字段 shardingAlgorithmName: inline # 规则名 tableStrategy: #水平分表的配置 standard: shardingColumn: order_no shardingAlgorithmName: hashmod sharding-algorithms: inline: # 规则解释,行表达式,适合字段是标准的数值的情况 type: INLINE props: algorithm-expression: master_order$->{user_id % 2} hashmod: # 规则解释,哈希取模,针对非数值的字段,使用哈希取模比较合适 type: HASH_MOD props: sharding-count: 2 # 这里有几个表就写几,并且表得以0序列号开始,比如 t_order0 这里表示字段进行hash运算后,对 2 进行取模,看落到那个表
代码
/** * 关联表数据的写测试 */ @GetMapping("/testConnect") public void testDatabase() { for (long i = 100; i < 103; i++) { Order order = new Order(); //分库分片键做关联 order.setOrderNo("ATGUIGU" + i); order.setUserId(1L); orderMapper.insert(order); for (long j = 1; j < 3; j++) { OrderItem orderItem = new OrderItem(); //分库分片键做关联 orderItem.setOrderNo(order.getOrderNo()); orderItem.setUserId(order.getUserId()); orderItem.setPrice(new BigDecimal(10)); orderItem.setCount(2); orderItemMapper.insert(orderItem); } } for (long i = 5; i < 7; i++) { Order order = new Order(); order.setOrderNo("ATGUIGU" + i); order.setUserId(2L); orderMapper.insert(order); for (long j = 1; j < 3; j++) { OrderItem orderItem = new OrderItem(); orderItem.setOrderNo(order.getOrderNo()); orderItem.setUserId(order.getUserId()); orderItem.setPrice(new BigDecimal(1)); orderItem.setCount(3); orderItemMapper.insert(orderItem); } } }
结果
订单用户相同的都被分到同一个库中,因为分库字段 user_id 是相同的,符合预期。
这里只测试了插入,没有测试查询。
查询会涉及到主表去关联那个库中的那个细单表的问题
(查询会先去选择两个库中的两个主单表中的其中一个,然后利用这个主单表去查本库的两个细单表(查两次),然后去查另一个库的两个细单表(查两次),然后两个主单表中的另外一个也同样来一遍,一共是8次)
如果指定表的后缀一样的是绑定在一起的,那么查询速率就会提高,只要查4次;
这里就需要做一下特殊配置了
# --- 3.2 垂直分片配置 --- sharding: tables: # 这里指定该表使用哪个数据堆的哪个表(数据堆用读写分离中配置的名称才可以) t_user: # 这个是实体上面 #TableName("值") 里面的值,使用到该实体,就会出现在这里 actualDataNodes: ds_user.t_user t_order: # --- 4 垂直分片后,还可以针对进一步的水平分片配置 --- actualDataNodes: master_order${0..1}.t_order${0..1} # 指定t_order相关的数据,需要在这些数据库堆、表中存储${0..1} databaseStrategy: #水平分库的配置 standard: shardingColumn: user_id # 水平分片的依据字段 shardingAlgorithmName: inline # 规则名 tableStrategy: #水平分表的配置 standard: shardingColumn: order_no shardingAlgorithmName: hashmod t_order_item: # --- 4 垂直分片后,还可以针对进一步的水平分片配置 --- actualDataNodes: master_order${0..1}.t_order_item${0..1} # 指定t_order相关的数据,需要在这些数据库堆、表中存储${0..1} databaseStrategy: #水平分库的配置 standard: shardingColumn: user_id # 水平分片的依据字段 shardingAlgorithmName: inline # 规则名 tableStrategy: #水平分表的配置 standard: shardingColumn: order_no shardingAlgorithmName: hashmod sharding-algorithms: inline: # 规则解释,行表达式,适合字段是标准的数值的情况 type: INLINE props: algorithm-expression: master_order$->{user_id % 2} hashmod: # 规则解释,哈希取模,针对非数值的字段,使用哈希取模比较合适 type: HASH_MOD props: sharding-count: 2 # 这里有几个表就写几,并且表得以0序列号开始,比如 t_order0 这里表示字段进行hash运算后,对 2 进行取模,看落到那个表 binding-tables[0]: # 这里 [] 内的序号,代表绑定的组数,可以累加 t_order,t_order_item binding-tables[1]: t_user,t_address
/**
* 关联表数据的读测试
* (会测试绑定)
*/
@GetMapping("/testGetOrderAmount")
public void testGetOrderAmount() {
List<OrderVo> orderAmountList = orderItemMapper.getOrderAmount();
orderAmountList.forEach(System.out::println);
}
<select id="getOrderAmount" resultType="com.springSharding.vo.OrderVo">
SELECT o.order_no, SUM(i.price * i.count) AS amount
FROM t_order o JOIN t_order_item i ON o.order_no = i.order_no
GROUP BY o.order_no
</select>
未配置:
可以看到,查询了八次
配置绑定
可以看到,只查询了4次
当系统中需要用到一些基础的数据,且对每个业务模块都是相同的数据的适合,就需要让这些数据在所有的分片数据源中都存在;表结构及其数据在每个数据库中均完全一致。 适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表。
广播具有以下特性:
(1)插入、更新操作会实时在所有节点上执行,保持各个分片的数据一致性
(2)查询操作,只从一个节点获取
(3)可以跟任何一个表进行 JOIN 操作
建表
需要在系统中每个库中都建表
CREATE TABLE t_dict(
id BIGINT,
dict_type VARCHAR(200),
PRIMARY KEY(id)
);
实体类
@TableName("t_dict")
@Data
public class Dict {
//可以使用MyBatisPlus的雪花算法
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String dictType;
}
## 。。。。## ## 上面的省略 sharding-algorithms: inline: # 规则解释,行表达式,适合字段是标准的数值的情况 type: INLINE props: algorithm-expression: master_order$->{user_id % 2} hashmod: # 规则解释,哈希取模,针对非数值的字段,使用哈希取模比较合适 type: HASH_MOD props: sharding-count: 2 # 这里有几个表就写几,并且表得以0序列号开始,比如 t_order0 这里表示字段进行hash运算后,对 2 进行取模,看落到那个表 # --- 5 垂直分片后,还可以进行绑定表配置 --- binding-tables[0]: t_order,t_order_item # --- 6 垂直分片后,还可以进行广播表的配置 --- broadcast-tables[0]: # 配置广播表,因为是广播,所以可以不用配置数据节点指向,只配置这里就可以 t_dict
代码
/** * 测试广播表 插入 */ @GetMapping("/testBroadcast") public void testBroadcast() { Dict dict = new Dict(); dict.setDictType("type1"); dictMapper.insert(dict); } /** * 测试广播表 读取 */ @GetMapping("/testSelectBroadcast") public void testSelectBroadcast() { List<Dict> dicts = dictMapper.selectList(null); dicts.forEach(System.out::println); }
结果
每个表中都插入了
需要注意的是,查询的时候,会随机从其中一个数据节点中获取
## 只查询一次
Logic SQL: SELECT id,dict_type FROM t_dict
SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
Actual SQL: master_order1 ::: SELECT id,dict_type FROM t_dict Dict(id=1585282641090666497, dictType=type1)
server: port: 7021 spring: application: name: service-shardingsphere shardingsphere: # ===一、模式配置=== mode: type: Memory # ===二、数据源配置=== datasource: # 这里是对应下面的数据库名 names: master_user,user_slave1,user_slave2,master_order0,master_order1 dataSource: master_user: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3307/db_user username: root password: root user_slave1: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3308/db_user username: root password: root user_slave2: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3309/db_user username: root password: root master_order0: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3310/db_order username: root password: root master_order1: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3311/db_order username: root password: root # ===三、规则配置=== rules: # --- 3.1 读写分离配置 --- readwrite-splitting: # 1.给每个大数据堆设置各种的读写分离的从库主库 dataSources: ds_user: # user业务数据库堆 type: Static props: write-data-source-name: master_user read-data-source-names: user_slave1,user_slave2 loadBalancerName: alg_round # 为读写分离设定规则名,自定义 ds_order0: # order业务数据库堆 type: Static props: write-data-source-name: master_order0 read-data-source-names: master_order0 loadBalancerName: alg_round # 为读写分离设定规则名,自定义 ds_order1: # order业务数据库堆 type: Static props: write-data-source-name: master_order1 read-data-source-names: master_order1 loadBalancerName: alg_round # 为读写分离设定规则名,自定义 # 2.给自定义规则名做解释 load-balancers: alg_round: type: ROUND_ROBIN alg_random: type: RANDOM alg_weight: type: WEIGHT props: user_slave1: 1 user_slave2: 2 # --- 3.2 垂直分片配置 --- sharding: tables: # 这里指定该表使用哪个数据堆的哪个表(数据堆用读写分离中配置的名称才可以) t_user: # 这个是实体上面 #TableName("值") 里面的值,使用到该实体,就会出现在这里 actualDataNodes: ds_user.t_user t_order: # --- 4 垂直分片后,还可以针对进一步的水平分片配置 --- actualDataNodes: master_order${0..1}.t_order${0..1} # 指定t_order相关的数据,需要在这些数据库堆、表中存储${0..1} databaseStrategy: #水平分库的配置 standard: shardingColumn: user_id # 水平分片的依据字段 shardingAlgorithmName: inline # 规则名 tableStrategy: #水平分表的配置 standard: shardingColumn: order_no shardingAlgorithmName: hashmod t_order_item: # --- 4 垂直分片后,还可以针对进一步的水平分片配置 --- actualDataNodes: master_order${0..1}.t_order_item${0..1} # 指定t_order相关的数据,需要在这些数据库堆、表中存储${0..1} databaseStrategy: #水平分库的配置 standard: shardingColumn: user_id # 水平分片的依据字段 shardingAlgorithmName: inline # 规则名 tableStrategy: #水平分表的配置 standard: shardingColumn: order_no shardingAlgorithmName: hashmod sharding-algorithms: inline: # 规则解释,行表达式,适合字段是标准的数值的情况 type: INLINE props: algorithm-expression: master_order$->{user_id % 2} hashmod: # 规则解释,哈希取模,针对非数值的字段,使用哈希取模比较合适 type: HASH_MOD props: sharding-count: 2 # 这里有几个表就写几,并且表得以0序列号开始,比如 t_order0 这里表示字段进行hash运算后,对 2 进行取模,看落到那个表 # --- 5 垂直分片后,还可以进行绑定表配置 --- binding-tables[0]: t_order,t_order_item # --- 6 垂直分片后,还可以进行广播表的配置 --- broadcast-tables[0]: # 配置广播表,因为是广播,所以可以不用配置数据节点指向,只配置这里就可以 t_dict # ===四、配置日志=== props: sql-show: true
JDBC 部分 完结
实际中,建议使用代码的方式,防止过多的引入三方应用程序
因此以下作为了解
目前 ShardingSphere-Proxy 提供了 3 种获取方式:
二进制包既可以Linux系统运行,又可以在windows系统运行
step1:解压二进制包
apache-shardingsphere-5.1.1-shardingsphere-proxy-bin.tar.gz
windows:使用解压软件解压文件
Linux:将文件上传至/opt目录,并解压
tar -zxvf apache-shardingsphere-5.1.1-shardingsphere-proxy-bin.tar.gz
step2:MySQL驱动
mysql-connector-java-8.0.22.jar
将MySQl驱动放至解压目录中的ext-lib
目录
spte3:修改配置conf/server.yaml
rules:
- !AUTHORITY
users:
- root@%:root
provider:
type: ALL_PRIVILEGES_PERMITTED
props:
sql-show: true
spte4:启动ShardingSphere-Proxy
Linux 操作系统请运行 bin/start.sh
Windows 操作系统请运行 bin/start.bat
指定端口号和配置文件目录:bin/start.bat ${proxy_port} ${proxy_conf_directory}
step5:远程连接ShardingSphere-Proxy
远程访问
mysql -h192.168.100.1 -P3307 -uroot -p
step6:访问测试
show databases;
step1:启动Docker容器
docker run -d \
-v /atguigu/server/proxy-a/conf:/opt/shardingsphere-proxy/conf \
-v /atguigu/server/proxy-a/ext-lib:/opt/shardingsphere-proxy/ext-lib \
-e ES_JAVA_OPTS="-Xmx256m -Xms256m -Xmn128m" \
-p 3321:3307 \
--name server-proxy-a \
apache/shardingsphere-proxy:5.1.1
-----------------------------------------
docker run -d -v //e/server/proxy-a/conf:/opt/shardingsphere-proxy/conf -v //e/server/proxy-a/ext-lib:/opt/shardingsphere-proxy/ext-lib -e ES_JAVA_OPTS="-Xmx256m -Xms256m -Xmn128m" -p 3321:3307 --name server-proxy-a apache/shardingsphere-proxy:5.1.1
step2:上传MySQL驱动
将MySQl驱动上传至e/server/proxy-a/ext-lib
目录
spte3:修改配置server.yaml
rules:
- !AUTHORITY
users:
- root@%:root
provider:
type: ALL_PRIVILEGES_PERMITTED
props:
sql-show: true
将配置文件上传至/atguigu/server/proxy-a/conf
目录
spte4:重启容器
docker restart server-proxy-a
step5:远程连接ShardingSphere-Proxy
ShardingSphere-Proxy容器中默认情况下没有mysql命令行客户端的安装,因此需要远程访问
mysql -h192.168.100.201 -P3321 -uroot -p
step6:访问测试
show databases;
常见问题:docker容器无法远程连接
容器可以成功的创建并启动,但是无法远程连接。排除防火墙和网络等问题后,看看是不是因为容器内存不足导致。
原因:
容器可分配内存不足
查看办法:
进入容器后查看ShardingSphere-Proxy的日志,如有有cannot allocate memory
,则说明容器内存不足
docker exec -it server-proxy-a env LANG=C.UTF-8 /bin/bash
cd /opt/shardingsphere-proxy/logs
tail stdout.log
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bz4jx909-1666882122131)(C:\Users\Administrator\Desktop\尚硅谷ShardingSphere5教程\笔记\笔记\assets\image-20220819151154763.png)]
解决方案:
创建容器的时候使用JVM参数
-e ES_JAVA_OPTS="-Xmx256m -Xms256m -Xmn128m"
Proxy就相当于是一个数据库中间件,应用程序连接上它就可以在直接进行存储数据,中间件会自动将数据放在相应的数据库中。因此连接数据库、配置主从、分片,等之类的配置,就放在proxy中进行
修改配置config-readwrite-splitting.yaml
schemaName: readwrite_splitting_db dataSources: write_ds: url: jdbc:mysql://192.168.100.201:3306/db_user?serverTimezone=UTC&useSSL=false username: root password: 123456 connectionTimeoutMilliseconds: 30000 idleTimeoutMilliseconds: 60000 maxLifetimeMilliseconds: 1800000 maxPoolSize: 50 minPoolSize: 1 read_ds_0: url: jdbc:mysql://192.168.100.201:3307/db_user?serverTimezone=UTC&useSSL=false username: root password: 123456 connectionTimeoutMilliseconds: 30000 idleTimeoutMilliseconds: 60000 maxLifetimeMilliseconds: 1800000 maxPoolSize: 50 minPoolSize: 1 read_ds_1: url: jdbc:mysql://192.168.100.201:3308/db_user?serverTimezone=UTC&useSSL=false username: root password: 123456 connectionTimeoutMilliseconds: 30000 idleTimeoutMilliseconds: 60000 maxLifetimeMilliseconds: 1800000 maxPoolSize: 50 minPoolSize: 1 rules: - !READWRITE_SPLITTING dataSources: readwrite_ds: type: Static props: write-data-source-name: write_ds read-data-source-names: read_ds_0,read_ds_1
将配置文件上传至/atguigu/server/proxy-a/conf
目录
重启容器
docker restart server-proxy-a
可以通过这种方式查看服务器中输出的SQL语句
docker exec -it server-proxy-a env LANG=C.UTF-8 /bin/bash
tail -f /opt/shardingsphere-proxy/logs/stdout.log
mysql> show databases;
mysql> use readwrite_splitting_db;
mysql> show tables;
mysql> select * from t_user;
mysql> select * from t_user;
mysql> insert into t_user(uname) values('wang5');
项目类型:Spring Initializr
SpringBoot脚手架:http://start.aliyun.com
项目名:sharding-proxy-demo
SpringBoot版本:2.3.7.RELEASE
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies>
package com.atguigu.shardingproxydemo.entity;
@TableName("t_user")
@Data
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String uname;
}
package com.atguigu.shardingproxydemo.mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
# 应用名称
spring.application.name=sharding-proxy-demo
# 开发环境设置
spring.profiles.active=dev
#mysql数据库连接(proxy)
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.100.201:3321/readwrite_splitting_db?serverTimezone=GMT%2B8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
package com.atguigu.shardingproxydemo; @SpringBootTest class ShardingProxyDemoApplicationTests { @Autowired private UserMapper userMapper; /** * 读数据测试 */ @Test public void testSelectAll(){ List<User> users = userMapper.selectList(null); users.forEach(System.out::println); } }
修改配置config-sharding.yaml
schemaName: sharding_db dataSources: ds_0: url: jdbc:mysql://192.168.100.201:3301/db_user?serverTimezone=UTC&useSSL=false username: root password: 123456 connectionTimeoutMilliseconds: 30000 idleTimeoutMilliseconds: 60000 maxLifetimeMilliseconds: 1800000 maxPoolSize: 50 minPoolSize: 1 ds_1: url: jdbc:mysql://192.168.100.201:3302/db_order?serverTimezone=UTC&useSSL=false username: root password: 123456 connectionTimeoutMilliseconds: 30000 idleTimeoutMilliseconds: 60000 maxLifetimeMilliseconds: 1800000 maxPoolSize: 50 minPoolSize: 1 rules: - !SHARDING tables: t_user: actualDataNodes: ds_0.t_user t_order: actualDataNodes: ds_1.t_order
可以通过这种方式查看服务器中输出的SQL语句
docker exec -it server-proxy-a env LANG=C.UTF-8 /bin/bash
tail -f /opt/shardingsphere-proxy/logs/stdout.log
mysql> show databases;
mysql> use sharding_db;
mysql> show tables;
mysql> select * from t_order;
mysql> select * from t_user;
修改配置config-sharding.yaml
schemaName: sharding_db dataSources: ds_user: url: jdbc:mysql://192.168.100.201:3301/db_user?serverTimezone=UTC&useSSL=false username: root password: 123456 connectionTimeoutMilliseconds: 30000 idleTimeoutMilliseconds: 60000 maxLifetimeMilliseconds: 1800000 maxPoolSize: 50 minPoolSize: 1 ds_order0: url: jdbc:mysql://192.168.100.201:3310/db_order?serverTimezone=UTC&useSSL=false username: root password: 123456 connectionTimeoutMilliseconds: 30000 idleTimeoutMilliseconds: 60000 maxLifetimeMilliseconds: 1800000 maxPoolSize: 50 minPoolSize: 1 ds_order1: url: jdbc:mysql://192.168.100.201:3311/db_order?serverTimezone=UTC&useSSL=false username: root password: 123456 connectionTimeoutMilliseconds: 30000 idleTimeoutMilliseconds: 60000 maxLifetimeMilliseconds: 1800000 maxPoolSize: 50 minPoolSize: 1 rules: - !SHARDING tables: t_user: actualDataNodes: ds_user.t_user t_order: actualDataNodes: ds_order${0..1}.t_order${0..1} databaseStrategy: standard: shardingColumn: user_id shardingAlgorithmName: alg_mod tableStrategy: standard: shardingColumn: order_no shardingAlgorithmName: alg_hash_mod keyGenerateStrategy: column: id keyGeneratorName: snowflake t_order_item: actualDataNodes: ds_order${0..1}.t_order_item${0..1} databaseStrategy: standard: shardingColumn: user_id shardingAlgorithmName: alg_mod tableStrategy: standard: shardingColumn: order_no shardingAlgorithmName: alg_hash_mod keyGenerateStrategy: column: id keyGeneratorName: snowflake bindingTables: - t_order,t_order_item broadcastTables: - t_dict shardingAlgorithms: alg_inline_userid: type: INLINE props: algorithm-expression: server-order$->{user_id % 2} alg_mod: type: MOD props: sharding-count: 2 alg_hash_mod: type: HASH_MOD props: sharding-count: 2 keyGenerators: snowflake: type: SNOWFLAKE
可以通过这种方式查看服务器中输出的SQL语句
docker exec -it server-proxy-a env LANG=C.UTF-8 /bin/bash
tail -f /opt/shardingsphere-proxy/logs/stdout.log
mysql> show databases;
mysql> use sharding_db;
mysql> show tables;
mysql> select * from t_order; --测试水平分片
mysql> select * from t_dict; --测试广播表
--------------------------------------------------- 完结 ------------------------------------------
参考视频链接
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。