专栏名称: SegmentFault思否
SegmentFault (www.sf.gg)开发者社区,是中国年轻开发者喜爱的极客社区,我们为开发者提供最纯粹的技术交流和分享平台。
目录
相关文章推荐
程序员的那些事  ·  清华大学:DeepSeek + ... ·  昨天  
程序员的那些事  ·  印度把 DeepSeek ... ·  昨天  
程序员小灰  ·  清华大学《DeepSeek学习手册》(全5册) ·  昨天  
OSC开源社区  ·  2024: 大模型背景下知识图谱的理性回归 ·  2 天前  
程序员小灰  ·  DeepSeek做AI代写,彻底爆了! ·  4 天前  
51好读  ›  专栏  ›  SegmentFault思否

日志 —— 每个开发者需要了解的实时数据聚合

SegmentFault思否  · 公众号  · 程序员  · 2019-11-11 12:12

正文

SegmentFault 社区专栏:眯眯眼猫头鹰的小树杈
译者:raledong



No.1

前言



我在大约六年前的一个较为巧合的时机加入了领英。 当时我们正面临着单机应用,集中式数据库带来的挑战,并开始将其迁移成一组定制的分布式系统。 这是一段很有趣的经历: 我们构建,编译并运行了一套分布式图形数据库,一个分布式的搜索后台应用,一套 Hadoop 和一代与二代键值数据库。

在此期间,我体验最深的一件事情是,这些工具的本质是日志。这些日志可以是预写式日志 (write-ahead logs) ,可以是提交日志或是事物日志。日志基本是所有分布式数据系统和实时应用架构的核心。

你很难脱离日志体系来透彻的了解关系型数据库,非关系型数据库,键值数据库,备份,paxos,hadoop,版本控制或是任何软件系统。 但是,仍然有不少软件工程师对日志并不熟悉。 我希望能够改变这一点。 在这篇文章中,我会带你学习日志所必须知道的一切,包括什么是日志以及如何使用日志进行数据集成,实时数据处理和系统构建。


No.2

第一部分:什么是日志



日志可能是最简单的存储抽象形式。 它只支持添加式写入,完全时间有序。


日志被添加到图片的末尾,并且按照从左往右的顺序读取。 每一条日志有唯一的顺序的日志编号。

日志记录的顺序隐藏了时间的属性,因为左边的日志默认要“老于”右边的日志。 每条日志的编号可以视作日志的时间戳。 从时间的角度来形容日志编号乍一看有点奇怪,但是它使得日志和任何物理时钟节耦。 在分布式系统中,该属性是至关重要的。

日志条目的内容和格式在本文汇总并不重要。 同样,我们也不能无限的往日志中新增内容,因为它会占据所有的空间。 稍后我会回来讨论这一点。

所以,日志并非完全不同与文件或是表格。 文件是一组二进制编码的,而表格是一组记录,而日志就是一种内容按照时间排序的表格或是文件。

至此你可能会想这么简单的东西有啥值得说的? 一个只支持添加式的记录和文件系统有关系? 答案是日志有一个特殊的目的: 它们记录发生的事件及其发生的时间。 从各个角度看来,这都是分布式文件系统中很多问题的本质所在。

但在深入解释之前,让我先将一些容易混淆的概念解释一下。 每个开发者都熟悉日志的另一个维度的定义: 即记录一个应用的无结构的错误信息或是错误路径,并使用 log4j 或是 syslog 写入本地文件系统中。

应用日志是本文中描述的日志概念的降级形式。 二者之间最大的区别在于文本日志主要是为了开发者来阅读,而本文描述的系统/数据日志主要是由程序来访问并解析。

(事实上,如果仔细想一想,人肉阅读单机上的日志的想法并不高明。当很多服务参与进来,这种方式很快变得难以管理,而日志就变成了打印入参出参以了解跨机器调用行为的工具。此时文本式的日志并不是最合适的形式)


数据库中的日志


