译:Kafka和Unix管道的示例

原文:http://logallthethings.com/2015/09/15/kafka-by-example-kafka-as-unix-pipes/

什么是Kafka

想象下有这样的对话.

你: 什么是Apache Kafka?
我: Apache Kafka是发布-订阅消息系统,分布式的提交日志
你: …什么?
我: 是的,它是一个分布式的,分区的,复制的提交日志服务
你: 你到底在说什么?

上面的描述(我)是正确的. 你只需要知道这些术语是什么意思, 但是如果不知道这些术语,就会感到很困惑.

那就让我们以另外一种方式来解释吧. 我喜欢通过例子来学习, 并且在学习的时候通过和我已经知道的东西互相比较,
我发现这种学习方式非常有帮助. 那么我们就以这种举例子,并且比较的方式来描述什么是Kafka吧.

Kafka就像Unix的管道

我会给一些例子来说明Kafka能干什么, 比较的对象是很多人都熟悉的: 命令行的Unix管道

看一个简单的例子:

1
$ cat *.txt | tr A-Z a-z | grep hello

这段脚本找出以.txt结尾的文件中所有包含单词”hello”的行.它包含了三个步骤/阶段:

  1. 从所有文件中输出所有行
  2. 将所有文本转换为小写
  3. 找出含有”hello”单词的行

所有这些步骤的每一个都写到标准输出流,后面的阶段会从标准的输入流中读取.

最简单的来看, Kafka就像一个Unix的管道: 你将数据写到它的一端, 然后数据从另一端出来.
(严格来说,你写的数据会通过网络传输,你读取的数据也是通过网络,不过现在我们暂时忽略这些.)

如果这就是Kafka所能做的,那有什么了不起的,对吧?实际上Kafka还有一些额外的特征,带来新的能力.

结构化数据

Unix的管道在文本数据行之间流动,通常是以新的一行为结束(这条管道). 这些行可以很长,但是工作单元仍然是一行文本.
如果你处理的不是ASCII数据,或者你处理的数据不能以一一行来表示就会有点麻烦. 而Kafka支持任意的格式和任意大小.
这就允许你可以存储任何数据到Kafka中: 文本,CSV,二进制数据,自定义编码数据等等. 对于Kafka而言,它只是一系列的
消息,其中每条消息都是一系列的字节. 比如可以(模拟)写一个Kafka的”命令行”:

1
$ TwitterFeed | filter_tweets From @apachekafka

这里的filter_tweets命令可能不是一个简单的基于字符串的grep,而是一种能够理解从TwitterFeed输出的数据格式.
比如TwitterFeed可能输出JSON,则filter_tweets需要做些JSON的处理.TwitterFeed如果返回的是二进制数据,
则filter_tweets需要知道二进制的格式/协议. 这种灵活性可以让Kafka成为一种发送任何数据类型的Unix管道.

数据持久化

我们可能有一个复杂的会花费一些时间才能跑完的命令.如果只运行一次,你可能不关心.但是如果你要多次迭代运行,
你可能会会将输出结果先写到一个文件中, 这样之后的阶段可以更快地迭代,而不需要重新多次运行很慢的那部分命令.

1
2
$ find . -type f | grep \.java > javafiles.txt
$ cat javafiles.txt | xargs grep ClassName

这个模式工作的很好,但是这意味着你需要提前计划去做(先写文件). 如果管道自身能够做这件时间就方便多了.

Kafka会持久化你发送的所有数据到磁盘上.持久化非常方便,不仅节省了你的一些时间,它还允许你能做之前不能做的一些事情.
就像上面的命令行一样,每个阶段的输出都被保存下来. 由于第一个阶段的输出被保存了,第二个阶段甚至不要求正在运行.
这种方式, Kafka作为生产者数据和消费者数据之间的缓冲区. 它保持了数据,允许消费者可用并且准备好的时候才读取数据.

