Kafka原理解析

Kafka 是一个分布式流式处理平台。LinkedIn 最早开发 Kafka 用于处理海量的日志有很大关系,最开始就不是为了作为消息队列的,由于高性能,以及随着发展很多短板都被逐步修复完善,后面才在消息队列领域占据了一席之地。

主要有两大应用场景:

  1. 消息队列 :建立实时流数据管道,以可靠地在系统或应用程序之间获取数据。
  2. 数据处理: 构建实时的流数据处理程序来转换或处理数据流。

Kafka架构

基础结构

  • Broker(代理) : 可以看作是一个独立的 Kafka 实例。多个 Kafka Broker 组成一个 Kafka Cluster。
  • Topic(主题) : Producer 将消息发送到特定的主题,Consumer 通过订阅特定的 Topic(主题) 来消费消息。
  • Partition(分区) : 一个 Topic 可以有多个 Partition ,并且同一 Topic 下的 Partition 可以分布在不同的 Broker 上,Producer在发送消息时会根据分区选择器选择一个分区进行发送,这样就可以实现负载均衡,提高吞吐的效果。
  • Replica(副本):Kafka 为分区(Partition)引入了多副本(Replica)机制。分区(Partition)中的多个副本之间会有一个leader和多个follower。发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步,生产者和消费者只与 leader 副本交互。当 leader 副本发生故障时会从 follower 中选举出一个 leader,但是 follower 中如果有和 leader 同步程度达不到要求的参加不了 leader 的竞选。
  • Producer(生产者) : 产生消息的一方。
  • Consumer(消费者) : 消费消息的一方。

多分区(Partition)以及多副本(Replica)机制有什么好处呢?

  1. Kafka 通过给特定 Topic 指定多个 Partition, 而各个 Partition 可以分布在不同的 Broker 上, 这样便能提供比较好的并发能力(负载均衡)。
  2. Partition 可以指定对应的 Replica 数, 这也极大地提高了消息存储的安全性, 提高了容灾能力,不过也相应的增加了所需要的存储空间。

Kafka使用 Zookeeper保存Broker集群的元数据信息,主要包括Broker和Topic的元数据信息,老版的Kafka的消费者信息也是使用Zookeeper进行存储,新版则使用Broker存储消费者信息。

在创建Topic时,通过指定分区的个数(num.patitions),kafka会根据一定的策略将分区分配在不同broker上来保证高吞吐,同时每个分区又可以根据复制系数(replicatlon.factor)分配多个副本到不同broker上,保证消息的可靠性。副本类型分为首领副本跟随者副本,除了首领副本,其余都叫跟随者副本,首领副本会和跟随者副本之间进行数据同步。首领副本负责消息的写入和读取,首领副本所在的broker宕机之后,控制器会选择一个跟随者副本成为新的首领副本。控制器其实就是一个 broker,只不过它除了具有一般 broker 的功能之外,还负责分区首领的选举。

如何均匀进行分区、副本分配?

  1. 将 n 个Broker和待分配的 i 个partition排序
  2. 将第 i 个partition分配到第 (i % n)个broker上
  3. 将第 i 个 partition的第 j 个 副本分配到第 ((i + j) % n)个broker上

假设你有 6个 broker,打算创建一个包含 10个分区的主题,并且复制系数为 3。那么 Kafka就会有 30个分区副本, 它们可以被分配给 6个 broker。在进行分区分配时,我们从分区首领开始,依次分配跟随者副本。如果分区0的首领在broker4上,那么它的第一个跟随者副本会在 broker 5 上,第二个跟随者副本会在 broker 0上。分区1的首领在broker5上,那么它的第一个跟随者副本在 broker0上,第二个跟随者副本在 broker1上。

生产者如何知道发送到哪个分区?

获取元数据信息

生产者需要获取到topic的元数据,topic有哪些分区,分区leader在哪几台broker上,broker的ip.port等,生产者可以请求任一broker获取这些元数据信息,因为所有 broker都缓存了这些信息。

消息分发策略

一条消息由keyvalue两部份构成,但key是可选的,如果指定了key,那么producer会根据keypartition机制(hash取模)来判断当前这条消息应该存储到哪个partition中。未指定key,则随机选择一个partition存储。

消息的消费原理

1.一个分区只会分配给一个Consumer(同RocketMQ)。[想并发高就用多个队列,而不是多个消费者消费一个队列]

2.消费者负载均衡:在集群环境下,partition会被均匀分布在各个节点中,并且每一个topic会有多个consumer来订阅消费消费,而每个consumer在向broker请求消息时,会分配一个专属的partition给到consumer,这样当多个consumer向某个topic消费消息时,broker会将请求分发到存储partition的各个节点上。这种设计即是消费端的负载均衡机制。

3.分区分配策略:

  • Range(范围分区):按顺序平分,多余的给前面。比如10个分区,3个消费者,C1分配P1-P4,C2分配P5-P7,C3分配P8-P10。
  • RoundRobin(轮询):分区hash求余后顺序落在对应的消费者上。
  • 指定分区消费:消费者指定消费哪个分区。

