Kafka核心技术与实战 02

Kafka通过主题(Topic)和分区(Partition)实现发布订阅,每个分区有多个副本(Replica)确保高可用。副本中,一个作为领导者提供服务,其余作为追随者用于数据冗余。如果领导者挂掉,追随者会通过选举成为新的领导者。客户端只与分区的领导者交互,避免了数据一致性问题。Kafka的分区和副本策略实现了负载均衡,但不支持追随者对外提供读服务,以防数据不一致。消费者位移(ConsumerOffset)用于跟踪消费进度,消费者组通过重平衡处理成员变化。Kafka的消息读取不保证全局顺序,可通过单分区实现局部顺序。分区分配和副本复制策略确保高效和容错,但不支持动态调整分区数量。消费者可以选择独立消费或组内消费,而位移信息存储在内部topic中,由ISR(In-Sync Replica)确保数据持久化。自定义分区策略可通过实现Partitioner接口实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在 Kafka 中,发布订阅的对象是主题(Topic),你可以为每个业务、每个应用甚至是每类数据都创建专属的主题。

高可用

  • 多个 Broker 进程能够运行在同一台机器上,但更常见的做法是将不同的 Broker 分散运行在不同的机器上,这样如果集群中某一台机器宕机,即使在它上面运行的所有 Broker 进程都挂掉了,其他机器上的 Broker 也依然能够对外提供服务

  • 备份机制(Replication)。备份的思想很简单,就是把相同的数据拷贝到多台机器上,而这些相同的数据拷贝在 Kafka 中被称为副本(Replica)

Kafka 的三层消息架构:

  • 第一层是主题层,每个主题可以配置 M 个分区,而每个分区又可以配置 N 个副本。
  • 第二层是分区层,每个分区的 N 个副本中只能有一个充当领导者角色,对外提供服务;其他 N-1 个副本是追随者副本,只是提供数据冗余之用
  • 第三层是消息层,分区中包含若干条消息,每条消息的位移从 0 开始,依次递增。
  • 最后,客户端程序只能与分区的领导者副本进行交互。

Kafka术语

  • 消息:Record。Kafka 是消息引擎嘛,这里的消息就是指 Kafka 处理的主要对象。
  • 主题:Topic。主题是承载消息的逻辑容器,在实际使用中多用来区分具体的业务。
  • 分区:Partition。一个有序不变的消息序列。每个主题下可以有多个分区。
  • 消息位移:Offset。表示分区中每条消息的位置信息,是一个单调递增且不变的值。
  • 副本:Replica。Kafka 中同一条消息能够被拷贝到多个地方以提供数据冗余,这些地方就是所谓的副本。副本还分为领导者副本和追随者副本,各自有不同的角色划分。副本是在分区层级下的,即每个分区可配置多个副本实现高可用。
  • 生产者:Producer。向主题发布新消息的应用程序。
  • 消费者:Consumer。从主题订阅新消息的应用程序。
  • 消费者位移:Consumer Offset。表征消费者消费进度,每个消费者都有自己的消费者位移。
  • 消费者组:Consumer Group。多个消费者实例共同组成的一个组,同时消费多个分区以实现高吞吐。
  • 重平衡:Rebalance。消费者组内某个消费者实例挂掉后,其他消费者实例自动重新分配订阅主题分区的过程。Rebalance 是 Kafka 消费者端实现高可用的重要手段。

最后我用一张图来展示上面提到的这些概念,希望这张图能够帮助你形象化地理解所有这些概念:

常见问题

  • 为什么 Kafka 不像 MySQL 那样允许追随者副本对外提供读服务?

如果允许follower副本对外提供读服务(主写从读),首先会存在数据一致性的问题,消息从主节点同步到从节点需要时间,可能造成主从节点的数据不一致。主写从读无非就是为了减轻leader节点的压力,将读请求的负载均衡到follower节点,如果Kafka的分区相对均匀地分散到各个broker上,同样可以达到负载均衡的效果,没必要刻意实现主写从读增加代码实现的复杂程度

首先明确一下:主从分离与否没有绝对的优劣,它仅仅是一种架构设计,各自有适用的场景

Redis和MySQL都支持主从读写分离,属于典型的读多写少场景 Kafka并不是这种场景

第二、如你所说,Redis和MySQL都支持主从读写分离,我个人觉得这和它们的使用场景有关。对于那种读操作很多而写操作相对不频繁的负载类型而言,采用读写分离是非常不错的方案——我们可以添加很多follower横向扩展,提升读操作性能。反观Kafka,它的主要场景还是在消息引擎而不是以数据存储的方式对外提供读服务,通常涉及频繁地生产消息和消费消息,这不属于典型的读多写少场景,因此读写分离方案在这个场景下并不太适合。

