目录
--------------------------------------------------------------------------------------------------------
课程地址: 【尚硅谷】Kafka3.x教程(从入门到调优,深入全面)_哔哩哔哩_bilibili
讲解版本:kafka3.x ;时长13小时
官网:Apache Kafka
Kafka2.8.0以后也可以配置不采用zookeeper,之前是必须采用zk的。(启动kafka前须启动zk)
说明:资料已下载,非常全!很赞!
第一二章 kafka概述&快速入门
1. kafka定义
传统上用作分布式的基于发布/订阅模式的消息队列(Message Queue),主要应用于大数据实时处理领域; 同时,Kafka是一个开源的分布式事件流平台(Event Streaming Platform),被数千家公司用于高性能数据管道、流分析、数据集成和关键任务应用。
2. 常见MQ有哪些?
目前企业中比较常见的消息队列产品主要有 Kafka、ActiveMQ、RabbitMQ、RocketMQ等。在大数据场景主要采用 Kafka 作为消息队列。在 JavaEE 开发中主要采用 ActiveMQ、RabbitMQ、RocketMQ。
3. 传统消息队列的应用场景
缓存/ 消峰、 解耦和 异步通信。
4. kafka基础架构
5. kafka的下载、安装、启动/停止
到官网下载:
第一步:将下载的压缩包拖到虚拟机centos102的/opt/software/目录下;
第二步:解压缩到指定目录/opt/module/下;
[root@centos102 software]# mkdir -p /opt/module
[root@centos102 software]# tar -zxvf kafka_2.12-3.0.0.tgz -C /opt/module
由于kafka压缩包是免安装包,故解压命令中参数为-zxvf
给解压的kafka改个名字:
第三步:修改配置文件;
进入/opt/module/kafka/config目录修改server.properties文件,具体如下:
#broker 的全局唯一编号,不能重复,只能是数字。
broker.id=0#kafka 运行日志(数据)存放的路径,路径不需要提前创建,kafka 自动帮你创建,可以配置多个磁盘路径,路径与路径之间可以用","分隔
log.dirs=/opt/module/kafka/datas#配置连接 Zookeeper 集群地址(在 zk 根目录下创建/kafka,方便管理)
zookeeper.connect=hadoop102:2181,hadoop103:2181,hadoop104:2181/kafka
第四步:分发kafka并修改配置broker.id
正确的分发命令:rsync -av kafka root@192.168.6.103:/opt/module
而不是:rsync -av kafka/ root@192.168.6.103:/opt/module (注意有没有/的区别)
第五步:java -version检查JDK是否安装成功并配置了环境变量;配置kafka的环境变量;启动;
/opt/module/kafka/bin/kafka-server-start.sh -daemon /opt/module/kafka/config/server.properties
[root@centos104 kafka]# bin/kafka-server-start.sh -daemon config/server.properties
老师说,启动kafka前须启动zk,但是我没有启动zk也能启动成功kafka。
我亲自下载搭建了zk集群,发现:
[root@centos102 zookeeper]# bin/zkServer.sh start 这是启动zk命令,必须是在zookeeper这个目录下才能启动,否则就报错;还有,zookeeper的配置文件zookeeper/conf/zoo.cfg中这个必须写绝对路径dataDir=/opt/module/zookeeper/data,(data这个目录是新建的,需要在这个目录下创建名称为 myid 的文件)否则zookeeper/data目录下的my.id文件读取不到,zk不能启动,从zookeeper/logs/下的日志中看到报错信息为myid文件读取不到:
第六步:停止服务时,须先关闭kafka再关闭zookeeper。老师写了批量启动停止的shell脚本。
保存退出后,执行 chmod 777 kf.sh 命令进行赋权。
kafka关闭命令:
[root@centos104 kafka]# bin/kafka-server-stop.sh
6. kafka命令行操作
注意:任何操作首先须启动服务端,即通过jps检查启动的服务,如果没有启动kafka时,须对集群的每台服务器执行命令启动kafka:
开启服务(在kafka根目录下执行):
bin/kafka-server-start.sh -daemon config/server.properties
6.1 主题命令行操作
相关命令都保存在kafka/bin/kafka-topics.sh这个文件中。
对Topic进行操作时,首先得使用--bootstrap-server参数连接上kafka broker主机名称和端口号。
对Topic进行操作,一般指增删改查。
zk集群启动的前提下,执行kafka主题命令行启动命令时,报错:could not be established. Broker may not be available. 一直在尝试连接。百度原因为:
kafka/config/server.properties文件中,需要进行如下配置:
操作了几次都无法成功,过了好几天,从头到尾,从克隆虚拟机开始重新操作了一遍,可以了,没有那个错误了!但上图中这个配置并没有改哦!我猜想可能是zookeeper没有真正启动,因为我只是执行了启动命令后jps查看启动的进程,但并没有执行命令bin/zkServer.sh status检查其是否真正启动!
当然,还有一种情况,就是你没有启动kafka的服务而直接执行如下命令,也会是一直连不上的情况:
kafka服务启动命令:
bin/kafka-server-start.sh -daemon config/server.properties
而直接执行:
bin/kafka-topics.sh --bootstrap-server centos102:9092 --list
练习
查看集群中的所有主题:
bin/kafka-topics.sh --bootstrap-server centos102:9092,centos103:9092,centos104:9092 --list
创建Topic命令:
bin/kafka-topics.sh --bootstrap-server centos102:9092,centos103:9092,centos104:9092 --create --topic topic1 --partitions 1 --replication-factor 3
6.2 生产者命令行操作
在6.1中我们创建了一个主题为topic1的主题,接下来创建一个生产者往topic1中发送数据。
生产者相关命令保存在bin/kafka-console-producer.sh中。
6.3 消费者命令行操作
消费者命令使用与生产者类似,可执行bin/kafka-console-consumer.sh回车进行求助。
第三章 生产者
3.1 生产者消息发送流程
3.1.1 发送原理
在消息发送的过程中,涉及到了 两个线程 ——main线程和Sender线程。在main线程
中创建了一个双端列队列RecordAccumulator。main线程将消息发送给RecordAccumulator,
Sender线程不断从RecordAccumulator中拉取消息发送到 Kafka Broker。
3.1.2 生产者重要参数
略,详见讲义。
3.2 异步发送API
3.2.1 普通异步发送
需求:创建 Kafka生产者,采用异步的方式发送到 Kafka Broker。
java代码编写,可参考讲义。运行下面代码前,记得将Linux虚拟机集群开启消费消息,观察数据变化。
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class TestProducter {
public static void main(String[] args) {
// 1. 创建 kafka 生产者的配置对象
Properties properties = new Properties();
// 2. 给 kafka 配置对象添加配置信息:bootstrap.servers
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"centos102:9092,centos103:9092,centos104:9092");
// key,value 序列化(必须):key.serializer,value.serializer
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 3. 创建 kafka 生产者对象
KafkaProducer<String,String> kafkaProducer = new KafkaProducer<>(properties);
// 4. 调用 send 方法,发送消息
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("topic1","test_data"+i));
}
// 5. 关闭资源
kafkaProducer.close();
}
}
3.2.2 带回调函数的异步发送
回调函数会在 producer 收到 ack 时调用,为异步调用,该方法有两个参数,分别是元数据信息(RecordMetadata)和异常信息(Exception),如果 Exception 为 null,说明消息发送成功,如果 Exception 不为 null,说明消息发送失败。
代码:
// 4. 调用 send 方法,发送消息(带回调函数)
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("topic1", "record_" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception e) {
if (e == null) {
// 没有异常,输出信息到控制台
System.out.println(" 主 题 : " + metadata.topic() + "->" + "分区:" + metadata.partition());
} else {
// 出现异常打印
e.printStackTrace();
}
}
});
// 延迟一会会看到数据发往不同分区
Thread.sleep(2);
}
3.3 同步发送API
只需在异步发送的基础上,再调用一下 get()方法即可。
3.4 生产者分区
分区的好处:
默认的分区策略:
/**
* The default partitioning strategy:
* <ul>
* <li>If a partition is specified in the record, use it
* <li>If no partition is specified but a key is present choose a partition based on a hash of the key
* <li>If no partition or key is present choose the sticky partition that changes when the batch is full.
*
* See KIP-480 for details about sticky partitioning.
*/
public class DefaultPartitioner implements Partitioner {
- 如果你指定了分区策略,那么使用你指定的分区策略;
- 如果没有指定分区,但key是存在的,那么会对key进行hash计算来确定分区;
- 如果分区和key都没有指定,选择在批处理已满时更改的粘性分区。
面试题:如果保证某张mysql表中的数据发送到某一分区?
答:指定key,且将表名设定为key。
自定义分区器:
- 将数据发往指定分区;
- 过滤脏数据;
第一步:自定义分区器MyPartitioner类实现Partitioner接口,重写其方法;
package org.wuya;
import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import java.util.Map;
public class MyPartitioner implements Partitioner {
@Override
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
String msgValue = value.toString();
int partition;
//如果消息中包含"record_"字符串,发送到0分区;否则发送到1分区
if (msgValue.contains("record_")) {
partition = 0;
} else {
partition = 1;
}
return partition;
}
//关闭资源
@Override
public void close() {
}
//配置方法
@Override
public void configure(Map<String, ?> configs) {
}
}
第二步:在java代码中,生产者的配置中添加分区器参数;
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,MyPartitioner.class);
第三步:测试。
3.5 生产经验——生产者如何提高吞吐量
package org.wuya;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class TestProductor2 {
public static void main(String[] args) {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "centos102:9092,centos103:9092,centos104:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
//batch.size:批次大小,默认 16K
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16 * 1024);
//linger.ms:等待时间,默认 0
props.put(ProducerConfig.LINGER_MS_CONFIG, 1);
//RecordAccumulator:缓冲区大小,默认 32M:buffer.memory
props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 32 * 1024 * 1024L);
//compression.type:压缩,默认 none,可配置值 gzip、snappy、lz4、zstd,通常使用snappy较多
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
//1.创建生产者(将消息发布到Kafka集群的Kafka客户端)
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(props);
//2.生产数据
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("topic1", "wuya_" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception e) {
if (e == null) {
System.out.println("主题:" + metadata.topic() + "分区:" + metadata.partition());
} else {
e.printStackTrace();
}
}
});
}
//3.关闭资源
kafkaProducer.close();
}
}
以上kafka生产者的默认配置信息在ProducerConfig.java类中的静态代码块中都可以看到。
3.6 生产经验——数据可靠性(不丢失)
回顾发送流程:首先kafka producer生产数据消息,通过send方法发送到缓存队列,达到batch.size或者linger.ms后,sender线程读取队列中消息经由selector发送到kafka集群,kafka集群接收到消息后按照一定规则进行处理后应答(acks)。这其中有3中应答方式:
- 0:生产者发送过来的数据,无需落盘(持久化)即进行应答;
- 1:Leader收到数据后应答;
- -1(或者all):Leader和isr队列的所有节点收齐数据后应答。默认值为-1。
数据可靠性分析:acks=0时,有丢失数据风险,可靠性差,效率高。acks=1时,Leader收到数据即进行应答,若应答完成后还没同步副本时,Leader挂了,会重新进行Leader选举,这种情况下新的Leader上面是没有刚刚的数据的,所以acks=1同样有丢失数据风险,可靠性中等,效率中等。acks=-1时,可靠性高,效率低,但其实它的可靠性高也是有条件的,如下:
ISR含义:
In-Sync Replicas (同步副本)的缩写;
Leader维护了一个动态的ISR,即和leader保持同步的follower集合。当ISR中的follower完成数据的同步之后,leader就会给follower发送ack。
如果follower长时间未向leader同步数据,则该follower将被踢出ISR,该时间阈值由replica.lag.time.max.ms参数设定。
Leader发生故障之后,就会从ISR中选举新的leader。
//设置acks,默认-1(同all)
properties.put(ProducerConfig.ACKS_CONFIG, -1);
//重试次数 retries,默认是int最大值2147483647
properties.put(ProducerConfig.RETRIES_CONFIG, 3);
3.7 生产经验——数据去重(幂等性&事务)
acks=-1时,虽然可以保证消息不丢失,确保可靠性,但是却存在消息重复发送的可能性,即不能保证消息的唯一性。即便将参数enable.idempotence设置为true(默认值就是true哦),也不能保证消息的唯一性,因为如果broker重启后PID就会改变。因此,要想保证消息唯一性,须使用事务解决。
生产者事务
1)生产者事务原理
说明:开启事务时,必须保证幂等性也是开启状态哦!
2)事务API
package org.wuya;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.errors.ProducerFencedException;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class TestProductor3 {
public static void main(String[] args) {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "centos102:9092,centos103:9092,centos104:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
//开启幂等性enable.idempotence,默认就是开启的
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG,true);
//设置acks,默认就是-1(同all)
props.put(ProducerConfig.ACKS_CONFIG, "-1");
//重试次数 retries,默认是int最大值2147483647
props.put(ProducerConfig.RETRIES_CONFIG, 3);
//TODO 使用kafka生产者时,必须指定事务id,否则运行程序会报错:Transactional method invoked on a non-transactional producer.
props.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG,"transactional_id_001");//事务id须全局唯一
//1.创建生产者(将消息发布到Kafka集群的Kafka客户端)
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(props);
//TODO 事务操作
//初始化事务
kafkaProducer.initTransactions();
//开启事务
kafkaProducer.beginTransaction();
try {
//2.生产数据
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("topic1", "wuy34a_" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception e) {
if (e == null) {
System.out.println("主题:" + metadata.topic() + "分区:" + metadata.partition());
} else {
e.printStackTrace();
}
}
});
}
// int i = 1/0;
//提交事务
kafkaProducer.commitTransaction();
} catch (Exception e) {
//回滚事务
kafkaProducer.abortTransaction();
} finally {
//3.关闭资源
kafkaProducer.close();
}
}
}
3.8 生产经验——数据有序
3.9 生产经验——数据乱序
第四章 kafka Broker
zookeeper存储kafka的哪些信息?
①集群中哪些机器(Broker)处于存活状态;
②每一个主题、每一个分区下面谁是Leader,谁是isr;
③Leader选举的controller信息。
主要记住这三个即可。
Broker kafka总体工作流程:
节点的服役与退役
略。详见视频25-27
kafka副本:
用于增加数据的可靠性;
默认副本为1个,生产环境通常设置为两个;
kafka副本分为Leader和follower,无论是生产者还是消费者,它们操作的数据只针对Leader;
Kafka 分区中的所有副本统称为 AR(Assigned Repllicas)。
AR = ISR + OSR
ISR,表示和 Leader 保持同步的 Follower 集合。如果 Follower 长时间未向 Leader 发送通信请求或同步数据,则该 Follower 将被踢出 ISR。该时间阈值由 replica.lag.time.max.ms参数设定,默认 30s。Leader 发生故障之后,就会从 ISR中选举新的 Leader。
OSR ,表示 Follower 与 Leader副本同步时,延迟过多的副本。
Leader选举流程:
选举原则是在isr中存活为前提,按照Replicas副本中排在前面的优先。
Follower故障处理细节:
Leader故障处理细节:
文件存储机制
2) 思考:Topic 数据到底存储 在什么位置?
略。
3)index 文件和 log 文件详解
面试题:kafka索引是按照什么方式存储的?
答:稀疏索引。默认每往log文件写入4kb数据,会往index文件写入一条索引。
文件清理策略
高效读写数据(高频面试)
第五章 kafka消费者
5.1 Kafka 消费方式
Kafka 消费方式为pull(拉)模式——consumer采用从broker中主动拉取数据。
5.2 Kafka 消费者 工作流程
5.2.1 消费者总体工作流程
5.2.2 消费者组原理
消费者组:
消费者组初始化流程:
图片中的7)是高频面试题,即消费者与集群是如何保持通信的呢?
答:心跳机制,默认是3秒。超时或者消费者处理消息的时间过长,都会触发再平衡。
消费者组详细消费流程:
5.2.3 消费者重要参数
略。见讲义。
5.3 消费者API
5.3.1 独立消费者案例(订阅主题)
需求:创建一个独立消费者,消费 topic1主题中数据。
注意:在消费者 API 代码中必须配置消费者组 id。命令行启动消费者不填写消费者组id会被自动填写随机的消费者组 id。
否则会报错:Exception in thread "main" org.apache.kafka.common.errors.InvalidGroupIdException: To use the group management or offset commit APIs, you must provide a valid group.id in the consumer configuration.
代码:
package org.wuya;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
public class TestConsumer1 {
public static void main(String[] args) {
//0.配置信息
Properties properties = new Properties();
//连接bootstrap.servers
//properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"centos102:9092,centos103:9092");
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"centos102:9092,centos103:9092,centos104:9092");
//反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
//配置消费者组id(必须得配)
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");//消费者组id的名字随意
//properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test01");//TODO 组id名字写成"test01",不知道为什么控制台打印不出任何信息!!!
//1.创建一个消费者
KafkaConsumer<String,String> kafkaConsumer = new KafkaConsumer<>(properties);
//2.订阅主题
List<String> topicsList = new ArrayList<>();
topicsList.add("topic1");
kafkaConsumer.subscribe(topicsList);
//3.消费数据
while(true){
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> record : consumerRecords) {
System.out.println(record);
}
}
}
}
5.3.2 独立消费者案例(订阅分区)
即:消费某主题的特定分区中的数据。
需求:创建一个独立消费者,消费 topic1 主题0号分区的数据。
消费端代码:(其他代码均与5.3.1中的相同)
生产者代码:
public class TestProductor2 {
public static void main(String[] args) {
Properties props = new Properties();
System.out.println(ProducerConfig.configNames());
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "centos102:9092,centos103:9092,centos104:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
//batch.size:批次大小,默认 16K
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16 * 1024);
//linger.ms:等待时间,默认 0
props.put(ProducerConfig.LINGER_MS_CONFIG, 1);
//RecordAccumulator:缓冲区大小,默认 32M:buffer.memory
props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 32 * 1024 * 1024L);
//compression.type:压缩,默认 none,可配置值 gzip、snappy、lz4、zstd,通常使用snappy较多
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
//1.创建生产者(将消息发布到Kafka集群的Kafka客户端)
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(props);
//2.生产数据(特定分区,如0分区)
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("topic1",0,"", "wuya_" + i), new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception e) {
if (e == null) {
System.out.println("主题:" + metadata.topic() + "分区:" + metadata.partition());
} else {
e.printStackTrace();
}
}
});
}
//3.关闭资源
kafkaProducer.close();
}
}
5.3.3 消费者组案例
需求:测试同一个主题的分区数据,只能由消费者组中的一个消费者消费,即不能由一个消费者组中的多个消费者消费。
代码:略。
问:如何指定多个消费者为一个消费者组?
答:通过在消费者端代码中配置相同的来指定。
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"相同的组id的名称");
5.4 生产经验——分区的分配以及再平衡
具体可以在kafka官网搜索“partition.assignment.strategy” 便可以看到4中分区策略。
org.apache.kafka.clients.consumer.RangeAssignor
: Assigns partitions on a per-topic basis.org.apache.kafka.clients.consumer.RoundRobinAssignor
: Assigns partitions to consumers in a round-robin fashion.org.apache.kafka.clients.consumer.StickyAssignor
: Guarantees an assignment that is maximally balanced while preserving as many existing partition assignments as possible.org.apache.kafka.clients.consumer.CooperativeStickyAssignor
: Follows the same StickyAssignor logic, but allows for cooperative rebalancing.
The default assignor is [RangeAssignor, CooperativeStickyAssignor], which will use the RangeAssignor by default, but allows upgrading to the CooperativeStickyAssignor with just a single rolling bounce that removes the RangeAssignor from the list.
Implementing the org.apache.kafka.clients.consumer.ConsumerPartitionAssignor
interface allows you to plug in a custom assignment strategy.
5.4.1 Range以及再平衡
Range分区策略原理:略。
缺点:当分区特别多时,会造成数据倾斜。
5.4.2 RoundRobin以及再平衡
RoundRobin分区策略原理:略。
Range是针对一个主题而言的,RoundRobin是针对集群中所有的主题(topic)而言的,所以RoundRobin不存在数据倾斜的问题。
RoundRobin 轮询分区策略,是把所有的 partition 和所有的consumer 都列出来,然后按照hashcode 进行排序,最后通过轮询算法来分配 partition 给到各个消费者。
// 修改分区分配策略
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, "org.apache.kafka.clients.consumer.RoundRobinAssignor");
5.4.3 Sticky以及再平衡
保证分配最大程度的平衡,同时保留尽可能多的现有分区分配。随机分配的。
5.5 offset位移
1. offset的默认维护位置
kafka中的三个重要角色:生产者、集群、消费者。生产者把数据发送到集群中的leader分区,消费者组中的消费者开始消费某一分区的数据。消费过程中,有个offset的概念,用来标记消费到哪了。
offset的默认维护位置:Kafka0.9版本之前,consumer默认将offset保存在Zookeeper中;从0.9版本开始,consumer默认将offset保存在Kafka一个内置的topic中,该topic为__consumer_offsets。
为什么0.9版本前维护在zk中,之后不在维护在zk了呢?
答:为了避免过多的网络交互。
__consumer_offsets 主题里面采用key和value 的方式存储数据。key是group.id+topic+分区号,value就是当前offset的值。每隔一段时间,kafka 内部会对这个 topic 进行压缩(compact),也就是每个group.id+topic+分区号就保留最新数据。
2. 自动提交offset
//自动提交开关"enable.auto.commit",默认就是true
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,true);
//自动提交间隔时间"auto.commit.interval.ms",默认为5000ms
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG,1000);
3. 手动提交offset
具体应用:
首先代码中把自动提交关掉:
//手动提交
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,false);
然后,手动提交offset:
//3.消费数据
while(true){
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> record : consumerRecords) {
System.out.println(record);
}
//同步提交offset
//kafkaConsumer.commitSync();
//异步提交 offset
kafkaConsumer.commitAsync();
}
4. 指定offset消费
auto.offset.reset = earliest | latest | none 默认是 latest。
任意指定offset位移开始消费
代码:
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
public class TestConsumerSeek {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"centos102:9092,centos103:9092");
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"wuya4");
KafkaConsumer<String,String> kafkaConsumer = new KafkaConsumer<>(properties);
List<String> topicsList = new ArrayList<>();
topicsList.add("topic1");
kafkaConsumer.subscribe(topicsList);
//指定位置进行消费
Set<TopicPartition> assignment = kafkaConsumer.assignment();
//保证分区分配方案已经制定完毕(因为消费者组初始化需要一个过程)
while(assignment.size()==0){
System.out.println("=======================================");
kafkaConsumer.poll(Duration.ofSeconds(1));
//更新数据,重新分配
assignment = kafkaConsumer.assignment();
}
//指定消费的offset
for (TopicPartition partition : assignment) {
kafkaConsumer.seek(partition,352);
}
while(true){
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
注意:每次执行完,需要修改消费者组名;(我实际操作时,不改也可以,只是第二次执行时需要的时间久一点,大概30秒左右)
5. 指定时间消费
没有直接指定时间消费的API,需要通过kafkaConsumer.offsetsForTimes(topicPartitionLongHashMap)这个API进行转换。
package org.wuya;
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.*;
public class TestConsumerSeek {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"centos102:9092,centos103:9092");
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"wuya53");
KafkaConsumer<String,String> kafkaConsumer = new KafkaConsumer<>(properties);
List<String> topicsList = new ArrayList<>();
topicsList.add("topic1");
kafkaConsumer.subscribe(topicsList);
//指定位置进行消费
Set<TopicPartition> assignment = kafkaConsumer.assignment();
//保证分区分配方案已经制定完毕(因为消费者组初始化需要一个过程)
while(assignment.size()==0){
System.out.println("=======================================");
kafkaConsumer.poll(Duration.ofSeconds(1));
//更新数据,重新分配
assignment = kafkaConsumer.assignment();
}
HashMap<TopicPartition, Long> topicPartitionLongHashMap = new HashMap<>();
for (TopicPartition topicPartition : assignment) {
topicPartitionLongHashMap.put(topicPartition, System.currentTimeMillis() - 1*24*3600*1000L);
}
Map<TopicPartition, OffsetAndTimestamp> topicPartitionOffsetAndTimestampMap = kafkaConsumer.offsetsForTimes(topicPartitionLongHashMap);
//指定消费的offset
for (TopicPartition partition : assignment) {
OffsetAndTimestamp offsetAndTimestamp = topicPartitionOffsetAndTimestampMap.get(partition);
kafkaConsumer.seek(partition,offsetAndTimestamp.offset());
}
while(true){
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
6. 漏消费&重复消费问题
5.6 生产经验——消费者事务
5.7 生产经验——数据积压( 消费者如何提高吞吐量)
第六章 Kafka-Eagle监控
Kafka-Eagle框架可以监控 Kafka 集群的整体运行情况,在生产环境中经常使用。
Kafka-Eagle 的安装依赖于 MySQL,MySQL 主要用来存储可视化展示的数据。
第七章 Kafka-Kraft模式
kafka2.8.0之后,不再必须依赖zookeeper了。