diff --git a/log-what-every-software-engineer-should-know-about-real-time-datas-unifying/README.md b/log-what-every-software-engineer-should-know-about-real-time-datas-unifying/README.md index f03c86e..b26cd07 100644 --- a/log-what-every-software-engineer-should-know-about-real-time-datas-unifying/README.md +++ b/log-what-every-software-engineer-should-know-about-real-time-datas-unifying/README.md @@ -47,6 +47,6 @@ - [第三部分:日志与实时流处理](part3-logs-and-real-time-stream-processing.md) - [数据流图(`data flow graphs`)](part3-logs-and-real-time-stream-processing.md#数据流图data-flow-graphs) - [有状态的实时流处理](part3-logs-and-real-time-stream-processing.md#有状态的实时流处理) - - [日志压缩(`log compaction`)](part3-logs-and-real-time-stream-processing.md#日志压缩log-compaction) + - [日志合并(`log compaction`)](part3-logs-and-real-time-stream-processing.md#日志合并log-compaction) - [第四部分:系统建设](part4-system-building.md) - [结束语](the-end.md) diff --git a/log-what-every-software-engineer-should-know-about-real-time-datas-unifying/part3-logs-and-real-time-stream-processing.md b/log-what-every-software-engineer-should-know-about-real-time-datas-unifying/part3-logs-and-real-time-stream-processing.md index 4c4b7db..103cfb8 100644 --- a/log-what-every-software-engineer-should-know-about-real-time-datas-unifying/part3-logs-and-real-time-stream-processing.md +++ b/log-what-every-software-engineer-should-know-about-real-time-datas-unifying/part3-logs-and-real-time-stream-processing.md @@ -3,7 +3,7 @@ - [数据流图(`data flow graphs`)](#数据流图data-flow-graphs) - [有状态的实时流处理](#有状态的实时流处理) -- [日志压缩(`log compaction`)](#日志压缩log-compaction) +- [日志合并(`log compaction`)](#日志合并log-compaction) 到目前为止,我只讲述了系统之间拷贝数据的理想机制。但是在存储系统之间搬运字节不是所要讲述内容的全部。 最终会发现,『日志』是流的另一种说法, @@ -115,34 +115,62 @@ 有状态的实时流处理 ------------------------ -一些实时流处理在转化时是无状态的记录。在流处理中大部分的应用会是相当复杂的统计、聚合、不同窗口之间的关联。例如有时人们想扩大包含用户操作信息的事件流(一系列的单击动作) —— 实际上关联了用户的单击动作流与用户的账户信息数据库。不变的是这类流程最终会需要由处理器维护的一些状态信息。例如数据统计时,你需要统计到目前为止需要维护的计数器。如果处理器本身失败了,如何正确的维护这些状态信息呢? +一些实时流处理做的只是无状态的单次记录的转换,但有很多使用方式需要在流处理的某个大小的时间窗口内进行更复杂的计数、聚合和关联(`join`)操作。 +比如,给一个的事件流(如用户点击的流)附加上做点击操作用户的信息, +—— 实际上即是关联点击流到用户的账户数据库。 +这类流程最终总是要处理者维护一些状态信息: +比如在计算一个计数时,需要到目前为止需要维护的计数器。 +在处理者可能挂掉的情况下,如何维护正确的状态? -最简单的替换方案是把这些状态信息保存在内存中。但是如果流程崩溃,它就会丢失中间状态。如果状态是按窗口维护的,流程就会回退到日志中窗口开始的时间点上。但是,如果统计是按小时进行的,那么这种方式就会变得不可行。 +最简单的方案是把状态保存在内存中。但是如果处理流程崩溃,会丢失中间状态。 +如果状态是按窗口维护的,处理流程只能会回退到日志中窗口开始的时间点上。 +但是,如果计数的时间窗口是1个小时这么长,那么这种方式可能不可行。 -另一个替换方案是简单的存储所有的状态信息到远程的存储系统,通过网络与这些存储关联起来。这种机制的问题是没有本地数据和大量的网络间通信。 +另一个方案是简单地存储所有的状态到远程的存储系统,通过网络与这些存储关联起来。 +但是这会损失数据的局部性并产生很多的网络间数据往返(`network round-trip`)。 -我们如何支持处理过程可以像表一样分区的数据呢? +如何才能支持即能和处理流程一样分片又像『表』的存储呢? -回顾一下关于表和日志二相性的讨论。这一机制提供了工具把数据流转化为与处理过程协同定位的表,同时也提供了这些表的容错处理的机制。 +回顾一下关于表和日志二相性的讨论。它正好提供到流转换成与处理相关的表的工具,同时也提供了表的容错处理的机制。 -流处理器可以把它的状态保存在本地的表或索引 —— [bdb](http://www.oracle.com/technetwork/products/berkeleydb),或者[leveldb](https://code.google.com/p/leveldb),甚至于类似于[Lucene](http://lucene.apache.org/) 或[fastbit](https://sdm.lbl.gov/fastbit)一样不常见的索引。这些内容存储在它的输入流中(或许是使用任意的转化)。生成的变更日志记录了本地的索引,它允许存储事件崩溃、重启等的状态信息。流处理提供了通用的机制用于在本地输入流数据的随机索引中保存共同分片的状态。 +流处理器可以把它的状态保存在本地的『表』或『索引』中 —— [`bdb`](http://www.oracle.com/technetwork/products/berkeleydb)、[`leveldb`](https://code.google.com/p/leveldb) +甚至是些更不常见的组件,如[`Lucene`](http://lucene.apache.org/) 或[`fastbit`](https://sdm.lbl.gov/fastbit)索引。 +这样一些存储的内容可以从它输入流生成(可能做过了各种转换后的输入流)。 +通过记录关于本地索引的变更日志,在发生崩溃、重启时也可以恢复它的状态。 +这是个通用的机制,用于保持 任意索引类型的协作分片(`co-partitioned`)的本地状态 与 输入流数据 一致。 -当流程运行失败时,它会从变更日志中恢复它的索引。每次备份时,日志把本地状态转化成一系列的增量记录。 +当处理流程失败时,可以从变更日志中恢复它的索引。 +每次备份时,即是日志把本地状态转化成一种增量记录。 -这种状态管理的方法有一个优势是把处理器的状态也做为日志进行维护。我们可以把这些日志看成与数据库表相对应的变更日志。事实上,这些处理器同时维护着像共同分片表一样的表。因为这些状态它本身就是日志,其它的处理器可以订阅它。如果流程处理的目标是更新结点的最后状态,这种状态又是流程的输出,那么这种方法就显得尤为重要。 +这种状态管理方案的优雅之处在于处理器的状态也是做为日志来维护。 +我们可以把这个日志看成是数据库表变更的日志。 +事实上,这些处理器本身就很像是自维护的协作分片的表。 +因为这些状态本身就是日志,所以其它处理器可以订阅它。 +如果处理流程的目标是更新结点的最后状态并且这个状态又是流程的一个自然的输出,那么这种方式就显得尤为重要。 -为了数据集成,与来自数据库的日志关联,日志和数据库表的二象性就更加清晰了。变更日志可以从数据库中抽取出来,日志可以由不同的流处理器(流处理器用于关联不同的事件流)按不同的方式进行索引。 +再组合使用上用于解决数据集成的数据库输出日志,日志和表的二象性的威力就更加明显了。 +从数据库中抽取出来的变更日志可以按不同的形式索引到各种流处理器中,以关联到事件流上。 -我们可以列举在Samza中有状态流处理管理的更多细节和大量实用的[例子](http://samza.incubator.apache.org/learn/documentation/0.7.0/container/state-management.html)。 +在`Samza`和这些大量[实际例子](http://samza.incubator.apache.org/learn/documentation/0.7.0/container/state-management.html)中, +我们说明了这种风格的有状态流处理管理的更多细节。 -日志压缩(`log compaction`) +日志合并(`log compaction`) ------------------------ -当然,我们不能奢望保存全部变更的完整日志。除非想要使用无限空间,日志不可能完全清除。为了澄清它,我们再来聊聊Kafka的实现。在Kafka中,清理有两种选择,这取决于数据是否包括关键更新和事件数据。对于事件数据,Kafka支持仅维护一个窗口的数据。通常,配置需要一些时间,窗口可以按时间或空间定义。虽然对于关键数据而言,完整日志的重要特征是你可以重现源系统的状态信息,或者在其它的系统重现。 +当然,我们不能奢望一直保存着全部变更的完整日志。 +除非想要使用无限空间,日志总是要清理。 +为了让讨论更具体些,我会介绍一些`Kafka`这方面的实现。 +在`Kafka`中,清理有两种方式,取决于数据包括的是键值存储的更新还是事件数据。 +对于事件数据,`Kafka`支持仅维护一个窗口的数据。通常,窗口配置成几天,但窗口也可以空间定。 +对于键值存储的更新,尽管完整日志的一个优点是可以回放以重建源系统的状态(一般是另一个系统中重建)。 -随着时间的推移,保持完整的日志会使用越来越多的空间,重现所耗费的时间越来越长。因些在Kafka中,我们支持不同类型的保留。我们移除了废弃的记录(这些记录的主键最近更新过)而不是简单的丢弃旧日志。我们仍然保证日志包含了源系统的完整备份,但是现在我们不再重现原系统的全部状态,而是仅仅重现最近的状态。我们把这一特征称为[日志压缩](https://cwiki.apache.org/confluence/display/KAFKA/Log+Compaction)。 +但是,随着时间的推移,保持完整的日志会使用越来越多的空间,并且回放的耗时也会越来越长。 +因些在`Kafka`中,我们支持不同类型的保留方式。 +我们删除过时的记录(如这些记录的主键最近更新过)而不是简单的丢弃旧日志。 +这样做我们仍然保证日志包含了源系统的完整备份,但是现在我们不再重现原系统曾经的所有状态,仅是最近的哪些状态。 +这一功能我们称之为[日志合并](https://cwiki.apache.org/confluence/display/KAFKA/Log+Compaction)。 -----------------