注意: 这一章还未完成编辑,你阅读的内容中还存在大量机器翻译的结果
第4章 使用日志构建数据系统
我要讨论的最后一个主题是日志在联机数据系统内部的作用。
日志在分布式数据库内部用于数据流的作用与它在大型组织中用于数据集成的作用之间存在类比。在这两种情况下,它都负责数据流,一致性和恢复。毕竟,一个组织如果不是一个非常复杂的分布式数据系统,那又是什么呢?
也许斜视一下,你可以将整个组织的系统和数据流视为一个非常复杂的分布式数据库。你可以将所有单个面向查询的系统(Redis,SOLR,Hive表等)查看为数据上的特定索引。你可以将诸如Storm或Samza之类的流处理系统视为一种非常完善的触发和视图实现机制。我已经注意到,古典数据库人员非常喜欢这种观点,因为它最终向他们解释了人们在使用所有这些不同数据系统的工作-它们只是不同的索引类型!
不可否认,现在数据类型的爆炸式增长,但是实际上,这种复杂性一直存在。即使在关系数据库的鼎盛时期,组织也有许多关系数据库!因此,自从大型机真正将所有数据都放在一个地方以来,可能就没有真正的集成。将数据分为多个系统的动机很多:规模,地理位置,安全性和性能隔离是最常见的。好的系统可以解决这些问题。例如,一个组织可能有一个包含所有数据并服务于众多不同群体的Hadoop集群。
因此,在迁移到分布式系统中时,数据处理已经存在一种可能的简化:将每个系统的许多小实例合并为几个大集群。许多系统尚不足以允许这样做,因为它们没有安全性,不能保证性能隔离或只是扩展性不够好。但是,这些问题都是可以解决的。你可以运行一个由整个组织的所有应用程序共享的大型多租户系统,而不必运行一个系统的许多小型单服务器实例。这样可以极大地提高管理和利用效率。
拆分?
我认为不同系统的爆炸式增长是由于构建分布式数据系统的困难而引起的。通过减少到单个查询类型或用例,每个系统都可以将其范围缩小到可以构建的一组事物中。运行所有这些系统会产生太多的复杂性。
我看到将来可能会遵循三个可能的方向。
第一种可能性是现状的延续:系统的分离或多或少会保持更长的时间。之所以会发生这种情况,可能是因为分发的困难难以克服,或者是因为这种专业化使每个系统的便利性和功能性达到了新的水平。只要这是正确的,数据集成问题将仍然是成功使用数据的最重要的重要问题之一。在这种情况下,集成数据的外部日志将非常重要。
第二种可能性是可能进行重新整合,其中具有足够普遍性的单个系统开始将所有不同功能合并回单个ubersystem。这个超级系统从表面上看就像关系数据库一样,但是在组织中的使用却大不相同,因为你只需要一个大型系统,而不需要多个小型系统。在这个世界上,除了系统内部要解决的问题之外,没有真正的数据集成问题。我认为建立这样一个系统的实际困难使这不太可能。
还有另一种可能的结果,也是很吸引作为工程师的我的一种。新一代数据系统的一个有趣方面是,它们几乎都是开源的。开源提供了另一种可能性:数据基础架构可以捆绑为服务和面向应用程序的系统API的集合。你已经在Java堆栈中看到了这种情况:
- Zookeeper可以处理大部分系统协调工作(也许会得到来自更高级的抽象,例如Helix或Curator)。
- Mesos和YARN处理虚拟化和资源管理。
- 诸如Lucene,RocksDB和LMDB之类的嵌入式库都可以建立索引。
- Netty,Jetty和Finagle和rest.li等更高级别的包装程序可以远程处理通讯。
- Avro,协议缓冲区,Thrift和不计其数的其他库处理序列化。
- Kafka和BookKeeper提供了备份日志。
如果将这些东西堆放并斜视一下,它将开始看起来像是LEGO版本的分布式数据系统工程。你可以将这些成分拼凑在一起,以创建大量可能的系统。对于最终用户而言,这显然不是一个故事,他们可能更关心API而不是其实现方式,但这可能是在不断发展的更加多样化和模块化的世界中实现单个系统的简单性的一条途径。如果分布式系统的实施时间从数年到数周不等,因为出现了可靠,灵活的构建基块,那么合并为单个整体系统的压力就消失了。
日志在系统架构中的位置
假定存在外部日志的系统允许各个系统放弃自己的大部分复杂性,并依靠共享日志。日志可以执行以下操作:
- 通过对并发进行排序来处理数据一致性(无论是最终的还是即时的)更新节点
- 提供节点之间的数据复制
- 向作者提供“提交”语义(例如仅在你的写保证不会丢失)
- 提供系统的外部数据订阅源
- 提供恢复丢失数据或引导新的失败副本的功能复制品
- 处理节点之间的数据重新平衡
这实际上是分布式数据系统所做工作的重要部分。实际上,剩下的大部分与最终面向客户的查询API和索引策略有关。这正是系统之间应有所不同的部分;例如,全文搜索查询可能需要查询所有分区,而按主键查询可能只需要查询负责该键数据的单个节点。
这是这样的。该系统分为两个逻辑部分:日志和服务层。日志按顺序捕获状态更改。服务节点存储服务查询所需的任何索引(例如,键值存储可能具有btree或sstable之类的内容,而搜索系统则具有反向索引的内容)。写入可以直接进入日志,尽管它们可能被服务层代理。写入日志会产生逻辑时间戳记(例如日志中的索引)。如果系统已分区(我假设已分区),那么日志和服务节点将具有相同数量的分区,即使它们的计算机数量可能非常不同。
服务节点订阅日志,并按照日志存储它们的顺序,尽快将写入操作应用于其本地索引(请参见图4-1)。
通过提供写入的时间戳作为其查询的一部分,客户端可以从任何节点获取“读取时写入”的语义。接收到此类查询的服务节点会将所需的时间戳记与其自身的索引点进行比较,并在必要时将请求延迟,直到它已被索引到至少那个时间为止,以避免服务过时的数据。
服务节点可能需要也可能不需要掌握所有权或领导者选举的概念。对于许多简单的用例,由于日志是事实的来源,因此服务节点可以完全没有领导者。
分布式系统必须做的棘手的事情之一是处理恢复故障节点或将分区从一个节点移到另一个节点。一种典型的方法是使日志仅保留一个固定的数据窗口,并将其与分区中存储的数据快照结合起来。日志也可以保留完整的数据副本并压缩日志本身。这将大量的复杂性从服务层(这是系统特定的)移出并移到了日志(这可以是通用的)中。
通过使用此日志系统,你可以获得针对ETL馈入其他系统的数据存储内容的完整开发的订阅API。实际上,许多系统可以在提供不同索引的同时共享同一日志,如图4-2所示:
请注意,这种以日志为中心的系统本身如何立即成为数据流的提供者,以便在其他系统中进行处理和加载。同样,流处理器可以消耗多个输入流,然后通过另一个对输出进行索引的系统为它们提供服务。
我发现作为日志和查询API的因素,这种系统视图非常有启发性,因为它使你可以将查询特征与系统的可用性和一致性方面分开。我实际上认为,这甚至是一种有用的方法,可以从心理上考虑并非以这种方式构建的系统以更好地理解它。
值得注意的是,尽管Kafka和BookKeeper是一致的日志,但这不是必需的。你可以轻松地将类似Dynamo的数据库纳入最终一致的AP日志和键值服务层。这样的日志有点棘手,因为它会重新传递旧消息,并取决于订阅者来处理(就像Dynamo本身一样)。
在日志中拥有单独的数据副本(尤其是完整副本)的想法使许多人感到浪费。实际上,有一些因素使这不再是一个问题。首先,日志可以是一种特别有效的存储机制。在实时数据中心中,我们在Kafka中拥有PB级日志存储的绝大部分。同时,许多服务系统需要更多的内存才能有效地提供数据(例如,文本搜索通常全部位于内存中)。服务系统也可能使用优化的硬件。例如,我们大多数实时数据系统要么内存不足,要么使用SSD。相比之下,日志系统仅执行线性读取和写入,因此使用大型多TB硬盘驱动器非常满意。最后,如图4-2所示,在数据由多个系统提供服务的情况下,日志成本将在多个索引上摊销。这种组合使外部日志的开销相当小。
这正是LinkedIn用来构建其自己的许多实时查询系统的模式。这些系统将数据库更新直接生成的日志或其他实时处理派生的日志作为输入,并在该数据流之上提供特定的分区,索引和查询功能。这就是我们实现搜索,社交图谱,新闻源和OLAP查询系统的方式。实际上,将单个数据源(无论是实时源还是从Hadoop派生的源)复制到多个服务系统中进行实时服务是很普遍的。事实证明,这种面向日志的数据流是一个巨大的简化假设。这些系统完全不需要外部可访问的写API。Kafka和数据库用作记录系统,并通过该日志将流更改为适当的查询系统。写入由托管特定分区的节点在本地处理。这些节点将日志提供的提要盲目地转录到其自己的存储中。可以通过重播上游日志来恢复发生故障的节点。
这些系统对日志的依赖程度有所不同。完全依赖的系统可以利用日志进行数据分区,节点还原,重新平衡以及一致性和数据传播的所有方面。在此设置中,实际的服务层不过是一种缓存,该缓存的结构旨在实现特定类型的处理,而写入直接进入日志。
结论
日志为我们提供了一种原理化的方法,可以对通过分布式系统级联的不断变化的数据进行建模。这对于在大型企业中为数据流建模以及在分布式数据库中为数据流内部建模一样有效。有了这种基本的抽象,就可以为我们提供将各种数据系统粘合在一起,处理实时更改的方法,以及本身就是一个有趣的系统和应用程序体系结构的方法。从某种意义上说,我们所有的系统现在都是分布式系统,因此曾经有点分散的分布式数据库实现细节现在变成了与现代软件工程师非常相关的概念。
章节列表: