学习笔记:The Log(我所读过的最好的一篇分布式技术文章)

前言

这是一篇学习笔记。
学习的材料来自Jay Kreps的一篇讲Log的博文。
原文非常长。可是我坚持看完了,收获颇多,也深深为Jay哥的技术能力、架构能力和对于分布式系统的理解之深刻所折服。同一时候也由于某些理解和Jay哥观点吻合而略沾沾自喜。

Jay Kreps是前Linkedin的Principal Staff Engineer。现任Confluent公司的联合创始人和CEO。Kafka和Samza的主要作者。

所谓笔记,就是看了文章。提笔就记。由于Jay哥本身本章组织的太好,而其本身的科学素质及哲学素质也非常高,所以私以为出彩的东西就不省略了。

一、资料来源

The Log: What every software engineer should know about real-time data’s unifying abstraction

二、笔记

2.1 Log的价值

1) Log是例如以下系统的核心:

  • 分布式图数据库
  • 分布式搜索引擎
  • Hadoop
  • 第一代和第二代K-V数据库

2) Log可能跟计算机的历史一样长。而且是分布式数据系统和实时计算系统的核心。
3) Log的名字非常多:

  • Commit log
  • Transaction log
  • Write-ahead log

4) 不理解Log,你就不可能充分理解

  • 数据库
  • NoSQL存储
  • K-V存储
  • 复制
  • Paxos算法
  • Hadoop
  • Version Control
  • 或者,不论什么软件系统

2.2 什么是Log?

2.2.1 概述

这里写图片描写叙述

  • 记录会附加到log的尾部。
  • 从左到右读取记录。
  • 每一个entry都有唯一且有序的log entry 序号。

记录的顺序定义了这样的一个概念:时间。
由于越靠左的记录越早。
Entry的序号能够当作一种时间戳,将记录的顺序当作时间这一概念看起来非常奇怪,可是非常快你就会发现,这样做:能够方便地将“时间”与任一特定的物理时钟解耦。
Log和常见的文件、表(table)没有那么大的区别。

  • 文件是一组字节
  • 表是一组记录
  • Log能够说是某种将记录按时间排序的文件或者表

这样说,可能你会觉得log如此简单,还有讨论的必要吗?
事实上,log的核心意义在于:

Log记录了何时发生了什么(they record what happened and when.)。

而这一条。一般是分布式系统最最最核心的东西。


注意,这里有必要澄清几个概念:

  • 本篇所讨论的Log和程序猿通常接触的应用日志(application logs)不同
  • 应用日志一般是一种非结构化的。记录错误信息、调试信息,用于追踪应用的运行的。给人看的日志。比方通过log4j或者 syslog来写入本地文件的日志。

  • 而本篇所讨论的log是通过编程方式訪问的,不是给人看的。比方“journal”、“data logs”。
  • 应用日志是本篇所讨论的log的一种特化。

2.2.2 数据库中的Logs

Log的起源不得而知,就像发明二分查找的人,难以意识到这样的发明是一种发明。
Log的出现和IBM的System R 一样早。
在数据库中,须要在数据库崩溃时。保持多种多样的数据结构和索引保持同步。
为保证原子性和持久性,数据库须要在对数据结构和索引进行改动提交之前。记录其要改动的内容。


所以log记录了何时发生了什么。而每一张表和索引本身,都是这样的历史信息的映射。
由于log是马上持久化的,所以当crash发生时,其成为恢复其他持久化结构的可靠来源。

Log从保证ACID特性的一种实现,发展成了一种数据库之间数据复制的手段。

非常显然。数据库中发生的一系列的数据变更。成为数据库之间 保持同步最须要的信息。
Oracle、MySQL、PostgreSQL,都包括了log传输协议。将log的一部分发送到用于保持复制的从数据库(Slave)。
Oracle的XStreams和GoldenState。将log当作一种通用的数据订阅机制。以提供给非Oracle的数据库订阅数据。


MySQL和PostgreSQL也提供了相似的组件,这些组件是数据系统架构的核心。
面向机器的Log。不仅仅可被用在数据库中,也能够用在:

  • 消息系统
  • 数据流(data flow)
  • 实时计算

2.2.3 分布式系统中的logs

Log攻克了两个非常重要的分布式数据系统中的问题:
1) 有序的数据变化
2) 数据分布式化

所谓的状态机复制原理(State Machine Replication Principle):

假设两个确定的处理过程,从同样的状态開始,依照同样的顺序。接收同样的输入,那么它们将会产生同样的输出,并以 同样的状态结束。

