赞
踩
互联网业务兴起之后,海量用户加上海量数据的特点,单个数据库服务器已经难以满足业务需要,必须考虑数据库集群的方式来提升性能。高性能数据库集群的第一种方式是“读写分离”
,第二种方式是“数据库分片”
。
**读写分离原理:**读写分离的基本原理是将数据库读写操作分散到不同的节点上,下面是其基本架构图:
读写分离的基本实现:
主库负责处理事务性的增删改操作,从库负责处理查询操作
,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。根据 SQL 语义的分析
,将读操作和写操作分别路由至主库与从库
。一主多从
的配置方式,可以将查询请求均匀的分散到多个数据副本,能够进一步的提升系统的处理能力。多主多从
的方式,不但能够提升系统的吞吐量,还能够提升系统的可用性,可以达到在任何一个数据库宕机,甚至磁盘物理损坏的情况下仍然不影响系统的正常运行。读写分离的问题:
读写分离分散了数据库读写操作的压力,但没有分散存储压力,为了满足业务数据存储的需求,就需要将存储分散到多台数据库服务器上
。
数据分片:
将存放在单一数据库中的数据分散地存放至多个数据库或表中,以达到提升性能瓶颈以及可用性的效果。 数据分片的有效手段是对关系型数据库进行分库和分表
。数据分片的拆分方式又分为垂直分片和水平分片
。
垂直分库:
按照业务拆分的方式称为垂直分片,又称为纵向拆分
,它的核心理念是专库专用。 在拆分之前,一个数据库由多个数据表构成,每个表对应着不同的业务。而拆分之后,则是按照业务将表进行归类,分布到不同的数据库中,从而将压力分散至不同的数据库。
垂直拆分可以缓解数据量和访问量带来的问题,但无法根治。如果垂直拆分之后,表中的数据量依然超过单节点所能承载的阈值,则需要水平分片来进一步处理。
垂直分表:
垂直分表适合将表中某些不常用的列,或者是占了大量空间的列拆分出去。
假设我们是一个婚恋网站,用户在筛选其他用户的时候,主要是用 age 和 sex 两个字段进行查询,而 nickname 和 description 两个字段主要用于展示,一般不会在业务查询中用到。description 本身又比较长,因此我们可以将这两个字段独立到另外一张表中,这样在查询 age 和 sex 时,就能带来一定的性能提升。
垂直分表引入的复杂性主要体现在表操作的数量要增加。例如,原来只要一次查询就可以获取 name、age、sex、nickname、description,现在需要两次查询,一次查询获取 name、age、sex,另外一次查询获取 nickname、description。
水平分表适合表行数特别大的表,水平分表属于水平分片
水平分片又称为横向拆分。
相对于垂直分片,它不再将数据根据业务逻辑分类,而是通过某个字段(或某几个字段),根据某种规则将数据分散至多个库或表中,每个分片仅包含数据的一部分。 例如:根据主键分片,偶数主键的记录放入 0 库(或表),奇数主键的记录放入 1 库(或表),如下图所示。
单表进行切分后,是否将多个表分散在不同的数据库服务器中,可以根据实际的切分效果来确定。
**水平分表:**单表切分为多表后,新的表即使在同一个数据库服务器中,也可能带来可观的性能提升,如果性能能够满足业务要求,可以不拆分到多台数据库服务器,毕竟业务分库也会引入很多复杂性;
**水平分库:**如果单表拆分为多表后,单台服务器依然无法满足性能要求,那就需要将多个表分散在不同的数据库服务器中。
下图展现了将数据分片与读写分离一同使用时,应用程序与数据库集群之间的复杂拓扑关系。
读写分离和数据分片具体的实现方式一般有两种: 程序代码封装
和中间件封装
。
程序代码封装指在代码中抽象一个数据访问层(或中间层封装)
,实现读写操作分离和数据库服务器连接的管理。
**其基本架构是:**以读写分离为例
中间件封装指的是独立一套系统出来
,实现读写操作分离和数据库服务器连接的管理。对于业务服务器来说,访问中间件和访问数据库没有区别,在业务服务器看来,中间件就是一个数据库服务器。
**基本架构是:**以读写分离为例
Cobar 是阿里巴巴开源的一款基于MySQL的分布式数据库中间件,提供了分库分表、读写分离和事务管理等功能。它采用轮询算法和哈希算法来进行数据分片,支持分布式分表,但是不支持单库分多表。
它以 Proxy
方式提供服务,在阿里内部被广泛使用已开源,配置比较容易,无需依赖其他东西,只需要有Java环境即可。兼容市面上几乎所有的 ORM 框架,仅支持 MySQL 数据库,且事务支持方面比较麻烦。
Mycat
是社区爱好者在阿里 Cobar 基础上进行二次开发的,也是一款比较经典的分库分表工具。它以 Proxy 方式提供服务,支持分库分表、读写分离、SQL路由、数据分片等功能。
兼容市面上几乎所有的 ORM 框架,包括 Hibernate、MyBatis和 JPA等都兼容,不过,美中不足的是它仅支持 MySQL数据库,目前社区的活跃度相对较低。
TDDL 是阿里巴巴集团开源的一款分库分表解决方案,可以自动将SQL路由到相应的库表上。它采用了垂直切分和水平切分两种方式来进行分表分库,并且支持多数据源和读写分离功能。
TDDL 是基于 Java 开发的,支持 MySQL、Oracle 和 SQL Server 数据库,并且可以与市面上 Hibernate、MyBatis等 ORM 框架集成。
不过,TDDL仅支持一些阿里巴巴内部的工具和框架的集成,对于外部公司来说可能相对有些局限性。同时,其文档和社区活跃度相比 ShardingSphere 来说稍显不足。
MySQL Fabric
是 MySQL 官方提供的一款分库分表解决方案,同时也支持 MySQL其他功能,如高可用、负载均衡等。它采用了管理节点和代理节点的架构,其中管理节点负责实时管理分片信息,代理节点则负责接收并处理客户端的读写请求。
它仅支持 MySQL 数据库,并且可以与市面上 Hibernate、MyBatis 等 ORM 框架集成。MySQL Fabric 的文档相对来说比较简略,而且由于是官方提供的解决方案,其社区活跃度也相对较低。
ShardingSphere 成员中的 sharding-jdbc 以 JAR
包的形式下提供分库分表、读写分离、分布式事务等功能,但仅支持 Java 应用,在应用扩展上存在局限性。
因此,ShardingSphere 推出了独立的中间件 sharding-proxy,它基于 MySQL协议实现了透明的分片和多数据源功能,支持各种语言和框架的应用程序使用,对接的应用程序几乎无需更改代码,分库分表配置可在代理服务中进行管理。
除了支持 MySQL,ShardingSphere还可以支持 PostgreSQL、SQLServer、Oracle等多种主流数据库,并且可以很好地与 Hibernate、MyBatis、JPA等 ORM 框架集成。重要的是,ShardingSphere的开源社区非常活跃。
如果在使用中出现问题,用户可以在 GitHub 上提交PR并得到快速响应和解决,这为用户提供了足够的安全感。
通过对上述的 5 个分库分表工具进行比较,我们不难发现,就整体性能、功能丰富度以及社区支持等方面来看,ShardingSphere 在众多产品中优势还是比较突出的。下边用各个产品的主要指标整理了一个表格,看着更加直观一点。
Apache ShardingSphere 是一套开源的分布式数据库解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的数据水平扩展、分布式事务和分布式治理等功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。
Apache ShardingSphere 旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。 关系型数据库当今依然占有巨大市场份额,是企业核心系统的基石,未来也难于撼动,我们更加注重在原有基础上提供增量,而非颠覆。
Apache ShardingSphere 5.x 版本开始致力于可插拔架构,项目的功能组件能够灵活的以可插拔的方式进行扩展。 目前,数据分片、读写分离、数据加密、影子库压测等功能,以及 MySQL、PostgreSQL、SQLServer、Oracle 等 SQL 与协议的支持,均通过插件的方式织入项目。 开发者能够像使用积木一样定制属于自己的独特系统。Apache ShardingSphere 目前已提供数十个 SPI 作为系统的扩展点,仍在不断增加中。
程序代码封装**
定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务
。 它使用客户端直连数据库,以 jar 包形式提供服务
,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。
中间件封装
定位为透明化的数据库代理端
,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持。 目前提供 MySQL 和 PostgreSQL版本,它可以使用任何兼容 MySQL/PostgreSQL 协议的访问客户端(如:MySQL Command Client, MySQL Workbench, Navicat 等)操作数据,对 DBA 更加友好。
ShardingSphere-JDBC 采用无中心化架构,适用于 Java 开发的高性能的轻量级 OLTP(连接事务处理) 应用;ShardingSphere-Proxy 提供静态入口以及异构语言的支持,适用于 OLAP(连接数据分析) 应用以及对分片数据库进行管理和运维的场景。
Apache ShardingSphere 是多接入端共同组成的生态圈。 通过混合使用 ShardingSphere-JDBC 和 ShardingSphere-Proxy,并采用同一注册中心统一配置分片策略,能够灵活的搭建适用于各种场景的应用系统,使得架构师更加自由地调整适合与当前业务的最佳系统架构。
功能列表
分布式事务
分布式治理
主从复制(也称 AB 复制)允许将来自一个MySQL数据库服务器(主服务器)的数据复制到一个或多个MySQL数据库服务器(从服务器)。
复制是异步的 从站不需要永久连接以接收来自主站的更新。
MySQL中复制的优点包括:
- 横向扩展解决方案 - 在多个从站之间分配负载以提高性能。在此环境中,所有写入和更新都必须在主服务器上进行。但是,读取可以在一个或多个从设备上进行。该模型可以提高写入性能(因为主设备专用于更新),同时显着提高了越来越多的从设备的读取速度。
- 数据安全性 - 因为数据被复制到从站,并且从站可以暂停复制过程,所以可以在从站上运行备份服务而不会破坏相应的主数据。
- 分析 - 可以在主服务器上创建实时数据,而信息分析可以在从服务器上进行,而不会影响主服务器的性能。
- 远程数据分发 - 您可以使用复制为远程站点创建数据的本地副本,而无需永久访问主服务器。
基本原理:**
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日志,并解析成具体操作,从而实现主从操作一致,最终数据一致。服务器规划:使用docker
方式创建,主从服务器IP一致,端口号不一致
atguigu-mysql-master
,端口3306
atguigu-mysql-slave1
,端口3307
atguigu-mysql-slave2
,端口3308
端口3307
- docker run -d \
- -p 3307:3306 \
- -v /home/yanyu/mymysql/master/conf:/etc/mysql/conf.d \
- -v /home/yanyu/mymysql/master/data:/var/lib/mysql \
- -e MYSQL_ROOT_PASSWORD=1 \
- --name yanyu-mysql-master \
- mysql:8.0.29
默认情况下MySQL的binlog日志是自动开启的,可以通过如下配置定义一些可选配置
vim /home/yanyu/mymysql/master/conf/my.cnf
- # 服务器唯一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 yanyu-mysql-master
binlog格式说明:
- binlog_format=STATEMENT:日志记录的是主机数据库的
写指令
,性能高,但是now()之类的函数以及获取系统参数的操作会出现主从数据不同步的问题。- binlog_format=ROW(默认):日志记录的是主机数据库的
写后的数据
,批量操作时性能较差,解决now()或者 user()或者 @@hostname 等操作在主从机器上不一致的问题。- binlog_format=MIXED:是以上两种level的混合使用,有函数用ROW,没函数用STATEMENT,但是无法识别系统变量
binlog-ignore-db和binlog-do-db的优先级问题:
- #进入容器:env LANG=C.UTF-8 避免容器中显示中文乱码
- docker exec -it yanyu-mysql-master env LANG=C.UTF-8 /bin/bash
- #进入容器内的mysql命令行
- mysql -uroot -p
- #修改默认密码校验方式
- ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
- -- 创建slave用户
- CREATE USER 'yanyu_slave'@'%';
- -- 设置密码
- ALTER USER 'yanyu_slave'@'%' IDENTIFIED WITH mysql_native_password BY '1';
- -- 授予复制权限
- GRANT REPLICATION SLAVE ON *.* TO 'yanyu_slave'@'%';
- -- 刷新权限
- FLUSH PRIVILEGES;
执行完此步骤后不要再操作主服务器MYSQL
,防止主服务器状态值变化
SHOW MASTER STATUS;
记下File
和Position
的值。执行完此步骤后不要再操作主服务器MYSQL,防止主服务器状态值变化。
可以配置多台从机slave1、slave2...,这里以配置slave1为例
端口3308
- docker run -d \
- -p 3308:3306 \
- -v /home/yanyu/mymysql/slave1/conf:/etc/mysql/conf.d \
- -v /home/yanyu/mymysql/slave1/data:/var/lib/mysql \
- -e MYSQL_ROOT_PASSWORD=1 \
- --name yanyu-mysql-slave1 \
- mysql:8.0.29
vim /home/yanyu/mymysql/slave1/conf/my.cnf
配置如下内容:
- [mysqld]
- # 服务器唯一id,每台服务器的id必须不同,如果配置其他从机,注意修改id
- server-id=3
- # 中继日志名,默认xxxxxxxxxxxx-relay-bin
- #relay-log=relay-bin
重启MySQL容器
docker restart yanyu-mysql-slave1
- #进入容器:
- docker exec -it yanyu-mysql-slave1 env LANG=C.UTF-8 /bin/bash
- #进入容器内的mysql命令行
- mysql -uroot -p
- #修改默认密码校验方式
- ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123';
在从机上执行以下SQL操作
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;
启动从机的复制功能,执行SQL:
- START SLAVE;
- -- 查看状态(不需要分号)
- SHOW SLAVE STATUS\G
**两个关键进程:**下面两个参数都是Yes,则说明主从配置成功!
需要的时候,可以使用如下SQL语句
- -- 在从机上执行。功能说明:停止I/O 线程和SQL线程的操作。
- stop slave;
-
- -- 在从机上执行。功能说明:用于删除SLAVE数据库的relaylog日志文件,并重新启用新的relaylog文件。
- reset slave;
-
- -- 在主机上执行。功能说明:删除所有的binglog日志文件,并将日志索引文件清空,重新开始所有新的日志文件。
- -- 用于第一次进行搭建主从库时,进行主库binlog初始化工作;
- reset master;
项目类型:Spring Initializr
SpringBoot脚手架:http://start.aliyun.com
- <dependency>
- <groupId>com.mysql</groupId>
- <artifactId>mysql-connector-j</artifactId>
- <scope>runtime</scope>
- </dependency>
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-boot-starter</artifactId>
- <version>3.4.1</version>
- </dependency>
- <dependency>
- <groupId>org.apache.shardingsphere</groupId>
- <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
- <version>5.1.1</version>
- </dependency>
- 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

