跳至主要內容

apzs...大约 43 分钟

尚硅谷ShardingSphere5实战教程

Mycat Sharding-JDBC Sharding-ProxySharding-Sidecar对比图

MycatSharding-JDBCSharding-ProxySharding-Sidecar
官方网站官方网站open in new window官方网站open in new window官方网站open in new window官方网站open in new window
源码地址GitHubopen in new windowGitHubopen in new windowGitHubopen in new windowGitHubopen in new window
官方文档Mycat 权威指南open in new window官方文档open in new window官方文档open in new window官方文档open in new window
开发语言JavaJavaJavaJava
开源协议GPL-2.0/GPL-3.0Apache-2.0Apache-2.0Apache-2.0
数据库MySQL Oracle SQL Server PostgreSQL DB2 MongoDB SequoiaDBMySQL Oracle SQLServer PostgreSQL 任何遵循 SQL92 标准的数据库MySQL/PostgreSQLMySQL/PostgreSQL
连接数
应用语言任意Java任意任意
代码入侵需要修改代码
性能损耗略高open in new window损耗低open in new window损耗略高open in new window损耗低open in new window
无中心化
静态入口
管理控制台Mycat-webSharding-UISharding-UISharding-UI
分库分表单库多表/多库单表✔️✔️✔️
多租户方案✔️------
读写分离✔️✔️✔️✔️
分片策略定制化✔️✔️✔️✔️
分布式主键✔️✔️✔️✔️
标准化事务接口✔️✔️✔️✔️
XA强一致事务✔️✔️✔️✔️
柔性事务--✔️✔️✔️
配置动态化开发中✔️✔️✔️
编排治理开发中✔️✔️✔️
数据脱敏--✔️✔️✔️
可视化链路追踪--✔️✔️✔️
弹性伸缩开发中开发中开发中开发中
多节点操作分页 去重 排序 分组 聚合分页 去重 排序 分组 聚合分页 去重 排序 分组 聚合分页 去重 排序 分组 聚合
跨库关联跨库 2 表 Join ER Join 基于 caltlet 的多表 Join------
IP 白名单✔️------
SQL 黑名单✔️------
存储过程✔️------

第01章 高性能架构模式

互联网业务兴起之后,海量用户加上海量数据的特点,单个数据库服务器已经难以满足业务需要,必须考虑数据库集群的方式来提升性能。高性能数据库集群的第一种方式是“读写分离”第二种方式是“数据库分片”

1、读写分离架构

**读写分离原理:**读写分离的基本原理是将数据库读写操作分散到不同的节点上,下面是其基本架构图:

img
img

读写分离的基本实现:

  • 主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。
  • 读写分离是根据 SQL 语义的分析将读操作和写操作分别路由至主库与从库
  • 通过一主多从的配置方式,可以将查询请求均匀的分散到多个数据副本,能够进一步的提升系统的处理能力。
  • 使用多主多从的方式,不但能够提升系统的吞吐量,还能够提升系统的可用性,可以达到在任何一个数据库宕机,甚至磁盘物理损坏的情况下仍然不影响系统的正常运行。

下图展示了根据业务需要,将用户表的写操作和读操路由到不同的数据库的方案:

image-20220804223138651
image-20220804223138651

CAP 理论:

CAP 定理(CAP theorem)又被称作布鲁尔定理(Brewer's theorem),是加州大学伯克利分校的计算机科学家埃里克·布鲁尔(Eric Brewer)在 2000 年的 ACM PODC 上提出的一个猜想。对于设计分布式系统的架构师来说,CAP 是必须掌握的理论。

在一个分布式系统中,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲。

  • C 一致性(Consistency):对某个指定的客户端来说,读操作保证能够返回最新的写操作结果
  • A 可用性(Availability):非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)
  • P 分区容忍性(Partition Tolerance):当出现网络分区后(可能是丢包,也可能是连接中断,还可能是拥塞),系统能够继续“履行职责”

CAP特点:

  • 在实际设计过程中,每个系统不可能只处理一种数据,而是包含多种类型的数据,有的数据必须选择 CP,有的数据必须选择 AP,分布式系统理论上不可能选择 CA 架构。

    • CP:如下图所示,为了保证一致性,当发生分区现象后,N1 节点上的数据已经更新到 y,但由于 N1 和 N2 之间的复制通道中断,数据 y 无法同步到 N2,N2 节点上的数据还是 x。这时客户端 C 访问 N2 时,N2 需要返回 Error,提示客户端 C“系统现在发生了错误”,这种处理方式违背了可用性(Availability)的要求,因此 CAP 三者只能满足 CP。
    img
    img
    • AP:如下图所示,为了保证可用性,当发生分区现象后,N1 节点上的数据已经更新到 y,但由于 N1 和 N2 之间的复制通道中断,数据 y 无法同步到 N2,N2 节点上的数据还是 x。这时客户端 C 访问 N2 时,N2 将当前自己拥有的数据 x 返回给客户端 C 了,而实际上当前最新的数据已经是 y 了,这就不满足一致性(Consistency)的要求了,因此 CAP 三者只能满足 AP。注意:这里 N2 节点返回 x,虽然不是一个“正确”的结果,但是一个“合理”的结果,因为 x 是旧的数据,并不是一个错乱的值,只是不是最新的数据而已。
img
img
  • CAP 理论中的 C 在实践中是不可能完美实现的,在数据复制的过程中,节点N1 和节点 N2 的数据并不一致(强一致性)。即使无法做到强一致性,但应用可以采用适合的方式达到最终一致性。具有如下特点:

    • 基本可用(Basically Available):分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
    • 软状态(Soft State):允许系统存在中间状态,而该中间状态不会影响系统整体可用性。这里的中间状态就是 CAP 理论中的数据不一致。
    • 最终一致性(Eventual Consistency):系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。

2、数据库分片架构

读写分离的问题:

读写分离分散了数据库读写操作的压力,但没有分散存储压力,为了满足业务数据存储的需求,就需要将存储分散到多台数据库服务器上

数据分片:

将存放在单一数据库中的数据分散地存放至多个数据库或表中,以达到提升性能瓶颈以及可用性的效果。 数据分片的有效手段是对关系型数据库进行分库和分表。数据分片的拆分方式又分为垂直分片和水平分片

2.1、垂直分片

垂直分库:

按照业务拆分的方式称为垂直分片,又称为纵向拆分,它的核心理念是专库专用。 在拆分之前,一个数据库由多个数据表构成,每个表对应着不同的业务。而拆分之后,则是按照业务将表进行归类,分布到不同的数据库中,从而将压力分散至不同的数据库。

img
img

下图展示了根据业务需要,将用户表和订单表垂直分片到不同的数据库的方案:

image-20220804221855449
image-20220804221855449

