Kafka相关知识点

简介

Kafka是一个分布式、分区的、多副本的、多订阅者,基于zookeeper协调的分布式日志系统(也可以当做消息队列系统),常见可以用于web/nginx日志、访问日志,消息服务等等。

根据官网的介绍,ApacheKafka®是一个分布式流媒体平台,它主要有3种功能:

  1:发布和订阅消息流。这个功能类似于消息队列,这也是kafka归类为消息队列框架的原因

  2:以容错的方式保存消息流。kafka以文件的方式来存储消息流

  3:在消息发布的时候可以进行处理。

主要应用的场景有两个:

      1:构建可在系统或应用程序之间可靠获取数据的实时流数据管道。     消息队列系统

      2:构建转换或响应数据流的实时流应用程序。   日志收集系统

Kafka术语介绍

broker

Kafka 集群包含一个或多个kafka服务器,每个kafka服务器节点称为broker。

broker存储topic的数据。如果某topic有N个partition,集群有N个broker,那么每个broker存储应该该topic的一个partition。

如果某topic有N个partition,集群有(N+M)个broker,那么其中有N个broker存储该topic的一个partition,剩下的M个broker不存储该topic的partition数据。

如果某topic有N个partition,集群中broker数目少于N个,那么一个broker存储该topic的一个或多个partition。在实际生产环境中,尽量避免这种情况的发生,这种情况容易导致Kafka集群数据不均衡。

topic

每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。物理上不同Topic的消息分开存储,对于相同的topic,也可以根据需要分为多个partition存储,而一个partition物理上对应一个文件夹,而partition也会根据需要存储在不同的broker上,因此一个Topic的消息可能会保存于一个或多个broker上,但用户在生产或消费消息的时候只需要指定对应的topic。

partition

topic中的数据可以分为一个或多个partition存储。每个partition在物理存储层面对应的是不同的文件夹,而这些文件夹中存放着多个log file(称为segment)。每个partition还可以根据配置生成多个副本,当然副本的数量自然也应该不大于kafka集群中broker的数量。当然kafka会为topic的每个partition在副本中选出一个leader,之后所有该partition的请求,实际操作的都是leader,然后再同步到其他的follower。当一个broker歇菜后,所有leader在该broker上的partition都会重新选举,选出一个leader。(这里不像分布式文件存储系统那样会自动进行复制保持副本数)

leader

每个partition有多个副本,其中有且仅有一个作为leader,leader是当前负责数据的读写的partition。

follower

follower跟随leader,所有写请求都通过leader路由,follower主动从leader拉取数据,follower与leader保持数据同步。如果leader失效,则从follower中选举出一个新的leader。当follower与leader挂掉、卡住或者同步太慢,leader会把这个follower从“in sync replicas”(ISR)列表中删除,重新创建一个lollower。

producer

生产者即数据的发布者,该角色将消息发布到Kafka的topic中。broker接收到生产者发送的消息后,broker将该消息追加到当前用于追加数据的segment文件中。生产者发送的消息,存储到一个partition中,生产者也可以指定数据存储的partition。

comsumer

消费者可以从broker中读取数据。消费者可以消费多个topic中的数据。

comsumer group

每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)。

 

kafka生产流程

kafka数据基本流向图

数据流向

上图中有两个生产者producer用于产生消息,产生的消息在存入kafka集群后,由集群分别发布给订阅的3个消费者组,每个消费者组中分别有1个或多个消费者,在kafka集群中有3个broker节点,每个节点中有topic_01和topic_02两种主题的消息,topic_01主题中又分为了两个partition,每个partition都共有3个副本集,topic_02主题有一个partition,该partition都也有3个副本集,其中一个是leader,其余两个是follower用于保证了良好的容灾。

Topic

kafka中主题是发布记录的类别或订阅源名称。可以简单的将同一个topic的消息理解为同一类消息。每个topic被分为指定数量的partition(区)提高kafka的吞吐量。每个partition实际上在磁盘上对应一个文件夹,这个文件夹中存放着该partition的所有消息和索引文件。kafka在接收到生产者发送的消息之后,会根据均衡策略将消息存储到不同的分区中。

