4.1 集群架构设计

4.1.1 架构设计理念

在集群的架构设计时,需要遵循以下三个维度:

  • 可用性
  • 扩展性
  • 一致性

    4.1.2 可用性设计

  • 站点高可用,冗余站点

  • 服务高可用,冗余服务
  • 数据高可用,冗余数据

保证高可用的方法是冗余。但是数据冗余带来的问题是数据一致性问题。
实现高可用的方案有以下几种架构模式:

  • 主从模式:简单灵活,能满足多种需求。比较主流的用法,但是写操作高可用需要自行处理。
  • 双主模式:互为主从,有双主双写、双主单写两种方式,建议使用双主单写

    4.1.3 扩展性设计

    扩展性主要围绕着读操作扩展和写操作扩展展开。
  1. 如何扩展以提高读性能
  • 加从库
    • 简单易操作,方案成熟。
    • 从库过多会引发主库性能损耗。建议不要作为长期的扩充方案,应该设法用良好的设计避免持续加从库来缓解读性能问题。
  • 分库分表:可以分为垂直拆分和水平拆分,垂直拆分可以缓解部分压力,水平拆分理论上可以无限扩 展。
  1. 如何扩展以提高写性能
  • 分库分表

    4.1.4 一致性设计

    一致性主要考虑集群中各数据库数据同步以及同步延迟问题。可以采用的方案如下:

  • 不使用从库:扩展读性能问题需要单独考虑,否则容易出现系统瓶颈。

  • 增加访问路由层:可以先得到主从同步最长时间t,在数据发生修改后的t时间内,先访问主库。

    4.2主从模式

    4.2.1 适用场景

    MySQL主从模式是指数据可以从一个MySQL数据库服务器主节点复制到一个或多个从节点。MySQL 默
    认采用异步复制方式,这样从节点不用一直访问主服务器来更新自己的数据,从节点可以复制主数据库
    中的所有数据库,或者特定的数据库,或者特定的表。
  1. Mysql主从复制的用途
  • 实时灾备,用于故障切换(高可用)
  • 读写分离,提供查询服务(读扩展)
  • 数据备份,避免影响业务(高可用)
  1. 主从部署必要条件
  • 从库服务器能连通主库
  • 主库开启binlog日志(设置log-bin参数)
  • 主从server-id不同

    4.2.2 实现原理

    异步复制

    1655108163(1).jpg

主从复制整体分为以下三个步骤:

  1. 主库将数据库的变更操作记录到Binlog日志文件中
  2. 从库读取主库中的Binlog日志文件信息写入到从库的Relay Log中继日志中
  3. 从库读取中继日志信息在从库中进行Replay,更新从库数据信息

在上述三个过程中,涉及了Master的BinlogDump Thread和Slave的I/O ThreadSQL Thread,它们
的作用如下:

  • Master服务器对数据库更改操作记录在Binlog中,BinlogDump Thread接到写入请求后,读取 Binlog信息推送给Slave的I/O Thread。
  • Slave的I/O Thread将读取到的Binlog信息写入到本地Relay Log中。
  • Slave的SQL Thread检测到Relay Log的变更请求,解析relay log中内容在从库上执行。

上述过程都是异步操作,俗称异步复制,存在数据延迟现象。

下图是异步复制的时序图。
1655108535(1).jpg
mysql主从复制存在的问题:

  • 主库宕机后,数据可能丢失
  • 从库只有一个SQL Thread,主库写压力大,复制很可能延时

解决方法:

  • 半同步复制—-解决数据丢失的问题
  • 并行复制——解决从库复制延迟的问题

    半同步复制

    为了提升数据安全,MySQL让Master在某一个时间点等待Slave节点的 ACK(_Ack_nowledge
    character)消息,接收到ACK消息后才进行事务提交,这也是半同步复制的基础,MySQL从5.5版本开
    始引入了半同步复制机制来降低数据丢失的概率。

主库事务写入分为 4 个步骤:

  1. InnoDB Redo File Write (Prepare Write)
  2. Binlog File Flush & Sync to Binlog File
  3. InnoDB Redo File Commit(Commit Write)
  4. Send Binlog to Slave

