分享

the log:每个想玩大数据的人都该懂点的实时数据知识(续)

 昵称27299644 2015-08-31




数据流图

流处理最有趣的特点是它与流处理系统的内部组织无关,但是与之密切相关的是,流处理是怎么扩展了之前在数据集成讨论中提到的认识:输入数据是什么。

我们主要讨论了原始数据的feeds 或说日志 —— 各种系统执行所产生的事件和数据行。

但是流处理允许我们包括了由其它 feeds计算出的 feeds在消费者看来,这些派生的 feeds和 用于生成他们的原始数据的feeds 看下来没什么差别。这些派生的 feeds可以按任意的复杂方式封装组合。

让我们再深入一点这个问题。

对于我们的目标,流处理作业是指从日志读取数据和将输出写入到日志或其它系统的任何系统。

用于输入和输出的日志把这些处理系统连接成一个处理阶段的图。

事实上,以这样的风格使用中心化的日志,你可以把组织全部的数据抓取、转化和工作流仅仅看成是一系列的写入它们的日志和处理过程。

流处理器根本不需要高大上的框架:
可以是读写日志的一个处理或者一组处理过程,但是为了便于管理处理所用的代码,可以提供一些额外的基础设施和支持。

在集成中日志的目标是双重的:

首先,日志让各个数据集可以有多个订阅者并使之有序。让我们回顾一下『状态复制』原理来记住顺序的重要性。为了更具体地说明,设想一下从数据库中更新数据流 —— 如果在处理过程中把对同一记录的两次更新重新排序,可能会产生错误的输出。

这里的有序的持久性要强于 TCP之类所提供的有序,因为不局限于单一的点对点链接,并且在流程处理失败和重连时仍然要保持有序。

其次,日志提供了处理流程的缓冲。这是非常基础重要的。如果多个处理之间是非同步的,那么生成上行流数据的作业生成数据可能比另一个下行流数据作业所能消费的更快。

这种情况下,要么使处理进程阻塞,要么引入缓冲区,要么丢弃数据。
丢弃数据似乎不是个好的选择,而阻塞处理进程,会使得整个的数据流的图被迫中止处理。

日志是一个非常非常大的缓冲,允许处理进程的重启或是失败,而不影响流处理图中的其它部分的处理速度。要扩展数据流到一个更庞大的组织,这种隔离性极其重要,整个处理是由组织中不同的团队提供的处理作业完成的。不能因为某个作业发生错误导致影响前面作业,结果整个处理流程都被卡住。

StormSama都是按这种风格构建,能用 kafka或其它类似的系统作为它们的日志。

有状态的实时流处理

一些实时流处理做的只是无状态的单次记录的转换,但有很多使用方式需要在流处理的某个大小的时间窗口内进行更复杂的计数、聚合和关联操作。

比如,给一个的事件流(如用户点击的流)附加上做点击操作用户的信息,—— 实际上即是关联点击流到用户的账户数据库。

这类流程最终总是要处理者维护一些状态信息:比如在计算一个计数时,需要维护到目前为止的计数器。在处理者可能挂掉的情况下,如何维护正确的状态?

最简单的方案是把状态保存在内存中。但是如果处理流程崩溃,会丢失中间状态。如果状态是按窗口维护的,处理流程只能会回退到日志中窗口开始的时间点上。但是,如果计数的时间窗口是1个小时这么长,那么这种方式可能不可行。

另一个方案是简单地存储所有的状态到远程的存储系统,通过网络与这些存储关联起来。但问题是没了数据的局部性并产生很多的网络间数据往返。

如何才能即支持像处理流程一样分片又支持像『表』一样的存储呢?

回顾一下关于表和日志二象性的讨论。它正好提供了把流转换成与这里我们处理中所需的表的工具,同时也是一个解决表的容错的处理机制。

