Kafka的理解

      Kafka 是一个分布式的基于发布/订阅模式的消息队列(Message Queue),主要应用于大数据实时处理领域。
架构图:
在这里插入图片描述
kafka角色:
1.Producer:消息生产者,向Kafka broker发消息的客户端。
2.Consumer:消息消费者,向Kafka broker获取消息的客户端。

3.Consumer Group(CG):消费者组,由多个consumer组成,消费者组内每个消费者负责消费不同的分区的数据,一个分区只能由一个组内消费者消费,消费者组之间互不影响。
所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。Kafka可自动为同一Group中的Consumer分摊负载,从而实现消息的并发读取,并在某个Consumer发生故障时,自动将它处理的partition分区转移给同Group中其他Consumer处理。

4.Broker:一台Kafka服务器就是一个broker,一个集群由多个broker组成,一个broker可以容纳多个topic。

5.Topic:可以理解为一个队列,生产者和消费者面向的都是一个topic。

6.Partition:为了实现扩展性,一个非常大的topic可以分布到多个broker(即服务器)上,一个topic可以分为多个partition,每个partition是一个有序的队列。

7.Replica(副本):为了保证集群中的某个节点发生故障时,该节点上的partition数据不丢失,且Kafka仍然能够继续工作,kafka提供了副本机制,一个topic的每个分区都有若干个副本,一个leader和若干个follower。

8.Leader:每个分区多个副本的主,生产者发送数据的对象以及消费者的对象都是Leader。

9.Follower:每个分区多个副本中的从,定时从Leader中同步数据,保持和Leader数据的同步,Leader发生故障时,某个follower会成为新的leader。
kafka工作流程及文件存储机制:
在这里插入图片描述
      broker可以存储多个topic,一个topic又可以拆分成多个分区(partition),每一个分区有自己的副本,副本通过主从来管理。leader和follower。
      Kafka 中消息是以 topic 进行分类的,生产者生产消息,消费者消费消息,都是面向 topic的。
      topic 是逻辑上的概念,而 partition 是物理上的概念,每个 partition 对应于一个 log 文件,该 log 文件中存储的就是 producer 生产的数据。Producer 生产的数据会被不断追加到该log 文件末端,且每条数据都有自己的 offset。消费者组中的每个消费者,都会实时记录自己消费到了哪个 offset,以便出错恢复时,从上次的位置继续消费。
Kafka集群使用可配置的保留期限持久保留所有已发布的消息,如果将保留策略设置为两天,则在发布消息后的两天内,该记录可供使用,之后将被丢弃以释放空间。
在这里插入图片描述
      由于生产者生产的消息会不断追加到 log 文件末尾,为防止 log 文件过大导致数据定位效率低下,Kafka 采取了分片和索引机制,将每个 partition 分为多个 segment。每个 segment对应两个文件——“.index”文件和“.log”文件。这些文件位于一个文件夹下,该文件夹的命名规则为:topic 名称+分区序号。例如,first 这个 topic 有三个分区,则其对应的文件夹为 first-0,first-1,first-2。

      保存形式为:000.index–000.log,然后超过1g以后生成新的segment。
那么新的segment怎么命名呢?命名为本segment中最小的消息id,这样在查数据的时候就可以利用二分了。

在这里插入图片描述
Kafka Akc(确认消息收到,保证数据的可靠性) :
      为保证Producer发送的数据能可靠的发送到指定的Topic,Topic的每个Partition收到Producer发送的数据后,都需要向Producer发送Akc(acknowledgement 确认收到),如果Producer收到Ack就会进行下一轮的发送,否则重新发送数据。
副本数据同步策略:
方案一:
      半数以上完成同步发送ack
      优点:延迟低
       缺点:选举新的leader时,容忍N台节点的故障,需要2N+1个副本

方案二:
      全部完成同步发送ack
      优点:选举新的leader时,容忍N台节点故障,需要N+1个副本
       缺点:延迟高

Kafka选择第二种方案,原因:
      ①.为了容忍N台节点故障,第一种方案需要2N+1个副本,第二种方案只需要N+1个副本,而Kafka每个分区都有大量的数据,第一种方案会造成大量数据的冗余。
       ②.网络延迟对Kafka影响较小
ISR(in-syncreplica set):
       Leader维护了一个动态的ISR,意为和leader保持同步的follower集合,当ISR中follower完成数据的同步后,leader就会给follower发送ack,如果follower长时间未向leader同步数据,则该follower将被踢出ISR,该时间阀值由replica.lag.time.max.ms参数设定,leader发生故障后,从ISR中选举新的leader。