Kafka是高性能的,它甚至可以运行在多台机器上,并且可以复制统一分数据到多台机器防止数据丢失造成的风险.
三个Kafka节点组成的集群能够处理每秒钟两百万的写入, 并能使网卡饱和.

由于数据被持久化到了Kafka中,并没有要求消费者要多快去读取数据.消费者可以想多快就多快,想多慢就多慢地读取数据.
因此它允许一个高性能的生产者, 并不会因为一个很慢的消费者而江堤生产者的性能. 看一个很慢的消费者的例子.

1
2
$ produceNumbers > numbers.txt
$ cat numbers.txt | xargs findPrimeFactorization

从密码学我们知道,将一个数字因式分解成质数是很慢的.假设我们分解了100万个数字,程序挂掉了.
当下次重启程序的时候如果能够从上次离开位置的那个点继续处理,而不是重复很多工作,那就很友好了.
以这个例子中,我期望的是从numbers.txt中的第一百万零一行开始继续处理.

Kafka有类似的概念叫做”offset”.Kafka中的每条记录都被分配了有序的offset,消费者可以选择在指定的offset重新开始.

数据持久化和offsets这两个特性,允许你构建一个消费者数据和生产者数据分开的系统.
数据持久化–非常快的数据持久化–意味着它能很快地吸收大批量的数据.
它允许消费者按照它能够读取的任何速度读取数据.它允许持久化数据, 即使消费者挂掉了.
offsets允许消费者继续执行, 无论它上次在什么地方退出,而不会重复工作.
在某种情况下,这是很有意义的: 你并不想在一次汇款中从银行账号中扣了两次钱.
另一方面,这是出于效率方面考虑的: 你并不想重新对已经处理的数字重新进行因式分解.
无论哪种情况, 这两个特性都允许你做传统的Unix管道所不能做的事情.

流数据

再看下第一个例子:

1
$ cat *.txt | tr A-Z a-z | grep hello

在这里例子中,第一个阶段(cat)输出所有的行然后就结束了. 整个管道会找到所有包含单词”hello”的行最后命令结束.
和下面的命令进行比较:

1
$ tail -F *.txt | tr A-Z a-z | grep hello

这个命令不会结束, 第一个阶段(tail)输出一些行,但是仍然保持着监听更多的数据.
如果你在之后往其中的一个添加了一行,tail命令会输出这个新行, 然后接下来的命令会处理它.

Kafka支持相同的概念.数据写到到Kafka并且被消费者读取可以看做一个流.
如果消费者到达数据的末尾, 它会继续等待即将到来的更多的数据. 当新的数据写入到Kafka,它会很快地被发送到消费者.
我在之前说过数据流进Kafka是很快的, 实际上数据从Kafka流出也是很快的.
一条记录被添加到Kafka后,能够在20ms之内发送给一个正在等待的消费者.

现在我们知道Kafka除了支持数据持久化,也支持流数据. 我们复习下之前的例子

1
2
$ produceNumbers > numbers.txt
$ cat numbers.txt | xargs findPrimeFactorization

上面的命令看起来向上一种批处理模式,因为produceNumbers最终会结束的.
但是数字是无限的,它永远不会结束, 所以实际上看起来应该是这样的:

1
$ produceNumbers |* findPrimeFactorization

这里我自己造了一个语法: |*表示这是一个Kafka管道.它能够归档所有东西到磁盘,并且发送流式的更新.

streaming updates流式更新, 数据是流式传入的,下游的方法基于最新的流数据做更新操作. 即对流数据更新操作

这种流式的数据允许你创建一个实时的管道,这里有个例子:

1
$ 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服务器日志.

fan-in

所有的服务器以漏斗形式的数据流入到Kafka管道. 你只有一个grep的进程在运行, 获取加载时间的进程在运行,
只有一个绘图的进程在运行. 但是它们是基于所有web服务器的输出日志的聚合.恭喜你,现在创建了一个数据中心的监控面板.