垂直拆分可以缓解数据量和访问量带来的问题,但无法根治。如果垂直拆分之后,表中的数据量依然超过单节点所能承载的阈值,则需要水平分片来进一步处理。

垂直分表:

垂直分表适合将表中某些不常用的列,或者是占了大量空间的列拆分出去。

假设我们是一个婚恋网站,用户在筛选其他用户的时候,主要是用 age 和 sex 两个字段进行查询,而 nickname 和 description 两个字段主要用于展示,一般不会在业务查询中用到。description 本身又比较长,因此我们可以将这两个字段独立到另外一张表中,这样在查询 age 和 sex 时,就能带来一定的性能提升。

垂直分表引入的复杂性主要体现在表操作的数量要增加。例如,原来只要一次查询就可以获取 name、age、sex、nickname、description,现在需要两次查询,一次查询获取 name、age、sex,另外一次查询获取 nickname、description。

img
img

水平分表适合表行数特别大的表,水平分表属于水平分片

2.2、水平分片

水平分片又称为横向拆分。 相对于垂直分片,它不再将数据根据业务逻辑分类,而是通过某个字段(或某几个字段),根据某种规则将数据分散至多个库或表中,每个分片仅包含数据的一部分。 例如:根据主键分片,偶数主键的记录放入 0 库(或表),奇数主键的记录放入 1 库(或表),如下图所示。

image-20220804222212087
image-20220804222212087

单表进行切分后,是否将多个表分散在不同的数据库服务器中,可以根据实际的切分效果来确定。

  • **水平分表:**单表切分为多表后,新的表即使在同一个数据库服务器中,也可能带来可观的性能提升,如果性能能够满足业务要求,可以不拆分到多台数据库服务器,毕竟业务分库也会引入很多复杂性;

  • **水平分库:**如果单表拆分为多表后,单台服务器依然无法满足性能要求,那就需要将多个表分散在不同的数据库服务器中。

阿里巴巴Java开发手册:

【推荐】单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。

说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表

3、读写分离和数据分片架构

下图展现了将数据分片与读写分离一同使用时,应用程序与数据库集群之间的复杂拓扑关系。

image-20220804223321167
image-20220804223321167

4、实现方式

读写分离和数据分片具体的实现方式一般有两种: 程序代码封装中间件封装

4.1、程序代码封装

程序代码封装指在代码中抽象一个数据访问层(或中间层封装),实现读写操作分离和数据库服务器连接的管理。

**其基本架构是:**以读写分离为例

img
img

4.2、中间件封装

中间件封装指的是独立一套系统出来,实现读写操作分离和数据库服务器连接的管理。对于业务服务器来说,访问中间件和访问数据库没有区别,在业务服务器看来,中间件就是一个数据库服务器。

**基本架构是:**以读写分离为例

img
img

4.3、常用解决方案

Apache ShardingSphere(程序级别和中间件级别)

MyCat(数据库中间件)

第02章 ShardingSphere

1、简介

官网:https://shardingsphere.apache.org/index_zh.html

文档:https://shardingsphere.apache.org/document/5.1.1/cn/overview/

Apache ShardingSphere 由 JDBC、Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。

2、ShardingSphere-JDBC

程序代码封装

定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。

image-20220804195402870
image-20220804195402870

3、ShardingSphere-Proxy

中间件封装

定位为透明化的数据库代理端,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持。 目前提供 MySQL 和 PostgreSQL版本,它可以使用任何兼容 MySQL/PostgreSQL 协议的访问客户端(如:MySQL Command Client, MySQL Workbench, Navicat 等)操作数据,对 DBA 更加友好。

image-20220804195432673
image-20220804195432673

第03章 MySQL主从同步

1、MySQL主从同步原理

img
img

基本原理:

slave会从master读取binlog来进行数据同步

具体步骤:

  • step1:master将数据改变记录到二进制日志(binary log)中。
  • step2: 当slave上执行 start slave 命令之后,slave会创建一个 IO 线程用来连接master,请求master中的binlog。
  • step3:当slave连接master时,master会创建一个 log dump 线程,用于发送 binlog 的内容。在读取 binlog 的内容的操作中,会对主节点上的 binlog 加锁,当读取完成并发送给从服务器后解锁。
  • step4:IO 线程接收主节点 binlog dump 进程发来的更新之后,保存到 中继日志(relay log) 中。
  • step5:slave的SQL线程,读取relay log日志,并解析成具体操作,从而实现主从操作一致,最终数据一致。

2、一主多从配置

服务器规划:使用docker方式创建,主从服务器IP一致,端口号不一致

image-20220807183231101
image-20220807183231101
  • 主服务器:容器名atguigu-mysql-master,端口3306
  • 从服务器:容器名atguigu-mysql-slave1,端口3307
  • 从服务器:容器名atguigu-mysql-slave2,端口3308

**注意:**如果此时防火墙是开启的,则先关闭防火墙,并重启docker,否则后续安装的MySQL无法启动

#关闭docker
systemctl stop docker
#关闭防火墙
systemctl stop firewalld
#启动docker
systemctl start docker

2.1、准备主服务器

  • step1:在docker中创建并启动MySQL主服务器:端口3306
docker run -d \
-p 3306:3306 \
-v /atguigu/mysql/master/conf:/etc/mysql/conf.d \
-v /atguigu/mysql/master/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name atguigu-mysql-master \
mysql:8.0.29

解释:

docker run -d \  # -d:守护进程的启动
-p 3306:3306 \   # -p 宿主机端口:容器端口  => 容器的3306端口映射到宿主机的3306端口
-v /atguigu/mysql/master/conf:/etc/mysql/conf.d \  # 容器的/etc/mysql/conf.d目录映射到宿主机的/atguigu/mysql/master/conf目录
-v /atguigu/mysql/master/data:/var/lib/mysql \  # 容器的/var/lib/mysql目录映射到宿主机的/atguigu/mysql/master/data目录
-e MYSQL_ROOT_PASSWORD=123456 \  # 设置变量参数
--name atguigu-mysql-master \    # 容器名
mysql:8.0.29                     # 容器使用的镜像
  • step2:创建MySQL主服务器配置文件:

默认情况下MySQL的binlog日志是自动开启的,可以通过如下配置定义一些可选配置

vim /atguigu/mysql/master/conf/my.cnf

配置如下内容(注意:一定要按i进入插入模式再粘贴配置,否则会丢掉开头的几个字符)

[mysqld]
## 服务器唯一id,默认值1
server-id=1
## 设置日志格式,默认值ROW
binlog_format=STATEMENT
## 二进制日志名,默认binlog
## log-bin=binlog
## 设置需要复制的数据库,默认复制全部数据库
#binlog-do-db=mytestdb
## 设置不需要复制的数据库
#binlog-ignore-db=mysql
#binlog-ignore-db=infomation_schema

重启MySQL容器

