内容简介:由于
Proxy
对比
- 客户端直连
- 少了一层
Proxy
转发,查询性能稍微好一点 - 整体架构简单,排查问题方便
- 需要了解 后端部署细节 ,在出现主从切换、库迁移时,客户端有感知,需要调整数据库连接信息
- 一般伴随着一个负责管理后端的组件,例如
ZooKeeper
- 一般伴随着一个负责管理后端的组件,例如
- 少了一层
- Proxy – 发展趋势
- 对客户端友好,客户端不需要关注后端细节,但后端维护成本较高
-
Proxy
也需要 高可用 架构,带Proxy的整体架构相对复杂
过期读
由于 主从延迟 ,主库上执行完一个更新事务后,立马在从库上执行查询,有可能读到刚刚的事务更新之前的状态
解决方案
强制走主库
- 将查询请求做 分类
- 必须要拿到最新结果的请求,强制将其发送到主库上
- 可以读到旧数据的请求,将其发到从库上
- 如果完全不能接受过期读,例如金融类业务,相当于放弃读写分离,所有的读写压力都在主库上
SLEEP
- 主库更新后,读从库之前先
SLEEP
一下,类似于SELECT SLEEP(1)
- 基于的假设:大多数主从延时在1秒内
- 卖家发布商品后,用
Ajax
直接把客户端输入的内容作为“新的商品”显示在页面上,而非真正的做数据库查询- 等卖家再次刷新页面,其实主从已经同步完成了,也达到了
SLEEP
的效果
- 等卖家再次刷新页面,其实主从已经同步完成了,也达到了
-
SLEEP
方案解决了类似场景下的过期读问题,但存在 不精确 的问题- 如果主从延时只有0.5秒,也会等到1秒
- 如果主从延迟超过了1秒,依然会出现 过期读 的问题
判断主从无延迟
-
SLOW SLAVE STATUS
.Seconds_Behind_Master
- 每次在从库执行查询请求前,先判断
Seconds_Behind_Master
是否等于0
-
Seconds_Behind_Master=0
才能执行查询请求 -
Seconds_Behind_Master
的精度为 秒 ,如果需要更高精度,可以考虑对比 位点 和GTID
- 每次在从库执行查询请求前,先判断
- 位点
-
Master_Log_File
和Read_Master_Log_Pos
,表示 读到的主库的最新位点 -
Relay_Master_Log_File
和Exec_Master_Log_Pos
,表示 从库执行的最新位点 -
Master_Log_File=Relay_Master_Log_File
和Read_Master_Log_Pos=Exec_Master_Log_Pos
- 表示接收到的日志已经 同步完成
-
-
GTID
-
Auto_Position=1
,表示 主从关系 使用了GTID
协议 -
Retrieved_Gtid_Set
,表示从库 收到 的所有日志的GTID
集合 -
Executed_Gtid_Set
,表示从库所有 已经执行完成 的GTID
集合
-
mysql> SHOW SLAVE STATUS\G; *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Log_File: master-bin.000003 Read_Master_Log_Pos: 484 Relay_Log_File: relay-bin.000003 Relay_Log_Pos: 699 Relay_Master_Log_File: master-bin.000003 Exec_Master_Log_Pos: 484 Seconds_Behind_Master: 0 Master_UUID: b0bda503-3cf1-11e9-8c3a-0242ac110002 Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates Retrieved_Gtid_Set: b0bda503-3cf1-11e9-8c3a-0242ac110002:1-6 Executed_Gtid_Set: b0bda503-3cf1-11e9-8c3a-0242ac110002:1-6,ba0b2f12-3cf1-11e9-9c40-0242ac110003:1-5 Auto_Position: 1
不精确
-
binlog
在主从之间的状态- 主库执行完成,写入
binlog
,反馈给客户端 -
binlog
被主库发送到从库,从库收到 - 从库执行
binlog
(应用relaylog
)
- 主库执行完成,写入
- 上面判断的 主从无延迟 : 从库收到的日志都执行完成了
- 并没有考虑这部分日志:客户端已经收到提交确认,但从库还未收到
- 主库上执行完成了3个事务:
trx1
、trx2
和trx3
-
trx1
和trx2
已经传到从库,并且已经执行完成了 -
trx3
在主库执行完成后,并且已经回复给客户端,但还未传到从库中 - 如果此时在从库上执行查询请求,按上面的逻辑,从库已经 没有同步延迟 了,但还是查不到
trx3
的变更,出现了 过期读
SEMI-SYNC
SEMI-SYNC设计
- 事务提交到时候,主库把
binlog
发给从库 - 从库收到
binlog
后,发回给主库一个ACK
,表示收到了 - 主库收到这个
ACK
以后,才能给客户端返回事务完成的确认
小结
- 启用了
SEMI-SYNC
- 所有给客户端发送过确认的事务,都确保了 某一个从库 已经收到了这个日志
-
SEMI-SYNC
+位点的方案,只针对 一主一从 的场景是成立的- 在 一主多从 的场景里,主库只要等到一个从库的
ACK
,就开始给客户端返回确认 - 如果对刚刚响应了
ACK
的从库执行查询请求(+判断 主从无延迟 ),能够确保读到最新的数据,否则可能是 过期读
- 在 一主多从 的场景里,主库只要等到一个从库的
- 如果在业务高峰期,主库的位点或者GTID集合更新很快,从库可能一直跟不上主库,导致从库迟迟无法响应查询请求
- 在出现 持续延迟 的情况下,可能会出现 过度等待 (判断 主从无延迟 )的情况
等主库位点
SELECT MASTER_POS_WAIT(file, pos[, timeout]);
- 在 从库 上执行
- 参数
file
和pos
指的是 主库 上的文件名和位置 -
timeout
单位为秒 - 返回正整数,表示从 命令开始执行 ,到应用完
file
和pos
,总共 执行了多少事务- 如果在执行期间,从库的同步线程发生异常,返回NULL
- 如果等待超过
timeout
秒,返回-1
- 如果刚开始执行的时候,发现 已经执行 过这个位置,返回
0
样例
先在 MySQL A
执行 trx1
,然后在 MySQL B
执行查询请求
- 事务
trx1
更新完成后,马上执行SHOW MASTER STATUS
,得到当前主库执行到的File
和Position
- 选择一个从库执行查询语句
- 在该从库上先执行
SELECT MASTER_POS_WAIT(File, Position, 1)
- 如果返回值
>=0
,则直接在这个从库上执行查询语句 - 否则,在主库上执行查询语句
- 一种退化机制,针对 主从延时不可控 的场景
-- MySQL A mysql> SHOW MASTER STATUS; +-------------------+----------+--------------+------------------+------------------------------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +-------------------+----------+--------------+------------------+------------------------------------------+ | master-bin.000003 | 643 | | | b0bda503-3cf1-11e9-8c3a-0242ac110002:1-7 | +-------------------+----------+--------------+------------------+------------------------------------------+ -- MySQL B mysql> SELECT MASTER_POS_WAIT('master-bin.000003',643,1); +--------------------------------------------+ | MASTER_POS_WAIT('master-bin.000003',643,1) | +--------------------------------------------+ | 0 | +--------------------------------------------+
等GTID
-- 等待,直到这个库执行的事务中包含传入的gtid_set,返回0 -- 超时返回1 SELECT WAIT_FOR_EXECUTED_GTID_SET(gtid_set, 1);
样例
- 从 MySQL 5.7.6开始,允许在执行完 更新类事务 后,把这个事务的
GTID
返回给客户端 - 事务
trx1
更新完成后,从返回结果中直接获取trx1
的GTID
,记为gtid1
- 选择一个从库执行查询语句
- 在该从库上先执行
SELECT WAIT_FOR_EXECUTED_GTID_SET(gtid1, 1)
- 如果返回
0
,则直接在这个从库上执行查询语句 - 否则,在主库上执行查询语句
-- MySQL A mysql> SHOW MASTER STATUS; +-------------------+----------+--------------+------------------+------------------------------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +-------------------+----------+--------------+------------------+------------------------------------------+ | master-bin.000003 | 643 | | | b0bda503-3cf1-11e9-8c3a-0242ac110002:1-7 | +-------------------+----------+--------------+------------------+------------------------------------------+ -- MySQL B mysql> SELECT WAIT_FOR_EXECUTED_GTID_SET('b0bda503-3cf1-11e9-8c3a-0242ac110002:1-7',1); +--------------------------------------------------------------------------+ | WAIT_FOR_EXECUTED_GTID_SET('b0bda503-3cf1-11e9-8c3a-0242ac110002:1-7',1) | +--------------------------------------------------------------------------+ | 0 | +--------------------------------------------------------------------------+
参考资料
《MySQL实战45讲》
转载请注明出处:http://zhongmingmao.me/2019/03/02/mysql-read-write-separation/
访问原文「MySQL -- 读写分离」获取最佳阅读体验并参与讨论
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 想用数据库“读写分离” 请先明白“读写分离”解决什么问题
- Java 读写锁浅析
- Golang文件读写
- ReentrantReadWriteLock 读写锁解析
- 用Python读写文件
- golang读写文件
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Principles of Object-Oriented JavaScript
Nicholas C. Zakas / No Starch Press / 2014-2 / USD 24.95
If you've used a more traditional object-oriented language, such as C++ or Java, JavaScript probably doesn't seem object-oriented at all. It has no concept of classes, and you don't even need to defin......一起来看看 《Principles of Object-Oriented JavaScript》 这本书的介绍吧!