流处理器可以把它的状态保存在本地的『表』或『索引』中 —— bdbleveldb甚至是些更不常见的组件,如 Lucenefastbit索引。这样一些存储的内容可以从它的输入流生成(可能做过了各种转换后的输入流)。通过记录关于本地索引的变更日志,在发生崩溃、重启时也可以恢复它的状态。这是个通用的机制,用于保持 任意索引类型的协作分片的本地状态与输入流数据 一致。

当处理流程失败时,可以从变更日志中恢复它的索引。每次备份时,即是日志把本地状态转化成一种增量记录。

这种状态管理方案的优雅之处在于处理器的状态也是做为日志来维护。
我们可以把这个日志看成是数据库表变更的日志。事实上,这些处理器本身就很像是自维护的协作分片的表。因为这些状态本身就是日志,所以其它处理器可以订阅它。如果处理流程的目标是更新结点的最后状态并且这个状态又是流程的一个自然的输出,那么这种方式就显得尤为重要。

再组合使用上用于解决数据集成的数据库输出日志,日志和表的二象性的威力就更加明显了。从数据库中抽取出来的变更日志可以按不同的形式索引到各种流处理器中,以关联到事件流上。

Samza和这些大量实际例子中,我们说明了这种风格的有状态流处理管理的更多细节。

日志合并

当然,我们不能奢望一直保存着全部变更的完整日志。除非想要使用无限空间,日志总是要清理。为了让讨论更具体些,我会介绍一些 Kafka这方面的实现。在 Kafka 中,清理有两种方式,取决于数据包括的是键值存储的更新还是事件数据。

对于事件数据, Kafka 支持仅维护一个窗口的数据。通常,窗口配置成几天,但窗口也可以按空间大小来定。对于键值存储的更新,尽管完整日志的一个优点是可以回放以重建源系统的状态(一般是另一个系统中重建)。

但是,随着时间的推移,保持完整的日志会使用越来越多的空间,并且回放的耗时也会越来越长。因此在 Kafka中,我们支持不同类型的保留方式。我们删除过时的记录(如这些记录的主键最近更新过)而不是简单的丢弃旧日志。

这样做我们仍然保证日志包含了源系统的完整备份,但是现在我们不再重现原系统曾经的所有状态,仅是最近的哪些状态。这一功能我们称之为 日志合并。

最后我要讨论的是在线数据系统设计中日志的角色。

日志服务在分布式数据库中服务于数据流 可以类比 日志服务在大型组织机构中服务于数据集成。在这两个应用场景中,日志要解决的问题 都是 数据流、一致性和可恢复性。如果组织不是一个很复杂的分布式数据系统呢,它究竟是什么?

分解单品方式而不是打包套餐方式?

如果你换个角度,可以把组织的系统和数据流整体看做整个一个分布式数据:
把所有独立的面向查询的系统(如 RedisSOLRHive表,等等)看做只是你的数据的特定的索引;
把流处理系统(如 StormSamza)看做只是一种很成熟的触发器和视图的具体机制。

我注意到,传统的数据库人员非常喜欢这样的观点,因为他们终于能解释通,这些不同的数据系统到底是做什么用的—— 它们只是不同的索引类型而已!

不可否认这段时间涌现了大量类型的数据系统,但实际上,这方面的复杂性早就存在。即使是在关系数据库的鼎盛时期,组织里就有种类繁多的关系数据库!因为大型机,所有的数据都存储在相同的位置,所以可能并没有真正的数据集成。有很多推动力要把数据分离到多个系统:数据伸缩性、地理地域、安全性和性能隔离是最常见的。

这些问题可以通过一个好的系统来解决:比如组织使用单个 Hadoop保存所有数据来服务大量各式各样的客户,这样做是可能的。

所以处理的数据向分布式系统变迁的过程中,已经有了个可能的简单方法:把大量的不同系统的小实例合到少数的大集群中。

许多的系统还不足好到支持这个方法:它们没有安全,或者不能保证性能隔离性,或者伸缩性不够好。不过这些问题都是可以解决的。

