ShardingSphere实战分表分库
介绍
ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar这3款相互独立的产品组成。
他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、云原生等各种多样化的应用场景。
垂直拆分
1.数据库拆分主要指分库分表,其目的主要是分散数据库压力,达到横向扩展,满足均衡访问等。
2.数据库拆分主要有两种方式:垂直拆分和水平拆分。
垂直拆分:
将不同业务功能相关的表放到不同的数据库中也就是类似于微服务架构中会员数据库/订单数据库/支付数据库
水平拆分
水平拆分:
当一张表的业务量行数如果超过500万行(阿里巴巴java开发手册官方推荐),分页/排序效率还是非常低,可以对同一张表数据实现拆分放到多个不同的表中存放。
如何分表分库
Mycat 与shadingjdbc 区别?
Mycat 是基于服务器端的形式实现数据库代理 基于服务器端的形式
改写sql语句
shadingjdbc 基于客户端 改写sql语句的形式实现实现数据库代理
基于客户端aop的机制改写sql语句。
shadingjdbc
Aop 代理 数据源
如果客户端发送jdbc语句 被aop代理改写sql语句
1. 基于服务器端mycat实现数据库代理
优点:能够保证数据库的安全性
缺点:效率比较低
2. 基于客户端Shardingjdbc实现数据库代理
优点:效率比较高
缺点:不能够保证数据库的安全性、客户端容易内存溢出
分表分库中间件
Mycat 基于服务器端 分表分库中间件
shadingjdbc 基于客户端改写sql 分表分库中间件
分表分库策略
分表分库需要考虑点:扩容性、每张表数据均匀性。
- 取余/取模 以后无法做扩容
- 按照范围分片 扩容性、每张表数据均匀性研究kafka的原理彻底明白
- 按照日期进行分片----订单表 - order2021 order2022 order2023 订单号码
- 按照枚举值分片
- 二进制取模范围分片
- 一致性hash分片
- 按照目标字段前缀指定的进行分区
- 按照前缀ASCII码和值进行取模范围分片
Mysql集群环境搭建
安装master节点
关闭防火墙
systemctl stop firewalld
使用docker启动mysql 主节点
docker run --name mysql_master -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7.13
参数说明:
mysql:5.7.13 指定运行的mysql版本
-d |
以守护进程的方式启动 |
- -restart=always |
docker重启时候容器自动重启 |
- -name |
为容器指定名称,这里是master |
-p |
将容器的指定端口映射到主机的指定端口,这里是将容器的3306端口映射到主机的3306端口 |
创建从节点账号
连接主节点数据库上执行
GRANT REPLICATION SLAVE ON *.* to 'slave'@'%' identified by 'slave'; show grants for 'slave'@'%';
master节点配置
- 创建mysql配置文件目录
mkdir -p /usr/local/mysql/master mkdir -p /usr/local/mysql/slave
2. 将容器中的/etc/mysql/my.cnf 拷贝到容器外部
docker cp mysql_master:/etc/mysql/my.cnf /usr/local/mysql/master/my.cnf
将容器中/etc/mysql/my.cnf 拷贝容器外部中/usr/local/mysql/master/my.cnf
3. 进入外部:cd /usr/local/mysql/master
编辑 my.cnf
新增:
log-bin=mysql-bin ##使用binary logging,mysql-bin是log文件名的前缀 server-id=1 ###唯一服务器ID,非0整数,不能和其他服务器的server-id重复
4. 将修改后的文件覆盖Docker中MySQL中的配置文件
docker cp /usr/local/mysql/master/my.cnf mysql_master:/etc/mysql/my.cnf
5 . 重启 mysql 的docker , 让配置生效
docker restart mysql_master
7. 查看主节点数据库是否开启binlog
show variables like 'log_bin'; show master status;
安装slave节点
关闭防火墙
systemctl stop firewalld
使用docker启动mysql slave节点
docker run --name mysql_slave -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7.13
参数说明:
mysql:5.7.13 指定运行的mysql版本
-d |
以守护进程的方式启动 |
- -restart=always |
docker重启时候容器自动重启 |
- -name |
为容器指定名称,这里是slave |
-p |
将容器的指定端口映射到主机的指定端口,这里是将容器的3306端口映射到主机的3306端口 |
slave节点配置
- 创建mysql配置文件目录
mkdir -p /usr/local/mysql/slave mkdir -p /usr/local/mysql/slave
2. 将容器中的/etc/mysql/my.cnf 拷贝到容器外部
docker cp mysql_slave:/etc/mysql/my.cnf /usr/local/mysql/slave/my.cnf
将容器中/etc/mysql/my.cnf 拷贝容器外部中/usr/local/mysql/slave/my.cnf
3. 进入外部:cd /usr/local/mysql/slave
编辑 my.cnf
新增:
log-bin=mysql-bin ##使用binary logging,mysql-bin是log文件名的前缀 server-id=2 ###唯一服务器ID,非0整数,不能和其他服务器的server-id重复
4. 将修改后的文件覆盖Docker中MySQL中的配置文件
docker cp /usr/local/mysql/slave/my.cnf mysql_slave:/etc/mysql/my.cnf
5 . 重启 mysql 的docker , 让配置生效
docker restart mysql_slave
查看从节点数据库bin_log 开启情况
show variables like 'log_bin';
STOP SLAVE; CHANGE MASTER TO MASTER_HOST='连接Navicat的ip', MASTER_PORT=正确的端口, MASTER_USER='正确的用户名', MASTER_PASSWORD='正确的密码'; START SLAVE;
my.conf 相关配置
[mysqld] ## 设置server_id,一般设置为IP,同一局域网内注意要唯一 server_id=100 ## 复制过滤:也就是指定哪个数据库不用同步(mysql库一般不同步) binlog-ignore-db=mysql ## 开启二进制日志功能,可以随便取,最好有含义(关键就是这里了) log-bin=edu-mysql-bin ## 为每个session 分配的内存,在事务过程中用来存储二进制日志的缓存 binlog_cache_size=1M ## 主从复制的格式(mixed,statement,row,默认格式是statement) binlog_format=mixed ## 二进制日志自动删除/过期的天数。默认值为0,表示不自动删除。 expire_logs_days=7 ## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。 ## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致 slave_skip_errors=1062
SpringBoot项目整合Sharing-Jdbc
maven依赖
<properties> <sharding-sphere.version>4.1.1</sharding-sphere.version> </properties> <!-- for spring boot --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>${sharding-sphere.version}</version> </dependency> <!-- for spring namespace --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-namespace</artifactId> <version>${sharding-sphere.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.6</version> </dependency>
初始化测试数据库脚本
CREATE TABLE `sharing_user` ( `id` int(11) NOT NULL, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
数据库连接健康检查问题
解决SpringBoot2.6.6+,由于`sharding-jdbc`没有完全实现Datasouce接口规范导致功能不足。导致数据库连接健康检查不通过
/** * @Author iron.guo * @Date 2023/1/15 * @Description */ public class DataSourceConfig { @Bean public DataSourcePoolMetadataProvider getMetadata(DataSource encryptDataSource){ return dataSource -> { if(dataSource instanceof EncryptDataSource && encryptDataSource instanceof EncryptDataSource ){ return new ShardingSphereDruidDataSourcePoolMetadata((EncryptDataSource) encryptDataSource); } return null; }; } public static class ShardingSphereDruidDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata<EncryptDataSource> { private final DruidDataSource druidDataSource; protected ShardingSphereDruidDataSourcePoolMetadata(EncryptDataSource dataSource) { super(dataSource); this.druidDataSource = (DruidDataSource) dataSource.getDataSource(); } @Override public Integer getActive() { return druidDataSource.getActiveCount(); } @Override public Integer getMax() { return druidDataSource.getMaxActive(); } @Override public Integer getMin() { return druidDataSource.getMinIdle(); } @Override public String getValidationQuery() { return druidDataSource.getValidationQuery(); } @Override public Boolean getDefaultAutoCommit() { return druidDataSource.isDefaultAutoCommit(); } } }
Sharing-Jdbc读写分离
设置sharing-jdbc读写分离配置
spring: main: # 允许Bean覆盖 allow-bean-definition-overriding: true shardingsphere: datasource: names: master,slave # 数据源名字 master: driverClassName: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource # 连接池 url: jdbc:mysql://127.0.0.1:3306/sharing_db # 主库地址 username: root # 主库用户名 password: 123456 # 主库密码 slave: driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource # 连接池 url: jdbc:mysql://127.0.0.1:3307/sharing_db # 从库地址 username: root password: 123456 masterslave: load-balance-algorithm-type: round_robin # 负载均衡算法, name: ms master-data-source-name: master # 主库数据源名字 slave-data-source-names: slave # 从库数据源名字 props: sql: show: true
测试接口
@GetMapping("writing") public ResponseResult<Boolean> writing(){ User user=new User(); user.setId(1); user.setName("iron"); userService.save(user); return ResponseResult.Success(); } @GetMapping("reading") public ResponseResult<?> reading(){ User user = userService.getById(1); return ResponseResult.Success(user); }
访问读接口
访问写接口
Sharing-Jdbc分库分表
分表分库配置
spring: main: # 允许Bean覆盖 allow-bean-definition-overriding: true shardingsphere: datasource: names: ds1,ds2 # 数据源名字 ds1: driverClassName: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource # 连接池 url: jdbc:mysql://127.0.0.1:3306/sharing_db # 主库地址 username: root # 主库用户名 password: 123456 # 主库密码 ds2: driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource # 连接池 url: jdbc:mysql://127.0.0.1:3307/sharing_db # 从库地址 username: root password: 123456 sharding: default-data-source-name: ds1 tables: sharing_user: key-generator: column: id type: SNOWFLAKE actual-data-nodes: ds$->{1..1}.sharing_user$->{1..2} ## 自定义分表算法 table-strategy: standard: precise-algorithm-class-name: com.tiger.sharingjdbc.config.TigerRangeShardingAlgorithm sharding-column: id # 分库策略:按id的奇偶性水平分片 database-strategy: inline: sharding-column: id algorithm-expression: ds$->{id % 2 + 1}
分片算法实现
/** * @Author iron.guo * @Date 2023/1/15 * @Description */ @Slf4j public class TigerRangeShardingAlgorithm implements PreciseShardingAlgorithm<Long> { private Long tableSize = 2L; @Override public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) { Double userId = Double.valueOf(preciseShardingValue.getValue()); Double temp = userId % tableSize +1; String tableName = "sharing_user" + (int)Math.ceil(temp); log.info("<tableName{}>", tableName); return tableName; } }
调用接口他会分布存放在不同的表中