MySQL——10、主从延时

1.1 主从延时

1.1.1 如何查看主从是否延时

可以通过监控 show slave status 命令输出的Seconds_Behind_Master参数的值,来检测主从延时:

NULL:表示io_thread或是sql_thread有任何一个发生故障;

0:该值为零,表示主从复制良好;

正值:表示主从已经出现延时,数字越大,表示从库延迟越严重。

1.1.2 为什么会造成主从延时

由于主库上可以多客户端并发的写入,当主库的TPS较高时,由于从库的SQL线程是单线程的,导致从库处理速度,可能会跟不上主库的处理速度,从而造成了延迟。

1.1.3 并发与主从延时的时间

\1) 主库并发达到1000/s时,从库的延时会有几毫秒,几乎可以忽略。

\2) 主库并发达到2000/s时,从库的延时会有几十毫秒。

\3) 主库并发达到4000/s,6000/s,8000/s时,此时主库的压力很大,都快挂了。从库的延时会达到几秒。

1.1.4 主从延时造成的现象

刚刚写入库的数据去查,却没有查到,可能是发生了主从延时,

比如:刚插入一条订单数据,然后立即根据id查询,有很大概率是取不到任何数据的,因为从库没来得及更新

注意:

实际上要虑好应该在什么场景下来用这个mysql主从同步,建议是一般在读远远多于写,而且读的时候一般对数据时效性要求没那么高的时候,才用mysql主从同步

所以通常来说,对于那种写了之后,立马就要保证可以查到的场景,采用强制读主库的方式,或数据库中间件,这样,就可以保证你肯定的可以读到数据

1.1.5 如何解决主从延时

1、分库,将主库拆分成多个;

2、重写代码,插入数据后,不要立即查询,如果需要立即查询的,直接读主库

​ i. 主库查询的Sql查询语句上加上/master/标识

​ ii. 使用mybatis插件,new MasterQueryRouter,start和end方法内的全走主库

MasterQueryRouter内部使用了threadlocal

3、开启并行复制

Mysql5.7的版本开启并行复制

通过设置参数slave_parallel_workers>0并且global.slave_parallel_type=‘LOGICAL_CLOCK’开启并行复制

理解:

一般来说,如果主从延迟较为严重:

\1) 架构层面:分库,将一个主库拆分为多个。

比如将1个库拆为4个主库,每个主库的写并发就500/s,此时主从延迟可以忽略不计。

\2) 开启mysql支持的并行复制,多个库并行复制。

但如果说某个库的写入并发就是特别高,单库写并发达到了2000/s,并行复制还是没意义。28法则,很多时候比如说,就是少数的几个订单表,写入了2000/s,其他几十个表10/s。

\3) 代码层面:重写代码。

写代码的同学,要慎重。当时我们其实短期是让那个同学重写了一下代码,插入数据之后直接就更新,不要查询。

\4) 直连主库。

如果确实存在插入后立马要求就查询到,然后根据结果(比如某个状态)反过来执行一些操作,则可以对这个查询设置【直连主库】。

但不推荐这种方法,这么搞导致读写分离的意义就丧失了。

1.1.6 如何强制主库查询

1、走主库查询的SQL上加/master/标识

2、使用mybatis插件,new MasterQueryRouter,start和end方法内的全走主库

理解:

一般的业务系统,对于数据库的读操作会远远大于写操作,为了提高查询性能,降低写库的压力,DBA团队一般会引入数据库中间件实现读写分离,比如atlas。

这种引入中间件的方式,对上层应用是透明的,对于应用层来说,就像连接了一个mysql实例。当SQL请求打到数据库中间件上时,查询的SQL会被自动路由到从库(多个从库均衡分配),写SQL会被路由到主库。

因为主从同步一定会存在延迟,所以在某些场景下,会出现SQL查询无法获取最新数据的情况,比如刚插入一条订单数据,然后立即根据id查询,有很大概率是取不到任何数据的,因为从库没来得及更新。

解决方法也很直接,强制查询走主库,atlas中间件就提供了这样的机制,在SQL前加上/master/,就会被路由到主库。

方法一:在需要走主库查询的SQL上加/master/标识

采用这种方式,我们的mybatis mapper文件可能存在一些重复的代码,比如给某些查询写一些强制走主库的版本,如下:

在业务处理中,也可能要写多个版本的方法,来区分是否强制查询走主库。

(推荐)方法二:通过mybatis插件机制动态修改SQL

期望业务代码可以这样写:

public void foo() {

MasterQueryRouter router = new MasterQueryRouter();

router.start(); // start和end方法之间的所有SQL查询均走主库

// sqlQuery1

// sqlQuery2

router.end(); // end方法之后的SQL查询仍然是默认走从库

// sqlQuery3

// some code

}

sqlQuery1, sqlQuery2查询走主库。sqlQuery3查询默认走从库。

MasterQueryRouter类通过ThreadLocal保证线程安全,且不需要到处传递是否强制主库查询的标识,在这一点上借鉴了Spring @Transaction的实现。

public class MasterQueryRouter implements Closeable { private static ThreadLocal isForceMaster = ThreadLocal.withInitial(() -> null); /** * 开启主库查询,之后的Sql查询语句都会走主库 / public void start() { isForceMaster.set(Boolean.TRUE); } /* * 关闭主库查询,之后的Sql查询都会默认走从库 / public void end() { isForceMaster.set(Boolean.FALSE); } /* * 是否强制走主库 * @return / public static boolean forceQueryToMaster() { return Boolean.TRUE.equals(isForceMaster.get()); } /* * 清理资源 * @throws IOException */ @Override public void close() { isForceMaster.remove(); } }

为了及时清理是否走主库的标识,这儿特意实现了Closeable接口,调用方需要保证在当前线程执行完毕后,清理资源。

结合lombok,代码会更加简洁,如下:

public void foo() { @Cleanup // try...finally... MasterQueryRouter router = new MasterQueryRouter(); router.start(); // start和end方法之间的所有SQL查询均走主库 // some code, contains sql query router.end(); // end方法之后的SQL查询仍然是默认走从库 // some code }

@Cleanup表示不需要手动通过try-finally来关闭资源,

这个方案,还可以再进一步,比如可以创建一个注解类@ForceMasterQuery,在需要强制主库查询的方法上加上这个注解,然后通过AOP的方式,在方法执行前开启主库查询,方法结束后关闭主库查询。

posted on 2021-10-20 10:13  夜萤火虫和你  阅读(464)  评论(0编辑  收藏  举报

导航