前置引入
随着互联网的飞速发展,传统的数据库服务器已经无法满足我们的需求,必须寻找其他办法,如数据库集群的方式去提系统性能。高性能数据库集群目前有两种架构模式:1. “读写分离"模式, 2. “数据库分片”模式。
读写分离架构
从上图可以看到,在数据库集群中,分为了主节点和众多从节点,对于业务服务器来说,读取数据一般与从节点进行交互,写数据的时候和主节点进行交互,主节点的数据更新之后,主节点与从节点之间会进行主从复制操作进行数据的同步。
问题
- 数据一致性问题:当我们向数据库主节点写入一条数据,在主从节点之间数据还没有更新的时候,向从节点查询数据,这显然是无法查到的。这里涉及到了分布式系统中的CAP定理:在一个分布式系统中,当涉及到读写操作时,只能保证一致性(Consistence),可用性(Availability),分区容错性(Partition tolerance)三者中的两个
- Consistence:对某个指定的客户端来说,读操作保证能返回最新的结果(现实中无法完美实现)
- Availability:非故障的节点在合理的时间内返回合理的响应
- Partition tolerance:当出现网络分区(各个服务器之间的交互出现了问题,如丢包、连接中断等)之后,系统能继续正常工作
- 分散了数据库的读写压力,但没有分散存储压力,解决办法就是数据分片,将数据分片后,存储到多台数据库服务器上,其实也就是我们常说的分库分表,一般分库分表的操作分为两种拆分方式:
- 垂直拆分:
- 垂直拆分之垂直分库
垂直拆分的核心理念是专库专用,按照业务拆分的方式进行拆分,举个例子,假设现在数据库中有几张表:分别是用户数据表,订单 表,商品数据表,拆分前这些表都是在同一个数据库中的,而拆分后,这些数据都被分到了不同的数据库中,如下所示是三个不同的数据库。
这种拆分方式可以缓解数据量和访问量带来的问题,这种方式还是无法解决单表数据过于庞大的情况,此时就需要用到水平分片了。 - 垂直拆分之垂直分表
垂直分表将以前的表中,一些不常用的列,或者是占用了大量空间的列进行拆分。举个栗子,假设一个婚恋网站,现在用户信息的字段有name、age、sex、nickName、description这些字段,而在网站搜索用户信息的时候,一般都是展示name、age、sex这几个字段信息,后面两个字段一般不进行展示,那此时我们就可以单独将name、age、sex作为一张表,nickName、description作为另外一张表。
- 垂直拆分之垂直分库
- 水平分片
- 水平分片之水平分库
如果单个表拆分为多个表之后,单台服务器依然无法满足性能要求,那就需要将表分散在不同的数据库服务器中 - 水平分片之水平分表
根据阿里巴巴的规范,当一个表中行数超过500w或容量超过2GB,就推荐进行分表操作了
- 水平分片之水平分库
- 垂直拆分:
ShardingSphere
简介:Apache ShardingSphere是一款开源分布式数据库生态项目,旨在碎片化的异构数据库上层构建生态,在最大限度的复用数据库原生存算能力的前提下,进一步提供面向全局的扩展和叠加计算能力。其核心采用可插拔架构,对上以数据库协议及SQL方式提供诸多增强功能,包括数据分片、访问路由、数据安全等。
ShardingSphere-JDBC
定位为轻量级Java框架,在Java 的 JDBC层提供的额外服务。它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。下面是ShardingSphere-JDBC的实战教程。
- Mysql集群的安装(基于Centos7和docker)读写分离的测试
-
在docker中创建并启动MySQL主服务器,端口3307
这里前提是已经下载好了centos和docker,以及mysql的镜像,这里我就不演示安装了。
我的mysql镜像是mysql:8.0.26,我根据这个镜像创建容器。docker run -d \ -p 3307:3306 \ -v /home/docker/software/mysql/master/conf:/etc/mysql/conf.d \ -v /home/docker/software/mysql/master/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=123456 \ --name mysql-master \ mysql:8.0.26
-d标识mysql容器以守护线程的方式创建,-p 3307:3306表示将宿主机的3307端口映射到docker的3306端口,下面两个-v是进行配置文件的映射,-e是设置MySQL的密码,-name给容器取名字。
执行成功后,可以看到容器已经运行成功! -
切换至/home/docker/software/mysql/master/conf下,创建my.conf文件,文件内容如下
[mysqld] #服务器唯一id server-id=1 #设置日志格式,默认row binlog-format=STATEMENT
保存后重启容器,docker restart mysql-master
-
进入mysql-master容器中,使用命令行登录主服务器
#进入容器内部 docker exec -it mysql-master env lANG=C.UTF-8 /bin/bash #进入容器内的mysql命令行 mysql -u root -p 123456 #修改默认密码校验方式 ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
-
设置mysql的主节点服务器
在主从同步的时候,从服务器会开一个io线程向主服务器中读取binlog日志,因此我们要在主节点中开一个账号给从节点,以访问主节点,下面是具体的命令:# 创建slave用户 CREATE USER ’slave‘@’%‘; # 设置密码 ALTER USER ’slave‘@’%' IDENTIFIED WITH mysql_native_password BY '123456'; # 授予复制权限 GRANT REPLICATION SLAVE ON *.* TO ’slave‘@’%‘; # 刷新权限 FLUSH PRIVILEGES;
-
安装从节点服务器
-
创建容器,端口为3308
docker run -d \ -p 3308:3306 \ -v /home/docker/software/mysql/slave/conf:/etc/mysql/conf.d \ -v /home/docker/software/mysql/slave/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=123456 \ --name mysql-slave01 \ mysql:8.0.26
-
创建slave01的配置文件
进入 /home/docker/software/mysql/slave/conf创建my.conf文件,文件内容如下:[mysqld] #服务器唯一id server-id=2
-
重启slave节点
docker restart mysql-slave01
-
进入容器内部,配置密码等,和主节点的操作一样,这里省略了。。。
-
在从节点上配置主从关系
# 在主节点的mysql控制台输入show master status 查看binlog的名字,以及偏移量position,填在下面 # slave是在主节点上为从节点创建的账号,上面已经创建了,主节点的端口是3307 CHANGE MASTER TO MASTER_HOST='192.168.101.65', MASTER_USER='slave',MASTER_PASSWORD='123456',MASTER_PORT=3307, MASTER_LOG_FILE='binlog.ooo003',MASTER_LOG_POS=1332;
-
安装slave02,步骤和安装01一样,注意命名和端口即可
例如:docker run -d \ -p 3309:3306 \ -v /home/docker/software/mysql/slave2/conf:/etc/mysql/conf.d \ -v /home/docker/software/mysql/slave2/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=123456 \ --name mysql-slave02 \ mysql:8.0.26
当我们安装好一台主节点master和两台从节点slave01和slave02,并开启主从复制后,我们在主节点的任何操作,都会同步到所有从节点上。大家可以自己尝试操作一下!
-
-
水平分片
2.1 需求
现在我们的需求就是,订单数据在两台数据库服务器的数据库db_order中,我们在向数据库插入数据的时候,要实现根据不同的算法,分别插入到这两个数据库服务器不同的订单表(t_order)中。以下是具体的操作。2.2 具体执行过程
先使用docker创建两个mysql容器,具体步骤上面有,名字分别为server-order0,server-order1,创建完成之后,分别在这两个数据库服务器中执行一下sql命令: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) );
现在server-order0和server-order1的服务器中有了一个名为db_order的数据库,且包含两张表t_order0和t_order1
2.3 shardingsphere配置文件编写
#-----------------------------------------基本配置 # 应用名称 spring.application.name=sharding-jdbc-demo # 开发环境设置 spring.profiles.active=dev # 内存模式 spring.shardingsphere.mode.type=Memory # 打印SQl spring.shardingsphere.props.sql-show=true #-----------------------------------------数据源配置 # 配置真实数据源 spring.shardingsphere.datasource.names=master,server-order0,server-order1 # 配置第1个数据源 spring.shardingsphere.datasource.master.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.master.jdbc-url=jdbc:mysql://192.168.101.65:3307/db_user?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true spring.shardingsphere.datasource.master.username=root spring.shardingsphere.datasource.master.password=123456 # 配置第2个数据源 spring.shardingsphere.datasource.server-order0.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.server-order0.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.server-order0.jdbc-url=jdbc:mysql://192.168.101.65:3310/db_order?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true spring.shardingsphere.datasource.server-order0.username=root spring.shardingsphere.datasource.server-order0.password=123456 # 配置第3个数据源 spring.shardingsphere.datasource.server-order1.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.server-order1.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.server-order1.jdbc-url=jdbc:mysql://192.168.101.65:3311/db_order?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true spring.shardingsphere.datasource.server-order1.username=root spring.shardingsphere.datasource.server-order1.password=123456 #-----------------------------------------标准分片配置 spring.shardingsphere.rules.sharding.tables.t_user.actual-data-nodes=master.t_user spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order$->{0..1}.t-order0 #------------------------分库策略 # 分片列名称 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}
在上述配置文件中,我们配置了三个数据源,分别是master,server-order0,server-order1,在标准分片配置注释下面,我们配置了真实的数据节点,
server-order$->{0..1}.t-order0
使用了行表达式,拆开看就是server-order0.t-order0,server-order1.t-order0,这样2个,分别对应数据源的不同表。在分片算法配置中,选择的分片算法为INLINE,这种类型支持我们自定义分片算法,spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_userid.props.algorithm-expression=server-order$->{user_id % 2}
这行代码代表的是,当插入数据的user_id为偶数的时候,就插入数据源server-order0中的t-order0中,当user_id为奇数的时候,就插入数据源server-order1中的t-order0中。
2.4 测试代码编写@Test public void testOrderInsert() { for (long i = 1; i < 5; i++) { Order order = new Order(); order.setOrderNo("Sharding" + i); order.setUserId(i); order.setAmount(new BigDecimal(100)); orderMapper.insert(order); } }
在你的springboot测试类中加入上面的方法,并运行,最后的结果如下,user_id为偶数时,数据插入到server-order0中,反之插入到server-order1中