首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >28 | 读写分离有哪些坑?

28 | 读写分离有哪些坑?

作者头像
HaC
发布于 2020-12-30 17:50:06
发布于 2020-12-30 17:50:06
1K0
举报
文章被收录于专栏:HaC的技术专栏HaC的技术专栏

上一篇说到了一主多从的结构以及切换流程。

读写分离

读写分离结构:

读写分离的主要目标就是分摊主库的压力。图 1 中的结构是客户端(client)主动做负载均衡,这种模式下一般会把数据库的连接信息放在客户端的连接层。也就是说,由客户端来选择后端数据库进行查询。

还有一种架构是,在 MySQL 和客户端之间有一个中间代理层 proxy,客户端只连接 proxy, 由 proxy 根据请求类型和上下文决定请求的分发路由。

带proxy的读写分离结构:

以上两种架构的区别:

  1. 客户端直连 客户端直连少了proxy,查询性能好一点,架构简单,易排查。但是出现主备切换、库迁移的时候,客户端需要调整数据库连接信息,一般采用这种架构,都会伴随着负责管理后端的组件,比如zookeeper。
  2. 带proxy 3对客户端友好,不需要关注后端细节,连接、维护等等由proxy完成,proxy也需要高可用的架构,架构整体相对复杂。

目前使用proxy架构比较多,但是以上两种都会遇到一种情况,就是主从同步存在延迟,户端执行完一个更新事务后马上发起查询,如果查询选择的是从库的话,就有可能读到刚刚的事务更新之前的状态。

前面我们说过了几种可能导致主备延迟的原因,以及对应的优化策略,但是主从延迟还是不能 100% 避免的。

这种 不一致的 “过期读” 有以下处理方案:

  • 强制走主库方案;
  • sleep 方案;
  • 判断主备无延迟方案;
  • 配合 semi-sync 方案;
  • 等主库位点方案;
  • 等 GTID 方案。

强制走主库方案

  1. 对于必须要拿到最新结果的请求,强制将其发到主库上。比如,在一个交易平台上,卖家发布商品以后,马上要返回主页面,看商品是否发布成功。那么,这个请求需要拿到最新的结果,就必须走主库。
  2. 对于可以读到旧数据的请求,才将其发到从库上。在这个交易平台上,买家来逛商铺页面,就算晚几秒看到最新发布的商品,也是可以接受的。那么,这类请求就可以走从库。

这种方案有点取巧,但是是用的最多的。

Sleep 方案

主库更新后,读从库之前先 sleep 一下。具体的方案就是,类似于执行一条 select sleep(1) 命令。 这个方案存在的问题就是不精确。这个不精确包含了两层意思:

  • 如果这个查询请求本来 0.5 秒就可以在从库上拿到正确结果,也会等 1 秒;
  • 如果延迟超过 1 秒,还是会出现过期读。

判断主备无延迟方案

要确保备库无延迟,通常有三种做法。 通过 show slave status 结果里的 seconds_behind_master 参数的值,可以用来衡量主备延迟时间的长短。

  1. 第一种确保主备无延迟的方法是,每次从库执行查询请求前,先判断 seconds_behind_master 是否已经等于 0。如果还不等于 0 ,那就必须等到这个参数变为 0 才能执行查询请求。
  2. 第二种对比位点 ;seconds_behind_master 的单位是秒,如果你觉得精度不够的话,还可以采用对比位点和 GTID 的方法来确保主备无延迟,如图为show slave status 结果的部分截图:
  • 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 这两组值完全相同,就表示接收到的日志已经同步完成。
  1. 对比 GTID 集合;
  • Auto_Position=1 ,表示这对主备关系使用了 GTID 协议。
  • Retrieved_Gtid_Set,是备库收到的所有日志的 GTID 集合;
  • Executed_Gtid_Set,是备库所有已经执行完成的 GTID 集合。 如果这两个集合相同,也表示备库接收到的日志都已经同步完成。

可见,对比位点和对比 GTID 这两种方法,都要比判断 seconds_behind_master 是否为 0 更准确。

但是这种方法还是有不精确的地方: 如图:

  1. trx1 和 trx2 已经传到从库,并且已经执行完成了;
  2. trx3 在主库执行完成,并且已经回复给客户端,但是还没有传到从库中。 此时在从库B上执行查询请求,还是查不到tx3。

