分布式消息中间件 kafka
什么是 kafka
kafka 介绍
kafka 是一款分布式消息发布和订阅系统,具有高性能、高吞吐量的特点而被广泛应用于大数据传输场景。是由 LinkedIn 公司发布,使用 scala 语言编写,之后成为 apache 基金会的一个顶级项目。
kafka 产生的背景
kafka 作为一个消息系统,早期设计的目的是用作 LinkedIn 的活动流和运营数据处理管道。 活动流数据是所有的网站对用户的使用情况做分析的时候要用到的最常规的部分,活动数据包括页面的访问量、被查看内容方面的信息以及搜索内容。这种数据通常的处理方式是先把各种活动以日志的形式写入某种文件,然后周期性的对这些文件进行统计分析。运营数据指的是服务器的性能数据(CPU ,IO 使用率,请求时间,服务日志等)
kafka 的应用场景
由于 kafka 具有更好的吞吐量,内置分区,冗余及容错性的优点(kafka 每秒可以处理几十万消息),让 kafka 成为了一个很好的大规模消息处理应用的解决方案。所以在企业级应用上,主要应用于几个方面:
- 行为跟踪:kafka 可以用于跟踪用户浏览页面,搜索及其他行为。通过发布-订阅模式实时记录到对应的 topic 中,通过后端大数据平台接入处理分析,并做更进一步的实时处理和监控。
- 日志收集:日志收集方面,有很多比较优秀的产品,比如 Apache Flume,很多公司使用 kafka 代理日志聚合。在实际应用开发中,我们应用程序的 log 都会输出到本地的磁盘上,排查问题的话通过 linux 命令来搞定,如果应用程序组成了负载均衡集群,并且集群的机器又几十台以上,那么想通过日志快速定位到问题,就是很麻烦的事情了。 所以很多公司都是把应用日志集中到 kafka 上,然后分别导入到 es 和 hdfs 上,用来做实时检索分析和离线统计数据备份等。而另一方面,kafka 又提供了很好的 api 来集成日志并且做日志收集。
kafka 自身的架构
一个典型的 kafka 集群包含若干个 producer 、若干个 broker、 若干个 consumerGroup 以及一个 zookeeper 集群(可选)。zookeeper 主要用于管理 kafka 的集群配置及服务协调。
producer 使用 push 模式将消息发布到 broker,consumer 通过监听使用 pull 模式从 broker 订阅并消费消息。
多个 broker 协同工作,producer 和 consumer 部署在各个业务逻辑中。 三者通过 zookeeper 管理协调请求和转发。这样就组成了一个高性能的分布式消息发布和订阅系统。
其中有一个细节是和其他 mq 中间件不同的点,producer 发送消息到 broker 的过程是 push,而 consumer 从 broker 消费消息的过程是 pull,主动去拉数据,而不是 broker 把数据主动发送给 consumer。
kafka 中的一些简单概念
Broker
一台 kafka 服务器就是一个 broker。一个集群由多个 broker 组成,一个 broker 可以容纳多个 topic。
topic
可以理解为一个队列。
partition
为了实现扩展性,一个非常大的 topic 可以分布到多个 broker 上,一个 topic 可以分为多个 partition,每个 partition 是一个有序的队列。partition 中的每条消息都会被分配一个有序的 id (offset)。kafka 只保证按一个 partition 中的顺序将消息发给 consumer,不保证一个 topic 的整体(多个 partition) 的顺序。
如下图既是将 test 这个 topic 分为三个 partition:
producer
消息生产者,其实就是向 kafka broker 发送消息的客户端。
consuemr
消息消费者,其实就是从 kafka broker 取消息的客户端。
consumer group
这是 kafka 中独有的概览,用于实现 topic 消息的广播和单播。一个 topic 可以由多个 consumer group。每条消息会发送给所有的 consumer group,但是这个 consumer group 需要将消息发给该 group 中的某个 consumer 来进行处理。如果需要广播,只要每个 consumer 是一个独立的 group 就可以了。
segment
partition 物理上由多个 segment 组成。
offset
每个partition都由一系列有序的、不可变的消息组成,这些消息被连续的追加到 partition 中。partition 中的每个消息都有一个连续的序列号叫做 offset,用于 partition 唯一标识一条消息。
生产者如何生产消息
生产者可以指定向 kafka 中的某个 topic 发送消息。每个 topic 可以有多个生产者向它发送消息,也可以有多个消费者去消费其中的消息。
每个 topic 可以划分多个分区(每个 topic 至少有一个分区)。同一个 topic 下的不同分区包含的信息是不同的(同一条消息不会存在多个分区),消息在进入分区时,都会分配一个 offset 偏移量,它是消息在此分区的唯一编号。kafka 通过 offset 保证消息在分区内的顺序,offset 的顺序不跨分区,即 kafka 只保证在同一个分区内的消息是有序的。
如下图中,对于一个名字为 test 的 topic,有三个分区,分别是 p0,p1,p2
- 每一条消息发送到 broker 时,会根据分区的规则选择存储到其中一个分区,如果分区的规则设置合理,那么所有的消息应该会均匀的分布在不同的分区中。有点类似分库分表,将数据做了分片处理。
消费者如何消费消息
每个 topic 都会有多个 partition,多个 partition 的好处在于,一方面能够对 broker 上的数据进行分片有效减少消息的容量从而提升 io 性能。另一方面,为了提高消费端的消费能力,一般会通过多个 consumer 去消费同一个 topic,也就是消费端的负载均衡。
在多个 partition 以及多个 consumer 的情况下,消费者的如何消费消息的。kafka 存在 consumerGroup 的概念,也就是 groupId 一样的 consumer,这些 consumer 属于一个 consumerGroup,组内的所有消费者协调在一起来消费订阅主题的所有分区。当然每个分区只能由同一个消费组内的 consumer 来消费,那么同一个 consumerGroup 里面的 consumer 是怎么去分配该消息哪个分区里的数据呢?如下图所示,三个分区,三个消费者,那么哪个消费者消费哪个分区?
对于这个图来说,三个消费者对应三个分区,也就是会分别消费三个分区,每个消费者消费一个分区。
kafka 是如果做分区分配的呢?
在 kafka 中,存在两种分区分配策略。一个中是 range(默认),另一种是 roundRobin(轮询)。
range 是范围分区,比如我们是 10 个分区,有三个消费者,则会每个消费者消费三个,还有个待消费的分区就会分配给 consumer1,也就是:
- consumer1 消费 0,1,2,3 四个分区
- consumer2 消费 4,5,6 三个分区
- consumer3 消费 7,8,9 三个分区
轮询分区策略就是把所有 partition 和所有 consumer 线程都列出来,然后按照 hashCode 进行排序。最后通过轮询算法分配 partition 给消费线程。如果所有 consumer 实例的订阅是相同的,那么partition 会均匀分配。
当出现以下几种情况,kafka 会进行依次分区分配操作,也就是 kafka consumer 的 rebalance
- 同一个 consumerGroup 内新增了消费者
- 消费者离开当前所属的 consumerGroup。比如主动停机或宕机
- topic 新增了分区(也就是分区数量发生变化)
消息持久化
kafka 是使用日志文件的方式来保存生产者和发送者的消息,每条消息都有一个 offset 值来表示它在分区中的偏移量。kafka 中存储的一般都是海量的消息数据,为了避免日志文件过大,log 并不是直接对应在一个磁盘上的日志文件,而是对应磁盘上的一个目录, 比如创建一个名为firstTopic的topic,其中有3个partition, 那么在 kafka 的数据目录(/tmp/kafka-log)中就有 3 个目 录,firstTopic-0~3
多个分区在集群上的分配
如果我们对于一个 topic,在集群中创建多个 partition,那么partition是如何分布的
- 将所有 N broker 和待分配的 i 个 partition 排序
- 将第 i 个 partition 分配到第(i mod n)个 broker 上
了解到这里的时候,大家再结合前面讲的消息分发策略, 就应该能明白消息发送到 broker 上,消息会保存到哪个分 区中,并且消费端应该消费哪些分区的数据了。
总结
本小节简单说明了下 kafka 中的生产消息、消费消息以及消息持久化的一些原理。下一节我们会详细说明一下 kafka 的一些特性。