一、Kafka基础
1、什么是消息系统?常见的消息系统
复习:
(1)消息:字符串、对象(序列化)
(2)消息类型:
- queue:队列,点对点
- topic:主题,群发----Kafka只支持
(3)同步消息系统:等待对方的回答,例如:ATM机
异步消息系统:不需要等待对方的回答,例如:微信
Kafka都支持
2、Kafka的体系架构、术语(概念)
producer生产者、consumer消费者、consumer group 消费者组。
Topic主题:分区Partition、冗余replicas、段Segment。Topic有分区组成、分区化为多个段。
3、部署Kafka的环境
tar -zxvf kafka_2.11-2.4.0.tgz -C ~/training/
核心配置文件:config/server.properties
注意:一个server.properties文件,对应一个broker
注意:在启动kafka的时候,默认启动内存为1G,修改kafka-server-start.sh脚本,修改启动内存为256M。
详细配置文件的说明请看最后。
(1)单机单broker
broker.id=0
表示broker0日志的路径
log.dirs=/root/training/kafka_2.11-2.4.0/logs/broker0
zookeeper.connect=bigdata111:2181
启动
zkServer.sh start
bin/kafka-server-start.sh config/server.properties &
(2)单机多broker
复制上面的单机单broker的配置文件
cp config/server.properties config/server1.properties
进行一下修改
broker.id=1
port=9093
log.dirs=/root/training/kafka_2.11-2.4.0/logs/broker1
zookeeper地址是一样的,所以不用修改
启动
bin/kafka-server-start.sh config/server1.properties &
(3)多机多broker,和单机多broker一样,只不过改一下broker.id,必须保证每个broker.id都不一样。
4、使用Kafka的命令行工具
(1)创建一个topic
bin/kafka-topics.sh --create \
--zookeeper bigdata111:2181 \
--replication-factor 2 \
--partitions 3 \
--topic mydemotopic1
分区的冗余度:一般跟broker一致,不超过3
bin/kafka-topics.sh --zookeeper bigdata111:2181 --list 列出所有的topic
bin/kafka-topics.sh --zookeeper bigdata111:2181 --describe --topic mydemotopic1
启动producer
bin/kafka-console-producer.sh --broker-list bigdata111:9092 --topic mydemotopic1
启动consumer消息消费
bin/kafka-console-consumer.sh --bootstrap-server bigdata111:9092 --topic mydemotopic1
从开始位置消费
bin/kafka-console-consumer.sh --bootstrap-server bigdata111:9092 --from-beginning --topic mydemotopic1
当消费者已经消费过信息,消费者就会把offset上交到zookeeper,记录到日志,下次消费时,接着这个位置进行消费。
显示key消费
bin/kafka-console-consumer.sh --bootstrap-server bigdata111:9092 --property print.key=true --topic mydemotopic1
(2)彻底删除topic
1、bin/kafka-topics.sh --delete
2、删除ZooKeeper中的topic对应的信息
3、删除broker中对应的日志
二、Kafka的应用程序开发:Java、Scala
在这里只简要说明Java如何开发。
添加依赖:
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.4.0</version>
</dependency>
1、使用Java开发消息生产者Producer、消息消费者Consumer:消息----String
创建ProducerDemo 类
public class ProducerDemo {
public static void main(String[] args) {
// 生成配置文件
Properties pro = new Properties();
// broker地址
pro.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.92.111:9092,192.168.92.111:9093");
/* 生产者需要server端在接收到消息后,进行反馈确认的尺度,主要用于消息的可靠性传输;
* acks=0表示生产者不需要来自server的确认;
* acks=1表示server端将消息保存后即可发送ack,而不必等到其他follower角色的都收到了该消息;
* acks=all(or acks=-1)意味着server端将等待所有的副本都被接收后才发送确认。
pro.put("acks", "all");
pro.put("retries", 0); //生产者发送失败后,重试的次数
//当多条消息发送到同一个partition时,该值控制生产者批量发送消息的大小
//批量发送可以减少生产者到服务端的请求数,有助于提高客户端和服务端的性能。
pro.put("batch.size", 16384);
//默认情况下缓冲区的消息会被立即发送到服务端,即使缓冲区的空间并没有被用完。
//可以将该值设置为大于0的值,这样发送者将等待一段时间后,再向服务端发送请求,以实现每次请求可以尽可能多的发送批量消息。
pro.put("linger.ms", 1);
//生产者缓冲区的大小,保存的是还未来得及发送到server端的消息,如果生产者的发送速度大于消息被提交到server端的速度,该缓冲区将被耗尽。
pro.put("buffer.memory", 33554432);
*/
// 指定消息的序列化 ----字符串
pro.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
pro.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
// 创建producer对象
Producer<String,String> producer = new KafkaProducer<String, String>(pro);
for (int i = 0; i < 10; i++) {
producer.send(new ProducerRecord<String, String>("mydemotopic1","key"+i ,"value:"+i));
}
producer.close();
}
}
创建ConsumerDemo类
public class ConsumerDemo {
public static void main(String[] args) {
// 生成配置文件
Properties pro = new Properties();
// broker 地址
pro.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.92.111:9092,192.168.92.111:9093");
//指定消费者组
pro.put(ConsumerConfig.GROUP_ID_CONFIG, "mygroup1");
/*
* kafka使用消费者分组的概念来允许多个消费者共同消费和处理同一个topic中的消息。
* 分组中消费者成员是动态维护的,如果一个消费者处理失败了,那么之前分配给它的partition将被重新分配给分组中其他消费者;
* 同样,如果分组中加入了新的消费者,也将触发整个partition的重新分配,每个消费者将尽可能的分配到相同数目的partition,以达到新的均衡状态;
pro.put("group.id", "mygroup");
//用于配置是否自动的提交消费进度;
pro.put("enable.auto.commit", "true");
//用于配置自动提交消费进度的时间
pro.put("auto.commit.interval.ms", "1000");
*/
//指定反序列化的方式
pro.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");
pro.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");
// 创建对象
Consumer<String,String> consumer = new KafkaConsumer<String, String>(pro);
// 监听topic主题
consumer.subscribe(Arrays.asList("mydemotopic1"));
while (true)
{
// 拉取信息
ConsumerRecords<String, String> polls = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> poll : polls) {
System.out.println("key:"+poll.key()+"\t"+"valus"+poll.value());
}
}
}
}
2、发送一个自定义的消息:对象(序列化)
添加依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
在这里用student进行举例
创建student类
public class Student {
private int stuID;
private String stuName;
public Student() {
}
public int getStuID() {
return stuID;
}
public void setStuID(int stuID) {
this.stuID = stuID;
}
public String getStuName() {
return stuName;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
@Override
public String toString() {
return "Student [stuID=" + stuID + ", stuName=" + stuName + "]";
}
}
对于生产者,要对提交的数据进行序列化,故创建StudentJSONSerializer类
public class StudentJSONSerializer implements Serializer<Student> {
@Override
public byte[] serialize(String s, Student student) {
return JSON.toJSONBytes(student);
}
}
创建生产者ProducerDemo 类
public class ProducerDemo {
public static void main(String[] args) {
// 生成配置文件
Properties pro = new Properties();
// broker地址
pro.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.92.111:9092,192.168.92.111:9093");
// 指定消息的序列化 ----字符串
pro.setProperty(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
pro.setProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"StudentJSONSerializer");
// 创建producer对象
Producer<String,Student> producer = new KafkaProducer<String, Student>(pro);
for (int i = 0; i < 10; i++) {
Student s = new Student();
s.setStuID(i);
s.setStuName("wu"+i);
producer.send(new ProducerRecord<String, Student>("mydemotopic1","key"+i ,s));
}
producer.close();
}
}
对于消费者,要对接收的数据进行反序列化,故创建StudentJSONDeSerializer
public class StudentJSONDeSerializer implements Deserializer<Student> {
@Override
public Student deserialize(String s, byte[] bytes) {
return JSON.parseObject(bytes,Student.class);
}
}
创建消费者ConsumerDemo
public class ConsumerDemo {
public static void main(String[] args) {
// 生成配置文件
Properties pro = new Properties();
// broker 地址
pro.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.92.111:9092,192.168.92.111:9093");
//指定消费者组
pro.put(ConsumerConfig.GROUP_ID_CONFIG, "mygroup1");
//指定反序列化的方式,指定key-value类型,都是Kafka封装好的序列化。
pro.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringDeserializer");
pro.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,"StudentJSONDeSerializer");
// 创建对象
Consumer<String,Student> consumer = new KafkaConsumer<String, Student>(pro);
// 监听topic主题
consumer.subscribe(Arrays.asList("mydemotopic1"));
while (true)
{
// 拉取信息
ConsumerRecords<String, Student> polls = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, Student> poll : polls) {
System.out.println("key:"+poll.key()+"\t"+"valus:"+poll.value().getStuName()+"|"+poll.value().getStuID());
}
}
}
}
三、Kafka的原理解析和监控
1、Kafka的原理解析
(1)持久化:日志log
- 指Kafka数据(消息)的持久化,将消息持久化到日志文件
- 顺序写入,新的消息被写到log文件的最后
- 记录offset的偏移量,当消费者进行消费消息的时候,会把自己的消费进度offset提交给kafka,然后kafka记录下来,下次消费时,就知道消费者消费到哪里。
- 为了加快查找消息对应的log文件,维护索引文件,索引文件和log文件在同个目录下。
查看log中的数据
bin/kafka-run-class.sh kafka.tools.DumpLogSegments --help
bin/kafka-run-class.sh kafka.tools.DumpLogSegments --files logs/broker0/mydemotopic1-0/00000000000000000000.log --print-data-log
- 写入数据:顺序写入日志,新消息写在后面
- 读取数据:根据offset偏移量。
(2)Producer生产者:三种消息发送的模式
- 发完即忘:发了就不管、性能最好
- acks=1 : leader收到就可以
- acks=all leader和follower都收到
另一角度:Java api提供的有。
同步:Future 对象
异步:回调函数
(3)Consumer消费者:采用消费者进行消息
(4)Leader的选举
副本机制 ----> 针对分区
(5)日志的清理
最后的配置文件说明也都提到过
log.retention.hours=168
日志默认保存7天
log.retention.bytes=1073741824
当磁盘空间小于1G,开始删除日志
log.retention.check.interval.ms=300000
每隔多长时间检查一下日志的过期
(6)消息的可靠传输----> 记录到log中,同时记录offset的偏移量
高水位线
支持:Kafka、Oracle----- 高水位线
-
Kafka:消费者Consumer只能消费高水位线以下的数据
-
Oracle:插入insert数据的时候,只能插入到高水位线的位置
容易造成存储碎片。 -
Flink ----水位线Watermark,作用:处理乱序数据
一个topic有3个分区,消费者只能消费follow的最少哪个数据位置开始消费,这是从安全的角度进行考虑的,保持数据的一致性,假如leader死掉了,不至于丢失数据。
(7)Kafka的配额:quota
HDFS的配额:名称配额、空间配额
Oracle的配额:举例:张三最多只能使用200M表空间
alter user 张三 quota 200M on users;
alter user 张三 quota unlimited on users; 没有限制
Kafka的配额:针对生产者、消费者
(1)producer:生产者单位时间(每秒)能发布到单台broker的字节数(producer_byte_rate)
(2)consumer:消费者单位时间(每秒)从单台broker上拉取的字节数(consumer_byte_rate)
针对三种不同级别
-
user用户级别
使用Kafka的用户认证的机制
借助JAAS(Java Authentication Authorization Service:Java认证授权服务)来实现 -
client ID客户端
ProducerConfig.CLIENT_ID
ConsumerConfig.CLIENT_ID -
user+Client ID
配置client-id。例如,client-id为”clientA”
bin/kafka-configs.sh --zookeeper bigdata111:2181 --alter --add-config \
'producer_byte_rate=1024,consumer_byte_rate=2048' \
-entity-type clients --entity-name clientA
配置user。例如,user为”user1”
bin/kafka-configs.sh --zookeeper bigdata111:2181 --alter --add-config \
'producer_byte_rate=1024,consumer_byte_rate=2048' \
--entity-type users --entity-name user1
配置user+clientid。例如,user为”user1”,clientid为”clientA”。
bin/kafka-configs.sh --zookeeper bigdata111:2181 --alter --add-config \
'producer_byte_rate=1024,consumer_byte_rate=2048' \
--entity-type users --entity-name user1 --entity-type clients \
--entity-name clientA
2、监控:KafkaOffsetMonitor
KafkaOffsetMonitor是Kafka的一款客户端消费监控工具,用来实时监控Kafka服务的Consumer以及它们所在的Partition中的Offset,我们可以浏览当前的消费者组,并且每个Topic的所有Partition的消费情况都可以一目了然。
2.1 启动KafkaOffsetMonitor
将下载下来的KafkaOffsetMonitor jar包上传到linux上,可以新建一个目录KafkaMonitor,用于存放KafkaOffsetMonitor-assembly-0.2.0.jar进入到KafkaMonitor目录下,通过java编译命令来运行这个jar包:
java -cp KafkaOffsetMonitor-assembly-0.2.0.jar com.quantifind.kafka.offsetapp.OffsetGetterWeb --zk 10.0.0.50:12181,10.0.0.60:12181,10.0.0.70:12181 --port 8088 --refresh 5.seconds --retain 1.days
参数说明:
zk :zookeeper主机地址,如果有多个,用逗号隔开
port :应用程序端口
refresh :应用程序在数据库中刷新和存储点的频率
retain :在db中保留多长时间
dbName :保存的数据库文件名,默认为offsetapp
四、集成Kafka
1、flume集成kafka
flume集成kafka只需要修改配置文件就可以了,在sink层有对应kafka的配置。
flume官方配置样例
flume配置文件如下
#bin/flume-ng agent -n a4 -f myagent/a4.conf -c conf -Dflume.root.logger=INFO,console
#定义agent名, source、channel、sink的名称
a4.sources = r1
a4.channels = c1
a4.sinks = k1
#具体定义source
a4.sources.r1.type = spooldir
a4.sources.r1.spoolDir = /tmp/logs
#定义拦截器,为消息添加时间戳
a4.sources.r1.interceptors = i1
a4.sources.r1.interceptors.i1.type = org.apache.flume.interceptor.TimestampInterceptor$Builder
#具体定义channel
a4.channels.c1.type = memory
a4.channels.c1.capacity = 10000
a4.channels.c1.transactionCapacity = 100
#具体定义sink
a4.sinks.k1.channel = c1
a4.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
a4.sinks.k1.kafka.topic = mytopic1
a4.sinks.k1.kafka.bootstrap.servers = bigdata111:9092
a4.sinks.k1.kafka.flumeBatchSize = 20
a4.sinks.k1.kafka.producer.acks = 1
a4.sinks.k1.kafka.producer.linger.ms = 1
a4.sinks.k1.kafka.producer.compression.type = snappy
#组装source、channel、sink
a4.sources.r1.channels = c1
a4.sinks.k1.channel = c1
注意事项:
1、首先kafka的主题实现存在,可以通过kafka命令进行创建主题
bin/kafka-topics.sh --create \
--zookeeper bigdata111:2181 \
--replication-factor 2 \
--partitions 3 \
--topic mydemotopic1
2、启动flume
bin/flume-ng agent -n a4 -f myagent/a4.conf -c conf -Dflume.root.logger=INFO,console
3、启动kafka命令监听
bin/kafka-console-consumer.sh --bootstrap-server bigdata111:9092 --topic mydemotopic1
然后就可以尝试在监听的文件尝试放文件。
2、storm集成kafka
storm 集成kafka说白了就是通过kafka提供的接口订阅主题,在上面我们也通过Java代码也进行了实现,但是,这些代码可以进一步的封装,进行代码复用,而这些封装工作不用我们去完成,storm api已经完成这项工作,我们只管去用就可以。
遗留问题:在用maven方式搭建stormwithkafka的时候,导入storm-core版本1.0.3,发现其中api的方法与之前不一样,最后采用原生的方式,添加storm-core的jar包到项目中,不知是不是storm的api发生改变。
展示主函数代码:
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.StormSubmitter;
import org.apache.storm.generated.StormTopology;
import org.apache.storm.kafka.*;
import org.apache.storm.spout.SchemeAsMultiScheme;
import org.apache.storm.topology.IRichSpout;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.tuple.Fields;
//主函数
public class WordCountToplogy {
public static void main(String[] args) throws Exception {
// 创建任务
TopologyBuilder builder = new TopologyBuilder();
//指定第一级组件:Spout任务
builder.setSpout("myspout", createKafkaSpout());
//指定第二级组件:拆分单词bolt
builder.setBolt("mysplit", new WordCountSplitBolt()).shuffleGrouping("myspout");
//指定第三级组件:单词计数bolt
builder.setBolt("mytotal", new WordCountTotalBolt()).fieldsGrouping("mysplit", new Fields("word"));
//创建任务,并运行
StormTopology job = builder.createTopology();
//有两种运行模式
// 1、本地模式
LocalCluster cluster = new LocalCluster();
cluster.submitTopology("MyDemo", new Config(), job);
// 2、集群模式
// StormSubmitter.submitTopology(args[0], new Config(), job);
}
private static IRichSpout createKafkaSpout() {
//指定ZooKeeper的地址,通过ZooKeeper获取broker的信息
BrokerHosts hosts = new ZkHosts("192.168.92.111:2181");
SpoutConfig conf = new SpoutConfig(hosts,"mydemotopic1","/mytopic1","mygroup");
// 下面这两个是kafka的配置 , 上面是storm连接kafka配置
//数据类型:String
conf.scheme = new SchemeAsMultiScheme(new StringScheme());
//注意:从最新的数据开始接收
conf.startOffsetTime = kafka.api.OffsetRequest.LatestTime();
return new KafkaSpout(conf);
}
}
注意:在第二级组件 中应将WordCountSplitBolt类中,tuple.getStringByField改成tuple.getstring(0),因为在集成kafka,对数据并没有指定字段。
3、spark streaming集成kafka
java代码展示
import kafka.serializer.StringDecoder;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.spark.SparkConf;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.api.java.JavaPairInputDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import org.apache.spark.streaming.kafka.KafkaUtils;
import java.util.*;
/**
* @author
* @date 2020/11/10-${TIEM}
*/
public class SparkKafka {
public static void main(String[] args) throws Exception {
Logger.getLogger("org.apache.spark").setLevel(Level.ERROR);
Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF);
final SparkConf conf = new SparkConf().setAppName("MyNetworkWordCount")
// local[2] 表示:相当于有两个CPU的核数
.setMaster("local[2]");
JavaStreamingContext js = new JavaStreamingContext(conf, Durations.seconds(3));
// 首先要创建一份kafka参数map
Map<String, String> kafkaParams = new HashMap<String, String>();
// 这里是不需要zookeeper节点,所以这里放broker.list
kafkaParams.put("metadata.broker.list",
"192.168.92.111:9092,192.168.92.111:9092");
// kafkaParams.put("auto.offset.reset", "smallest");
// 然后创建一个set,里面放入你要读取的Topic,可以并行读取多个topic
Set<String> topics = new HashSet<String>();
topics.add("mydemotopic1");
// 获取kafka数据
JavaPairInputDStream<String, String> line = KafkaUtils.createDirectStream(js,
String.class, // key类型
String.class, // value类型
StringDecoder.class, // 解码器
StringDecoder.class,
kafkaParams,
topics);
line.print();
js.start();
js.awaitTermination();
}
}
结果:
然后就可以做后续操作!
4、flink集成kafka
在讲flink的时候进行讲说。
配置文件的详细说明
大家可以用英语翻译软件把配置文件的英文翻译一下,配置文件的英文解释的很清楚。
############################# Server Basics #############################
broker.id=0 #是broker ID号,在集群中必须唯一
############################# Socket Server Settings #############################
#listeners=PLAINTEXT://:9092
使用的协议、broker端口(使用参数port)
num.network.threads=3 #接收请求的线程数
num.io.threads=8 #执行请求的线程数
socket.send.buffer.bytes=102400 #发送缓存区
socket.receive.buffer.bytes=102400 #接收缓冲区
socket.request.max.bytes=104857600 #接收请求的最大长度
############################# Log Basics #############################
log.dirs=/root/training/kafka_2.11-2.4.0/logs/broker0 #用于保存broker的日志(Kafka的数据文件)
num.partitions=1 # 是Topic默认分区数
num.recovery.threads.per.data.dir=1 #使用log执行恢复的线程数
############################# Internal Topic Settings #############################
# The replication factor for the group metadata internal topics "__consumer_offsets" and "__transaction_state"
# For anything other than development testing, a value greater than 1 is recommended to ensure availability such as 3.
offsets.topic.replication.factor=1
transaction.state.log.replication.factor=1
transaction.state.log.min.isr=1
############################# Log Flush Policy #############################
#log.flush.interval.messages=10000 #每隔多少条消息进行刷新
#log.flush.interval.ms=1000 #刷新的时间间隔
决定日志如何删除
############################# Log Retention Policy #############################
log.retention.hours=168 #日志默认保存7天
#log.retention.bytes=1073741824 #当磁盘空间小于1G,开始删除日志
log.segment.bytes=1073741824 #日志的段的大小
log.retention.check.interval.ms=300000 #每隔多长时间检查一下日志的过期
############################# Zookeeper #############################
# server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002".
zookeeper.connect=bigdata111:2181
# Timeout in ms for connecting to zookeeper
zookeeper.connection.timeout.ms=6000
############################# Group Coordinator Settings #############################
# The following configuration specifies the time, in milliseconds, that the GroupCoordinator will delay the initial consumer rebalance.
# The rebalance will be further delayed by the value of group.initial.rebalance.delay.ms as new members join the group, up to a maximum of max.poll.interval.ms.
# The default value for this is 3 seconds.
# We override this to 0 here as it makes for a better out-of-the-box experience for development and testing.
# However, in production environments the default value of 3 seconds is more suitable as this will help to avoid unnecessary, and potentially expensive, rebalances during application startup.
group.initial.rebalance.delay.ms=0