Kafka 深度解析
发表于 2015-01-02 | 更新于 2017-03-15 | 分类于 Kafka , big data , 大数据 , 分布式 | 评论次
数 0 | 阅读次数 | 字数 17,487
介绍 Kafka 背景,使用消息系统的优势,常用消息系统对比,Kafka 架构介绍,Kafka 实现
语义分析,Replication 及 Leader Election 机制剖析,Consumer Group Rebalance 实现
原理介绍,以及 Benchmark 测试。
原创文章,转载请务必将下面这段话置于文章开头处(保留超链
接)。
本 文 转 发 自 技 术 世 界 , 原 文 链 接
http://www.jasongj.com/2015/01/02/Kafka 深度解析
1.背景介绍
1.1 Kafka 简介
Kafka 是一种分布式的,基于发布/订阅的消息系统。主要设计目标如下:
o 以时间复杂度为 O(1)的方式提供消息持久化能力,即使对 TB 级以上数据也能保证
常数时间的访问性能
o 高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒 100K 条消息的传
输
o 支持 Kafka Server 间的消息分区,及分布式消费,同时保证每个 partition 内的消
息顺序传输
o 同时支持离线数据处理和实时数据处理
1.2 为什么要用消息系统
解耦
在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。消息队列在处理过程中间
插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口。这允许你独立
的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束
冗余
有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数
据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。在被许多消息
队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理
过程明确的指出该消息已经被处理完毕,确保你的数据被安全的保存直到你使用完毕。
扩展性
因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的;只要另外
增加处理过程即可。不需要改变代码、不需要调节参数。扩展就像调大电力按钮一样简单。
灵活性 & 峰值处理能力
在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见;如果
为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够
使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
可恢复性
当体系的一部分组件失效,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即
使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。而这种允许
重试或者延后处理请求的能力通常是造就一个略感不便的用户和一个沮丧透顶的用户之间
的区别。
送达保证
消息队列提供的冗余机制保证了消息能被实际的处理,只要一个进程读取了该队列即可。在
此基础上,部分消息系统提供了一个”只送达一次”保证。无论有多少进程在从队列中领取
数据,每一个消息只能被处理一次。这之所以成为可能,是因为获取一个消息只是”预定”
了这个消息,暂时把它移出了队列。除非客户端明确的表示已经处理完了这个消息,否则这
个消息会被放回队列中去,在一段可配置的时间之后可再次被处理。
顺序保证
在大多使用场景下,数据处理的顺序都很重要。消息队列本来就是排序的,并且能保证数据
会按照特定的顺序来处理。部分消息系统保证消息通过 FIFO(先进先出)的顺序来处理,
因此消息在队列中的位置就是从队列中检索他们的位置。
缓冲
在任何重要的系统中,都会有需要不同的处理时间的元素。例如,加载一张图片比应用过滤
器花费更少的时间。消息队列通过一个缓冲层来帮助任务最高效率的执行–写入队列的处理
会尽可能的快速,而不受从队列读的预备处理的约束。该缓冲有助于控制和优化数据流经过
系统的速度。
理解数据流
在一个分布式系统里,要得到一个关于用户操作会用多长时间及其原因的总体印象,是个巨
大的挑战。消息队列通过消息被处理的频率,来方便的辅助确定那些表现不佳的处理过程或
领域,这些地方的数据流都不够优化。
异步通信
很多时候,你不想也不需要立即处理消息。消息队列提供了异步处理机制,允许你把一个消
息放入队列,但并不立即处理它。你想向队列中放入多少消息就放多少,然后在你乐意的时
候再去处理它们。
1.3 常用 Message Queue 对比
RabbitMQ
RabbitMQ 是使用 Erlang 编写的一个开源的消息队列,本身支持很多的协议:AMQP,
XMPP, SMTP, STOMP,也正因如此,它非常重量级,更适合于企业级的开发。同时实现
了 Broker 构架,这意味着消息在发送给客户端时先在中心队列排队。对路由,负载均衡或
者数据持久化都有很好的支持。
Redis
Redis 是一个基于 Key-Value 对的 NoSQL 数据库,开发维护很活跃。虽然它是一个
Key-Value 数据库存储系统,但它本身支持 MQ 功能,所以完全可以当做一个轻量级的队
列服务来使用。对于 RabbitMQ 和 Redis 的入队和出队操作,各执行 100 万次,每 10 万
次记录一次执行时间。测试数据分为 128Bytes、512Bytes、1K 和 10K 四个不同大小的数
据。实验表明:入队时,当数据比较小时 Redis 的性能要高于 RabbitMQ,而如果数据大
小超过了 10K,Redis 则慢的无法忍受;出队时,无论数据大小,Redis 都表现出非常好的
性能,而 RabbitMQ 的出队性能则远低于 Redis。
ZeroMQ
ZeroMQ 号称最快的消息队列系统,尤其针对大吞吐量的需求场景。ZMQ 能够实现
RabbitMQ 不擅长的高级/复杂的队列,但是开发人员需要自己组合多种技术框架,技术上
的复杂度是对这 MQ 能够应用成功的挑战。ZeroMQ 具有一个独特的非中间件的模式,你
不需要安装和运行一个消息服务器或中间件,因为你的应用程序将扮演了这个服务角色。你
只需要简单的引用 ZeroMQ 程序库,可以使用 NuGet 安装,然后你就可以愉快的在应用
程序之间发送消息了。但是 ZeroMQ 仅提供非持久性的队列,也就是说如果宕机,数据将
会丢失。其中,Twitter 的 Storm 0.9.0 以前的版本中默认使用 ZeroMQ 作为数据流的传输
(Storm 从 0.9 版本开始同时支持 ZeroMQ 和 Netty 作为传输模块)。
ActiveMQ
ActiveMQ 是 Apache 下的一个子项目。 类似于 ZeroMQ,它能够以代理人和点对点的技
术实现队列。同时类似于 RabbitMQ,它少量代码就可以高效地实现高级应用场景。
Kafka/Jafka
Kafka 是 Apache 下的一个子项目,是一个高性能跨语言分布式发布/订阅消息队列系统,
而 Jafka 是在 Kafka 之上孵化而来的,即 Kafka 的一个升级版。具有以下特性:快速持久
化,可以在 O(1)的系统开销下进行消息持久化;高吞吐,在一台普通的服务器上既可以达
到 10W/s 的吞吐速率;完全的分布式系统,Broker、Producer、Consumer 都原生自动
支持分布式,自动实现负载均衡;支持 Hadoop 数据并行加载,对于像 Hadoop 的一样的
日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka 通
过 Hadoop 的并行加载机制来统一了在线和离线的消息处理。Apache Kafka 相对于
ActiveMQ 是一个非常轻量级的消息系统,除了性能非常好之外,还是一个工作良好的分布
式系统。
2. Kafka 解析
2.1 Terminology
o Broker
Kafka 集群包含一个或多个服务器,这种服务器被称为 broker
o Topic
每条发布到 Kafka 集群的消息都有一个类别,这个类别被称为 topic。(物理上不
同 topic 的消息分开存储,逻辑上一个 topic 的消息虽然保存于一个或多个 broker
上但用户只需指定消息的 topic 即可生产或消费数据而不必关心数据存于何处)
o Partition
parition 是物理上的概念,每个 topic 包含一个或多个 partition,创建 topic 时可
指定 parition 数量。每个 partition 对应于一个文件夹,该文件夹下存储该 partition
的数据和索引文件
o Producer
负责发布消息到 Kafka broker
o Consumer
消费消息。每个 consumer 属于一个特定的 consumer group(可为每个 consumer
指定 group name,若不指定 group name 则属于默认的 group)。使用 consumer
high level API 时,同一 topic 的一条消息只能被同一个 consumer group 内的一
个 consumer 消费,但多个 consumer group 可同时消费这一消息。
2.2 Kafka 架构
如上图所示,一个典型的 kafka 集群中包含若干 producer(可以是 web 前端产生的
page view,或者是服务器日志,系统 CPU、memory 等),若干 broker(Kafka 支持水
平扩展,一般 broker 数量越多,集群吞吐率越高),若干 consumer group,以及一个
Zookeeper 集群。Kafka 通过 Zookeeper 管理集群配置,选举 leader,以及在 consumer
group 发生变化时进行 rebalance。producer 使用 push 模式将消息发布到 broker,
consumer 使用 pull 模式从 broker 订阅并消费消息。
2.2.1 Push vs. Pull
作为一个 messaging system,Kafka 遵循了传统的方式,选择由 producer 向 broker
push 消息并由 consumer 从 broker pull 消息。一些 logging-centric system,比如
Facebook 的 Scribe 和 Cloudera 的 Flume,采用非常不同的 push 模式。事实上,push 模
式和 pull 模式各有优劣。
push 模式很难适应消费速率不同的消费者,因为消息发送速率是由 broker 决定的。
push 模式的目标是尽可能以最快速度传递消息,但是这样很容易造成 consumer 来不及处
理消息,典型的表现就是拒绝服务以及网络拥塞。而 pull 模式则可以根据 consumer 的消
费能力以适当的速率消费消息。
2.2.2 Topic & Partition
Topic 在逻辑上可以被认为是一个 queue。每条消费都必须指定它的 topic,可以简单
理解为必须指明把这条消息放进哪个 queue 里。为了使得 Kafka 的吞吐率可以水平扩展,
物理上把 topic 分成一个或多个 partition,每个 partition 在物理上对应一个文件夹,该文
件夹下存储这个 partition 的所有消息和索引文件。
每个日志文件都是“log entries”序列,每一个 log entry 包含一个 4 字节整型数(值
为 N),其后跟 N 个字节的消息体。每条消息都有一个当前 partition 下唯一的 64 字节的
offset,它指明了这条消息的起始位置。磁盘上存储的消息格式如下:
message length : 4 bytes (value: 1+4+n)
“magic” value : 1 byte
crc : 4 bytes
payload : n bytes
这个“log entries”并非由一个文件构成,而是分成多个 segment,每个 segment
名为该 segment 第一条消息的 offset 和“.kafka”组成。另外会有一个索引文件,它标明