☰
读懂《The Log》第一部分:为什么日志是分布式实时数据的统一抽象
2026/10/8 1:42:23 网站建设 项目流程
  • 文档
  • 教程
  • 知识库

【免费下载链接】translations

🐼 Chinese translations for classic software development resources

项目地址:https://gitcode.com/gh_mirrors/tr/translations
点击查看免费下载

导读:本文基于本仓库收录的经典译文《日志:每个软件工程师都应该知道的有关实时数据的统一抽象》第一部分(part1-what-is-a-log.md),系统拆解"日志(Log)"这一贯穿数据库、分布式系统与实时数据处理的核心抽象。你将理解:为什么"追加写入、按序读取"的日志能定义分布式系统中的时间,如何借助状态机复制原理让多副本保持一致,以及"表与事件的二象性"为什么让日志成为比表更底层的数据结构。读完本文,你可以带着这套底层认知继续阅读本仓库中《The Log》系列的数据集成、实时流处理与系统构建章节,形成完整的知识闭环。

日志:最简单的存储抽象

原文对日志的定义非常凝练:日志是一种简单到不能再简单的存储抽象——只能追加(append-only)、按照时间完全有序(totally-ordered)的记录序列。它看起来就是上图那样一串从左到右排列的记录:

  • 写入:只在日志的末尾追加新记录,从不修改或删除已有记录;
  • 读取:从左到右顺序读取,先写入的记录先被读到;
  • 编号:每一条记录都拥有一个唯一、有序的日志记录编号(sequence number)。

次序即时间:日志与物理时钟的解耦

这是第一部分最关键的一个洞察:日志记录的次序(ordering)定义了"时间"概念——位于左侧的记录比右侧的更早,日志记录编号可以看作这条记录的"时间戳"。

刚开始把次序直接当成时间会让人觉得怪异,但这个做法有一个极其便利的性质:它把"时间"与任何一个特定的物理时钟(physical clock)解耦了。物理时钟在多机环境下不可靠:不同机器的时钟可能不同步、可能被 NTP 校准回拨,而基于日志次序的"逻辑时间"只依赖事件之间的先后关系,天然与机器无关。原文特别强调:引入分布式系统之后,这会成为一个必不可少的性质。

译注提示:分布式系统中的时间、次序、时钟概念是整个领域最基础也最根本的问题,关联文档中推荐了 Leslie Lamport 的论文《Time, Clocks and the Ordering of Events in a Distributed System》作为延伸阅读,建议先读完本系列再深入研究。

日志与文件、数据表的异同

原文用一个简单的类比消解了"日志很特殊"的错觉:

  • 文件是一系列字节;
  • 数据表(table)是由一系列记录组成;
  • 日志实际上就是一种按照时间顺序存储记录的数据表或文件。

三者在存储形态上并没有本质区别。真正不同的是日志的用途:日志记录的是"什么时间发生了什么事情",而"记录发生了什么"恰恰是分布式数据系统在许多方面要解决的真正核心问题。

需要提醒的一点:日志不可能无限追加,因为存储空间总会耗尽——原文明确指出会在后续章节(日志合并 log compaction)回来讨论这个问题。

先厘清概念:数据日志与应用日志

每个程序员都熟悉另一种"日志"——应用通过syslog或log4j写入本地文件的无结构错误信息或追踪信息。为了与本文讨论的日志区分,原文将前者称为应用日志记录(application logging)。

两者的核心区别在于面向的对象:

  • 应用日志:主要为了方便人阅读,是文本形式;
  • 数据日志(journal / data logs):用于程序的访问,是结构化、可被机器解析的记录。

原文甚至认为,应用日志是"日志"概念的退化形态:当系统涉及很多服务和服务器时,靠人去阅读散落在各台机器上的文本日志很快就会变得难以管理——我们的真实目的很快就变成"输入查询、输出用于理解多台机器行为的图表"。因此,文件中的字句文本几乎肯定不如本文所描述的结构化日志合适。

数据库中的日志:从 ACID 到复制的演进

起源与崩溃恢复

