Kafka 设计架构原理详细解析(超详细图解)

什么是Kafka?

Apache Kafka是一个开放源代码的分布式事件流平台,成千上万的公司使用它来实现高性
能数据管道,流分析,数据集成和关键任务等相关的应用程序。

Kafka的应用场景

  1. 构造实时流数据管道,它可以在系统或应用之间可靠地获取数据 (相当于message queue),特别是在集群情况下,多个服务器需要建立交流
  2. 构建实时流式应用程序,对这些流数据进行转换或者影响。 (就是流处理,通过kafka stream topic和topic之间内部进行变化)

Kafka架构设计

在这里插入图片描述
Producer:生产者可以将数据发布到所选择的topic(主题)中。生成者负责将记录分配到topic的哪一个分区(partition)中,这里可以使用对多个partition循环发送来实现多个server负载均衡

Broker:日志的分区(partition)分布在Kafka集群的服务器上。每个服务器处理数据和请求时,共享这些分区。每一个分区都会在以配置的服务器上进行备份,确保容错性。
其中,每个分区都有一台server作为leader,零台或堕胎server作为follows。leader server处理一切对分区的读写请求,而follwers只需被动的同步leader上的数据。当leader宕机了,followers中的一台server会自动成为新的eader,每台server都会成为某些分区的leader和某些分区的follower,因此集群的负载是均衡的

Consumer:消费者使用一个group(消费组)名称来表示,发布到topic中的每条记录将被分配到订阅消费组中的其中一个消费者示例。消费者实例可以分布在多个进程中或多个机器上
这里有两个注意的地方:

  1. 如果所有的消费者实例在同一个消费组中,消息记录会负载均衡到消费组中的每一个消费者实例
  2. 如果所有的消费者实例在不同的消费组中,则会将每条消息记录广播到所有的消费组或消费者进程中
    在这里插入图片描述
    如图中所示,这个Kafka集群中有两台server,四个分区(p0-p3)和两个消费组。这时分区中的消息记录会广播到所有的消费者组中

Kafka 生产者架构

在这里插入图片描述
基本流程:

  1. 主线程Producer中会经过拦截器、序列化器、分区器,然后将处理好的消息发送到消息累加器中
  2. 消息累加器每个分区会对应一个队列,在收到消息后,将消息放到队列中
  3. 使用ProducerBatch批量的进行消息发送到Sender线程处理(这里为了提高发送效率,减少带宽),ProducerBatch中就是我们需要发送的消息,其中消息累加器中可以使用Buffer.memory配置,默认为32MB
  4. Sender线程会从队列的队头部开始读取消息,然后创建request后会经过会被缓存,然后提交到Selector,Selector发送消息到Kafka集群
  5. 对于一些还没收到Kafka集群ack响应的消息,会将未响应接收消息的请求进行缓存,当收到Kafka集群ack响应后,会将request请求在缓存中清除并同时移除消息累加器中的消息

Kafka 消费者架构

在这里插入图片描述
基本流程:
Consumer Group中的Consumer向各自注册的分区上进行消费消息
Consumer消费消息后会将当前标注的消费位移信息以消息的方式提交到位移主题中记录,一个Consumer Group中多个Consumer会做负载均衡,如果一个Consumer宕机,会自动切换到组内别的Consumer进行消费

关键的点:
Consumer Group:组内多个的Consumer可以公用一个Consumer Id,组内所有的Consumer只能注册到一个分区上去消费,一个Consumer Group只能到一个Topic上去消费

位移主题

位移主题的主要作用是保存Kafka消费者的位移信息

Kafka老版本之前:
在Kafka老版本之前处理方式是自动或手动地将位移数据提交到Zookeeper进行保存,Consumer重启后,自动从Zookeeper中读取消费位移信息,从而在上次的offset地方继续消费
优点: Kafka Broker中不需要保存位移数据,减少了Broker端需要持有的状态信息,有利于动态扩展
缺点: 每一个Consumer消费后需要发送位移信息到Zookeeper,而Zooker不适用于这种高频的写操作

Kafka最新版本中位移主题的处理方式:
Consumer的位移信息offset会当作一条条普通消息提交到位移主题(_consumer_offsets)中

Kafka 文件存储架构

在这里插入图片描述
window文件系统中的文件列表:
在这里插入图片描述
这里比较好理解:

  1. 一个Topic分别存储在不同的partition中
  2. 一个partitioin对应着多个replica备份
  3. 一个relica对应着一个Log
  4. 一个Log对应多个LogSegment
  5. 而在LogSegment中存储着log文件、索引文件、其它文件

Kafka 如何保证数据有序性?

一些场景需要保证多个消息的消费顺序,比如订单,但在kafka中一个消息可能被发到多个partition中多个线程处理,被多个消费者消费,无法保证消息的消费顺序

解决方案:将需要顺序消费的消息发送的时候设置将某个topic发送到指定的partition(也可以根据key的hash与分区进行运算),则在partition中的消息也是有序的,消费的时候将一组同hash的key放到同一个queue中保证同一个消费者下的同一个线程对此queue进行消费。

总结一个producer->一个partition->一个queue->一个comsumer->一个线程
当对于需要顺序消费的消息数量大的时候,无法保证吞吐量

Kafka 如何保证数据可靠性?

AR(Assigned Replicas):分区中的所有副本统称为AR。所有消息会先发送到leader副本,然后follower副本才能从leader中拉取消息进行同步。但是在同步期间,follower对于leader而言会有一定程度的滞后,这个时候follower和leader并非完全同步状态

OSR(Out Sync Replicas):follower副本与leader副本没有完全同步或滞后的副本集合

ISR(In Sync Replicas):AR中的一个子集,ISR中的副本都是与leader保持完全同步的副本,如果某个在ISR中的follower副本落后于leader副本太多,则会被从ISR中移除,否则如果完全同步,会从OSR中移至ISR集合。

在默认情况下,当leader副本发生故障时,只有在ISR集合中的follower副本才有资格被选举为新leader,而OSR中的副本没有机会(可以通过unclean.leader.election.enable进行配置)

HW(High Watermark):高水位,它标识了一个特定的消息偏移量(offset),消费者只能拉取到这个水位 offset 之前的消息

LEO(Log End Offset):标识当前日志文件中下一条待写入的消息的offset。在ISR集合中的每个副本都会维护自身的LEO,且HW==LEO。
在这里插入图片描述
图中,HW就是8,Consumer只能拉去0~7的消息,LEO就是15,代表消息还没有同步到follower

下面通过一个例子来说明下ISR、HW、LEO之间的关系:
在这里插入图片描述
假设由一个leader副本,它有两个follower副本,这时候producer向leader写入3、4两条消息,我们来观察下他们是如何同步的
在这里插入图片描述
这个时候写入两条消息到leader,这个时候LEO变为5,然后follower开始同步leader数据
在这里插入图片描述
由于网络或其它原因,follower2同步效率较低,还没有完成同步,这个时候HW的offset为4,在此offset之前的消息Consumer都可见
在这里插入图片描述
在一定的延迟后,follower2也完成了队leader副本的同步,这时HW为5,LEO为5,且两个follower副本都在ISR集合中,在leader或follower宕机后,会在ISR集合的副本中选举一个来当新的leader副本

HW高水位的弊端:

  1. 高水位更新需要一轮额外的拉取请求
  2. leader和follower之间同步会有时间差,可能导致数据不一致或数据丢失
    接下来通过一个例子来进行详细说明消息1消息丢失的过程(min.insync.replicas=1):
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    对于消息不一致的情况
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    就是leader、follower同时宕机,然后由follower先恢复且写入消息1,HW=1,leader恢复启之后发现HW相等,则不进行同步,但实际上他们的消息1不是同一个消息,导致消息不一致

在kafka 0.11.0.0版本中引入Leader Epoch来解决使用高水位导致的数据丢失和数据不一致的问题
所谓leader epoch实际上是一对值:(epoch,offset),epoch标识leader的版本号,从0开始,每变更一次leader,epoch+1;而offset对应于该epoch版本的leader写入第一条消息(成为leader后的首条消息)的位移
(0,0)、(1,120)表示第一个leader从位移0开始写入消息,共写了120条,第二个leader版本号为1,从位移120处开始写入消息

规避数据丢失(图片来源网络):
在这里插入图片描述
规避数据不一致(图片来源网络):
在这里插入图片描述

Kafka 高性能原因分析

  1. 顺序写入:顺序写入与随机写入速度相差高达6000倍

  2. 批量处理:使用消息累加器仅多个消息批量发送,既节省带宽有提高了发送速度

  3. 消息压缩:kafka支持队消息压缩,支持格式有:gzip、snapply、lz4,可以使用compression.type配置

  4. 页缓存:在消息发送后,并没有等到消息写入磁盘后才返回,而是到page Cache中就返回。page Cache与文件系统的写入由操作系统自动完成

  5. 零拷贝(zero-copy):Kafka两个重要过程都使用了零拷贝技术,且都是操作系统层面的狭义零拷贝,一是Producer生产的数据存到broker,二是 Consumer从broker读取数据。
    正常的非零拷贝的数据拷贝过程:
    在这里插入图片描述
    硬盘—>内核缓冲区—>用户缓冲区—>内核socket缓冲区—>协议引擎

    • Producer生产的数据持久化到broker,采用mmap文件映射实现顺序的快速写入
      在这里插入图片描述
      硬盘—>内核缓冲区—>共享到用户空间缓存,共享而不是复制

    • Customer从broker读取数据,采用sendfile将磁盘文件读到OS内核缓冲区后,直接转到socket buffer进行网络发送。 sendfile() 只是适用于应用程序地址空间不需要对所访问数据进行处理的情况
      在这里插入图片描述
      硬盘—>内核缓冲区—>内核socket缓冲区—>协议引擎

关键配置

Broker 配置

名称描述类型默认值配置示例
log.dir保存日志数据的目录(对log.dirs属性的补充)string/tmp/kafka-logs
log.dirs保存日志数据的目录,如果未设置将使用log.dir的配置stringnulllog.dirs=/home/kafka1,/home/kafka2,/home/kafka3
zookeeper.connectZookeeper主机地址stringzookeeper.connect=zk1:2181,zk2:2181,zk3:2181/kafka1
listeners监听器列表 - 使用逗号分隔URI列表和监听器名称。如果侦听器名称不是安全协议,则还必须设置listener.security.protocol.map。指定主机名为0.0.0.0来绑定到所有接口。留空则绑定到默认接口上。stringnull合法监听器列表的示例:PLAINTEXT:// myhost:9092,SSL://:9091 CLIENT://0.0.0.0:9092,REPLICATION:// localhost:9093
listener.security.protocol.map侦听器名称和安全协议之间的映射。stringPLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_SSL:SASL_SSL
auto.create.topics.enable是否允许在服务器上自动创建topicbooleantrue推荐为false
unclean.leader.election.enable指定副本是否能够不再ISR中被选举为leader,即使这样可能会丢失数据booleanfalse推荐为true
auto.leader.rebalance.enable是否允许leader平衡。后台线程会定期检查并触发leader平衡booleantrue推荐为true
log.retention.{hoursminutesms}日志删除的时间阈值(时、分、毫秒)int
log.rentention.bytes日志删除的大小阈值long-1-1,表示没有限制
message.max.byteskafka允许的最大的一个批次消息大小int1000012=976KB

Topic 配置

名称描述类型默认值配置示例
retention.ms规定了该topic消息被保存的时长long604800000
retention.bytes规定了要为该 Topic 预留多大的磁盘空间long-1
max.message.bytes KafkaKafka允许接收该topic最大消息大小int1000012=976KB

Consumer 配置

名称描述类型默认值配置示例
auto.commit.interval.ms消费者偏移量自动提交给Kafka的频率(以毫秒为单位)int5000

总结

文章中分别介绍了Kafka的整体架构、架构设计的细节、Kafka实现高性能所作出的努力及一些常用的配置。同时通过比较多的图解来详细说明一些复杂逻辑。

扩展