第三、Kafka副本机制使用的是异步消息拉取,因此存在leader和follower之间的不一致性。如果要采用读写分离,必然要处理副本lag引入的一致性问题,比如如何实现read-your-writes、如何保证单调读(monotonic reads)以及处理消息因果顺序颠倒的问题。相反地,如果不采用读写分离,所有客户端读写请求都只在Leader上处理也就没有这些问题了——当然最后全局消息顺序颠倒的问题在Kafka中依然存在,常见的解决办法是使用单分区,其他的方案还有version vector,但是目前Kafka没有提供。

最后、社区正在考虑引入适度的读写分离方案,比如允许某些指定的follower副本(主要是为了考虑地理相近性)可以对外提供读服务。当然目前这个方案还在讨论中。

因为mysql一般部署在不同的机器上一台机器读写会遇到瓶颈,Kafka中的领导者副本一般均匀分布在不同的broker中,已经起到了负载的作用。即:同一个topic的已经通过分区的形式负载到不同的broker上了,读写的时候针对的领导者副本,但是量相比mysql一个还实例少太多,个人觉得没有必要在提供度读服务了。(如果量大还可以使用更多的副本,让每一个副本本身都不太大)

1,kafka的分区已经让读是从多个broker读从而负载均衡,不是MySQL的主从,压力都在主上;

2,kafka保存的数据和数据库的性质有实质的区别就是数据具有消费的概念,是流数据,kafka是消息队列,所以消费需要位移,而数据库是实体数据不存在这个概念,如果从kafka的follower读,消费端offset控制更复杂;

3,生产者来说,kafka可以通过配置来控制是否等待follower对消息确认的,如果从上面读,也需要所有的follower都确认了才可以回复生产者,造成性能下降,如果follower出问题了也不好处理

\1. 首先Partition已经承担了Master-Slave的“高性能”功效。
\2. Follower的设计和存在,是为了“高可用”的目的,所以“高性能”的重担不在它的肩上。
\3. Leader是source of truth,所以读写都应该在leader上。
\4. 由于有__consumer_offsets的机制,每次读写都应该是在leader上,如果replica也参与了read服务,那么consumer offet就乱了。

  • 重平衡故事

整个故事是这样的:假设C1消费P0,P1, C2消费P2,P3。如果C1从未提交,C1挂掉,C2开始消费P0,P1,发现没有对应提交位移,那么按照C2的auto.offset.reset值决定从那里消费,如果是earliest,从P0,P1的最小位移值(可能不是0)开始消费,如果是latest,从P0, P1的最新位移值(分区高水位值)开始消费。但如果C1之前提交了位移,那么C1挂掉之后C2从C1最新一次提交的位移值开始消费。

所谓的重复消费是指,C1消费了一部分数据,还没来得及提交这部分数据的位移就挂了。C2承接过来之后会重新消费这部分数据。

  • 问个分区的问题,比如一个topic分为0,1,2 个分区
    写入0到9条消息,按照轮训分布:
    0分区:0,1,2,9
    1分区:3,4,5,
    2分区:6,7,8
    那对于消费端来说,不管是p2p点对点模式,还是push/sub模式来说,
    如何保证消费端的读取顺序也是从0到9?因为0到9条消息是分布在3个
    分区上的,同时消费者是主动轮训模式去读分区数据的,
    有没有可能读到后面写的数据呢?比如先读到5在读到4?

目前Kafka的设计中多个分区的话无法保证全局的消息顺序。如果一定要实现全局的消息顺序,只能单分区

  • 1、 kafka是按照什么规则将消息划分到各个分区的?
    2、既然同一个topic下的消息分布在不同的分区,那是什么机制将topic、partition、record关联或者说管理起来的?

\1. 如果producer指定了要发送的目标分区,消息自然是去到那个分区;否则就按照producer端参数partitioner.class指定的分区策略来定;如果你没有指定过partitioner.class,那么默认的规则是:看消息是否有key,如果有则计算key的murmur2哈希值%topic分区数;如果没有key,按照轮询的方式确定分区。
\2. 这个层级其实是逻辑概念。在物理上还是以日志段(log segment)文件的方式保存,日志段文件在内存中有对应的Java对象,里面关联了你说的这些。

  • 1个分区只能同时被1个消费者消费,但是1个消费者能同时消费多个分区是吗?那1个消费者里面就会有多个消费者位移变量?
    如果1个主题有2个分区,消费者组有3个消费者,那至少有1个消费者闲置?

在一个消费者组下,一个分区只能被一个消费者消费,但一个消费者可能被分配多个分区,因而在提交位移时也就能提交多个分区的位移。

针对你说的第二种情况,答案是:是的。有一个消费者将无法分配到任何分区,处于idle状态。

  • 假如只有一个Producer进程,Kafka只有一分区。Producer按照1,2,3,4,5的顺序发送消息,Kafka这个唯一分区收到消息一定是1,2,3,4,5么? Producer端,网络,数据格式等因素,会不会导致Kafka只有一个分区接收到数据顺序跟Producer发送数据顺序不一致