我不知道日志的概念起源于何处--也许就和二分查找法一样,因为过于简单而并没有被发明者视为是一个创举。 早在 IBM 的 R 系统中它就已经存在了。 数据库中使用日志实现在应用崩溃时能够同步各类数据和索引。

为了保证操作的原子性和持久性,数据库会在执行具体的变更之前,将需要修改的数据先写入日志中。 日志记录了发生的事件,而每个表格/索引将这些历史变更映射为当前的数据结构或索引。 因为日志的实时持久化的,因此它被视为在系统崩溃时对其它持久化数据结构进行恢复的权威标准。

随着时间流失,日志从最开始 ACID 的事务实现方式,进化成了数据库之间数据备饭的工具。 事实证明,对数据库执行的一系列变更记录,正好可以用于同步远程的备份数据库。 Oracle,MySQL 和 PostgreSQL 使用日志传输协议将部分日志发送到备份数据库。

息传输,数据流和实时数据处理场景是非常合适的。

分布式系统中的日志


日志解决的两个问题: 顺序变化和分布式数据,在分布式数据系统中更为重要。 对更新操作的顺序达成一致是这些系统进行设计时的核心问题。

这种在分布式系统中以日志为中心的思想来源于一个简单的观察,我将其称为状态机备份理论 ( State Machine Replication Principle)

如果两个相同的,确定的进程以同一个状态开始,并以同样的顺序传入相同的输入,那么它们会输出相同的结果并以相同的状态结束。

这看上去有点晦涩,让我们深入理解其含义。

确定性是指该过程和时间无关,并且不会让任何外带输入影响其结果。 比如一个程序的输出,受特定的线程执行顺序,或是调用获取当前时间 API 或是其它非可重复的操作的影响,则将其视为非确定性的。

进程的状态是指在程序执行完毕后,任何在内存中或是磁盘上存留下来的数据。

需要注意按照相同的顺序传入相同的输入--这是日志参与的部分。 这是一个非常启发性的理念: 如果你向两段确定性的代码片段传入相同的输入日志,它们会产生相同的输出。

分布式计算的应用显而易见。 你可以将原先使用多台机器做同一件事情,转化为实现一个分布式一致性日志来给这些进程相同的输入流。 这里使用日志的目的是将输入流中的所有不确定性排除掉,来确保每个备份可以使用这个输入流进行同步。

一旦你理解了这一点,这个理念就不再复杂了: 它等于是说确定性的处理得出的结论也是确定的。 尽管如此,我认为它是一个更加通用的分布式系统设计的工具。

这个方法的优点之一是用来索引日志的时间戳同样可以用来描述备份的状态--你可以用该备份处理过的最大日志编号来描述该备份的状态。 这个时间戳结合日志可以唯一的描述整个备份当前所处的状态。

在系统中使用这个思想的方式有很多,具体采用哪种方式取决于将什么内容写入了日志。 比如,我们可以将调用服务的请求,或是服务在响应请求后状态的变化,或是它执行的转换指令写入日志。 理论上来说,我们甚至可以将每个备份执行的机器指令以及或是方法与参数名写入日志中。 只要两个进程用同样的方式处理同一组输入,这些进程就可以在不同的备份中保持一致的状态。

不同的人群会用不同的方式描述日志。数据库开发者会区分物理日志和逻辑日志。物理日志是指记录每一行内容变更的日志。而逻辑日志是指不仅记录内容的变更,还会记录导致内容变更的 SQL 日志 (插入,更新和删除语句)

分布式系统语义下,通常区分两种处理和备份的通用方法。 状态机模型通常指我们将一组输入请求写入日志,而每个备份处理这些请求。 对于这个模型进行略微修改的一个模型称为基本备份模型,它将一个备份选为主机器,并且允许这个主机器按照请求到达的顺序逐个处理请求,并将处理后的状态变更写入日志中。 其它的备份同步主机器的状态变更,这样当主机器崩溃后,其它机器可以重新选举出新的主机器并替代它。