解析:

  • 当Master不需要关注Slave是否接受到Binlog Event时,即为传统的主从复制。
  • 当Master需要在第三步等待Slave返回ACK时,即为 after-commit,半同步复制(MySQL 5.5引入)。
  • 当Master需要在第二步等待 Slave 返回 ACK 时,即为 after-sync,增强半同步(MySQL 5.7引入)。

下图是 MySQL 官方对于半同步复制的时序图,主库等待从库写入 relay log 并返回 ACK 后才进行
Engine Commit。
半同步复制.jpg

并行复制

MySQL的主从复制延迟一直是受开发者最为关注的问题之一,MySQL从5.6版本开始追加了并行复制功能,目的就是为了改善复制延迟问题,并行复制称为enhanced multi-threaded slave(简MTS)。
在从库中有两个线程IO Thread和SQL Thread,都是单线程模式工作,因此有了延迟问题,我们可以采 用多线程机制来加强,减少从库复制延迟。(IO Thread多线程意义不大,主要指的是SQL Thread多线 程)
在MySQL的5.6、5.7、8.0版本上,都是基于上述SQL Thread多线程思想,不断优化,减少复制延迟。

Mysql5.6

MySQL 5.6版本也支持所谓的并行复制,但是其并行只是基于库的。如果用户的MySQL数据库中是多个
库,对于从库复制的速度的确可以有比较大的帮助。
并行复制.jpg
基于库的并行复制,实现相对简单,使用也相对简单些。基于库的并行复制遇到单库多表使用场景就发
挥不出优势了,另外对事务并行处理的执行顺序也是个大问题。

Mysql5.7

MySQL 5.7是基于组提交的并行复制,MySQL 5.7才可称为真正的并行复制,这其中最为主要的原因就
是slave服务器的回放与master服务器是一致的,即master服务器上是怎么并行执行的slave上就怎样进
行并行回放。不再有库的并行复制限制。
MySQL 5.7中组提交的并行复制究竟是如何实现的?
MySQL 5.7是通过对事务进行分组,当事务提交时,它们将在单个操作中写入到二进制日志中。如果多
个事务能同时提交成功,那么它们意味着没有冲突,因此可以在Slave上并行执行,所以通过在主库上
的二进制日志中添加组提交信息。

MySQL 5.7的并行复制基于一个前提,即所有已经处于prepare阶段的事务,都是可以并行提交的。这
些当然也可以在从库中并行提交,因为处理这个阶段的事务都是没有冲突的。在一个组里提交的事务,
一定不会修改同一行。这是一种新的并行复制思路,完全摆脱了原来一直致力于为了防止冲突而做的分
发算法,等待策略等复杂的而又效率底下的工作。

InnoDB事务提交采用的是两阶段提交模式。一个阶段是prepare,另一个是commit。

为了兼容MySQL 5.6基于库的并行复制,5.7引入了新的变量slave-parallel-type,其可以配置的值有:
DATABASE(默认值,基于库的并行复制方式)、LOGICAL_CLOCK(基于组提交的并行复制方式)。

那么如何知道事务是否在同一组中,生成的Binlog内容如何告诉Slave哪些事务是可以并行复制的?
在MySQL 5.7版本中,其设计方式是将组提交的信息存放在GTID中。为了避免用户没有开启GTID功能
(gtid_mode=OFF),MySQL 5.7又引入了称之为Anonymous_Gtid的二进制日志event类型
ANONYMOUS_GTID_LOG_EVENT。
通过mysqlbinlog工具分析binlog日志,就可以发现组提交的内部信息。
5.7日志.jpg
可以发现MySQL 5.7二进制日志较之原来的二进制日志内容多了last_committed和
sequence_number,last_committed表示事务提交的时候,上次事务提交的编号,如果事务具有相同
的last_committed,表示这些事务都在一组内,可以进行并行的回放。

Mysql8.0

MySQL8.0 是基于write-set的并行复制。MySQL会有一个集合变量来存储事务修改的记录信息(主键哈
希值),所有已经提交的事务所修改的主键值经过hash后都会与那个变量的集合进行对比,来判断改行
是否与其冲突,并以此来确定依赖关系,没有冲突即可并行。这样的粒度,就到了 row级别了,此时并
行的粒度更加精细,并行的速度会更快。

