再有人问你kafka 把这篇扔给他(建议收藏)

news2024/11/15 19:42:47

前言

最近重新读起kafka的内容,看到kafka官网文档里,有专门一栏讲kafka的设计,觉得很受益。我们常常会知道这个中间件是什么,是什么机制,这次想换个角度来聊,它在设计消息系统的时候,都做了哪些考虑?为什么这么考虑?

kafka只有一个,但是它的设计思想正在被成百上千个组件在学习和借鉴。这或许是更有价值的一件事。

PS:如果有讲的不对的地方,欢迎大家指正。

PPS : 另外,本文比较长,偏基础,部分细节直接通过目录跳过也行 😂

那我开始了啊

kafka 原理及设计思考

  • 前言
  • 消息中间件的编年史
  • 一简介
    • kafka 设计规划
    • rabbitMQ 架构分析
    • kafka 架构图
  • 二基本原理
    • 1持久化
      • 磁盘
      • 顺序写
      • 存储设计及查找过程
      • 对比
      • 小结
    • 2生产者
      • 第一问 发送到哪
      • 第二问 怎么知道发送成功了
      • 第三问 既不丢也不重复?
      • 小结
    • 3消费者
      • 第一问 Push or pull
      • 第二问 消费哪个分区
      • 第三问 从哪条消息开始消费
      • 小结
  • 三特性及其具体保证
    • 1高可用及水平扩展
    • 2高吞吐
        • 1 直连
        • 2 顺序写
        • 3 PageCache
        • 4 零拷贝
        • 5 批量压缩
        • 6 批量发送
        • 7 水平扩展
  • 总结

消息中间件的编年史

在正式介绍kafka的相关细节特性之前,先插播一个消息中间件的编年史。
image.png

  • 2001年提出JMS的消息模型和规范以来
  • 2003年诞生了ActiveMQ,在今天它也还在不断迭代;
  • 2007年的RabbitMQ,它是第一个基于应用层协议的开放标准AMQP的协议实现;
  • 2011年kafka,也就是我们今天的主角。「给它的台词要多点」大家都知道它最初是LinkedIn研发的,在2011年初开源之后,2012年成为Apache 项目,直到今天 依然是Apache 顶级项目。一开始用ACtiveMQ 来解决业务问题,但扩展性不足,还经常导致阻塞,所以开始自研了Kafka。
  • 2012年阿里的RocketMQ开源。它也参照Kafka的设计理念,并主要对消息的可靠传输及事务性做了优化。
  • 2016年Yahoo开发的Pulsar,这是基于云原生的消息系统,在多租户上可以做彻底的隔离,处理速度也更快,但是相对来说落地实践的场景还不够丰富,并且缺少一个里程碑的稳定版本。
  • 2018年,我们的JMQ完成第四次大版本的迭代,性能也有了极大的提升。也就是我们现在在用的JMQ4

大家看kafka后边几个的设计架构,也会多多少少能从其中看出一些kafka设计的影子。

一简介

kafka 设计规划

image.png

总所周知,kafka最初是美国一家LinkedIn 的工程师研发,当时主要解决数据管道(data pipeline)的问题。
当时这家公司内部有很多子系统用于收集和分析,比如用户行为操作,他们会定期把数据以xml的格式发送到统一的地方进行离线处理。既然现有解决方案
reps便开始组织团队进行消息传递系统的研发; 定位:作为统一的平台来处理大公司可能拥有的所有实时数据源。定位很宏观,所以要想的场景就比较多。
" 它必须具有高吞吐量才能支持高容量事件流,例如实时日志聚合。

  • 它必须具有高吞吐量才能支持高容量事件流,例如实时日志聚合。据加载。
  • 它需要优雅地处理大量数据积压,以便能够支持来自离线系统的定期数据加载。
  • 这也意味着系统必须处理低延迟交付,以处理更传统的消息传递用例。在存在机器故障的情况下保证容错能力。"
  • 在将流馈送到其他数据系统进行服务的情况下,我们知道系统必须能够在存在机器故障的情况下保证容错能力。

