1.0 Kafka 概述
1.1 kafka 定义
Kafka 是一个分布式的基于发布/订阅模式的消息队列(Message Queue),主要应用于大数据实时处理领域。
1.2 消息队列
1.2.1 传统消息队列的应用场景
使用消息队列的好处
- 解耦(类似Spring的IOC)
- 允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
- 可恢复性
- 系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
- 缓冲
- 有助于控制和优化数据流经过系统的速度, 解决生产消息和消费消息的处理速度不一致的情况。
- 灵活性 & 峰值处理能力(削峰)
- 在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
- 异步通信
- 很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
1.2.2 消费模式
消息队列的两种模式
-
点对点模式:
一对一,消费者主动拉取数据,消息收到后消息清除
消息生产者生产消息发送到Queue中,然后消息消费者从Queue中取出并且消费消息。
消息被消费以后, queue 中不再有存储,所以消息消费者不可能消费到已经被消费的消息。
Queue 支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费。
-
发布/订阅模式:
一对多,消费者消费数据之后不会清除消息,但是保存数据有时效。
消息生产者(发布)将消息发布到 topic 中,同时有多个消息消费者(订阅)消费该消息。
和点对点方式不同,发布到 topic 的消息会被所有订阅者消费。
1.3 基础架构
-
Producer : 消息生产者,就是向 Kafka ;
-
Consumer : 消息消费者,向 Kafka broker 取消息的客户端;
-
Consumer Group (CG): 消费者组,由多个 consumer 组成。 消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费;消费者组之间互不影响。 所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
一个分区只能被一个消费者组里面的某一个消费者消费
-
Broker :经纪人 一台 Kafka 服务器就是一个 broker。一个集群由多个 broker 组成。一个 broker可以容纳多个 topic。
-
Topic : 话题,可以理解为一个队列, 生产者和消费者面向的都是一个 topic;
-
Partition: 为了实现扩展性,一个非常大的 topic 可以分布到多个 broker(即服务器)上,一个 topic 可以分为多个 partition,每个 partition 是一个有序的队列;
-
Replica: 副本(Replication),为保证集群中的某个节点发生故障时, 该节点上的 partition 数据不丢失,且 Kafka仍然能够继续工作, Kafka 提供了副本机制,一个 topic 的每个分区都有若干个副本,一个 leader 和若干个 follower。
-
Leader: 每个分区多个副本的“主”,生产者发送数据的对象,以及消费者消费数据的对象都是 leader。
-
Follower: 每个分区多个副本中的“从”,实时从 leader 中同步数据,保持和 leader 数据的同步。 leader 发生故障时,某个 Follower 会成为新的 leader。(类似于备份)
replica 英 [ˈreplɪkə] 美 [ˈreplɪkə] n.复制品;仿制品
topic 英 [ˈtɒpɪk] 美 [ˈtɑːpɪk] n.话题;题目;标题
2.0 Kafka 快速入门
2.1 Kafka 安装&启动&关闭
-
解压
-
修改配置文件
cd /usr/kafka/config
修改 server.properties
delete.topic.enable=true
#kafka 运行日志存放的路径
log.dirs=/usr/kafka/kafka/logs
#配置连接 Zookeeper 集群地址
zookeeper.connect=master:2181,slave1:2181,slave2:2181
#broker 的全局唯一编号,不能重复
broker.id=0
#删除 topic 功能使能
delete.topic.enable=true
#处理网络请求的线程数量
num.network.threads=3
#用来处理磁盘 IO 的现成数量
num.io.threads=8
#发送套接字的缓冲区大小
socket.send.buffer.bytes=102400
#接收套接字的缓冲区大小
socket.receive.buffer.bytes=102400
#请求套接字的缓冲区大小
socket.request.max.bytes=104857600
#kafka 运行日志存放的路径
log.dirs=/usr/kafka/kafka/logs
#topic 在当前 broker 上的分区个数
num.partitions=1
#用来恢复和清理 data 下数据的线程数量
num.recovery.threads.per.data.dir=1
#segment 文件保留的最长时间,超时将被删除
log.retention.hours=168
#配置连接 Zookeeper 集群地址
zookeeper.connect=master:2181,slave1:2181,slave2:2181
-
配置环境变量
#KAFKA_HOME export KAFKA_HOME=/usr/kafka/kafka export PATH=$PATH:$KAFKA_HOME/bin
-
启动 zookeeper
bin/zkServer.sh start
-
启动 kafka
bin/kafka-serve-start.sh -daemon config/server.properties
-
写一个群启脚本
#!/bin/bash case $! in "start"){ for i in master slave1 slave2 do echo "========== $i ==========" ssh $i '/usr/kafka/kafka/bin/kafka-server-start.sh -daemon /usr/kafka/kafka/config/server.properties' done };; "stop"){ for i in master slave1 slave2 do echo "========== $i ==========" ssh $i '/usr/kafka/kafka/bin/kafka-server-stop.sh' done };; esac
2.2 Kafka命令行操作
-
查看当前服务器中的所有 topic
bin/kafka-topics.sh --list --zookeeper master:2181
-
创建topic(主题)
bin/kafka-topics.sh --zookeeper
hadoop102:2181 --create --replication-factor 3 --partitions 1 --
topic first
创建名为first的主题。保存在logs 里面 名叫" first-0 " 0 是分区的 id
选项说明:
–topic 定义 topic 名
–replication-factor 定义副本数
–partitions 定义分区数
副本数 > 分区数
-
删除topic
bin/kafka-topics.sh --delete --zookeeper master:2181 --topic first
-
显示信息
bin/kafka-topics.sh --describe --topic first --zookeeper master:2181
-
发送消息
bin/kafka-console-producer.sh --topic first --broker-list master:9092
-
消费者:消费信息
bin/kafka-console-consumer.sh --topic first --zookeeper master:2181
控制台:
bin/kafka-console-consumer.sh --topic first --zookeeper master:2181 --from-beginning
–from-beginning:会把主题中以往所有的数据都读取出来。
前两个过时了,现在使用bootstrap-server
bin/kafka-console-producer.sh --topic first --boostarp-server master:9092
2.2.1 数据日志分离
- 删除日志文件logs:
rm -rf logs
- 进入zk删除:``bin/zk.Cli.sh
ls /
rmr /cluster` 或者把 zkdata 中的 version 删掉
3.0 案例
3.1 Flume读取日志数据并写入到Kafka,ConsoleConsumer进行实时消费
-
编写用于实时产生日志的shell文件
-
创建临时存放日志文件的目录
mkdir -p /tmp/flumetokafka/logs mkdir -p /tmp/flumetokafka/testdata
-
接下来编写shell文件
vim output.sh #写入 for((i=5612; i<6000; i++)); do touch $PWD/testdata/20170913-jangzhangz-$i.log echo 'When we will see you again. Put a little sunshine in your life.----'+$i >> $PWD/testdata/20170913-jangzhangz-$i.log mv $PWD/testdata/20170913-jangzhangz-$i.log $PWD/logs/ done
tmp/flumetokafka/logs/就是flume进行监听日志文件的数据目录
-
-
为flume构建agent
首先进入flume所在目录下的conf目录,编写构建agent的配置文件(flume2kafka.properties):
Flume2KafkaAgent.sources=mysource Flume2KafkaAgent.channels=mychannel Flume2KafkaAgent.sinks=mysink Flume2KafkaAgent.sources.mysource.type=spooldir Flume2KafkaAgent.sources.mysource.channels=mychannel Flume2KafkaAgent.sources.mysource.spoolDir=/tmp/flumetokafka/logs Flume2KafkaAgent.sinks.mysink.channel=mychannel Flume2KafkaAgent.sinks.mysink.type=org.apache.flume.sink.kafka.KafkaSink Flume2KafkaAgent.sinks.mysink.kafka.bootstrap.servers=master:9092,slave1:9092,slave2:9092 Flume2KafkaAgent.sinks.mysink.kafka.topic=flume-data Flume2KafkaAgent.sinks.mysink.kafka.batchSize=20 Flume2KafkaAgent.sinks.mysink.kafka.producer.requiredAcks=1 Flume2KafkaAgent.channels.mychannel.type=memory Flume2KafkaAgent.channels.mychannel.capacity=30000 Flume2KafkaAgent.channels.mychannel.transactionCapacity=100
-
求证成功
-
启动 flume agent
bin/flume-ng agent -c conf -f conf/flume2kafka.properties -n Flume2KafkaAgent -Dflume.root.logger=INFO,console
-
启动 kafka消费者
bin/kafka-console-consumer.sh --zookeeper master:2181 --topic flume-data --from-beginning
bin/kafka-console-consumer.sh --zookeeper master:2181 --topic flume-data --from-beginning
-
-
生产日志
在tmp/flumetokafka/ 下执行
./output.sh
-
查看 terminal显示的消费记录情况
正常执行后,结果会出现类似情况:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fGS60dBF-1640089765175)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211208155301387.png)]
3.2 使用Flume监控本机的端口,将数据发送给Kafka
使用Flume监控本机的6666端口,将数据发送给Kafka,并启动Kafka的消费者,将数据打印到控制台,其中Kafka的topic自定义
-
首先启动 zk 和 kafka
-
然后 创建一个主题
bin/kafka-topics.sh --create --zookeeper hadoop102:2181 --replication-factor 1 --partitions 1 --topic mytopic
-
启动一个消费者
bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic mytopic
-
启动一个生产者(测试)
bin/kafka-console-producer.sh --broker-list hadoop102:9092 --topic mytopic
-
然后启动flume监控
bin/flume-ng agent --conf conf --conf-file job/flume-kafka.conf --name a1 -Dflume.root.logger=INFO,console
-
配置文件
#flume-kafka.conf a1.channels = c1 a1.sources = r1 a1.sinks = k1 # Describe/configure the source a1.sources.r1.type = netcat a1.sources.r1.bind = localhost a1.sources.r1.port = 6666 # Describe the sink a1.sinks.k1.type = logger a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink #主题mytopic和创建的topic一样 a1.sinks.k1.kafka.topic = mytopic a1.sinks.k1.kafka.bootstrap.servers = master:9092 # Use a channel which buffers events in memory a1.channels.c1.type = memory a1.channels.c1.capacity = 1000 a1.channels.c1.transactionCapacity = 100 # Bind the source and sink to the channel a1.sources.r1.channels = c1 a1.sinks.k1.channel = c1
-
最后使用nc来输入数据 (kafka消费者会读取数据)
nc localhost 6666
kafka消费者 打印出数据即为成功
3.3 对接案例:Flume实时读取数据导入到Kafka的mytopic主题中
文件配置:
agent1.sources = logsrc
agent1.channels = memcnl
agent1.sinks = kafkasink
#source p
agent1.sources.logsrc.type = exec
agent1.sources.logsrc.command = tail -F /tmp/root/hive.log
agent1.sources.logsrc.shell = /bin/sh -c
agent1.sources.logsrc.batchSize = 50
agent1.sources.logsrc.channels = memcnl
# Each sink's type must be defined
agent1.sinks.kafkasink.type = org.apache.flume.sink.kafka.KafkaSink
agent1.sinks.kafkasink.brokerList=master:9092, slave1:9092,slave2:9092
agent1.sinks.kafkasink.topic=mytopic
agent1.sinks.kafkasink.requiredAcks = 1
agent1.sinks.kafkasink.batchSize = 20
agent1.sinks.kafkasink.channel = memcnl
# Each channel's type is defined.
agent1.channels.memcnl.type = memory
agent1.channels.memcnl.capacity = 1000
-
启动Flume节点
bin/flume-ng agent -c conf/ -n agent -f job/flume3kafka.conf -Dflume.mointoring.type=http -Dflume.monitoring.port=10100
3.4 使用flume监控对应的目录数据,将实时日志数据采集到kafka中
-
启动Kafka , 创建好Flume监控数据导入的 topic logtopic
-
将日志采集接口部署到linux节点上,启动日志采集接口
java -jar 日志采集接口文件
-
启动 Flume 监控对应的日志目录,将“用户登录日志”采集到kafka topic 中
my.properites
#设置source名称 a.sources = r1 #设置channel的名称 a.channels = c1 #设置sink的名称 a.sinks = k1 # For each one of the sources, the type is defined #设置source类型为TAILDIR,监控目录下的文件 #Taildir Source可实时监控目录一批文件,并记录每个文件最新消费位置,agent进程重启后不会有重复消费的问题 a.sources.r1.type = TAILDIR #文件的组,可以定义多种 a.sources.r1.filegroups = f1 f2 #第一组监控的是test1文件夹中的什么文件:.log文件 a.sources.r1.filegroups.f1 = /software/logs/common/.*log #第二组监控的是test2文件夹中的什么文件:以.txt结尾的文件 #a.sources.r1.filegroups.f2 = /software/logs/system/.*txt # The channel can be defined as follows. #设置source的channel名称 a.sources.r1.channels = c1 a.sources.r1.max-line-length = 1000000 #a.sources.r1.eventSize = 512000000 # Each channel's type is defined. #设置channel的类型 a.channels.c1.type = memory # Other config values specific to each type of channel(sink or source) # can be defined as well # In this case, it specifies the capacity of the memory channel #设置channel道中最大可以存储的event数量 a.channels.c1.capacity = 1000 #每次最大从source获取或者发送到sink中的数据量 a.channels.c1.transcationCapacity=100 # Each sink's type must be defined #设置Kafka接收器 a.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink #设置Kafka的broker地址和端口号 a.sinks.k1.brokerList=master1:9092,slave1:9092,slave2:9092 #设置Kafka的Topic a.sinks.k1.topic=logtopic #设置序列化方式 a.sinks.k1.serializer.class=kafka.serializer.StringEncoder #Specify the channel the sink should use #设置sink的channel名称 a.sinks.k1.channel = c1
-
生成数据访问日志采集接口,查看topic数据
flume-ng agent
-n a
-f /software/apache-flume-1.9.0-bin/conf/flume-conf-file-kafka.properties
-Dflume.root.logger=INFO,console
kafka启动:
bin/kafka-console-consumer.sh --bootstrap-server master:9092,slave1:9092,slave2:9092
--topic logtopic
3.5 Flink消费Kafka
package com.atguigu.bigdata.spark.sql.Spark和Flink代码
import java.util.Properties
case class Test(aa: Double)
object Flinkstream {
val REDISIP = extractjob.RedisIP //redisip
val ZKUrl = extractjob.XKIP + ":2181" //zk url+ip
val KAFKAURL = extractjob.KafkaIP + ":9092" //kafka ip
val TOPIC = "topictest"
val GROUPID = "Test1"
def main(args: Array[String]): Unit = {
var env = StreamExecutionEnvironment.getExecutionEnvironment
if ((args.length > 0 && args(0).equals("local")) || args.length == 0) {
env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI()
}
val properties = new Properties()
properties.setProperty("zookeeper.connect", ZKUrl)
properties.setProperty("bootstrap.servers", KAFKAURL)
properties.setProperty("group.id", GROUPID)
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)
env.setParallelism(6)
env.enableCheckpointing(5000)
env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE)
val kafkaSource = new FlinkKafkaConsumer(TOPIC, new SimpleStringSchema, properties)
val datastream: DataStream[Test] = env.addSource(kafkaSource).filter(x => {
"正确值".equals(x.split(":")(0)) //过滤数据
}).map(x => {
//获取到想要的数据并转成 Test类,方便凑走
Test(x.split(":")(1).split(",")(3).toDouble)
})
val config: FlinkJedisPoolConfig = new FlinkJedisPoolConfig.Builder().setHost(REDISIP).build()
//数据进行累加计算,在插入redis
datastream.map(x => {
("aaTest", x.aa)
}).keyBy(_._1).sum(1).map(x => (x._1, x._2.toString))
.addSink(new RedisSink[(String, String)](config, new MyRedisMapper) {})
}
}
class MyRedisMapper extends RedisMapper[(String, String)] {
// 方法用于指定对接收来的数据进行什么操作
override def getCommandDescription: RedisCommandDescription = {
new RedisCommandDescription(RedisCommand.SET)
}
// 于指定接收到的数据中哪部分作为key
override def getKeyFromData(data: (String, String)): String = {
data._1
}
// 方法用于指定接收到的数据中哪部分作为value
override def getValueFromData(data: (String, String)): String = {
data._2
}
}
new RedisSink[(String, String)](config, new MyRedisMapper) {})
}
}
class MyRedisMapper extends RedisMapper[(String, String)] {
// 方法用于指定对接收来的数据进行什么操作
override def getCommandDescription: RedisCommandDescription = {
new RedisCommandDescription(RedisCommand.SET)
}
// 于指定接收到的数据中哪部分作为key
override def getKeyFromData(data: (String, String)): String = {
data._1
}
// 方法用于指定接收到的数据中哪部分作为value
override def getValueFromData(data: (String, String)): String = {
data._2
}
}