并行复制配置与调优

  • binlog_transaction_dependency_history_size:用于控制集合变量的大小。
  • binlog_transaction_depandency_tracking:用于控制binlog文件中事务之间的依赖关系,即last_committed值。
    • COMMIT_ORDERE: 基于组提交机制
    • WRITESET: 基于写集合机制
    • WRITESET_SESSION: 基于写集合,比writeset多了一个约束,同一个session中的事务 last_committed按先后顺序递增
  • transaction_write_set_extraction:用于控制事务的检测算法,参数值为:OFF、 XXHASH64、MURMUR32
  • master_info_repository:开启MTS功能后,务必将参数master_info_repostitory设置为TABLE,这样性能可以有50%~80%的提升。这是因为并行复制开启后对于元master.info这个文件的更新将会大幅提升,资源的竞争也会变大。
  • slave_parallel_workers:若将slave_parallel_workers设置为0,则MySQL 5.7退化为原单线程复制,但将slave_parallel_workers设置为1,则SQL线程功能转化为coordinator线程,但是只有1个worker 线程进行回放,也是单线程复制。然而,这两种性能却又有一些的区别,因为多了一次coordinator线程的转发,因此slave_parallel_workers=1的性能反而比0还要差。
  • slave_preserve_commit_order:MySQL 5.7后的MTS可以实现更小粒度的并行复制,但需要将slave_parallel_type设置为LOGICAL_CLOCK,但仅仅设置为LOGICAL_CLOCK也会存在问题,因为此时在slave上应用事务的顺序是无序的,和relay log中记录的事务顺序不一样,这样数据一致性是无法保证的,为了保证事务是按照relay log中记录的顺序来回放,就需要开启参数slave_preserve_commit_order。
  • 要开启enhanced multi-threaded slave其实很简单,只需根据如下设置:
    1. slave-parallel-type=LOGICAL_CLOCK
    2. slave-parallel-workers=16
    3. slave_pending_jobs_size_max = 2147483648
    4. slave_preserve_commit_order=1
    5. master_info_repository=TABLE
    6. relay_log_info_repository=TABLE
    7. relay_log_recovery=ON

并行复制监控

  1. 在使用了MTS后,复制的监控依旧可以通过SHOW SLAVE STATUS\G,但是MySQL 5.7在 performance_schema库中提供了很多元数据表,可以更详细的监控并行复制过程。

    1. mysql> show tables like 'replication%';
    2. +---------------------------------------------+
    3. | Tables_in_performance_schema (replication%) |
    4. +---------------------------------------------+
    5. | replication_applier_configuration |
    6. | replication_applier_status |
    7. | replication_applier_status_by_coordinator |
    8. | replication_applier_status_by_worker |
    9. | replication_connection_configuration |
    10. | replication_connection_status |
    11. | replication_group_member_stats |
    12. | replication_group_members |
    13. +---------------------------------------------+
  2. 通过replication_applier_status_by_worker可以看到worker进程的工作情况:

  • mysql> select * from replication_applier_status_by_worker;
  1. 如果MySQL 5.7要使用MTS(并行复制)功能,建议使用新版本,最少升级到5.7.19版本,修复了很多Bug。

    4.2.3 操作案例

    环境搭建

  2. 在两台服务器分别安装Mysql,例如下载Mysql的rpm文件的压缩包