解决: 配合 semi-sync。 semi-sync 做了这样的设计:

  1. 事务提交的时候,主库把 binlog 发给从库;
  2. 从库收到 binlog 以后,发回给主库一个 ack,表示收到了;
  3. 主库收到这个 ack 以后,才能给客户端返回“事务完成”的确认。 (有点像tcp的三次握手) 也就是说,如果启用了 semi-sync,就表示所有给客户端发送过确认的事务,都确保了备库已经收到了这个日志。

虽然这种方法解决了主库掉电,binlog还没及时传过去,会导致数据丢失。

但是,semi-sync+ 位点判断的方案,只对一主一备的场景是成立的。在一主多从场景中,主库只要等到一个从库的 ack,就开始给客户端返回确认。这时,在从库上执行查询请求,就有两种情况:

  1. 如果查询是落在这个响应了 ack 的从库上,是能够确保读到最新数据;
  2. 但如果是查询落到其他从库上,它们可能还没有收到最新的日志,就会产生过期读的问题。

就是说有一个从库ack了,就算同步成功了,但是有多个从库,不能保证断电了,你查询的从库刚好是这个已经同步成功的从库,有可能是断电没有传过去的从库。

其实,判断同步位点的方案还有另外一个潜在的问题,即:如果在业务更新的高峰期,主库的位点或者 GTID 集合更新很快,那么上面的两个位点等值判断就会一直不成立,很可能出现从库上迟迟无法响应查询请求的情况。

主备持续延迟一个事务:

以上是一个一直处于延迟的一个事务状态。 trx1同步完成了redolog,binlog还没同步,此时状态2是查不到的,只有在状态3才能查到。 所以,semi-sync 配合判断主备无延迟的方案,存在两个问题:

  1. 一主多从的时候,在某些从库执行查询请求会存在过期读的现象;
  2. 在持续延迟的情况下,可能出现过度等待的问题。

以下方案可以解决这两个问题。

等主库位点方案

涉及一条命令,在从库上执行的一条命令:

代码语言:javascript
复制
select master_pos_wait(file, pos[, timeout]);

这条命令的逻辑如下:

  1. 它是在从库执行的;
  2. 参数 file 和 pos 指的是主库上的文件名和位置;
  3. timeout 可选,设置为正整数 N 表示这个函数最多等待 N 秒。

逻辑如图:

  1. trx1 事务更新完成后,马上执行 show master status 得到当前主库执行到的 File 和 Position;
  2. 选定一个从库执行查询语句;
  3. 在从库上执行 select master_pos_wait(File, Position, 1);
  4. 如果返回值是 >=0 的正整数,则在这个从库执行查询语句;
  5. 否则,到主库执行查询语句。

总结: 这里我们假设,这条 select 查询最多在从库上等待 1 秒。那么,如果 1 秒内 master_pos_wait 返回一个大于等于 0 的整数,就确保了从库上执行的这个查询结果一定包含了 trx1 的数据。步骤 5 到主库执行查询语句,是这类方案常用的退化机制。因为从库的延迟时间不可控,不能无限等待,所以如果等待超时,就应该放弃,然后到主库去查。

如果所有的从库的延迟都超过1秒,那查询都跑到主库上了。

GTID 方案

命令:

代码语言:javascript
复制
 select wait_for_executed_gtid_set(gtid_set, 1);

这条命令的逻辑是:

  1. 等待,直到这个库执行的事务中包含传入的 gtid_set,返回 0;
  2. 超时返回 1。

等 GTID 的执行流程:

  1. trx1 事务更新完成后,从返回包直接获取这个事务的 GTID,记为 gtid1;
  2. 选定一个从库执行查询语句;
  3. 在从库上执行 select wait_for_executed_gtid_set(gtid1, 1);
  4. 如果返回值是 0,则在这个从库执行查询语句;
  5. 否则,到主库执行查询语句。

等主库位点的方案一样,等待超时后是否直接到主库查询,需要做限流。 限流需要修改MySQL客户端的代码。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/02/25 ,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 读写分离
    • 强制走主库方案
    • Sleep 方案
    • 判断主备无延迟方案
    • 等主库位点方案
  • GTID 方案
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档