Rebanlance触发分区分配策略【与RocketMQ不同】

触发条件

  • 同一个consumer group内新增了consumer
  • consumer离开当前所属的group,比如主动宕机或主动停止
  • topic新增了分区(有消费者订阅的某个topic的分区数量发生了变化)

Coordinator角色

kafka集群中的一个节点,每个consumer group都有一个Coordinator来管理以及协调成员消息消费,该角色负责consumer leader选举、consumer的分区派发、rebalance

确定coordinator角色

consumer group中的第一个consumer启动的时候,它会去和kafka server确定谁是它们组的coordinator,broker会返回一个负载最小的节点id,并将该broker设置为该group的coordinator,之后该group内所有consumer都和该coordinator进行通信。

Rebanlance

在rebalance之前,需要保证consumer group确定了coordinator。整个rebalance过程分为 Join(Join Group) 和 Sync(Synchronizing Group State) 两个阶段:

  • Join:consumer加入group,并选举leader。consumer group中的所有成员,都向coordinator发送一个JoinGroup的请求,申请加入groupcoordinator收到group中所有成员的JoinGroup请求后,会在group中选择一个consumer担任leader角色,并把组成员信息和订阅信息分发给组内的每个consumer
  • Sync:leader为group所有成员分配partition。consumer leadergroup中成员对应的partition分区方案发送给coordinator,然后由coordinatorpartition方案同步给组内的所有成员。这样每个成员都知道自己应该消费哪些分区。

解决方案

顺序消息

Kafka 只能为我们保证 Partition(分区) 中的消息有序。每个消息被添加到分区时,会分配一个offset(偏移量,从0开始编号),是消息在一个分区中的唯一编号,kafka可通过offset保证消息在同一个分区的顺序

消息丢失

发送者丢失:1同步发送结果;或 2设置回调重试

接收者丢失:提交offset后处理消息挂了导致消息重复消费;处理后提交offset时挂了又会导致重复消费

重复消费

1.每个生产者有一个sequenceID,broker会记录每个producer对应的当前最大sequence,如果新的消息带上的sequence不大于当前的最大sequence就拒绝这条消息,如果消息落盘会同时更新最大sequence,这个时候重发的消息会被服务端拒掉从而避免消息重复。

2.消费者保证幂等。

事务消息

不同于rocketmq的事物(保证本地执行+发消息是一个事务),kafka的事物类似数据库(保证发送多个消息到不同主题分片是一个事务,也有事务隔离级别配置-消费者RU/RC)

消息堆积

削峰 ——如果这个峰值太大了会导致大量消息堆积在队列,原因主要有二:生产者生产太快或者消费者消费太慢。

  • 生产者生产太快:可以使用一些 限流降级 的方法。
  • 消费者消费过慢:最快速解决消息堆积问题的方法还是增加消费者实例,不过 同时你还需要增加每个主题的队列数量(一个队列只会被一个消费者消费 )。也可以先检查 是否是消费者出现了大量的消费错误 ,或者查看是否是哪一个线程卡死,出现了锁资源不释放等等的问题。

push/pull

Producer使用push模式将消息推送到Broker的某个Topic,Consumer通过监听topic使用pull模式从Broker拉取消息。

(同rocketmq消费者pull是为了避免push可能造成消费者处理不过来)

高性能

1.批量+压缩:

kafka的生产者在消息发送的时候,可以根据分批大小和分批等待时间,对消息进行批量发送,同时会使用相应的压缩算法(snappy、gzip)对消息进行压缩(增加了消息延迟时间,可以关掉)

2.顺序读写:

kafka中对于每个分区的消息都是顺序写入和顺序读取的,这样可以提高磁盘的读写速度

3.零拷贝技术:

kafka默认使用sendfile技术来实现消息的读取和写入,sendfile相对于mmap不仅可以减少拷贝次数,还可以减少上下文切换的次数。

4.索引文件:

kafka的文件存储形式包含了日志文件和索引文件,索引文件可以有效提高消息的读取速度

高可用

  • 副本Replica:kafka为了提高partition的可靠性而提供了副本的概念,通过副本机制来实现冗余备份。replica尽量均匀的分布在集群机器上。
  • ISR副本集合:表示目前“可用且消息量与leader相差不多的副本集合,是整个partition副本集合的一个子集”。成为isr中的副本,必须满足两个条件(1.副本所在节点必须维持着与zookeeper的连接;2.副本最后一个消息的offset与leader副本的最后一条消息的offset之间的差值不能超过指定的阀值)

1.副本同步原理

  • 1)Producer只将消息发送到该主题的Leader分片,Leader会将该消息写入其本地Log;
  • 2)每个Follower都通过ZooKeeper找到该Partition的Leader,从Leader pull数据(没数据会阻塞一会儿/时间可配)
  • 3)Follower在收到该消息并写入其Log后,向Leader发送ACK;
  • 4)一旦Leader收到了ISR中的所有Replica的ACK,该消息就被认为已经commit了,Leader将增加HW(HighWatermark)并且向Producer发送ACK;