- @TableName("t_user")
- @Data
- public class User {
- @TableId(type = IdType.AUTO)
- private Long id;
- private String uname;
- }
- @Mapper
- public interface UserMapper extends BaseMapper<User> {
- }
- @SpringBootTest
- public class ReadWriteTest {
-
- @Autowired
- private UserMapper userMapper;
-
-
- /**
- * 读写分离:写入数据的测试
- */
- @Test
- public void testInsert(){
-
- User user = new User();
- user.setUname("张三丰");
- userMapper.insert(user);
- }
-
- /**
- * 读写分离:事务测试
- */
- @Transactional//开启事务
- @Test
- public void testTrans(){
-
- User user = new User();
- user.setUname("铁锤");
- userMapper.insert(user);
-
- List<User> users = userMapper.selectList(null);
- }
-
-
- /**
- * 读写分离:负载均衡测试
- */
- @Test
- public void testSelectAll(){
-
- List<User> users1 = userMapper.selectList(null);
- List<User> users2 = userMapper.selectList(null);
- List<User> users3 = userMapper.selectList(null);
- List<User> users4 = userMapper.selectList(null);
- // users.forEach(System.out::println);
- }
-
- }

为了保证主从库间的事务一致性,避免跨服务的分布式事务,ShardingSphere-JDBC的主从模型中,事务中的数据读写均用主库
。
- @Transactional//开启事务
- @Test
- public void testTrans(){
-
- User user = new User();
- user.setUname("铁锤");
- userMapper.insert(user);
-
- List<User> users = userMapper.selectList(null);
- }
- /**
- * 读数据测试
- */
- @Test
- public void testSelectAll(){
- List<User> users = userMapper.selectList(null);
- List<User> users = userMapper.selectList(null);//执行第二次测试负载均衡
- users.forEach(System.out::println);
- }
也可以在web请求中测试负载均衡
-
- @RestController
- @RequestMapping("/userController")
- public class UserController {
-
- @Autowired
- private UserMapper userMapper;
-
- /**
- * 测试负载均衡策略
- */
- @GetMapping("selectAll")
- public void selectAll(){
- List<User> users = userMapper.selectList(null);
- users.forEach(System.out::println);
- }
- }

