• 作者:老汪软件技巧
  • 发表时间:2024-08-22 15:02
  • 浏览量:

读写分离是为了将对数据库的读、写分散到不同的数据库实例上。这样的设计并不一定是完美的。读写分离主要针对的是读多写少的场景,对于写多读少的场景就不合适了。比如,持久化落库就是一个写多读少的场景,多数情况是在不断的将数据记录到数据库,偶尔才需要去查询一下,相当于归档,大多数数据可能都不会被访问到。当然,大多数应用都有读多写少的特性,这也使得读写分离具有广泛的应用场景。

多数情况下,我们的读写分离都是采用一主多从的架构,也就是一台主数据库负责写入操作,其他数据库负责读取操作。主数据库和其他数据库之间会进行数据同步,这种同步是准实时的。既然是准实时,说明还是有一个时间差,这个时间差导致了主库和从库的数据不一致。

那么如何处理这种不一致情况呢?通常有 3 个思路:

显然,从库没有我想要的数据,我们就可以从主库读取,不同的框架都提供强制走主库的 API 支持。这是比较主流的一种做法,但是这种方法增加了主库的压力,相当于读写分离一定程度上失效了。

有一个不用动脑子的方法,就是让程序 sleep 一段时间之后再去读数据,通常主从之间的时间差不会差太大,都是毫秒级别的。

如何实现读写分离?

那么说了这么多,如何实现读写分离呢?其实很多现成的框架组件都帮我们做好了,无论使用哪个框架,它们的原理都是一样的,基本都是以下 3 步:

首先部署多个 MySQL 实例,选择其中一台作为主库;

保证主从数据库是准实时同步;

将写请求分给主库,读请求分给从库。

其中比较著名的是官方的 MySQL Router 以及阿里的 MyCat。它们都是独立部署的比较重要的中间件。实际上是在程序和 MySQL 中间加了一个代理层。应用程序将所有的请求都交给代理层处理,由代理层负责分发读写请求,将它们路由到对应的数据库中。

更常见的做法是直接引入一个 Jar 包,这个包允许你配置多个表,主从数据库地址等等,这样就不需要部署独立的中间件了,简化了开发和运维的成本,其中比较著名的是 Sharding-JDBC,这个比较推荐,很多大厂都在使用。

下面我们聊聊读写分离的核心——主从同步。主从同步主要是通过 MySQL binlog 日志来进行的,binlog 主要记录了 MySQL 数据库中数据的所有 DDL 和 DML 操作。因此,我们根据主库的 binlog 日志就可以把数据同步到其他数据库实例中。咱们贴一个来自国外 toptal 平台的文章《MySQL Master-Slave Replication on the Same Machine》的图:

上面的过程是这样的:

通过上面的分析,咱们可以很明显地看出来,由于 binlog 包含了所有 DDL 和 DML 操作,因此 binlog 除了可以用于主从复制,还可以恢复数据,是一个多功能的文件。上面提到的 I/O 线程和 SQL 线程可以看作是在从库上工作的 2 个流水线工人,I/O 线程负责原材料的运输,写入本地的 relay log 中,SQL 线程负责从 relay log 获取原材料并加工。

上面我们提到了 relay log 的格式和 binlog 非常相似,那么为什么需要 relay log 呢?relay log 中文翻译为“中继日志”,中继的意思是桥梁纽带,除了数据本身以外 relay log 内部还包含了一些子文件,这些文件记录了上一次读取到主库同步过来的 binlog 的位置,复制的进度以及连接主库的配置信息等等。

很多三方工具可以帮助我们实现 MySQL 和其他数据库或者另外一台 MySQL 数据库之间的数据同步。大多数情况下,这些三方工具的底层原理都是基于 binlog。它们的原理就是在模拟 MySQL 主从复制的过程,解析 binlog 将数据同步到其他的数据源。

除了 MySQL,比如咱们常用的分布式 NoSQL、缓存 Redis 等,也通过主从复制实现了读写分离。

总结

今天我们聊了 MySQL 的读写分离,读写分离几乎在所有大并发的场景得到了运用。主写从读已经成为一个很普遍的技术场景。读写分离给我们带来方便的同时,我们也要注意主从同步的延时。通常可以通过 API 强制走主库来避免这个问题,但是这就相当于没有做读写分离,更好的方案是在业务上避免这种操作,比如不要在插入之后立刻读取。

我们讨论了读写分离的原理和市面上常用的工具,虽然有很多中间件很有名,但是大厂还是用 Sharding-JDBC 最多,因为 Sharding-JDBC 轻量、几乎无侵入。最后我们了解了一下主从复制的流程,这个流程只有 4 步,非常简单。