在使用 Kafka 时,我们经常会遇到的问题有哪些?
- 会不会丢消息?
- Offset 怎么保存?
- Consumer 重复消费问题怎么处理?
- 如何保证消息的顺序?
- 数据倾斜怎么处理?
- 一个 Topic 分配多少个 Partiton 合适以及修改 Partiton有哪些限制?
如何保证消息不丢失?
- 消息不丢失对于消息队列来说至关重要,但要实现这一点也是非常困难。因为服务器可能会宕机,磁盘可能会坏,所以对于 Kafka 的消息不丢失主要指以下两方面:
- 如果发送失败,发送方要能对它进行重试或者相应处理。
- 如果发送成功,要确保当一部分数量的 Kafka 服务器全部被物理销毁,这个消息依旧能够被持久化保存下来。
如何保证消息发布的可靠性
以下引用之前分享的部分PPT内容
-
当 Producer 将一个 Message 发送到 Topic 被写入到 Leader Partition 中后,并被所有 ISR 给同步到本地,此时只要 ISR 的机器有一台还存活着且磁盘完好,这个消息就能够正常存在。如果在 Leader 刚写入完,但此时 Leader 立马挂了,会导致这个消息永久丢失。如果要实现绝对意义的不丢失,就需要 Producer 当且仅当获到确认通知后,才认为消息发送是成功的。但这种等待的性能损耗会随着 Replication 的数量增多而线形增多。
-
在有些场景下,我们可以只要求 Leader 写入完成就告诉我们成功了(不用等待写入副本集)。但会存在一个消息重发的情况:当 Leader 写入完成后通知 Producer 时,由于网络原因丢包了,导致 Producer 没收到确认信息,误以为发送失败了,此时又继续发送了同一份消息,这个时候可能会存两份。 此时,Consumer 需要在消费 Message 的时候明确处理这种情况,比如可以在每条消息里加一个全局唯一 ID 去标识一个消息,在消费的时候去判断是否消费过这个消息。
-
如果在能够接受 Message 丢失的情况,只要不去关注 Leader 的写入成功信息即可,每个消息仅发送一次,不在乎发送是否成功。
-
在 Kafka 中,我们可以有以下三个参数来处理上述情况:
- acks=0: Producer 不等待 Broker 的 acks。发送的消息可能丢失,但永远不会重发。
- acks=1: Leader 不等待其他 Follower 同步,Leader 直接写 log 然后发送 acks 给 Producer。
- acks=all: Leader 等待所有 Follower 同步完成才返回 acks。
如何保证消息消费的可靠性
以下引用之前分享的部分PPT内容
纠正:在0.8版本之前,Kafka一直将 Consumer 的 Offset 信息记录在 ZooKeeper 中。由于 ZooKeeper 并不适合大批量的频繁写入操作,从0.8.2版本开始 Kafka 开始支持将 Consumer 的 Offset 信息保存在 Kafka 内部的 Topic 中(从0.9.0版本开始默认将 Offset 存储到系统 Topic 中)
- 在正常情况下,希望消息队列里的消息仅被消费一次,且一定会被消费一次,并且处理结果一定是成功的。
- Kafka 的 Consumer 机制只是提供了一个保存 Offset 的接口,由于在没有过期的情况下,Kafka 并不会主动去删除消息,所以我们的问题仅仅在于如何去确保保存 Offset 和处理消息成功这两个操作是一个原子操作。
以下引用之前分享的部分PPT内容
-
At most once 至多一次:当可以接受个别消息没有被处理的情况,我们也可以选择先保存 Offset ,再处理消息。
-
At least once 至少一次:如果我们的 Consumer 是一个幂等函数,相同的输入执行多次也不会影响到最终结果。那么我们就能够接受重复处理消息的情况。而此时只要确保所有的消息都能够被至少消费一次就行了。这种场景我们可以选择先处理消息,再保存 Offset 。
-
Exactly once 有且仅有一次:一般情况下,如果 Consumer 仅仅只是做无状态的操作,我们完全不需要考虑它是否多次消费的问题。但大部分时候 IO 操作是有状态的,如将计算结果保存到数据库中的操作。有一种解决方案是,如借助 MySQL insert ingore把每次消费的 Offset 作为一个字段(唯一键或唯一键的一部分)一起存入数据库中。
如何保证消息的顺序
- Kafka 每个 Partition 都是相互独立的,Kafka 只能保证单个 Partition 下的有序。
- 局部有序:当我们所需要的有序其实是针对单个用户的有序,而不要求全局有序。我们可以以用户的 ID 作为 key , 确保单个用户一定会被分配到某个固定的 Partition 上(可能会引起数据倾斜问题),这样我们就能够实现单个用户维度的有序了。
- 如果一定要全局的有序,所有消息都使用同一个 key ,这样他们一定会被分配到同一个 Partition 上,这种做法适用于临时性且数据量不大的小需求,消息量大了会有性能压力。
数据倾斜怎么处理?
- 为 Producer 选择合适的 Key
- 假设一个场景,我们需要将每个用户的 Page View 信息给存入 Kafka ,此时我以 userId 来作为 key 。理想情况下这种选择是不会错的,但如果有爬虫来模拟用户操作时,此用户的访问量可能是正常用户的百倍甚至千倍,这时,虽然 userId 作为 key 是均匀分布的,但其背后的数据量却并不一定是均匀分布的,就可能产生数据倾斜的情况,导致各个 Partition 数据量分布不均匀。
如何选择 Partiton 的数量
- 在创建 Topic 的时候可以指定 Partiton 数量,也可以在创建完后手动修改。但 Partiton 数量只能增加不能减少。中途增加 Partiton 会导致各个 Partiton 之间数据量的不平等。
- Partition 的数量直接决定了该 Topic 的并发处理能力。但也并不是越多越好。Partition 的数量对消息延迟性会产生影响。
- 一般建议选择 Broker Num * Consumer Num ,这样平均每个 Consumer 会同时读取 Broker 数目个 Partition , 这些 Partition 压力可以平摊到每台 Broker 上。
如有不当之处请指出,后续逐步完善更正