docker restart atguigu-mysql-master

binlog格式说明:

  • binlog_format=STATEMENT:日志记录的是主机数据库的写指令,性能高,但是now()之类的函数以及获取系统参数(@@hostname)的操作会出现主从数据不同步的问题。(日志中记录的是sql命令)
  • binlog_format=ROW(默认):日志记录的是主机数据库的写后的数据,批量操作时性能较差,解决now()或者 user()或者 @@hostname 等操作在主从机器上不一致的问题。(日志中记录的是计算后的结果)
  • binlog_format=MIXED:是以上两种level的混合使用,有函数用ROW,没函数用STATEMENT,但是无法识别系统变量

binlog-ignore-db和binlog-do-db的优先级问题:

img
img
  • step3:使用命令行登录MySQL主服务器:

如果使用低版本的连接工具(如低版本的SQLyogNavicat等)会出现如下错误

authentication plugin 'caching_sha2_password' cannot be loaded;

这是因为mysql8的密码校验方式从mysql_native_password修改为了caching_sha2_password

我们使用ShardingSphere时也需要mysql_native_password这种校验方式

我们可用使用如下步骤来修改密码校验方式

#进入容器:env LANG=C.UTF-8 避免容器中显示中文乱码
docker exec -it atguigu-mysql-master env LANG=C.UTF-8 /bin/bash
#进入容器内的mysql命令行(密码为 123456)
mysql -uroot -p
#修改默认密码校验方式
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
  • step4:主机中创建slave用户:
-- 创建slave用户 ( % 表示允许所有的远程服务器访问主服务器 )
CREATE USER 'atguigu_slave'@'%';
-- 设置密码
ALTER USER 'atguigu_slave'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
-- 授予复制权限
GRANT REPLICATION SLAVE ON *.* TO 'atguigu_slave'@'%';
-- 刷新权限
FLUSH PRIVILEGES;
  • step5:主机中查询master状态:

执行完此步骤后不要再操作主服务器MYSQL,防止主服务器状态值变化

SHOW MASTER STATUS;

记下FilePosition的值。执行完此步骤后不要再操作主服务器MYSQL,防止主服务器状态值变化。

image-20220804191852164
image-20220804191852164

从服务器会根据binlog文件和指定的位置读取日志。

2.2、准备从服务器

slave1

可以配置多台从机slave1、slave2...,这里以配置slave1为例

  • step1:在虚拟机中创建并启动MySQL从服务器:端口3307

注意:不是在第一个mysql容器里面执行,可以新开一个窗口执行

退出到虚拟机:使用exit;退出mysql命令行,再使用exit退出mysql容器,即可到虚拟机。

docker run -d \
-p 3307:3306 \
-v /atguigu/mysql/slave1/conf:/etc/mysql/conf.d \
-v /atguigu/mysql/slave1/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name atguigu-mysql-slave1 \
mysql:8.0.29
  • step2:创建MySQL从服务器配置文件:
vi /atguigu/mysql/slave1/conf/my.cnf

配置如下内容:

[mysqld]
## 服务器唯一id,每台服务器的id必须不同,如果配置其他从机,注意修改id
server-id=2
## 中继日志名,默认xxxxxxxxxxxx-relay-bin
#relay-log=relay-bin

重启MySQL容器

docker restart atguigu-mysql-slave1
  • step3:使用命令行登录MySQL从服务器:
#进入容器:
docker exec -it atguigu-mysql-slave1 env LANG=C.UTF-8 /bin/bash
#进入容器内的mysql命令行
mysql -uroot -p
#修改默认密码校验方式
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
  • step4:在从机上配置主从关系:

从机上执行以下SQL操作(这是一个sql命令,注意主机的HOSTLOG_FILELOG_POS

CHANGE MASTER TO MASTER_HOST='192.168.100.201', 
MASTER_USER='atguigu_slave',MASTER_PASSWORD='123456', MASTER_PORT=3306,
MASTER_LOG_FILE='binlog.000003',MASTER_LOG_POS=1357; 
slave2
docker run -d \
-p 3308:3306 \
-v /atguigu/mysql/slave2/conf:/etc/mysql/conf.d \
-v /atguigu/mysql/slave2/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name atguigu-mysql-slave2 \
mysql:8.0.29
vi /atguigu/mysql/slave2/conf/my.cnf
[mysqld]
## 服务器唯一id,每台服务器的id必须不同,如果配置其他从机,注意修改id
server-id=3
## 中继日志名,默认xxxxxxxxxxxx-relay-bin
#relay-log=relay-bin
docker restart atguigu-mysql-slave2
[root@localhost conf]# docker ps
CONTAINER ID   IMAGE               COMMAND                  CREATED             STATUS          PORTS                                                  NAMES
92e3dab8b7b7   mysql:8.0.29        "docker-entrypoint.s…"   5 minutes ago       Up 12 seconds   33060/tcp, 0.0.0.0:3308->3306/tcp, :::3308->3306/tcp   atguigu-mysql-slave2
b8256b1eca52   mysql:8.0.29        "docker-entrypoint.s…"   14 minutes ago      Up 31 seconds   33060/tcp, 0.0.0.0:3307->3306/tcp, :::3307->3306/tcp   atguigu-mysql-slave1
e4ec8ee777fb   mysql:8.0.29        "docker-entrypoint.s…"   About an hour ago   Up 42 minutes   0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp   atguigu-mysql-master
#进入容器:
docker exec -it atguigu-mysql-slave2 env LANG=C.UTF-8 /bin/bash
#进入容器内的mysql命令行
mysql -uroot -p
#修改默认密码校验方式
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
CHANGE MASTER TO MASTER_HOST='192.168.100.201', 
MASTER_USER='atguigu_slave',MASTER_PASSWORD='123456', MASTER_PORT=3306,
MASTER_LOG_FILE='binlog.000003',MASTER_LOG_POS=1357; 

2.3、启动主从同步

启动从机的复制功能

在两个从节点的mysql命令行里都执行如下SQL:

START SLAVE;
-- 查看状态(不需要分号,\G表示让列纵向显示)
SHOW SLAVE STATUS\G

**两个关键进程:**下面两个参数都是Yes,则说明主从配置成功!

img
img

2.4、实现主从同步

在主机中执行以下SQL,在从机中查看数据库、表和数据是否已经被同步

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);
  • @@hostname:值为容器的id

从节点可用执行如下sql判断主从复制是否成功

show databases;
show tables;
select * from t_user;

可以看到t_user表里uname里插入的@@hostname不一致,这是因为我们前面在主服务器添加了binlog_format=STATEMENT配置,STATEMENT模式会导致主从这种数据不一致

2.5、停止和重置

需要的时候,可以使用如下SQL语句

-- 在从机上执行。功能说明:停止I/O 线程和SQL线程的操作。
stop slave; 

