Kafka核心知识笔记整理

        本文内容主要从《Kafka技术内幕:图文详解Kafka源码设计与实现》一书中摘录总结成文,可以让我们以最快的速度回顾相关的核心知识点。文章成文以常见的领域模块组织。

集群模块

角色

  1. 生产者。客户端消息发送。
  2. 消费者。客户端消息消费。
  3. 协调者。选举产生的某个服务端节点,管理消费组里的消费者生死操作、异常处理。接受消费者的心跳,将消费者的消费进度写入内部主题。执行分区再平衡操作。

分区

  1. 分区可以有多个副本分区,主分区负责读写,副本分区仅用来同步主分区的数据。在主分区发生下线时替换它。备份副本采用异步拉取方式。
  2. 主分区维护ISR和OSR两个本地集合变量。ISR是同步中的正常副本集合,OSR是暂时未跟上同步步伐的副本集合。
  3. 分区是以均衡的方式存在于所有节点上的,这样可以最大化并行度和提升吞吐量以及容灾。

元数据

  1. 元数据信息都是以注册到zookeeper上共享的方式实现的。比如分区、消费进度、节点、主题、偏移量、所有权等
  2. kafka控制器(Master)会主动推送元数据信息到其他节点缓存到本地,主要包含主题和分区的信息,每个节点元数据都是相同的,这样客户端随便请求一个节点都可以获取到最新的元数据并缓存到本地。这样可以大大提高主题-分区的路由查询效率,避免每次都从ZK中获取元数据信息,降低ZK的负担。
  3. 生产者发送消息到目标节点分区和消费者拉取目标分区消息,都是从本地缓存的主题-分区元数据获得目标节点位置的。如果本地还没有缓存,则会去代理节点拉取一份。
  4. 采用对代理节点、主题、分区、副本进行状态机管理的设计,这样显得更加清晰易懂。通过触发事件机制优化代码结构。状态一般有新建、上线、下线、不存在四种,理清他们之间的转换关系

选举

  1. 服务端消息代理节点会选举出一个领导leader作为控制节点。管理分区分配、分区选举、故障转移。但是元数据信息基本都存储在ZK上。这有个好处是元数据不会轻易丢失,新领导可以继续正常工作,避免了元数据同步的问题。这点设计和其他分布式系统有很大不同,一般元数据都存放在中心节点上。
  2. leader的选举采用基于ZK临时节点的竞选机制。leader下线后,临时节点被删除,ZK通知其他节点就会又去竞选,成为新的leader。
  3. 新版消费者和分区再平衡操作是由消费组协调者完成的。每个消费组都会选举服务端的一个节点作为协调者,消费者提交的消费进度信息也是由协调者完成写入内部主题的。协调者根据ZK上的分区、消费组、消费者信息执行再平衡操作。
  4. 分区主副本宕机,控制器从其ISR(同步中)集合中选第一个副本作为新的主副本。如果ISR为空,则从AR(全部副本)中选第一个存活的副本作为主副本。此时有丢失数据的风险。

生产消费模块

