概述
Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者在网站中的所有动作流数据。
kafka的相关概念
Broker:Kafka集群包含一个或多个服务器,这种服务器被称为broker
Topic:每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处)
Partition:分区Partition是物理上的概念,每个Topic包含一个或多个Partition.建立topic的时候可以指定对应Partition数量
Producer:消息生产者,负责发布消息到Kafka broker
Consumer:消息消费者,向Kafka broker读取消息的客户端。
Consumer Group:每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)。
kafka的生产消费流程简图:
kafka生产者
kafka生产者是有客户端实现向kafka服务端写入文件的实现。
kafka生产者流程
- 第一步封装
使用ProducerRecord封装我们的消息体成一个record,
在封装中我们必须要传入的有:
记录的消息体(value),topic。
可选的参数有:
(记录的时间戳)timestamp、记录键值(key),记录的请求头(hearders)
- 第二步 获取集群数据元信息
将topic添加到metadata的topics集合中,获取集群中分区数cluster.partitionCountForTopic(topic),构建ClusterAndWaitTime对象。
- 第三步 序列化消息数据
实现序列。Kafka 提供了默认的序列化机制,也支持自定义序列化。
- 第四步 调用Partitioner.partition方法选择合适的分区
消息没有key则获取集群原数据的分区后随机选择,有key则使用murmur2hash算出分区。
- 第五步 消息放到消息累加器
分好区的消息不是直接被发送到服务端,而是放入了生产者的一个缓存里面。在这个缓存里面,多条消息会被封装成为一个批次(batch),每一个分区里的数据有序的,默认一个批次的大小是 16K。
- 第六步 唤醒sender发送线程
返回的结果RecordAppendResult的 if (result.batchIsFull || result.newBatchCreated)则唤醒sender线程。
- 第七步:Sender 线程把一个一个批次发送到服务端。
生产者流程图如下:
kafka消费者
kafka中的消费者和消费者组
消费者
在java中通常用一个消费线程来表示。一个分区对应一个消费线程。一个分区的数据只能被 同一个消费组内 的一个 消费者
消费,而 不能拆给同一个消费者组多个消费者 消费。当其中某一些消费者离开或者的时候,就会进行Partition Rebalance分区再均衡,使partition的所有权在消费者之间转移。
消费者组
消费者组是一组消费者的集合。消费者用一个消费者组名表示自己在哪一个消费者组。每个topic中的数据
可以被 多个 消费者
消费,但是每个消费者组
消费的数据是 互不干扰 的。所以说,每个 消费组
消费的都是完整的数据。
以下是消费者和分区的个数消费关系图:
kafka的消费这的负载均衡算法
1. A=(partition数量/同组内消费者总个数)
2. M=对上面所得到的A值小数点第一位向上取整
3. 计算出该消费者拉取数据的patition合集:Ci = [P(M*i )~P((i + 1) * M -1)]
假设有一个topic有十个pitition。三个消费者。则计算结果为:
A=10/3 = 3.333
M = 4
C0 = [p(0*4),p(0+1)*4-1] = [p0,p3]
C1 = [p(1*4),p(1+1)*4-1] = [p4,p7]
C2 = [p(2*4),p(2+1)*4-1] = [p8,p11]
所以最终的结果是消费者0消费 p0~p3四个分区。;消费者1消费 p4~p7四个分区;消费者2消费 p8~p10三个分区。
消费者提交偏移量
消费者需要向kafka服务端提交自己的位移数据,告诉服务端自己已经处理到了那个位置的数据。消费者通过_consumer_offset 的特殊主题发送 消息,消息里包含每个分区的偏移量。 偏移量的提交不会影响最新的数据的消费,应为服务端自己也维护了一个每个partition的位置。两者在所有消费者正常的情况下不会相互影响。所以消费者一直处于运行状态,偏移量就没有 什么用处。不过,如果消费者发生崩溃、有新的消费者加入群组或者停止后从新消费,就会触发再均衡,再均衡会给每个消费者分配新的分区,而不一定是之前处理的那个。为了能够继续 之前的工作,消费者需要读取每个分区最后一次提交 的偏移量,然后从偏移量指定的地方 继续处理。
自动提交偏移量
自动提交偏移量是在每次消费数据的时候,自动提交上一次消费的记录。自动体积可以通过设置 enable.auto.commit 为 true,这样Kafka 会在开始调用 poll 方法时,提交上次 poll 返回的所有消息。从顺序上来说,poll 方法的逻辑是先提交上一批消息的位移,再处理下一批消息,因此它能保证不出现消费丢失的情况。在默认情况下,Consumer 每 5 秒自动提交一次位移。可以通过修改 auto.commit.interval.ms 的值来改变提交频率。
自动提交位移虽然能保证所有的数据都能被消费和处理。但是可能会出现重复消费。如果消费者在还没有提交偏移量的时候就发生崩溃,那就会导致下一次从新消费的时候会消费到部分崩溃之前以及消费的数据。
手动提交偏移量
把 auto.commit.offset 设为 false
,让应用程序决定何时提交偏移量。手动提交偏移量分为两种,一种是同步提交,一种是异步提交。一般情况下是两种混合使用。
同步提交偏移量commitSync()
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
process(records); // 处理消息
try {
consumer.commitSync(); //提交偏移量
} catch (CommitFailedException e) {
}
}
commitSync() 将会提交由 poll() 返回的最新偏移量 , 所以在处理完所有记录后要 确保调用了 commitSync()。否则还是会有丢失消息的风险。但是再均衡会导致最近一 批消息到发生再均衡之间的所有消息都将被重复处理。在成功提交或碰到无怯恢复的错误之前, commitSync() 会一直重试(应用程序也一直阻塞)。
异步提交偏移量commitAsync()
同步提交的操作,在提交偏移量和处理数据是单线程的,所以放入锅服务端还没有返回消息,那么么所有的数据消费和处理都会被阻塞掉。这样就会影响程序的吞吐量。通过一部提交偏移量来降低提交频率来提升吞吐量,但如果发生了再均衡, 会增加重复消息的数量。使用异步提交 API。我们只管发送提交请求,无需等待 broker的响应。
while (true) {
ConsumerRecords<String, String> records =
consumer.poll(Duration.ofSeconds(1));
process(records); // 处理消息
try {
consumer.commitAsync(); //异步提交
} catch (CommitFailedException e) {
}
}
commitAsync()t提交在出现问题时它不会自动重试。因为它是异步操作,倘若提交失败后自动重试,那么它重试时提交的位移值可能早已经“过期”或不是最新值了。
参考文档:
http://kafka.apache.org/10/documentation.html
https://baijiahao.baidu.com/s?id=1658221660132943620&wfr=spider&for=pc
https://www.jianshu.com/p/6845469d99e6