-- 在从机上执行。功能说明:用于删除SLAVE数据库的relaylog日志文件,并重新启用新的relaylog文件。
reset slave;

-- 在主机上执行。功能说明:删除所有的binglog日志文件,并将日志索引文件清空,重新开始所有新的日志文件。
-- 用于第一次进行搭建主从库时,进行主库binlog初始化工作;
reset master;

2.6、常见问题

问题1

启动主从同步后,常见错误是Slave_IO_Running: No 或者 Connecting 的情况,此时查看下方的 Last_IO_ERROR错误日志,根据日志中显示的错误信息在网上搜索解决方案即可

img
img

典型的错误例如:Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'Client requested master to start replication from position > file size'

解决方案:

-- 在从机停止slave
SLAVE STOP;

-- 在主机查看mater状态
SHOW MASTER STATUS;
-- 在主机刷新日志
FLUSH LOGS;
-- 再次在主机查看mater状态(会发现File和Position发生了变化)
SHOW MASTER STATUS;
-- 修改从机连接主机的SQL,并重新连接即可
问题2

启动docker容器后提示 WARNING: IPv4 forwarding is disabled. Networking will not work.

img
img

此错误,虽然不影响主从同步的搭建,但是如果想从远程客户端通过以下方式连接docker中的MySQL则没法连接

C:\Users\administrator>mysql -h 192.168.100.201 -P 3306 -u root -p

解决方案:

#修改配置文件:
vim /usr/lib/sysctl.d/00-system.conf
#追加
net.ipv4.ip_forward=1
#接着重启网络
systemctl restart network

第04章 ShardingSphere-JDBC读写分离

1、创建SpringBoot程序

1.1、创建项目

项目类型:Spring Initializr

SpringBoot脚手架:http://start.aliyun.com

项目名:sharding-jdbc-demo

SpringBoot版本:2.3.7.RELEASE

1.2、添加依赖

<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>
        <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>

1.3、创建实体类

package com.atguigu.shardingjdbcdemo.entity;

@TableName("t_user")
@Data
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String uname;
}

1.4、创建Mapper

package com.atguigu.shardingjdbcdemo.mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

1.5、配置读写分离

application.properties:

## 应用名称
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

## 负载均衡算法名称(我们可以修改名称为下面配置的`负载均衡算法配置名称`即可改变负载均衡算法)
## 比如我们改为alg_round即为ROUND_ROBIN轮询算法,alg_random即为RANDOM随机访问算法,alg_weight为WEIGHT按权重进行访问算法
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.load-balancer-name=alg_round

## 负载均衡算法配置(alg_round、alg_random、alg_weight可以随便起,但最好见名知意)
## 负载均衡算法类型
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.0
spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.props.slave2=2.0

## 打印SQl
spring.shardingsphere.props.sql-show=true

配置说明:

2、测试

2.1、读写分离测试

package com.atguigu.shardingjdbcdemo;

@SpringBootTest
class ReadwriteTest {

    @Autowired
    private UserMapper userMapper;

    /**
     * 写入数据的测试
     * 不开启事务时,写操作在主节点,读操作在从节点
     */
    @Test
    public void testInsert(){

        User user = new User();
        user.setUname("张三丰");
        userMapper.insert(user);
        
        List<User> users = userMapper.selectList(null);
        users.forEach(System.out::println);
    }

}

2.2、事务测试

为了保证主从库间的事务一致性,避免跨服务的分布式事务,ShardingSphere-JDBC的主从模型中,事务中的数据读写均用主库

  • 不添加@Transactional:insert对主库操作,select对从库操作
  • 添加@Transactional:则insert和select均对主库操作
  • **注意:**在JUnit环境下的@Transactional注解,默认情况下就会对事务进行回滚(即使在没加注解@Rollback,也会对事务回滚)
/**
 * 读写分离:事务测试
 * 开启事务时,写操作和读操作都在主节点
 * 在junit测试上添加 @Transactional 注解,默认情况下 即使不加@Rollback注解 也会对事务进行回滚
 */
@Transactional//开启事务
@Test
public void testTrans(){

    User user = new User();
    user.setUname("铁锤");
    userMapper.insert(user);

    List<User> users = userMapper.selectList(null);
    users.forEach(System.out::println);
}

2.3、负载均衡测试

可以看到slave1slave2是轮询进行查找

/**
 * 读写分离:负载均衡测试
 */
@Test
public void testSelectAll(){

    for (int i = 0; i < 10; i++) {
        userMapper.selectList(null);
    }
    // users.forEach(System.out::println);
}

注意:不要查询只查一次但执行多次测试用例,这样只会从slave1节点上查询,因为执行每一次测试程序都是新开了一个新的程序

也可以在web请求中测试负载均衡,浏览器访问 http://localhost:8080/user/selectAll ,在日志中可以看到slave1slave2也是轮询进行查找的

package com.atguigu.shardingjdbcdemo.controller;

@RestController
@RequestMapping("/userController")
public class UserController {

    @Autowired
    private UserMapper userMapper;

    /**
     * 测试负载均衡策略
     */
    @GetMapping("selectAll")
    @ResponseBody
    public List<User> selectAll(){

        List<User> users = userMapper.selectList(null);
        return users;
    }
}

第05章 ShardingSphere-JDBC垂直分片

1、准备服务器

服务器规划:使用docker方式创建如下容器(使用server-userserver-order这两个数据库)

注意:可以使用如下命令停止前面配置的主从复制的容器,但一定不要删除了,后面学习ShardingSphere-Proxy还会用到

docker stop atguigu-mysql-master
docker stop atguigu-mysql-slave1
docker stop atguigu-mysql-slave2
image-20220807232456342
image-20220807232456342
  • 服务器:容器名server-user,端口3301

  • 服务器:容器名server-order,端口3302

1.1、创建server-user容器

  • step1:创建容器:
docker run -d \
-p 3301:3306 \
-v /atguigu/server/user/conf:/etc/mysql/conf.d \
-v /atguigu/server/user/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name server-user \
mysql:8.0.29
  • step2:登录MySQL服务器:
#进入容器:
docker exec -it server-user env LANG=C.UTF-8 /bin/bash
#进入容器内的mysql命令行
mysql -uroot -p
#修改默认密码插件
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
  • step3:创建数据库:
CREATE DATABASE db_user;
USE db_user;
CREATE TABLE t_user (
 id BIGINT AUTO_INCREMENT,
 uname VARCHAR(30),
 PRIMARY KEY (id)
);

1.2、创建server-order容器

  • step1:创建容器:
docker run -d \
-p 3302:3306 \
-v /atguigu/server/order/conf:/etc/mysql/conf.d \
-v /atguigu/server/order/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name server-order \
mysql:8.0.29
  • step2:登录MySQL服务器:
#进入容器:
docker exec -it server-order env LANG=C.UTF-8 /bin/bash
#进入容器内的mysql命令行
mysql -uroot -p
#修改默认密码插件
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
  • step3:创建数据库:
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) 
);

2、程序实现

2.1、创建实体类

package com.atguigu.shardingjdbcdemo.entity;

@TableName("t_order")
@Data
public class Order {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String orderNo;
    private Long userId;
    private BigDecimal amount;
}

2.2、创建Mapper

package com.atguigu.shardingjdbcdemo.mapper;

@Mapper
public interface OrderMapper extends BaseMapper<Order> {
}

2.3、配置垂直分片

## 应用名称
spring.application.name=sharding-jdbc-demo
## 环境设置
spring.profiles.active=dev

## 配置真实数据源
spring.shardingsphere.datasource.names=server-user,server-order

## 配置第 1 个数据源
spring.shardingsphere.datasource.server-user.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-user.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.server-user.jdbc-url=jdbc:mysql://192.168.100.201:3301/db_user
spring.shardingsphere.datasource.server-user.username=root
spring.shardingsphere.datasource.server-user.password=123456

## 配置第 2 个数据源
spring.shardingsphere.datasource.server-order.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.server-order.jdbc-url=jdbc:mysql://192.168.100.201:3302/db_order
spring.shardingsphere.datasource.server-order.username=root
spring.shardingsphere.datasource.server-order.password=123456

## 标准分片表配置(数据节点)
## spring.shardingsphere.rules.sharding.tables.<table-name>.actual-data-nodes=值
## 值由数据源名 + 表名组成,以小数点分隔。
## <table-name>:逻辑表名
## 代码中对 t_user 表的操作自动映射到 server-user.t_user 表
spring.shardingsphere.rules.sharding.tables.t_user.actual-data-nodes=server-user.t_user
## 代码中对 t_order 表的操作自动映射到 server-order.t_order 表
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order.t_order


## 打印SQL
spring.shardingsphere.props.sql-show=true

3、测试垂直分片

package com.atguigu.shardingjdbcdemo;

@SpringBootTest
public class ShardingTest {


    @Autowired
    private UserMapper userMapper;

    @Autowired
    private OrderMapper orderMapper;

    /**
     * 垂直分片:插入数据测试
     */
    @Test
    public void testInsertOrderAndUser(){

        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);

    }

    /**
     * 垂直分片:查询数据测试
     */
    @Test
    public void testSelectFromOrderAndUser(){
        User user = userMapper.selectById(1L);
        Order order = orderMapper.selectById(1L);
    }
}

常见错误

image-20220810163534068
image-20220810163534068

ShardingSphere-JDBC远程连接的方式默认的密码加密规则是:mysql_native_password

因此需要在服务器端修改服务器的密码加密规则,如下:

ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';

第06章 ShardingSphere-JDBC水平分片

1、准备服务器

服务器规划:使用docker方式创建如下容器(使用server-userserver-order0server-order1这三个数据库)

可以使用如下命令停止server-order容器

docker stop server-order
image-20220808033239206
image-20220808033239206
  • 服务器:容器名server-order0,端口3310

  • 服务器:容器名server-order1,端口3311

1.1、创建server-order0容器

  • step1:创建容器:
docker run -d \
-p 3310:3306 \
-v /atguigu/server/order0/conf:/etc/mysql/conf.d \
-v /atguigu/server/order0/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name server-order0 \
mysql:8.0.29
  • step2:登录MySQL服务器:
#进入容器:
docker exec -it server-order0 env LANG=C.UTF-8 /bin/bash
#进入容器内的mysql命令行
mysql -uroot -p
#修改默认密码插件
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
  • step3:创建数据库:

注意:水平分片的id需要在业务层实现,不能依赖数据库的主键自增

CREATE DATABASE db_order;
USE db_order;
CREATE TABLE t_order0 (
  id BIGINT,
  order_no VARCHAR(30),
  user_id BIGINT,
  amount DECIMAL(10,2),
  PRIMARY KEY(id) 
);
CREATE TABLE t_order1 (
  id BIGINT,
  order_no VARCHAR(30),
  user_id BIGINT,
  amount DECIMAL(10,2),
  PRIMARY KEY(id) 
);

1.2、创建server-order1容器

  • step1:创建容器:
docker run -d \
-p 3311:3306 \
-v /atguigu/server/order1/conf:/etc/mysql/conf.d \
-v /atguigu/server/order1/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name server-order1 \
mysql:8.0.29
  • step2:登录MySQL服务器:
#进入容器:
docker exec -it server-order1 env LANG=C.UTF-8 /bin/bash
#进入容器内的mysql命令行
mysql -uroot -p
#修改默认密码插件
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
  • **step3:创建数据库:**和server-order0相同

注意:水平分片的id需要在业务层实现,不能依赖数据库的主键自增

CREATE DATABASE db_order;
USE db_order;
CREATE TABLE t_order0 (
  id BIGINT,
  order_no VARCHAR(30),
  user_id BIGINT,
  amount DECIMAL(10,2),
  PRIMARY KEY(id) 
);
CREATE TABLE t_order1 (
  id BIGINT,
  order_no VARCHAR(30),
  user_id BIGINT,
  amount DECIMAL(10,2),
  PRIMARY KEY(id) 
);

2、基本水平分片

2.1、基本配置

#========================基本配置
## 应用名称
spring.application.name=sharging-jdbc-demo
## 开发环境设置
spring.profiles.active=dev
## 内存模式
spring.shardingsphere.mode.type=Memory
## 打印SQl
spring.shardingsphere.props.sql-show=true

2.2、数据源配置

#========================数据源配置
## 配置真实数据源
spring.shardingsphere.datasource.names=server-user,server-order0,server-order1

## 配置第 1 个数据源
spring.shardingsphere.datasource.server-user.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-user.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.server-user.jdbc-url=jdbc:mysql://192.168.100.201:3301/db_user
spring.shardingsphere.datasource.server-user.username=root
spring.shardingsphere.datasource.server-user.password=123456

## 配置第 2 个数据源
spring.shardingsphere.datasource.server-order.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.server-order.jdbc-url=jdbc:mysql://192.168.100.201:3310/db_order
spring.shardingsphere.datasource.server-order.username=root
spring.shardingsphere.datasource.server-order.password=123456

## 配置第 3 个数据源
spring.shardingsphere.datasource.server-order.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.server-order.jdbc-url=jdbc:mysql://192.168.100.201:3311/db_order
spring.shardingsphere.datasource.server-order.username=root
spring.shardingsphere.datasource.server-order.password=123456

2.3、标椎分片表配置