mysql-5.7.28-1.el7.x86_64.rpm-bundle.tar

  1. 使用命令 tar -xvf mysql-5.7.28-1.el7.x86_64.rpm-bundle.tar解压
  2. 解压后会生成多个rpm文件,这些文件有依赖关系所以得按一定顺序安装,安装之前先卸载centos7自带的mariadb数据库。
  3. 使用命令 rpm -qa|grep mariadb 查看mariadb数据库是否存在。如果存在,则使用命令 rpm -e mariadb-libs-5.5.41-2.el7_0.x86_64 —nodeps 删除,删除后在运行查看命令查看是否已经删除。
  4. 安装mysql的rpm文件,顺序如下
    1. rpm -ivh mysql-community-common-5.7.28-1.el7.x86_64.rpm
    2. rpm -ivh mysql-community-libs-5.7.28-1.el7.x86_64.rpm
    3. rpm -ivh mysql-community-libs-compat-5.7.28-1.el7.x86_64.rpm
    4. rpm -ivh mysql-community-client-5.7.28-1.el7.x86_64.rpm
    5. rpm -ivh mysql-community-server-5.7.28-1.el7.x86_64.rpm
    6. rpm -ivh mysql-community-devel-5.7.28-1.el7.x86_64.rpm(可装可不装)
  5. 初始化mysql数据库:mysqld —initialize —user=mysql
  6. 会随机生成一个密码,使用命令 cat /var/log/mysqld.log 查看默认密码。
  7. 设置服务器启动时mysql自启动:systemctl start mysqld.service
  8. 登录进数据库:mysql -uroot -p
  9. 修改密码:set password=password(‘’root);
  10. 退出再使用新密码登录。

    配置主从

    主库

  11. 编辑my.cnf文件 vi /etc/my.cnf。

  12. 在[mysqld]行下面编辑

    1. [mysqld]
    2. #开启binlog
    3. log_bin=mysql-bin
    4. #主从server-id不能一样
    5. server-id=1
    6. #每次写入操作都与磁盘同步
    7. sync-binglog=1
    8. #忽略哪些库不同步(其它库默认同步)
    9. binlog-ignore-db=performance_schema
    10. binlog-ignore-db=information_schema
  13. 保存配置修改后重启mysql服务:systemctl restart mysqld;

  14. 登录进数据库
  15. 开启授权,授权哪些从库可以访问主库
  • grant replication slave on . to ‘账号’@’%’ identified by ‘密码’;
  • grant all privileges slave on . to ‘账号’@’%’ identified by ‘密码’;
  • flush privileges;
  1. 查看主库状态:show master status;

从库

  1. 编辑配置文件:vi /etc/my.cnf

    [mysqld]
    server-id=2
    relay_log=mysql-relay-log
    #只读数据库
    read_only=1
    
  2. 保存配置修改后重启mysql服务:systemctl restart mysqld;

  3. 登录进数据库
  4. 执行命令查看从库状态:show slave status;
  5. 指定主库:change master to master_host=’192.168.1.120’,master_port=3306,master_user=’root’,master_password=’root’,master_log_file=’mysql-bin.000002’,master_log_pos=869;
  6. 开启从库:start slave;
  7. 测试在主库中进行操作,从库有没有同步。

第二个从库
由于使用第二个从库时,主库的信息已经有很多了,此时如果通过主从复制来把数据复制到从库对于从库来说会是不小的负担,且速度较慢。所以我们使用mysqldump命令先把主库的所有数据导出成sql文件,再在从库中执行,这样大大减少数据复制时间。

  1. 主库导出命令:mysqldump —all-databases > mysql_backup_all.sql
  2. 把生成的mysql_backup_all.sql文件传到从库使用source命令进行执行。

    半同步复制

    半同步复制需要一个插件来支撑,需要自行安装
    首先配置主库

  3. 登录mysql,使用 select @@have_dynamic_loading; 查看是否拥有插件热安装的支持。

  4. 使用 show plugins; 查看插件
  5. 安装插件:install plugin rpl_semi_sync_master soname ‘semisync_master.so’;
  6. 查看该插件参数:show variables like ‘%semi%’;
  7. 开启semi插件:set global rpl_semi_sync_master_enabled=1;
  8. 修改超时时间:set global rpl_semi_sync_master_timeout=1;

从库安装:

  1. 安装插件:install plugin rpl_semi_sync_slave soname ‘semisync_slave.so’;
  2. 查看该插件参数:show variables like ‘%semi%’;
  3. 开启semi插件:set global rpl_semi_sync_slave_enabled=1;
  4. 重启slave。stop slave; start slave;

配置完成,现在在主库写入,复制到从库的方式就是半同步复制了。
可以在主库中查看日志:cat /var/log/mysqld.log,可以看到Semi-sync的日志就是半同步复制了。

并行复制

先登录主库:

  1. 查看并行复制组提交参数:show variables like ‘%binlog_group%’;
  2. 修改组提交参数:
    1. set global binglog_group_commit_sync_delay=1000;
    2. set global binglog_group_commit_sync_no_delay_count=100;

