这是Jay Kreps在三月写的一篇文章,用来介绍Kafka Streams。当时Kafka Streams尚未正式发布,因此具体的API和功能和0.10.0.0版(2016年6月发布)有所区别。可是Jay Krpes在这简文章里介绍了不少Kafka Streams在设计方面的考虑,仍是很值得一看的。html
如下的并不会彻底按照原文翻译,由于那么搞太累了……这篇文件的确很长,并且Jay Kreps写的重复的地方也挺多,有些地方也有些故弄玄虚的意思。不过他想说的道理倒挺容易搞清楚。git
我很高兴能宣布Kafka的新特性-Kafka Streams的预览。Kafka Streams是一个使用Apache Kafka来构造分布式流处理程序的Java库。它前做为Kafka 0.10版本的一部分,其源码在Apache Kafka项目下。。github
使用Kafka Streams构建的一个流处理程序看起来像是这样:docker
须要注意的是:Kafka Streams是一个Java库,而不是一个流处理框架,这点和Strom等流处理框架有明显地不一样数据库
这个程序和0.10.0.0版在细节上有不少不一样。对Kafka 0.10.0.0版的Kafka Streams, 实际能运行的例子能够在Kafka Streams工程的examples包底下找到。须要注意的是,这个例子使用了lambda表达式,这是JAVA8的特性。apache
在KStream的构造上,体现了它跟Kafka的紧密关系。好比,它默认的输入流的元素就是K,V对形式的,输出流也是这样,所以在构造输入输出流时须要分别指定K和V的Serde。其中KStream的API使用了不少集合函数,像map, flatMap, countByKey等,这个也能够称为Kafka Streams的DSL。编程
虽然只是一个库,可是Kafka Streams直接解决了在流处理中会遇到的不少难题:windows
对于想要跳过这些前言,想直接看文档的人,大家能够直接去到Kafka Streams documention. 这个blog的目的在于少谈"what"(由于相关的文档会进行详细地描述),多谈"why"。缓存
Kafka Streams是一个用来构建流处理程序的库,特别是其输入是一个Kafka topic,输出是另外一个Kafka topic的程序(或者是调用外部服务,或者是更新数据库,或者其它)。它使得你以一种分布式以及容错的方式来作这件事情。性能优化
在流处理领域有不少正在进行的有趣的工做,包括像 Apache Spark, Apache Storm, Apache Flink, 和 Apache Samza这样的的开源框架,也包括像Google’s DataFlow 和 AWS Lambda同样的专有服务。全部,须要列一下Kafka Streams和这些东西的类似以及不一样的地方。
坦率地说,在这个生态系统中,有开源社区带来的很是多的各类杂乱地创新。咱们对于全部这些不一样的处理层(processing layer)感到很兴奋:尽管有时候这会让人感到有点困惑,可是技术水平的确在很快地进步。咱们想让Kafka可以成为全部这些处理层的合适的数据源。咱们想要Kafka Streams填充的空缺不大在于这些框架所关注的分析领域,而在于构建用于处理流式数据的核心应用和微服务。我在下面一节将会深刻讲述这些不一样之处,而且开始讲解Kafka Streams是怎么使这种类型的程序更简单的。
若是想要知道一个系统设计是否在真实状况下工做良好,惟一的方法就是把它构建出来,把它用于真实的程序,而后看看它有什么不足。在我以前在LinkedIn的工做中,我很幸运地可以成为设计和构造流处理框架Apache Samza的小组的成员。咱们把它推出到一系列内部程序之中,在生产中提供为它提供支持,而且帮助把它做为一个Apache项目开源。
那么,咱们学到了什么呢?不少。咱们曾经有过的一个关键的错觉是觉得流处理将会被以一种相似于实时的MapReduce层的方式使用。咱们最终却发现,大部分对流处理有需求的应用实际上和咱们一般使用Hive或者Spark job所作的事情有很大不一样,这些应用更接近于一种异步的微服务,而不是批量分析任务的快速版本。
我所说是什么意思呢?个人意思是大部分流处理程序是用来实现核心的业务逻辑,而不是用于对业务进行分析。
构建这样的流处理程序须要解决的问题和典型的MapReduce或Spark任务须要解决的分析或ETL问题是很是不一样的。它们须要一般的程序所经历的处理过程,好比配置,布署,监控,等。简而言之,它们更像是微服务(我知道这是一个被赋予了过多意义的名词),而不像是MapReduce任务。Kafka取代了HTTP请求为这样的流处理程序提供事件流(event streams)。
以前的话,人们用Kafka构造流处理程序时有两个选择:
1. 直接拿Consumer和Producer的API进行开发
2. 采用一个成熟的流处理框架
这两种选择各有不足。当直接使用Kafka consumer和producer API时,你若是想要实现比较复杂的逻辑,像聚合和join,就得在这些API的基础上本身实现,仍是有些麻烦。若是用流处理框架,那么就添加了不少不少复杂性,对于调试、性能优化、监控,都带来不少困难。若是你的程序既有同步的部分,又有异步的部分,那么就就不得不在流处理框架和你用于实现你的程序的机制之间分隔开。
虽然,事情不老是这样。好比你已经有了一个Spark集群用来跑批处理任何,这时候你想加一个Spark Streaming任务,额外添加的复杂性就挺小。可是,若是你专门为了一个应用布署一个Spark集群,那么这的确大大增长了复杂性。
可是,咱们对Kafka的定位是:它应该成为流处理的基本元素,因此咱们想要Kafka提供给你可以摆脱流处理框架、可是又具备很是小的复杂性的东西。
咱们的目的是使流处理足够简化,使它可以成为构造异步服务的主流编程模型。这有不少种方法,可是有三个大的方面是想在这个blog里深刻讨论一下:
这三个方面比较重要,因此把英文也列出来。
下面对每一个方面单独进行讨论。
Kafka Streams使得构建流处理服务更简单的第一点就是:它不依赖于集群和框架,它只是一个库(并且是挺小的一个库)。你只须要Kafka和你本身的代码。Kafka会协调你的程序代码,使得它们能够处理故障,在不一样程序实例间分发负载,在新的程序实例加入时从新对负载进行平衡。
我下面会讲一下为何我认为这是很重要的,以及咱们以前的一点经历,来帮助理解这个模型的重要性。
我前边讲到咱们构造Apache Samza的经历,以及人们实际想要的(简单的流服务)和咱们构建的东西(实时的MapReduce)之间的距离。我认为这种概念的错位是广泛的,毕竟流处理作的不少事情是从批处理世界中接管一些能力,用于低延迟的领域。一样的MapReduce遗产影响了其它主流的流处理平台(Storm, Spark等),就像它们对Samza的影响同样。
在LinkedIn在不少生产数据的处理服务是属于低延迟领域的:email, 用户生成的内容,新消息反馈等。其它的不少公司也应该有相似的异步服务,好比零售业须要给商品排序、从新订价,而后卖出,对于金融公司,实时数据更是核心。大部分这些业务,都是异步的,对于渲染页面或者更新移动app的屏幕就不会有这样的问题(这些是同步的)。
那么为何在Storm, Samza, Spark Streaming这样的流处理框架之上构建这样的核心应用这么繁琐呢?
一个批处理框架,像是MapReduce或者Spark须要解决一些困难的问题:
不幸的是,为了解决这些问题,框架就得变得颇有侵入性。为了作到容错和扩展,框架得控制你的程序如何布署、配置、监控和打包。
那么,Kafka Streams有什么不一样呢?
Kafka Streams对它想要解决的问题要更关注得多。它作了如下的事情:
它使用了Kafka为普通的consumer所提供的一样的组管理协议(group manager protocol)来实现。Kafka Streams能够有一些本地的状态,存储在磁盘上,可是它只是一个缓存。若是这个缓存丢失了,或者这个程序实例被转移到了别的地方,这个本地状态是能够被重建的。你能够把Kafka Streams这个库用在你的程序里,而后启动任意数量的你想要程序实例,Kafka将会把它们进行分区,而且在这些实例间进行负载的平衡。
这对于实现像滚动重启(rolling restart)或者无宕机时间的扩展(no-downtime expansion)这样简单的事情是很重要的。在现代的软件工程中,咱们把这样的功能看作是应该的,可是不少流处理框架却作不到这点。
从流处理框架中分离出打包和布署的缘由是,打包和布署这个领域自己就正在进行自身的复兴。Kafka Streams可使用经典的老实巴交维工具,像是Puppet, Chef, Salt来布署,把能够从命令行来启动。若是你年轻,时髦,你也能够把你的程序作成Dock镜像;或者你不是这样的人,那么你能够用WAR文件。
可是,对于寻找更加有灵活的管理方式的人,有不少框架的目标就是让程序更加灵活。这里列了一部分:
这个生态系统就和流处理生态同样专一。
的确,Mesos和Kubernets想要解决的问题是把进程分布到不少机器上,这也是当你布署一个Storm任务到Storm集群时,Storm尝试解决的问题。关键在于,这个问题最终被发现是挺难的,而这些通用的框架,至少是其中优秀的那些,会比其它的作得好得多-它们具备执行像在保持并行度的状况下重启、对主机的粘性(sticky host affinity)、真正的基于cgroup的隔离、用docker打包、花哨的UI等等功能。
你能够在这些框架里的任何一种里使用Kafka Streams,就像你会对其它程序作的同样,这是用来实现动态和有弹性的进程管理的一种简单的方式。好比,若是你有Mesos和Marathon,你可使用Marathon UI直接启动你的Kafka Streams程序,而后动态地扩展它,而不会有服务中断, Meos会管理好进程,Kafka会管理和负载匀衡以及维护你的任务进程的状态。
使用一种这些的框架的开销是和使用Storm这样的框架的集群管理部分是同样的,可是优势是全部这些框架都是可选的(固然,Kafka Streams没有了它们也能够很好的工做)。
Kafka Strems用于简化处理程序的另外一个关键方式是把“表”和"流“这两个概念紧密地结合在一块儿。咱们在以前的"turning the database inside out"中简化这个想法。那句话抓住了做为结果的系统是如何重铸程序和它的数据之彰的关系以及它是怎么应于数据变化,这样的要点。为了理想这些,我会回顾一下,解释我对于”table"和"stream"的定义,以及把两者结合在一块儿如何可以简化常见的异步程序。
传统的数据库都是关于在表格中存储状态的。当须要对事件流进行反应时,传统数据库作得并很差。什么是事件呢?事件只是一些已经发生了的事-能够是一个点击、一次出售、源自某个传感器的一个动态,或者抽象成任何这个世界上发生的事情。
像Storm同样的流处理程序,是从这个等式的另外一端出发的。它们被设计用于处理事件流,可是基于流来产生状态倒是后面才加进来的。
我认为异步程序的基本问题是把表明当前世界状态的tables与表明正在发生事件的event streams结合在一块儿。框架须要处理好如何表示它们,以及如何在它们之间进行转化。
为何说这些概念是相关的呢?咱们举一个零售商的简单例子。对于零售商而言,核心的事件流是卖出商品、订购新商品以及接收订购的商品。“库存表”是一个基于当前的存货量,经过售出和接收流进行加减的“表”。对于零售商而言两个关键的流处理动做是当库存开始下降时订购商品,以及根据供需关系调整商品价格。
在咱们开始研究流处理以前,让咱们先试着想解表和流的关系。我想在这里最好引用一下Pat Helland关于数据库和日志的话:
事务日志记录了对于数据库的全部改变。高速的append操做是日志发生改变的惟一方式。从这个角度来看,数据库保存了日志里最新记录的缓存。事实记录于日志中。数据库是一部分日志的缓存。被缓存的部分恰好是每一个记录的最新值,以及源自于日志的索引值。
这究竟是在说什么呢?它的意义实际上位于表和流的关系的核心。
让咱们以这个问题开始:什么是流呢?这很简单,流就是一系列的记录。Kafka把流建模成日志,也就是说,一个无尽的健/值对序列:
key1 => value1key2 => value2key1 => value3...
那么,什么是表呢?我认为咱们都知道,表就是像这样的东西:
Key1 |
Value1 |
Key2 |
Value3 |
其中value多是不少列,可是咱们能够忽略其中的细节,简单地把它们认为是KV对(添加更多的列并不会改变将要讨论的东西)。
可是当咱们的流随时间持续更新,新的记录出现了,这只是咱们的表在某个特定时间的snapshot。表格是怎么变化的呢?它们是被更新的。一个表实际上并非单一一个东西,而是像下面这样的一系列东西:
time = 0
key1 | value1 |
time = 1
Key1 |
Value1 |
Key2 |
Value2 |
time = 2
Key1 |
Value3 |
Key2 |
Value2 |
可是这个序列有一些重复。若是你把没有改变的行去掉,只记录更新,那么就能够用一个有序的更新序列来表示这张表:
可是,这不就变成流了吗?这种类型的流一般补称为changelog, 由于它展现了更新序列,按照更新的顺序记录了每一个记录的最新的值。
因此,表就是流之上的一个特殊的视图。这样说可能有些奇怪,可是我认为这种形式的表跟咱们脑海中的长方形的表对于“表其实是什么”是同样能够反映其本质的。或者,这样实际上更加天然,由于它抓住了“随时间改变”的概念(想想:有什么数据真的不会改变呢?)。
换句话说,就像Pat Helland指出的那样,一张表就是一个流里的每一个key的最新的值的缓存。
用数据库的术语来讲:一个纯粹的流就是全部的更新都被解释成INSERT语句(由于没有记录会替换已有的记录)的表,而一张表就是一个全部的改变都被解释成UPDATE的流(由于全部使用一样的key的已存在的行都会被覆盖)。
这种双面性被构建进Kafka中已经有一段时间了,它被以compacted topics的形式展示。
好的,这就是流和表是什么。那么,这跟流处理有啥关系呢?由于,最终你会发现,流和表的关系正是流处理问题的核心。
我上面已经给了一个零售商的例子,在这个例子里“商品到货”和“商品售出”这两个流的结果就是一个存货表,而对存货表的更改也会触发像“定货”和“更改价格”这样的处理。
在这个例子中,存货表固然不仅是在流处理框架中创造出来的东西,它们可能已经在一个数据库中了。那好,把一个由变化组成的流捕捉到一个表中被称为Change Capture, 数据库就作了这个事。Change capture数据流的格式就是我以前描述的changelog格式。这类型的change capture是你可使用Kafka Connect轻松搞定的事情,Kafka Connect是一个用于data capture的框架,是Kafka 0.9版本新加的。
经过以这种方式构建表的概念,Kafka使得你从变化流(stream of changes)获得的表中推导出数值。换句话说,就是让你能够像处理点击流数据同样处理数据库的变化流。
你能够把这种基于数据库变化触发计算的功能看做相似于数据库的触发器和物化视图功能,可是这个功能却不只限于一个数据库,也不只限于PL/SQL,它能够在数据中心的级别执行,而且能够工做于任何数据源。
咱们到了怎么样能够把一个把变成一个更新流(也是一个changelog),而且使用KafkaStreams基于它计算一些东西.可是表/流的双面性用相反的方式也是可行的.
假如你有一个用户的点击流,你想计算每一个分户的点击总数.KafkaStreams可让你计算这种聚合(aggregation),而且,你所计算出来的每一个用户的占击数就是一张表.
在实现时,Kafka Streams把这种基于流计算出来的表存储在一个本地数据库中(默认是RocksDB,可是你能够plugin其它数据库).这个Job的输出就是这个表的hcnagelog. 这个changelog是用于高可用的计算的(译注:就是当一个计算任务失败,而后在别的地方重启时,能够从失败以前的位置继续,而不用整个从新计算),可是它也能够被其它的KafkaStreams进程消费和处理,也能够用KafkaConnect导到其它的系统里.
这种支持本地存储的架构已经在Apache Samza中出现,我这前从系统架构的角度写过一篇关于这个的文章.KafkaStreams与Apache Samza的关键的革新是表的概念再也不是一个低层的基础设施,而是一个和stream同样的一等成员.Streams在Kafka Streams提供的programming DSL中用KStream类表示, 表是用KTable类表示.它们有一些共同的操做,也能够像表/流的双面性暗示的那样能够互相转换,可是,它们也有不一样之处.(译注:接下来的几句比较难懂,若是以为理解得不对,能够看原文).好比,对一个KTable执行取合操做时,Kafka Streams知道这个KTable底层是一个stream of updates,所以会基于这个事实进行处理.这样作是有必要的,是因对一个正在变化的表计算sum的语义跟对一个由不可变的更新组成的流计算sum的语义是彻底不一样的.与之类似的还有,join两个流(好比点击点和印象流)的语法和对一个表和一个流(好比点击流和帐户表)进行join的语义是彻底不一样的.经过用DSL对这两个概念进行建模,这些细节自动就分清楚了.(译注:我以为这段话的意思是就Kafka Streams会考虑到KTable底层其实是一个流,因此会采用与计算普通表的aggregation和join不一样的特殊的计算方式)
窗口、时间和乱序的事件是流处理领域的另外一个难搞的方面。可是,使人惊奇的是,能够证实的是一个简单的解决方案落到了表的概念上面。紧密关注流处理领域的人应该据说过"event time"这个概念,它被Google Dataflow团队的人很是有说服力地频繁地讨论。他们抓住的问题是:若是事件无序地达到,那么怎么作windowed操做呢?乱序的数据在大多数分布式场景下是没法避免的,由于咱们的确没法保证在不一样的数据中心或者不一样的设备上生成的数据的顺序。
在零售商的例子中,一个这种windowed computing的例子就是在一个十分钟的时间窗口中计算每种商品的售出数量。怎么能知道何时这个窗口结束了呢?怎么知道在这个时间段的全部出售事件都已经到达并且被处理了呢?若是这些都不能肯定的话,怎么能给出每种商口的售出总数的最终值呢?你不管在何时基于此时统计的数量作出答案,均可能会太早了,在后续可能会有更多的事件到达,使得你以前的答案是错误的。
Kafka Streams使得处理这个问题变得很简单:windowed aggregation的语义,例如count,就表示对于这个windows的“迄今为止"的count。随着新的数据的到达,它保持更新,下流的接收者能够自已决定何时完成统计。对,这个能够更新的数量的概念看起来莫名其秒的熟悉:它不是别的,就是一个表,被更新的windows就是key的一部分。天然而然的,下游操做知道这个流表示一个表,而且在这些更新到达时处理它们。
在数据库变化流之上进行计算和处理乱序事件的windowd aggregation,使用的是一样的机制,我认为这是很优雅的。这种表和流的关系并非咱们发明的,在旧的流处理的文章中,好比CQL中,已经展现了它的不少细节,可是这个理论却没有融合进大多数现实世界的系统——数据库处理表时、流处理系统处理流时,而且也没多数没有把二者都作为一等公民。
有一个基于我上边提出的一些特性的正在发展的功能可能不是那么明显。我讨论了Kafka Streams是如何让你透明地在RocksDB或其它本地数据结构中维护一个基于流推演出来的表的。由于这个处理的过程的状态都在物理上存在于你的程序中,这就开启了另外一项使人兴奋的新的用途的可能性:使得你的程序能够直接查询这个状态。
咱们当前还没暴露出来这个接口-咱们如今还专一于使得流处理的API先稳事实上下来,可是,我以为对于一些特定类型的数据敏感的程序,这是一个很吸引人的架构。
这意味着,你可构建,好比,一个嵌入了Kafka Streams的REST服务,它能够直接查询数据流经过流处理运算获得的本地的聚合结果。这种类型的有状态的服务的好处在这里讨论过。并非在全部领域这么作都合适,你一般只是想要把结果输出到一个外部的数据库中。可是,假如你的服务的每一个请求都须要访问不少数据,那么把这些数据放在本地内存或者一个很快的本地RocksDB实例中会很是有用。
咱们的全部这些的最高目录是使得构建和操做流处理程序的过程变得更简单。咱们的信念是流处理应该是一个构建应用程序的主流方式,公司所作的事情的很大一部分在异步领域,流处理正是用来干这个的。可是为了使这点成为现实,咱们还须要使Kafka Streams在这方面更简单更可依赖。这种对于操做的简化的一部分就是摆脱对外部集群的依赖,可是它还简化了其它的地方。
若是对人们是怎么构建流处理程序进行观察的话,你会发现除了框架自己,流处理程序倾向于具备高度的架构复杂性。这是一个典型的流处理程序的架构图。
(图)
这里有如此多的会变化的部分:
把这么一大堆东西弄下来不只不是人们想追求的,并且一般也是不现实的。即便你已经有了这个架构的全部部分,把它个整合在一块儿,把它监控好,可以发挥它的全部做用,也是很是很是困难的。
Kafka Streams的一个最使人欣喜的事情就是它的核心概念不多,并且它们贯穿于整个系统中。
咱们已经谈论过一些大的点:摆脱额外的流处理集群,把表格和有状态的处理彻底整合进流处理自己。使用Kafka Streams,这个架构能够瘦身成这样:
可是使得流处理变得简单这个目标比这两点远得多。
由于它直接构建于Kafka的基础操做之上,Kafka Streams很是小。它的整个代码基础只有不到九千行。你喜欢的话,一下行就看完了。这意味着你会遇到的除了Kafka本身的producer和consumer之外的复杂性是很容易承担的。
这有不少小的含义:
简单来讲一个kafka Strems程序在不少方面看起来就像其它的直接用Kafka producer或consumer写的程序同样,可是它写起来要简洁得多。
除了Kafka client暴露出来那些配置之外,额外的配置项很是少。
若是你改了代码,想要使用新的逻辑从新处理数据,你也不须要一个彻底不一样的系统。你只须要回退你程序的Kafka offsets,而后让它从新处理数据(你固然也能够在Hadoop端或者其它地方从新处理,可是关键是你能够选择不这么作).
尽管最初的样例架构是由一系列独立的组件组成,而且它们也只是部分地工做在一块儿,可是咱们但愿你未来会感受到Kafka、Kafka Connect和Kafka Streams就是为了一块儿工做而设计的。
就像其它的预览版同样,有一些功能咱们尚未完成。下面是一些将会添加进来的功能。
接下来会利用内置的表提供提供对程序状态的查询。
当前的KafkaStreams继承了Kafka的"at least once"的消息传递语义。Kafka社区正在探索如何实现跨Kafka Connect, Kafka, KafkaStream和其它计算引擎的消息传递语义。