数据库分库分表

在项目开发的过程中,随着项目的不断庞大,伴随着的就是数据量的不断膨胀,当数据达到了一个量级之后,就会考虑数据的拆分。这个问题对于所有的中大型项目都不可避免要进行处理。

docker下安装mysql

docker run --name mysql3311 -p 3311:3306 --privileged=true -ti -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_USER=user -e MYSQL_PASSWORD=pass -v /home/mysql/docker-data/3311/conf:/etc/mysql/conf.d -v /home/mysql/docker-data/3311/data/:/var/lib/mysql -v /home/mysql/docker-data/3311/logs/:/var/log/mysql -d mysql:5.7

数据库扩展思想

  • 热备份:数据库在运行的过程中,对数据进行备份操作。相对的,还有冷备份,冷备份需要停机,然后对数据进行备份操作。
  • 多活:所谓的多活,就是让数据库机器节点会存在多个,避免单点情况的出现。
  • 故障切换:当一台数据库物理机出现异常状况时,可以自动的切换到其他物理机上。
  • 读写分离:当存在存在多台数据库物理机,将读写操作分别交给不同的机器完成。
  • 负载均衡:假设当存在多台数据库物理机接收读请求时,多个请求会均匀的分配到不同的机器上,避免大量请求压在某一台机器上。

Mysql常见架构设计

​ 首先对于架构设计来说,没有百分百的完美架构,只有适合的架构。要想理解mysql的分库分表,必须要先对mysql的架构设计有一定的了解,对于mysql架构,一定会使用到读写分离,在此基础上有五种常见架构设计:一主一从或多从、主主复制、级联复制、主主与级联复制结合。

主从复制

概念

​ 这种架构设计是使用的最多的。在读写分离的基础上,会存在一台master作为写机,一个或多个slave作为读机。因为在实际的情况下,读的请求量一般是远远大于写请求的。

image-20200524232250958

​ 采用这种架构之后,当应用写入输入时,会把数据写入到master节点,然后由master节点将写入数据复制到slave节点上。

缺点:

​ 1)因为master是单点存在的,所以如果要对master进行停机维护,则无法接收写请求。

​ 2)master需要将写入数据复制到各个slave节点,但是复制是有一定的时间延迟的,因此有可能出现查询数据延迟。

​ 3)如必须要对master进行停机维护,则需将某一个slave提升为master节点,将哪一个slave提升为master也需要考虑。

​ 4)当某一个slave被提升为master后,则会造成被提升的master节点与原master的数据不一致。并且之前的master并没有最新的binlog信息。

搭建

​ 此处以一主一备来进行演示。

​ 根据刚才docker创建mysql容器的参数指定,需要在两台机器上的**/home/mysql/docker-data/3307/conf目录下,需要创建mysql的配置文件my.cnf**。

# For advice on how to change settings please see
# http://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html

[mysqld]
#
# Remove leading # and set to the amount of RAM for the most important data
# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
# innodb_buffer_pool_size = 128M
#
# Remove leading # to turn on a very important data integrity option: logging
# changes to the binary log between backups.
# log_bin
#
# Remove leading # to set options mainly useful for reporting servers.
# The server defaults are faster for transactions and fast SELECTs.
# Adjust sizes as needed, experiment to find the optimal values.
# join_buffer_size = 128M
# sort_buffer_size = 2M
# read_rnd_buffer_size = 2M
#datadir=/home/mysql/docker-data/3307/data
#socket=/home/mysql/docker-data/3307/mysql.sock

character_set_server=utf8
init_connect='SET NAMES utf8'

# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0

#log-error=/home/mysql/docker-data/3307/logs/mysqld.log
#pid-file=/home/mysql/docker-data/3307/mysqld.pid
lower_case_table_names=1
#指定主机号,不允许出现重复
server-id=1423307
#开启binlog
log-bin=mysql-bin
auto_increment_increment=2
auto_increment_offset=1

#rpl_semi_sync_master_enabled=1
#rpl_semi_sync_master_timeout=10000

