原文:http://logallthethings.com/2015/09/15/kafka-by-example-kafka-as-unix-pipes/
什么是Kafka
想象下有这样的对话.
你: 什么是Apache Kafka?
我: Apache Kafka是发布-订阅消息系统,分布式的提交日志
你: ...什么?
我: 是的,它是一个分布式的,分区的,复制的提交日志服务
你: 你到底在说什么?
上面的描述(我)是正确的. 你只需要知道这些术语是什么意思, 但是如果你不知道这些术语,就会感到很困惑.
那就让我们以另外一种方式来解释吧. 我喜欢通过例子来学习, 并且在学习的时候通过和我已经知道的东西互相比较,
我发现这种学习方式非常有帮助. 那么我们就以这种举例子,并且比较的方式来描述什么是Kafka吧.
Kafka就像Unix的管道
我会给一些例子来说明Kafka能干什么, 比较的对象是很多人都熟悉的: 命令行的Unix管道
看一个简单的例子:
$ cat *.txt | tr A-Z a-z | grep hello
这段脚本找出以.txt结尾的文件中所有包含单词"hello"的行.它包含了三个步骤/阶段:
- 从所有文件中输出所有行
- 将所有文本转换为小写
- 找出含有"hello"单词的行
所有这些步骤的每一个都写到标准输出流,后面的阶段会从标准的输入流中读取.
最简单的来看, Kafka就像一个Unix的管道: 你将数据写到它的一端, 然后数据从另一端出来.
(严格来说,你写的数据会通过网络传输,你读取的数据也是通过网络,不过现在我们暂时忽略这些.)
如果这就是Kafka所能做的,那有什么了不起的,对吧?实际上Kafka还有一些额外的特征,带来新的能力.
结构化数据
Unix的管道在文本数据行之间流动,通常是以新的一行为结束(这条管道). 这些行可以很长,但是工作单元仍然是一行文本.
如果你处理的不是ASCII数据,或者你处理的数据不能以一一行来表示就会有点麻烦. 而Kafka支持任意的格式和任意大小.
这就允许你可以存储任何数据到Kafka中: 文本,CSV,二进制数据,自定义编码数据等等. 对于Kafka而言,它只是一系列的
消息,其中每条消息都是一系列的字节. 比如可以(模拟)写一个Kafka的"命令行":
$ TwitterFeed | filter_tweets From @apachekafka
这里的filter_tweets命令可能不是一个简单的基于字符串的grep,而是一种能够理解从TwitterFeed输出的数据格式.
比如TwitterFeed可能输出JSON,则filter_tweets需要做些JSON的处理.TwitterFeed如果返回的是二进制数据,
则filter_tweets需要知道二进制的格式/协议. 这种灵活性可以让Kafka成为一种发送任何数据类型的Unix管道.
数据持久化
我们可能有一个复杂的会花费一些时间才能跑完的命令.如果只运行一次,你可能不关心.但是如果你要多次迭代运行,
你可能会会将输出结果先写到一个文件中, 这样之后的阶段可以更快地迭代,而不需要重新多次运行很慢的那部分命令.
$ find . -type f | grep \.java > javafiles.txt
$ cat javafiles.txt | xargs grep ClassName
这个模式工作的很好,但是这意味着你需要提前计划去做(先写文件). 如果管道自身能够做这件时间就方便多了.
Kafka会持久化你发送的所有数据到磁盘上.持久化非常方便,不仅节省了你的一些时间,它还允许你能做之前不能做的一些事情.
就像上面的命令行一样,每个阶段的输出都被保存下来. 由于第一个阶段的输出被保存了,第二个阶段甚至不要求正在运行.
这种方式, Kafka作为生产者数据和消费者数据之间的缓冲区. 它保持了数据,允许消费者可用并且准备好的时候才读取数据.
Kafka是高性能的,它甚至可以运行在多台机器上,并且可以复制统一分数据到多台机器防止数据丢失造成的风险.
三个Kafka节点组成的集群能够处理每秒钟两百万的写入, 并能使网卡饱和.
由于数据被持久化到了Kafka中,并没有要求消费者要多快去读取数据.消费者可以想多快就多快,想多慢就多慢地读取数据.
因此它允许一个高性能的生产者, 并不会因为一个很慢的消费者而江堤生产者的性能. 看一个很慢的消费者的例子.
$ produceNumbers > numbers.txt
$ cat numbers.txt | xargs findPrimeFactorization
从密码学我们知道,将一个数字因式分解成质数是很慢的.假设我们分解了100万个数字,程序挂掉了.
当下次重启程序的时候如果能够从上次离开位置的那个点继续处理,而不是重复很多工作,那就很友好了.
以这个例子中,我期望的是从numbers.txt中的第一百万零一行开始继续处理.
Kafka有类似的概念叫做"offset".Kafka中的每条记录都被分配了有序的offset,消费者可以选择在指定的offset重新开始.
数据持久化和offsets这两个特性,允许你构建一个消费者数据和生产者数据分开的系统.
数据持久化--非常快的数据持久化--意味着它能很快地吸收大批量的数据.
它允许消费者按照它能够读取的任何速度读取数据.它允许持久化数据, 即使消费者挂掉了.
offsets允许消费者继续执行, 无论它上次在什么地方退出,而不会重复工作.
在某种情况下,这是很有意义的: 你并不想在一次汇款中从银行账号中扣了两次钱.
另一方面,这是出于效率方面考虑的: 你并不想重新对已经处理的数字重新进行因式分解.
无论哪种情况, 这两个特性都允许你做传统的Unix管道所不能做的事情.
流数据
再看下第一个例子:
$ cat *.txt | tr A-Z a-z | grep hello
在这里例子中,第一个阶段(cat)输出所有的行然后就结束了. 整个管道会找到所有包含单词"hello"的行最后命令结束. 和下面的命令进行比较:
$ tail -F *.txt | tr A-Z a-z | grep hello
这个命令不会结束, 第一个阶段(tail)输出一些行,但是仍然保持着监听更多的数据.
如果你在之后往其中的一个添加了一行,tail命令会输出这个新行, 然后接下来的命令会处理它.
Kafka支持相同的概念.数据写到到Kafka并且被消费者读取可以看做一个流.
如果消费者到达数据的末尾, 它会继续等待即将到来的更多的数据. 当新的数据写入到Kafka,它会很快地被发送到消费者.
我在之前说过数据流进Kafka是很快的, 实际上数据从Kafka流出也是很快的.
一条记录被添加到Kafka后,能够在20ms之内发送给一个正在等待的消费者.
现在我们知道Kafka除了支持数据持久化,也支持流数据. 我们复习下之前的例子
$ produceNumbers > numbers.txt
$ cat numbers.txt | xargs findPrimeFactorization
上面的命令看起来向上一种批处理模式,因为produceNumbers最终会结束的.
但是数字是无限的,它永远不会结束, 所以实际上看起来应该是这样的:
$ produceNumbers |* findPrimeFactorization
这里我自己造了一个语法: |*
表示这是一个Kafka管道.它能够归档所有东西到磁盘,并且发送流式的更新.
streaming updates流式更新, 数据是流式传入的,下游的方法基于最新的流数据做更新操作. 即对流数据更新操作
这种流式的数据允许你创建一个实时的管道,这里有个例子:
$ tail -F /var/log/httpd/access_log |* grep index.html |* get_load_time |* make_fancy_graph
这个管道会查询你的web服务器日志. 它会提取主页的所有pageload,获取出页面加载的时间,创建一个可视化的图,并及时更新.
太棒了,你刚刚创建了一台服务器的监控面板. 如果页面加载时间抖动,你可以在几秒内从图中观察到.
所有的这些Kafka管道(每个|*
)都会持久化和缓冲数据. 管道中的任何一个阶段都可能出错,并在任何时候重启,
并且可以在它们上次离开的地方继续. 它们可以处理的很慢,或者一直紧紧跟着(上一个阶段).
或者如果它们落后的太多,可以被停止,并移到新拥有更快CPU的服务器上,也能够从上一次作业离开的地方继续.
你还可以创建一些其他类型的实时管道:
- 在黑色星期五这天实时更新你的店铺的销量.你不仅能够实时获知哪些物品的销量,还能实时地响应:对畅销品订阅更多的库存.
- 实时收集登陆次数,并注入到指令监测系统用来检查正在进行的攻击,并且能够屏蔽欺诈的IP地址
- 实时更新交通速度传感器,你能够分析交通模式,并控制交通灯的时间
Fan in
Kafka同时也支持多个生产者往相同的地方写数据. 想象下前面的场景,但现在从多个服务器上收集web服务器日志.
所有的服务器以漏斗形式的数据流入到Kafka管道. 你只有一个grep的进程在运行, 获取加载时间的进程在运行,
只有一个绘图的进程在运行. 但是它们是基于所有web服务器的输出日志的聚合.恭喜你,现在创建了一个数据中心的监控面板.
这里的好处是你可以从很多的地方收集数据, 但是只在一个中央的地方存储并处理所有这些收集到的数据.
Kafka可以成为你的公司中所有数据的中心收集节点. 将分散在各个服务器上的数据都收集到统一一个节点.
Fan out
Kafka不仅支持多个生产者写到同一个地方,也支持多个消费者从相同的地方读取数据.
再强调一次,Kafka在多个阶段之间能够缓冲数据. 上面的三个管道:find_ip_address
, grep index.html
,get_login_attempts
--都能够按照自己的步伐(消费速度)从access_log这个Kafka管道中读取数据.
前面两个看起来会相当快,但是第三个可能会慢点.但是没关系,Kafka会保持这些数据(不会因为其他消费者消费了就删除数据)
这样的好处是一个单一的数据源可能用不同的方式处理,每种使用方式都和其他方式都是独立的,并且不会相互影响.
假设我们找到了一种检测黑客的方式. 我们可以将detect_hackers
实例部署在已有的实例旁(共存),然后一起测试.
对于相同的输入,看看他们都有什么不同的表现(验证我们的新的检测方式是否达到了预期的效果).
一旦我们决定选择使用新的方式会更好点,我们会通知下游的notify_security
作业监听更好的检测方式.
并且新的方式真的很稳定了,我们可以将老的检测方式移除掉.
看看我们都做了什么?
- 1.我们在生产环境的数据上直接运用新的算法,并做了真实的测试
- 2.对相同的数据,将新的算法和旧的算法一起测试
- 3.仅仅使用了一个开关就更改了notify_security作业的输入
- 4.保持旧的算法继续运行,以防需要切回去(上面的场景实际是将旧的算法删除了)
这个特性使得Kafka带给我们的威力非常大.通过将同一份数据分散到多个地方,我们可以从数据中获得多个分组的能力.
每个管道的工作都是独立的并且都是以自己的消费速度进行的. 并且让我们在开发新的功能时能够重用已经存在的数据.
并行
让我们专注于上面多个管道中的其中一个.
假设geoip(地理位置)数据库是非常慢的. Kafka会在这个阶段之前缓冲所有的数据,所以即使很慢,也不会丢失任何数据.
但是查询geoip会拖慢整个管道的速度. 所以你会部署一个很快速的geoip数据库. 但是这并不能帮你太多, 因为你每次
都是从find_ip_address的输出结果中一条接着一条地查询. 你真正需要的是并行!
Kafka支持在你的Kafka管道中添加子管道(sub-pipes). 你可以将所有以1结束的IP地址发送到第一个子管道,将所有以
2结束的IP地址发送到第二个管道,等等. 现在你的请求能够通过round-robin的方式发送到数据库中. 看起来是这样的:
Kafka管道中的数字0到9表示所有以这个数字结束的IP地址,会被放到相同的管道中(图中每个geoip_lookup都是一个子管道)
每个geoip_lookup作业都只会从find_ip_address管道中读取一部分数据,可以允许你以并行的方式查询:一次10个线程.
这种方式应该能满足你在geoip阶段快速地在地球图形上绘点, 这下你满意了吧!
Kafka称所有的这些是partitions
. 它允许你将数据以逻辑的分组方式分到多个通道中,但是每个函数都是独立的.
一批数据会分散到多个节点, 每个节点之间都做同样的工作. 但是它们之间不会相互影响的.
Kafka和Unix哲学
仔细看看上面的例子,你会发现Kafka的管道这个角色是很小的.Kafka管道并不会做过滤IP地址的工作,不会做查询IP地址的工作,
也不会对很大的数字做因式分解. 这都取决于你. Kafka做的事情是将你的所有工具都联系在一起.这样看来它就像胶水/粘合物.
但是它这个粘合物能够让你构建出很多有趣的东西. Kafka负责很多平凡的事情,而这些是作为事情的解决者的你并不愿意去做的.
它能够帮你保存数据,能在任何一个点开始读取数据,可以从多个数据源聚合数据,并将数据同时发送给多个目标.
Kafka这种能力让你重新思考解决问题的方式. 将一个问题分解成多个阶段,每个阶段可以单独开发实现,并独立地测试.
这一切都是基于Kafka能将所有的组件都粘合在一起. 而且Kafka可以在网络之间完成这些事情, 所以你甚至可以将你的计算组件
分布在多个节点, 也就有了水平扩展, 分布式处理, 高可用性等特点.
这种将一个大问题分解成多个小问题的思想和Unix的哲学是一致的. 实际上Unix管道的发明人Doug McIlroy这么说过:
This is the Unix philosophy: Write programs that do one thing and do it well.
Write programs to work together. Write programs to handle text streams,
because that is a universal interface.
Kafka允许你将Unix哲学运用到工程师急待解决的大数据量,低延迟,网络之间的问题.
声明
在这篇文章中,我简化了一些事情,现在我们解释下之前遗留的东西.
- Kafka是一个软件,你能够通过网络和它对话. 它有自己定制的网络协议,但有客户端库帮你做这些事情了.
- 有方便的命令行kafka-console-producer.sh读取标准输入流写到Kafka中.
kafka-console-consumer.sh可以从Kafka中读取输出,输出到标准输出流.你可以使用他们实现上面的命令. - Kafka客户端使得你能够从Kafka中读写数据构建自己的应用程序
- Kafka的管道实际上是叫做"topics"
- Kafka的topic都有名称. 每个topic的数据集和其他topic都是分开的.
EOF.