配置标识 | 详细说明 | 全限定类名 |
---|---|---|
ROUND_ROBIN | 基于轮询的负载均衡算法 | org.apache.shardingsphere.infra.algorithm.loadbalancer.round.robin.RoundRobinLoadBalanceAlgorithm |
RANDOM | 基于随机的负载均衡算法 | org.apache.shardingsphere.infra.algorithm.loadbalancer.random.RandomLoadBalanceAlgorithm |
WEIGHT | 基于权重的负载均衡算法 | org.apache.shardingsphere.infra.algorithm.loadbalancer.weight.WeightLoadBalanceAlgorithm |
- 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
般的机器(4核16G),单库的MySQL并发(QPS+TPS)超过了2k,系统基本就完蛋了。最好是并发量控制在1k左右。这里就引出一个问题,为什么要分库分表?
分库分表目的:解决高并发,和数据量大的问题。
1、高并发情况下,会造成IO读写频繁,自然就会造成读写缓慢,甚至是宕机。一般单库不要超过2k并发,NB的机器除外。
2、数据量大的问题。主要由于底层索引实现导致,MySQL的索引实现为B+TREE,数据量其他,会导致索引树十分庞大,造成查询缓慢。第二,innodb的最大存储限制64TB。
要解决上述问题。最常见做法,就是分库分表。
分库分表的目的,是将一个表拆成N个表,就是让每个表的数据量控制在一定范围内,保证SQL的性能。 一个表数据建议不要超过500W。
不停机分库分表数据迁移
一般数据库的拆分也是有一个过程的,一开始是单表,后面慢慢拆成多表。那么我们就看下如何平滑的从MySQL单表过度到MySQL的分库分表架构。
1、利用mysql+canal做增量数据同步,利用分库分表中间件,将数据路由到对应的新表中。
2、利用分库分表中间件,全量数据导入到对应的新表中。
3、通过单表数据和分库分表数据两两比较,更新不匹配的数据到新表中。
4、数据稳定后,将单表的配置切换到分库分表配置上。
准备服务器
服务器规划:使用docker
方式创建如下容器
服务器:容器名server-user
,端口3301
服务器:容器名server-order
,端口3302
- @TableName("t_order")
- @Data
- public class Order {
- @TableId(type = IdType.AUTO)
- private Long id;
- private String orderNo;
- private Long userId;
- private BigDecimal amount;
- }
- package com.atguigu.shardingjdbcdemo.mapper;
-
- @Mapper
- public interface OrderMapper extends BaseMapper<Order> {
- }
- # 应用名称
- 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>:逻辑表名
- spring.shardingsphere.rules.sharding.tables.t_user.actual-data-nodes=server-user.t_user
- spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order.t_order
-
-
- # 打印SQL
- spring.shardingsphere.props.sql-show=true# 应用名称
- 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>:逻辑表名
- spring.shardingsphere.rules.sharding.tables.t_user.actual-data-nodes=server-user.t_user
- spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order.t_order
-
-
- # 打印SQL
- spring.shardingsphere.props.sql-show=true

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

