28-读写分离有哪些坑?
# 读写分离的方式
- 客户端直连服务器,这种性能最好,但是需要客户端改动代码,分别判断连接哪个库,简单说就是主备切换或者迁移的时候客户端能够感知到,并随之改动代码。
- 通过 proxy 的方式,客户端只连接 proxy,而不用关注切换的细节。但是这样性能会有所下降。
但是中间会有些坑,比如主库执行了一个更新或者新增,客户端立刻去读了,这样就会读到之间的一个状态,学名为过期读。
# 强制走主库方案
让那些需要立刻读的请求强制走到主库连接上,但是对于某些系统来说所有请求都不能允许过期读,意味着读写分离已经没有意义了。
# sleep 方案
就在执行新增或者更新的之后自动执行一个 select sleep (1) ,一般来说都能在 1 秒钟之内同步完成,但是也有例外,如果这个同步花了 20ms 那么也要等待 1 秒,如果同步大于 1 秒,还是有过期读的发生。
# 判断主备无延迟方案
方案一:根据 SBM 的值变成了 0,就代表主备同步了。但是这样不够精准。
方案二:对比位点确保主备无延迟,主库的 Master_Log_File 和 Read_Master_Log_Pos 是主库的最新的位点,Relay_Master_Log_File 和 Exec_Master_Log_Pos 是备库的最新位点,两者相同就代表同步完成了。
方案三:GTID 集合判断,Retrieved_Gtid_Set 是备库收到的 GTID 集合,Executed_Gtid_Set 是备库执行的 GTID 集合,两者相同代表日志同步完成。
方案二和三都比一要精准的,但是还是不够精准,因为在检查位点的时候不能保证主库是否在处理新的请求。
# 配合 semi-sync 方案
semi-sync 的步骤是
- 主库发送 binlog 给备库
- 备库收到后直接 ack
- 主库收到备库的 ack 后才能给客户端返回成功的字样
所以 semi-sync 解决了一主一备的情况下普通的复制模式下主库突然掉电导致 binlog 还没来记得发送给备库造成的主备不一致问题。
但还是说只解决了一主一备的问题,一主多备还是会出现问题,如果请求正好落到了回复 ack 的那台备库还是正常的,但是要是落到了其他备库中又会产生过期读。
# 等主库位点方案
通过 select master_pos_wait(file, pos[, timeout]);
命令在备库执行,file 和 pos 都是主库上的、timeout 自定义,返回一个数字。
- 如果主备同步发生异常,返回 NULL。
- 如果等待超过 timeout 秒,返回 - 1。
- 如果已经执行过这个位置了,返回 0
- 如果返回一个正整数 M,表示 file 和 pos 位置 binlog,执行了 M 个事务。
所以根据上面的步骤可以得知,如果说在 file 和 pos 位置的 binlog 在备库中执行出来已经 >=0 了,说明备库已经完成了同步,可以把请求在备库执行了。否则就到主库。
# 等 GTID 方案
这个命令 select wait_for_executed_gtid_set(gtid_set, 1);
的逻辑是直到执行完成这个集合中的事务,返回 0,如果超时返回 1。
所以就可以按以下步骤
- 主库事务完成后,从返回包(正文中有获取步骤)中获取这个事务的 GTID,记为 gtid1;
- 从库执行这个命令,如果返回的是 0 就可以从从库去查询,否则去主库。
# 总结
方案中没有完美无瑕的,具体选择哪种方案需要按照业务来定。
# 上一章答案
如果一个新的从库接上主库,但是需要的 binlog 已经没了,要怎么做?
- 可以创建一个主服务器的备份,然后通过 mysqldump 之类的工具去从库上执行备份文件。
- 如果有其他从库中有全量的 binlog 的话,可以先将这个新从库接到老从库上。