词汇
-
ISR
分区中的所有副本统称为AR(Assigned Replicas)。所有与leader副本保持一定程度同步的副本(包括leader)组成ISR(in-sync replicas)。而与leader副本同步滞后过多的副本(不包括leader),组成OSR(out-sync replicas),所以,AR = ISR + OSR。在正常情况下,所有的follower副本都应该与leader副本保持一定程度的同步,即AR = ISR,OSR集合为空。 -
Broker
Broker 即代理服务器,是服务的提供者。
在消息队列 CKafka 中,Broker 是一个单独的 CKafka Server,主要用来接收生产者发送的消息、分配 Offset、并将消息保存到磁盘中。同时,Broker 也接收来自消费者和其他 Broker 的请求,根据请求类型进行相应处理并返回响应。 -
分区
分区(Partition)是用于存储消息的物理概念,是 CKafka 水平扩展性能的基础,您可以通过增加服务器,并在服务器上分配分区的方式,增加 CKafka 的并行处理能力。 -
副本
在消息队列 CKafka 中,副本(Replica)是消息的冗余备份,每个分区可以有多个副本,每个副本包含的消息是一样的(在同一时刻,副本之间并不完全一样,这依赖同步机制)。
在消息队列 CKafka 中每个分区至少有双副本,保障服务的高可用。
在云数据仓库 ClickHouse 中,为了保障服务的高可用性,ClickHouse 提供了副本机制,将单个节点的数据冗余存储在2个或多个节点上。 -
Offset
Offset 是消息在分区(Partition)的唯一序号。 -
消费者分组
消费者分组(Consumer Group)是消费者的集合,在 CKafka 中,多个 Consumer 可以组成一个 Consumer Group,且一个 Consumer 只能属于一个 Consumer Group。Consumer Group 保证其订阅 Topic 的每个分区只被分配给该 Consumer Group 中的一个 Consumer 处理。 -
topic
Topic(主题)是某一种分类的名字,消息在 Topic 中可以被存储和发布。CKafka 对外使用 Topic 的概念,生产者往 Topic 中写消息,消费者从 Topic 中读消息。为了做到水平扩展,一个 Topic 实际是由多个 Partition(分区)组成,遇到瓶颈时,可以通过增加 Partition 的数量进行横向扩容。 -
ZooKeeper
ZooKeeper 是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
在消息队列 CKafka 中,ZooKeeper 主要用于存储集群的元数据(MetaData)、进行 Leader 选举、故障容错等。
技术原理
消息队列 CKafka 的架构图如下所示:
- 生产者 Producer 可能是网页活动产生的消息、服务日志等信息。生产者通过 push 模式将消息发布到 Cloud Kafka 的 Broker 集群。
- 集群通过 Zookeeper 管理集群配置,进行 leader 选举,故障容错等。
- 消费者 Consumer 被划分为若干个 Consumer Group。消费者通过 pull 模式从 Broker 中消费消息。
高吞吐
消息队列 CKafka 中存在大量的网络数据持久化到磁盘和磁盘文件通过网络发送的过程。这一过程的性能直接影响 Kafka 的整体吞吐量,主要通过以下几点实现:
- 高效使用磁盘:磁盘中顺序读写数据,提高磁盘利用率。
- 写 message:消息写到 page cache,由异步线程刷盘。
- 读 message:消息直接从 page cache 转入 socket 发送出去。
- 当从 page cache 没有找到相应数据时,此时会产生磁盘 IO,从磁盘加载消息到 page cache,然后直接从 socket 发出去。
- Broker 的零拷贝(Zero Copy)机制:使用 sendfile 系统调用,将数据直接从页缓存发送到网络上。
- 减少网络开销
- 数据压缩降低网络负载。
- 批处理机制:Producer 批量向 Broker 写数据、Consumer 批量从 Broker 拉数据。
数据持久化
消息队列 CKafka 的数据持久化主要通过如下原理实现:
- Topic 中 Partition 存储分布
在消息队列 CKafka 文件存储中,同一 Topic 有多个不同 Partition,每个 Partition 在物理上对应一个文件夹,用户存储该 Partition 中的消息和索引文件。例如,创建两个 Topic,Topic1 中存在5个 Partition,Topic2 中存在10个 Partition,则整个集群上会相应生成5 + 10 = 15个文件夹。 - Partition 中文件存储方式
Partition 物理上由多个 segment 组成,每个 segment 大小相等,顺序读写,快速删除过期 segment, 提高磁盘利用率。
水平扩展(Scale Out)
- 一个 Topic 可包含多个 Partition,分布在一个或多个 Broker 上。
- 一个消费者可订阅其中一个或者多个 Partition。
- Producer 负责将消息均衡分配到对应的 Partition。
- Partition 内消息是有序的。
消费者分组(Consumer Group)
- 消息队列 CKafka 不删除已消费的消息。
- 任何 Consumer 必须属于一个 Group。
- 同一 Consumer Group 中的多个 Consumer 不同时消费同一个 Partition。
- 不同 Group 同时消费同一条消息,多元化(队列模式、发布订阅模式)
多副本
多副本设计可增强系统可用性、可靠性。
Replica 均匀分布到整个集群,Replica 的算法如下:
将所有 Broker(假设共 n 个 Broker)和待分配的 Partition 排序。
将第 i 个 Partition 分配到第(i mod n)个 Broker 上。
将第 i 个 Partition 的第 j 个 Replica 分配到第((i + j) mod n)个 Broker 上。
Leader Election 选举机制
消息队列 CKafka 在 ZooKeeper 中动态维护了一个 ISR(in-sync replicas),ISR 里的所有 Replica 都跟上了 Leader。只有 ISR 里的成员才有被选为 Leader 的可能。
- 假设 ISR 中 f + 1个 Replica,一个 Partition 能在保证不丢失已 commit 的消息的前提下
容忍 f 个 Replica 的失败。 - 假设共有 2f + 1个 Replica(包含 Leader 和 Follower),commit 之前必须保证有 f + 1个
Replica 复制完消息,为了保证正确选出新的 Leader,fail 的 Replica 不能超过 f 个。
leader 切换
在建立一个新 topic 时,kafka broker 集群会进行每个 partition 的 leader 分配,将当前 topic 的 partition 均匀分布在每个 broker 上。
但在使用一段时间后,可能会出现 partition 在 broker 上分配不均,
或是出现客户端在生产消费中抛出 BrokerNotAvailableError,NotLeaderForPartitionError 等异常。
这通常都是由于 partition 发生了 leader 切换导致的,典型场景如下:
- 当某个 partition leader 所在的 broker 发生某些意外情况,例如网络中断,程序崩溃,机器硬件故障导致无法与 broker controller 通信时,
当前 topic partition 将会发生 leader 切换,leader 将迁移至 follower partition 上。- 当 kafka 集群设置 auto.leader.rebalance.enable = true 进行自动 reBalance,或是人工增加/削减 broker 并手动触发 reBalance 时,
由于涉及到 partition 自动平衡,此时也会出现 leader 切换。
当由于 broker 意外中断,导致 leader 切换时:
- 如果客户端设置 ack = all,并且 min.insync.replicas > 1 ,由于消息同时在 leader partition 和 follower partition 都确认,因此消息不会丢失。
- 如果客户端设置 ack = 1 ,此时可能会出现设置在 replica.lag.time.max.ms 时间中的消息未同步到 follower partition,可能导致消息丢失。
数据可靠性
生产者将数据发送到消息队列 CKafka 时,数据可能因为网络抖动而丢失,此时消息队列 CKafka 未收到该数据。可能情况:
- 网络负载高或者磁盘繁忙时,生产者又没有重试机制。
- 磁盘超过购买规格的限制,例如实例磁盘规格为9000GB,在磁盘写满后未及时扩容,会导致数据无法写入到消息队列 CKafka。
- 突发或持续增长峰值流量超过购买规格的限制,例如实例峰值吞吐规格为100MB/s,在长时间峰值吞吐超过限制后未及时扩容,会导致数据写入消息队列 CKafka 变慢,生产者有排队超时机制时,导致数据无法写入到消息队列 CKafka。
解决办法
- 生产者对自己重要的数据,开启失败重试机制。
- 针对磁盘使用,在配置实例时设置好监控和 告警策略 ,可以做到事先预防。
遇到磁盘写满时,可以在控制台及时升配(消息队列 CKafka 非独占实例间升配为平滑升配不停机且也可以单独升配磁盘)或者通过修改消息保留时间降低磁盘存储。 - 为了尽可能减少生产端消息丢失,您可以通过 buffer.memory 和 batch.size(以字节为单位)调优缓冲区的大小。缓冲区并非越大越好,如果由于某种原因生产者宕机了,那么缓冲区存在的数据越多,需要回收的垃圾越多,恢复就会越慢。应该时刻注意生产者的生产消息数情况、平均消息大小等(消息队列 CKafka 监控中有丰富的监控指标)。
- 配置生产端 ACK
当 producer 向 leader 发送数据时,可以通过 request.required.acks 参数以及 min.insync.replicas 设置数据可靠性的级别。
- 当 acks = 1时(默认值),生产者在 ISR 中的 leader 已成功收到数据可以继续发送下一条数据。如果 leader 宕机,由于数据可能还未来得及同步给其 follower,则会丢失数据。
- 当 acks = 0时,生产者不等待来自 broker 的确认就发送下一条消息。这种情况下数据传输效率最高,但数据可靠性确最低。
- 当 acks = -1或者 all 时,生产者需要等待 ISR 中的所有 follower 都确认接收到消息后才能发送下一条消息,可靠性最高。
即使按照上述配置 ACK,也不能保证数据不丢,例如,当 ISR 中只有 leader 时(ISR 中的成员由于某些情况会增加也会减少,最少时只剩一个 leader),此时会变成 acks = 1的情况。所以需要同时在配合 min.insync.replicas 参数(此参数可以在消息队列 CKafka 控制台 Topic 配置开启高级配置中进行配置),min.insync.replicas 表示在 ISR 中最小副本的个数,默认值是1,当且仅当 acks = -1或者 all 时生效。
建议配置的参数值
此参数值仅供参考,实际数值需要依业务实际情况而定。
- 重试机制:message.send.max.retries=3;retry.backoff.ms=10000;
- 高可靠的保证:request.required.acks=-1;min.insync.replicas=2;
- 高性能的保证:request.required.acks=0;
- 可靠性+性能:request.required.acks=1;