每个partition在存储层面是append log文件。任何发布到此partition的消息都会被直接追加到log文件的尾部,每条消息在文件中的位置称为offset(偏移量),offset为一个long型数字,它是唯一标记一条消息。

每个消费者保留的唯一元数据是该消费者在日志中的偏移或位置。这种偏移由消费者控制:通常消费者在读取记录时会线性地提高其偏移量,但事实上,由于消费者控制位置,它可以按照自己喜欢的任何顺序消费记录。例如,消费者可以重置为较旧的偏移量以重新处理过去的数据,或者跳到最近的记录并从“现在”开始消费。

Kafka默认将offset保存在kafka(0.9之前是保存在zookeeper中)。具体的方法就是把offset提交,跟随具体消息一期保存在log文件中。可以从下图看出,具体log文件中存储的数据是什么样的。

partition

kafka中的partition分布在集群服务器上,每个partition根据配置生成响应数量的副本(副本数量需要小于broker的数量),用于实现容错。

topic具体partition都有对应的leader和follower,leader处理数据的读写,follower被动的同步数据,当leader挂掉后,其中的一个follower会成为新的leader。在kafka的集群中,partition的leader和follower会均衡的分布到不同的broker中,这样保证了集群的负载平衡。

关于数据保存的策略

  • kafka提供基于时间的策略删除消息数据,在kafka的配置文件中对应参数为:log.retention.hours,例如,如果保留策略设置为两天,则在发布记录后的两天内,它可供使用,之后将被丢弃以释放空间。
  • kafka还提供基于partition的log文件大小删除旧消息数据的策略,在kafka的配置文件中对应参数为:log.segment.bytes,例如可以配置Partition文件超过1GB时删除旧数据。

producer

生产者负责将消息发送给指定的topic,生产者也可以指定具体发送给topic的哪一个partition。

consumer

消费者订阅topic是以消费者组订阅的,同一个消费者组里可能有多个消费者,但是同一个消费者组里的不同消费者不能同时消费一个partition的消息,也就是说,一个partition里的消息,只能被消费者组里的一个消费者获取,可以被多个消费者组订阅。

消费者

当一个消费者组中新增了一个消费者实例时,该实例将从组中其他消费者接管一些分区,当一个消费者组中一个实例死亡时,该实例接管的分区会重新分配给该消费者组中的其他实例。

Reblance机制介绍:

Reblance本质上是一种协议,规定了一个Consumer Group下的所有的Consumer如何达成一致来分配订阅Topic的每个Partition。比如某个group下有2个consumer,它订阅了一个具有10个分区的topic。正常情况下,Kafka平均会为每个consumer分配5个分区。

进行reblance的情况:

  • 有新的consumer加入
  • 旧的consumer挂了
  • coordinator挂了,集群选举出新的coordinator
  • topic的partition新加
  • consumer调用unsubscrible(),取消topic的订阅

kafka消息传送

kakfa消息传送过程中可能存在的情况

  • At most once:最多一次,消息可能会丢失,但不会重复
  • At least once:最少一次,消息不会丢失,可能会重复
  • Exactly once:只且一次,消息不丢失不重复,只且消费一次(0.11中实现,仅限于下游也是kafka)

at most once: 消费者fetch消息,然后保存offset,然后处理消息;当client保存offset之后,但是在消息处理过程中出现了异常,导致部分消息未能继续处理.那么此后"未处理"的消息将不能被fetch到,这就是"at most once".

at least once: 消费者fetch消息,然后处理消息,然后保存offset.如果消息处理成功之后,但是在保存offset阶段发生异常导致保存操作未能执行成功,这就导致接下来再次fetch时可能获得上次已经处理过的消息,这就是"at least once"。这篇博文介绍了常见的重复消费的情况。其实在保存offset阶段,如果发生rebalance,导致已经消费消息但是offset保存失败,就会导致重复消费的情况。

exactly once: kafka中并没有严格的去实现(基于2阶段提交,事务),我们认为这种策略在kafka中是没有必要的.

通常情况下"at-least-once"是我们首选.(相比at most once而言,重复接收数据总比丢失数据要好).