所谓确定的(deterministic),是指处理过程是时间无关的,其处理结果亦不受额外输入的影响。


能够通过非确定的样例来理解:

  • 多线程的运行顺序不同导致不同的结果
  • 运行getTimeOfDay()方法
  • 其他的不能反复的处理过程

所谓状态,能够是机器上的随意数据,不管在处理结束后。是在机器的内存中还是磁盘上。
同样的输入依照同样的顺序,产生同样的结果,这一点值得引起你的注意。这也是为什么log会如此重要,这是一个直觉性的概念:假设你将同一个log输入两个确定性的程序,它们将产生同样的输出。


在分布式系统的构建中。意识到这一点。能够使得:
让全部的机器做同样的事,规约为:
构建分布式的、满足一致性的log系统,以为全部处理系统提供输入。

Log系统的作用,就是将全部的输入流之上的不确定性驱散,确保全部的处理同样输入的复制节点保持同步。

这样的方法的最妙之处在于,你能够将索引日志的时间戳,作为全部复制节点的时钟来对待:

通过将复制节点所处理过的log中最大的时间戳,作为复制节点的唯一ID,这样,时间戳结合log。就能够唯一地表达此节点的整个状态。

应用这样的方法的方式也非常多:

  • 在log中记录对一个服务的请求
  • 在回复请求的前后,记录服务状态的变化
  • 或者。服务所运行的一系列转换命令。等等。

理论上来讲,我们能够记录一系列的机器指令,或者所调用方法的名称及參数,仅仅要数据处理进程的行为同样。这些进程就能够保证跨节点的一致性。


常玩儿数据库的人,会将逻辑日志和物理日志区分对待:

  • 物理日志:记录了全部的行内容的变化。

  • 逻辑日志:不是记录内容的变化。而是Insert , update , delete等导致行内容变化的SQL语句。

对分布式系统。通常有两种方式来处理复制和数据处理:
1) State machine model(active - active)
2) Primary-back model (active - passive)

例如以下图所看到的:
这里写图片描写叙述

为了理解上述两种方式的不同。来看个简单的样例:
如今,集群须要提供一个简单的服务,来做加法、乘法等算术运算。

初始,维护一个数字,比方0。

  • Active – active :在日志记录这样的一些操作,如“+1”、“*2”等,这样,每一个复制节点须要运行这些操作。以保证最后的数据状态是一致的。

  • Active – passive:一个单独的master节点。运行“+1”、“*2”等操作,而且在日志中记录操作的结果,如“1”、“3”、“6”等。

上面的样例也揭示了,为什么顺序是复制节点之间保持一致性的关键因素。假设打乱了这些操作的顺序。就会得到不同的运算结果。
分布式log,能够当做某些一致性算法的数据结构:

  • Paxos
  • ZAB
  • RAFT
  • Viewstamped Replication

一条log,表征了一系列的关于下一个值是什么的决定。

2.2.4 Changelog

从数据库的角度来看。一组记录数据变化的changelog和表。是对偶和互通的。
1) 根据记录了数据变化的log,能够重构某一状态的表(也能够是非关系型存储系统中有key的记录)
2) 相反,表假设发生了变化。能够将变化计入log。

这正是你想要的准实时复制的秘籍所在!

这一点和版本号控制所做的事情极为相似:管理分布式的、并发的、对状态进行的改动。

版本号控制工具,维护了反映改动的补丁,这事实上就是log,你和一个被签出(checked out)的分支快照进行交互,这份快照就相当于数据库中的表。

你会发现。版本号控制与分布式系统中,复制都是基于log的:当你更新版本号时,你仅仅是拉取了反映了版本号变化的补丁,并应用于当前的分支快照。

2.3 数据集成(Data integration)

2.3.1 数据集成的含义

所谓数据集成。就是将一个组织中的全部服务和系统的数据,变得可用。

实际上。对数据进行有效利用。非常符合马斯洛的层次需求理论。


金字塔的最底层,是收集数据,将其整合进应用系统中(不管是实时计算引擎,还是文本文件,还是python脚本)。
而这些数据,须要经过转换,保持一个统一、规范、整洁的格式。以易于被读取和处理。
当上面的要求被满足后。就能够開始考虑多种多样的数据处理方式。比方map – reduce 或者实时查询系统。


非常显然,假设没有一个可靠的、完备的数据流,Hadoop就仅仅是一个昂贵的、难以整合的加热器(集群非常费电么?)。
相反,假设能保证数据流可靠、可用且完备,就能够考虑更高级的玩法、更好的数据模型和一致的、更易被理解的语义。