​ 在master的docker容器中添加mysql权限,开启备份机复制,并且设置备份用户信息

#添加权限
GRANT REPLICATION SLAVE,FILE,REPLICATION CLIENT ON *.* TO 'repluser'@'%' IDENTIFIED BY '123456';

#刷新权限
FLUSH PRIVILEGES;

image-20200525231404926

​ 设置并刷新权限后,重启mysql服务器,可以查看master上的binlog信息

show master status;

image-20200525231505062

注:如果没有查询到任何信息代表master的binlog日志没有开启,查看配置文件或者重启docker容器

​ 接着在slave中进入到mysql容器,设置master信息,用于标注当前slave的master是谁。

change master to master_host='master的ip',master_port=master的端口号,master_user='repluser',master_password='123456',master_log_file='master中的binlob文件',master_log_pos=master中的position位置信息;

change master to master_host='192.168.200.180',master_port=3310,master_user='repluser',master_password='123456',master_log_file='mysql-bin.000001',master_log_pos=154;

image-20200525231937581

​ 设置完成后,还要开启slave中的IOSQL线程,这两个线程主要用于slave中进行数据备份,可以先查看slave中这两个线程的状态

show slave status/G

image-20200525232119712

​ 此时,在slave中,这两个线程是关闭的,需要将这两个线程进行开启

start slave;

image-20200525232307499

image-20200525232323712

​ 此时可以看到,这两个线程已经开启。

​ 截止到此,mysql主从复制就已经搭建完毕了。接着可以来查看相关的状态信息。

​ 首先可以先查看slave中的binlog是否已经开启

show global variables like "%log%";

image-20200525233252673

​ 根据信息,slave节点中的binlog日志已经开启。

​ 接着还可以查看master、slave中的进程信息,

​ 在master中输入

show processlist

image-20200525233400935

​ 根据信息已经告诉我们,master已经发送的自身的binlog信息到slave上,并且正在等待更多的更新操作。

​ 同时也可以在slave中输入

show processlist

image-20200525233509873

​ 根据信息可以看到,在slave它已经连接到了master,正在等待master发送事件,并且slave已经读取了所有的relay log信息,并且正在等待更多的更新操作。

测试

​ 1)在master中的user数据库中创建一张表。创建成功后,slave中也会同步更新出相同的表。

​ 2)在master中enjoy数据库的表中新增一条记录,新增成功后,slave中也会同步新增一条数据。

​ 3)但在slave中新增记录,master中并不会同步更新,因为现在配置的是单向复制。只会master向slave中复制数据。

Mysql复制原理解析

​ 刚才已经搭建完了主从复制,虽然效果已经实现了,但是对于mysql内部的复制操作,其内部又是如何完成的呢?

​ 在mysql中,其有两种复制机制,分别是:异步复制半同步复制。默认采用异步复制。

复制机制的实现原理

异步复制执行流程

image-20200824144352020

1)应用事务提交到master

2)master接收到应用事务提交请求后,会更新内部的binlog日志,接着让mysql引擎执行事务操作,并返回给客户端执行结果信息。同时在master中会存在一个事件监听,其会一直监听着master中binlog日志文件的改变,一旦发现日志文件发生改变,则会触发dump线程。

3)dump线程被触发后,会通知slave中的IO线程现在有事务操作要进行同步。

4)slave中IO线程接收到通知后,会从slave中relay-log.info文件中获取slave中的binlog日志文件和pos位置信息。接着会把这部分信息发送给master的dump线程。

5)master的dump线程收到这些信息后,会根据slave发送的binlog日志文件和pos位置,将最新的binlog日志和pos位置后面的内容同步给slave的IO线程。

6)slave的IO线程接收到这些信息后,会将这部分内容同步到slave中的relay-bin文件中。

7)当relay-bin文件发生改变后,会触发slave 线程执行sql操作,【异步操作】

8)当slave向relay-bin写入完成后,还会向master返回一个ACK消息,通知slave已经执行成功。