kafka消息传送过程中的生产者

生产者配置中"acks"指定了必须有多少个分区副本接收到了消息,生产者才会认为消息是发送成功的

通过配置acks不同的值来设置生产者ACK具体采用哪一种机制。生产者ACK共有下面三种机制:

acks=0:消息发送完毕即offset增加,生产者继续发送可以继续发送消息。这意味着生产者producer不等待来自broker同步完成的确认继续发送下一条(批)消息。此选项提供最低的延迟但最弱的耐久性保证(当服务器发生故障时某些数据会丢失,如leader已死,但producer并不知情,发出去的信息broker就收不到)。对应At most once。

acks=1:集群的leader收到了消息,生产者将会受到发送成功的一个响应,然后才可以发送下一条消息。如果消息无撞到达leader节点(比如leader节点崩愤,新的首领还没有被选举出来),生产者会收到一个错误响应,为了避免数据丢失,生产者会重发消息。不过,如果Leader刚刚接收到消息,Follower还没来得及同步过去,结果Leader所在的broker宕机了,此时也会导致这条消息丢失。

acks=-1(all):所有参与复制的节点全部收到消息的时候,生产者才会收到来自服务器的一个响应,这种模式最安全,但是吞吐量受限制,它可以保证不止一个服务器收到消息,就算某台服务器奔溃,那么整个集群还是会正产运转。对应At least once。

三种机制,性能依次递减 (producer吞吐量降低),数据健壮性则依次递增。

在acks的基础上吞吐量还取决于使用的是同步发送还是异步发送。当消息发送成功或失败后,同步发送是通过调用Future 对象的get()方法,让发送客户端等待服务器的响应,这样的做法显然会增加延迟问题(阻塞)(在网络上传输一个来回的延迟)。异步发送是客户端使用回调函数,延迟(阻塞)问题就可以得到缓解,不过吞吐量还是会受发送中消息数量的限制(比如,生产者在收到服务器响应之前可以发送多少个消息)。对应At least once。

高可用性配置

要保证数据写入到Kafka是安全的,高可用的,需要如下的配置:

  • topic的配置:offsets.topic.replication.factor>=3,即副本数至少是3个;
  • broker的配置:leader的选举条件unclean.leader.election.enable=true(默认为false),此参数标识当ISR中没有副本时,是否可以选举OSR( Out-of-sync Replicas)中的副本(不推荐,会造成数据丢失)。
  • producer的配置:acks=all,并且生产者发送消息必须同步发送,即需要调用send()方法返回的Future对象的get()方法

kafka高可用设计解析

为何需要给partition设置副本

  在Kafka在0.8以前的版本中,是没有Replication的,一旦某一个Broker宕机,则其上所有的Partition数据都不可被消费,这与Kafka数据持久性及Delivery Guarantee的设计目标相悖。同时Producer都不能再将数据存于这些Partition中。

        在没有Replication的情况下,一旦某机器宕机或者某个Broker停止工作则会造成整个系统的可用性降低。随着集群规模的增加,整个集群中出现该类异常的机率大大增加,因此对于生产系统而言Replication机制的引入非常重要。

为什么Partition副本需要leader

  引入Replication之后,同一个Partition可能会有多个Replica,而这时需要在这些Replication之间选出一个Leader,Producer和Consumer只与这个Leader交互,其它Replica作为Follower从Leader中复制数据。

  因为需要保证同一个Partition的多个Replica之间的数据一致性(其中一个宕机后其它Replica必须要能继续服务并且即不能造成数据重复也不能造成数据丢失)。如果没有一个Leader,所有Replica都可同时读/写数据,那就需要保证多个Replica之间互相(N×N条通路)同步数据,数据的一致性和有序性非常难保证,大大增加了Replication实现的复杂性,同时也增加了出现异常的机率。而引入Leader后,只有Leader负责数据读写,Follower只从Leader顺序Fetch数据(N条通路),系统更加简单且高效。

如何将partition均匀的分布到kafka的集群中

