最近项目上线,遇到了主从问题。按理说公司基建不至于呈现这种问题,但便是呈现了。或许由于用的不是原生的MySQL吧。主从推迟会给前端和服务端带来很多问题,需求花费时刻用工程手法来处理,我以为这是很不合理的。
举几个由于主从推迟会导致问题场景:
- 创立了一个产品然后当即跳转到概况页
- 在列表页更新了用户的权限,当即刷新
凡是像这种操作后当即获取的,全会有问题。
为什么要有主从
MySQL数据库的主从(Master-Slave)架构首要是为了完结数据的高可用性(High Availability)和读写别离,详细的原因如下:
- 数据备份:主从架构可以完结数据的实时备份,从库可以作为主库的一个镜像存在,当主库呈现问题时,可以敏捷切换到从库,保证数据的安全性。
- 读写别离:在主从架构中,主库首要担任写操作,从库首要担任读操作,这样可以分管主库的压力,进步体系的处理才干。
- 故障切换:当主库呈现故障时,可以敏捷切换到从库,保证服务的连续性,进步体系的可用性。
- 负载均衡:经过主从架构,可以将读恳求涣散到多个从库,完结负载均衡,进步体系的功能。
- 数据一致性:主从仿制可以保证数据在主库和从库之间保持一致,进步数据的精确性。 因而,为了保证数据的安全性和体系的高可用性,MySQL通常会选用主从架构。
主从怎么同步
下面是一个update 句子在主节点 A 履行,然后同步到从节点 B 的完整流程图
从库 B 跟主库 A 之间保持了一个长衔接。主库 A 内部有一个线程,专门用于服务从库 B的这个长衔接。一个业务日志同步的完整过程是这样的:
-
在从库 B 上经过 change master 指令,设置主库 A 的 IP、端口、用户名、暗码,以及要从哪个方位开端恳求 binlog,这个方位包括文件名和日志偏移量。
-
在从库 B 上履行 start slave 指令,这时分从库会启动两个线程,便是图中的 io_thread 和 sql_thread。其间 io_thread 担任与主库树立衔接。
-
主库 A 校验完用户名、暗码后,开端按照从库 B 传过来的方位,从本地读取 binlog,发给 B。
-
从库 B 拿到 binlog 后,写到本地文件,称为中转日志(relay log)。
-
sql_thread 读取中转日志,解析出日志里的指令,并履行。
为什么会主从推迟
什么是主从推迟?
-
主库 A 履行完结一个业务,写入 binlog,咱们把这个时刻记为 T1;
-
之后传给从库 B,咱们把从库 B 接收完这个 binlog 的时刻记为 T2;
-
从库 B 履行完结这个业务,咱们把这个时刻记为 T3。
所谓主从推迟,便是同一个业务,在从库履行完结的时刻和主库履行完结的时刻之间的差值,也便是 T3-T1。
你可以在从库上履行 show slave status 指令,它的回来成果里面会显现seconds_behind_master,用于表明当时从库推迟了多少秒。
在网络正常的时分,日志从主库传给从库所需的时刻是很短的,即 T2-T1的值是十分小的。也便是说,网络正常状况下,主从推迟的首要来源是从库接收完 binlog和履行完这个业务之间的时刻差。
主从推迟来源
- 有些部署条件下,从库地点机器的功能要比主库地点的机器功能差。
- 从库的压力大。如从库上的查询耗费了很多的 CPU 资源,影响了同步速度,形成主从推迟。
- 大业务。由于主库上有必要等业务履行完结才会写入 binlog,再传给从库。所以,假如一个主库上的句子履行 10 分钟,那这个业务很或许就会导致从库推迟 10分钟。
- 大表 DDL,也是典型的大业务场景。
- 从库的并行仿制才干。查看软件版别,在官方的 5.6 版别之前,MySQL (sql_thread)只支持单线程仿制,由此在主库并发高、TPS 高时就会呈现严峻的主从推迟问题。
怎么处理
一般的主从结构如下:
一旦呈现主从推迟问题,有如下处理计划
强制走主库计划 – 有点可行
- 将查询恳求做分类,有必要拿到最新成果的,强制恳求到主库
- 长处:可以区分场景,压力可控
- 缺陷:
- 前后端都得改动。前端判别是否走主库,服务端判别指定场景走查主库
- 后续保护也比较麻烦
- 有时前端无法判别出场景,如进概况页,前端无法判别是刚创立完跳转的仍是打开的是早已创立的
- 悉数走主库
- 长处:简略便捷
- 缺陷:不区分详细场景,主库压力大
- 先读从库,从库没有读主库
- 长处:相对简略
- 缺陷:无法处理一切场景,如list的场景,由于必定有数据,但并不知道是否精确
sleep 计划 – 有点可行
- 前端推迟恳求
- 长处:简略便捷
- 缺陷:用户体验欠好
- 写相关接口,服务端回来成果概况,前端展示概况。如创立产品接口,服务端回来产品的详细信息,前端直接展示详细信息,不恳求接口
- 长处:逻辑比较通顺、明晰
- 缺陷:
- 前端需求完结多套逻辑。如虽然是概况,但至少或许来自创立和概况接口;在成员列表中删去member成功后就直接不显现,不再调用list接口
- 保护本钱高,如关于概况页,假如后续概况也变更,创立和概况接口怎么保持一致
- 无法处理一切场景,如创立空间后获取成员列表(至少有自己)
判别主从推迟计划 – 有的可行
- 写操作写redis(过期时刻1~2s),读的时分判别是否有redis,有则读主库。如创立产品,则在redis里记载产品id,获取概况的时分先判别该产品id是否在redis存在,假如存在则读主库。
- 长处:
- 前端无需改动,服务端改动相对可控,而且设置为弱依赖,所以问题应该不大
- 能处理大部分问题
- 缺陷:
- 服务端需求依据场景记载、读取redis
- 有必定概率添加主库压力,但整体可控
- 判别主从无推迟计划 – 不可行
- 每次从库履行查询恳求前,先判别seconds_behind_master 是否现已等于 0。假如还不等于 0 ,那就有必要比及这个参数变为0 才干履行查询恳求。可以使用 show slave status。
- 对比位点保证主从无推迟
- 对比 GTID 集合保证主从无推迟
这种方式感觉不太现实
- 完结上本钱高
- 依然有过期读问题。由于上面判别主从无推迟的逻辑,是“从库收到的日志都履行完结了”。可是,从 binlog在主从之间状态的剖析中,不难看出还有一部分日志,处于客户端现已收到提交承认,而从库还没收到日志的状态。
- 假如在业务更新的高峰期,主库的位点或者 GTID 集合更新很快,那么上面的两个位点等值判别就会一向不建立,很或许呈现从库上迟迟无法呼应查询恳求的状况
- 配合 semi-sync(半同步仿制) 计划 – 不可行
主从无推迟计划 + semi-sync 计划 能处理过期读问题。
semi-sync 做了这样的规划:
-
业务提交的时分,主库把 binlog 发给从库;
-
从库收到 binlog 以后,发回给主库一个 ack,表明收到了;
-
主库收到这个 ack 以后,才干给客户端回来“业务完结”的承认。
这样,semi-sync 配合前面关于位点的判别,就可以确认在从库上履行的查询恳求,可以防止过期读。
但这种计划也不太现实,而且没有完全处理问题
- semi-sync+ 位点判别的计划,只对一主一从的场景是建立的。假如落到其它从库,仍是会呈现过期读
- 等主库位点计划 – 不可行
select master_pos_wait(file, pos[, timeout]);
这条指令的逻辑如下:
-
它是在从库履行的;
-
参数 file 和 pos 指的是主库上的文件名和方位;
-
timeout 可选,设置为正整数 N 表明这个函数最多等候 N 秒。
回来成果如下:
-
假如履行期间,从库同步线程产生反常,则回来 NULL;
-
假如等候超越 N 秒,就回来 -1;
-
假如刚开端履行的时分,就发现现已履行过这个方位了,则回来 0。
-
正常回来的成果是一个正整数 M,表明从指令开端履行,到应用完 file 和 pos 表明的 binlog 方位,履行了多少业务。
使用方法如下
-
trx1 业务在主库更新完结后,立刻履行 show master status 得到当时主库履行到的 File 和Position;
-
选定一个从库履行查询句子;
-
在从库上履行 select master_pos_wait(File, Position, 1);
-
假如回来值是 >=0 的正整数,则在这个从库履行查询句子
这种也不太现实,想想完结复杂度有多高。
- 等 GTID 计划 – 不可行
select wait_for_executed_gtid_set(gtid_set, 1);
这条指令的逻辑是:
-
等候,直到这个库履行的业务中包括传入的 gtid_set,回来 0;
-
超时回来 1。
等 GTID 的履行流程为:
-
trx1 业务更新完结后,从回来包直接获取这个业务的 GTID,记为 gtid1;
-
选定一个从库履行查询句子;
-
在从库上履行 select wait_for_executed_gtid_set(gtid1, 1);
-
假如回来值是 0,则在这个从库履行查询句子;
-
否则,到主库履行查询句子。
总结
真的是基建要做好,基建好了大家能把精力放到更重要的工作上。假如真呈现主从推迟问题,能选择的计划其实比较少。要么就分场景走主库、要么前端sleep(偏暂时计划)、要目服务端自己判别一下主从推迟状况。
这次咱们选择redis打点记载,看看效果怎么样吧。
最终
大家假如喜欢我的文章,可以关注我的大众号(程序员麻辣烫)
我的个人博客为:shidawuhen.github.io/
往期文章回忆: