kafka和flink的入门到精通 4 生产数据流程

参考023 - 大数据 - Kafka - 生产者 - 生产数据的准备_哔哩哔哩_bilibili

链接:https://pan.baidu.com/s/1QMOJVkRy4nKkjzoDryvQXw
提取码:fcoe

本文接着上一篇kafka和flink的入门到精通 3 组件扩展,kafka-生产者_水w的博客-CSDN博客

目录

4.1 生产数据流程

◼  生产数据的准备

 ➢ KafkaProducer,ProducerRecod

 ◼  采集器

 ◼ 拦截器的实现

 ◼ 元数据请求和更新

◼  分区选择

◼  将数据缓存到采集器中

◼  sender从采集器中获取数据发送到服务器


4.1 生产数据流程

◼  生产数据的准备

 ➢ KafkaProducer,ProducerRecod

这个“KafkaProducer”下面是一些初始化的配置操作, 

  • clientId:;
  • partitioner:是可配置的,默认为DefaultPartitioner;
  • interceptors:拦截器;
  • maxRequestSize:最大请求大小;
  • compressionType:压缩方式;
  • accumulator:采集器;
  • metadata:元数据;
  • sender:发送对象,会随着线程的启动而执行;
  • ioThread;

因此,accumulator就是快递员-----缓冲区,sender就是运输工具。

ProducerRecord的参数:

 ◼  采集器

小人要发送快递,做的第一件事就是 send操作来发送数据,把record做了一系列的拦截interceptors(不是只有一个),做了一些定制化的操作,形成了新的Record。

当把消息拦截之后,那么把消息发给谁,我们不知道。那么这些东西在哪,就在元数据当中。需要从元数据当中得到一些信息,比如集群的信息。然后把Key做序列化,消息继续往下走。

但是Topic主题有多个分区,那么接下来就要问消息要放在哪个分区Partition?因为如果都往一个分区里放会导致过载。-------分区选择

现在Topic和分区Partition我都知道了,那么TopicPartition对象就会被构建出来,构建出来之后,开始做一些检验操作,会判断当前数据的大小有没有超过阈值。

 接下来,重点是一个RecordAccumulator缓冲区,快递就被快递员拿到了。

 这就是一个比较完整的流程了。那么接下来就详细说说。

 ◼ 拦截器的实现

生成一个自定义拦截器“MyProducerInterceptor”,继承“ProducerInterceptor”,在新的拦截器中定义onSend()方法,为了好区分,我们让key和Value不一样。(原本的拦截器中的onSend()方法,key和Value是一样的。)

 将生成的自定义拦截器“MyProducerInterceptor”进行配置,

 运行代码,刷新之后,会发现多了一条数据,

 这就叫拦截器。

 ◼ 元数据请求和更新

 此时,拦截器已经处理完了,

 Matadata不是频繁的从服务器得到数据,它是可以缓存下来的,缓存以后做一些事情。

可以看到,这儿的代码“watiOnMetadata” 等待元数据,那么为什么要等待元数据?因为元数据中包含很多信息和内容,我们需要知道里面有什么。

  • cache:缓存;
  • newTopics
  • cluster:集群;

那么集群中有什么?

可以看到, cluster集群中有:

  • nodes:服务器节点的集合;
  • controller;
  • partitionByTopicPartition:当亲的分区里的分区信息;
  • partitionByTopic:某一个主题topic当中的所有分区;
  • availablePartitionByTopic:可用的分区;
  • partitionByNode:某一个node节点当中的所有分区;

等等,里面东西太多了,这里不一一列举了。

 

剩下的,我们就需要怎么去等待元数据的?

那么接下来,我们就需要知道是怎么去等待元数据的?我们怎么就把元数据得到了呢?

默认情况下是肯定不知道元数据的,因为最开始的时候,第一次发送数据,我们都不知道服务器有哪些分区,那么我们要怎么划分分区?连元数据都没有,我们怎么做分区选择?

所以首先要把元数据得到。

可以看到,requestUpdateForTopic(topic) 就意味着我准备发送请求来更新元数据了。

可以看到代码中,sender唤醒,进行awaitUpdate()来等待更新, 什么时候更新完毕了,程序才会继续走下去。所以现在这个awaitUpdate()就会阻塞等待,sender是一个线程,sender.wakeup()就会执行,那么也就是说当前的生产线程(主线程)就会暂停,seder发送线程就会开始。

比如,我现在想发到广州一个快递,问快递员说能不能发到广州,快递员说你等一等我问一下。他得知道它们公司能不能发到广州。问了公司说可以发,快递员才会给你说可以,我就得到了这个信息。

  • sender会向集群的服务器Kafka Servers发送一个请求MatadataRequest(METADATA),传了一个ApiKeys叫METADATA;
  • doSend()向服务器发送request请求;
  • 请求发送到了Kafka Servers之后,放到了requestChnnel中,然后再从requestChnnel中把request请求取出来,KafkaApis会得到请求对象。
  • KafkaApis中, 到了handle这一步,就会取到请求对象了,拿到了ApiKey
  • 得到以后,到了handleTopicMetaRequest来处理这个请求,拿到了请求信息,拿到之后就可以把所有的Topic和分区都可以得到了。得到以后,发给客户端。
  • 此时,sender得到响应对象response,对响应处理,其中如果是metadataResonse,就会有一个处理成功的响应handleSuccessfulResonse。就完成了设定,进行更新update,当前就有数据了。

 

