1. Kafka internal
1.1 Request processing
Kafka 的 request 分为:
- produce request
- fetch request
- metadata request
- and so on
1.1.1 Metadata request
kafka client 会定期向任意一个 broker(所有的 brokers 都拥有 partition metadata)发起 metadata request 来获取到每个 topic partition leader 的位置,并缓存在本地(有定期的缓存更新机制),从而在需要发送 produce or fetch requests 时正确的定位 broker 的位置。
如果缓存更新延迟,导致错误的 produce request 发到非 leader partition 的 broker 上,该 broker 会直接返回 error,而不是像 ElasticSearch 那样内部转发。
client 收到 produce/fetch response 的 "not a leader" error 后会 refresh metadata。
1.1.2 Produce request
produce request 的过程之前也聊过了,一个重要配置是 acks。
1.1.3 Fetch request
fetch request 由 consumer 或者 follower replicas 发起。
client 发起人request:
- 通知 broker 自己要 poll 的 messages 的 topics, partitions & offsets
-
可以设置 messages 数量的上限 & 下限,上限是为了防止 OOM,下限是为了减少网络次数。
- 可以设置 timeout,如果 timeout 时间内,messages 量不够,也直接返回目前可以返回的 messages
consumers 只能 poll 到写入所有 replicas 的 messages,这也是为了尽量保证 consistency。
这就带来一个问题,replication 是有 lag 的,会导致消息无法第一时间到达 consumers。
// todo : This delay is limited to replica.lag.time.max.ms—the amount of time a replica can be delayed in replicating new messages while still being considered in-sync.
broker 会校验 request 的正确性,包括 leader partition, offset。
broker 使用 zero-copy method 将 messages 发送给 client,大大增加了性能。
broker 处理 fetch request 的方式和处理 produce request 非常相似。
1.1.4 OffsetCommitRequest
过去,kafka 使用 zk 维护 consumers 的 offsets。当 consumer 启动时,向 zk 发请求获取要读的 partition 以及 offset。
但是因为频繁的读写 offsets 对 zk 的压力较大,所以推荐通过 Kafka broker 直接管理,现在 kafka 通过创建一个单独的特殊的 __consumer_offsets topic 来维护 offsets。
1.2 Physical Storage
我们会将 partition 在物理层面拆分成 segments。默认情况下,每个 segment 包含 1GB 的数据/一周的数据。当 broker 往 partition 中写数据时,如果当前 segment 的限制到了,会关闭该文件 & 创建一个新的。当前正在使用的 segment 叫做 active segment。每个 segment 会单独存储在 *.log 的物理文件中。
1.2.1 Indexes
由于 Kafka 支持 consumers 从任意可用的 offset 处读取 message,所以需要能快速的定位 message 所在的 segment 位置,Kafka 为每个 partition 创建有 index,index 维护从 offset 到 segment file 及 file 中的位置的映射。
index 也按照 segment 来拆分。
1.2.2 Compact
有一种场景,只保存相同 key 的最新的 message,就用到了 compact,将老旧的 message delete 掉。本质上也是一种 retention policy,技术细节这里不做介绍了。
这部分内容主要整理总结自 《Kafka The Definitive Guide》 Chapter 6: Reliable Data Delivery
2. Reliable
Kafka 的 components 有 producer, broker, consumer,整体服务的 reliable(可靠性) 依赖每一个环节的 reliable,需要 Linux administrators, network and storage administrators, and the application developers 共同努力。
最常为人知的 Reliability Guarantees 是 ACID,它只要用在关系型数据库中。
2.1 Reliability Guarantees
Kafka 提供的基本的 guarantee 包括:
- 单个 partition 中 messages 的 order
- produce 只有 messages 被写到所有的 replicas 时,该 messages 才被认为 "committed"
- acks: 0, producer 通过网络成功发出去就不管了
- acks: 1, producer 等到 leader 收到消息
- acks: all, producer 等到 message committed
- messages 只要 committed,就不会丢失,因为已经同步到所有 replicas
- consumers 只能读到 committed 的 messages
但是这种 guarantee 只能保证基本的 reliable,并不能保证 fully reliable。
系统中存在着 trade-offs,需要在 reliable 和 availability, high throughput, low latency, hardware costs 间做 trade-offs。
2.2 Replication
replication 是 kafka reliability guarantee 的核心。通过做 replication 来对抗 broker 的 crash。
如何判断某个 replica 是处于 in-sync 状态呢:
- 不言而喻 leader 是 in-sync 的
- 与 zk 有活跃的 session,定期(默认 6s)给 zk 发送 heartbeat
- 在过去10秒(可配置)内从 leader 那里 fetch messages
- 在过去10秒内获取 leader 的最新消息。 也就是说,追随者仍然从领导者那里得到消息是不够的; 它必须几乎没有滞后。
可以看出:
- in-sync 是指 replica 处在活跃的持续同步数据的状态,但不代表 leader 的数据已经全部同步到 replica 中。即使 replica 处在 in-sync 状态,消息数据本身的同步也是有延迟的。
- 只要在一个时间周期内有同步操作,replica 就被认为是 in-sync 的,这会导致误判。如 replica 向 zk, leader 活跃过状态后,立马 crash(out of sync),但由于还没到下次同步状态的时间,此时 leader 会误以为此 replica 还是 in-sync 的。
什么情况下可能导致 replicas out-of-sync 呢:
- broker 在某些时间做 garbage collection,导致进程 pause 一段时间,此时会丢掉和 zk, leader 的链接,被认为 out-of-sync,在 gc 完后,重新链接 leader & zk,恢复回 in-sync 状态
- replica 处在的 broker crash/ network partition
2.3 Broker Configuration
对于 reliable 的配置,可以配置在 broker level,也可以配置在 topic level。
2.3.1 Replication Factor
replication.factor 即副本个数,这个参数在 topic 创建后,也可以手动修改的。
replication.factor 越大,会有越高的 reliability,但是会带来更大的备份延迟时间。
不同的 replicas 建议在不同的 brokers 上,而存有 partition replicas 的 broker 尽量在不同的 rack 上。
topic level 的配置是 replication.factor
broker level 的配置是 default.replication.factor(针对自动创建的 topic)
2.3.2 Unclean Leader Election
clean leader election 是指当 leader unavailable 时,kafka 会自动从 in-sync 的 replicas 中选举一个作为新 leader。所有 committed data(committed 是指 message 已经备份到所有 in-sync 的 replicas 中) 都不会丢失。
但是,当 leader unavailable 时,完全没有 in-sync replicas 存在时,该怎么办?参数 unclean.leader.election.enable 决定了这种情况发生时的处理办法,默认配置为 true。
假设存在 3 个 replicas,如果 2 个 followers 全变为 unavailable,leader 会继续接受 write request, followers 变为 out-of-sync。现在 leader unavailable,之前 unavailable 的 out-of-sync 的 followers 恢复后,系统如何抉择:
- 如果不允许 out-of-sync 的 replica 参与 election 变成新 leader,那么 partition 只能处于 unavailable 状态,直到之前的 leader 恢复。这种配置适合金融等强数据一致性问题的场景。
- 如果允许 out-of-sync 的 replica 变成新 leader,可能会丢失一部分写在老 leader 中的数据,可能引起不同 consumer group 读到的数据不一致。这种配置适合对数据一致性不那么敏感的场景。
可以看出,这也是在 consistency 和 availability之间做选择,符合 CAP 理论。默认为 true,说明 Kafka 倾向于选择 AP & weak consistency。
Kafka 的数据同步模式很像 Master / Slave Async。
2.3.3 Minimum In-Sync Replicas
min.insync.replicas 这个参数的意思有些晦涩,我读了两三遍才算理解。它是指 replicas 中,至少有几个 replicas 处于 in-sync 状态,leader 才会执行写请求,否则拒绝写。而不是 message 至少写入了几个 replicas。
假设有 3 个 replicas(leader 包含在内),如果 min.insync.replicas = 2,则当 producer 写 messages 时,至少要有两个 replicas 处于 in-sync 状态,leader 才会执行写入操作,否则返回 producer NotEnoughReplicasException。
所以该参数配置的越高,则 messages 越可能成功写入到更多的副本。如果该参数设置为 1,则 leader 不会关心其他 replicas 是否处于及时更新数据的状态,只要 leader 自身写没问题,就写入了,而其他 replicas 可能过很长时间才能同步到数据,这就增加了 leader unavailable 时,数据丢失的风险。
2.4 Using Producers in a Reliable System
上面说完了如何配置 broker 来尽量保证 reliable,但是如果 producer 配置不当,也会导致丢数据的情况发生。
2.4.1 ACK
考虑如下场景,partition 有 3 个 replicas:
- acks=0。producer 向 leader 发送请求时,如果 serialized error 或者 network fail 时,会返回 error,能成功发出去就认为写成功,但是 leader & replicas 在过程中出任何问题导致数据没写成功,都会造成数据丢失。
- acks=1。当 producer 向 leader 写消息,leader 写成功,返回 producer success,之后在数据还没同步到 replicas 时 crash,replicas 中的一个晋升为 leader。此时,producer 认为数据写成功了,但是 leader 却把这条数据丢失了。
- acks=all。可以规避第一种情况,但是,当 producer 向 leader 写消息时,leader 宕机了 & 新的 leader 还没选出来,producer 会收到 “Leader not Available” 。如果 producer 没有处理好异常,没有重试直到写成功,这条消息也会丢失。但是如果做好 error handle,是可以保证数据不丢失的。
单独 acks 的配置不能完全保证数据的成功存储。对于 producer 来说,为了保证 reliable,需要:
- Use the correct acks configuration to match reliability requirements
- Handle errors correctly both in configuration and in code
2.4.2 Retries
当有 error 发生时,需要进行 handle。error 分为两种:
- Retriable errors: KafkaProducer 针对这种异常,可以自动发起重试。全部逻辑隐藏在 send 方法中,开发人员不需要人工干预
- a connection error can be resolved because the connection may get reestablished.
- A “no leader” error can be resolved when a new leader is elected for the partition.
- Nonretriable errors: 这种错误没法通过重试修复,会直接抛异常,需要开发人员在代码层面处理
- message size too large error
- serialization errors
retry 也有风险:
- 可能造成消息被重复写入多次。考虑以下场景:broker 成功写消息,但是由于网络原因,没及时返回 ack,producer 认为消息创建失败,发起 retry,会导致消息写入两次。当然了,我们可以在 consumer 端解决这个问题,将 consumer 做成 idempotent 幂等的接口。
- producer 耗尽了 retry 次数
这些处理方式都需要根据业务需求,做出恰当的选择。
2.5 Using Consumers in a Reliable System
由于 Kafka broker 内部已经保证了返回给 consumer 的都是 committed data(保存到全部 in-sync replicas 中),所以 consumer 需要处理的事情就简单多了。但是 consumer 还需要能够正确的处理跟踪消息 offset & 处理消息,保证每次获取到的 message 的正确性,既不重复,也不缺失。
consumer 中有 4 个参数可以影响 reliable:
- group.id: 之前已经有过详细介绍
- auto.offset.reset: 控制当 consumer 提交的 offset 不存在,或者 consumer 刚启动 & offset topic 中没有记录过 offset 时的处理方法
- earliest: 可以导致重复处理消息
- latest: 可能导致遗漏处理消息
- enable.auto.commit: 这个配置很关键,是否让 consumer 自动提交 offset,或者开发人员在自己的代码中提交
- true: 好处,不需要开发人员写代码干预。坏处:可控性差,由于是周期性提交 offset,可能会重复处理消息。
- false: 好处,灵活控制提交频率
- auto.com mit.interval.ms: 如果 enable.auto.commit=true,这个配置自动提交的间隔周期,默认是 5s,周期越短,越能减少意外发生时,重复处理的消息数
2.5.1 Consumer retry
有时,当 consumer poll 到 messages 后,处理 messages 时会发生错误,如短暂的写入 db 失败,但是又不想丢失消息,这时可以:
- 存储消息到某个 buffer(本地 queue, redis, db...) 中,consumer 继续提交最新的 offset
- 存储消息到 kafka 中单独为这种情况创建的 topic 中,有专门的 consumer 来从该 topic 中消费