为了更好的做负载均衡,Kafka尽量将所有的Partition均匀分配到整个集群上。一个典型的部署方式是一个Topic的Partition数量大于Broker的数量。同时为了提高Kafka的容错能力,也需要将同一个Partition的Replica尽量分散到不同的机器。实际上,如果所有的Replica都在同一个Broker上,那一旦该Broker宕机,该Partition的所有Replica都无法工作,也就达不到HA的效果。同时,如果某个Broker宕机了,需要保证它上面的负载可以被均匀的分配到其它幸存的所有Broker上。

Kafka分配Replica的算法如下:

1.将所有Broker(假设共n个Broker)和待分配的Partition排序

2.将第i个Partition分配到第(i mod n)个Broker上

3.将第i个Partition的第j个Replica分配到第((i + j) mode n)个Broker上

HW与leader epoch(0.11.0.0版本后采用leader epoch)

这篇文章介绍得很清楚https://www.cnblogs.com/warehouse/p/9545429.html

kafka副本同步机制

版本0.9及以后的Kafka只使用一个参数来确定滞后副本(lagging replica),而不再使用replica.lag.max.messages参数。这是因为什么原因呢?

  在详细解释此事之前我们先明确一些公共的术语以方便后续的讨论:

  • ISR:in-sync replicas。与leader副本保持同步状态的副本集合(leader副本本身也在ISR中)。
  • OSR:Out-of-sync Replicas, 与Leader 滞后过多的副本,如何判断滞后,请参考之前的ISR.
  • AR: Assigned Replicas,分区中所有的副本都称为 AR,即AR=ISR+OSR
  • High Watermark:副本高水位值,简称HW,它表示该分区最新一条已提交消息(committed message)的位移
  • LEO:log end offset。从名字上来看似乎是日志结束位移,但其实是下一条消息的位移,即追加写下一条消息的位移

  值得一提的,HW表示的是最新一条已提交消息的位移。注意这里是已提交的,说明这条消息已经完全备份过了(fully replicated),而LEO可能会比HW值大——因为对于分区的leader副本而言,它的日志随时会被追加写入新消息,而这些新消息很可能还没有被完全复制到其他follower副本上,所以LEO值可能会比HW值大。两者的关系可参考下图:

消费者只能消费到HW线以下的消息;而未完全备份的消息不能被消费者消费。

  明白了这些术语之后,还有个问题需要研究下: follower部分与leader副本不同步,这是什么意思?不同步(out of sync)意味着follower副本无法追上leader副本的LEO,而这又是什么意思呢?我们举个简单的例子来说明。设想我们有一个topic,它只有一个分区,备份因子是3。假设这三个副本分别保存在broker1,broker2和broker3上。leader副本在broker1上,其他两个broker上的副本都是follower副本,且当前所有的副本都在ISR中。现在我们设置replica.lag.max.messages等于4——表示只要follower副本落后leader副本的消息数小于4,该follower副本就不会被踢出ISR。如果此时有个producer程序每次给这个topic发送3条消息,那么初始状态如下:

很显然,目前2个follower副本与leader副本是同步的,即它们都能追上leader副本的LEO。假设此时producer生产了1条新消息给leader副本,而同时broker3上的follower副本经历了一次Full GC,那么现在的日志状态如下图:

从上图可以发现,leader副本的HW值和LEO值已然变得不一样了。不过更重要的是,最新生产的这条消息是不会被视为“已提交”的,除非broker3被踢出ISR或者broker3上的follower副本追上了leader的LEO。由于replica.lag.max.messages=4,而broker3上的follower副本也只是落后leader副本1条消息,所以此时broker3上的副本并不满足条件因而也不会被踢出ISR。对于broker3上的副本而言,事情变得相当简单——只需追上leader的LEO即可。如果我们假设broker3因为Full GC停顿了100ms之后追上了leader的进度,那么此时的日志状态应该如下图所示:

 此时一切都很完美了,leader的HW值与LEO值相同;2个follower副本都与leader副本是同步的。

   那么有什么可能的原因会使得follower副本与leader副本不同步呢?归纳起来有三种原因:

  • 速度跟不上——follower副本在一段时间内都没法追上leader副本的消息写入速度,比如follower副本所在broker的网络IO开销过大导致备份消息的速度慢于从leader处获取消息的速度
  • 进程卡住了——follower副本在一段时间内根本就没有向leader副本发起FetchRequest请求(该请求就是获取消息数据),比如太过频繁的GC或其他失败导致
  • 新创建的——如果用户增加了备份因子,很显然新follower副本在启动过程初始肯定是全力追赶leader副本,因而与其是不同步的

  replica.lag.max.messags参数就是用于检测第一种情况的。当然Kafka还提供了一个参数 replica.lag.time.max.ms来检测另外两种情况。比如如果设置 replica.lag.time.max.ms=500ms,只要follower副本每隔500ms都能发送FetchRequest请求给leader,那么该副本就不会被标记成dead从而被踢出ISR。

  由于本文重点关注replica.lag.max.messages参数,那么我们来说一下Kafka检测第一种情况会碰到的问题。回到之前提到的那个例子,如果producer一次性发送消息的速率是2条/秒,即一个batch都有2条消息,那么显然设置replica.lag.max.messages=4是个相当安全且合适的数值。为什么? 因为在leader副本接收到producer发送过来的消息之后而follower副本开始备份这些消息之前,follower副本落后leader的消息数不会超过3条消息。但如果follower副本落后leader的消息数超过了3条,那么你肯定希望leader把这个特别慢的follower副本踢出ISR以防止增加producer消息生产的延时。从这个简单的例子上来看,这个参数似乎工作得很好,为什么要移除它呢?根本原因在于如果要正确设置这个参数的值,需要用户结合具体使用场景自己去评估——基于这个原因,新版本Kafka把这个参数移除了。

  好了,我来详细解释一下这个根本原因。首先,对于一个参数的设置,有一点是很重要的:用户应该对他们知道的参数进行设置,而不是对他们需要进行猜测的参数进行设置。对于该参数来说,我们只能去猜它应该设置成哪些值,而不是根据我们的需要对其进行设置。为什么?举个例子,假设在刚才那个topic的环境中producer程序突然发起了一波消息生产的瞬时高峰流量增加,比如producer现在一次性发送4条消息过来了,也就是说与replica.lag.max.messages值相等了。此时,这两个follower副本都会被认为是与leader副本不同步了,从而被踢出ISR,具体日志状态如下图所示:

   从上图看,这两个follower副本与leader不再同步,但其实他们都是存活状态(alive)的且没有任何性能问题。那么在下次FetchRequest时它们就能追上leader的LEO,并重新被加入ISR——于是就出现了这样的情况:它们不断地被踢出ISR然后重新加回ISR,造成了与leader不同步、再同步、又不同步、再次同步的情况发生。想想就知道这是多大的开销!问题的关键就在replica.lag.max.messages这个参数上。用户通过猜测设置该值,猜测producer的速度,猜测leader副本的入站流量。

  可能有用户会说该参数默认值是4000,应该足够使用了吧。但有一点需要注意的是,这个参数是全局的!即所有topic都受到这个参数的影响。假设集群中有两个topic: t1和t2。假设它们的流量差异非常巨大,t1的消息生产者一次性生产5000条消息,直接就突破了4000这个默认值;而另一个topic,t2,它的消息生产者一次性生产10条消息,那么Kafka就需要相当长的时间才能辨别出t2各个分区中那些滞后的副本。很显然这种流量差异巨大的topic很容易地在同一个集群上部署,那么这种情况下replica.lag.max.messages参数应该设置什么值呢? 显然没有合适的值,对吧?

  综上所述,新版本的Kafka去除了这个参数,改为只使用一个参数就能够同时检测由于slow以及由于进程卡壳而导致的滞后(lagging)——即follower副本落后leader副本的时间间隔。这个唯一的参数就是replica.lag.time.max.ms,默认是10秒。对于第2,3种不同步原因而言,该参数没有什么具体的变化。但是对于第一种情况,检测机制有了一些微调——如果一个follower副本落后leader的时间持续性地超过了这个阈值,那么这个副本就要被标记为dead从而被踢出ISR。这样即使出现刚刚提到的producer瞬时峰值流量,只要follower没有持续性地落后,它就不会反复地在ISR中移进移出。