接着。注意力就能够转移到可视化、报表、算法和预測上来(挖啊机啊深度啊)。

2.3.2 数据集成的两个复杂性

事件

事件数据,记录了事件是怎么发生的。而不仅仅是发生了什么,这一类log通常被当做应用日志,由于一般是由应用系统写入的。

但这一点。事实上混淆了log的功能。
Google的財富,事实上,是由一个建立在(用户)点击流和好恶印象(体验)之上的相关性pipeline产生的,而点击流和印象,就是事件。

各种各样的专业数据系统的爆发

这些系统存在的原因:

  • 联机分析(OLAP)
  • 搜索
  • 简单的在线存储
  • 批处理
  • 图谱分析
  • 等等(如spark)

显然。要将数据整合进这样的系统中,对于数据集成来讲,极为困难。

2.3.3 基于日志结构的数据流

每种逻辑意义上的数据源,都能够根据log进行建模。

数据源能够是记录了事件(点击和PV)的应用程序。能够是接受更改的数据库表。

每一个订阅者。都尽可能快地从这些数据源产生的log中获取新的记录,应用于本地的存储系统。而且提升其在log中的读取偏移(offset)。订阅者能够是不论什么数据系统。比方缓存、Hadoop、还有一个站点的数据库。或者搜索引擎。

Log,实际上提供了一种逻辑时钟,针对数据变化。能够測量不同的订阅者所处的状态,由于这些订阅者在log中的读取偏移不同且相互独立,这样的偏移就像一个时间意义上的“时刻”一样。

这里写图片描写叙述

考虑这样一个样例,一个数据库。和一些缓存server:
Log提供了这样一种能力,能够使得全部的缓存server得到同步,并推出它们所处的“时刻”。

假设我们写入了一个编号为X的log。要从某个缓存server读取数据,为了不读到老数据,仅仅须要保证:在缓存server将数据(同步)拷贝到X这个位置前,我们不从这个缓存中读取不论什么东西就可以。

此外。log还提供了作为缓冲区的能力,以支持生产者和消费者的行为以异步的方式进行。

最关键的一个支持异步的原因,是订阅系统可能会发生崩溃、因维护而下线。接着恢复上线,而在这样的情况下。每一个订阅者都以自己的步调消费数据。

一个批处理系统,比方Hadoop,或者一个数据仓库,是以小时或天为单位消费数据。而一个实时系统,通常在秒级消费数据。
而数据源或者log,对消费数据的订阅者一无所知。所以,须要在pipeline中做到无缝的加入订阅者和移除订阅者。

更重要的是,订阅者,仅仅须要知道log,而不须要对其所消费的数据的来源有不论什么了解,不管这个数据源是RDBMS、Hadoop,还是一个最新流行的K-V数据库,等等。

之所以讨论log,而不是消息系统。是由于不同的消息系统所保证的特性不同,而且用消息系统这个词,难以全面和精确表达某种语义,由于消息系统。更重要的在于重定向消息。

可是,能够将log理解为这样一种消息系统。其提供了持久性保证及强有序的语义,在通讯系统中,这称作原子广播。

2.4 在Linkedin

Linkedin眼下的主要系统包括(注:2013年):

  • Search
  • Social Graph
  • Voldemort (K-V存储)
  • Espresso (文档存储)
  • Recommendation engine
  • OLAP query engine
  • Hadoop
  • Terradata
  • Ingraphs (监控图谱及metrics服务)

每一个系统。都在其专业的领域提供专门的高级功能。

(这一段太长太长了。Jay兄十分能侃啊,所以挑重点的来记吧。)

1) 之所以引入数据流这个概念,是由于要在oracle数据库的表之上,建立一个抽象的缓存层,为搜索引擎的索引构建和社交图谱更新,提供拓展能力。

2) 为了更好的处理linkedin的一些推荐算法,開始搭Hadoop集群。但团队在此块的经验尚浅,所以走了非常多弯路。

3) 開始时。简单粗暴地觉得仅仅要将数据从oracle数据仓库中拉出来,丢进hadoop就能够了。结果发现:第一,将数据从oracle数据仓库高速导出是个噩梦;第二。也是更糟糕的一点。数据仓库中某些数据的处理不正确。导致了hadoop的批处理任务不能按预期输出结果,且通过hadoop批处理运行任务,通常不可逆。特别是在出了报表之后。

4) 最后。团队抛弃了从数据仓库中出数据的方式,直接以数据库和logs为数据源。

接着,造出了一个轮子:K-V 存储(Voldemort)。