准备服务器
服务器规划:使用docker
方式创建如下容器
服务器:容器名server-order0
,端口3310
服务器:容器名server-order1
,端口3311
基本配置
- #========================基本配置
- # 应用名称
- spring.application.name=sharging-jdbc-demo
- # 开发环境设置
- spring.profiles.active=dev
- # 内存模式
- spring.shardingsphere.mode.type=Memory
- # 打印SQl
- spring.shardingsphere.props.sql-show=true
- #========================数据源配置
- # 配置真实数据源
- 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

-
- #========================标准分片表配置(数据节点配置)
- # spring.shardingsphere.rules.sharding.tables.<table-name>.actual-data-nodes=值
- # 值由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。
- # <table-name>:逻辑表名
- spring.shardingsphere.rules.sharding.tables.t_user.actual-data-nodes=server-user.t_user
- 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
- @Test
- public void testInsertOrder(){
-
- Order order = new Order();
- order.setOrderNo("ATGUIGU001");
- order.setUserId(1L);
- order.setAmount(new BigDecimal(100));
- orderMapper.insert(order);
- }
优化上一步的分片表配置
- #========================标准分片表配置(数据节点配置)
- # spring.shardingsphere.rules.sharding.tables.<table-name>.actual-data-nodes=值
- # 值由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。
- # <table-name>:逻辑表名
- spring.shardingsphere.rules.sharding.tables.t_user.actual-data-nodes=server-user.t_user
- spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order$->{0..1}.t_order$->{0..1}
分片规则:order表中user_id
为偶数时,数据插入server-order0服务器
,user_id
为奇数时,数据插入server-order1服务器
。这样分片的好处是,同一个用户的订单数据,一定会被插入到同一台服务器上,查询一个用户的订单时效率较高。
- #------------------------分库策略
- # 分片列名称
- spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-column=user_id
- # 分片算法名称
- spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-algorithm-name=alg_inline_userid
-
- #------------------------分片算法配置
- # 行表达式分片算法
- # 分片算法类型
- spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_userid.type=INLINE
- # 分片算法属性配置
- 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
-
- # 哈希取模分片算法
- # 分片算法类型
- spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.type=HASH_MOD
- # 分片算法属性配置
- spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.props.sharding-count=2

