24 | MySQL是怎么保证主备一致的? 建议把从库设置成只读模式 有时候一些运营类的查询语句会被放到备库上去查,设置为只读可以防止误操作; 防止切换逻辑有 bug,比如切换过程中出现双写,造成主备不一致; 可以用 readonly 状态,来判断节点的角色。 从库设置为只读会不会影响主从同步 主从同步的线程拥有超级权限,readonly 不起作用
事务日志的同步过程 备库上执行 change master 命令,设置主库的 ip、端口、用户名、密码、binlog文件名、binlog偏移量 备库上执行 start slave 命令开启两个线程 io_thread、sql_thread 主库校验登录信息,并按照备库的请求读取binlog,发送给备库 备库拿到binlog,写入本地文件(relay log),io_thread 线程负责 sql_thread 读取中转日志,解析出日志里的命令,并执行 binlog 格式 对于一条删除语句,三种日志格式的记录是不同的 delete from t where a>=4 and t_modified<='2018-11-10' limit 1; binlog 格式之 statement 记录原 sql 语句。 切日志格式为 statement,执行这条带 limit 的删除语句时会产生 warning,原因是如果主库和从库执行这条语句时选用的索引不同,执行的结果不同
binlog 格式之 row 会记录删除行的具体信息,以及删除行的主键id,不会导致主备结果不一致。
binlog 格式之 mixed statement 格式有可能导致主备不一致,row 格式很占内存,mixed 格式是一个折中,不会引起主备差异使用 statement 格式,会引起主备不一致使用 row 格式
双 M 结构中的循环复制问题 双 M 结构在切换的时候不用修改主备关系,但是会导致 M1 生成的binlog传到 M2执行过后,M2 又传递给 M1,导致循环依赖问题
25 | MySQL是怎么保证高可用的 主备延迟 主库执行完,写入 binlog,这个时刻记为 T1 binlog 传递给备库 B,B接收完的时刻记为 T2 备库B重放 binlog,执行事务结束的时刻记为 T3 主备延迟就是 T3-T1,具体时间可以通过在备库上执行show slave status查看seconds_behind_master得到
主备延迟的来源 备库机器性能差 备库压力大,查询请求耗费了大量cpu资源 大事务 备库的并行复制能力差 主备切换的策略 可靠性优先 过程
1,判断备库 B 延迟的时间是否小于某个值(比如5s),否则持续重试这一步直到满足 2,把主库 A 改成只读状态 3,判断备库的延迟时间,直到变为 0 为止 4,把备库 B 改成可读写状态 5,把业务请求切到备库 B 总结:会存在短暂的系统不可用,具体取决于步骤1和步骤3
可用性优先 4,把备库 B 改成可读写状态 5,把业务请求切到备库 B
26 | 备库为什么会延迟好几个小时? 通过多线程复制来减少主备延迟
io_thread 线程负责接收 binlog 并存入 relay log sql_thread 线程负责读取 relay log 并执行,我们可以把这里改成多个线程并行的,具体就是一个 coordinator 负责读取 relay log 并解析,然后分发给不同的 worker 执行 多线程复制的原则 不能造成更新覆盖;这要求更新同一行的两个事务必须分发到一个worker进程中 同一个事务不能拆开,必须放到同一个worker中 Mysql 5.5 版本下的并行方案(本身不支持并行,通过自己开发策略) 按表分发策略 原则 如果两个事务更新不同的表,那么它们肯定可以并行。
思路 每个 worker 对应一个 hash 表,hash 表的 key 是当前worker队列里的事务所涉及的表,value 存储具体有多少个事务和这个表相关。
分配规则 以事务T的分配为例
1, 事务 T 涉及到表 t1,worker1 队列中有事务在修改 t1,T 和 worker1 冲突 2,按照 1 的逻辑,判断 T 和每个 worker 的冲突关系 3,T 同时和多个 worker 冲突,coordinator 进入等待 4,每个 worker 持续进行,每个事务完成时都会修改 worker 对应的 hash 表,和 t1 相关的事务都执行结束时,t1 从 hash 表中删除,worker 和 T 便不再冲突 5,coordinator 发现和 T 冲突的只有 worker1 了,就把 T 分配给 worder1 coordinator 继续处理一下个日志,继续上述的分配流程 事务和worker的关系
27 | 主库出问题了,从库怎么办? 一主多从情况下的主备切换问题 基于位点的主备切换 个人理解: 主备同步时是基于 log file 和 position 位点信息的,为了不丢数据,可以选择一个稍微靠前的位点,然后在备库上跳过已经执行过的事务。 那么主备切换时,我们可以等新的主库 A’ 同步完 relay log,将其设置成可读写状态,获取最新的 log file 和原主库 A 断电时刻对应的 position 位点信息,然后在其它从库上设置新的主库为 A’,从新的 log file + position 处开始同步
注:即使 A 和 A’ 的日志相同,在同一时刻,两者的位点信息也是不同的
缺陷 首先 position 信息不是绝对精确的,这可能会导致一些问题:插入数据的事务被重复执行报主键冲突错误、删除数据时找不到对应的行; 针对这些情况,可以选择在主备切换的过程中跳过这些错误
GTID,5.6 引入 全局事务 ID,不同于之前所说的那个 transaction_id,事务开始时分配的那个id是严格递增的,即使事务被回滚,这个id也会被占用,而 GTID 在事务提交时才会被分配,可以理解成一个事务日志的唯一ID
每个数据库实例都会维护一个自己的 GTID 集合,当执行 relay log 中的事务时,如果发现这条事务的 id 已经存在于自己的集合中,则跳过不再执行
基于 GTID 的主备切换 从库 B 发送自己的 GTID 集合 set_b 给 A’,A’ 用自己的集合与 set_b 比对,找到所有差集,并找出最早的一条事务日志发给 B,就从这条日志的位置开始同步
28 | 读写分离有哪些坑? 读写分离,以及怎么处理主备延迟导致的读写分离问题
读写分离的架构 客户端自己做负载均衡 优点 少了一层,查询性能更优 架构简单,排查问题方便 缺点 客户端要处理的问题变多,麻烦 通过代理层 proxy 做负载均衡 优点 客户端友好,客户端不需要关注后端细节 缺点 对运维团队要求高 如何避免读到的数据不是最新的 强制走主库 将请求分类,对于那些需要立马获取最新数据的请求,强制走主库查询
sleep方案 大多数情况下,主备延迟在 1s 内,可以在查询数据时,sleep 1s,这样有很大概率拿到最新数据
判断主备无延迟方案 每次在备库查询时,需要判断 seconds_behind_master 是否等于0,如果不等于0,需要等待
配合 semi-sync 方案 等主库位点方案 等 GTID 方案