上文我们介绍过了读写分离。其实基于MySQL的主从复制实现读写分离的方案,最怕遇到的就是主从延迟。
因为写是发生在主库上的,而读是基于从库的,一旦主从之间数据复制出现延迟了,就会出现刚写入的数据读不到的问题。
正常情况下,MySQL的主从延迟都是非常小的,一般都不超过 1ms。但是在极端情况下也会出现查不到的情况。所以,我们需要想办法解决这个问题。
一般来说,为了减少主从延迟带来的影响,我们在实现读写分离时,可以采用以下几种方案做优化。
一般来说,虽然我们做了读写分离,但是也不是无脑分的,我们还会把读请求分成两类,一类是可以接受延迟的读,一类是不能接受延迟的读。
比如历史订单的查询、比如数据报表的生成、比如数据对账的查询、比如非关键业务的查询,比如评论信息等,这些都是可以延迟的读,这些读的话就可以完全从备库走。
而对于那些不能接受延迟的读,那么就需要注意了,就需要考虑进行强制读主库。
这种方案其实是用的比较多的,不要以为他是逃避了问题,有的时候,没必要给自己创造困难硬上!
上面我们提到了对于一些不能接受延迟的读请求,需要强制走主库。
还有一些情况,那就是一些核心的业务操作,或者是在一个事务上下文中的读请求,这时候也需要读主库的。
比如说我在创建订单的过程中,我会先插入一个订单,然后再查询订单信息进行后续操作,这个过程中,是要保证数据一定能查到的,这时候就也需要强制走主库。
具体如何实现强制读主库呢,如果是我们前面介绍的通过自己写代码分流的方案的话,就比较容易了,我们可以自己控制读写哪个数据源,那么就自己硬编码就好了。
如果是使用我推荐的中间件的方案的话,比如ShardingJDBC,他也是支持强制路由的(https://shardingsphere.apache.org/document/legacy/3.x/document/cn/manual/sharding-jdbc/usage/hint/ ),可以通过设置hint的方式让SQL只操作主库。
除了上面我们说的强制读主库的方案,还有一个常见做法叫做二次读取。
啥意思呢,就是我的读取操作,默认读从库,但是如果我从库读取的时候没读到,那我为了避免因为数据延迟导致的,那么就再进行一次从主库读取。
这个实现方式的话也是需要我们定制的开发代码。但是这个方案我不太建议,因为这种一旦出现延迟,也会导致你的主库会有大量的请求过去,造成很大的压力的。
除了上面说的方案之外,还有一些场景中,是采用了一些特殊的手段,来确保主备一致。
比如在极客时间的《MySQL 45讲》中,作者提到过一些方案(但是其实用的都不多,还是前面说的几个方案更多一点):
Sleep方案:就是主库更新之后,读从库之前先sleep 1秒,然后再读从库。
判断主备无延迟方案:每次从库执行查询请求前,先判断 secondsbehindmaster 是否已经等于 0。如果还不等于 0 ,那就必须等到这个参数变为 0 才能执行查询请求。
等主库位点方案:其核心思想是在从库上执行读操作前,确保从库已经同步了特定的主库位点(即主库的数据变更位置)。这样可以保证读操作获取的数据是最新的,避免了因主从复制延迟而导致的数据不一致问题。
等 GTID 方案:和位点原理一样,MySQL 5.7.6 版本开始,允许在执行完更新类事务后,把这个事务的 GTID 返回给客户端,在执行读操作前,应用检查从库是否已经应用了该GTID标识的事务。这通常涉及查询从库的复制状态,确认已经处理的GTID集合包含了特定的GTID。
GTID(Global Transaction Identifier)为每个事务提供了一个全局唯一的标识符,使得主从复制过程中的数据变更能够更加精确和容易追踪。