要了解二者之间的区别,先看一个简单问题。假设现在有一个多备份的“计算服务”,它保存了一个数字作为其状态 (该数字初始化为 0) ,并且对该值执行加减乘除操作。

状态机模型会将所有的变化操作写入日志,比如“+1”,“*2”等。而基本备份模型则会让一个主节点执行计算操作,并记录计算后的结果,如“1”,“3”,“6“。这个例子也证明了为什么顺序性是在备份之间保持一致性的关键:加法和乘法操作的乱序会产生不同的结果。

其实我觉得,由于历史原因,我们关于日志产生了一些偏见,也许是因为最近几年分布式计算的理论超越了其实际应用。 在现实世界中,一致性问题被看的简单了。 计算机系统很少会对单个值执行一致性计算,它们通常需要处理一系列请求。 因此日志,相比于简单的单值寄存器,是更常见的抽象。

不仅如此,这些分布式一致性算法,如 Paxos,Raft 等,并不太关注底层的日志抽象系统。 因此,我们会将日志视为一个商业化的构建模块,而不会关心其具体的实现细节。 就像是当我们讨论哈希表时,不会关心我们是通过线性探测法或是其它的方式来进行冲突解决的这种细节。 日志就像是一个通用的商业化接口,它底下隐藏了很多的算法和实现,来确保提供最优性能。

变更日志 101:表格和事件的互补性


再回到数据库。关于变更日志和表格有一个很特别的互补性。日志就像是银行处理的所有的借款和还款的记录,而表格则记录了当前账户的余额。如果你记录了变更日志,就可以通过执行这些变更来创造出捕获当前状态的表格。这个表格会记录每个健的最终状态 (或者说日志的一个时刻) 。因此日志是更基础的数据结构,除了来创造最终表格,还可以通过它创造各种衍生的表格。


这个过程也可以反过来执行: 如果你有一个需要执行更新操作的表格,你可记录这些变更并发布一个包含所有变更的变更日志给当前的表格。 这个变更日志就是需要提供给需要提供给近实时同步的备份的数据信息。

因此,从这个角度来看,二者是互补的: 表格捕获数据的当前状态,而日志捕获变更。 日志的魅力在于,如果它捕获了完整的变更日志,它不仅能够生成最终版本的表格,还能够生成任意其它版本的表格。 它是对表格过去的每一个状态的有效备份。

这可能会让你想起源代码版本控制。 版本控制和数据库之间存在紧密的关联。 版本控制和分布式数据库系统解决的问题很类似: 管理状态上分布的,并发的变化。 版本控制系统通常将一组修补变更进行建模,本质上也是一条日志。 开发者与代码的一个快照进行交互,而快照就类似于表格。

和别的分布式有状态的系统一样,版本控制中的备份通过日志来实现: 当你更新代码仓库时,其实是将所有代码变更拉取下来,并将它们依次执行到当前的快照上。

接下来的内容


在本文接下来的内容中,我会试着以超出分布式计算或抽象的分布式计算模型之外的领域来说明日志的好处。 包括:

•  数据集成: 使各个组织的数据可以在各个存储介质和处理系统中快速访问
实时数据处理: 基于数据流的计算
分布式系统设计: 如何通过日志中心的设计思想简化系统设计

这些应用都围绕着将日志作为独立服务的思想来实现。

在每个场景下,日志的易用性均来自于其提供的简单功能: 提供一个持久的,可重现的历史记录。 令人惊讶的是,上述这些问题的核心都在于支持每台机器需要能够按照自己的处理速度来处理确定的历史记录。


No.3

第二部分:数据集成



数据集成是指支持所有的服务和系统均能够访问该组织机构的所有数据。

数据集成并非通用概念,但是我找不到更加合适的术语。 而另一个相对而言更加具有识别度的概念 ETL 只包含数据集成中的一部分操作--生成一个关联型的数据仓库。 对于数据集成,你并没有听到太多关于大数据概念的令人窒息的炒作,但尽管如此,我相信“使数据可用”这个平凡的问题是任何一个组织机构可以关注的更有价值的事情之一。

