Kafka
一 消息队列/消息中间件(Message Queue)
1 什么是消息中间件?为什么要用消息中间件?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MLn9yx0A-1615266939058)(001.png)]
2 消息队列
2.1 Message
网络中两台设备之间传递的数据都是消息。例如:文字、图片、声音、视频等等
2.2 Queue
一种特殊的线性表,特点是先进先出。入队、出队
2.3 MQ(Message + Queue)
保存消息的队列,消息的传输过程中以队列作为容器,主要提供了生产、消费接口提供外部调用。
MQ一般分为两大类:
p2p(点对点)
pub/sub(发布/订阅)
2.3.1 p2p
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3hTuiBpd-1615266939063)(002.png)]
2.3.2 pub/sub
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9QROw380-1615266939065)(003.png)]
2.3.3 常见MQ
- RabbitMQ,Erlang编写,支持负载均衡,同时支持p2p和pub/sub
- Redis,基于Key/Value的Nosql的数据库,还可以做轻量级的pub/sub。短消息低于10kb性能比RabbitMQ更好。
- ZeroMQ轻量级的消息中间级,不需要单独的消息服务器。支持p2p
- ActiveMQ,JMS实现,支持p2p,支持持久化
- Kafka/Jafka,高性能跨语言的分布式发布/订阅的消息中间件。数据持久化、全分布式、支持在线和离线的处理
- RocketMQ,纯Java实现的,支持发布/订阅,支持本地事务和分布式事务
二 Kafka介绍与安装
1 简介
领英(LinkedIn)公司发布的,scala语言编写的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YwLf0atN-1615266939067)(004.png)]
2 kafka的3大特点
- 高吞吐量
支持每秒上百万的消息的生产和消费
- 持久性
有一套非常完善的消息存储机制,确保数据的安全——中间存储
- 分布式
基于分布式的扩容和容错机制。kafka的数据会被复制到多台服务器上,当单台机器故障还有其他的机器的数据保证。
3 Kafka服务
- Topic : 主题,Kafka中的处理消息的分类
- Broker : 消息服务器的代理,Kafka中的一个kafka服务器节点就是一个broker
- Partition : Topic物理上的分区,一个topic在一个broker上被分为1个或多个partition。partition在创建topic的时候指定。
- Message : 消息,通信的基本单位,每个消息都属于一个分区。
4 Kafka安装
4.1 Kafka的伪分布式安装和全分布式安装
全分布式的安装:
1. 安装全分布式的zookeeper
伪分布式的安装
1. 直接使用kafka自带的zookeeper脚本
4.2 伪分布式安装
4.2.1 安装
[root@chancechance software]# tar -zxvf kafka_2.11-1.1.1.tgz -C /opt/apps/
[root@chancechance apps]# mv kafka_2.11-1.1.1/ kafka-1.1.1
[root@chancechance kafka-1.1.1]# vi /etc/profile
export KAFKA_HOME=/opt/apps/kafka-1.1.1
export PATH=$PATH:$SPARK_HOME/bin:$SPARK_HOME/sbin:$KAFKA_HOME/bin
4.2.2 server.properties
[root@chancechance config]# vi server.properties
############################# Server Basics #############################
# The id of the broker. This must be set to a unique integer for each broker.
broker.id=0
############################# Log Basics #############################
# A comma separated list of directories under which to store log files
log.dirs=/opt/apps/kafka-1.1.1/data/kafka
############################# Zookeeper #############################
# Zookeeper connection string (see zookeeper docs for details).
# This is a comma separated host:port pairs, each corresponding to a zk
# server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002".
# You can also append an optional chroot string to the urls to specify the
# root directory for all kafka znodes.
zookeeper.connect=10.206.0.4:2181/kafka
tip:
如果是全分布式你需要将你的kafka发布给其他的服务器节点,然后需要修改broker.id
4.2.3 启动kafka
## 1. 先启动zookeeper
[root@chancechance bin]# zookeeper-server-start.sh -daemon $KAFKA_HOME/config/zookeeper.properties
## 2. 连接zk服务器
[root@chancechance bin]# zookeeper-shell.sh 10.206.0.4:2181
## 3. 启动kafka
[root@chancechance bin]# kafka-server-start.sh -daemon $KAFKA_HOME/config/server.properties
4.3 全分布式
1. 自己安装zookeeper
2. servier.propertis
broker.id=1 qphone01
broker.id=2 qphone02
broker.id=3 qphone03
3. 启动
3.1启动全分布式的zookeeper
3.2启动kafka
5 zookeeper中kafka的znode节点含义
/kafka
/cluster/id --> 表示kafka集群中的版本和编号
{"version":"1","id":"XeVX1Ac1Trqj4qkFunQMEA"}
/controller --> 他是kafka中非常重要的角色,控制kafka中的leader的选举,控制topic的crud。brokerid表示对应的broker承担了controller的角色
{"version":1,"brokerid":1,"timestamp":"1602226517468"}
/controller_epoch --> 1表示controller的纪元,换句话来说表示的是controller的更迭。每当controller的broker更换一次,controller的版本就+1,同时纪元+1
1
/brokers
/ids [1,2,3] ---> 在当前kafka中的broker的实力id列表
/topics [qphone01, hadoop, __consumer_offsets] --> kafka的主题列表
/seqid --> kafka系统的序列id
/consumers --> 老版本中用于存储kafka消费者信息,主要用于保存offset。新版本这个目录就没有用了
/config --> 存储配置信息
三 Kafka的基本操作
1 命令行操作
1.1 kafka的topic的操作——kafka-topics.sh
1.1.1 create topic
kafka-topics.sh \
--create \
--topic hadoop \ ## 创建的主题名称
--zookeeper 10.206.0.4:2181/kafka \ ## 指定kafka关联的zk的地址
--partitions 3 \ ## 指定topic的分区数
--replication-factor 1 ## 指定副本因子,副本因子的数量必须小于等于broker的个数
Created topic "hadoop".
1.1.2 list topic
kafka-topics.sh \
--list \
--zookeeper 10.206.0.4:2181/kafka
1.1.3 describe topic
kafka-topics.sh \
--describe \
--topic hadoop \
--zookeeper 10.206.0.4:2181/kafka
Topic:hadoop PartitionCount:3 ReplicationFactor:3 Configs:
Topic: hadoop Partition: 0 Leader: 2 Replicas: 2,3,1 Isr: 2,3,1
Topic: hadoop Partition: 1 Leader: 3 Replicas: 3,1,2 Isr: 3,1,2
Topic: hadoop Partition: 2 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3
##
#Partition : 当前主题对应的分区编号
#Replicas : 副本因子,当前kafka集群中对应的partition所在的broker的broker.id列表
#Leader :该Partition中对应的所有副本的leader,处理该partition的读写请求
#Isr :该Partition存活的副本对应的broker.id列表
1.1.4 alter topic
kafka-topics.sh \
--alter \
--topic hadoop \
--zookeeper 10.206.0.4:2181/kafka \
--partitions 4
WARNING: If partitions are increased for a topic that has a key, the partition logic or ordering of the messages will be affected
Adding partitions succeeded!
tip:
分区是只能增加不能减少的
1.1.5 删除主题
kafka-topics.sh \
--delete \
--topic hadoop \
--zookeeper 10.206.0.4:2181/kafka
1.1.6 生产数据——kafka-console-producer.sh
kafka-console-producer.sh \
--broker-list 10.206.0.4:9092 \
--topic hadoop
1.1.7 消费数据——kafka-console-consumer.sh
kafka-console-consumer.sh \
--topic hadoop \
--bootstrap-server 10.206.0.4:9092
2 Java API操作
2.1 Producer API
2.1.1 导入依赖
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>1.1.1</version>
</dependency>
2.1.2 代码
package cn.qphone.kafka.day2;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.Properties;
public class Demo1_Producer {
public static void main(String[] args) {
//1. 设置属性
Properties props = new Properties();
props.put("bootstrap.servers", "146.56.208.76:9092");
props.put("acks", "all");
props.put("retries", 0);
props.put("batch.size", 16384);
props.put("linger.ms", 1);
props.put("buffer.memory", 33554432);
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
//2. 获取到生产者对象
Producer<String, String> producer = new KafkaProducer<>(props);
//3. 发送消息
ProducerRecord<String, String> record = new ProducerRecord<>("hadoop", null, "你好");
producer.send(record);
//4. 释放资源
producer.close();
}
}
tip:
如果你使用的是云服务器,你会发现你的公网ip访问的kafka的消息,在消费者中是接收不到的,根本原因就是他默认是通过内网ip监听的服务接口
解决方案:修改server.properties
advertised.listeners=PLAINTEXT://146.56.208.76:9092
2.2 Consumer API
package cn.qphone.kafka.day2;
import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import java.util.Arrays;
import java.util.Properties;
public class Demo2_Consumer {
public static void main(String[] args) {
//1. 准备数据
Properties props = new Properties();
props.put("bootstrap.servers", "146.56.208.76:9092");
props.put("group.id", "hzbigdata2004"); // 设置消费者组
props.put("enable.auto.commit", "true");
props.put("auto.commit.interval.ms", "1000");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
//2. 创建消费者对象
Consumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("hadoop")); // 订阅消息
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);
for (ConsumerRecord<String, String> record : records) {
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
}
}
}
}
2.3 优化代码
2.3.1 producer.properties
bootstrap.servers=146.56.208.76:9092
acks=all
retries=0
batch.size=16384
linger.ms=1
buffer.memory=33554432
key.serializer=org.apache.kafka.common.serialization.StringSerializer
value.serializer=org.apache.kafka.common.serialization.StringSerializer
2.3.2 Producer
package cn.qphone.kafka.day2;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.io.IOException;
import java.util.Properties;
public class Demo1_Producer {
public static void main(String[] args) throws IOException {
//1. 设置属性
Properties props = new Properties();
props.load(Demo1_Producer.class.getClassLoader().getResourceAsStream("producer.properties"));
//2. 获取到生产者对象
Producer<String, String> producer = new KafkaProducer<>(props);
//3. 发送消息
ProducerRecord<String, String> record = new ProducerRecord<String, String>("hadoop", "hello");
producer.send(record);
//4. 释放资源
producer.close();
}
}
2.3.3 consumer.properties
bootstrap.servers=146.56.208.76:9092
group.id=hzbigdata2004
enable.auto.commit=true
auto.commit.interval.ms=1000
key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
value.deserializer=org.apache.kafka.common.serialization.StringDeserializer
2.3.4 Consumer
package cn.qphone.kafka.day2;
import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import java.io.IOException;
import java.util.Arrays;
import java.util.Properties;
public class Demo2_Consumer {
public static void main(String[] args) throws IOException {
//1. 准备数据
Properties props = new Properties();
props.load(Demo1_Producer.class.getClassLoader().getResourceAsStream("consumer.properties"));
//2. 创建消费者对象
Consumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("hadoop")); // 订阅消息
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);
for (ConsumerRecord<String, String> record : records) {
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
}
}
}
}
2.4 Topic API——AdminClient
2.4.1 create topic
package cn.qphone.kafka.day2;
import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.NewTopic;
import java.util.Arrays;
import java.util.Properties;
public class Demo3_Topic {
public static void main(String[] args) {
//1. 创建主题
/**
* kafka-topics.sh \
* --create \
* --topic hadoop \ ## 创建的主题名称
* --zookeeper 10.206.0.4:2181/kafka \ ## 指定kafka关联的zk的地址
* --partitions 3 \ ## 指定topic的分区数
* --replication-factor 1 ## 指定副本因子,副本因子的数量必须小于等于broker的个数
*/
//0. 设置参数
Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "146.56.208.76:9092");
//1. 创建对象
AdminClient adminClient = AdminClient.create(properties);
//2. 创建主题
adminClient.createTopics(Arrays.asList(new NewTopic("spark", 3, (short)1)));
//3. 释放
adminClient.close();
}
}
2.4.2 list topic
package cn.qphone.kafka.day2;
import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.ListTopicsResult;
import org.apache.kafka.common.KafkaFuture;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ExecutionException;
public class Demo4_ListTopics {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//0. 设置参数
Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "146.56.208.76:9092");
//1. 创建对象
AdminClient adminClient = AdminClient.create(properties);
//2. list
ListTopicsResult listTopicsResult = adminClient.listTopics();
//2.1 names:获取主题名称
KafkaFuture<Set<String>> names = listTopicsResult.names();
Set<String> names_set = names.get(); // 获取名称的字符串表现形式
//2.2 遍历
for (String name: names_set) {
System.out.println(name);
}
//3.
adminClient.close();
}
}
2.5 相关参数说明
bootstrap.servers : 你所要连接到kafka的brokers
key.serializer : key的序列化器
value.serializer : value的序列化器
acks=[0|-1/all|1] : 消息确认机制
0 : 不做确认,只管发送消息即可
-1/all : 不仅需要leader将数据写入到本地磁盘,并确认。还需要将所有的消息都同步到副本的服务器中。
1 : 只需要leader进行消息确认。后期副本所在的服务器自己从leader从同步
batch.size : 每个分区用户缓存未发送record空间大小
linger.ms : 无论缓冲区是否沾满,都会延迟xxx ms来发送请求
buffer.memory : 一个生产者对象的缓冲区大小
retries : 当消息发送失败之后重试的次数
——————————————————————————————————————————————————————————————————————
group.id : 消费者组,它可以决定对这个主题的消费的最大并行度,最优的情况是消费者组中的消费者的数量刚好等于分区数。
enable.auto.commit : 自动的消费数据
auto.commit.interval.ms : 自动消费的时间间隔,他们俩一起决定了你的消费的速度
key.deserializer : key的反序列化器
value.deserializer : value的反序列化器
2.6 消费者组
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pJm7U6Yx-1615266939068)(005.png)]
3 自定义分区器
3.0 默认的分区策略
每一条ProducerRecord,这个对象中包含了:topic,partition-id以及key和value对偶
3种策略:
1. 如果你指定了分区,那么你的这条record就直接添加到指定partition-id中
2. 如果没有指定分区编号,但是你指定了key,使用key进行hash选择partition
3. 如果既没有指定分区编号,又没有指定key,默认使用轮询的方式选择partition
3.1 随机分区器
3.1.1 分区器——Demo5_RandomPartitioner
package cn.qphone.kafka.day2;
import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import java.util.Map;
import java.util.Random;
public class Demo5_RandomPartitioner implements Partitioner {
private Random random = new Random();
/**
* 决定了分区策略
* @param topic 主题
* @param key 数据key
* @param keyBytes 数据key的字节数组
* @param value 数据value
* @param valueBytes 数据value的字节数组
* @param cluster 集群对象
* @return
*/
@Override
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
//1. 获取分区数
Integer count = cluster.partitionCountForTopic(topic);
//2. 产生分区索引
int partition = random.nextInt(count);
return partition;
}
/**
* 释放
*/
@Override
public void close() {
}
/**
* 设置你的配置
* @param configs
*/
@Override
public void configure(Map<String, ?> configs) {
}
}
3.1.2 producer.properties
partitioner.class=cn.qphone.kafka.day2.Demo5_RandomPartitioner
四 flume整合kafka
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QzhVvXAR-1615266939069)(006.png)]
1 安装flume
略
2 新建一个主题
kafka-topics.sh \
--create \
--topic flume-kafka \
--zookeeper 10.206.0.4:2181/kafka \
--partitions 3 \
--replication-factor 1
3 netcat_kafka.conf
a1.sources = r1
a1.sinks = k1
a1.channels = c1
a1.sources.r1.type = netcat
a1.sources.r1.bind = 10.206.0.4
a1.sources.r1.port = 6666
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100
a1.channels.c1.byteCapacityBufferPercentage = 20
a1.channels.c1.byteCapacity = 800000
a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
a1.sinks.k1.kafka.topic = flume-kafka
a1.sinks.k1.kafka.bootstrap.servers = 10.206.0.4:9092
a1.sinks.k1.kafka.flumeBatchSize = 20
a1.sinks.k1.kafka.producer.acks = 1
a1.sinks.k1.kafka.producer.linger.ms = 1
a1.sinks.k1.kafka.producer.compression.type = snappy
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
4 测试
##1. 启动yarn:start-yarn.sh
##2. 启动zk:
zookeeper-server-start.sh -daemon $KAFKA_HOME/config/zookeeper.properties
zkServer.sh start
##3. 启动kafka
kafka-server-start.sh -daemon $KAFKA_HOME/config/server.properties
##4. 启动flume
nohup flume-ng agent -n a1 -c /opt/apps/flume-1.9.0/conf -f /opt/apps/flume-1.9.0/conf/netcat_kafka.conf > /dev/null 2>&1 &
##5. 启动消费者
kafka-console-consumer.sh \
--topic flume-kafka \
--bootstrap-server 10.206.0.4:9092
##6. 启动netcat
yum -y install telnet
telnet 10.206.0.4 6666
五 Kafka的架构之道
1 kafka相关术语
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2zf80RST-1615266939070)(007.png)]
1.1 replicas
每一个分区,根据副本因子N,会有N个副本。比如broker1有一个topic,这个主题分区为topic-1,副本因子为2,那么在两个broker的数据目录里就都有一个topic-1,其中一个是leader,一个是follower
1.2 segment
Partition在物理上是由多个segment组成的。每个segment中存储了很多的message
1.3 Leader
每个Partition有多个副本,其中有且仅有一个是作为Leader,其他都是Follower。Leader负责将数据读写到Partition。
1.4 Follower
Follower跟随Leader的,所有的写请求都是通过Leader路由的,Leader将数据广播给所有的Follower,follower的数据是保持和leader同步的。如果leader失效,会从剩下的follower选举出一个新的leader。当follower挂掉了、卡住、同步慢,leader会把这个follower从“in sync follower(isr)”列表中移除,重新创建一个新的follower。
1.5 Offset
kafka的存储文件都是按照offset.log来命名的,用offset命名日志最大的好处就是方便查找。例如想要查找位于2100的数据,只需要查找2000.log, 3000.log。
2 Kafka的分布式模型
Kafka的分布式指的其实就是分区被分布在多台server(broker)上。同时每个分区leader和follower组成的副本(replicas)。其实大佬和马仔,大佬负责处理,而马仔负责同步。
kafka的分区日志(message)分布在kafka的集群上的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-USruQjA5-1615266939070)(008.png)]
3 kafka的文件存储
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YNVRZZ7t-1615266939071)(009.png)]
4 topic的partition
4.1 为什么要分区
- 提高并发
- 方便集群中扩展
4.2 partition数据在哪个位置
1. server.properties
log.dir=$KAFKA_HOME/data/kafka
2. 例如:为了test-1/test-2分了4个partition
|--test-1-0
|--test-1-1
|--test-1-2
|--test-1-3
|--test-2-0
|--test-2-1
|--test-2-2
|--test-2-3
4.3 多节点partition存储分布
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6MkeASVb-1615266939072)(010.png)]
4.4 分区分配策略
1. 有n个broker和partition进行排序
2. 将第i个partition分配到i mod / n个broker
4.5 测试多leader的情况:4分区+2个副本+3个broker
kafka-topics.sh \
--create \
--topic kafka-topics \ ## 创建的主题名称
--zookeeper 10.206.0.4:2181/kafka \ ## 指定kafka关联的zk的地址
--partitions 4 \ ## 指定topic的分区数
--replication-factor 2
kafka-topics.sh \
--describe \
--topic kafka-topics \
--zookeeper 10.206.0.4:2181/kafka
Topic:hadoop PartitionCount:4 ReplicationFactor:2 Configs:
Topic: kafka-topics Partition: 0 Leader: 1 Replicas: 1,2 Isr: 1,2
Topic: kafka-topics Partition: 1 Leader: 2 Replicas: 2,3 Isr: 2,3
Topic: kafka-topics Partition: 2 Leader: 3 Replicas: 3,1 Isr: 3,1
Topic: kafka-topics Partition: 3 Leader: 1 Replicas: 1,3 Isr: 1,3
第1个partition分配到第(1 % 3) = 1个broker
第2个partition分配到第(2 % 3) = 2个broker
第3个partition分配到第(3 % 3) = 3个broker
第3个partition分配到第(4 % 3) = 1个broker
5 partition中的文件存储
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gj0qe0r4-1615266939072)(011.png)]
5.1 kafka分区的segment
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-edHIH9Vh-1615266939073)(012.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O741pYY4-1615266939073)(013.png)]
5.2 message的消息结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cyMo6yfk-1615266939074)(014.png)]
6 Consumer Group
参考前面的说明
7 offset的维护
由于consumer在消费的时候有可能会出现类似于宕机等等情况,consumer回复之后不能重头开始消费,需要接着上次宕机之前的offset继续消费,所以consumer需要实时的记录消费到哪个offset位置了。
kafka默认是定期的帮你自动的提交offset,你也可以选择手动的提交。另外kafka还会自动group消费情况保存起来,形成了一个offset的map,如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mGPawRBY-1615266939075)(015.png)]
0.9版本之前使用的是zookeeper来维护kafka的偏移量,这种方式叫做low level API
0.9版本之后使用的是broker来维护kafka的偏移量,这种方式叫做high level API
自动提交enable.auto.commit=true,更新频率auto.commit.interval.ms属性决定的。这种方式也被称为at most once。当捕获到消息就立刻更新偏移量,无论消费是否成功。
手动提交enable.auto.commit=false,这种方式成为at least once。当捕获到消息,等到消费完成之后在调用方式提交偏移量;如果消费失败呢,则偏移量不会更新,那么当我们重复消费成功之后才会更新偏移量。
8 kafka中push和pull
一个思考问题,当我们在消费数据的时候,是消费者从kafka中pull数据,还是 broker push数据到消费者?首先生产者一定是向broker中push消息,并由消费者从broker中pull数据。
9 Exactly Once(恰好一次)
对于一些比较重要的消息,保证Exactly Once语义。保证消息被发送且仅被发送一次。
producer.properties
enable.idempotent=true
acks=-1
10 Kafka的leader选举
10.1 zab协议(zookeeper atomic broadcast)
这个协议可保证集群中的所有的zookeepr的读写操作都能正常执行:
zab协议2种模式:
广播模式
回复模式
10.2 curator框架
10.2.1 导入依赖
<dependencies>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
</dependency>
</dependencies>
10.2.2 znode的crud
- CuratorUtils
package cn.qphone.kafka.day3;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.retry.ExponentialBackoffRetry;
public class CuratorUtils {
private static CuratorFramework client;
static {
client = CuratorFrameworkFactory.builder()
.connectString("146.56.208.76")
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
}
public static CuratorFramework getCuratorClient() {
if(client.getState() == CuratorFrameworkState.STOPPED) { // 如果说你的client已经close了
client = CuratorFrameworkFactory.builder()
.connectString("146.56.208.76")
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
}
return client;
}
public static void close(CuratorFramework client) {
if (null != client) client.close();
}
}
- 创建节点
package cn.qphone.kafka.day3;
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.CreateMode;
public class Demo1_Curator {
public static void main(String[] args) throws Exception {
//1. 获取到入口类
CuratorFramework client = CuratorUtils.getCuratorClient();
//2. 必须先启动
client.start();
//3. 创建一个节点
client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.PERSISTENT)
.forPath("/curator/zookeeper6", "nice to meet you".getBytes());
//4. 释放资源
CuratorUtils.close(client);
client = CuratorUtils.getCuratorClient();
client.start();
client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.PERSISTENT)
.forPath("/curator/zookeeper7", "nice to meet you".getBytes());
CuratorUtils.close(client);
}
}
- 查询
public class Demo1_Curator {
public static void main(String[] args) throws Exception {
//1. 获取到入口类
CuratorFramework client = CuratorUtils.getCuratorClient();
//2. 必须先启动
client.start();
//3. 罗列所有节点
List<String> paths = client.getChildren().forPath("/");
for (String path : paths) {
System.out.println(path);
}
//4. 释放资源
CuratorUtils.close(client);
}
}
- 获取数据
package cn.qphone.kafka.day3;
import org.apache.curator.framework.CuratorFramework;
public class Demo1_Curator {
public static void main(String[] args) throws Exception {
//1. 获取到入口类
CuratorFramework client = CuratorUtils.getCuratorClient();
//2. 必须先启动
client.start();
//3. 获取数据
byte[] data = client.getData().forPath("/curator/zookeeper");
System.out.println(new String(data));
//4. 释放资源
CuratorUtils.close(client);
}
}
- 修改数据
public class Demo1_Curator {
public static void main(String[] args) throws Exception {
//1. 获取到入口类
CuratorFramework client = CuratorUtils.getCuratorClient();
//2. 必须先启动
client.start();
//3. 获取数据
client.setData().forPath("/curator/zookeeper", "hello".getBytes());
byte[] data = client.getData().forPath("/curator/zookeeper");
System.out.println(new String(data));
//4. 释放资源
CuratorUtils.close(client);
}
}
- 删除
public class Demo1_Curator {
public static void main(String[] args) throws Exception {
//1. 获取到入口类
CuratorFramework client = CuratorUtils.getCuratorClient();
//2. 必须先启动
client.start();
//3.删除节点
client.delete().forPath("/curator/zookeeper");
//4. 释放资源
CuratorUtils.close(client);
}
}
10.2.3 模拟动态的监听服务器的上下线
zk如何监听服务器的上下线?
当服务器启动的时候会向目标的zk注册一个节点路径,让这个路径存在说明这个服务器在线,当这个路径不存在说明服务器下线了。我们节点这个路径存在与否。
监听这个节点是否存在,使用watch
package cn.qphone.kafka.day3;
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import java.util.List;
public class Demo3_CuratorService_Discover implements Watcher {
private String path = "/taobao";
private CuratorFramework client;
private List<String> children;
public Demo3_CuratorService_Discover() {
try {
client = CuratorUtils.getCuratorClient();
client.start();
children = client.getChildren().usingWatcher(this).forPath(path);
System.out.println("初始化节点信息:" + children);
}catch (Exception e) {
e.printStackTrace();
}
}
/**
* 当zk产生节点相关的事件的时候都会调用,zk自己调用
*/
@Override
public void process(WatchedEvent event) {
try {
System.out.println("当前方法被调用---->");
List<String> newChildren = client.getChildren().usingWatcher(this).forPath(path);
if (newChildren.size() > children.size()) { // 新增,上线
for (String child : newChildren) {
if (!children.contains(child)) {
System.out.println("新增节点:"+child);
}
}
}else { // 减少
for (String child : children) {
if (!newChildren.contains(child)) {
System.out.println("被删除的节点:"+child);
}
}
}
children = newChildren; // 将新的子节点的列表覆盖老的子节点列表
}catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Demo3_CuratorService_Discover().start();
}
public void start() {
for (;;){}
}
}