登录从库:

  1. 查看slave参数:show variables like ‘%slave%’;
  2. 先停止slave:stop slave;
  3. 设置并行复制相关参数:
    1. set global slave_parallel_type=’LOGICAL_CLOCK’;
    2. set global slave_parallel_worker=8;
  4. 查看relay参数:show variables like ‘%relay_log%’;
  5. 修改参数:set global master_info_responsitory=’TABLE’;
  6. 修改配置文件:vi /etc/my.cnf 追加:relay_log_recovery=1
  7. 重启MySQL:systemctl restart mysqld
  8. set global relay_log_info_responsitory=’TABLE’;
  9. 使用set global设置参数可能重启服务器会被重置,所以最好都写入配置文件。
  10. 配置完重启服务:systemctl restart mysqld

    4.2.4 读写分离

    读写分离引入的时机

    大多数互联网业务中,往往读多写少,这时候数据库的读会首先成为数据库的瓶颈。如果我们已经优化
    了SQL,但是读依旧还是瓶颈时,这时就可以选择“读写分离”架构了。
    读写分离首先需要将数据库分为主从库,一个主库用于写数据,多个从库完成读数据的操作,主从库之
    间通过主从复制机制进行数据的同步,如图所示。
    读写分离1.jpg
    在应用中可以在从库追加多个索引来优化查询,主库这些索引可以不加,用于提升写效率。
    读写分离架构也能够消除读写锁冲突从而提升数据库的读写性能。使用读写分离架构需要注意:主从同
    步延迟和读写分配机制问题

    主从同步延迟

    使用读写分离架构时,数据库主从同步具有延迟性,数据一致性会有影响,对于一些实时性要求比较高
    的操作,可以采用以下解决方案。

  11. 写后立刻读:在写入数据库后,某个时间段内读操作就去主库,之后读操作访问从库。

  12. 二次查询:先去从库读取数据,找不到时就去主库进行数据读取。该操作容易将读压力返还给主库,为了避免恶意攻击,建议对数据库访问API操作进行封装,有利于安全和低耦合。
  13. 根据业务特殊处理:根据业务特点和重要程度进行调整,比如重要的,实时性要求高的业务数据读写可以放在主库。对于次要的业务,实时性要求不高可以进行读写分离,查询时去从库查询。

    读写分离落地

    读写路由分配机制是实现读写分离架构最关键的一个环节,就是控制何时去主库写,何时去从库读。目
    前较为常见的实现方案分为以下两种:

  14. 基于编程和配置实现(应用端):程序员在代码中封装数据库的操作,代码中可以根据操作类型进行路由分配,增删改时操作主库,查询时操作从库。这类方法也是目前生产环境下应用最广泛的。优点是实现简单,因为程序在代码中实现,不需要增加额外的硬件开支,缺点是需要开发人员来实现,运维人员无从下手,如果其中一个数据库宕机了,就需要修改配置重启项目。

  15. 基于服务器端代理实现(服务器端):中间件代理一般介于应用服务器和数据库服务器之间,从图中可以看到,应用服务器并不直接进入到master数据库或者slave数据库,而是进入MySQL proxy代理服务器。代理服务器接收到应用服务器的请求后,先进行判断然后转发到后端master和slave数据库。