#========================标准分片表配置(数据节点配置)
## spring.shardingsphere.rules.sharding.tables.<table-name>.actual-data-nodes=值
## 值由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。
## <table-name>:逻辑表名
## 代码中对 t_user 表的操作自动映射到 server-user.t_user 表
spring.shardingsphere.rules.sharding.tables.t_user.actual-data-nodes=server-user.t_user
## 代码中对 t_order 表的操作自动映射到 "server-order0.t_order0"、"server-order0.t-order1"、"server-order1.t_order0"、"server-order1.t_order1" 这两个数据库各两张表,共4张表
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order0.t_order0,server-order0.t_order1,server-order1.t_order0,server-order1.t_order1

修改Order实体类的主键策略:

//@TableId(type = IdType.AUTO)//依赖数据库的主键自增策略
@TableId(type = IdType.ASSIGN_ID)//分布式id

如果不修改会报如下错误,即 代码层面没有添加id,而数据库也没有主键自增策略,而id又要求是非空的

org.springframework.dao.DataIntegrityViolationException: 
### Error updating database.  Cause: java.sql.SQLException: Field 'id' doesn't have a default value
### The error may exist in com/atguigu/shargingjdbcdemo/mapper/OrderMapper.java (best guess)
### The error may involve com.atguigu.shargingjdbcdemo.mapper.OrderMapper.insert-Inline
### The error occurred while setting parameters
### SQL: INSERT INTO t_order  ( order_no, user_id, amount )  VALUES  ( ?, ?, ? )
### Cause: java.sql.SQLException: Field 'id' doesn't have a default value
; Field 'id' doesn't have a default value; nested exception is java.sql.SQLException: Field 'id' doesn't have a default value

测试:保留上面配置中的一个分片表节点分别进行测试,检查每个分片节点是否可用

/**
     * 水平分片:插入数据测试
     */
@Test
public void testInsertOrder(){

    Order order = new Order();
    order.setOrderNo("ATGUIGU001");
    order.setUserId(1L);
    order.setAmount(new BigDecimal(100));
    orderMapper.insert(order);
}

2.4、行(inline)表达式

优化上一步的分片表配置 行表达式 :: ShardingSphere (apache.org)open in new window

#========================标准分片表配置(数据节点配置)
## spring.shardingsphere.rules.sharding.tables.<table-name>.actual-data-nodes=值
## 值由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。
## <table-name>:逻辑表名
## 代码中对 t_user 表的操作自动映射到 server-user.t_user 表
spring.shardingsphere.rules.sharding.tables.t_user.actual-data-nodes=server-user.t_user
## 代码中对 t_order 表的操作自动映射到 "server-order0.t_order0"、"server-order0.t-order1"、"server-order1.t_order0"、"server-order1.t_order1" 这两个数据库各两张表,共4张表
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order$->{0..1}.t_order$->{0..1}

2.5、分片算法配置

水平分库:

分片规则:order表中user_id为偶数时,数据插入server-order0服务器user_id为奇数时,数据插入server-order1服务器。这样分片的好处是,同一个用户的订单数据,一定会被插入到同一台服务器上,查询一个用户的订单时效率较高。

#------------------------分库策略
## 对t_order表的user_id字段使用INLINE算法,决定路由到哪个数据库中
## 分片列名称
spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-column=user_id
## 分片算法名称(接下来我们需要为名为alg_inline_userid的sharding-algorithms配置type、props等)
spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-algorithm-name=alg_inline_userid

#------------------------分片算法配置
## 行表达式分片算法
## 分片算法类型(为为alg_inline_userid配置type)
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_userid.type=INLINE
## 分片算法属性配置(为alg_inline_userid配置props)
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_userid.props.algorithm-expression=server-order$->{user_id % 2}

## 取模分片算法
## 分片算法类型
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.type=MOD
## 分片算法属性配置
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.props.sharding-count=2

为了方便测试,先设置只在 t_order0表上进行测试

xxx.actual-data-nodes=server-order$->{0..1}.t_order0

如果不配置表为 t_order0,则会报如下错误,即虽然我们配置了多数据的策略却没有配置多表的策略

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: 
### Error updating database.  Cause: java.lang.IllegalStateException: Insert statement does not support sharding table routing to multiple data nodes.
### The error may exist in com/atguigu/shargingjdbcdemo/mapper/OrderMapper.java (best guess)
### The error may involve com.atguigu.shargingjdbcdemo.mapper.OrderMapper.insert-Inline
### The error occurred while setting parameters
### SQL: INSERT INTO t_order  ( id, order_no, user_id, amount )  VALUES  ( ?, ?, ?, ? )
### Cause: java.lang.IllegalStateException: Insert statement does not support sharding table routing to multiple data nodes.

测试:可以分别测试行表达式分片算法和取模分片算法

/**
     * 水平分片:分库插入数据测试
     */
@Test
public void testInsertOrderDatabaseStrategy(){

    for (long i = 0; i < 4; i++) {
        Order order = new Order();
        order.setOrderNo("ATGUIGU001");
        order.setUserId(i + 1);
        order.setAmount(new BigDecimal(100));
        orderMapper.insert(order);
    }

}

水平分表:

分片规则:order表中order_no的哈希值为偶数时,数据插入对应服务器的t_order0表order_no的哈希值为奇数时,数据插入对应服务器的t_order1表。因为order_no是字符串形式,因此不能直接取模。

#------------------------分表策略
## 对t_order表的order_no字段使用HASH_MOD算法,决定将这行数据插入到哪个数据表中
## 分片列名称
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-column=order_no
## 分片算法名称(接下来我们需要为名为alg_hash_mod的sharding-algorithms配置type、props等)
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-algorithm-name=alg_hash_mod


#------------------------分片算法配置
## 哈希取模分片算法
## 分片算法类型(为alg_hash_mod配置type)
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.type=HASH_MOD
## 分片算法属性配置(为alg_hash_mod配置props)
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.props.sharding-count=2

测试前不要忘记将如下节点改回原来的状态

xxx.actual-data-nodes=server-order$->{0..1}.t_order$->{0..1}

测试:

/**
     * 水平分片:分表插入数据测试
     */
@Test
public void testInsertOrderTableStrategy(){

    for (long i = 1; i < 5; i++) {

        Order order = new Order();
        order.setOrderNo("ATGUIGU" + i);
        order.setUserId(1L);
        order.setAmount(new BigDecimal(100));
        orderMapper.insert(order);
    }

    for (long i = 5; i < 9; i++) {

        Order order = new Order();
        order.setOrderNo("ATGUIGU" + i);
        order.setUserId(2L);
        order.setAmount(new BigDecimal(100));
        orderMapper.insert(order);
    }
}

/**
     * 测试哈希取模
     */
@Test
public void testHash(){

    //注意hash取模的结果是整个字符串hash后再取模,和数值后缀是奇数还是偶数无关
    System.out.println("ATGUIGU001".hashCode() % 2);
    System.out.println("ATGUIGU0011".hashCode() % 2);
}