​ 对于这一系列的操作,可以发现master和slave在进行同步时是以异步的方式完成的,master写入完binlog后,会马上通过引擎进行事务提交并向客户端返回响应,对于与slave同步的操作,则是异步完成的。

​ 虽然这种方式的RT很快,但是容易出现数据不一致的情况。

半同步复制执行流程

image-20200824144623639

​ 半同步复制与异步复制的工作流程大体相似,但不同的是,当master中的binlog日志写入完成后,其不会马上通过引擎进行事务提交,而会处于等待,等到slave同步完成向master返回ACK通知后,才会唤醒等待,继续向下执行。

​ 等待的时长,默认为10秒,但该时间可以配置。

​ 半同步复制尽量的避免的主从数据不一致的情况,但是会造成吞吐量的降低。

​ 对于这个问题,mysql也进行了解决,假设使用半同步复制进行备份时,slave节点挂掉了,那么当master等待10秒后,仍然会进行引擎提交,同时会将半同步复制切换为异步复制。等到slave节点重启后,又会自动的从异步复制切换到半同步复制。

主从异步复制日志效果

​ Mysql在进行复制操作时,默认是基于异步复制完成的。那为了更好的体会异步复制的效果,可以通过mysql日志来查看具体的复制过程效果。

1)启动主从两台Mysql服务器。

2)查看master的Mysql日志信息

docker logs -f mysql3307

image-20200529224302948

​ 根据当前查看的日志信息,在master中已经开启了dump线程连接到了id为1453307的slave节点,并且该id就是在slave的mysql配置文件中设置的id。

​ 同时pos内容包括当前的binlog日志和pos位置。

3)查看slave的Mysql日志信息

docker logs -f mysql3307

image-20200529224651937

​ 根据slave中的日志信息,可以看到,当前slave中已经开启了relay-log日志,其对应文件信息就是xxxxx-relay-bin。其内部保存的就是slave中的相关binlog信息和pos位置信息。

​ 同时在slave中也已经开启了SQL Thread,并且根据信息可以,它会从xxxx-relay-bin.0006文件的367位置开始复制。

​ 同时在slave中也开启了IO Thread,其已经连接到master,并且会从master的binlog日志的154的位置开启复制。

4)查看master当前的binlog日志信息。

#确定当前master正在使用的binlog日志文件
cat mysql-bin.index

#查看当前binlog日志文件内容
tail -f mysql-bin.000001

image-20200529230743091

5)查看slave当前的日志信息

[root@bogon data]# cat relay-log.info 
7
./8122977f8b0a-relay-bin.000002
864
mysql-bin.000004
1251
0
0
1

[root@bogon data]# cat master.info
25
mysql-bin.000004
1251
192.168.200.142
repluser
123456
3307

[root@bogon data]# cat 8122977f8b0a-relay-bin.index
./8122977f8b0a-relay-bin.000001
./8122977f8b0a-relay-bin.000002

image-20200529231644827

6)监控slave日志信息

tail -f 8122977f8b0a-relay-bin.000002

image-20200529231855898

7)master中新增数据,触发主从同步

master中日志改变内容如下

image-20200529232120418

slave中日志改变内容如下

image-20200529232202700

主从半同步复制配置&效果演示

配置

1)进入mysql容器,加载lib,主从节点都要配置,因为主从节点间会存在切换。

install plugin rpl_semi_sync_master soname 'semisync_master.so';

install plugin rpl_semi_sync_slave soname 'semisync_slave.so';

2)查看插件信息

show plugins;

image-20200530005008363

3)启用半同步(务必先启用从库,再启用主库)

#先启用从库,再启用主库

从库:set global rpl_semi_sync_slave_enabled= {0|1}; # 1:启用,0:禁止

主库:
set global rpl_semi_sync_master_enabled= {0|1}; # 1:启用,0:禁止
set global rpl_semi_sync_master_timeout=10000; # 单位为ms

​ 当主库开启半同步复制后,打印日志信息如下:

image-20200530005339598

4)从库重启IO Thread