也就是用我们常说的:高吞吐,低延时,支持离线,高可用
那么kafka是怎么做的?这里面
有很多设计师自己的思考,篇幅原因,我们重点说几个核心内容。

开始之前就问一个问题,实现这些很难么?🌚
别急,那我们先看一下kafka出现之前的当时的消息系统架构。

rabbitMQ 架构分析

rabbitMQ 高可用模式:

image.png

这是rabbitMq的高可用模式。每个节点都有一个队列,生产者生产的数据投递到指定的服务器的队列上,然后队列再进行同步。每个节点都有一个完整的镜像,包含全部的数据。任何一个节点宕机之后,其他节点都还有一个完整数据,别的consumer 可以到其他节点上去消费数据。
但是当我们数据量再大的时候,是没办法水平扩展的。

kafka 架构图

image.png

水平扩展:
我们看kafka的这个图里,生产者ProducerA 生产的2条消息分别发送到了分区0 和分区1,然后消费者从对应的分区上进行消费。
如果topic的数据量更大的话,那还是可以增加新的分区来水平扩展。也就是可以增加新的分片数量。

高可用:
每个分区可以设置副本。Follower实时从 leader 中同步数据,保持和 leader 数据的同步。leader 发生故障时,某个 follower 会成为新的 leader 。消费者会通过zk感知到变化,然后去从新的leader上消费数据。

那是谁来控制副本选举的过程的呢?
整个集群会选举一个Broker 作为Controller。他负责管理整个集群所有分区和副本的状态调整。

在这里先有一个大体的印象,我们接下来具体看下主要模块的工作流程是什么样的。

二基本原理

1持久化

磁盘

image.png

有第一个问题要讨论的就是磁盘。因为传统观念是磁盘总是很慢。kafka重视高吞吐,为什么要选用慢的磁盘存储?

顺序写

实际上快慢完全取决于使用方式。这是09年发表的实验结论:

image.png

看前两条, 磁盘的顺序写入量是随机写入量的100多倍。

!!值得注意的是,这个是对内存的随机写,也就是说当我们顺序写入磁盘是,是可能比内存效率还要高。假设是如果基于内存的话,:对象内存开销非常高,并且也会随着堆内数据的增加,GC的速度越来越慢。

综合对比:
硬盘相对于内存来说,无论扩展还是成本稳定性优势更明显,可以保存更长,所以的存储方式选用了磁盘进行顺序写。

存储设计及查找过程

那么我们来看下,具体的存储设计。
image.png

【图1】生产者生产的消息落到这个分区之后,会顺序的追加到这里,每条数据都有自己的 offset。

【图2】每个分区对应于一个log 文件。为防止 log 文件过大导致数据定位效率低下,将每个 partition 分为多个 segment。每个 segment里对应这样几个文件。

【图3】这几个文件都以为当前 segment的第一条消息的 offset 命名

【图4】通过索引可以定位到对应的文件位置。

【小优化】这里有个优化是,为了减少文件大小,index采用稀疏索引,大约每往log文件写入4kb数据,会往index文件写入一条索引。那如果我想找图4 里,offset为2的,我就先通过索引1找到对应的日志文件,然后再去进行遍历。

从图里看到的问题来了:
a 消息顺序性(限单分区内 局部顺序):
从这样的存储结构里,我们能看到消息在存储过程是有顺序的,但是只局限于这一个分区上。比如我发送了两条消息。一条消息到分区0 ,一条消息到分区1 那有顺序么,保证不了,因为不在一个队列里排序。

b IO竞争:
另一点上,因为顺序写,所以磁盘的效率非常高,但是如果单台机器上的Partition 数量太多,会发生多个文件同时IO的情况,会引起IO竞争,性能大幅下降。所以单机Partatio数量需要适度(就是具体是多少,这也不好说)。

对比

这里我们对比一下。这两个在存储上都有借鉴kafka,又都有不同的改造。
image.png

