这个时候,我们立马开启了排查问题模式,首先,check了一下上次加的缓存,发现缓存正常,然后接着根据DBA提供的信息搜索日志,此时,发现在这个时间段订单请求量突增,大概是平常订单请求量的2到3倍,然后经过了解,发现在这个时间段内,营销系统那边做了一些活动,导致订单请求量突增。
说白了就是做了促销活动后,大量下单的用户会不断刷新订单来查询订单的信息,比如看一下订单是否开始配送,此时大量的请求会打到了MySQL上去,此时单库又抗不了这么读请求,就导致了数据库负载很高,从而严重降低了MySQL的查询效率。
现在我们缓存也加过了,但是数据库负载还是很高,此时该怎么办呢?
其实也很简单,既然单个库扛不住,那就搞2个库一起来抗呗,因为对于外卖订单来说是典型的读多写少的场景,所以,在这个场景下,我们可以搞个一主两从的架构来进行优化,就像这样:
也就是写数据走主库,而读数据走从库,可以看到,此时由于我们搞了2个从库,这2个从库可以一起来抗大量的读请求。
非常关键的一点就是,从库会通过主从复制,从主库中不断的同步数据,以此来保证从库的数据和主库是一模一样的,所以想要实现读写分离,那么,就先要了解主从复制是怎么玩儿的。
主从复制的原理是什么?
我们以mysql一主两从架构为例,也就是一个master节点下有两个slave节点,在这套架构下,写请求统一交给master节点处理,而读请求交给slave节点处理。
为了保证slave节点和master节点的数据一致性,master节点在写入数据之后,同时会把数据复制一份到自己的各个slave节点上。
在复制的过程中一共会使用到三个线程,一个是binlog dump线程,位于master节点上,另外两个线程分别是I/O线程和SQL线程,它们都分别位于slave节点上,如下图:
结合图片,我们一起来看下主从复制的核心流程:
(1)当master节点接收到一个写请求时,这个写请求可能是增删改操作,此时会把写请求的操作都记录到binlog日志中。
(2)master节点会把数据复制给slave节点,如图中的slave01节点和slave02节点,这个过程,首先得要每个slave节点连接到master节点上,当slave节点连接到master节点上时,master节点会为每一个slave节点分别创建一个binlog dump线程,用于向各个slave节点发送binlog日志。
(3)binlog dump线程会读取master节点上的binlog日志,然后将binlog日志发送给slave节点上的I/O线程。
(4)slave节点上的I/O线程接收到binlog日志后,会将binlog日志先写入到本地的relaylog中,relaylog中就保存了binlog日志。
(5)slave节点上的SQL线程,会来读取relaylog中的binlog日志,将其解析成具体的增删改操作,把这些在master节点上进行过的操作,重新在slave节点上也重做一遍,达到数据还原的效果,这样就可以保证master节点和slave节点的数据一致性了。
主从复制的有几种模式?
mysql的主从复制,分为全同步复制、异步复制、半同步复制和增强半同步复制 这四种。
全同步复制
首先,全同步复制,就是当主库执行完一个事务之后,要求所有的从库也都必须执行完该事务,才可以返回处理结果给客户端;因此,虽然全同步复制数据一致性得到保证了,但是主库完成一个事物需要等待所有从库也完成,性能就比较低了。
异步复制
而异步复制,当主库提交事物后,会通知binlog dump线程发送binlog日志给从库,一旦binlog dump线程将binlog日志发送给从库之后,不需要等到从库也同步完成事务,主库就会将处理结果返回给客户端。
因为主库只管自己执行完事务,就可以将处理结果返回给客户端,而不用关心从库是否执行完事务,这就可能导致短暂的主从数据不一致的问题了,比如刚在主库插入的新数据,如果马上在从库查询,就可能查询不到。
而且,当主库提交事物后,如果宕机挂掉了,此时可能binlog还没来得及同步给从库,这时候如果为了恢复故障切换主从节点的话,就会出现数据丢失的问题,所以异步复制虽然性能高,但数据一致性上是较弱的。
mysql主从复制,默认采用的就是异步复制这种复制策略。
半同步复制
半同步复制,顾名思义就是在同步和异步中做了折中选择,我们可以结合着MySQL官网来看下是半同步主从复制的过程,来看下这样图:
当主库提交事务后,至少还需要一个从库返回接受到binlog日志,并成功写入到relaylog的消息,这个时候,主库才会将处理结果返回给客户端。
相比前2种复制方式,半同步复制较好地兼顾了数据一致性以及性能损耗的问题。
同时,半同步复制也存在以下几个问题:
- 半同步复制的性能,相比异步复制而言有所下降,相比于异步复制是不需要等待任何从库是否接收到数据的响应,而半同步复制则需要等待至少一个从库确认接收到binlog日志的响应,性能上是损耗更大的。
- 主库等待从库响应的最大时长是可以配置的,如果超过了配置的时间,半同步复制就会变成异步复制,那么,异步复制的问题同样也就会出现了。
- 在MySQL 5.7.2之前的版本中,半同步复制存在着幻读问题的。
当主库成功提交事物并处于等待从库确认的过程中,这个时候,从库都还没来得及返回处理结果给客户端,但因为主库存储引擎内部已经提交事务了,所以,其他客户端是可以到从主库中读到数据的。
但是,如果下一秒主库突然挂了,就像这样图一样:
此时,下一次请求过来,因为主库挂了,就只能把请求切换到从库中,因为从库还没从主库同步完数据,所以,从库中当然就读不到这条数据了,和上一秒读取数据的结果对比,就造成了幻读的现象了。
增强半同步复制
最后,增强半同步复制,是mysql 5.7.2后的版本对半同步复制做的一个改进,原理上几乎是一样的,主要是解决幻读的问题。
主库配置了参数 rpl_semi_sync_master_wait_point = AFTER_SYNC 后,主库在存储引擎提交事物前,必须先收到从库数据同步完成的确认信息后,才能提交事务,以此来解决幻读问题。
可以参考下MySQL官网是怎么描述增强半同步主从复制过程的:
主从延迟问题和常规解决方案
主库写入的速度是很快的,因为主库是多线程并发写入的,但是,从库是单线程从主库拉取数据的,所以从库从主库复制数据的速度,就比较慢了,从而产生了主从延迟的问题。
mysql 从 5.6版本开始,就支持多线程复制,但是5.6版本是基于库级别去操作,也就是说会给每个数据库开启一个线程,不同库处理时在同一时间内是互不影响的;但是,当业务的压力集中到一个库时,又会回到和单线程复制一样的状况了。
直到mysql 5.7版本,开始引入了基于组提交(group_commit)的概念,这个时候才 真正 支持多路复制功能,官方称为enhanced multi-threaded slave(简称MTS),所以,推荐大家尽可能选择MySQL 5.7之后的版本。
而主库挂载的从库数量过多,也会导致主从复制延迟的问题,一般我们是建议一个主库挂载从库的数量,在3~5个比较合适。
另外,我们执行的SQL语句中,如果慢SQL语句过多,也会导致主从复制延迟,比如,我们工作中会遇到批量插入的场景,如果一批插入的数据量过大,就容易造成执行时间过长。
假如,从执行完一份 批量插入数据的SQL语句开始,到在从库上能查到这些数据的这个过程中,如果耗费了10秒,就导致主从库之间就延迟10秒了;所以,SQL优化会是一个常态化的工作,可以通过慢SQL日志或监控平台监控慢SQL,如果单个数据写入时间过长的话,可以将一批数据分片分批次写入。
最后,如果出现网络延迟或者机器的性能比较差,也会导致主从复制延迟的问题,这种情况没什么可说的,及时优化网络提升机器性能就行了。
读写分离实战
读写分离配置核心组件流程图:
| 读写分离配置步骤
(1)配置文件中配置主从库连接信息
(2)注入数据源
(3)数据源切换上下文,其中使用了ThreadLocal保存当前线程的数据源
(4)继承AbstractRoutingDataSource类重写determineCurrentLookupKey方法实现数据源动态切换
(5)创建读库的自定义注解
(6)切面类
(7)需要走读库的业务方法上添加@ReadOnly注解,那么执行这些业务方法时就会被切面拦截修改数据源从而走读库进行查询。
(8)写主库、读从库的效果
1)生成订单
2)查询订单