kafka文档阅读记录

kafka文档阅读记录

持久化

  • Kafka 对消息的存储和缓存严重依赖于文件系统,kafka的持久化队列是建立在简单的文件读取和文件追加两种操作之上,这和日志解决方案相同,这种架构的优点在于所有操作的复杂度为O(1)即常数,而且读操作不会影响写操作,读操作之间也不会相互影响,这在性能上有明显的优势
  • 因为性能和数据大小完全分离开来,因此可以充分利用大量廉价的容量大的硬盘。这样在性能上几乎不会有损失,而且能够尽可能的扩展硬盘空间,使得可以让消息保留相对较长的一段时间(如一周),而不是试图在被消费后立即删除。

效率

  • 前提:追加日志文件和简单读取的方式已经消除了磁盘性能的问题
  • 问题:系统性能低下的另外两个原因:大量的小型I/O操作,过多的字节拷贝
  • 分析:
        发生场景:小型I/O操作主要是在客户端和服务端之间以及服务端自身的持久化操作中,
  • 解决方式:
    • 大量小型I/O操作:使用“消息块”方式传输消息,使得网络请求将多个消息打成一组,而不是每次只发送一条消息,分担网络往返中的开销
    • 过多的字节拷贝:pagecache+sendfile的组合使用,使得在kafka集群中,大多数 consumer 消费时,将看不到磁盘上的读取活动,数据将完全由缓存提供。
    • 使用sendfile方法,可以允许操作系统将数据从pagecache直接发送到网络,这样避免重新复制数据。所以这种优化方式,只需要最后一步的copy操作,将数据复制到NIC缓冲区。
    • broker 维护的消息日志本身就是一个文件目录,每个文件都由一系列以相同格式写入到磁盘的消息集合组成,这种写入格式被 producer 和 consumer 共用。保持这种通用格式可以对一些很重要的操作进行优化: 持久化日志块的网络传输。

生产者

  • 负载均衡
    客户端可以控制消息发送数据到哪个分区,实现基本的负载均衡方式,也提供了一些特定语义的分区函数(如根据指定的键值进行hash分区,也可以自定义分区函数),
    每个partition(分区)对应一个目录,kafka相关的存储机制保证
  • 异步发送

消费者

    consumer通过向broker发送“fetch”请求来获取想要消费的partition,在每个请求中都指定了对应的offset和消费消息数量,因此是由consumer来控制该offset,并且可以在需要的时候通过回退该位置进行重新消费相应的数据。

  • Push or pull:策略:Producer将数据push到broker,consumer从broker中pull数据,因此对于消息(数据)来说都是由生产者和消费者主动push或pull消息,kafka本身不主动进行。
  • push与pull方式的优缺点:
    pull-based好处:1.consumer可以控制接收消息的速度和时间,可以在适当的时间赶上落后于producer的速度;2.可以发送批量消息给consumer,而push-based则必须选择立即发送请求或累计更多的数据,并不知道下游的consumer能否立即处理这些数据,如果改为低延迟状态,会导致一次只发送一条消息以致于传输的数据不再被缓冲,这种方式是极度浪费。
    pull-based不足之处:consumer需要一直轮询来接收获取broker的消息,如果broker中消息都被消费了,则consumer可能会一直处于轮询中直到有数据发送过来。–这个可以通过在consumer连接时设置阻塞等待。
  • offset理解:为了使得broker与consumer针对被消费的数据保持一致性,通过完全不同的一种方式解决消息丢失问题(保持消息状态一致性),每个partition在任意给定时间内只能被订阅了该topic的consumer group中的一个consumer消费(每个consumer group共享一个offset),通过offset来记录消费的位置(具体参见存储机制),即下一条要消费的消息的offset。这样使得需要记录的被消费的消息的状态信息相当少,使得以非常低的代价实现和消息确认机制等同的效果(参考传统消息确认机制)。
    另外通过offset的设置,使得consumer对于消费消息来说更加灵活,可以通过回退offset来再次消费之前的消息,这在实际应用中是一个很好的特性。

消息传递语义

  • Kafka可以提供的消息交付语义保证有多种:
    1. at most once:最多一次,消息可能被丢失但不能重传
    2. at least once:至少一次,消息可以被重传但不能丢失
    3. exactly once:只有一次,每条消息只被传递一次,需要保证不丢失也不会重传
  • 在0.11.0版本之前,kafka采用的是at least once语义,如果 producer 没有收到表明消息已经被提交的响应, 那么 producer 除了将消息重传之外别无选择。
  • 在0.11.0版本开始,kafka producer增加了幂等性的机制,保证重传不会在log中产生重复的消息保存,实现原理:broker给每个producer分配唯一ID,并且producer给每条被发送的消息分配来序列号作为标识,并且producer新增了类似事务性的语义(消息事务),发送到多个topic partition的功能,保证消息要么全部发送并保存成功,要么一条都没写入进去。
  • two-phase commit
  • 消息事务