如果retries>0并且max.in.flight.requests.per.connection>1有可能出现消息乱序的情况

  • 分区数量一开始定义后,后面可以增加分区后,原来分区的数据应该不会迁移吧?分区数量可以减少吗?

不会自动迁移,需要你手动迁移。分区数不可以减少

  • Kafka可以关闭重平衡吗?

使用standalone consumer就完全避免rebalance了。事实上很多主流大数据流处理框架(Spark、Flink)都是这么使用的

  • 一个分区的N个副本是在同个Broker中的吗,还是在不同的Broker中,还是说是随机的?

一个分区的N个副本一定在N个不同的Broker上。

  • \1. leader 副本分布在哪个broker上随机的吗?还是有什么机制
    \2. 如文中最后一个图所示,假如broker1挂掉,broker2上的follower副本会变为leader副本吗?假如不止一个follower副本,是不是有某种选举方式来决定哪个follower副本会升级为leader副本?

\1. 有算法,不是随机的。其实不只是leader,整个副本在broker上的分配策略大体上都遵循轮询的公平法则。
\2. 从follower中选择leader的算法如下:
2.1 从ISR中选择存活的第一个副本为新leader
2.2 如果ISR为空,看是否开启了unclean leader选举,如果没有开启,那么Kafka干脆就不选leader了,直接将分区置于不可用状态;否则Kafka就从剩下的存活副本中选第一个副本作为leader(这里的顺序就是ZooKeeper中保存的副本集合顺序,即assigned_replicas项)

  • 客户端会首先请求topic分区的leader副本在哪个broker上,

客户端发送Metadata请求获取每个topic分区的leader,之后再发送真实的数据请求(Produce请求或Fetch请求)

  • Kafka不能推送消息给consumer。Consumer只能不断地轮训去获取消息。从Kafka流向consumer的唯一方式就是通过poll。另外维持一个长连接去轮训的开销通常也没有你想的那么大,特别是Kafka用的是Linux上的epoll,性能还不错,至少比select好。
  • 怎么使用自带带shell脚本获取到某个topic的当前最新的offset位置

bin/kafka-run-class.sh kafka.tools.GetOffsetShell

  • offset保存在broker端的内部topic中,不是在clients中保存
  • replica的leader和follower之间如何复制数据保证消息的持久化的问题

1.生产者消息发过来以后,写leader成功后即告知生产者成功,然后异步的将消息同步给其他follower,这种方式效率最高,但可能丢数据;2.同步等待所有follower都复制成功后通知生产者消息发送成功,这样不会丢数据,但效率不高;3.折中的办法,同步等待部分follower复制成功,如1个follower复制成功再返回,这样兼顾效率和消息的持久化。具体选择哪种要根据业务情况进行选择。说的不对的地方请老师斧正。

目前Kafka不支持第三种“折中”办法。。。要么只写leader,要么所有follower全部同步。但是,我同意很多分布式系统是可以配置同步follower和异步follower共存的,比如一个同步follower+N-1个异步follower的伪同步。Facebook的MySQL就是这个原理,参见http://yoshinorimatsunobu.blogspot.com/2014/04/semi-synchronous-replication-at-facebook.html

  • kafka 不是领导者副本向追随者副本 推送消息吗? 这里说的是追随者副本向领导者副本拉取消息?

Kafka不是PUSH模型,而是PULL模型,即follower副本不断地从leader处拉取消息。

  • 自定义分区路由算法

首先创建一个类实现org.apache.kafka.clients.producer.Partitioner接口,然后指定producer的partitioner.class属性为该类的地址即可实现你自己的分区逻辑。

  • “是否一个consumer可以消费多个partition的数据” — 可以的
    “是否一个partition可以被同一个group中的多个consumer消费” — 不可以的,只能被组内的一个consumer实例消费
  • 有一个问题,如果一个broker主挂了,那么他的消费者位移,如何传递给从?

内部位移topic的写入都是acks=all的,所以follower副本与leader副本实时同步

  • 副本既然是做数据冗余之用,那是不是一个leader只需要有一个副本就好了?

其实我也是觉得业界的Hadoop三副本是很保守的方案。我们一般公司里面的业务消息也没那么值钱,那必要存两份。通常情况下冗余一份足够了:)

如果你觉得你的业务消息很值钱的话,那么保存多份是有意义的

  • 消费者是否一定得属于某个消费者组?可以独自地消费消息吗?

可以独自消费。Kafka有standalone consumer,它不是消费者组的概念,就是单独的消费者

声明:从极客时间上整理的地址如下

https://time.geekbang.org/column/article/99318

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lakernote

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值