5) 即使是数据拷贝这样不高大上的活儿,也占领了团队大量的时间去处理。更糟的是,一旦数据处理的pipeline中有个点出错,hadoop立刻变得废柴,由于再牛逼的算法跑在错误的数据上。仅仅有一个后果,就是产生很多其他的错误数据。

6) 即使团队构建的东西抽象层次非常高,针对每种数据源还是须要特定的配置,而这也是非常多错误和失败的根源。

7) 一大批程序猿想跟进,每一个程序猿都有一大批的想法,集成这个系统,加入这个功能。整合这个特色,或者想要自己定义的数据源。

8) Jay哥開始意识到:
第一, 尽管他们构建的pipelines还非常糙,可是却极其有价值。即使是攻克了数据在新的系统(如hadoop)中可用的问题,也解锁了一大批可能性。曾经难做的计算開始变为可能。新的产品和分析。仅须要解锁其他系统中的数据,而且进行整合。就能够easy地做出来。

第二, 非常明显,可靠地数据装载须要更坚实的支撑,假设能够捕获全部的结构。就能够让hadoop数据装载全然自己主动化,不须要加入新的数据源或人工改动数据的模式。

数据会奇妙地出如今HDFS中。而新的数据源加入后。Hive的表会用合适的列自己主动化地、自适应地生成。

第三。数据覆盖度远远不足。

由于要处理非常多新的数据源,非常难。

9) 为了解决新数据源加入后的数据装载问题,团队開始了这样的尝试:

这里写图片描写叙述

非常快。他们发现这样搞行不通,由于公布和订阅、生产和消费。数据流通常还是双向的,这成了一个O(n^2)的问题。


所以,他们须要的是这样的模型:

这里写图片描写叙述

须要将每一个消费者从数据源隔离。理想的情况下,这些消费者仅仅和一个data repository进行交互,而这个repository能够提供它们訪问随意数据的能力。

10)消息系统 + log = Kafka。kafka横空出世。

2.5 Log和ETL、数据仓库的关系

2.5.1 数据仓库

1) 一个装有干净的、结构化的、集成的数据repository,用于分析。
2) 尽管想法非常美好,可是获取数据的方式有点过时了:周期性地从数据库获取数据,将其转换为某种可读性更佳的格式。
3) 之前的数据仓库问题在于:将干净的数据和数据仓库高度耦合

数据仓库,应该是一组查询功能的集合,这些功能服务于报表、搜索、ad hot 分析。包括了计数(counting)、聚合(aggregation)、过滤(filtering)等操作,所以更应该是一个批处理系统。

可是将干净的数据和这样的一种批处理系统高度耦合在一起,意味着这些数据不能被实时系统消费。比方搜索引擎的索引构建、实时计算和实时监控系统。等等。

2.5.2 ETL

Jay哥觉得,ETL无非做两件事:

1) 对数据进行抽取和清洗,将数据从特定的系统中解锁
2) 重构数据,使其能通过数据仓库进行查询。

比方将数据类型变为适配某个关系型数据库的类型,将模式转换为星型或者雪花模式,或者将其分解为某种面向列的存储格式。

可是,将这两件事耦合在一起,问题非常大,由于集成后的、干净的数据,本应能被其他实时系统、索引构建系统、低延时的处理系统消费。

数据仓库团队,负责收集和清洗数据,可是,这些数据的生产者往往由于不明白数据仓库团队的数据处理需求,导致输出非常难被抽取和清洗的数据。
同一时候,由于核心业务团队对和公司的其他团队保持步调一致这件事儿不敏感,所以真正能处理的数据覆盖度非常低,数据流非常脆弱,非常难高速应对变化。

所以,更好的方式是:

这里写图片描写叙述

假设想在一个干净的数据集上做点搜索、实时监控趋势图、实时报警的事儿,以原有的数据仓库或者hadoop集群来作为基础设施。都是不合适的。

更糟的是,ETL所构建的针对数据仓库的数据载入系统,对其他(实时)系统点儿用没有。

最好的模型。就是在数据公布者公布数据之前,就已经完毕了数据的清洗过程,由于仅仅有公布者最清楚它们的数据是什么样的。而全部在这个阶段所做的操作,都应该满足无损和可逆

全部丰富语义、或加入值的实时转换,都应在原始的log公布后处理(post-processing),包括为事件数据建立会话。或者加入某些感兴趣的字段。

原始的log依然可被单独使用,可是此类实时应用也派生了新的參数化的log。

最后,仅仅有相应于详细的目标系统的数据聚合操作,应作为数据装载的一部分,比方转换为星型或雪花型模式,以在数据仓库中进行分析和出报表。由于这个阶段,就像传统的ETL所做的那样。由于有了非常干净和规范的数据流,(有了log后)如今变得非常easy。