数据的有效使用某种程度上遵循了马斯洛的需求层级模型。在金字塔的底端涉及了捕获所有相关的数据,并能够将其放在一个可用的访问环境中 (比如一个实时访问系统或是文本文件或是 python 脚本) 。这个数据需要用通用的方式建模,使其易于读取和处理。当这些通用的数据捕获能力建设好之后,才能够通过工具用各种方式处理这些数据--如 MapReduce,实时查询系统等。

需要注意一点: 没有一个可靠且完整的数据流的话,Hadoop 集群无异于一个昂贵且难以使用的工具。 只有当数据和处理可用后,开发者才能将关注点移动到如何建好数据模型和易于理解的语境。 最后,关注点可以移动到更复杂的处理,比如可视化,报表和使用算法进行处理与预测。

从我的过往经验看来,很多公司在基础的数据收集上存在很大的缺陷。 它们缺乏可靠的完整的数据流,却想要直接实施高端的数据建模技巧。 这完全是一种倒退。

所以问题在于,我们如何能够横跨公司的所有数据系统,构建一个可靠的数据流?

数据集成的两个复杂点


事件数据井喷


数据的第一个趋势是时间数据的增加。 事件数据记录了发生了什么事情,而非事情本分。 在 web 系统中,这可以是用户的行为日志,也可使机器级别的统计数据,用来监控数据中心的可靠性。 人们倾向于将这些称为日志数据,因为它们通常被写入应用日志中,但它会混淆形式和功能。 其实这些数据在现代互联网处于核心地位。 毕竟谷歌的财富,就是通过点击和事件之间的关联创造的。

而这并不只局限于互联网公司,只是因为互联网公司已经电子化,因此这些数据更好管理。 金融数据一直以来也是事件为中心的。 RFID 将这种跟踪添加到物理对象中。 我认为,随着传统商业的数字化,这一趋势将继续下去。

这种类型的事件数据记录发生了什么,并且通常比传统的数据库的数据量大几个量级。 这意味着处理上带来了巨大的挑战。

特殊的数据系统的爆发式产生


第二个趋势是面向特殊场景的数据系统逐渐流行起来并且近五年来被广泛使用。 这些数据系统包括 OLAP,Elastic Search,批处理系统 Hadoop,图形系统 graphlab 等等。

各种类型数据的组合以及将这些数据流入各种类型的系统给数据集成带来了巨大的问题。

日志结构的数据流


日志是处理系统间数据流的通用数据结构。 它的思路很简单: 将公司的所有数据收集起来放入日志中心,并提供实时的数据订阅。

每一个逻辑数据源可以视为独立的日志。数据源可以是应用将事件 (如点击或页面浏览) 打印的日志,或是数据库表格的变更操作日志。每一个订阅系统尽可能快速的阅读这些日志,将这些日志应用于自己的数据上,并继续推进日志的阅读。订阅者可以是任何数据系统--缓存,Hadoop,其他地址的数据库,搜索引擎等。



比如,日志的概念使得每一个订阅者执行的变更进度可以通过逻辑时钟度量。 它通过每个订阅者当前订阅的时间节点进度,简化了不同订阅者彼此之间的状态比较。

想象一个简单的场景,现在有一个数据库和一组缓存服务器。 日志提供了一种同步更新到所有这些系统的方式,并能够告知当前系统的同步进度。 假设我们写了一条日志 X 并且需要从缓存执行一个读操作。 假设我们想要确保不会读到过期数据,只需要确保不要从任何还未同步日志 X 的缓存服务器读取即可。

日志还可以充当实现数据异步消费的缓存。 这一点很重要,尤其是在存在多个消费速率各不相同的消费者的场景下。 这意味着订阅系统崩溃或是停服维护后,重启时可以立刻恢复状态。 批处理系统如 Haddop 或是数据仓库可能会以小时级或是天级来进行消费,而实时查询系统可能需要以秒级消费。






请到「今天看啥」查看全文