kafka节点控制器选举

  1. 先获取 zk 的 /cotroller 节点的信息,获取 controller 的 broker id,如果该节点不存在(比如集群刚创建时),那么获取的 controller id 为-1;
  2. 如果 controller id 不为-1,即 controller 已经存在,直接结束流程;
  3. 如果 controller id 为-1,证明 controller 还不存在,这时候当前 broker 开始在 zk 注册 controller;
  4. 如果注册成功,那么当前 broker 就成为了 controller,这时候开始调用 onBecomingLeader() 方法,正式初始化 controller(注意: controller 节点是临时节点 ,如果当前 controller 与 zk 的 session 断开,那么 controller 的临时节点会消失,会触发 controller 的重新选举);
  5. 如果注册失败(刚好 controller 被其他 broker 创建了、抛出异常等),那么直接返回。

在这里 controller 算是成功被选举出来了,controller 选举过程实际上就是各个 broker 抢占式注册该节点,注册成功的便为 Controller。

kafka分区leader选举

Kafka通过leaderSelector完成leader的选举。

可能触发为partition选举leader的场景有: 新创建topic,broker启动,broker停止,controller选举,客户端触发,reblance等等 场景。在不同的场景下选举方法不尽相同。Kafka提供了几种leader选举方式,说明:

OfflinePartitionLeaderElectionStrategy

触发场景:
    * 当创建分区(创建主题或增加分区都有创建分区的动作)或分区上线(比如分区中原先的leader副本下线,此时分区需要选举一个新的leader上线来对外提供服务)的时候都需要执行leader的选举动作。
选举有两种情况:
    1.AR列表中找到第一个存活的副本,且这个副本在目前的ISR列表中,unclean.leader.election.enable=false。
    2.AR列表中找到第一个存活的副本,这个副本可以不在的ISR列表中而在OSR列表中,unclean.leader.election.enable=true。

ControlledShutdownPartitionLeaderElectionStrategy,当某节点被优雅的关闭时 (执行 ControlledShutdown) ,该节点的副本都会下线,于此对应的分区需要执行 Leader 选举。

触发场景:
    * kafka的broker进程退出,发送消息给controller,controller触发
选举:
    * 在ISR列表中的选出存活的replica,还要确保这个副本不处于正在被关闭的节点上。否则抛出异常

PreferredReplicaPartitionLeaderElectionStrategy

触发场景:
    * znode节点/admin/preferred_replica_election写入相关数据
    * partition-rebalance-thread线程进行触发reblance时
    * 新产生controller
选举 :
    1) AR中取出一个作为leader,如果与原有leader一样,抛出异常
    2) 新leader的replica的broker存活且replica在ISR中,选出,否则抛出异常

ReassignPartitionLeaderElectionStrategy

触发场景:
    * znode节点LeaderAndIsr发生变化
    * Broker启动时
    * zknode节点/admin/reassign_partitions变动
    * 新产生controller时
选举:
    * 新设置的AR中,存在broker存活的replica且replica在ISR中则选出为leader,否则抛出异常

在所有的leader选举策略中,如果符合条件的replica有多个,如Seq[int],则使用的是Seq.head,取的是seq的第一个。

消费者组中的选举

类似broker中选了一个controller出来,消费也要从broker中选一个coordinator,用于分配partition。

组协调器GroupCoordinator需要为消费组内的消费者选举出一个消费组的coordinator,这个选举的算法也很简单,分两种情况分析:

  1. 如果消费组内还没有leader,那么第一个加入消费组的消费者即为消费组的leader。
  2. 如果某一时刻leader消费者由于某些原因退出了消费组,那么会重新选举一个新的leader,这个重新选举leader的过程又更“随意”了,在GroupCoordinator中消费者的信息是以HashMap的形式存储的,其中key为消费者的member_id,而value是消费者相关的元数据信息。leaderId表示leader消费者的member_id,它的取值为HashMap中的第一个键值对的key,这种选举的方式基本上和随机无异。总体上来说,消费组的leader选举过程是很随意的。

关于kafka的各种问题

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章