查询测试:

/**
     * 水平分片:查询所有记录
     * 查询了两个数据源,每个数据源中使用UNION ALL连接两个表
     */
@Test
public void testShardingSelectAll(){

    List<Order> orders = orderMapper.selectList(null);
    orders.forEach(System.out::println);
}

/**
     * 水平分片:根据user_id查询记录
     * 查询了一个数据源,每个数据源中使用UNION ALL连接两个表
     */
@Test
public void testShardingSelectByUserId(){

    QueryWrapper<Order> orderQueryWrapper = new QueryWrapper<>();
    orderQueryWrapper.eq("user_id", 1L);
    List<Order> orders = orderMapper.selectList(orderQueryWrapper);
    orders.forEach(System.out::println);
}

2.6、分布式序列(id)算法

雪花算法:分布式主键 :: ShardingSphere (apache.org)open in new window

水平分片需要关注全局序列,因为不能简单的使用基于数据库的主键自增。

这里有两种方案:一种是基于MyBatisPlus的id策略;一种是ShardingSphere-JDBC的全局序列配置。

基于MyBatisPlus的id策略:将Order类的id设置成如下形式

@TableId(type = IdType.ASSIGN_ID)
private Long id;

基于ShardingSphere-JDBC的全局序列配置:和前面的MyBatisPlus的策略二选一

#------------------------分布式序列策略配置
## 对t_order表的id字段使用alg_snowflake算法(雪花算法)自动为该id字段填充值
## 分布式序列列名称
spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.column=id
## 分布式序列算法名称(接下来我们需要为名为alg_snowflake的key-generators配置type、props等)
spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.key-generator-name=alg_snowflake

## 分布式序列算法配置
## 分布式序列算法类型(为alg_snowflake配置type)
spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.type=SNOWFLAKE
## 分布式序列算法属性配置
#spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.props.xxx=

此时,需要将实体类中的id策略修改成以下形式:(mybatis-plus的配置优先级比shardingsphere-jdbc高,如果都配置了雪花算法,则mybatis-plus的配置会生效)

//当配置了shardingsphere-jdbc的分布式序列时,自动使用shardingsphere-jdbc的分布式序列
//当没有配置shardingsphere-jdbc的分布式序列时,自动依赖数据库的主键自增策略
@TableId(type = IdType.AUTO)

3、多表关联

3.1、创建关联表

server-order0、server-order1服务器中分别创建两张订单详情表t_order_item0、t_order_item1,共4张表

我们希望同一个用户的订单表和订单详情表中的数据都在同一个数据源中,避免跨库关联,因此这两张表我们使用相同的分片策略。

那么在t_order_item中我们也需要创建order_nouser_id这两个分片键

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)
);

3.2、创建实体类

package com.atguigu.shardingjdbcdemo.entity;

@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.3、创建Mapper

package com.atguigu.shargingjdbcdemo.mapper;

@Mapper
public interface OrderItemMapper extends BaseMapper<OrderItem> {

}

3.4、配置关联表

t_order_item的分片表、分片策略、分布式序列策略和t_order一致

#------------------------标准分片表配置(数据节点配置)
spring.shardingsphere.rules.sharding.tables.t_order_item.actual-data-nodes=server-order$->{0..1}.t_order_item$->{0..1}

#------------------------分库策略
## 分片列名称
spring.shardingsphere.rules.sharding.tables.t_order_item.database-strategy.standard.sharding-column=user_id
## 分片算法名称
spring.shardingsphere.rules.sharding.tables.t_order_item.database-strategy.standard.sharding-algorithm-name=alg_mod

#------------------------分表策略
## 分片列名称
spring.shardingsphere.rules.sharding.tables.t_order_item.table-strategy.standard.sharding-column=order_no
## 分片算法名称
spring.shardingsphere.rules.sharding.tables.t_order_item.table-strategy.standard.sharding-algorithm-name=alg_hash_mod

#------------------------分布式序列策略配置
## 分布式序列列名称
spring.shardingsphere.rules.sharding.tables.t_order_item.key-generate-strategy.column=id
## 分布式序列算法名称
spring.shardingsphere.rules.sharding.tables.t_order_item.key-generate-strategy.key-generator-name=alg_snowflake

3.5、测试插入数据

同一个用户的订单表和订单详情表中的数据都在同一个数据源中,避免跨库关联

/**
     * 测试关联表插入
     */
@Test
public void testInsertOrderAndOrderItem(){

    for (long i = 1; i < 3; 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("ATGUIGU" + i);
            orderItem.setUserId(1L);
            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("ATGUIGU" + i);
            orderItem.setUserId(2L);
            orderItem.setPrice(new BigDecimal(1));
            orderItem.setCount(3);
            orderItemMapper.insert(orderItem);
        }
    }

}

4、绑定表

**需求:**查询每个订单的订单号和总订单金额

4.1、创建VO对象

package com.atguigu.shardingjdbcdemo.entity;

@Data
public class OrderVo {
    private String orderNo;
    private BigDecimal amount;
}

4.2、添加Mapper方法

package com.atguigu.shardingjdbcdemo.mapper;

@Mapper
public interface OrderMapper extends BaseMapper<Order> {

    @Select({"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"})
    List<OrderVo> getOrderAmount();

}

4.3、测试关联查询

/**
     * 测试关联表查询
     */
@Test
public void testGetOrderAmount(){

    List<OrderVo> orderAmountList = orderMapper.getOrderAmount();
    orderAmountList.forEach(System.out::println);
}

4.4、配置绑定表

在原来水平分片配置的基础上添加如下配置:数据分片 :: ShardingSphere (apache.org)open in new window

#------------------------绑定表
spring.shardingsphere.rules.sharding.binding-tables[0]=t_order,t_order_item

配置完绑定表后再次进行关联查询的测试:

  • **如果不配置绑定表:测试的结果为8个SQL。**多表关联查询会出现笛卡尔积关联。

  • 如果配置绑定表:测试的结果为4个SQL。 多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。

绑定表:指分片规则一致的一组分片表。 使用绑定表进行多表关联查询时,必须使用分片键进行关联,否则会出现笛卡尔积关联或跨库关联,从而影响查询效率。

5、广播表

4.1、什么是广播表

指所有的分片数据源中都存在的表,表结构及其数据在每个数据库中均完全一致。 适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表(省份、民族、部门、科室、学院 等比较固定,很少去改变的表)。

广播具有以下特性:

(1)插入、更新操作会实时在所有节点上执行,保持各个分片的数据一致性

(2)查询操作,只从一个节点获取

(3)可以跟任何一个表进行 JOIN 操作

4.2、创建广播表

在server-order0、server-order1和server-user服务器中分别创建t_dict表