Kafka Ack答应机制:
       对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据的少量丢失,所以没必要等ISR中的follower全部接收成功
Kafka Akc三种可靠级别:
acks:
       0:producer不等待broker的ack,这一操作提供了一个最低的延迟,broker一接收到还没写入磁盘就已经返回,当broker故障时有可能丢失数据。
       1:producer等待broker的ack,partition的leader落盘成功后返回ack,如果在follower同步之前leader故障,那么将丢失数据。
      -1:producer等待broker的ack,partition的leader和follower全部落盘成功后才返回ack,但是如果follower在同步完成后、broker发送数据之前,leader发生故障,会造成数据重复。
acks = 1 数据丢失案例:
在这里插入图片描述
acks = -1 数据重复案例:
在这里插入图片描述
故障处理细节:
      Log文件中的HWLEO
      HW(Hight Watermark): 所有副本中最小的LEO,指的是消费者能见到的最大的offset,ISR队列中最小的LEO。
      LEO(Log End Offset): 每个副本最后一个offset。
在这里插入图片描述
如果此时Server-A-Leader(服务器-A-主节点)挂掉
在这里插入图片描述
Leader故障:
       leader发生故障后,会从ISR中选举新的leader,为了保证多个副本之间的数据一致性,其余的follower会先将各自的log文件高于HW的部分截取掉,然后从新的leader中同步数据。
注意:只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复。
那怎么解决故障恢复后,数据丢失和重复的问题呢?
      kafka在0.11版本引入了Lead Epoch来解决HW进行数据恢复时可能存在的数据丢失和重复的问题。
      leader epoch实际是一对值(epoch, offset),epoch表示leader版本号,offset为对应版本leader的LEO,它在Leader Broker上单独开辟了一组缓存,来记录(epoch, offset)这组键值对数据,这个键值对会被定期写入一个检查点文件。Leader每发生一次变更epoch的值就会加1,offset就代表该epoch版本的Leader写入的第一条日志的位移。当Leader首次写底层日志时,会在缓存中增加一个条目,否则不做更新。这样就解决了之前版本使用HW进行数据恢复时可能存在的数据丢失和重复的问题。
      这就有点像HashMap源码里面的modCount,用来记录整体的更新变化。
Follower故障:
       follower发生故障会被临时踢出ISR,等待该follower恢复后,follower会读取本地磁盘记录上的HW,并将log文件高于HW的部分截取掉,从HW开始向leader进行同步。等待该follower的LEO大于等于该partition的HW,等follower追上leader之后就可以重新加入ISR了。
Exactly Once :
      将服务器的 ACK 级别设置为-1,可以保证 Producer 到 Server 之间不会丢失数据,即 At Least Once 语义。
      相对的,将服务器 ACK 级别设置为 0,可以保证生产者每条消息只会被发送一次,即 At Most Once 语义。
      At Least Once 可以保证数据不丢失,但是不能保证数据不重复;相对的,At Least Once可以保证数据不重复,但是不能保证数据不丢失。但是,对于一些非常重要的信息,比如说交易数据,下游数据消费者要求数据既不重复也不丢失,即 Exactly Once 语义。
      0.11 版本的 Kafka,引入了一项重大特性:幂等性。所谓的幂等性就是指 Producer 不论向 Server 发送多少次重复数据,Server 端都只会持久化一条。(多次处理一条消息跟只处理一次是等效的)
      幂等性结合 At Least Once 语义,就构成了 Kafka 的 Exactly Once 语义。即: At Least Once + 幂等性 = Exactly Once
      要启用幂等性,只需要将 Producer 的参数中 enable.idompotence 设置为 true 即可。Kafka的幂等性实现其实就是将原来下游需要做的去重放在了数据上游。开启幂等性的 Producer 在初始化的时候会被分配一个 PID,发往同一 Partition 的消息会附带 Sequence Number。而Broker 端会对<PID, Partition, SeqNumber>做缓存,当具有相同主键的消息提交时,Broker 只会持久化一条。
      但是 PID 重启就会变化,同时不同的 Partition 也具有不同主键,所以幂等性无法保证跨分区跨会话的 Exactly Once。
Kafka 消费者:
消费方式:
      consumer 采用 pull(拉)模式从 broker 中读取数据。
为什么采用pull模式?
      push(推)模式很难适应消费速率不同的消费者,因为消息发送速率是由 broker 决定的。
它的目标是尽可能以最快速度传递消息,但是这样很容易造成 consumer 来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而 pull 模式则可以根据 consumer 的消费能力以适当的速率消费消息。
pull模式有什么不足?kafka如何解决这一问题?
      pull 模式不足之处是,如果 kafka 没有数据,消费者可能会陷入循环中,一直返回空数据。针对这一点,Kafka 的消费者在消费数据时会传入一个时长参数 timeout,如果当前没有数据可供消费,consumer 会等待一段时间之后再返回,这段时长即为 timeout。