读写分离2.jpg
目前有很多性能不错的数据库中间件,常用的有MySQL Proxy、MyCat以及Shardingsphere等等。

  • MySQL Proxy:是官方提供的MySQL中间件产品可以实现负载平衡、读写分离等。
  • MyCat:MyCat是一款基于阿里开源产品Cobar而研发的,基于 Java 语言编写的开源数据库中间件。
  • ShardingSphere:ShardingSphere是一套开源的分布式数据库中间件解决方案,它由ShardingJDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成。已经在2020年4月16日从Apache孵化器毕业,成为Apache顶级项目。
  • Atlas:Atlas是由 Qihoo 360公司Web平台部基础架构团队开发维护的一个数据库中间件。
  • Amoeba:变形虫,该开源框架于2008年开始发布一款 Amoeba for MySQL软件。

    读写分离实战

  1. 在主从模式搭好的情况下,下载数据库中间件:mysql-proxy-0.8.5-linux-el6-x86-64bit.tar.gz
  2. 使用tar -zxvf mysql-proxy-0.8.5-linux-el6-x86-64bit.tar.gz 进行解压。
  3. 创建配置文件,vim /etc/mysql_proxy.cnf

    user=root
    #主从mysql的账户密码
    admin-username=root
    admin-password=root
    #代理地址 mysql-proxy运行的地址
    proxy-address=192.168.95.134:4040
    #设置主库(写)
    proxy-backend-addresses=192.168.95.130:3306
    #从库(读)
    proxy-read-only-backend-addresses=192.168.95.132:3306
    #分发脚本(使用自带解压的)
    proxy-lua-script=...路径/rw.splitting.lua
    log-file=/var/log/mysql-proxy.log
    log-level=debug
    daemon=true
    keepalive=true
    
  4. 修改权限 chmod 660 /etc/mysql_proxy.cnf

  5. 修改…路径/rw.splitting.lua配置文件,min_idel_connections = 1
  6. 启动proxy。cd到proxy的bin目录下,使用./mysql-proxy —default-file=/etc/mysql_proxy.cnf

4.3 双主模式

4.3.1 适用场景

主从模式:一主多从、读写分离等,如果发生单点故障,从库切换成主库需要进行改动,需要耗费时间。
双主模式:双主模式是指两台服务器互为主从,任何一台服务器发生变更,都会通过复制应用到另一方服务器中,增加了Mysql的入口,提升了主库的可用性。
因此随着业务的发展,数据库的架构都可从主从模式切换成双主模式。

使用双主单写还是双主双写?建议双主单写,双主双写存在以下问题:

  • ID冲突:在A库写入时,还未同步到B库,此时也对B库进行写入,如果采用主键自动递增模式会造成ID主键冲突。
  • 更新丢失:同一条数据在两个主库同时进行更新,会发生前面覆盖后面的更新丢失。

高可用架构入如下图所示
双主模式2.jpg

其中一个Master提供线上服务,另一个Master作为备胎供高可用切换, Master下游挂载Slave承担读请求。
随着业务发展,架构会从主从模式演变为双主模式,建议用双主单写,再引入高可用组件,例如
Keepalived和MMM等工具,实现主库故障自动切换。

4.3.2 配置实现

  1. 在配置好主从的基础上,再加一个主库,比如再第三台服务器上安装mysql服务。
  2. 在第一个主库进行配置(4.2.3的基础上),打开配置文件 vi /etc/my.cnf ```properties [mysqld]

    开启binlog

    log_bin=mysql-bin

    主从server-id不能一样

    server-id=1

    每次写入操作都与磁盘同步

    sync-binglog=1

    忽略哪些库不同步(其它库默认同步)

    binlog-ignore-db=performance_schema binlog-ignore-db=information_schema

relay_log=mysql-relay-bin log_slave_updates=1

双写的时候防止id冲突设置的id自动增长步长,比如1,3,5,7

auto_increment_offset=1 auto_increment_increment=2


3. 重启mysql,systemctl restart mysqld
3. 配置第二个主库,vi /ect/my.cnf
```properties
[mysqld]
#开启binlog
log_bin=mysql-bin
server-id=3
sync-binglog=1
binlog-ignore-db=performance_schema
binlog-ignore-db=information_schema
relay_log=relay-bin
log_slave_updates=1
#2,4,6,8
auto_increment_offset=2
auto_increment_increment=2
  1. 重启mysql,systemctl restart mysqld
  2. 登录进第二个主库,进行授权
  • grant replication slave on . to ‘账号’@’%’ identified by ‘密码’;
  • grant all privileges slave on . to ‘账号’@’%’ identified by ‘密码’;
  • flush privileges;
  1. show master status; 进行查看主库状态
  2. 在两台主库上执行互相复制对方数据的命令,比如A库复制B库,B库复制A库
    1. change master to master_host=’192.168.1.120’,master_port=3306,master_user=’root’,master_password=’root’,master_log_file=’mysql-bin.000002’,master_log_pos=869;
    2. master_log_file和pos参数可以使用show master status命令在需要复制的主库上查出来。分别是file和position列的值。
  3. 然后在两个主库分别启动slave,start slave;
  4. 可以使用show slave status \G; 命令查看从库状态