洞察万象知学问:OB 4.x版本日志流深度剖析
首先为大家介绍一下 OceanBase 开源负责人老纪的公众号“老纪的技术唠嗑局”,该公众号会持续更新与#数据库、#AI、#技术架构相关的各类技术内容,欢迎有兴趣的朋友关注!
近期在与 DBA 进阶教程群及社区论坛的小伙伴交流时,不少人都提出了同一个疑问:究竟什么是“日志流”呢?
今日为大家分享庆涛大佬近期的一篇博客文章,借此来阐释 OceanBase 中这个较难理解的“日志流”概念。
本篇内容干货较多,大家可挑选感兴趣的部分进行阅读。若想直接切入主题,可从文中“多副本同步的粒度”部分开始阅览。
自 OB V4 版本推出“单机分布式一体化”架构后,衍生出了一个新概念“日志流”,这大概是 V4 版本里较难理解的概念之一,即便 OB V4 之前版本的用户也难免感到困惑。本文就来分享一下我对这一设计的理解。
日志流虽是新名词,但其要解决的问题却是数据库最基本且核心的问题,并非全新的问题。所以我先从数据库的基本原理说起。
数据库事务日志
数据库是用于存储数据的,但并非存储数据的唯一途径。在数据库诞生之前,文件就能够存储数据。
如今人们更倾向于将数据存于数据库而非文件,原因有诸多。其一,文件若损坏可能无法打开,致使数据丢失。其二,文件难以容纳大量数据,数据量过大时故障发生几率更高。当然,数据库关系模型与 SQL 语言的成功也是重要因素,但此处并非重点。
文件存储于文件系统中,文件损坏的一类原因往往是文件系统损坏。比如机器宕机重启时,文件内容尚在文件系统缓存里,突然掉电便会丢失。文件系统对此也有一些设计,例如写入元数据、写入文件系统自身的日志。机器重启后文件系统可自行扫描日志,开展一些数据恢复逻辑。这有一定作用,但并非每次都能奏效。偶尔仍会发现文件损坏,这依赖于文件系统的能力。数据库的文件大多也存于文件系统中(ORACLE 在 AIX 系统里的数据文件使用裸设备是例外情况),数据库的数据至关重要,显然不能仅依靠文件系统那点恢复能力。
所以数据库在写入数据文件之前,还会同步写入一份数据的修改日志,也就是 REDO 日志。这项技术被称为 Write Ahead Logging
技术。可以说所有的关系数据库都有这一设计。ORACLE 称之为 REDO 日志,PostgreSQL 称之为 pg_log
,MySQL 称之为 Innodb REDO
日志和 Binary
日志,SQLServer 称之为 ldf
文件。
这份事务日志对数据库至关重要,所以有些数据库的备份还需要备份这份日志。ORACLE 的归档日志便是对 REDO 日志的备份(随后 DBA 再进行二次备份归档日志),MySQL 的 Binary 日志也有归档设计。
传统数据库主备同步
事务日志的可靠性决定了数据库的可靠性。事务日志通常是实时写入磁盘(而非缓存至文件系统,MySQL 默认设置不可靠便是违背此原则)。
然而,若磁盘损坏,仍可能致使事务日志受损,强行打开数据库或许会丢失少量数据。所以数据库仍存在一定风险。应对此风险的办法是另寻一台服务器创建数据库的副本。所谓副本即数据完全相同。要确保副本数据一致需借助数据同步技术。新增副本时,可将原数据称作主副本,新数据称作备副本。主备副本间的数据同步是保证备副本数据一致的技术,此同步并非数据双写,而是将主副本的事务日志同步至备副本并在备副本上应用该日志。
ORACLE 的 Dataguard
技术、SQLServer 的 Mirror
复制技术都属于主备同步。它们的特点是对整个数据库进行主备同步,备库(即备副本)与主库的数据只会有延时,不会丢失。通俗来讲就是所有表都同步。MySQL 的主从复制也属于这类技术,但 MySQL 耍了些花样,可以定制表的同步。此外,MySQL 的 Binary Log
中记录的是数据变化的 SQL 或者数据行变化的 SQL,这使得 MySQL 的备库有一定概率与主库不完全一致。灵活是灵活了,但可靠性就降低了。不过这里说的还是同步的粒度,ORACLE 最小的同步粒度是数据库实例级别(ORACLE 没有细分数据库的概念),SQLServer 最小的同步粒度是数据库级别,MySQL 最小是数据库级别(只是可以设置哪些表同步或不同步)。
数据同步还有方向问题。ORACLE 和 SQLServer 的数据同步都是单向的主备同步。SQLServer 实例中包含多个数据库,两个实例之间不同数据库的同步方向可以不同。所以数据库级别是单向的,实例级别有可能是双向的。MySQL 最为灵活,数据库级别都可以双向同步(也就是通常所说的 Master-Master
同步。哪怕是同一个表都能够双向同步。同一个表双向同步最大的风险是数据交叉覆盖,这对业务是一场灾难。DBA 如果把控不了这种风险,还是别用双向同步为好。
MySQL 双向同步的能力在特殊场景下有很大作用。比如阿里巴巴电商业务数据三地多中心单元化多活架构中就大量运用,这是因为业务能够保证每个地区(单元)写入的数据互不冲突。例如用户数据,每个淘宝用户读写数据时可能都被路由到不同单元中心机房里的数据库。它实现了同一个表的同一记录写入点是单一的,表级别呈现出多点写入能力。ORACLE 和 SQLServer 的主备库都没有这一能力。但它们有自己的集群产品,数据读写也可以多节点进行,只不过数据存储是同一份(共享存储)。这是另一种解决方案。
多副本的副本
现在回归分布式数据库。所有分布式数据库都有两个共同点。一是数据存储采用分布式技术拆分,二是数据有多副本。前者数据拆分后会有多个分片。这里额外说一下副本概念和分片概念的区别,副本是 replica
,分片是 partition
。MySQL 的分库分表方案中的分表,对应这里的分片概念。ORACLE 的分区表的分区也对应分片的概念。分片概念属于分布式技术,本文不讨论分片概念,仅围绕副本概念进行解释。
需要注意的是,一般分布式数据库的多副本是在一个集群范畴内。ORACLE 的 Dataguard 部署的主备库通常不会被称为一个集群,因为主库和备库可以独立运维,并且备库可以临时不可用。主备之间是松耦合的。而分布式数据库里的主备是放在一个集群里讨论的。比如 OB 集群数据默认有三副本(前提是三节点部署)。整个 OB 集群里面不再区分主备实例或主备库,没有这种概念。不过 OB 集群里的数据会有主备副本的概念,这一般是讲述 OB 多副本原理时会提及的。这里的问题是这个副本到底对应着什么。这是理解日志流的第一个关键点,后续会进行阐述。
OB 从 V3 版本开始,常让人困惑的是 OB 在主集群里有多副本之外还弄出了一个备集群的概念,主备集群的关系类似于 ORACLE 主备库的关系。此时主备集群的同步就和 ORACLE Dataguard 一样是整体的集群级别。由于分布式数据库规模可能非常庞大,OB 从 V4 版本开始将这个同步粒度细化到租户级别,搞出了主备租户的概念。主备租户可以在同一个集群,也更倾向于在不同集群。所以 OB V4 集群不再有主备之说,只有租户有主备之说。备租户只能读不能写,类似 ORACLE Active Standby
。主备租户同步是单向同步,没有双向同步。备集群或备租户是客户高可用容灾场景的需要(特别是金融行业),所以 TiDB 集群就没有备集群或备库的概念。OBV3 主备集群之间平时是松耦合的,可以异构(机器类型异构、机器资源异构)。到 OBV4 后主备租户之间也是松耦合,可以异构的。日志流概念的解释是针对主租户内部的,备租户内的日志流无需关注,和主租户同理。所以后面就不再讨论 OB 备集群或备租户。
OB 主备租户

OB 集群三节点部署时,OB 租户一般也选择三副本部署,即都有三个 Zone 。TiDB 没有租户概念,可以把 TiDB 的实例类比为 OB 的租户。OB 和 TiDB 的多副本都有一个共同特征,就是只有一个主副本,其他全是备副本。并且总的副本数(包括主副本)尽可能是奇数。虽然偶数也行,但意义不大,这是由 OB 和 Paxos 的多副本同步选举协议决定的,都属于多数派选举协议。
OB 使用的 MultiPaxos
,TiDB 使用的是 MultiRaft
协议,这两个协议同出一源,都是 Paxos 协议,Raft 是 Paxos 的简化版。
多副本同步的事情自古就有,只是用协议概念提出是分布式数据库讲得比较多。MySQL 的主从同步机制也可以称为协议,只是没有一个专门的名字,或者叫异步同步比较接近。MySQL 后来还搞出了半同步。ORACLE 主备同步有最大性能、最大保护、最大可用等等。之所以有这么多名堂,都是为了尽可能保证备副本数据跟主副本一致。
为什么不能单一地选择强制保证所有备副本数据跟主副本一致呢,那样固然最安全,但性能最差,扩展性也最差。异步同步或最大性能是最不安全的,但性能最好。最大保护和半同步都属于只要有一个备副本跟主副本一致了就认为安全的,Paxos 和 Raft 多数派同步协议是认为半数以上副本成员数据一致了才是最安全的。可以简单理解为理论上性能和安全是相反的。
所以理论上同等规格同等测试场景下,OB 或 TiDB 的三副本架构下 OLTP 性能很难超过 MySQL 的一主两从的半同步协议下的性能。当然实际上还是有很多变数,取决于各个数据库的 SQL、事务、存储引擎的工程实现能力和效果。为什么有些国产数据库厂商性能 PK 中改个配置就能性能大幅提升呢,很可能改的就是这个同步设置(此外还有事务日志是否强制落盘)。
至此说清楚了多副本同步机制的原理,剩下就可以说这个副本的粒度了。
多副本同步的粒度
此副本所指范围可大可小。在 OBV4 版本之前,副本最大可涵盖一个 ZONE 内的所有数据。若集群有三个 ZONE,那就意味着所有业务数据有三个副本(前提是所有租户也采用三副本设置)。副本最小可至表的分区(即术语 partition
)。OB 的分区表与 ORACLE 和 MySQL 的分区表一一对应。
分区是比表更细致的粒度。普通表可视为单分区,分区表则是多分区。每个分区仅能存在于一个节点上,所以普通表(单分区)理论上存在容量可能超出单机存储容量的风险。这便是 OB 中超大表必须进行分区的其中一个缘由。
副本的粒度也代表了多副本同步的粒度,或者说数据同步的粒度。OBV4 版本之前可以针对租户、表设置 PRIMARY_ZONE
,即设置主副本的位置。若是租户级别设置,那么租户内所有表的 PRIMARY_ZONE
都沿用这个设置。一般设置为单个 Zone 时,就是所有业务表数据分区的主副本都在这个 Zone 的节点上,这就跟传统主备库特征类似。但 PRIMARY_ZONE
设置为两个或多个 Zone 时,就会有部分数据分区的主副本变换到其他 Zone 上。这种变换是分区粒度的主备副本角色切换,是 OB 高可用能力的微观体现。通俗来讲 OB 主备切换是分区粒度进行的,可以很多分区一起切换。这给业务带来的感受是 OB 故障切换时有些表先恢复有些表后恢复,而传统主备切换是同时恢复。OB 这种切换感受可能要好很多。不过弊端也有,切换粒度太细太耗费 CPU 资源,例如 OB V3 版本的 RTO 可能在 10s~30s
左右(这比传统主备切换分钟级别的 RTO 还是要好很多)。

OBV4 版本的改进是副本的概念还是不变,但多副本同步的粒度变粗了,做成一批数据副本的同步集中管理。那这一批副本到底是多少数据呢?取决于租户的 PRIMARY_ZONE
设置。
如果 PRIMARY_ZONE
设置为单个 ZONE
,那么这一批数据副本就指代全部业务数据。
如果 PRIMARY_ZONE
设置为两个 ZONE
,那这一批数据副本可以简单理解为一部分数据。
比如租户数据分布在 ZONE1,ZONE2,ZONE3
,PRIMARY_ZONE
设置为 ZONE1,ZONE2;ZONE3
时,则一部分数据同步方向是 ZONE1 -> ZONE2, ZONE1 -> ZONE3
,另一部分数据同步方向是 ZONE2 -> ZONE1, ZONE2 -> ZONE3
。
前面说过多副本同步实际上同步的是事务日志,OBV4 管理日志同步方向就是用日志流这个概念。日志流也可以有多副本,主副本到备副本的同步方向就是日志流的同步方向。数据分区的日志都会归属到日志流。一个日志流所辖的所有数据分区的同步方向都跟日志流的同步方向保持一致,以及日志流主副本所在的节点就是日志流所辖的所有数据分区的主副本所在的节点。
日志流
当租户的 PRIMARY_ZONE
设置的是单 Zone,那显然就只应该有一个日志流(有两个就是重复,目前看 OB 无意这样设计)。
但实际上用户真的看到了两个日志流并且方向还是一样的,那是因为 OB 专门把业务租户的 OB 元数据抽出来单独放一个日志流里进行管理,这个日志流 ID 是 1,而业务日志流 ID 是从 1001 开始。
当租户 PRIMARY_ZONE
设置的是多个 Zone 时,其用意也是数据主副本分散开,分散后自然就有多个同步方向了,那么就有了多个日志流。换句话说租户某个节点上只要有业务数据的主副本,那么上面必然有一个业务日志流的主副本,这样有多少个日志流就能算出来了。
很多人困惑于官网里日志流数量计算方法,被上面文字计算逻辑搞晕,就是因为没有理解这点。日志流是为了管理数据多副本同步方向用的,在实现上是先要计算好日志流的数量然后再随机分片数据分区归属于这个日志流。这是代码实现逻辑,官网上那段解释恰好是代码逻辑,所以不便于用户初次理解。

OB 还有一个特殊的业务场景,能加深对日志流的理解。OB 的复制表,其用意是为了避免表连接中出现跨节点的连接。一般基础配置表、静态表适合设置为复制表,这样在租户的每个节点上都会有这个表的副本。如果租户的拓扑是 2-2-2
,一共 6 个节点,那么复制表就有 6 个副本,其中 1 个主副本 5 个备副本,那么从主副本会有 5 个同步链路到备副本。这个同步协议叫全同步协议,所有副本都要同步成功(如果某个副本故障了,OB 会等待一段时间然后就超时退出,将故障副本提出了投票的群体。当然,如果后期故障副本恢复了,也是能自动请回桌面上)。复制表的同步显然跟前面的三副本多数派同步不一样,所以 OB 会单独配置一个日志流。
这听起来又让日志流数量计算复杂一点。实际上运维人员根本不需要关心有多少个日志流。运维人员只要着眼于 PRIMARY_ZONE
的设置即可,或者着眼于你是否需要数据的读写入口分散到两个 ZONE 或者多个 ZONE 上。
运维人员不需要关心哪些表归属于哪个日志流,就像不需要关心数据打散的时候哪些表的主副本在哪个节点上。因为分散位置是无法干预的,也不需要干预。如果要干预,只有一个特殊场景就是期望某些表的分区主副本能在一起,那就使用表分组技术。表分组不会改变日志流的数量,只会影响部分数据分区是否归属于同一个日志流。表分组的规则也很复杂,这是因为实际业务表的情形比较复杂(有些分区有些不分区,分区的表有些分区策略也不完全一致,分区还有一级分区二级分区的区别。这里面情况复杂得很)。
OB 论坛上还有个问题,单副本为什么要有日志流?其实也很好理解,多副本日志同步指的是事务日志在所有副本的同步和落盘,主副本所在节点的日志同步给自己是省了,落盘还是要做的。日志流是 OB 这个日志同步逻辑工程实现的设计,所以单副本依然会看到日志流。
总结
好的,至此日志流的相关内容已解释完毕。简单总结如下:
* 可将日志流视作租户数据日志同步方向管理的工程实现方案。
* 无需计算日志流