日志概念的起源已不可考——它可能像二分查找一样,简单到发明者不觉得这是一项发明。早在 IBM 的系统 R(System R)时代,日志就已出现。

日志在数据库中的最初用法是崩溃恢复:为了在崩溃时保持各种数据结构和索引的同步、保证操作的原子性(atomic)与持久性(durable),数据库在修改它维护的各种数据结构(表、索引等)之前,先把要做的更改操作信息写入日志。这就是通常所说的**预写日志(write-ahead log)**机制。

这里的核心思想值得反复咀嚼:日志记录了"发生了什么",而每个表或者索引都只是更改历史中的一个投影(projection)。由于日志会被立即持久化,一旦发生崩溃,日志就成为恢复所有其他持久化结构的可靠来源。

从 ACID 细节到复制机制

随着时间的推移,日志的用途从 ACID 的实现细节成长为数据库之间复制数据的一种方法。结论很直接:发生在数据库上的更改序列,正是与远程副本数据库(replica database)保持同步所需的操作。

原文给出了当时的工程事实:

  • Oracle、MySQL 和 PostgreSQL都包含日志传送协议(log shipping protocol),把日志传输给作为备库(slave)的副本数据库;
  • Oracle还把日志产品化为通用的数据订阅机制,为非 Oracle 数据订阅用户提供了XStreams与GoldenGate;
  • 在 MySQL 和 PostgreSQL 中,类似的设施是许多数据架构的关键组件。

正是由于这样的起源,机器可识别的日志概念长期以来被局限在数据库内部,日志作为数据订阅机制的用法似乎是"偶然出现"的。但原文指出:这恰恰是支持各种消息传输、数据流和实时数据处理的理想抽象——这一判断为后续三部分(数据集成、实时流处理、系统构建)埋下了伏笔。

分布式系统中的日志:排序与分发

进入分布式场景,日志的价值被彻底放大。原文明确:日志解决了两个问题——更改动作的排序(ordering)和数据的分发(distribution),而这两者在分布式数据系统中尤为重要。协商达成一致的更改动作顺序(或协商一致后去做有副作用的数据拷贝)正是分布式系统设计的核心问题之一。

状态机复制原理

分布式系统以日志为中心的方案来自一个简单观察,原文称之为状态机复制原理(State Machine Replication Principle):

如果两个相同的、确定性的进程从同一状态开始,并且以相同的顺序获得相同的输入,那么这两个进程将会生成相同的输出,并且结束在相同的状态。

拆解其中的关键词:

  • 确定性(deterministic):处理过程与时间无关,且不受任何"带外(out of band)"输入影响。例如,程序的输出如果受线程执行的具体顺序、getTimeOfDay调用或其他不可重复事件的影响,那它通常就是非确定性的;
  • 状态(state):进程保存在机器上的任何数据——处理结束时,这些数据要么在内存里,要么在磁盘上。

"以相同的顺序输入相同的内容"这句话应当触发你的条件反射:这个地方要引入日志。直觉上,如果给两段确定性代码相同的日志输入,它们就会产生相同的输出。应用到分布式计算中,结论顺理成章:把"用多台机器执行相同事情"的问题,化简为"用分布式一致性日志作为这些处理的输入"的问题。日志的目的是把所有非确定性的东西排除在输入流之外,以确保处理这些输入的各个副本(replica)保持同步。

(正如原文所说,一旦理解后你会发现,这个原理差不多等于"确定性的处理过程就是确定性的"——但它确实是分布式系统设计中一个更通用的工具。)

一个数字描述一个副本

这个方案有一个绝妙之处:用于索引日志的时间戳,可以充当保持副本状态的时钟。

因为每个副本只是按顺序消费日志,所以你可以只用"一个数字"来描述每一个副本——即该副本已处理的最大日志记录的时间戳。日志中的时间戳与副本的完整状态一一对应:知道了副本消费到哪条记录,就知道了它的完整状态。这把"描述整个副本状态"的问题压缩成了"维护一个偏移量"的问题,为后续讨论"可重放的历史记录"和"按各自速度消费"奠定了理论基础。