CREATE TABLE t_dict(
    id BIGINT,
    dict_type VARCHAR(200),
    PRIMARY KEY(id)
);

4.3、程序实现

4.3.1、创建实体类
package com.atguigu.shardingjdbcdemo.entity;

@TableName("t_dict")
@Data
public class Dict {
    //可以使用MyBatisPlus的雪花算法
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    private String dictType;
}
4.3.2、创建Mapper
package com.atguigu.shardingjdbcdemo.mapper;

@Mapper
public interface DictMapper extends BaseMapper<Dict> {
}
4.3.3、配置广播表
#数据节点可不配置,默认情况下,向所有数据源广播
spring.shardingsphere.rules.sharding.tables.t_dict.actual-data-nodes=server-user.t_dict,server-order$->{0..1}.t_dict

## 广播表
spring.shardingsphere.rules.sharding.broadcast-tables[0]=t_dict

4.4、测试广播表

注意:如果某个数据库的插入操作执行失败,其他数据库不会回滚,从而导致数据不一致

@Autowired
private DictMapper dictMapper;

/**
     * 广播表:每个服务器中的t_dict同时添加了新数据
     */
@Test
public void testBroadcast(){

    Dict dict = new Dict();
    dict.setDictType("type1");
    dictMapper.insert(dict);
}

/**
     * 查询操作,只从一个节点获取数据
     * 随机负载均衡规则
     */
@Test
public void testSelectBroadcast(){

    List<Dict> dicts = dictMapper.selectList(null);
    dicts.forEach(System.out::println);
}

第07章 启动ShardingSphere-Proxy

1、获取

目前 ShardingSphere-Proxy 提供了 3 种获取方式:

2、使用二进制发布包安装

二进制包既可以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}

eg: start.bat 3307 ==> 指定端口为 3007

step5:远程连接ShardingSphere-Proxy

远程访问

mysql -h192.168.100.1 -P3307 -uroot -proot

step6:访问测试

show databases;
image-20220819152009158
image-20220819152009158

3、使用Docker安装

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

step2:上传MySQL驱动

将MySQl驱动mysql-connector-java-8.0.22.jar上传至/atguigu/server/proxy-a/ext-lib目录

spte3:修改配置server.yaml

将配置文件上传至/atguigu/server/proxy-a/conf目录,或者直接使用vi /atguigu/server/proxy-a/conf/server.yaml创建

rules:
  - !AUTHORITY
    users:
      - root@%:root
    provider:
      type: ALL_PRIVILEGES_PERMITTED

props:
  sql-show: true

spte4:重启容器

docker restart server-proxy-a

step5:远程连接ShardingSphere-Proxy

ShardingSphere-Proxy容器中默认情况下没有mysql命令行客户端的安装,因此需要远程访问(在自己的本机上连接)

mysql -h192.168.100.201 -P3321 -uroot -p

step6:访问测试

show databases;
image-20220819152009158
image-20220819152009158

常见问题: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 
image-20220819151154763
image-20220819151154763

解决方案:创建容器的时候使用JVM参数

-e ES_JAVA_OPTS="-Xmx256m -Xms256m -Xmn128m"

第08章 ShardingSphere-Proxy读写分离

1、修改配置文件

修改配置config-readwrite-splitting.yaml 或者直接使用vi /atguigu/server/proxy-a/conf/config-readwrite-splitting.yaml创建

注意:需要修改为自己虚拟机的ip,保证/atguigu/server/proxy-a/conf目录只有server.yamlconfig-readwrite-splitting.yaml两个配置文件

  • schemaName:向外提供的虚拟表
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

使用docker ps命令查看正在运行的容器

我们可以使用docker stop命令关闭不需要运行的容器,如docker stop server-user关闭server-user容器

我们可以使用docker start命令开启需要运行的容器,如 docker start atguigu-mysql-master开启atguigu-mysql-master容器

最终确保server-proxy-aatguigu-mysql-masteratguigu-mysql-slave1atguigu-mysql-slave2这4个容器在运行,由于server-proxy-a需要连接这3台真实的mysql数据库,所以最好让server-proxy-a最后再重启一次,保证其能连的上

2、实时查看日志

可以通过这种方式查看服务器中输出的SQL语句

docker exec -it server-proxy-a env LANG=C.UTF-8 /bin/bash
tail -f /opt/shardingsphere-proxy/logs/stdout.log 

3、远程访问测试

在自己电脑上使用如下命令操作容器里的代理数据库,即可在docker容器中实时看到输出日志

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');

4、应用程序访问Proxy

4.1、创建项目

项目类型:Spring Initializr

SpringBoot脚手架:http://start.aliyun.com

项目名:sharding-proxy-demo

SpringBoot版本:2.3.7.RELEASE

4.2、添加依赖

<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>

4.3、创建实体类

package com.atguigu.shardingproxydemo.entity;

@TableName("t_user")
@Data
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String uname;
}

4.4、创建Mapper

package com.atguigu.shardingproxydemo.mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

4.5、配置数据源

## 应用名称
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

4.6、测试

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);
    }
}

第09章 ShardingSphere-Proxy垂直分片

1、修改配置文件

修改配置config-sharding.yaml

需要删掉前面创建的config-readwrite-splitting.yaml配置文件,否则config-readwrite-splitting.yaml配置文件里配置的数据库我们连不上;保证/atguigu/server/proxy-a/conf目录只有server.yamlconfig-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

启动server-proxy-aserver-orderserver-user这3个容器,最后执行

docker restart server-proxy-a

保证server-proxy-a能连上其他两台真实的mysql数据库

2、实时查看日志

可以通过这种方式查看服务器中输出的SQL语句

docker exec -it server-proxy-a env LANG=C.UTF-8 /bin/bash
tail -f /opt/shardingsphere-proxy/logs/stdout.log 

3、远程访问测试

mysql> show databases;
mysql> use sharding_db;
mysql> show tables;
mysql> select * from t_order;
mysql> select * from t_user;

第10章 ShardingSphere-Proxy水平分片

1、修改配置文件

修改配置config-sharding.yaml

修改前面创建的config-sharding.yaml配置文件,保证/atguigu/server/proxy-a/conf目录只有server.yamlconfig-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

启动server-proxy-aserver-userserver-order0server-order1这4个容器,最后执行

docker restart server-proxy-a

保证server-proxy-a能连上其他两台真实的mysql数据库

2、实时查看日志

可以通过这种方式查看服务器中输出的SQL语句

docker exec -it server-proxy-a env LANG=C.UTF-8 /bin/bash
tail -f /opt/shardingsphere-proxy/logs/stdout.log 

3、远程访问测试

mysql> show databases;
mysql> use sharding_db;
mysql> show tables;
mysql> select * from t_order; --测试水平分片
mysql> select * from t_dict; --测试广播表
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.0.0-alpha.8