生产者

  1. 生产者要让消息发送到指定的分区,则需要指定消息的键(本质就是通过对键进行哈希取模运算得到固定的分片值),否则默认就会被平均的发送到所有分区。因为分区上的消息有严格的顺序性,所以指定键可以实现消息消费的先后顺序。这对于其他MQ而言都是非常困难的,因为其他MQ的队列模式,一个队列可以对应于多个消费者线程,消息推送后,接受的顺序无法得到保障。
  2. 生产者使用配置“应答值”平衡数据丢失风险和性能。0表示生产者将消息放入发送缓冲区就认为成功,1表示分区主副本写入完成就认为成功,-1/all表示主副本和所有同步中的备份副本都写入完成才算消息发送成功。
  3. 生产者发送一个消息到某个分区,如果当前请求的节点上不存在这个分区。则会触发一个生产者重定向操作。重新连接目标分区节点发送消息。因为每个节点都存有整个集群的分区元数据信息。
  4. 生产者发送的消息并不会立即真的被发送出去,而是先放入本地的缓冲区。等缓冲区满或定时时间到了,才会以批量的方式发送到服务端。缓冲区的消息会按照分区不同分类存放,也就是不同的分区独享一个缓冲队列(双端队列)。有一个专门负责发送的线程不断的去轮询这些本地缓存队列,从中批量拉取消息并并创建一个到目标服务端节点的发送对象。实际上这个关键线程是不会被网络阻塞的。因为利用底层的IO多路复用模型,创建到目标节点的请求后就返回了。服务端的响应最终通过回调方式触发客户端的逻辑。这大大提高了吞吐量。
  5. 生产者发送消息到服务端有两种模式,即按照分区发送和按照目标服务节点发送。显然后者效率更高,但就要打包多个目标节点上的分区缓冲队列中的消息一起发送。
  6. 生产者对每个目标服务端节点发送的请求是排队模式,即只能串行发送,而不会并行发送。只有等前面一个发送请求发送完后,才能进行下一个发送请求。显然这是为了保证消息的顺序性存放到服务端分区,不然可能后来的消息比前面的消息更快的被服务端处理并存放至分区文件中。也就是说,消息的顺序性是在生产者端被确认的。这点也类似于zk中的事务顺序性机制。
  7. 生产者的发送消息首先会被序列化为二进制的Byte数组,然后才放入缓冲区。这样可以节省消息网络发送时再序列化的时间。如果消息有键则按键哈希取模得到分区号,放入对应的缓冲队列中。如果没有,则使用rund-robin轮流法平均的放入分区。
  8. 整个生产者发送消息到服务端的过程都是异步+缓冲模式,通过封装请求对象和响应对象,并使用线程之间的等待-通知机制实现。大大提高了效率。
  9. 客户端发送消息有异步和同步两种方式。实现了Future模式,用一个send方法完成了异步和同步两种发送方式。异步回调和同步等待

消费者

  1. 通过主题进行业务隔离。通过主题下的分区保证一对一消费模式(点对点)。通过划分消费组实现一对多消费模式(订阅)。也就是说一个分区只能对应到某个消费组中的一个消费者线程连接,不能有多个同时消费这个分区。但是一个消费者线程却可以对应多个分区,并进行轮询消费。
  2. 采用消费者Pull(拉取)模式消费消息。分片上的消息会一直存在,直到某个过期时间后自动删除。在这期间每个消费组下的消费者可以自己控制消费进度和位置(偏移量),也可以重复消费某个消息。这点设计和其他MQ有很大的区别,其他MQ大多采用服务端Push(推送)模式。这种模式存在很多问题,还会降低MQ的吞吐量。比如无法根据消费者的消费能力决定推送多少消息,为了不重复推送消息,需要对消息进行跟踪标记,并让消费者通过ACK机制来确保消息是否被处理了。
  3. 分区大于某分组下的消费者数量,则会比较平均的分配到每个消费者对接。但是如果分区数量小于某分组下的消费者数量,那么必然出现某些消费者无法对接分片的情况。这和其他MQ的设计有很大区别。
  4. 动态维护分区和消费者之间的平衡关系。当分区或消费者数量出现调整时,都会自动触发这一再平衡机制。太过频繁会影响性能。
  5. 重新分配分区和消费者后,为了能平滑过度,新消费者会获取分区的消费进度继续进行消费即可。分区的消费进度保存在zk中共享,是以消费组区分的。
  6. 通常一个JVM进程认为是一个消费者,但是消费者可以指定启动的线程数。相当于一个消费者可以有多个消费者线程数。分区的分配就是和消费者线程关联的,和消费者无关。
  7. 消费者连接器负责对本地消费者进行分区分配至具体的消费者线程以及将分区消息拉取至对应消费者线程的消费队列中。最后还会将每个消费者线程消费的进度信息提交到ZK中。
  8. 如果一个消费者线程对应了多个分区,则这些分区的消息拉取下来后会共享一个消费者线程队列。
  9. 消费者的消息拉取线程,是根据分区主副本所在节点分配的。也就是说在同一个目标节点的分区会共享一个拉取线程,而不是每个分区都创建一个拉取线程,这样显得不经济了。
  10. 拉取线程拉取的消息以消息块方式放入一个阻塞队列,每一块都是以分区划分的,等待消费者线程迭代获取消息。
  11. 消费进度偏移量默认是以定时机制自动提交的(每隔5s)。这点和其他MQ非常大的不同,他们基本都是以单个消息消费马上ACK机制实现的。kafka定时ACK机制是批量的,性能高。缺点是每条消息消费成功后确认不及时,异常情况下会导致重复消费消息或丢失消息。旧版偏移量提交到ZK,新版提交到内部主题实现。原因是担心对ZK的压力过大。
  12. 消费者也可以关闭默认自动进度提交,配置enable.auto.commit=false。改为手动提交。消费进度偏移量提交有同步(commitSync())和异步(consumer.commitAsync())之分。同步会阻塞消费者消费消息,性能较差,一般有特殊同步场景下使用。
  13. 消费者通过发送心跳给协调者,而不是集群的Master。协调者管理消费组里的消费者生死操作、异常处理。
  14. 消费者消费消息也是批量拉取方式。这是一个轮询方式,无论是否有消息都会定时请求服务端拉取。这也是Pull模式的缺点,如果服务端没有消息,就是一个无用的动作。Push模式就不会。为了解决这个问题,kafka使用阻塞消费者请求方式,直到服务端收集到足够的消息后再返回给消费者。