2.Leader副本的选举过程

KafkaController会监听ZooKeeper的/brokers/ids节点路径,一旦发现有broker挂了,在该broker上的leader分区会重新选举leader(KafkaController挂了,各个broker会重新leader选举出新的KafkaController)

  • a) 优先从isr列表中选出第一个作为leader副本,这个叫优先副本,理想情况下优先副本就是该分区的leader副本 
  • b) 如果isr列表为空,则查看该topic的unclean.leader.election.enable配置。为false的话,则表示不允许,直接抛出NoReplicaOnlineException异常;
  • c)为true则代表允许选用非isr列表的副本作为leader,那么此时就意味着数据可能丢失。一旦选举成功,则将选举后的leader和isr和其他副本信息写入到该分区的对应的zk路径上。

可靠性

1.acks

acks参数指定了必须要有多少个分区副本收到消息,生产者才会认为消息写入是成功的。这个参数对消息丢失的可能性有重要影响。

1)ack=0,生产者在成功写入悄息之前不会等待任何来自服务器的响应。

2)ack=1,只要集群的首领副本收到消息,生产者就会收到一个来自服务器的成功响应。

3)ack=all或-1,只有当所有同步副本全部收到消息时,生产者才会收到一个来自 服务器的成功响应。

2.不完全首领选举:

不完全首领选择是指是否允许不同步副本选举称为首领副本,10s内没有请求首领副本最新消息的跟随者副本被称之为不同步副本。如果我们允许不同步的副本成为首领,那么就要承担丢失数据和出现数据不一致的风险。 如果不允许它们成为首领,那么就要接受较低的可用性,因为我们必须等待原先的首领恢复到可用状态 。通过unclean.leade.election.enable参数设置不完全首领选举是否开启

3.最小同步副本:

通过min.insync.replicas参数设置最小同步副本个数。例如包含3个副本的主题,如果 min.insync.replicas被设为 2,那么至少要存在两个同步副本才能向leader分区写入数据。

4.同步异步刷盘:

同步异步刷盘的区别在于,消息存储在内存中以后,是否会等待执行完刷盘动作再返回。kafka可以通过配置flush.message和flush.ms来设置刷盘策略,如果flush.message设置为5,表示每5条消息进行一次刷盘。如果flush.ms设置为1000,表示每过1000ms进行一次刷盘。 

5.什么情况会丢失信息?

acks=0、1:Leader副本接收成功后挂掉,而Follower副本可能还在同步,新Leader副本(原Follower副本)未能和其保持一致,就会出现消息丢失的情况。

acks=-1但isr列表为空:所有的Replia都不工作,等待Replica唤醒期间的消息都会丢失。

文件存储

1.组成:每个分区会创建一个分区目录,分区目录下面存放的是日志文件(.log)和索引文件(.index / .timeIndex)

2.命名:日志文件名为上一个log文件的最后一个offset的值+1,作为新的日志和索引文件名(索引和日志文件同名/仅后缀不同)。(比如00000000000000000271.log文件的上一个日志文件中最后的offset一定是270)

3.清理:日志文件和索引文件会不断被清理,依赖于topic的保留时长(log.retention.ms)和保留字节大小(log.retention.bytes)决定。

 

日志文件

1.每个日志文件大小默认1G,可根据log.segment.bytes配置;

2.日志文件存储了消息的具体内容(offset、messageSize、data);offset类似消息自增id

索引文件

索引包含两个部分(均为4个字节的数字),分别为相对offset和position

  • 相对offset:因为数据文件分段以后,每个数据文件的起始offset不为0,相对offset表示这条Message相对于其所属数据文件中最小的offset的大小。例如:分段后的一个数据文件的offset是从20开始,那么offset为25的Message在index文件中的相对offset就是25-20 = 5。存储相对offset可以减小索引文件占用的空间(像es的所以也使用增量id压缩来减少存储空间)。
  • position,表示该条Message在数据文件中的绝对位置。只要打开文件并移动文件指针到这个position就可以读取对应的Message了。

稀疏索引(像跳表)

index文件中并没有为数据文件中的每条Message建立索引,而是采用了稀疏存储的方式,每隔一定字节的数据建立一条索引。这样避免了索引文件占用过多的空间,从而可以将索引文件保留在内存中。但缺点是没有建立索引的Message也不能一次定位到其在数据文件的位置,从而需要做一次顺序扫描,但是这次顺序扫描的范围就很小了。

消费者offset存储原理

  • 消费者offset是消费端在某个分区消费完一个消息之后的一个偏移量,下次再消费时从该偏移位置开始继续消费,所以kafka需要保存每个消费端在某个分区消费的offset值。
  • kafka在内部维护了一个__consumer_offsets的topic,该topic默认有50个partition,用于保存每个consumer group消费消息时的offset信息。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值