这里的好处是你可以从很多的地方收集数据, 但是只在一个中央的地方存储并处理所有这些收集到的数据.
Kafka可以成为你的公司中所有数据的中心收集节点. 将分散在各个服务器上的数据都收集到统一一个节点.

Fan out

Kafka不仅支持多个生产者写到同一个地方,也支持多个消费者从相同的地方读取数据.

fan-out

再强调一次,Kafka在多个阶段之间能够缓冲数据. 上面的三个管道:find_ip_address, grep index.html,
get_login_attempts–都能够按照自己的步伐(消费速度)从access_log这个Kafka管道中读取数据.
前面两个看起来会相当快,但是第三个可能会慢点.但是没关系,Kafka会保持这些数据(不会因为其他消费者消费了就删除数据)

这样的好处是一个单一的数据源可能用不同的方式处理,每种使用方式都和其他方式都是独立的,并且不会相互影响.
假设我们找到了一种检测黑客的方式. 我们可以将detect_hackers实例部署在已有的实例旁(共存),然后一起测试.
对于相同的输入,看看他们都有什么不同的表现(验证我们的新的检测方式是否达到了预期的效果).

detect_hacker

一旦我们决定选择使用新的方式会更好点,我们会通知下游的notify_security作业监听更好的检测方式.

hacker_new

并且新的方式真的很稳定了,我们可以将老的检测方式移除掉.

delete_old

看看我们都做了什么?

  • 1.我们在生产环境的数据上直接运用新的算法,并做了真实的测试
  • 2.对相同的数据,将新的算法和旧的算法一起测试
  • 3.仅仅使用了一个开关就更改了notify_security作业的输入
  • 4.保持旧的算法继续运行,以防需要切回去(上面的场景实际是将旧的算法删除了)

这个特性使得Kafka带给我们的威力非常大.通过将同一份数据分散到多个地方,我们可以从数据中获得多个分组的能力.
每个管道的工作都是独立的并且都是以自己的消费速度进行的. 并且让我们在开发新的功能时能够重用已经存在的数据.

并行

让我们专注于上面多个管道中的其中一个.

parallelism

假设geoip(地理位置)数据库是非常慢的. Kafka会在这个阶段之前缓冲所有的数据,所以即使很慢,也不会丢失任何数据.
但是查询geoip会拖慢整个管道的速度. 所以你会部署一个很快速的geoip数据库. 但是这并不能帮你太多, 因为你每次
都是从find_ip_address的输出结果中一条接着一条地查询. 你真正需要的是并行!

Kafka支持在你的Kafka管道中添加子管道(sub-pipes). 你可以将所有以1结束的IP地址发送到第一个子管道,将所有以
2结束的IP地址发送到第二个管道,等等. 现在你的请求能够通过round-robin的方式发送到数据库中. 看起来是这样的:

para2

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哲学运用到工程师急待解决的大数据量,低延迟,网络之间的问题.

声明

在这篇文章中,我简化了一些事情,现在我们解释下之前遗留的东西.

  1. Kafka是一个软件,你能够通过网络和它对话. 它有自己定制的网络协议,但有客户端库帮你做这些事情了.
  2. 有方便的命令行kafka-console-producer.sh读取标准输入流写到Kafka中.
    kafka-console-consumer.sh可以从Kafka中读取输出,输出到标准输出流.你可以使用他们实现上面的命令.
  3. Kafka客户端使得你能够从Kafka中读写数据构建自己的应用程序
  4. Kafka的管道实际上是叫做”topics”
  5. Kafka的topic都有名称. 每个topic的数据集和其他topic都是分开的.

EOF.


文章目录
  1. 1. 什么是Kafka
  2. 2. Kafka就像Unix的管道
  3. 3. 结构化数据
  4. 4. 数据持久化
  5. 5. 流数据
  6. 6. Fan in
  7. 7. Fan out
  8. 8. 并行
  9. 9. Kafka和Unix哲学
  10. 10. 声明