RocketMQ :
在同一个Broker上,所有的数据(包括不同topic的数据)都记录在同一个文件中(CommitLog)。这样不就避免了IO的问题么?全部线性来写。这样效率高了,但是对比kafka来说,又灵活性低了。比如我想按照topic定制不同的存储时间。它就做不到了。

Jmq4 :
比他们更晚,它在这一点取了两者的平衡。在同一个Broker上,按topic分类,然后同一个topic下对应一组消息文件(Log Files),顺序存放这个Topic的消息。然后收到的消息按照对应的Topic写入依次追加写入消息文件中,然后异步创建索引并写入对应Partition的索引文件中。

各有优劣,都是在灵活性和性能之间取一个平衡。

小结

image.png

2生产者

接下来说生产者这部分。看看生产者这边要考虑哪些事情:
image.png

第一问 发送到哪

首先消息发送到哪,这是生产者发送的时候就可以决定了。有这样几种方式。

image.png

  • 指定分区
  • Hash(key)%partition
  • Round-robin

比如想保持相对的顺序性,可以按照一个维度去hash,保证比如同一个订单落到同一个分区里,这样大部分情况下就是有序的。

第二问 怎么知道发送成功了

大家说这个问题很简单。就发送之后有返回码呗。 集群给个回应不就行了。
是的。那这个回应是什么时候给呢?

image.png
从最简单的消息系统说起

等等,先心里想一个答案再接着看分析:

  • A 发过去就完事了, 这样吞吐率最高。但是万一没成功 消息就丢了
  • B leader 收到之后 ,follower还没来得及同步数据 。Leader 就挂掉了-》数据丢了
  • C 半数以上。也就是这一半的机器没都挂掉,就还有数据。容灾能容这一半,这个看着很合理(实际上kafka没有这个选项)
  • D 全部follower 同步之后 ,好处是 只要有一台机器没挂,消息就不会丢。但是缺点是如果有一台机器故障 一直没有收到呢?(实际上kafka也没有这个选项)

如果要保证可靠性,确保消息不丢失那答案是:

image.png

那什么是ISR列表:
ISR(In-sync-replicas):在同步中的副本,维护机制是:如果在replica.lag.time.max.ms时间内系统没有发送fetch请求,或者已经在发送请求,但是在该限定时间内没有赶上Leader的数据就被剔除ISR列表。
(在Kafka-0.9.0版本剔除replica.lag.max.messages消息个数限定,因为这个会导致其他的Broker节点频繁的加入和退出ISR。)

想想有了这样的列表,是不是比固定的半数还是全部机器都要灵活和靠谱。实际上这个返回值什么时候返回,也是可配的,看场景:
image.png

好了,到这里,我们知道如果配置了 acks=all,那发送的消息就不会丢失了,
但是如果我这样设置了,可是给返回的时候机器挂了,那生产者还要重新发送一个消息,这样不就重复了么。那么消息可以避免重复么?

第三问 既不丢也不重复?

也就是语义上 正好一次。
image.png

嗯,kafka在这方面也是做了一些努力的!

image.png

这是什么意思呢?这个原理是什么呢?
我们首先来看下为什么会重复,

image.png

不重复的原理就是有一个唯一ID,然后这样当第二次落到分区的时候,发现一样了,就不再重新写入了
image.png

但是这样也有一个问题:问题再这个唯一ID,是由PID 和 SequenceNumber组成的。而PID是在服务启动的时候。也就是说重启PID就变了。那重启之后赶上消息重新发送,这时候消息就重复了。还有没有办法解决呢?
有。
kafka后续迭代的版本里增加了事务。当然事务不止是为了解决这个问题)

图不用细看 ==
image.png

先给结论:
幂等+事务,实现生产者恰好一次语义
怎么做到的呢?

为了实现事务,应用程序必须提供全局唯一的transactionalId,这通过客户端参数显示设置。
有了这个ID,那如果发送返回结果失败了,事务要么等恢复了继续原来的事务,我们这个事务回滚。恢复的话,因为事务ID可以直接对应到原来的消息ID,这样的话,我们就可以知道这个消息是重复的。 回滚的话,原来写入消息就回滚了,重新发送就行,也不会重复。

