参考023 - 大数据 - Kafka - 生产者 - 生产数据的准备_哔哩哔哩_bilibili
链接:https://pan.baidu.com/s/1QMOJVkRy4nKkjzoDryvQXw
提取码:fcoe
本文接着上一篇kafka和flink的入门到精通 3 组件扩展,kafka-生产者_水w的博客
目录
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。