一、Kafka对接Flume
既然我们学习了Kafka,那么我们肯定是要用Kafka的,在企业中最常用的流程处理方式如下👇
日志、埋点数据 👉 flume 👉 kafka 👉 flume(根据情景增删该流程) 👉 HDFS
虽然Kafka和Flume对接很简单,但是我们还是记录一下,以免哪天忘了就尴尬了你说是不是呀QAQ,如果小伙伴们对Flume的基本操作有些生疏的,可以去看一下我的另一篇博客 Flume入门解析(一),加强一下对Flume的印象!
好了,废话不多说,我们开始吧!
大致流程为:
监控本地文件 ---> flume ---> kafka ---> flume ---> HDFS
Kafka对接Flume时,有两种方式
方式1:常规方式:File Source -> Memory Channel-> Kafka Sink
方式2:其他方式:File Source -> Kafka Channel
方式1👇
第一步:配置Flume
该Flume配置是监控本地文件,传输到Kafka
vim exec2kafka.conf
# define
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# source
a1.sources.r1.type = exec
a1.sources.r1.command = tail -F -c +0 /opt/datas/web.log
a1.sources.r1.shell = /bin/bash -c
# sink
a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
a1.sinks.k1.kafka.bootstrap.servers = broker地址:9092...
a1.sinks.k1.kafka.topic = 主题名
a1.sinks.k1.kafka.flumeBatchSize = 20
a1.sinks.k1.kafka.producer.acks = 1
a1.sinks.k1.kafka.producer.linger.ms = 1
# channel
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100
# bind
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
该Flume配置是消费Kafka中的数据,传输到HDFS
vim kafka2hdfs.conf
# define
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# source
a1.sources.r1.type = org.apache.flume.source.kafka.KafkaSource
a1.sources.r1.channels = c1
a1.sources.r1.batchSize = 5000
a1.sources.r1.batchDurationMillis = 2000
a1.sources.r1.kafka.bootstrap.servers = broker地址:9092...
a1.sources.r1.kafka.topics = 主题名1、主题名2...
a1.sources.r1.kafka.consumer.group.id = 组id
# sink
a1.sinks.k1.type = hdfs
a1.sinks.k1.channel = c1
a1.sinks.k1.hdfs.path = /flume/events/%y-%m-%d/%H%M/%S
a1.sinks.k1.hdfs.filePrefix = events-
a1.sinks.k1.hdfs.round = true
a1.sinks.k1.hdfs.roundValue = 10
a1.sinks.k1.hdfs.roundUnit = minute
# channel
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100
# bind
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
第二步:开启Flume
bin/flume-ng agent -c conf/ -n a1 -f conf/exec2kafka.conf
bin/flume-ng agent -c conf/ -n a1 -f conf/kafka2hdfs.conf
第三步:开启Kafka
bin/kafka-server-start.sh -daemon ./config/server.properties
第四步:向本地文件写入数据然后在HDFS查看数据
可以看到数据已经传输到了HDFS上,大功告成!
方式2👇 (Kafka Channel中存储在Kafka的磁盘中,比内存更可靠)
第一步:配置Flume
该Flume配置是监控本地文件,传输到Kafka
vim exec2kafka.conf
# define
a1.sources = r1
a1.channels = c1
# source
a1.sources.r1.type = exec
a1.sources.r1.command = tail -F -c +0 /opt/datas/web.log
a1.sources.r1.shell = /bin/bash -c
# configure channel
a1.channels.c1.type = org.apache.flume.channel.kafka.KafkaChannel
a1.channels.c1.kafka.bootstrap.servers = broker地址:9092...
a1.channels.c1.kafka.topic = 主题名
a1.channels.c1.parseAsFlumeEvent = false
a1.channels.c1.kafka.consumer.group.id = 消费者组id
# bind
a1.sources.r1.channels = c1
该Flume配置是消费Kafka中的数据,传输到HDFS
vim kafka2hdfs.conf
# define
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# source
a1.sources.r1.type = org.apache.flume.source.kafka.KafkaSource
a1.sources.r1.channels = c1
a1.sources.r1.batchSize = 5000
a1.sources.r1.batchDurationMillis = 2000
a1.sources.r1.kafka.bootstrap.servers = broker地址:9092...
a1.sources.r1.kafka.topics = 主题名1、主题名2...
a1.sources.r1.kafka.consumer.group.id = 消费者组id
# sink
a1.sinks.k1.type = hdfs
a1.sinks.k1.channel = c1
a1.sinks.k1.hdfs.path = /flume/events/%y-%m-%d/%H%M/%S
a1.sinks.k1.hdfs.filePrefix = events-
a1.sinks.k1.hdfs.round = true
a1.sinks.k1.hdfs.roundValue = 10
a1.sinks.k1.hdfs.roundUnit = minute
# channel
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100
# bind
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
第二步:开启Flume
bin/flume-ng agent -c conf/ -n a1 -f conf/exec2kafka.conf
bin/flume-ng agent -c conf/ -n a1 -f conf/kafka2hdfs.conf
第三步:开启Kafka
bin/kafka-server-start.sh -daemon ./config/server.properties
第四步:向本地文件写入数据然后在HDFS查看数据
可以看到数据已经传输到了HDFS上,大功告成!
二、Kafka自定义分区器
我们知道,Topic是有分区的,所以Producer在发送数据的数据,它是如何知道该把数据发送到哪里去呢?
我们可以在 doSend
方法中看到一个 partition的方法,根据这个方法得出了分区
我们看一看这个方法内部是如何实现的呢?如下图👇
我们看到,第一步它从ProducerRecord 中获取分区,如果你还有影响,可以记得 ProducerRecord 的构造方法正好有一个可以传入分区,如下图👇
所以我们可以知道,在构建 ProducerRecord 对象的时候可以指定分区,这样就可以把数据发送到Topic的一个分区上,从而不会出现乱序的情况,但这不是我们的重点,我们再来看一下下面的代码我们在构建 ProducerRecord 的时候如果没有传入分区数,则下面的三元表达式肯定不成立,返回的是第二个参数,那第二个参数里面是怎样的呢?如下图👇
当我们点击进来之后发现是一个接口,这个接口就是我们今天的重点,待会儿再聊,我们先看一下它的实现类,如下图👇
这个 DefaultPartitioner
就是我们默认的分区器了,再没有指定分区的情况下,都是走的这个分区器如下就是它的分区方法,一共两个逻辑,有key和没有key的两种情况,有兴趣的可以研究一下!!!
好了,上面废话了那么多,现在开始进入正题吧!
如果我们觉得它的默认分区器不满足我们的要求,我们也可以自定义分区器的,我们就模仿官方的来写一个试试吧!!!
首先我们知道,自定义一个分区器肯定要继承 Paritioner 接口的
然后在partition内自定义分区逻辑,如下图👇
然后编写 Producer 逻辑,向Properties中添加我们的分区器,覆盖默认的
运行之后得到结果如下图,分区效果实现了!!!
三、Kafka拦截器
如果我们想对Producer生产的数据进行个性化处理,那这时候该怎么办呢?
没错,就是用拦截器,如下图👇
简单观察源码我们可以发现,有一个ProducerInterceptor
的接口,字面意思都可以猜出Producer端的拦截器,所以,我们创建一个类来继承它实现它的对应方法看看效果吧!!!
需求:对Producer生产的数据加上时间戳,并且统计生产的数据成功数量和失败数量
因为有两个需求,为了简单实现解耦,所以创建两个类,实现简单的拦截器链效果
第一个
拦截器代码如下👇,在onSend
方法中实现我们的自定义逻辑
第二个
拦截器代码如下👇,在onAcknowledgement
方法中根据返回的元数据是否为空判断数据发送成功与否
metadata 和 exception:metadata为空,则数据发送失败,exception为空,则数据发送成功
简单创建生产者
我们在Properties中添加拦截器即可,添加的是拦截器全限定类名哦!
简单创建消费者
效果如下图👇