小结

image.png

3消费者

消费者这边,要考虑哪些事情呢?

第一问 Push or pull

image.png

第一个问题是 消息模型里两种经典方式,Push or pull 。
kafka 选用哪种方式。分析一下:
Push :很难适应消费速率不同的消费者,因为消息发送速率是由 broker 决定的。它的目标是尽可能以最快速度传递消息,但是这样很容易造成 consumer 来不及处理消息,拒绝服务。

pull :可以根据 consumer 的消费能力以适当的速率消费消息。pull 模式不足之处是,如果 kafka 没有数据,消费者可能会陷入循环中,一直返回空数据。

【小优化】👉🏻 针对这一点,Kafka 的消费者在消费数据时会传入一个时长参数 timeout,如果当前没有数据可供消费,consumer 会等待一段时间之后再返回,这段时长即为 timeout。

第二问 消费哪个分区

我知道消息是拉取了,那我有个消费者,这么多分区,我该消费哪个呢?有什么规则么?有,这几种分区策略:
image.png

但是这里有一点需要注意并且很重要的是,因为kafka一个分区只能对应一组消费者里的一个。

image.png

【重要】所以单纯增加消费者数量到一定程度后无法增加消费速度。

比如这种情况 不论是哪种分配策略,都一定是只有2个消费者在同时工作。

第三问 从哪条消息开始消费

image.png

我们知道每个消息有个offset ,通过offset就能知道从哪消费。那么这个offset 应该是谁来维护呢,消费者还是生产者 还是哪里?这肯定不能是消费端,因为一旦机器重启,这消息就丢了。但是也需要消费者消费完之后说一声。这样服务端才知道你消费完了。

offset 提交机制有两种:

image.png
这就是我们常遇到这个问题。就是消费者到底是要自动提交offset 还是手动提交?分析一下:
a : 设置自动提交之后,间隔一段时间就提交一次告诉服务端我消费到这里了。但是问题是,如果你消费的慢,可能这时候这消息还没消费完。那机器故障消息就丢了。另外,我已经消费完了,但是还没到自动提交时间,机器宕机了。那服务端不知道你消费到这里了。那下次等重启之后,又重新消费了,重了

1 自动提交 offset ——》 可能丢失,重复
2手动提交 offset ——》 不丢失,可能重复

小结

image.png

所以说,消费端这边是可以做到不丢失,但是不保证不重复,还是需要业务幂等。

三特性及其具体保证

第三部分是了解了基本原理之后,我们再来整体看下 kafka特性 以及对其都做了哪些设计

1高可用及水平扩展

image.png

这部分内容做个回顾,在上文架构图里有提。(略)

2高吞吐

image.png

我们具体看下高吞吐的保证:
1-4 也可以理解为保证低延迟 , 就是生产到消费的时间尽可能的短:
5-7 是尽可能的多。这样就比较好理解了。

1 直连

这有什么特点?因为kafka是直接通过生产端来进行了负载均衡,没有引入其他的负载均衡中间件。因为,确实是有其他MQ组件来单独用的负载均衡中间件。身份证号就不报了。感兴趣的可以去查查

2 顺序写

这点存储里已经提过啦。

3 PageCache

在 Kafka 中,大量使用了 PageCache, 当一个进程准备读取磁盘上的文件内容时,操作系统会先查看待读取的数据页是否在 PageCache 中,如果命中则直接返回数据,从而避免了对磁盘的 I/O 操作;如果没有命中,操作系统则会向磁盘发起读取请求并将读取的数据页存入 PageCache 中,之后再将数据返回给进程。

4 零拷贝

于Linux 系统调用机制,就是跳过“用户缓冲区”的拷贝,建立一个磁盘空间和内存的直接映射,数据不再复制到“用户态缓冲区”系统上下文切换减少为2次,可以提升至少一倍的性能

image.png