2.6 Log文件和事件

以log为核心的架构,还有个额外的优点,就是易于实现无耦合的、事件驱动的系统。

传统的 捕获用户活动和系统变化的方式,是将此类信息写入文本日志,然后抽取到数据仓库或者hadoop集群中进行聚合和处理。这个问题和前面所述的数据仓库和ETL问题相似:数据与数据仓库的高度耦合。

在Linkedin,其基于kafka构建了事件数据处理系统。

为各种各样的action定义了成百上千种事件类型,从PV、用户对于广告的赶脚(ad impressions)、搜索。到服务的调用和应用的异常。等等。

为了体会上述事件驱动系统的优点,看一个简单的关于事件的样例:
在工作机会页面上,提供一个机会。这个页面应该仅仅负责怎样展示机会,而不应该过多地包括其他逻辑。可是。你会发现,在一个具有相当规模的站点中,做这件事,非常easy就会让越来越多的与展示机会无关的逻辑牵扯进来。

比方,我们希望集成以下系统功能:
1) 我们须要将数据发送到hadoop和数据仓库做离线处理。
2) 我们须要统计页面浏览次数,以确保某些浏览不是为了抓取网页内容什么的。


3) 我们须要聚合对此页面的浏览信息。在机会公布者的分析页面上呈现。
4) 我们须要记录某用户对此页面的浏览记录。以确保我们对此用户提供了有价值的、体验良好的不论什么适宜此用户的工作机会,而不是对此用户一遍又一遍地反复展示某个机会(想想老婆不在家才干玩的游戏吧,那红绿蓝闪烁的特效。配合那劲爆的DJ风舞曲,或者那摇摆聚焦的事业峰和齐X小短裙的girls,然后点进去才发现是标题党的ad吧!)。
5) 我们的推荐系统须要记录对此页面的浏览记录,以正确地追踪此工作机会的流行度。

非常快,仅仅展示机会的页面逻辑。就会变得复杂。当我们在移动端也添加了此机会的展示时,不得不把逻辑也迁移过去,这又加剧了复杂程度。还没完,纠结的东西是,负责处理此页面的project师,须要有其他系统的知识。以确保上述的那些功能能正确的集成在一起。

这仅仅是个极其简单的样例,在实践中,情况仅仅会更加复杂。
事件驱动能够让这件事变得简单。

负责呈现机会的页面,仅仅须要呈现机会并记录一些和呈现相关的因素,比方工作机会的相关属性,谁浏览了这个页面。以及其他的实用的与呈现相关的信息。页面不须要保持对其他系统的知识和了解,比方推荐系统、安全系统、机会公布者的分析系统,还有数据仓库。全部的这些系统仅仅须要作为订阅者,订阅这个事件,然后独立地进行它们各自的处理就可以,而呈现机会的页面不须要由于新的订阅者或消费者的加入而做出改动。

2.7 构建可扩展的log

分离公布者和订阅者不新奇,可是要保证多个订阅者能够实时处理消息,而且同一时候保证扩展能力,对于log系统来说。是一件比較困难的事。

假设log的构建不具备高速、低开销和可扩展能力。那么建立在此log系统之上的一切美好都免谈。

非常多人可能觉得log系统在分布式系统中是个非常慢、重型开销的活儿,而且仅用来处理一些相似于ZooKeeper更适合处理的元数据等信息。

可是Linkedin如今(注:2013年),在kafka中每天处理600亿条不同的消息写入(假设算数据中心的镜像的话。那就是几千亿条写入)。

Jay哥他们怎么做到的呢?

1) 对log进行切割(partitioning the log)
2) 通过批量读写优化吞吐量
3) 避免不必要的数据拷贝

通过将log切为多个partition来提供扩展能力:

这里写图片描写叙述

1) 每一个partition都是有序的log,可是partitions之间没有全局的顺序。

2) 将消息写入哪个partition全然由写入者控制。通过依照某种类型的key(如user_id)进行切割。

3) 切割使得log的附加操作。能够不用在分片(sharding)之间进行协调就进行。同一时候,保证系统的吞吐量和kafka集群的规模呈线性关系。

4) 尽管没有提供全局顺序(实际上消费者或者订阅者成千上万,讨论它们的全局顺序一般没有啥价值),可是kafka提供了这样一种保证:发送者依照什么顺序将消息发给某个partition,从这个partition递交出去的消息就是什么顺序(什么顺序进,什么顺序出)。

5) 每一个partition都依照配置好的数目进行复制。假设一个leader节点挂了,其他的节点会成为新的leader。