存储模块

磁盘

  1. 每个分区副本都对应一个单独的日志目录,目录下会有多个日志分段(Logsegment)文件,而不是一个文件,这样会使得文件后续变得非常大,不利于管理。
  2. 每个日志分段由一个数据文件和一个索引文件组成。数据文件存储真正的消息内容,而索引文件只是为了能够跟快的定位数据文件里的消息内容。
  3. 任何时候一个分区目录下只有一个活动的可写日志分段,上一个日志达到最大占用空间(1G),就会创建一个新的日志分段继续写。
  4. 生成者给服务端发送消息时就已经按照分区归纳了一批消息,称为消息集。其中的每一条消息都已经序列化为字节,并按"偏移量+长度+消息内容"的格式化好了。服务端接收后无需再使用资源去做这些事情,只需重新修改下其中的偏移量即可。这样就大大降低了服务端的压力(资源耗费)。之所以要修改偏移量是因为生产客户端计算的偏移量是临时的,只有服务端才知道最终存储时的绝对偏移量。
  5. 分区的每个日志分段偏移量都是连续递增的、全局的,称绝对偏移量。而不是每个日志分段都重新从0开始计算的。
  6. 日志分段的数据文件和索引文件以分段的基准偏移量(绝对偏移量)命名。这种设计很好的和索引相结合。zk中也是类似的设计。
  7. 数据文件添加消息前会先添加索引文件。并不是每条消息都会建立索引,而是根据配置的间隔大小每隔xx条消息才会建立一个索引。这种稀疏索引有利于减少索引文件大小,这样就可以把整个索引文件载入内存加快检索效率。
  8. 索引文件的key是消息的偏移量,value是消息所在数据文件的位置(偏移量)。是为了降低索引的空间占用,key的偏移量被设计为相对基准偏移量的偏移量,这样存储占用就短了很多,相对直接存储绝对偏移量。索引使用时,也是拿到消息的绝对偏移地址减去分段的基准地址(绝对偏移地址)就得到了相对当前分段基准地址的偏移地址了。
  9. 客户拉取消息定位过程:入参就是本次要拉取消息的绝对偏移地址。1、计算出最近的分段日志并读入其索引文件。2、计算出索引文件中的相对偏移地址,并找到最近的一个key获得其数据文件中的消息偏移地址。如果此时消息不是目标消息,则还需要进入下一步继续迭代数据文件进行查找读取。3、通过数据文件中每条消息的大小值和偏移量迭代计算并找到目标消息为止。
  10. 每个分区日志文件目录下存在一个检查点文件,用于节点故障重启时恢复数据。分区的日志每次flush到磁盘成功后都会去更新检查点文件。日志管理器又会定时的汇总所有分区的检查点文件到全局检查点文件中。

