在使用 Kafka 构建数据管道时,通常有两种使用场景:
第一种,把 Kafka 作为数据管道的两个端点之一:
例如,把 Kafka 里的数据移动到 S3 上,或者把 MongoDB 里的数据移动到 Kafka 里;
第二种,把 Kafka 作为数据管道两个端点的中间媒介:
例如,为了把 Twitter 的数据移动到 ElasticSearch 上,需要先把它们移动到 Kafka 里,再将它们从 Kafka 移动到 ElasticSearch 上。
LinkedIn 和其他一些大公司都将 Kafka 用在上述两种场景中,后来在 0.9 版本的Kafka 里增加了Kafka Connect(以下简称 Connect)。
Kafka 为数据管道带来的主要价值在于:
它可以作为数据管道各个数据段之间的大型缓冲区,有效地解耦管道数据的生产者和消费者。Kafka 的解耦能力以及在安全和效率方面的可靠性,使它成为构建数据管道的最佳选择。
有些应用把 Kafka 看成是数据管道的一个端点,他们会想“我怎么才能把数据从 Kafka 移到 ElasticSearch 里”。
这么想是理所当然的,不过我们要讨论的是如何在更大的场景里使用 Kafka,这些场景至少包含两个端点(可能会更多),而且这些端点都不是 Kafka。对于那些面临数据集成问题的人来说,我们建议他们从大局考虑问题,而不只是把注意力集中在少量的端点上。过度聚焦在短期问题上,只会增加后期维护的复杂
性,付出更高的成本。
本篇和下面几篇将讨论在构建数据管道时需要考虑的几个常见问题。这些问题并非 Kafka 独有,它们
都是与数据集成相关的一般性问题。
首先,本篇会着重讨论在集成多个系统时需要考虑的几个最重要的问题。
有些系统希望每天一次性地接收大量数据,而有些则希望在数据生成几毫秒之内就能拿到它们。大部分数据管道介于这两者之间。一个好的数据集成系统能够很好地支持数据管道的各种及时性需求,而且在业务需求发生变更时,具有不同及时性需求的数据表之间可以方便地进行迁移。
Kafka 作为一个基于流的数据平台,提供了可靠且可伸缩的数据存储, 可以支持几近实时的数据管道和基于小时的批处理。
生产者可以频繁地向 Kafka 写入数据,也可以按需写入;
消费者可以在数据到达的第一时间读取它们,也可以每隔一段时间读取一次积压的数据。
Kafka 在这里扮演了一个大型缓冲区的角色,降低了生产者和消费者之间的时间敏感度。
实时的生产者和基于批处理的消费者可以同时存在,也可以任意组合。实现回压策略也因此变得更加容易,Kafka 本身就使用了回压策略(必要时可以延后向生产者发送确认),消费速率完全取决于消费者自己。
除了及时性的要求,还需要避免单点故障,并能够自动从各种故障中快速恢复。
数据通过数据管道到达业务系统,哪怕出现几秒钟的故障,也会造成灾难性的影响,对于那些要求毫秒级的及时性系统来说尤为如此。
数据传递保证是可靠性的另一个重要因素。有些系统允许数据丢失,不过在大多数情况下,它们要求至少一次传递。也就是说,源系统的每一个事件都必须到达目的地,不过有时候需要进行重试,而重试可能造成重复传递。有些系统甚至要求仅一次传递——源系统的每一个事件都必须到达目的地,不允许丢失,也不允许重复。
之前讨论过 Kafka 的可用性和可靠性保证。Kafka 本身就支持“至少一次传递”。
如果再结合具有事务模型或唯一键特性的外部存储系统,Kafka 也能实现“仅一 次传递”。
因为大部分的端点都是数据存储系统,它们提供了“仅一次传递”的原语支持, 所以基于 Kafka 的数据管道也能实现“仅一次传递”。
值得一提的是,Connect API 为集成外部系统提供了处理偏移量的 API,连接器因此可以构建仅一次传递的端到端数据管道。 实际上,很多开源的连接器都支持仅一次传递。
为了满足现代数据系统的要求,数据管道需要支持非常高的吞吐量。更重要的是,在某些 情况下,数据管道还需要能够应对突发的吞吐量增长。
由于将 Kafka 作为生产者和消费者之间的缓冲区,消费者的吞吐量和生产者的吞吐量就不会耦合在一起了。我们也不再需要实现复杂的回压机制,如果生产者的吞吐量超过了消费者的吞吐量,可以把数据积压在 Kafka 里,等待消费者追赶上来。
通过增加额外的消费者或生产者可以实现 Kafka 的伸缩,因此我们可以在数据管道的任何一边进行动态的伸缩,以便满足持续变化的需求。
因为 Kafka 是一个高吞吐量的分布式系统,一个适当规模的集群每秒钟可以处理数百兆的数据,所以根本无需担心数据管道无法满足伸缩性需求。另外,Connect API 不仅支持伸缩,而且擅长并行处理任务。稍后,我们将会介绍数据源和数据池(Data Sink)如何在多个线程间拆分任务,最大限度地利用 CPU 资源,哪怕是运行在单台机器上。
Kafka 支持多种类型的压缩,在增长吞吐量时,Kafka 用户和管理员可以通过压缩来调整网络和存储资源的使用。
数据管道需要协调各种数据格式和数据类型,这是数据管道的一个非常重要的因素。
数据类型取决于不同的数据库和数据存储系统。你可能会通过 Avro将XML或关系型数据加载到Kafka 里,然后将它们转成JSON 写入 ElasticSearch,或者转成 Parquet 写入 HDFS,或者转成 CSV 写入 S3。
Kafka 和 Connect API 与数据格式无关。之前我们介绍过,生产者和消费者可以使用各种序列化器来表示任意格式的数据。
Connect API 有自己的内存对象模型,包括数据类型和 schema。不过,可以使用一些可插拔的转换器将这些对象保存成任意的格式,也就是说,不管数据是什么格式的,都不会限制我们使用连接器。
很多数据源和数据池都有 schema,我们从数据源读取 schema,把它们保存起来,并用它们验证数据格式的兼容性,甚至用它们更新数据池的 schema。
从 MySQL 到 Hive 的数据管道就是一个很好的例子。如果有人在 MySQL 里增加了一个字段,那么在加载数据时,数据管道可以保证 Hive 里也添加了相应的字段。
另外,数据池连接器将 Kafka 的数据写入外部系统,因此需要负责处理数据格式。有些连接器把数据格式的处理做成可插拔的,比如 HDFS 的连接器就支持 Avro 和 Parquet。
通用的数据集成框架不仅要支持各种不同的数据类型,而且要处理好不同数据源和数据池之间的行为差异。例如,在关系型数据库向 Syslog 发起抓取数据请求时,Syslog 会将数据推送给它们,而 HDFS 只支持追加写入模式,只能向 HDFS 写入新数据,而对于其他很多系统来说,既可以追加数据,也可以更新已有的数据。
数据转换比其他需求更具争议性。数据管道的构建可以分为两大阵营,即 ETL 和 ELT。
表示提取—转换—加载(Extract-Transform-Load)。
也就是说,当数据流经数据管道时,数据管道会负责处理它们。这种方式为我们节省了时间和存储空间,因为不需要经过保存数据、修改数据、再保存数据这样的过程。
不过,这种好处也要视情况而定。有时候,这种方式会给我们带来实实在在的好处,但也有可能给数据管道造成不适当的计算和存储负担。
这种方式有一个明显不足,就是数据的转换会给数据管道下游的应用造成一些限制,特别是当下游的应用希望对数据进行进一步处理的时候。假设有人在 MongoDB 和 MySQL 之间建立了数据管道,并且过滤掉了一些事件记录,或者移除了一些字段,那么 下游应用从 MySQL 中访问到的数据是不完整的。如果它们想要访问被移除的字段,只能 重新构建管道,并重新处理历史数据(如果可能的话)。
表示提取—加载—转换(Extract-Load-Transform)。
在这种模式下,数据管道只做少量的转换(主要是数据类型转换),确保到达数据池的数据尽可能地与数据源保持一致。这种情况也被称为高保真(high fidelity)数据管道或数据湖(data lake)架构。
目标系统收集“原始数据”,并负责处理它们。
这种方式为目标系统的用户提供了最大的灵活性,因为它们可以访问到完整的数据。
在这些系统里诊断问题也变得更加容易,因为数据被集中在同 一个系统里进行处理,而不是分散在数据管道和其他应用里。
这种方式的不足在于,数据的转换占用了目标系统太多的 CPU 和存储资源。有时候,目标系统造价高昂,如果有可能人们希望能够将计算任务移出这些系统。
安全性是人们一直关心的问题。对于数据管道的安全性来说,人们主要关心如下几个方面。
Kafka 支持加密传输数据,从数据源到 Kafka,再从 Kafka 到数据池。还支持认证(通过 SASL 来实现)和授权。所以可以保证,如果一个主题包含了敏感信息,在不经授权的情况下,数据是不会流到不安全的系统里的。
Kafka 还提供了审计日志用于跟踪访问记录。 通过编写额外的代码,还可能跟踪到每个事件的来源和事件的修改者,从而在每个记录之间建立起整体的联系。
我们不能总是假设数据是完美的,而要事先做好应对故障的准备。
因为 Kafka 会长时间地保留数据,所以我们可以在适当的时候回过头来重新处理出错的数据。
数据管道最重要的作用之一是解耦数据源和数据池。它们在很多情况下可能发生耦合。例如下面这些:
有些公司为每一对应用程序建立单独的数据管道。
例如,使用 Logstash 向ElasticSearch 导入日志,使用 Flume 向 HDFS 导入日志,使用GoldenGate 将 Oracle 的数据导到 HDFS,使用 Informatica 将 MySQL 的数据或 XML 导到 Oracle,等等。
他们将数据管道与特定的端点耦合起来,并创建了大量的集成点,需要额外的部署、维护和监控。当有新的系统加入时,他们需要构建额外的数据管道,从而增加了采用新技术的成本,同时遏制了创新。
如果数据管道没有保留 schema 元数据,而且不允许 schema 发生变更,那么最终会导致生产者和消费者之间发生紧密的耦合。
没有了 schema,生产者和消费者需要额外的信息来解析数据。假设数据从 Oracle 流向 HDFS,如果 DBA 在 Oracle 里添加了一个字段,而且没有保留 schema 信息,也不允许修改 schema,那么从 HDFS 读取数据时可能会发生错误,因此需要双方的开发人员同时升级应用程序才能解决这个问题。
不管是哪一种情况,它们的解决方案都不具备灵活性。
如果数据管道允许 schema 发生变更,应用程序各方就可以修改自己的代码,无需担心对整个系统造成破坏。
我们在讨论数据转换时就已提到,数据管道难免要做一些数据处理。
在不同的系统之间移动数据肯定会碰到不同的数据格式和不同的应用场景。**不过,如果数据管道过多地处 **
理数据,那么就会给下游的系统造成一些限制。
在构建数据管道时所做的设计决定都会对下游的系统造成束缚,比如应该保留哪些字段或应该如何聚合数据,等等。
如果下游 的系统有新的需求,那么数据管道就要作出相应的变更,这种方式不仅不灵活,而且低效、不安全。
更为灵活的方式是尽量保留原始数据的完整性,让下游的应用自己决定如何处理和聚合数据。
参考这里