MySQL读写分离原理及主从同步延时解决
1、 为什么要读写分离
高并发场景下,往往小部分数据在缓存中是读取不到的。
缓存里读取不到数据可分为两种原因:
- 缓存服务刚启动或只是缓存预热了部分数据。
- 缓存的内存塞满了,自动LRU ,删除了一些数据。
假如写请求1000/s ,读请求5000/s,有4000的读请求落到了缓存中,则有写请求1000/s + 读请求1000/s 落到了数据库中。如果突然请求量增加到写请求1000/s + 读请求3000/s ,那么数据库可能就危险了。
2、 如何实现MySQL 的读写分离?
就是基于MYSQL原生支持主从复制架构。简单来说,就搞一个主库,挂多个从库,然后我们就单单只是写主库,然后主库会自动把数据给同步到从库上去。
2.1 主从复制原理
主库将变更(增删改操作) 写入 binlog 日志,然后从库连接到主库之后,主库有一个IO 线程,读取binlog 日志。从库有一个 IO 线程,同步主库的binlog 日志,并写入从库的一个 relay 中继日志 中(这个过程是串行的)。接着从库中有一个 SQL 线程 会从中继日志读取 binlog日志(5.7.x版本后支持多线程并行读取),然后执行 binlog 日志中的内容,也就是在自己本地再次执行一遍 SQL,这样就可以保证自己跟主库的数据是一样的。
问题 :
这里有一个非常重要的一点,就是从库同步主库数据的过程是串行化的,也就是说主库上并行的操作,在从库上会串行执行。所以这就是一个非常重要的点了,由于从库从主库拷贝日志以及串行执行 SQL 的特点,在高并发场景下,从库的数据一定会比主库慢一些,是有延时的。所以经常出现,刚写入主库的数据可能是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。
而且这里还有另外一个问题,就是如果主库突然宕机,然后恰好数据还没同步到从库,那么有些数据可能在从库上是没有的,有些数据可能就丢失了。
2.2 核心问题之主从延迟问题产生的原因
从库同步主库数据的过程是串行化的,主库上可并行操作更改主库数据并记录binlog日志。由于从库从主库拷贝日志以及串行执行 SQL 的特点,在高并发场景下,从库的数据一定会比主库慢一些,是有延时的。所以经常出现,刚写入主库的数据可能是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。
压力测试 :
写请求1000/s-2000/s 大概有10 - 30ms左右的延迟。
写请求4000/s - 5000/s 大概有1 - 3秒左右的延迟。
2.3 核心问题之主从复制的数据丢失问题
如果主库突然宕机,然后恰好数据还没同步到从库,那么有些数据可能在从库上是没有的,有些数据可能就丢失了。
通过半同步复制,强行binlog和中继relay日志同步才算主库的一次操作成功,即ack确定。
2.4 半同步复制的原理
- 半同步复制
semi-sync
策略
主库写入 binlog 日志之后,就会将强制此时立即将数据同步到从库,从库将日志写入自己本地的 relay log 之后,接着会返回一个 ack 给主库,主库接收到至少一个从库的 ack 之后才会认为写操作完成 。
2.5 并行复制的原理
多库并发重放relay中继日志,缓解主从延迟问题。从库开启多个SQL线程,并行读取 relay log 中不同库的日志,然后并行重放不同库的日志,这是库级别的并行。
3、主从延迟问题导致的生产环境的问题
3.1 主从延迟场景概述
场景1 :如果1秒的写请求量很大,在写入后马上查询可能会存在查询不到。比如1万个用户领了消费券,然后这其中有几千个用户马上使用消费券支付订单。这时可能就存在有的用户页面显示,没有消费券可用(刚刚已经领了)。由于主从延迟的问题,导致在关联查询用户可用消费券数据的时候,从表中还有部分消费券的数据没有从主表中同步过来,没有查询到可用消费券。
场景2 :以前线上确实处理过因为主从同步延时问题而导致的线上的 bug,属于小型的生产事故。有个同学是这样写代码逻辑的。先插入一条数据,再把它查出来,然后更新这条数据。在生产环境高峰期,写并发达到了 2000/s,这个时候,主从复制延时大概是在小几十毫秒。线上会发现,每天总有那么一些数据,我们期望更新一些重要的数据状态,但在高峰期时候却没更新。用户跟客服反馈,而客服就会反馈给我们。
3.2 查看MySQL服务器的状态信息
通过 MySQL 命令来查看服务器的状态信息 :
SHOW STATUS
查看 Seconds_Behind_Master
这行的服务器状态信息,可以看到从库复制主库的数据落后了几ms。
执行SHOW STATUS命令时,会得到300多条状态信息,这种情况下,如果直接用这个命令,从中筛选需要的记录也是一个很耗费精力的事情。这时候,就可以使用 LIKE 来定制我们的命令。
查看当前服务器启动后的运行时间 :
show status like 'uptime';
得到如下结果
查看MYSQL服务器的其他状态信息
3.3 主从延迟解决方案
如果主从延迟较为严重,有以下解决方案:
- 分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计。 (即减少binlog同步日志)
- 打开 MySQL 支持的并行复制,多个库并行复制。如果说某个库的写入并发就是特别高,单库写并发达到了 2000/s,并行复制还是没意义。
- 重写代码。插入数据时立马查询可能查不到。比如尽量避免插入后就马上读。
如果 强行要求 :必须插入后,立马要求就能查询到 ,有以下解决方案:
- 通过数据库中间件,设置读写直连主库。不推荐这种方法,这么搞导致读写分离的意义就丧失了。
- 如果基于有这样的业务要求。不要试图在数据库层解决并发的读操作问题,至少不要在主从架构的数据库层解决。要在数据库层之上架构一个redis这样的分布式缓存来解决,它是专门干这个的。其性能肯定远高于从备机读取数据。
- 基于redis 先缓存一份这样的数据,同时进行MYSQL的数据操作。