上图是一个读写分离的基本架构
读写分离的目的是什么,分摊主库的压力
常见的读写分离的实现是通过一个中间件,客户端只连接中间件,客户端连接proxy,由proxy根据请求类型和上下文来进行分发
对于不同读写分离的架构的优缺点
1.客户端直连 因为不用转发,所以性能能较好,但是需要在主备切换/库迁移的时候,客户端进行手动调整,会比较麻烦,一般采用这种架构,都会有一个手动管理后端的组件,比如Zookeeper
2..带proxy的架构,对客户端比较友好,客户端不关心,但是性能并不高,现在来看,趋势是往着带proxy的架构方向去发展的
但是由于主备之间存在的一定的延迟,于是可能会出现如果主库上更新了一个事务,在从库上立刻查询可能查询不到
这就是最终一致性的问题
常用的解决方案有:
1.强制走主库的方案
2.sleep方案
3.判断主备延迟方案
4.配合semi-sync方案
5,主库位点方案
6.GTID方案
我们依次介绍方案
1.强制走主库的方案,就是对查询的请求进行分类,可以分为
对于必须要求最新结果的语句,强制发布到主库,对于没有要求的请求,可以发布到从库
2.sleep方案
其实就是对每一次请求,让其延迟查询一次,就是每次请求之前,都执行一次select selep(1),这个方案看起来对用户体验很不好,但其实并不是,比如商家发布商品,可以利用取巧的手段来进行实现,比如异步存库,可以默认其直接下发成功,在页面上直接显示成功了
3.判断主备无延迟
利用show slave status结果,其中提供的second_behind_master参数的值
可以采用每次查询判断second_behind_master来判断是否可以进行执行,如果不为0,就等这个参数为0再进行查询
4.利用位点比较
首先是一条命令
select master_pos_wait(file, pos[, timeout]);
逻辑如下
1.从从库执行
2.参数 file 和 pos 指的是主库上的文件名和位置;
3.timeout 可选,设置为正整数 N 表示这个函数最多等待 N 秒
这个命令正常返回的结果是一个正整数 M,表示从命令开始执行,到应用完 file 和 pos 表示的 binlog 位置,执行了多少事务。
返回的情况分为
1.如果执行期间,备库同步线程发生异常,则返回 NULL;
2.如果等待超过 N 秒,就返回 -1;
3.如果刚开始执行的时候,就发现已经执行过这个位置了,则返回 0
那么整体的流程就是
1.trx1 事务更新完成后,马上执行 show master status 得到当前主库执行到的 File 和 Position;
2.选定一个从库执行查询语句;
3.在从库上执行 select master_pos_wait(File, Position, 1);如果返回值是 >=0 的正整数,则在这个从库执行查询语句;否则,到主库执行查询语句。
说到底,还是可能去主库上进行查询的,压力都压在主库上了
5.GTID方案
如果数据库开启了GTID,那么可以等待GTID的方法\
类似的命令为
select wait_for_executed_gtid_set(gtid_set,1)
等待,直到这个库的事务包含了这个gtid_set,返回0
不然等待返回1
在前面的等待位点基础上,MySQL进行了优化
因为GTID会跟着事务的完成,在完成结果里面一并返回回去,这样,可以不用再去主库里面查询这个事务的position了
GTID在查询中返回的方式很简单,就是将session_track_gtids设置为OWN_GTID,通过API,从mysql_session_track_get_frist返回包中解析出GTID的值即可
于是整体流程为
1.trx1事务更新完成后,获取到这个GTID
2.选定一个从库执行查询语句
3.执行如上的语句
4.如果返回值是0.则在这个从库执行查询
5.不然,去主库执行查询