为了支持事务,Kafka 0.11.0版本引入以下概念:
1.事务协调者:类似于消费组负载均衡的协调者,每一个实现事务的生产端都被分配到一个事务协调者(Transaction Coordinator)。
2.引入一个内部Kafka Topic作为事务Log:类似于消费管理Offset的Topic,事务Topic本身也是持久化的,日志信息记录事务状态信息,由事务协调者写入。
3.***控制消息(Control Messages)***:这些消息是客户端产生的并写入到主题的特殊消息,但对于使用者来说不可见。它们是用来让broker告知消费者之前拉取的消息是否被原子性提交。
4.TransactionId:不同生产实例使用同一个TransactionId表示是同一个事务,可以跨Session的数据幂等发送。当具有相同Transaction ID的新的Producer实例被创建且工作时,旧的且拥有相同Transaction ID的Producer将不再工作,避免事务僵死。
5.Producer ID:每个新的Producer在初始化的时候会被分配一个唯一的PID,这个PID对用户是不可见的。主要是为提供幂等性时引入的。
6.Sequence Numbler:(对于每个PID,该Producer发送数据的每个<Topic, Partition>都对应一个从0开始单调递增的Sequence Number。
7.每个生产者增加一个epoch:用于标识同一个事务Id在一次事务中的epoch,每次初始化事务时会递增,从而让服务端可以知道生产者请求是否旧的请求。
8.幂等性:保证发送单个分区的消息只会发送一次,不会出现重复消息。增加一个幂等性的开关enable.idempotence,可以独立与事务使用,即可以只开启幂等但不开启事务。

存储机制

日志存储方式

  1. kafka对消息队列数据的存储形式是以日志文件的形式,同一个Topic下有多个parition,每个partition对应一个目录,名称规则为:topic名称+有序序列号,有序序列号从0开始到partition数量-1,partition为实际物理的概念,而topic则为逻辑概念,实现上都是以每个Partition为基本实现单元的。
  2. 每个partition又细分为多个segement,每个segement文件由两部分组成,分别为.index文件和.log文件,分别表示segement的索引文件和数据文件(元数据),也称为消息索引和消息实体。索引文件中的元数据指向对应数据文件中message的"物理偏移地址"。其命名规则为:partition全局的第一个segement从0开始,后续每个segement文件名为上一个segement文件最后一条消息的offset值,长度为20为数值,缺位用0填充。
  3. 从.index中通过某条索引在.log中对应的位置
  4. 从partition中通过offset查找message
  5. 上述保存了对应消息的物理偏移位地址,但并未保存消息的结束地址,这个涉及到消息的物理结构,每个消息都具有固定的物理结构,根据物理结构可以确定消息的大小和结束位置。包括offset(8 Bytes)、消息体的大小(4 Bytes)、crc32(4 Bytes)、magic(1 Byte)、attributes(1 Byte)、key length(4 Bytes)、key(K Bytes)、payload(N Bytes)等等字段。
    kafka建立了基本的索引结构,大部分消息都会被顺序读取,当然也会存在少量的随机读取消息(如处理的时候这条消息处理失败,需要重新处理),所以建立索引以支持少量随机查询。所以在索引的实现上,基本上就是为了支持针对某个Offset进行二分查找而存在的索引。

复制原理和同步方式

复制原理,通过副本策略实现,kafka的每个topic的partition有N个副本(replicas),其中N是topic的复制因子的个数,通过多副本机制实现故障自动转移,在kafka集群中的一个Broker失效的情况下仍保证服务可用。kafka发生复制时,为了确保partition的日志能够有序的写到其他节点,N个replicas中,其中一个为leader,其他都为follower,leader处理partition的所有读写请求,follower会被动定期的去复制leader上的数据(批量,并不是每有一条数据就去复制)。
kafka提供了数据复制算法保证,如果leader发生故障或挂掉,会有一个新的leader被选举并接收客户端的消息写入。leader主要负责维护和跟踪ISR中所有follower滞后的状态,kafka允许follower与leader之间的数据具有滞后,但不能落后太多,当producer发送一条消息到broker后,leader写入消息并复制到所有follower,消息提交只有才被成功复制到所有副本中,如果follower落后太多或者失效,leader将会把它从ISR中删除。
副本策略:ISR机制
ISR(In-Sync Replicas):指副本同步队列,副本数对kafka的吞吐率是有影响的,但可以增强可用性,默认的replica数量为1,及每个partition都有一个唯一的leader,为了确保消息的可靠性,通常应用中会将设置为大于1的值。所有的副本统称为Assigned Replicas(AR),ISR是AR的一个子集,每个partition都有各自的leader和follower,由leader维护对应的ISR列表,follower从leader同步数据有一些延迟,有两个衡量延迟标准的维度:延迟时间(replica.lag.max.ms)和延迟消息条数(replica.lag.max.messages),任意一个超过阈值都会被提出ISR,存储OSR(outof-Sync Replicas)列表,新加入的follower也会先放在OSR,AR=ISR+OSR。

0.10.x版本后,kafka移除了replica.lag.max.messages参数,因为在实际过程中该参数没有起到应有的作用,该参数表示当前某个副本落后leader的消息数量超过该参数的值,那么leader就会把follower从OSR中删除。假设设置该参数值为4,那么如果producer一次传送至broker的消息数量都小于4条时,因为在leader接收到producer发送的消息之后且在follower副本开始拉取这些消息之前,follower落后leader的消息数量都不会超过4条,因此不会有follower会被移出ISR列表,但当producer发起瞬时高峰流量时,即一次发送超过4条,此时follower都会被认为与leader不同步了,从而被移出ISR,但实际上follower都是正常且没有性能问题,那么之后追上leader后被重新加入到ISR中,于是他们不断的在被移出和回归ISR之间循环,这无疑增加了不必要的性能损耗。另外这个参数的值不好控制,无法给定合适的值,设置太大了,影响真正"落后"的follower的移出;设置太小了,会导致follower频繁的移出和加入。

ISR的管理是保存在ZK节点中的,具体位置为: /brokers/topics/[topic]/partitions/[partition]/state,目前会有两个地方进行维护:

  • Controller:kafka集群中的一个Broker会被选举为Controller,主要负责Partition管理和副本状态管理,也会执行类似重分配partition之类的管理任务。Controller会选举新的leader,ISR和新的leader_epoch,controller_epoch写入ZK额相关节点中,同时发起LeaderANdIsrRequest通知所有的replicas。
  • leader:leader有单独的线程定期检查ISR中的follower是否脱离ISR,如果发现ISR发生变化,则会将新的ISR信息返回到ZK的相关节点中。

leader选举

  • 当leader宕机了,kafka是如何在follower中选举出新的leader的?因为follower可能落后很多或者直接crash了,所以必须确保选择“最新”的follower作为新的leader。一个基本的原则就是,如果leader不在了,新的leader必读拥有原来的leader commit的所有消息。这就需要做一个折中,如果leader在表明一个消息被commit前等待更多的follower确认,那么在它挂掉之后就有更多的follower可以成为新的leader,但这也会造成吞吐率下降。

  • leader选举的算法有很多,如Zookeeper的Zab、Raft以及ViewsStamped Replication。而Kafka使用的算法更像是微软的PacificA算法。

  • kafka在zookeeper中为每个partition动态的维护了一个ISR,这个ISR中所有的replica都跟上了leader(没有落后),只有ISR里面的成员才能有被选举为leader的可能(unclean.leader.election.enable=false),这种模式下,对于f+1个副本,一个kafka topic 能在保证不丢失已经 commit 消息的前提下容忍f个副本的失败,在大多是场景下比较有利。事实上为了容忍f个副本的失败, “少数服从多数”的方式和ISR在commit前 需要等待的副本的数量时一样的,但是ISR需要的总的副本个数几乎是“少数服从多数”的方式的一半。

  • 在ISR中至少有一个follower时,kafka可以确保已经commit的数据不丢失,但如果所有的replica都挂了,就无法保证数据不丢失了,这种情况下有两种可行的方案:

    • 等待ISR中任意一个replica“活”过来,并且选它作为leader
    • 选择第一个“活”过来的replica(并不一定是在ISR中)的作为leader
  • 这就需要在可用性和一致性之间做一个抉择,如果一定要等待ISR中的replica“活”过来,那不可用的时间就可能会相对较长,而且如果ISR中所有的replica都无法“活”过来了,或者数据丢失了,这个partition将永远不可用了。而第二种方式时,即使它并不保证已经包含了所有已commit的消息,它也会成为leader而作为consumer的数据源。默认情况下,kafka采用的是第二种策略(即unclean.leader.election.enable=true)。

可靠性和持久性保证

  • Kafka的高可靠性的保障来源于其健壮的副本(replication)策略。通过调节其副本相关参数,可以使得Kafka在性能和可靠性之间运转的游刃有余。Kafka从0.8.x版本开始提供partition级别的复制,replication的数量可以在$KAFKA_HOME/config/server.properties中配置(default.replication.refactor)。
  • 顺序写+无状态,不维护消息的状态,也不维护消息的存储策略

未完待续…

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