日志里记什么:物理日志与逻辑日志

根据写进日志的内容不同,状态机复制原理有不同的应用方式。理论上,可以记录:

  • 服务的输入请求日志;
  • 从请求到响应的服务状态变化日志;
  • 服务所执行的状态转换命令日志;
  • 甚至各副本执行的机器指令序列、方法名和参数序列。

只要两个进程以相同方式处理这些输入,副本进程就会保持一致状态。

不同领域对此有不同叫法。数据库工作者通常区分为:

  • 物理日志(physical logging):记录每一行被改变的内容;
  • 逻辑日志(logical logging):不记录被改变的行,而是记录引起行内容改变的 SQL 语句(insert、update、delete)。

两种复制模型:主-主(active-active)与主备(primary-backup)

分布式系统文献通常把处理与复制方案宽泛地分成两类:

  • 状态机模型(State Machine Model),常被称为主-主模型(active-active model):记录输入请求日志,各个副本各自处理每个请求;
  • 主备模型(primary-backup model):选出一个副本作为 leader,leader 按请求到达顺序处理请求,并输出它处理请求产生的状态变化日志;其他副本按顺序应用 leader 的状态变化日志,保持与 leader 同步,并能在 leader 失败时接替它成为新 leader。

为了理解两者差异,原文给出了一个经典例子——一个需要复制的"算法服务",维护一个初始值为 0 的独立数字,可对它进行加法和乘法运算:

  • 主-主方式:输出所进行的变换日志,比如+1、*2等。各个副本都应用这些变换,从而经过一系列相同的值;
  • 主备方式:由一个独立的 Master 执行这些变换,输出结果日志,比如1、3、6等。

这个例子清楚展示了为什么顺序是保证副本间一致性的关键:加法和乘法的顺序改变将导致完全不同的结果((1+1)*2 = 4与(1*2)+1 = 3不同)。

日志与一致性算法:Paxos、ZAB、RAFT 与 Viewstamped Replication

分布式日志可以看作建模一致性(consensus)问题的数据结构:因为日志代表了"下一个追加值"的一系列决策。

  • Paxos算法簇:你需要"眯起眼睛"才能在 Paxos 中找到日志的身影,尽管构建日志是它最常见的实际应用。Paxos 通过扩展协议multi-paxos来构建日志——把日志建模为一系列一致性值的问题,日志的每条记录对应一个一致性值;
  • ZAB(ZooKeeper 所用算法)、RAFT、Viewstamped Replication:这些协议中日志的身影明显得多,它们建模的问题直接就是维护分布式一致的日志。

原文还提出一个颇有远见的观点:在现实中,计算机系统几乎不需要决定"单个的值",而是要处理一序列的请求,因此日志(而不是简单的单值寄存器)是更自然的抽象。对算法的专注有时掩盖了系统底层所需的日志抽象——正如我们讨论哈希表时不会纠结于"用线性探测的 murmur hash 还是某个变种",日志终将成为一种大众化的接口,可以有多种竞争的算法和实现去提供最好的保证和最佳的性能。

延伸阅读:本仓库还收录了两篇与本节直接相关的一致性算法译文,可与本文对照阅读:Paxos Made Simple 中文翻译 与 PaxosLease:实现租约的无盘 Paxos 算法,前者给出多实例 Paxos(Multi-Paxos)的简洁描述,后者是"最简单且可以实际使用的 Paxos 算法变种"。

变更日志 101:表与事件的二象性

回到数据库场景,原文揭示了变更日志与表之间迷人的二象性(duality):

  • 日志类似借贷清单和银行处理流水;
  • 数据库表则是当前账户的余额。

如果有变更日志,你就可以应用这些变更生成数据表并得到当前状态;而表记录的是每条数据的最后状态——即日志在某个特定时间点的投影。

日志是更基本的数据结构

由此可以认识到:日志是更基本的数据结构——日志除了可用来创建原表,也可以用来创建各类衍生表(表也可以是非关系型用户使用的键值数据存储,keyed data store)。