在实际应用中,如果我们需要把磁盘中的某个文件内容发送到远程服务器上,那么它必须要经过几个拷贝的过程:见图一
从磁盘中读取目标文件内容拷贝到内核缓冲区,CPU控制器再把内核缓冲区的数据复制到用户空间的缓冲区中;接着在应用程序中,把用户空间缓冲区中的数据拷贝到内核下的Socket Buffer中
最后,把在内核模式下的SocketBuffer中的数据赋值到网卡缓冲区(NIC Buffer);网卡缓冲区再把数据传输到目标服务器上。

在这个过程中我们可以发现,数据从磁盘到最终发送出去,要经历4次拷贝,而在这四次拷贝过程中,有两次拷贝是浪费的,而零拷贝,就是把这两次多于的拷贝省略掉,应用程序可以直接把磁盘中的数据从内核中直接传输给Socket,而不需要再经过应用程序所在的用户空间。
见上图二所示。

5 批量压缩

压缩也是为了减少文件的大小,注意是批量,一批进行统一的压缩。

1)kafka的发送端将消息按照批量(如果批量设置一条或者很小,可能有相反的效果)的方式进行压缩。
2)服务器端直接将压缩消息保存(如果kafka的版本不同,也存在broker需要先解压缩再压缩的问题)
3)消费端自动解压缩

kafka支持三种压缩算法,lz4、snappy、gzip。

6 批量发送

“避免过多的小 I/O 操作”
在很多情况下,系统的瓶颈不是CPU或磁盘,而是网络IO,所以降低大量小的 I/O:使消息消息分批发送会使整个吞吐量得到明细的提升。

7 水平扩展

一台机器的吞吐有限,我还可以用N台。

总结

今天主要写了一些kafka基本的原理及其高吞吐的保障。希望能对看到这里的同学有一些帮助 🤦🏻‍♀️ 。如果有哪里不对的地方,欢迎大家提出来~

然后,点个赞再走啊!! 🌚


1 官方文档 kafka design: https://kafka.apache.org/documentation/#design

2 部分图引自网络。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/61459.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

火山引擎虚拟数字人技术与应用

导读:火山引擎正在打造完善的虚拟数字人技术和应用体系,那么火山引擎是如何定义虚拟数字人的呢?火山引擎 2D 虚拟数字人和 3D 数字人采用了怎样先进的技术?火山引擎数字人有哪些应用和前景展望?今天我们就来一起探秘火…

【Transformer】Transformer理论知识

Transformer目录Transformer介绍Seq2seq结构Encoder结构Decoder结构Autoregressive Decoder(AT):Encoder和Decoder对比和联系Cross attention:Non-autoregressive Decoder(NAT):训练Seq2seq Mod…

基于ssm高校档案管理系统源码

档案管理作为企事业单位管理工作的基础,档案是企事业单位建设信息系统的重要组成部分,档案是提高企事业单位工作质量和工作效率的必要条件,甚至是维护历史真实面貌的一项重要工作。 1、档案信息数字化能够提供档案信息方便快捷的服务方式。 数…

量化风控的贷前实操课—详解的规则调优

风控全流程涉及贷前风控、贷中监控、贷后催收,每个模块都环环相扣,互相关联。而作为其中最关键模块的贷前风控,几乎是整个风控模块中最重要的部分,现金贷等互金产品重贷前、信用卡重贷中。 贷前风控是可以说整个防控中的第一道防线…

简单的CNN实现——MNIST手写数字识别

0.概述 此文章不涉及复杂的理论知识,仅仅只是利用PyTorch组建一个简单的CNN去实现MNIST的手写数字识别,用好的效果去激发学习CNN的好奇心,并且以后以此为基础,去进行一些改造。(前提是把基础代码看明白) 本…

java计算机毕业设计ssm社团管理系统0gl2e(附源码、数据库)

java计算机毕业设计ssm社团管理系统0gl2e(附源码、数据库) 项目运行 环境配置: Jdk1.8 Tomcat8.5 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。…

Android Room的使用详解