依我之见,不同系统大量涌现的原因是构建分布式数据系统的难度。
把关注点消减到单个查询类型或是用例,各个系统可以把关注范围控制到一组能构建出来的东西上。但是把全部这些系统运行起来,这件事有非常多的复杂性。

我觉得解决这类问题将来有三个可能的方向:

第一种可能性是延续现状:各个分离的系统在往后很长的一段时间里基本保持不变。发生这种可能要么是因为建设分布式系统的困难很难克服,要么系统的专用化能让各个系统的便得性和能力达到一个新的高度。
只要现状不变,为了能够使用数据,数据集成问题将仍会最核心事情之一。如果是这样,用于集成数据的外部日志将会非常的重要。

第二种可能性是一个统一合并的系统,这个系统具备足够的通用性,逐步把所有不同的功能合并成单个超极系统。这个超级系统表面看起来类似关系数据库,但在组织中使用方式会非常不一样,因为只能用一个大系统而不是无数个小系统。在这样的世界里,除了系统自身的内部,不存在真正的数据集成问题。我觉得,因为建设这样的系统的实际困难,使这个情况不太可能发生。

还有另一种可能的结果,呃,其实我觉得这个结果对工程师很有吸引力。新一代数据系统的一个让人感兴趣的特征是,这个系统几乎是完全开源的。开源提供了另一个可能性:数据基础架构不用是打包套餐式的而是分解单品成一组服务及面向应用的 API

Java栈中,你可以看到这种状况在一定程度上已经发生了:

  • Zookeeper处理系统之间的协调的很多问题。
    (或许诸如 HelixCurator等高级别抽象可以有些帮助)。

  • MesosYARN
    处理虚拟化( virtualization)和资源管理。

  • LuceneLevelDB等嵌入式类库做为索引。

  • NettyJetty 和 更高层封装如 Finaglerest.li处理远程通信。

  • AvroProtocol BuffersThriftumpteen zillion等其它类库处理序列化。

  • KafkaBookeeper提供后端支持的日志。

如果你把上面这些叠成一堆,换个角度去看,它会有点像是乐高版的分布式数据系统工程。你可以把这些零件拼装在一起,创建大量的可能的系统。

显然,上面说的不是面向 主要关心 APIAPI实现的最终用户,但在一个更多样化和模块化且持续演变的世界中,这可能一条途径可以通往简洁的单个系统。因为随着可靠的、灵活的构建模块的出现,实现分布式系统的时间由年缩减为周,聚合形成大型整体系统的压力将会消失。

日志在系统架构中的地位

提供外部日志的系统允许各个系统抛弃很多各自的复杂性,依靠共享的日志。在我看来,日志可以做到以下事情:

  • 通过对节点的并发更新的排序处理,处理了数据一致性(无论即时的还是最终的一致)

  • 提供节点之间的数据复制

  • 为写入者提供『提交』语义(仅当写入数据确保不会丢失时才会收到完成确认)

  • 为系统提供外部的数据订阅

  • 对于丢失数据的失败了的复本,提供恢复或是启动一个新复本的能力

  • 调整节点间的数据平衡

这就是一个数据分布式系统所要做的主要部分,实际上,剩下的大部分内容是与最终用户要面对的查询 API和索引策略相关的。这正是不同系统间的应该变化的部分,例如:一个全文搜索查询语句可能需要查询所有的分区,而一个主键查询只需要查询负责这个主键数据的单个节点就可以了。

下面我们来看下系统是如何工作的。

系统被分为两个逻辑部分:日志和服务层。日志按顺序捕获状态变化。
服务节点存储索引提供查询服务需要的所有信息(比如键值存储的索引可能会类似 BTreeSSTable,搜索系统可能用的是倒排索引)。写入操作可以直接进入日志,尽管可能经过服务层的代理。

在写入日志的时候会生成逻辑时间戳(称为日志中的索引),如果系统是分区的,我假定是会分区,那么日志和服务节点会包含相同分区个数,尽管两者的机器台数可能相差很多。