6) 一条log。同文件系统一样。线性的读写模式可被优化,将小的读写log能够组成更大的、高吞吐量的操作。Kafka在这件事上做的非常猛。

批处理用在了各种场景之下。比方client将数据发送到服务端、将数据写入磁盘、server之间的数据复制、将数据传送给消费者,以及确认提交数据等场景。

7) 最后,kafka在内存log、磁盘log、网络中发送的log上,採用了非常easy的二进制格式。以利于利用各种优化技术。比方零拷贝传输数据技术(zero-copy data transfer)。

诸多的优化技术,汇聚起来。能够让你即使在内存爆满的情形下。也能依照磁盘或网络能提供的最大能力进行数据读写。

2.8 Logs和实时处理

你以为Jay哥提供了这么个漂亮的方法把数据复制来复制去就完了?
你!

错!了!

Log是流的还有一种说法。logs是流处理的核心。

2.8.1 什么是流处理

Jay哥觉得:
1)流处理是连续数据处理的基础设施。
2)流处理的计算模型,就如同MapReduce或其他分布式处理框架一样,仅仅是须要保证低延迟。


3)批处理式的收集数据模式。导致了批处理式的数据处理模式。
4)连续的收集数据模式,导致了连续的数据处理模式。
5)Jay哥讲了个美国人口普查的方式来解释批处理。

在linkedin。不管是活动数据还是数据库的变化。都是连续的。


批处理按天处理数据,和连续计算将窗体设为一天雷同。

所以,流处理是这样一种过程:
6)在处理数据时,带了一个时间的概念,不须要对数据保持一个静态的快照,所以能够在用户自己定义的频率之下。输出结果,而不必等数据集到达某种“结束”的状态。
7)从这个意义上讲。流处理是批处理的一种泛化,而且考虑到实时数据的流行程度,这是一种极其重要的泛化。
8)很多商业公司无法建立流处理引擎,往往由于无法建立流数据收集引擎。
9)流处理跨越了实时响应式服务和离线批处理的基础设施之间的鸿沟。
10)Log系统,攻克了非常多流处理模式中的关键问题,当中最大的一个问题就是怎样在实时的多个订阅者模式下。提供可用数据的问题(流数据收集)。

2.9 数据流图谱

流处理中最有趣的地方在于。其拓展了什么是数据源(feeds)这一概念。
不管是原始数据的logs、feeds,还是事件、一行一行的数据记录,都来自应用程序的活动。


可是,流处理还能够让我们处理来自其他feeds的数据,这些数据和原始数据,在消费者看来,并无二致,而这些派生的feeds能够包括随意程度的复杂性。

这里写图片描写叙述

一个流处理任务,应该是这样的:从logs读取数据。将输出写入logs或者其他系统。

作为输入和输出的logs,连通这些处理本身,和其他的处理过程。构成了一个图。

事实上,以log为核心的系统,同意你将公司或机构中的数据捕获、转换以及数据流。看作是一系列的logs及在其上进行写入的处理过程的结合。

一个流处理程序,事实上不必非常高大上:能够是一个处理过程或者一组处理过程,可是。为了便于管理处理所用的代码,能够提供一些额外的基础设施和支持。

引入logs有两个目的:

1) 保证了数据集能够支持多个订阅者模式,及有序。


2) 能够作为应用的缓冲区。这点非常重要,在非同步的数据处理进程中,假设上游的生产者出数据的速度更快。消费者的速度跟不上。这样的情况下,要么使处理进程堵塞。要么引入缓冲区,要么丢弃数据。
丢弃数据似乎不是个好的选择,而堵塞处理进程。会使得全部的数据流的处理图谱中的处理进程卡住。

而log。是一种非常大。特大,非常大的缓冲区。它同意处理进程的重新启动,使得某个进程失败后,不影响流处理图谱中的其他进程。这对于一个庞大的机构去扩展数据流是非常关键的,由于不同的团队有不同的处理任务。显然不能由于某个任务错误发生。整个流处理进程都被卡住。

Storm和Samza就是这样的流处理引擎,而且都能用kafka或其他相似的系统作为它们的log系统。