◼  分区选择

此时的“watiOnMetadata” 等待元数据已经完成,

我们继续往下,这儿的序列化操作直接按步骤进行就可以,

我们继续往下,接下来,就讲讲Kafka的数据到底应该放在哪个分区里?

 都往一个分区里放,会导致负载太大,出现问题,所以我们要均匀的。

可以从代码看出,

分区选择的判断:

  • 如果指定分区号,则直接使用;
  • 如果没有分区号,那么采用分区器决定分区;
    • 如果没有指定key,会从所有分区中随机选一个,进行存放;
    • 如果没有key,会将key进行散列后,再和分区数量取余;

 那么我们接着往下走,就到了数据大小检测。

◼  将数据缓存到采集器中

 那么我们接着往下走,就到了数据大小检测, 因为数据不能太大, 太大的话承载不了。

可以从这部分的代码中看到,数据检测的是两个大小:

  • maxRequestSize:最大请求字节数,大小为1MB;
  • totalRequestSize:总共的内存大小,大小为32MB; 

可以从代码中找到, maxRequestSize是来自一个“MAX_REQUEST_SIZE_CONFIG”的,大小为1MB;

totalRequestSize是来自一个“BIFFER_MEMORY_CONFIG”的,大小为32MB;

 如果超过了阈值就会有问题。

 

那么我们接着往下,数据大小检测完成之后,就到了这个位置 ,

可以看到,这里的“accumulator.append()”的accumulator,就是我们的快递员了。那么现在相当于我们已经把快递给到了快递员。

把快递给到了快递员,快递员一定会要么?不一定,因为有些东西还没填全,快递员是不会收我们的快递的。

从accumulator.append()的代码中,

我们可以看到有一个Deque双端队列(两边都可以放,都可以取),

如果这个时候,从Deque双端队列中取出一条数据发送出去,此时突然这条数据发送失败了怎么办?就得重新发送,否则数据就会没了。

Deque双端队列的特点就可以保证如果发送失败了,那么我还可以再把数据放回到头部位置。

Deque可不是只有一个,是根据分区信息来得到Deque的。Deque里面包含着ProducerBatch,这个就类似于箱子的感觉,把快递放到箱子里,只不过这个箱子外面包裹着一个更大的箱子。

所以现在就等同于我们拿到了一个装快递的箱子,现在就要把快递(消息)放到箱子里面去,

从代码中,可以找到这个“tryAppend()”,这个就是尝试把把快递(消息)放到箱子里面去,try是指不一定能放到箱子里。

 因为从大箱子里面取出最后一个箱子,取出来之后,想把key和vlaue放进去,发现箱子没那么大,放不下去。所以就会有if语句来判断两种情况,如果有空间则就可以放进去,没有空间就放不进去。

空间足够的话,就可以放进去了。放进去以后,发送成功, 就会等待响应。

那么此时的图如下所示:

把消息加到accumulator进去之后, 继续往下,可以看到,

 有一个对箱子的判断,如果箱子满了或者有新的被创建出来,那么意味着这个时候,要唤醒sender。也就是说我们的sender要准备发送数据了。

也即是说我们已经把快递放到了箱子里,这个时候要问sender有没有时间,有时间的话来一趟把箱子送走,所以此时sender被唤醒。

◼  sender从采集器中获取数据发送到服务器

 从sender的代码中可以看到,

现在这个地方,就准备把数据从缓冲区中取出来发送。从元数据中取到集群cluster的相关信息,把cluster中可用的节点准备好。

那么什么是可用的节点?

现在我们是要把消息归了类发给上海和深圳,可是上海和深圳有好几个区,我们得把发往上海和深圳不同的区的快递放一块儿。

我们在缓冲区是根据分区选择来将消息数据分类,到了这个车(sender)上就不一样了,会按照节点来分类,所以分类方式就会发送变化。

拿到leader之后,就知道给哪个分区发了。

这样的好处就是把发往多个分区的消息合成一个,请求就会好很多。

现在我们已经知道了准备给谁发,知道以后,往下,判断当前的元数据是不是需要更新。

继续往下,现在相当于把前面的小箱子拿到了sender里面,拿到以后,形成一个Map。

 构建了另外的请求:

  • ProduceRequest(PRODUCE)
  • ProduceRequestData(ACKS,DATA)
  • HandleProduceResponse

到了这一步,sender把请求就准备好了,就可以把请求发给服务器,服务器处理了操作之后,会发送响应response给sender。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

水w

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值