服务节点订阅日志,并按照日志存储的顺序尽快把日志写到它的本地索引中。

客户端只要在查询语句中提供某次写入操作的时间戳,就可以有从任何节点『读到该次写入』的语义 ——服务节点收到该查询语句后,会将其中的时间戳与自身索引的位置比较,如果必要,服务节点会延迟请求直到它的索引至少已经跟上那个时间戳,以避免提供的是旧数据。

服务节点可能会或可能不会需要感知 master身份或是当选 leader。对很多简单的使用场景,服务节点集群可以完全无需 leader,因为日志是正确真实的信息源。

分布式系统所需要处理的一件比较复杂的事是 恢复失败节点 和 在结点之间移动分区。典型的做法是仅保留一个固定窗口的数据,并把这个数据和分区中存储数据的一个快照关联。另一个相同效果的做法是,让日志保留数据的完整拷贝,并 对日志做垃圾回收。这把大量的复杂性从特定于系统的系统服务层移到了通用的日志中。

有了这个日志系统,你得到一个成熟完整的订阅 API,这个 API可以订阅数据存储的内容,驱动到其它系统的 ETL操作。事实上,许多系统都可以共享相同的日志以提供不同的索引,如下所示:

注意,这样的以日志为中心的系统是如何做到本身即是 在其它系统中要处理和加载的数据流的提供者的呢?同样,流处理器既可以消费多个输入流,然后通过这个流处理器输出把这些输入流的数据索引到其它系统中。

我觉得把系统分解成日志和查询 API的观点很有启迪性,因为使得查询相关的因素与系统的可用性和一致性方面解耦。我其实觉得这更是个好用的思路,可以对于没按这种方式构建的系统做概念上的分解。

值得一提的是,尽管 KafkaBookeeper都是一致性日志,但并不是必须的。你可以轻松把 Dynamo之类的数据库作为你的系统的最终一致的 AP日志和键值对服务层。这样的日志使用起来很灵活,因为它会重传了旧消息并依赖订阅者的信息处理(很像 Dynamo所做的)。

很多人认为在日志中维护数据的单独拷贝(特别是做全量数据拷贝)太浪费。然而事实上,有几个因素可以让这个不成为问题。首先,日志可以是一种特别高效的存储机制。在我们 Kafka生产环境的服务器上,每个数据中心都存储了超过75TB的数据。同时其它的许多服务系统需要的是多得多的内存来提供高效的服务(例如文本搜索,它通常是全在内存里)。

其次,服务系统会用优化过的硬件。例如,我们的在线数据系统或者基于内存提供服务或者使用固态硬盘。相反,日志系统只需要线性读写,因此很合适用TB级的大硬盘。

最后,如上图所示,多个系统使用日志数据提供服务,日志的成本是分摊到多个索引上。

上面几点合起来使得外部日志的开销相当小。

LinkedIn正是使用这个模式构建了它很多的实时查询系统。这些系统的数据来自数据库(使用作为日志概念的数据总线,或是来自Kafka的真正日志),提供了在这个数据流上特定的分区、索引和查询能力。这也是我们实现搜索、 social graphOLAP查询系统的方式。事实上,把单个数据源(无论来自 Hadoop的在线数据源还是派生数据源)复制到多个在线服务系统中,这个做法很常见。

这种方式经过了验证可以大大简化系统的设计。系统根本不需要给外部提供写入 APIKafka和数据库通过日志给查询系统提供记录和变更流。各个分区的结点在本地完成写操作。这些结点只要机械地把日志中的数据转录到自己的存储中。失败的结点通过回放上游的日志就可以恢复。

系统的强度取决于日志的使用方式。一个完全可靠的系统把日志用作数据分片、结点的存储、负载均衡,以及所有和数据一致性和数据传播有关的方面。在这样的架构中,服务层实际上只不过是一种『缓存』,可以通过直接写日志就能完成某种处理。如果你从头一直做读到了这,那么我对日志的理解你大部分都知道了。


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多