(注:Jay哥相当猛。前有kafka,后有samza。

2.10 有状态的实时处理

非常多流处理引擎是无状态的、一次一记录的形式。但非常多用例都须要在流处理的某个大小的时间窗体内进行复杂的counts , aggregations和joins操作。
比方。点击流中,join用户信息。

那么,这样的用例。就须要状态的支持。在处理数据的地方,须要维护某个数据的状态。

问题在于。怎样在处理者可能挂掉的情况下保持正确的状态?

将状态维护在内存中可能是最简单的,但抵不住crash。

假设仅在某个时间窗体内维护状态,当挂掉或者失败发生,那么处理能够直接回退到窗体的起点来重放,可是,假设这个窗体有1小时那么长,这可能行不通。

还有个简单的办法。就是把状态存在某个远程的存储系统或数据库中,可是这会损失数据的局部性并产生非常多的网络间数据往返(network round-trip)。

回顾下,上文中曾提到的数据库中的表和log的对偶性


一个流处理组件,能够使用本地的存储或索引来维护状态:

  • Bdb
  • Leveldb
  • Lucene
  • Fastbit

通过记录关于本地索引的changelog。用于在crash后恢复状态。这样的机制,事实上也揭示了一种一般化的。能够存储为随意索引类型的,与输入流同一时候被切割(co-partitioned)的状态。

当处理进程崩溃,其能够从changelog中恢复索引,log充当了将本地状态转化为某种基于时间备份的增量记录的角色。

这样的机制还提供了一种非常优雅的能力:处理过程本身的状态也能够作为log被记录下来,显然,其他的处理过程能够订阅这个状态。

结合数据库中的log技术。针对数据集成这一场景,往往能够做出非常强大的事:

将log从数据库中抽取出来,并在各种各样的流处理系统中进行索引。那么。与不同的事件流进行join就成为可能。

2.11 Log 合并

显然,用log记录全时全量的状态变更信息。不太可能。

Kafka使用了log合并或者log垃圾回收技术:

1) 对于事件数据,kafka仅仅保留一个时间窗体(可在时间上配置为几天,或者按空间来配置)
2) 对于keyed update,kafka採用压缩技术。

此类log。能够用来在另外的系统中通过重放技术来重建源系统的状态。

假设保持全时全量的logs,随着时间增长,数据将会变得越来越大,重放的过程也会越来越长。


Kafka不是简单地丢弃老的日志信息,而是採用合并的方式。丢弃废弃的记录,比方。某个消息的主键近期被更新了。

这里写图片描写叙述

2.12 系统构建

2.12.1 分布式系统

Log,在分布式数据库的数据流系统和数据集成中所扮演的角色是一致的:

  • 抽象数据流
  • 保持数据一致性
  • 提供数据恢复能力

你能够将整个机构中的应用系统和数据流,看作是一个单独的分布式数据库。
将面向查询的独立系统,比方Redis , SOLR , Hive tables 等等,看作是一种特别的、数据之上的索引。
将Storm、Samza等流处理系统,看做一种精心设计过的触发器或者物化视图机制。

各式各样的数据系统。爆发性的出现。事实上。这样的复杂性早已存在。


在关系型数据库的辉煌时期(heyday)。某个公司或者机构光关系型数据库就有非常多种。

显然,不可能将全部的东西都丢进一个Hadoop集群中,期望其解决全部的问题。

所以,怎样构建一个好的系统,可能会像以下这样:

构建一个分布式系统。每一个组件都是一些非常小的集群,每一个集群不一定能完整提供安全性、性能隔离、或者良好的扩展性。可是,每一个问题都能得到(专业地)解决。

Jay哥觉得,之所以各式各样的系统爆发性地出现。就是由于要构建一个强大的分布式系统十分困难。

而假设将用例限制到一些简单的,比方查询这样的场景下,每一个系统都有足够的能力去解决这个问题,可是要把这些系统整合起来,非常难。

Jay哥觉得在未来构建系统这事儿有三种可能:

1) 保持现状。这样的情况下。数据集成依然是最头大的问题。所以一个外部的log系统就非常重要(kafka!)
2) 出现一个强大的(假设辉煌时期的关系型数据库)能解决全部问题的系统,这似乎有点不可能发生(提它干嘛?)。
3) 新生代的系统大部分都开源,这揭示了第三种可能:数据基础设施可被离散为一组服务、以及面向应用的系统API,各类服务各司其事,每一个都不完整,却能专业滴解决专门的问题。事实上通过现存的java技术栈就能看出端倪:

  • ZooKeeper:解决分布式系统的同步、协作问题(也可能受益于更高抽象层次的组件如helix、curator).
  • Mesos、YARN:解决虚拟化和资源管理问题。

  • 嵌入式的组件Lucene、LevelDB:解决索引问题。

  • Netty、Jetty及更高抽象层次的Finagle、rest.li解决远程通讯问题。
  • Avro、Protocol Buffers、Thrift及umpteen zlin:解决序列化问题。
  • Kafka、bookeeper:提供backing log能力。