分区分配策略:
       一个 consumer group 中有多个 consumer,一个 topic 有多个 partition,所以必然会涉及到 partition 的分配问题,即确定那个 partition 由哪个 consumer 来消费。
Kafka 有两种分配策略,一是 RoundRobin,一是 Range。
有以下情况会导致消费者分区的重新分配:
1.当consumer group的成员数量增加或者减少。
2.当消费者订阅的主题的partition数量变更。
Range策略:
      Range策略是对每个主题而言的。它会将每个topic的分区从0往后一次排列。
其分配算法是用topic分区的总个数除以消费者个数(这里指的消费者是同一个消费者组内的,因为不同组会得到topic的全量消息),除尽的话则消费者均匀分配,除不尽的话,在前面的消费者会多消费一个分区。
      我们列举三种情况来说明它:
1)有一个topic,有6个partition,有两个消费者,则经过首次分区分配后,会形成如下的形式:
在这里插入图片描述

      如上所示,将6个分区均匀的分给了两个消费者,6除以三除尽了,所以前三个属于consumer1,后三个属于consumer2。

2)有一个topic,有7个partition,有两个消费者,则经过首次分区分配后,会形成如下的形式:
在这里插入图片描述
如上所示,consumer1比consumer2多消费了一个分区的数据。
3)有两个topic,每个有5个partition,有两个消费者,则经过首次分区分配后,会形成如下的形式:
在这里插入图片描述
      如上所示,发现consumer1总共消费6分区,而consumer2只消费4个。
      综上所述,能够看出Range一个较为明显的弊端,分配不均衡。
RoundRobin策略:
      RoundRobinAssignor的分配策略是将消费组内订阅的所有Topic的分区及所有消费者进行排序后尽量均衡的分配(RangeAssignor是针对单个Topic的分区进行排序分配的)。如果消费组内,消费者订阅的Topic列表是相同的(每个消费者都订阅了相同的Topic),那么分配结果是尽量均衡的(消费者之间分配到的分区数的差值不会超过1)。如果订阅的Topic列表是不同的,那么分配结果是不保证“尽量均衡”的,因为某些消费者不参与一些Topic的分配。
在这里插入图片描述
      以上两个topic的情况,相比于之前RangeAssignor的分配策略,可以使分区分配的更均衡。不过考虑这种情况,假设有三个消费者分别为C0、C1、C2,有3个Topic T0、T1、T2,分别拥有1、2、3个分区,并且C0订阅T0,C1订阅T0和T1,C2订阅T0、T1、T2,那么RoundRobinAssignor的分配结果如下:
在这里插入图片描述
      看上去分配已经尽量的保证均衡了,不过可以发现C2承担了4个分区的消费而C1订阅了T1,是不是把T1P1交给C1消费能更加的均衡呢?
StickyAssignor:
      StickyAssignor分区分配算法,目的是在执行一次新的分配时,能在上一次分配的结果的基础上,尽量少的调整分区分配的变动,节省因分区分配变化带来的开销。Sticky是“粘性的”,可以理解为分配结果是带“粘性的”——每一次分配变更相对上一次分配做最少的变动。其目标有两点:

      1.分区的分配尽量的均衡。
      2.每一次重分配的结果尽量与上一次分配结果保持一致。
      当这两个目标发生冲突时,优先保证第一个目标。第一个目标是每个分配算法都尽量尝试去完成的,而第二个目标才真正体现出StickyAssignor特性的。

      StickyAssignor算法比较复杂,下面举例来说明分配的效果(对比RoundRobinAssignor),前提条件:

      有4个Topic:T0、T1、T2、T3,每个Topic有2个分区。
      有3个Consumer:C0、C1、C2,所有Consumer都订阅了这4个分区。
在这里插入图片描述
      上面红色的箭头代表的是有变动的分区分配,可以看出,StickyAssignor的分配策略,变动较小。
offset的维护:
      由于Consumer在消费过程中可能会出现断电宕机等故障,Consumer恢复后,需要从故障前的位置继续消费,所以Consumer需要实时记录自己消费到哪个位置,以便故障恢复后继续消费。Kafka0.9版本之前,Consumer默认将offset保存在zookeeper中,从0.9版本开始,Consumer默认将offset保存在Kafka一个内置的名字叫_consumeroffsets的topic中。默认是无法读取的,可以通过设置consumer.properties中的exclude.internal.topics=false来读取。