Android Room的使用详解 一:Room的基本介绍 Room 是 Android 架构组件的一部分,Room 持久性库在 SQLite上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。具体来说,Room 具有以下优…

【Linux Kernel 6.1 代码剖析】- 进程管理概论

目录 进程与线程的概念(内核线程和用户线程) 进程的3种基本状态 引入挂起后的7种基本状态 Linux 内核6.1 - 进程的8种详细状态 进程控制块 PCB SMP 架构 进程与线程的概念(内核线程和用户线程) 进程是正在运行的程序实体&a…

基于java+ssm+vue+mysql的旅游管理系统

项目介绍 随着现在网络的快速发展,网上管理系统也逐渐快速发展起来,网上管理模式很快融入到了许多企业的之中,随之就产生了“旅游信息管理系统”,这样就让旅游信息管理系统更加方便简单。 对于本旅游信息管理系统的设计来说&…

QDir(目录)

QDir 类提供对目录结构及其内容的访问,QDir 用于操作路径名、访问有关路径和文件的信息以及操作底层文件系统,它也可以用来访问Qt的资源系统。 Qt使用“/”作为通用目录分隔符,就像URL中的“/”用作路径分隔符一样。如果您始终使用“/”作为…

2022年大一学生实训作业【基于HTML+CSS制作中华传统文化传统美德网站 (6页面)】

🎉精彩专栏推荐 💭文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 💂 作者主页: 【主页——🚀获取更多优质源码】 🎓 web前端期末大作业: 【📚毕设项目精品实战案例 (10…

【YOLOv7/YOLOv5系列算法改进NO.47】改进激活函数为GELU

文章目录前言一、解决问题二、基本原理三、​添加方法四、总结前言 作为当前先进的深度学习目标检测算法YOLOv7,已经集合了大量的trick,但是还是有提高和改进的空间,针对具体应用场景下的检测难点,可以不同的改进方法。此后的系列…

SparkSQL - 介绍及使用 Scala、Java、Python 三种语言演示

一、SparkSQL 前面的文章中使用 RDD 进行数据的处理,优点是非常的灵活,但需要了解各个算子的场景,需要有一定的学习成本,而 SQL 语言是一个大家十分熟悉的语言,如果可以通过编写 SQL 而操作RDD,学习的成本…

ARM汇编之程序状态寄存器传输指令

ARM汇编之程序状态寄存器传输指令前言 首先,请问大家几个小小问题,你清楚: CLZ指令的常见使用场景;状态寄存器访问指令有哪些? 今天,我们来一起探索并回答这些问题。为了便于大家理解,以下是…

[附源码]Python计算机毕业设计SSM金牛社区疫情防控系统(程序+LW)

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

[附源码]JAVA毕业设计老年人健康饮食管理系统(系统+LW)

[附源码]JAVA毕业设计老年人健康饮食管理系统(系统LW) 目运行 环境项配置: Jdk1.8 Tomcat8.5 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项…

LeetCode 0542. 01 矩阵

【LetMeFly】542.01 矩阵 力扣题目链接:https://leetcode.cn/problems/01-matrix/ 给定一个由 0 和 1 组成的矩阵 mat ,请输出一个大小相同的矩阵,其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。 两个相邻元素间的距离为 1 。 示…

MySQL数据库之存储引擎

MySQL数据库之存储引擎数据存储引擎介绍MyISAM数据引擎概述MyISAM的特点介绍及数据引擎对应文件MyISAM的存储格式分类MyISAM适用的生产场景举例InnoDB数据引擎概述InnoDB特点介绍及数据引擎对应文件InnoDB适用生产场景分析企业选择存储引擎的依据如何配置存储引擎查看系统支持的…

c<8>指针

目录 2,指针的赋值 2.1C语言允许指针赋值为0(初始化) 2.2指针赋值例 2.3输出指针的值 3,用指针引用数组 3.1利用指针输入数组 3.2优先级问题 4.多维数组 5.字符串 5.1通过指针引用字符串 4.函数中对指针的应用 4.1将指针变…

[附源码]计算机毕业设计车源后台管理系统

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…