从某种角度来看,构建这样的分布式系统,就像某个版本号的乐高积木一样。这显然跟更关心API的终端用户没有太大关系,可是这揭示了构建一个强大系统并保持简单性的一条道路:
显然。假设构建一个分布式系统的时间从几年降到几周,那么构建一个独立的庞大系统的复杂性就会消失,而这样的情况的出现,一定是由于出现了更可靠、更灵活的“积木”。

2.12.2 Log在系统构建中的地位

假设一个系统。有了外部log系统的支持。那么每一个独立的系统就能够通过共享log来减少其自身的复杂性,Jay哥觉得log的作用是:

1) 处理数据一致性问题。不管是马上一致性还是终于一致性,都能够通过序列化对于节点的并发操作来达到。

2) 在节点间提供数据复制。

3) 提供“提交”的语义。

比方。在你觉得你的写操作不会丢失的情况下进行操作确认。

4) 提供外部系统可订阅的数据源(feeds)。

5) 当节点因失败而丢失数据时,提供恢复的能力。或者又一次构建新的复制节点。

6) 处理节点间的负载均衡。

以上,大概是一个完整的分布式系统中应提供的大部分功能了(Jay哥确实爱Log。)。剩下的就是client的API和诸如一些构建索引的事了,比方全文索引须要获取全部的partitions,而针对主键的查询,仅仅须要在某个partition中获取数据。

(那把剩下的事情也交代下吧,Jay哥威武!)

系统可被分为两个逻辑组件(这强大的理解和功力):

1) Log层
2) 服务层

Log层,以序列化的、有序的方式捕获状态的变化。而服务层,则存储外部查询须要的索引,比方一个K-V存储可能须要B-tree、sstable索引。而一个搜索服务须要倒排索引。

写操作既能够直接入log层,也能够通过服务层做代理。

写入log会产生一个逻辑上的时间戳(log的索引)。比方一个数字ID,假设系统partition化了,那么。服务层和log层会拥有同样的partitions(但其各自的机器数可能不同)。

这里写图片描写叙述

服务层订阅到log层,而且以最快的速度、按log存储的顺序追log。将数据和状态变化同步进自己的本地索引中。

client将会得到read-your-write的语义:

通过对任一一个节点,在查询时携带其写入时的时间戳,服务层的节点收到此查询。通过和其本地索引比較时间戳,假设必要。为了防止返回过期的老数据。推迟请求的运行,直到此服务节点的索引同步跟上了时间戳。

服务层的节点,或许须要、或许不须要知道leader的概念。在非常多简单的用例中,服务层可不构建leader节点,由于log就是事实的来源。

还有一个问题,怎样处理节点失败后的恢复问题。能够这样做,在log中保留一个固定大小的时间窗体,同一时候对数据维护快照。也能够让log保留数据的全量备份并使用log合并技术完毕log自身的垃圾回收。

这样的方法,将服务层的众多复杂性移至log层。由于服务层是系统相关(system-specific)的,而log层确能够通用。

基于log系统,能够提供一组完备的、供开发使用的、可作为其他系统的ETL数据源、并供其他系统订阅的API。

Full Stack !

这里写图片描写叙述

显然。一个以log为核心的分布式系统,其本身马上成为了可对其他系统提供数据装载支持及数据流处理的角色。

同样的,一个流处理系统。也能够同一时候消费多个数据流,并通过对这些数据流进行索引然后输出的还有一个系统,来对外提供服务。

基于log层和服务层来构建系统。使得查询相关的因素与系统的可用性、一致性等因素解耦。

或许非常多人觉得在log中维护数据的单独备份。特别是做全量数据拷贝太浪费、太奢侈。但事实并不是如此:

1) linkedin(注:2013年)的kafka生产集群维护了每数据中心75TB的数据,而应用集群须要的存储空间和存储条件(SSD+很多其他的内存)比kafka集群要高。
2) 全文搜索的索引。最好全部装入内存,而logs由于都是线性读写,所以能够利用便宜的大容量磁盘。
3) 由于kafka集群实际运作在多个订阅者的模式之下,多个系统消费数据,所以log集群的开销被摊还了。
4) 全部以上原因。导致基于外部log系统(kafka或者相似系统)的开销变得非常小。

2.13 结语
Jay哥在最后,不仅厚道地留下了非常多学术、project上的有价值的论文和參考链接,还非常谦逊地留下了这句话:

If you made it this far you know most of what I know about logs.

终。

posted @ 2017-08-17 15:06  llguanli  阅读(1443)  评论(0编辑  收藏  举报