Kafka 高效读写数据:
顺序写磁盘:
      Kafka是将消息持久化到本地磁盘中的,一般人会认为磁盘读写性能差,可能会对Kafka性能提出质疑。实际上不管是内存还是磁盘,快或慢的关键在于寻址方式,磁盘分为顺序读写与随机读写,内存一样也分为顺序读写与随机读写。基于磁盘的随机读写确实很慢,但基于磁盘的顺序读写性能却很高,一般而言要高出磁盘的随机读写三个数量级,一些情况下磁盘顺序读写性能甚至要高于内存随机读写。

      Kafka 的 producer 生产数据,要写入到 log 文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到 600M/s,而随机写只有 100K/s。这与磁盘的机械机构有关,顺序写之所以快,是因为其省去了大量磁头寻址的时间。

零拷贝技术:
      “零拷贝”是指计算机操作的过程中,CPU 不需要为数据在内存之间的拷贝消耗资源。而它通常是指计算机在网络上发送文件时,不需要将文件内容拷贝到用户空间(User Space)而直接在内核空间(Kernel Space)中传输到网络的方式。

      零拷贝并不是不需要拷贝,而是减少不必要的拷贝次数。通常是说在 IO 读写过程中。
参考:https://blog.csdn.net/SwjtuPC/article/details/123535458?spm=1001.2014.3001.5501
page cache:
      为了优化读写性能,Kafka利用了操作系统本身的Page Cache,就是利用操作系统自身的内存而不是JVM空间内存。这样做是因为,

      JVM中一切皆对象,对象的存储会带来额外的内存消耗;
      使用JVM会受到GC的影响,随着数据的增多,垃圾回收也会变得复杂与缓慢,降低吞吐量;
      另外操作系统本身对page cache做了大量优化,通过操作系统的Page Cache,Kafka的读写操作基本上是基于系统内存的,读写性能也得到了极大的提升。
分区分段:
      Kafka的message是按topic分类存储的,topic中的数据又是按照一个一个的partition即分区存储到不同broker节点。每个partition对应了操作系统上的一个文件夹,partition实际上又是按照segment分段存储的。这也非常符合分布式系统分区分桶的设计思想。

      通过这种分区分段的设计,Kafka的message消息实际上是分布式存储在一个一个小的segment中的,每次文件操作也是直接操作的segment。为了进一步的查询优化,Kafka又默认为分段后的数据文件建立了索引文件,就是文件系统上的.index文件。这种分区分段+索引的设计,不仅提升了数据读取的效率,同时也提高了数据操作的并行度。

      总之,Kafka采用顺序读写、Page Cache、零拷贝以及分区分段等这些设计,再加上在索引方面做的优化,另外Kafka数据读写也是批量的而不是单条的,使得Kafka具有了高性能、高吞吐、低延时的特点。
Zookeeper 在 Kafka 中的作用:
      Kafka 集群中有一个 broker 会被选举为 Controller,负责管理集群 broker 的上下线,所有 topic 的分区副本分配和 leader 选举等工作。
      Controller 的管理工作都是依赖于 Zookeeper 的。

      broker是kafka的节点,当kafka集群启动的时候,所有的broker都会去zookeeper中注册自己,最先注册成功的那个会成为controller,并由它来监控zookeeper中的其它所有broker的信息,当发生变化的时候做出相应的反应。
在这里插入图片描述
Kafka 事务:
      Kafka 从 0.11 版本开始引入了事务支持。事务可以保证 Kafka 在 Exactly Once 语义的基础上,生产和消费可以跨分区和会话,要么全部成功,要么全部失败。

Producer 事务
      为了实现跨分区跨会话的事务,需要引入一个全局唯一的 Transaction ID,并将 Producer 获得的PID 和Transaction ID 绑定。这样当Producer 重启后就可以通过正在进行的 TransactionID 获得原来的 PID。
为了管理 Transaction,Kafka 引入了一个新的组件 Transaction Coordinator。Producer 就是通过和 Transaction Coordinator 交互获得 Transaction ID 对应的任务状态。TransactionCoordinator 还负责将事务所有写入 Kafka 的一个内部 Topic,这样即使整个服务重启,由于事务状态得到保存,进行中的事务状态可以得到恢复,从而继续进行。

Consumer 事务
      上述事务机制主要是从 Producer 方面考虑,对于 Consumer 而言,事务的保证就会相对较弱,尤其时无法保证 Commit 的信息被精确消费。这是由于 Consumer 可以通过 offset 访问任意信息,而且不同的 Segment File 生命周期不同,同一事务的消息可能会出现重启后被删除的情况。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值