测试:可以分别测试行表达式分片算法和取模分片算法
- /**
- * 水平分片:分库插入数据测试
- */
- @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是字符串形式,因此不能直接取模。
- #------------------------分表策略
- # 分片列名称
- spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-column=order_no
- # 分片算法名称
- spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-algorithm-name=alg_hash_mod
-
-
- #------------------------分片算法配置
- # 哈希取模分片算法
- # 分片算法类型
- spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.type=HASH_MOD
- # 分片算法属性配置
- spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.props.sharding-count=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);
- }

ShardingSphere提供灵活的配置分布式主键生成策略方式。在分片规则配置模块克配置每个表的主键生成策略。默认使用雪花算法。(snowflake)生成64bit的长整型数据。支持两种方式配置
雪花算法:
水平分片需要关注全局序列,因为不能简单的使用基于数据库的主键自增。
这里有两种方案:一种是基于MyBatisPlus的id策略;一种是ShardingSphere-JDBC的全局序列配置。
基于MyBatisPlus的id策略:
将Order类的id设置成如下形式
- @TableId(type = IdType.ASSIGN_ID)
- private Long id;
基于ShardingSphere-JDBC的全局序列配置
:和前面的MyBatisPlus的策略二选一
- #------------------------分布式序列策略配置
- # 分布式序列列名称
- spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.column=id
- # 分布式序列算法名称
- spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.key-generator-name=alg_snowflake
-
- # 分布式序列算法配置
- # 分布式序列算法类型
- spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.type=SNOWFLAKE
- # 分布式序列算法属性配置
- #spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.props.xxx=
此时,需要将实体类中的id策略修改成以下形式:
- //当配置了shardingsphere-jdbc的分布式序列时,自动使用shardingsphere-jdbc的分布式序列
- //当没有配置shardingsphere-jdbc的分布式序列时,自动依赖数据库的主键自增策略
- @TableId(type = IdType.AUTO)
在server-order0、server-order1
服务器中分别创建两张订单详情表t_order_item0、t_order_item1
我们希望同一个用户的订单表和订单详情表中的数据都在同一个数据源中,避免跨库关联
,因此这两张表我们使用相同的分片策略。
那么在t_order_item
中我们也需要创建order_no
和user_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)
- );

