1.mysql是怎么保证数据不丢的?
结论是:只要redo log和binlog保证持久化 到磁盘 就能保证mysql异常重启后,数据可恢复
1.1 binlog的写入机制
binlog写入逻辑: 事务执行过程中,先把日志写到 binlog cache事务提交时,再把binlog cache 写到binlog文件中,binlog cache的大小由 binlog_cache_size参数控制,binlog cache是由每个线程独享的,如里一个线程中的binlog cache被写满,就需要暂存到磁盘中,如下图
图中的write 指的就是把日志文件写入到文件系统的 page cache,fsync 是将数据持久化到磁盘的操作
参数sync_binlog控制 write和fsync的写入时机
- sync_binlog=0时,表示每次提交事务都只写write ,不fsync;
- sync_binlog=1时,表示每次提交事务都会执行fsync;
- sync_binlog=N(N>1)时,表示每次提交事务都write,但是累积N个事务后才fsync
在实际的业务场景中,考虑到丢失日志量的可控性,一般不建议将这个参数设成0,比较常见的是将其设置成100~1000中的某个数值,如果设置为N,如果主机发生异常重启,会丢失最近N个事务的binlog日志
1.2 redo log 写入机制
在innodb中,参数innodb_flush_log_at_trx_commit用来控制 redo log的写入策略
- 设置为0时,表示每次事务提交时,都只是把redo log 留在 redo log buffer中;
- 设置为1时,表示每次事务提交时都将redo log 直接持久化到磁盘
- 设置为2时,表示每次事务提交时都只是把redo log写到page cache中
innodb有一个后台线程,每隔一秒,就会把 redo log buffer中的日志,调用write写的文件系统中的page cache,然后调用 fsync持久化到磁盘,在这过程中,一个没有提交的事务也有可能会被持久化到磁盘中
1.2.1 redo log 主动写盘时机
- 一种是 redo log buffer占用的空间即将达到innodb_log_buffer_size 一半的时候,后台线程会主动写盘,这里的写盘是write 不会调用fsync
- 一种是并行的事务提交时,顺带将这个事务的redo log buffer持久化到磁盘
1.2.2 组提交
为了优化io性能,在刷盘时会用到组提交机制,一次组提交里面,组员越多,节约磁盘iops的效果越好
如果想提升binlog组提交的效果,可以通过设置 binlog_group_commit_sync_delay 和binlog_group_commit_sync_no_delay_count参数控制
- binlog_group_commit_sync_delay 参数,表示延迟多少微秒后才调用 fsync;
- binlog_group_commit_sync_no_delay_count 参数,表示累积多少次以后才调用 fsync。
这两个条件是或的关系,只要有一个满足条件就会调用fsync
2. mysql是怎么保证主备一致的
2.1 主备基本原理
说明:
备库 B 跟主库 A 之间维持了一个长连接。主库 A 内部有一个线程,专门用于服务备库 B 的这个长连接。一个事务日志同步的完整过程是这样的:
在备库 B 上通过 change master 命令,设置主库 A 的 IP、端口、用户名、密码,以及 要从哪个位置开始请求 binlog,这个位置包含文件名和日志偏移量。
在备库 B 上执行 start slave 命令,这时候备库会启动两个线程,就是图中的 io_thread 和 sql_thread。其中 io_thread 负责与主库建立连接。
主库 A 校验完用户名、密码后,开始按照备库 B 传过来的位置,从本地读取 binlog, 发给 B。
备库 B 拿到 binlog 后,写到本地文件,称为中转日志(relay log)。
sql_thread 读取中转日志,解析出日志里的命令,并执行。
2.2 binlog的三种格式
- statement,此种模式下,binlog记录的是sql原文,如果sql中包括函数,索引的时候,在从库执行时,可能会出现主备库不一致的情况
- row,此种模式下,binlog记录的是真实影响到的行记录,在从库执行时,不会出现不一致情况,但是会导致binlog日志文件过大
- mixed,此种模式下 ,属于一个折中方案,在些格式下,mysql会自已判断这条sql 是否可能引起主备不一致情况,如果可能就用row模式,否则就用statement格式
2.3 利用binlog进行数据恢复
使用 mysqlbinlog 解析 binlog文件 ,找到要恢复的数据,进行数据恢复
常用的方法
show master status 查看使用的 binlog文件和 pos_position位置
使用 mysqlbinlog --start-position=binlog_position /var/lib/mysql/mysql-bin.000001 > /path/to/sql | mysql -u your_username -p your_database_name 进行数据恢复
2.4 双主结构下的循环复制问题
业务逻辑在节点 A 上更新了一条语句,然后再把生成的 binlog 发给节点 B,节点 B 执行 完这条更新语句后也会生成 binlog。(我建议你把参数 log_slave_updates 设置为 on, 表示备库执行 relay log 后生成 binlog)。
3 mysql是怎么保证高可用的
在保证高可用的前提下,可能会有主备延迟的产生,有如下几种情况
- 在一些部署条件下,可能备服务器上的机器性能要差些,如里是这种情况,可以加装多台从服务器来缓解压力
- 大事务执行,也会带来主备延迟,解决办法,尽量减少大事务的发生,比较分批删除大量数据,或者在业务低峰期执行大事务
- 对大表执行ddl操作,也会导致主备延迟的产生,解决办法:尽量避免
- 备库的并行复制能力低也有可能导致主备延迟
3.1主备切换对应的策略
- 可靠性优先
- 判断备库 B 现在的 seconds_behind_master,如果小于某个值(比如 5 秒)继续下一 步,否则持续重试这一步;
- 把主库 A 改成只读状态,即把 readonly 设置为 true;
- 判断备库 B 的 seconds_behind_master 的值,直到这个值变成 0 为止;
- 把备库 B 改成可读写状态,也就是把 readonly 设置为 false;
- 把业务请求切到备库 B。
- 可以看到,这个切换流程中是有不可用时间的。因为在步骤 2 之后,主库 A 和备库 B 都处 于 readonly 状态,也就是说这时系统处于不可写状态,直到步骤 5 完成后才能恢复。 在这个不可用状态中,比较耗费时间的是步骤 3,可能需要耗费好几秒的时间。这也是为什 么需要在步骤 1 先做判断,确保 seconds_behind_master 的值足够小。 试想如果一开始主备延迟就长达 30 分钟,而不先做判断直接切换的话,系统的不可用时间 就会长达 30 分钟,这种情况一般业务都是不可接受的。 当然,系统的不可用时间,是由这个数据可靠性优先的策略决定的。你也可以选择可用性优 先的策略,来把这个不可用时间几乎降为 0。
- 可用性优化
- 步骤 2 中,主库 A 执行完 insert 语句,插入了一行数据(4,4),之后开始进行主备切 换。
- 步骤 3 中,由于主备之间有 5 秒的延迟,所以备库 B 还没来得及应用“插入 c=4”这个 中转日志,就开始接收客户端“插入 c=5”的命令。
- 步骤 4 中,备库 B 插入了一行数据(4,5),并且把这个 binlog 发给主库 A。
- 步骤 5 中,备库 B 执行“插入 c=4”这个中转日志,插入了一行数据(5,4)。而直接 在备库 B 执行的“插入 c=5”这个语句,传到主库 A,就插入了一行新数据(5,5)
最后的结果就是,主库 A 和备库 B 上出现了两行不一致的数据。可以看到,这个数据不一 致,是由可用性优先流程导致的。
那么,如果我还是用可用性优先策略,但设置 binlog_format=row,情况又会怎样呢? 因为 row 格式在记录 binlog 的时候,会记录新插入的行的所有字段值,所以最后只会有 一行不一致。而且,两边的主备同步的应用线程会报错 duplicate key error 并停止。也就 是说,这种情况下,备库 B 的 (5,4) 和主库 A 的 (5,5) 这两行数据,都不会被对方执行。
在实际应用中,建议还是以可靠性策略优先为主,毕竟数据完整性更重要
4. 主备切换的方法
当主库出现问题时,把从库切换为主库的方式有如下几种
4.1基于位点的主备切换
语句如下:
CHANGE MASTER TO
MASTER_HOST=$host_name
MASTER_PORT=$port
MASTER_USER=$user_name
MASTER_PASSWORD=$password
MASTER_LOG_FILE=$master_log_name
MASTER_LOG_POS=$master_log_pos
这条命令有这么 6 个参数: MASTER_HOST、MASTER_PORT、MASTER_USER 和 MASTER_PASSWORD 四个参 数,分别代表了主库 A’的 IP、端口、用户名和密码。 最后两个参数 MASTER_LOG_FILE 和 MASTER_LOG_POS 表示,要从主库的 master_log_name 文件的 master_log_pos 这个位置的日志继续同步。而这个位置就是 我们所说的同步位点,也就是主库对应的文件名和日志偏移量。可以通过 show master status来查找
可能遇到的问题: 会遇到主键冲突的问题
解决办法:
主动跳过一个事务。跳过命令的写法是: set global sql_slave_skip_counter=1; start slave;
通过设置 slave_skip_errors 参数,直接设置跳过指定的错误。在执行主备切换时,有这么两类错误,是经常会遇到的: 1062 错误是插入数据时唯一键冲突; 1032 错误是删除数据时找不到行。
因此,我们可以把 slave_skip_errors 设置为 “1032,1062”,这样中间碰到这两个错误时 就直接跳过。
4.2 基于 GTID进行主备切换 (全局事务id)
在 GTID 模式下,备库 B 要设置为新主库 A’的从库的语法如下:
CHANGE MASTER TO
MASTER_HOST=$host_name
MASTER_PORT=$port
MASTER_USER=$user_name
MASTER_PASSWORD=$password
master_auto_position=1
其中,master_auto_position=1 就表示这个主备关系使用的是 GTID 协议。可以看到,前 面让我们头疼不已的 MASTER_LOG_FILE 和 MASTER_LOG_POS 参数,已经不需要指定 了。
取binlog 的逻辑是
- 实例 B 指定主库 A’,基于主备协议建立连接。
- 实例 B 把 set_b 发给主库 A’。
- 实例 A’算出 set_a 与 set_b 的差集,也就是所有存在于 set_a,但是不存在于 set_b 的 GTID 的集合,判断 A’本地是否包含了这个差集需要的所有 binlog 事务。 a. 如果不包含,表示 A’已经把实例 B 需要的 binlog 给删掉了,直接返回错误; b. 如果确认全部包含,A’从自己的 binlog 文件里面,找出第一个不在 set_b 的事务, 发给 B;
- 之后就从这个事务开始,往后读文件,按顺序取 binlog 发给 B 去执行。