mmap 内存映射

传统文件访问:
在这里插入图片描述

mmap的作用是映射文件描述符和指定文件的(off_t off)区域至调用进程的(addr,addr *len)的内存区域,如下图所示:
在这里插入图片描述
直接将文件映射到内存中,且不需要经过cache、分页物理存储

参考

Kafka 中文文档
Kafka水位(high watermark)与leader epoch的讨论

  • 11
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
### 回答1: Spark Streaming是一个流处理框架,可以处理实时数据流。而Kafka是一个分布式的消息队列系统,可以实现高吞吐量的数据传输。将Spark Streaming与Kafka整合起来,可以实现高效的实时数据处理。 以下是Spark Streaming整合Kafka详细指南: 1. 首先,需要在pom.xml文件中添加Kafka和Spark Streaming的依赖。 2. 接着,需要创建一个KafkaProducer,用于向Kafka发送数据。可以使用Kafka的Java API来创建KafkaProducer。 3. 然后,需要创建一个KafkaConsumer,用于从Kafka接收数据。同样可以使用Kafka的Java API来创建KafkaConsumer。 4. 在Spark Streaming中,需要创建一个StreamingContext对象。可以使用SparkConf对象来配置StreamingContext。 5. 接着,需要创建一个DStream对象,用于从Kafka接收数据。可以使用KafkaUtils.createDirectStream()方法来创建DStream对象。 6. 然后,可以对DStream对象进行一系列的转换操作,例如map、filter、reduce等操作,以实现对数据的处理。 7. 最后,需要调用StreamingContext.start()方法来启动StreamingContext,并调用StreamingContext.awaitTermination()方法来等待StreamingContext的终止。 以上就是Spark Streaming整合Kafka详细指南。通过以上步骤,可以实现高效的实时数据处理。 ### 回答2: 随着大数据时代的到来,数据量和处理需求越来越庞大,企业需要通过数据分析和挖掘来对业务进行优化和提升。而Apache Spark是一款分布式大数据处理框架,可优化批处理、交互式查询和流处理的数据工作负载。而Kafka是一款高吞吐量的分布式消息队列系统,可应用于日志收集、流处理和实时数据管道等场景。Spark Streaming和Kafka的共同应用可以实现实时流处理,并可轻松构建实时数据管道。 为了整合Spark Streaming和Kafka,需要进行几个基本步骤: 1.下载安装Kafka并启动Kafka服务。 2.添加Kafka的依赖包到Spark Streaming项目中。通常,引入kafka-clients库就足够了。 3.编写Spark Streaming作业程序,这样就可以从Kafka中拉取数据。 下面是一个详细的Spark Streaming整合Kafka指南: 1.安装Kafka Spark Streaming和Kafka之间的集成是通过Kafka的高级API来实现的,因此需要在本地安装Kafka并让其运行。具体的安装和设置Kafka的方法在官方文档上都有详细说明。在本文中,我们不会涉及这些步骤。 2.添加Kafka依赖包 在Spark Streaming应用程序中引入Kafka依赖包。要在Scala中访问Kafka,需要在代码中添加以下依赖包: ``` // For Kafka libraryDependencies += "org.apache.kafka" %% "kafka" % "0.10.0.0" ``` 3.编写Spark Streaming作业程序 Spark Streaming提供了对输入的高级抽象,可以在时间间隔内将数据流变成DStream。以下是使用Apache Spark Streaming和 Kafka读取数据的Scala示例: ``` import org.apache.kafka.clients.consumer.ConsumerConfig import org.apache.kafka.common.serialization.StringDeserializer import org.apache.spark.SparkConf import org.apache.spark.streaming.kafka010._ import org.apache.spark.streaming.{Seconds, StreamingContext} object KafkaStreaming { def main(args: Array[String]) { val topics = Array("testTopic") val groupId = "testGroup" val kafkaParams = Map[String, Object]( ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "localhost:9092", ConsumerConfig.GROUP_ID_CONFIG -> groupId, ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer], ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer], ConsumerConfig.AUTO_OFFSET_RESET_CONFIG -> "earliest", ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG -> (false: java.lang.Boolean) ) val conf = new SparkConf().setAppName("KafkaStreaming").setMaster("local[2]") val ssc = new StreamingContext(conf, Seconds(5)) val messages = KafkaUtils.createDirectStream[String, String]( ssc, LocationStrategies.PreferConsistent, ConsumerStrategies.Subscribe[String, String](topics, kafkaParams) ) val lines = messages.map(_.value) lines.print() ssc.start() ssc.awaitTermination() } } ``` 该例子会从名为topicName 的Kafka主题上获取消息,并且每隔5秒钟打印一次消息。 4.启动应用程序 在启动应用程序之前,请确保Kafka和Zookeeper正在运行,并且Kafka的主题已被创建。然后使用以下命令启动Spark Streaming作业程序,在本地大力测试: ``` $SPARK_HOME/bin/spark-submit --class com.spark.streaming.KafkaStreaming --master local[2] KafkaStreaming-1.0-SNAPSHOT.jar ``` 总之,通过上面的四个步骤,您将能够将Kafka和Spark Streaming集成起来,创建实时流处理的应用程序。这两个工具的结合非常适合实时数据处理,例如实时指标看板或监控模型。就像大多数技术一样,集成两个工具的正确方法通常需要进行扩展和微调。但是,这个指南是一个基础例子,可以帮助您理解两个工具之间的关系,以及一些基本的集成步骤。 ### 回答3: Spark是目前被广泛应用于分布式计算领域的一种强大的工具,而Kafka则是一个高性能的分布式消息队列。对于需要在分布式系统中处理流式数据的应用场景,将Spark与Kafka整合起来进行处理则是一种非常有效的方式。本文将详细介绍如何使用Spark Streaming整合Kafka进行流式数据处理。 1. 环境准备 首先需要安装好Scala环境、Spark和Kafka。 2. 创建Spark Streaming应用 接下来,需要创建一个Spark Streaming应用。在创建的过程中,需要指定数据流的输入源以及每个批次的处理逻辑。 ```scala import org.apache.spark.streaming.kafka.KafkaUtils import org.apache.spark.streaming.{StreamingContext, Seconds} object KafkaStream { def main(args: Array[String]): Unit = { val conf = new SparkConf().setAppName("kafka-stream") val ssc = new StreamingContext(conf, Seconds(5)) val topicSet = Set("test") val kafkaParams = Map("metadata.broker.list" -> "localhost:9092") val kafkaStream = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder]( ssc, kafkaParams, topicSet ) kafkaStream.map(_._2).flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _).print() ssc.start() ssc.awaitTermination() } } ``` 在上述代码中,我们定义了对`test`主题的数据流进行处理,并使用了`KafkaUtils`工具类对Kafka进行了连接。接着,我们使用了`map`函数将消息内容转换为字符串,并对字符串进行了切分。然后,使用`reduceByKey`函数对字符串中的单词进行了统计。最后,我们调用了`print`函数将统计结果输出到控制台中。 3. 运行Spark Streaming应用 到这里,我们已经完成了对Spark Streaming应用的编写。接下来,需要在终端窗口中运行以下命令启动Spark Streaming应用。 ```shell $ spark-submit --class KafkaStream --master local[2] kafka-stream_2.11-0.1.jar ``` 在启动之前需要将kafka-stream_2.11-0.1.jar替换成你的jar包名。 4. 启动Kafka的消息生产者 在应用启动之后,我们还需要启动一个消息生产者模拟向Kafka发送数据。 ```shell $ kafka-console-producer.sh --broker-list localhost:9092 --topic test ``` 在控制台输入一些数据后,我们可以在Spark Streaming应用的控制台输出中看到统计结果。这表明我们已经成功地使用Spark Streaming整合了Kafka进行流式数据处理。 总结 本文详细介绍了如何使用Spark Streaming整合Kafka实现流式数据处理。在实际生产环境中,还需要考虑数据的安全性、容错性、扩展性等多种因素。因此,需要对代码进行优化,以便更好地满足实际需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我思知我在

原创不易,多多一键三连

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值