stop slave io_thread;
start slave io_thread;

5)截止到此已经完成半同步开启配置,可以查看主库状态信息和参数信息

#查询状态信息
show global status like "%sync%";

#查询参数信息
show global variables like '%sync%';

image-20200530005851845

image-20200530005802183

效果演示

​ 根据上述的配置,当前主从两台服务器的复制方式已经改为半同步复制。接下来就可以来查看具体的效果。

1)正常的向master中添加数据,slave可以进行正常数据更新。

master打印日志信息如下: 开启半同步复制,关闭异步复制

image-20200530010448157

2)关闭slave的IO Thread。

image-20200530010702895

​ 再次向master中添加数据。此时可以发现,当进行数据提交时,会出现等待,过了十秒后,会对数据进行保存。同时slave中不会同步的进行数据更新。

image-20200530010747005

​ 并且master中会打印日志信息,等待超时,关闭半同步复制。

image-20200530010928093

​ 此时复制机制就会由半同步复制转换为异步复制,当再次向master中添加数据,不会再次出现等待。

3)slave中重新开启IO Thread。

​ 首先:异步复制会再次转换为半同步复制,master中打印日志信息如下:

image-20200530011251399

​ 其次:在slave IO Tthread关闭这段时间内的数据,会同步到slave中,不会出现数据丢失。

主主复制

概念

​ 对于主从复制来说,其内部会存在一台master以及一台或多台slave。但有一个非常明显的问题,master是单点存在。一旦master宕机,则无法进行数据的写入。为了解决这个问题,可以使用主主复制架构。

​ 在主主复制架构中,会存在两台master,没有slave。并且会对这两台master进行读写分离,两台master会进行相互的复制。

image-20200530012403644

​ 在此架构中,两台master会进行双向复制,为什么这么做呢? 因为假设现在负责写的master宕机了,那么写的工作则会交给之前负责读的服务器来完成,相当于它即负责写又负责读。等到原先负责写的master恢复了,其在继续负责写工作。 反之亦然。因此才需要两者间进行双向复制。

​ 此时缺点也非常明显,虽然master不存在单点了,但是对于读来说,如果并发量大的话,它肯定扛不住。对于主主复制架构来说,应用较少。

搭建

​ 主主复制的搭建和主从非常类似,只不过主主复制会进行互指。

1)参照主从完成搭建。

2)原slave端也要开启权限

#添加权限
GRANT REPLICATION SLAVE,FILE,REPLICATION CLIENT ON *.* TO 'repluser'@'%' IDENTIFIED BY '123456';

#刷新权限
FLUSH PRIVILEGES;

#重启mysql服务并查看binlog信息
show master status

3)在master这一端也要配置slave的相关配置

change master to master_host='master的ip',master_port=master的端口号,master_user='repluser',master_password='123456',master_log_file='master中的binlob文件',master_log_pos=master中的position位置信息;

change master to master_host='192.168.200.181',master_port=3308,master_user='repluser',master_password='123456',master_log_file='mysql-bin.000002',master_log_pos=154;

start slave;

3)查看master和slave的进程列表:show processlist。可以发现他们现在互为主备。

master:

image-20200530020237138

slave:

image-20200530020332258

测试

​ 当在两台服务器中添加数据,都可以完成双向同步。

级联复制架构

​ 当读压力现在增大并且还想减小主从复制的性能消耗,可以采用级联复制架构。

image-20200601225751946

​ 写请求的入口仍为一个,但当master向slave进行复制时,对于slave可以分为多层, master只要向其中两台slave复制即可,然后再由slave将其数据复制到后面更多的slave中。

​ 通过这种方式可以减轻master向slave复制的IO压力。

​ 但是这种架构也存在一个弊端:slave的延迟会加大。

双主与级联复制结合架构

​ 对于master在前面几种架构设计中,都存在单点问题, 对于master单点问题的解决,可以采用当前的架构。通过这种架构不仅可以解决master单点的问题,也可以解决slave延迟的问题。

image-20200601230908495