内存

  1. 消息采用append方式追加到日志分段,性能要比文件的随机寻址操作肯定要高很多。日志管理器使用定时或大小策略,flush(调用OS的fsync系统方法)刷入磁盘。这种方式适用于不会更新已有 数据的场景。
  2. 索引文件可以通过MMAP内存映射方式做快速查找。
  3. 底层对数据文件的读写采用MMAP技术,这样无论有多少个读取线程和写入线程都可以共享同一个文件描述符。实现零拷贝读写文件和发送到网络传输。
  4. 生产者发送的消息是先放入本地的缓冲区。
  5. 消费者也是先将消息拉取到本地的消费队列中然后再消费

网络模块

  1. 消息发送给消费者时采用“零拷贝”技术。避免数据反复多次从内核缓冲区拷贝到用户缓冲区,然后再由用户缓冲区拷贝到内核网络socket缓冲区的次数,直接从内核缓冲区直达socket,底层利用Linux的sendfile系统调用实现,从而大大提高了效率,这个技术有点像MMAP(内存映射)。
  2. 使用基于JAVA NIO的selector模型实现的IO多路复用方式作为底层RPC框架。类似Netty。

容错模块

事务机制

  1. 支持生产者事务方式提交多条不同主题-分区的消息,有已提交读和未提交读两种事务隔离级别。
  2. 通过事务协调者来管理生产者提交的事务消息,协调者通过专门的事务日志来复制分区消息并暂存事务消息,相当于对这批消息打上标签,客户端拉取后,不会马上触发消费,而是等待协调者的统一命令。

数据一致性

  1. 消费者心跳对象错误:消费者对协调者进行心跳,如果协调者下线选出了新的协调者时,可能有些消费者还会连接旧协调者进行继续心跳,如果后面旧的协调者又上线了。为了解决这个问题,协调者对错误心跳的消费者返回正确的协调者,这样消费者就会及时更正心跳对象。
  2. 重复消费消息:如果我们设auto.commit.interval.ms=60000,16:34首次提交偏移量62,此时又拉取了2条消息,此时分区2对应的消费者宕机,发生了分区再均衡,(分区的所有权从一个消费者转到另一个消费者被称为再均衡。一般新增消费者,消费者关闭或改变分区数都会发生再均衡)分区2的消息由另一个消费者消费,新的消费者会读取16:34提交的那个偏移量,这样就会发生重复消费了。
  3. 消费消息丢失:消费者一次pull100条新消息,并且提交了偏移量,此时消费者还没处理完,就宕机了,又发生了再均衡,由另一个消费者消费该分区的消息,新的消费者会读取旧消费者最后一次提交的偏移量,此时就会发生消息丢失了。
  4. 生产者发送消息丢失:生成者发送消息时,如果设置“应答值”为0或1,则有丢失消息的风险。要设置为-1或者all才行。
  5. 重复提交消息:服务端消息写入成功,但反馈给生产者过程中网络问题失败,会导致生成者重复提交消息(无需用户编码重试)。此时kafka保证“至少一次”语义,消费者会重复消费到消息。但是新版本对此做了“幂等性”处理,保证同一个生产者且同一个会话连接不会重复写入消息。这是通过生产者编号和消息序号作为key在服务端识别的。这种处理方案也适用于数据库操作。
  6. 重复消费消息:消费者拉取一批消息消费完后提交偏移量过程中出现异常,比如宕机。则其他消费者接收分区后,会重复拉取已经被消费过得消息。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值