这个过程还是可逆的:如果你对一张表进行更新,你可以记录这些变更,并把所有更新的"变更日志"发布到表的状态信息中——这些变更日志正是你所需要的、支持准实时复制的数据。

基于此,"表与事件的二象性"就变得清晰了:

  • 表支持了静态数据;
  • 日志记录了变更。

日志的魅力在于它是变更的完整记录:它不仅包含表的最终版本内容,而且可以用于重建任何存在过的其它版本。事实上,日志可以看作表每一个历史状态的一系列备份。

译注:二象性(duality)是个冷门词汇,常借自"光的波粒二象性"——数据在不同条件下分别表现出"表"与"事件"的性质。若觉得"二象"难以理解,可以理解为"对偶""互通""对称"或"可逆",即数据表和数据事件之间可以互相转化。

版本控制的类比

这可能会让你想到源代码版本控制(source code version control)——源码控制与数据库之间有着密切的关系:

  • 版本管理解决的是与分布式数据系统非常类似的问题——管理分布式、并发的状态变更;
  • 版本管理系统建模的是补丁序列(the sequence of patches),这实际上就是日志;
  • 你可以检出当前代码的一个"快照"直接操作,这个代码快照可以类比成表;
  • 正如有状态的分布式系统一样,版本控制系统通过日志来完成复制:更新代码即拉下补丁并应用到当前快照。

原文还提到,销售日志数据库的公司Datomic在其系统设计中应用了这些想法——但这些想法并非 Datomic 专属,相关理念在分布式系统和数据库文献中已沉淀多年。

面向后续:日志如何走向数据集成与实时处理

第一部分的理论内容可能略显抽象,但它是后续全部实战内容的地基。原文预告了剩下三部分的核心主题,本仓库均已收录:

  1. 数据集成(Data Integration)——让组织中所有存储和处理系统可以容易地访问组织所有的数据;核心方案是把所有数据提取到用于实时订阅的中心日志中;
  2. 实时数据处理——计算生成的数据流;"日志"就是"流"的另一种说法,日志是流处理的核心;
  3. 分布式系统设计——如何通过集中式日志的设计来简化实际应用系统。

所有这些用法,都是通过把日志用作一个独立服务来实现的。贯穿始终的底层逻辑是:日志的好处都来自它所提供的简单功能——生成持久化的、可重放的历史记录。原文特别强调了一个"令人意外"的事实:能让多台机器以确定性的方式、按各自的速度重放历史记录的能力,正是这些问题的核心。

如果你希望从更完整的视角进入这个主题,建议先阅读本系列的概述与译序,再依次读完数据集成、实时流处理与系统构建三个部分,最后可参考文末的学术论文、系统与开源软件清单继续深挖。

小结

第一部分的核心结论可以浓缩为五句话:

  1. 日志是只能追加、完全有序的记录序列,记录次序定义了时间,日志编号即时间戳,天然与物理时钟解耦;
  2. 日志与表只是同一枚硬币的两面:日志记录"发生了什么",表是更改历史的投影,日志是更基本的数据结构;
  3. 状态机复制原理让"以相同顺序向确定性进程提供相同输入"成为分布式一致性的通用解法,一个偏移量数字即可描述副本状态;
  4. 数据库的预写日志从 ACID 实现细节演进为复制机制,而分布式一致性算法(Paxos、ZAB、RAFT、Viewstamped Replication)的本质工作就是构建日志;
  5. 日志的核心能力是生成持久化、可重放的历史记录——这正是数据集成、实时流处理与分布式系统设计三大实战场景共同的地基。

把握住"日志即顺序、日志即时间、日志即历史"这三层含义,你就掌握了理解现代分布式数据系统的钥匙。

  • 文档
  • 教程
  • 知识库

【免费下载链接】translations

🐼 Chinese translations for classic software development resources

项目地址:https://gitcode.com/gh_mirrors/tr/translations
点击查看免费下载

相关推荐

上一篇:Open Generative AI深度技术指南:开源AI创作平台架构解析与实战部署
下一篇:模块化精准控制:重新定义桌面机械臂的开源方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询