处理读写分离延迟

由于网络、CPU资源等原因,读写分离的延迟不可避免。所以,在引入读写分离方案的时候,要优先考虑数据延迟对业务是否有影响。下面主要讨论对有影响的情况下,有哪些方式可以尽量减少影响。

延迟处理方案

主从延迟可以很小,也可能很大,这是我们无法控制的。如果只用选择一个方案来解决延迟问题,就要考虑到最极端的情况,势必会让方案的成本超出预期。所以,最好是根据业务的重要程度,选择不同的方案。

读主库

对于一致性要求高的地方,这是最简单的方式,读主库就没有数据延迟问题。所以,引入读写分离之后,一定要对业务进行合理的划分。在功能实现上,如果用的ShardingPhere框架,默认在事务里的查询会默认走主库,一般就是写库。打开spring.shardingsphere.props.sql-show=true 配置就能打印SQL在哪个库执行。

Actual SQL: master ::: Insert Into ...
Actual SQL: slave0 ::: Select * From ...

等待几秒

大多数情况下,延迟时间在1秒之内。写入成功后,主动等待1秒后再查询,可以确保大多数情况能查到数据。这种方式实现简单,对业务侵入性低。看似很low,但能解决对一致性有要求的情景。当然,无脑等1秒在延迟很低的情况下,1秒就是白等。

判断GTID

GTID全局事务ID是Mysql5.6引入,我们可以通过获取主库提交事务后的GTID,在从库里判断GTID是否已经执行。已经执行则代表主库的事务已经同步到从库。

先通过SHOW VARIABLES LIKE 'gtid_mode' 命令查看GTID是否开启,返回ON表示开启。

+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| gtid_mode     | ON    |
+---------------+-------+

未开启的话要在my.cnf配置文件里开启。

gtid_mode = ON
enforce_gtid_consistency = ON

获取事务的GTID

在提交事务后,通过SHOW VARIABLES LIKE 'gtid_executed'; 或者SELECT @@GTID_EXECUTED; 查询到已经提交的事务ID集合,可能会返回ff782504-9b09-11ee-a4c3-0050568c4224:1-3 这样的格式,然后我们处理一下,获取最后一个事务IDff782504-9b09-11ee-a4c3-0050568c4224:3

从库判断事务是否同步

在从库通过*wait_for_executed_gtid_set*(gtid_set [, timeout]) 方法可以查询指定的事务ID是否已经执行成功,方法返回0表示已经执行;返回1表示执行超时;返回其他数值表示失败。

  • gtid_set是事务ID的集合,查询一个或多个事务可以用5f4cea4d-38b5-11ec-8814-0800272d6057:1,5f4cea4d-38b5-11ec-8814-0800272d6057:3 或连续的话用5f4cea4d-38b5-11ec-8814-0800272d6057:1-5这样的格式
  • timeout是可选参数,可以设置方法执行的超时时间,单位是秒。不传的话用的是**slave_net_timeout** 变量的值,默认是60秒。

例如,我们执行select *wait_for_executed_gtid_set*('5f4cea4d-38b5-11ec-8814-0800272d6057:3', 1); 返回了0,我们就知道数据同步完成,就可以继续查询数据。如果返回非0,就直接查主库。

总结

要判断主从严格一致的成本是很高的,用到GTID来判断主从延迟,对代码的侵入性很高,而且要额外的查询GTID和判断GTID是否同步,性能也会受到影响。如果业务场景对一致性要求很高,为了简化编程,还不如直接查主库。如果对一致性要求不高,直接读从库,或者等待1秒也是不错的方式。