- 同一个主题的不同分区包含的消息是不同的。
- Kafka 保证的是分区有序而不是全局有序。
- topic 是逻辑上的概念,partition是物理上的概念,因为每个partition 都对应于一个.log文件存储在kafka 的log目录下,该log 文件中存储的就是producer 生产的数据。
- 消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费。
- 一般分区数和消费者数保持相等,如果这个主题的消费者数大于主题的分区数,那么多出来的消费者将消费不到数据,只能浪费系统资源。
- 生产者生产的消息会不断追加到log 文件末尾,为防止log 文件过大导致数据定位效率低下,Kafka 采取了分片和索引机制,将每个partition 分为多个segment。每个segment都有对应的.index文件和.log文件以及.timeindex文件。
- kafka高效读写的原因之一在于使用了顺序写磁盘技术和零拷贝技术。
生产者
生产者发送消息时的分区原则如下:
- 构造器中指明 partition 时,直接将指定值作为 partition值;
- 没有指明partition值但有key的情况下,将key的hash值与topic 的 partition 数进行取余得到 partition 值;
- 既没有指定 partition 值又没有传入 key 值时,第一次调用时随机生成1个整数(后面每次调用在这个整数上自增),并与这个topic的partition数取模得到partition值,即采用round-robin 算法。
ISR
一个主题的分区中所有的副本集合称之为AR(Assigned Replicas)。leader节点维护了一个动态的集合,即in-sync replica set (ISR),指的是和leader保持一定程度同步的follower集合(包括leader节点在内)。与leader副本同步滞后过多的副本组成OSR(Out-of-Sync Replicas)集合,因此AR=ISR+OSR。正常情况下所有的follower都应该和leader保持一定程度的同步,即AR=ISR。
ACK确认机制
acks=1,即默认配置。生产者发送消息后,只要分区的leader副本成功写入消息,就会收到服务器的ack成功响应。如果消息写入leader失败,比如leader已经挂了,正在重新选举,此时生产者客户端会收到错误响应,可以进行重发。如果写入leader成功、follower同步完成之前leader挂了,将会出现消息丢失。所以acks=1是Kafka消息可靠性和吞吐量之间的折中方案,一般也是默认的配置。
acks=0,生产者发送消息之后无需等待任何服务端的响应。如果在消息从发送到写入Kafka 的过程中出现某些异常,导致Kafka 并没有收到这条消息,那么生产者也无从得知,消息也就丢失了。在其他配置环境相同的情况下,acks=0 可以使Kafka达到最大的吞吐量。
acks=-1,和acks=all效果一样。生产者在消息发送之后,需要等待ISR 中的所有副本都成功写入消息之后才能够收到来自服务端的ack响应。在其他配置环境相同的情况下,acks 设置为-1 可以达到最高的数据可靠性。但是如果在follower 同步完成后,broker给生产者客户端发送ack之前,leader发生故障,将会造成数据重复。
Exactly Once
将acks级别设置为-1,可以保证producer和kafka server之间不丢失数据,即保证At least once 语义。
acks级别设置为0,可以保证每条消息只会发送1次,不会重复,即At most once语义。
有些场景下,数据的消费者要求数据既不丢失也不重复,即Exactly Once语义。0.11版本的 Kafka,引入了一项重大特性:幂等性。即不论producer向server发送多少次重复数据,server端只会持久化1条。将producer中的enable.idompotence参数设为true即可开启幂等性。开启幂等性的Producer在初始化的时候会分配一个pid,发往同一个Partition的消息会附带sequence number。Kafka服务端,即broker端会对<pid,partition,seqnumber>做缓存,当具有相同键的消息提交时,broker只会持久化一条。但是PID重启就会变化,并且不同的Partition具有不同的键,所以kafka的幂等性无法保证跨分区、跨会话的Exactly Once。
消费者
消费者消费策略
Range(默认)和Robin
RoundRobin 轮询分区策略,是把所有的 partition 和所有的 consumer 都列出来,然后按照 hascode 进行排序,最后通过轮询算法来分配 partition 给到各个消费者。
轮询分区分为如下两种情况:
1.同一消费组内所有消费者订阅的topic都是相同的
2.同一消费者组内的消费者订阅的消息有不相同的
消费位移
消费位移必须做持久化保存,而不是单单保存在内存中,否则消费者重启之后就无法知晓之前的消费位移。Kafka 0.9 版本之前,consumer 默认将offset 保存在Zookeeper 中,新版consumer客户端 默认将offset 保存在Kafka 内置的主题 __consumer_offsets中。通常把将消费位移存储起来(持久化)的动作称为“提交”,消费者在消费完消息之后需要执行消费位移的提交