创建实体类
- 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;
- }
创建Mapper
- package com.atguigu.shargingjdbcdemo.mapper;
-
- @Mapper
- public interface OrderItemMapper extends BaseMapper<OrderItem> {
-
- }
配置关联表
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

测试插入数据
同一个用户的订单表和订单详情表中的数据都在同一个数据源中,避免跨库关联
- /**
- * 测试关联表插入
- */
- @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);
- }
- }
-
- }

**需求:**查询每个订单的订单号和总订单金额
配置绑定表
在原来水平分片配置的基础上添加如下配置:
- #------------------------绑定表
- spring.shardingsphere.rules.sharding.binding-tables[0]=t_order,t_order_item
配置完绑定表后再次进行关联查询的测试:
**如果不配置绑定表:测试的结果为8个SQL。**多表关联查询会出现笛卡尔积关联。
如果配置绑定表:测试的结果为4个SQL。 多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。
绑定表:
指分片规则一致的一组分片表。 使用绑定表进行多表关联查询时,必须使用分片键进行关联,否则会出现笛卡尔积关联或跨库关联,从而影响查询效率。
指所有的分片数据源中都存在的表,表结构及其数据在每个数据库中均完全一致。 适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表。
广播具有以下特性:
(1)插入、更新操作会实时在所有节点上执行,保持各个分片的数据一致性
(2)查询操作,只从一个节点获取
(3)可以跟任何一个表进行 JOIN 操作
配置广播表
- #数据节点可不配置,默认情况下,向所有数据源广播
- 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
- 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
修改配置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

配置数据源
- # 应用名称
- 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
- 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

修改配置文件
修改配置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

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。