创作不易,各位看官点赞收藏.
Kafka:是一个开源的分布式事件流平台,用于高性能数据管道、流分析、数据集成和关键任务应用。在一些大数据领域中通常使用 kafka 作为消息队列,在 JavaEE 开发中也有 ActiveMQ、RabbitMQ、RocketMQ 等等消息队列。
消息队列是一种在分布式系统中用于不用组件之间传递和处理数据的通信机制,基于异步通信模式,允许发送者将消息发送到队列中,接受者从队列中获取消息数据并进行处理。
消息队列几种模式:
点对点模式:消息生产者将消息放入队列,每一条消息只能被一个消费者消费,消费者将消息处理完以后会将消息从队列中移除,这种适合单一消息被一个消费者处理的场景。
发布 - 订阅模式:生产者将消息发布到一个主题中,多个消费者可以订阅主题来接收消息,每一个消费者都会收到相同的消息,消息会被保存即使被消费也不会被删除。
应用场景:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gLys6siY-1692610909240)(https://jx-image-storage.oss-cn-hangzhou.aliyuncs.com/image/image-20221125185444191-16917431184751.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bUhYcjMH-1692610909241)(https://jx-image-storage.oss-cn-hangzhou.aliyuncs.com/image/image-20221125190838306-16917431184763.png)]
下载地址https://www.apache.org/dyn/closer.cgi?path=/kafka/3.5.0/kafka_2.13-3.5.0.tgz
# 解压
tar -zxvf kafka_2.13-3.5.0.tgz
cd kafka_2.13-3.5.0
注意:
配置文件 - server.properties :
# 常用配置
# 身份唯一标识,不能重复
broker.id=0
# 日志文件
log.dirs=/tmp/kafka-logs
# 依赖的zookeeper节点地址,一般会加一个kafka节点
zookeeper.connect=localhost:2181/kafka
# 与zookeeper连接超时时间
zookeeper.connection.timeout.ms=18000
启动 kafka 服务:
# 先启动一个 kafka 自带的 zookeeper,-daemon 后台运行
bin/zookeeper-server-start.sh -daemon config/zookeeper.properties
# 启动 kafka 服务
bin/kafka-server-start.sh -daemon config/server.properties
配置文件:修改
config/KRaft/server.properties
文件。
# 生成集群UUID(只执行一次)
KAFKA_CLUSTER_ID="$(bin/kafka-storage.sh random-uuid)"
# 格式化日志目录(只执行一次)
bin/kafka-storage.sh format -t $KAFKA_CLUSTER_ID -c config/kraft/server.properties
# 启动kafka服务
bin/kafka-server-start.sh -daemon config/kraft/server.properties
# 停止服务
bin/kafka-server-stop.sh
脚本简单使用:
# 创建一个主题
bin/kafka-topics.sh --create --topic quickstart-events --bootstrap-server localhost:9092
# 查看某个主题参数信息
bin/kafka-topics.sh --describe --topic quickstart-events --bootstrap-server localhost:9092
# 向主题中写入消息
bin/kafka-console-producer.sh --topic quickstart-events --bootstrap-server localhost:9092
# 阅读消息
bin/kafka-console-consumer.sh --topic quickstart-events --from-beginning --bootstrap-server localhost:9092
KRaft 方式搭建集群:
# 每一个节点的唯一标识id,不能重复
node.id=0
# 集群中每个 Controller IP地址和端口号
controller.quorum.voters=0@192.168.32.135:9093,[email protected]:9093,[email protected]
# 内网监听ip地址
listeners=PLAINTEXT://192.168.32.137:9092,CONTROLLER://192.168.32.137:9093
# 外网监听ip地址
advertised.listeners=PLAINTEXT://192.168.32.137:9092
# 生成uuid,并把uuid记录下来
./bin/kafka-storage.sh random-uuid
gfCReVjpRqWi3RzL-sg7Lw
# -t 的参数就是生成的唯一集群id,每个节点都要根据这个id去执行命令
./bin/kafka-storage.sh format -t gfCReVjpRqWi3RzL-sg7Lw -c ./config/kraft/server.properties
# 启动 kafka 服务
./bin/kafka-server-start.sh -daemon ./config/kraft/server.properties
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YZp3ESQp-1692610909242)(https://jx-image-storage.oss-cn-hangzhou.aliyuncs.com/image/image-20230814170630024.png)]
导入依赖:
<dependency>
<groupId>org.apache.kafkagroupId>
<artifactId>kafka-clientsartifactId>
<version>3.5.1version>
dependency>
代码编写:
public static void main(String[] args) throws ExecutionException, InterruptedException {
Properties properties = new Properties();
// Kafka服务端的主机名和端口号
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.32.135:9092,192.168.32.136:9092,192.168.32.137:9092");
// 等待所有副本节点的应答
properties.put(ProducerConfig.ACKS_CONFIG, "0");
// 消息发送最大尝试次数,默认一直重试
properties.put(ProducerConfig.RETRIES_CONFIG, 0);
// 一批消息处理大小,默认16KB
properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
// 请求延时,默认0
properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);
// 发送缓存区内存大小,默认32MB
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG,33554432);
// key序列化
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// value序列化
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// kafka 生产者对象
KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
// 构建消息
ProducerRecord<String, String> message = new ProducerRecord<>("quickstart-events", "key1", "value1");
/**
* 有两种消息发送方式:
* 1:异步方式:send()方法返回一个异步Future对象,
* 2:回调异步方式:可以在构建参数时设置一个回调方法
* 3:同步发送:根据send()返回的future对象调用其get()方法进行阻塞主线程
*/
// Future send = producer.send(message); // 异步方式
producer.send(message, new Callback() { // 回调异步
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
System.out.println("消息发送成功");
}
});
// 同步发送,get()方法会阻塞线程,知道上一批数据全部发送成功,返回结果包含了消息主题、分区等信息
RecordMetadata metadata = producer.send(message).get();
producer.close();
}
注意:主线程会先将数据发送到缓冲区,然后由 sender 线程进行异发送,而同步发送是一批数据发送到缓冲区由 sender 线程发送到 kafka 集群才会允许下一批数据进行发送。
消息分区:将同一个主题的消息数据分区数据到不同的 broker 机器上。
分区策略:生产者写入消息到 topic,Kafka 将依据不同的策略将数据分配到不同的分区中。
注意:如果发送的分区不存在,则客户端一直会进行等待连接,阻塞线程所有线程。
自定义分区器:可以根据业务需求自定义分区器,实现 Partitioner 接口重写 partition() 方法。
// 自定义分区器
public class CustomerPartitioner implements Partitioner {
/**
* 重写对应的分区策略
* @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) {
if (key == null){
return 0;
}else {
return 1;
}
}
@Override
public void close() {
}
@Override
public void configure(Map<String, ?> configs) {
}
}
// 配置对象配置对应的自定义分区器
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, CustomerPartitioner.class.getName());
提高生产者吞吐量:主要是通过配置属性,结合实际生产环境调整配置。
// 一批消息处理大小,默认16KB
properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
// 请求延时,默认0
properties.put(ProducerConfig.LINGER_MS_CONFIG, 5);
// 发送缓存区内存大小,默认32MB
properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG,33554432);
// 设置数据压缩方式
properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
数据可靠性:kafka 生产者在生产数据时有三种 ACK 应答级别,不同应答数据可靠性不一样,默认级别是 -1(all)。
在同步副本数据时,如果某个副本无法应答 Leader,Leader 也不会应答生产者。但是 Leader 维护了 ISR 一个动态副本队列,如果超过默认 30s 没有副本心跳就会把对应副本剔除队列,这样就不会长期去等待无法同步的副本。
最佳实践方式:(ACK 级别为 -1) + (分区副本 >= 2) + (ISR 中应答最小副本 >= 2)。
// ACK 应答级别
properties.put(ProducerConfig.ACKS_CONFIG, "-1");
// ISR 副本超时时间,默认30分钟
properties.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, 1000 * 60 * 30);
数据重复问题:
幂等性: 指生产者向 Broker 发送多少条重复数据,Broker 都只会持久化一条数据。 重复数据判断依据:PID(会话ID)、Partition(分区号)、SeqNumber(自增序列号),三者都不相同则表示不同数据。
/**
* 开启幂等性:默认开启
* 开启前提条件:max.in.flight.requests.per.connection等待请求数小于等于5
* retries:大于等于0
* ACK:-1
*/
properties.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
生产者事务:开启事务必须先开启幂等性,事务是基于幂等性的。
try {
// 开启事务
producer.beginTransaction();
for (int i=0;i<10;i++){
ProducerRecord<String, String> message = new ProducerRecord<>("topic-3",UUID.randomUUID().toString(), UUID.randomUUID().toString());
producer.send(message);
}
// 提交事务
producer.commitTransaction();
}catch (Exception e){
// 回滚事务,如果数据发送出现异常就会回滚所有发送的数据
producer.abortTransaction();
e.printStackTrace();
}finally {
producer.close();
}
注意:生产者在使用事务前需要指定自定义唯一的 transaction-id,在第一次使用事务会初始化一个 __transaction_state 主题数据,默认有 50 个分区,这里面存放着对事务数据的存放。
数据有序性:
单分区有序:由于在 Sender 线程中最多缓存 5 个请求,第一个请求没有应答前可以发送第二个请求,就可能出现第一个请求失败后重试导致数据乱序,但是在 Kafka1.0 之后会缓存生产者发送的最近 5 个请求的元数据,会根据幂等性序列号进行排序然后再进行数据持久化。
/**
* 保证单分区数据有序的前提条件:(也可以将最大请求数设置为1,只有一个请求缓存)
*/
properties.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
// sender 线程缓存最大请求数
properties.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, 5);
Zookeeper 模式:在 kafka2.8.0 之前是需要依赖 zookeeper 组件,由 zookeeper 负责集群元数据管理、控制器的选举等。
KRaft 模式:在 KRaft 中,一部分 broker 节点被指定为控制器,这些 Controller 提供 Zookeeper 的共识服务,集群的所有元数据以主题方式存储在 kafka 中。
注意:每一个 Broker 节点既可以充当 Broker,也可以充当 Controller 角色,两者也可以同时充当。
新建节点:修改新增节点配置,启动节点服务到集群中。
# -t 的参数就是生成的唯一集群id,每个节点都要根据这个id去执行命令
./bin/kafka-storage.sh format -t gfCReVjpRqWi3RzL-sg7Lw -c ./config/kraft/server.properties
# 启动 kafka 服务
./bin/kafka-server-start.sh -daemon ./config/kraft/server.properties
新建负载均衡计划:即使新增了节点,但是以前数据依然不会对新节点做负载均衡,需要我们自己去对旧数据做负载均衡。
vim topic-to-move.json
{
"version": 1, // 版本号固定1
"topics": [ // 需要做负载均衡的主题名称
{
"topic": "topic-1"
},
{
"topic": "topic-2"
},
{
"topic": "topic-3"
}
]
}
# --bootstrap-server:连接服务,--topic-to-move-json-file:指定负载均衡计划文件,--broker-list "0,1,2,3":指定负载均衡的broker的id
./bin/kafka-reassign-partitions.sh --bootstrap-server 192.168.32.135:9092 --topics-to-move-json-file ./json/topic-to-move.json --broker-list "0,1,2,3" --generate
vim increase-replication-factor.json
,将生成的计划复制进去,然后执行计划。# 执行计划命令
./bin/kafka-reassign-partitions.sh --bootstrap-server 192.168.32.135:9092 --reassignment-json-file ./json/increase-replication-factor.json --execute
# 验证是否执行成功
./bin/kafka-reassign-partitions.sh --bootstrap-server 192.168.32.135:9092 --reassignment-json-file ./json/increase-replication-factor.json --verify
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HIqaBd3l-1692610909244)(https://jx-image-storage.oss-cn-hangzhou.aliyuncs.com/image/image-20230816113532281.png)]
节点下线:节点下线只需要将在均衡计划中主题对应的节点去掉需要下线节点 id,然后执行对应计划就可以,然后将节点关机。
Kafka 副本:用于提高数据的可靠性,默认副本 1 个,生产环境一般配置 2 个。太多副本会增加磁盘存储空间也会增加网络上数据传输,降低效率。Kafka 中副本分为 Leader 副本和 Follower 副本,但是生产者和消费者都只会去操作 Leader 副本,Follower 副本只是用于存放备份数据。
AR = ISR + OSR
Leader 选举:当 Leader 宕机以后,Follower 会根据一定规则选举出新的 Leader,在集群中由某个 Controller 节点用于选举新的 Leader。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dAYoxwlS-1692610909244)(https://jx-image-storage.oss-cn-hangzhou.aliyuncs.com/image/image-20230816175330559.png)]
Broker 故障:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RlGuLYGG-1692610909244)(https://jx-image-storage.oss-cn-hangzhou.aliyuncs.com/image/image-20230817093910619.png)]
Follower 故障: 首先会被踢出 ISR 队列,其它正常的 Broker 继续同步数据。当故障 Follower 重新上线后,它会读取磁盘记录的上次 HW 记录,并将 log 数据高于 HW 的数据截取掉,然后从 HW 部分开始向后继续从 Leader 同步数据,当数据同步到所有副本的 HW 水平就可以重新加入 ISR 队列。
Leader 故障: 首先会被踢出 ISR 队列,然后选举出新的 Leader,为保证数据一致性,其它的 Follower 会将高于 HW 的数据裁剪掉,然后和新的 Leader 进行同步。
注意:这只能保证副之间数据一致性,但是不能保证数据不丢失或者不重复(旧 Leader 可能存在还未同步的数据)。
手动调整 Broker 副本:kafka 默认副本是均分分配在每个 Broker 上,可能出现指定副本需求。
vim increase-replication-factor.json
,将 topic-1 主题的副本放在 0、1 节点上。{
"version":1,
"partitions":[
{
"topic":"topic-1",
"partition":0,
"replicas":[
0,
1
]
},
{
"topic":"topic-1",
"partition":1,
"replicas":[
0,
1
]
},
{
"topic":"topic-1",
"partition":2,
"replicas":[
1,
0
]
},
{
"topic":"topic-1",
"partition":3,
"replicas":[
1,
0
]
}
]
}
# 执行
./bin/kafka-reassign-partitions.sh --bootstrap-server 192.168.32.135:9092 --reassignment-json-file ./json/increase-replication-factor.json --execute
# 验证
./bin/kafka-reassign-partitions.sh --bootstrap-server 192.168.32.135:9092 --reassignment-json-file ./json/increase-replication-factor.json --verify
Leader Partition 自动平衡:正常情况 kafka 会将分区均匀分配到每一个 Broker 上。当某个 Leader 宕机,新 Leader 可能会集中在其它 几台 Broker 上,这可能造成负载不均衡的情况,但是生产中一般会关闭这个功能,因为触发自动平衡很耗性能。
auto.leader.rebalance.enable
:是否开启分区自动平衡,默认开启。leader.imbalance.per.broker.percentage
:默认值是10%,broker 中允许 Leader 不平衡比例,如果操作这个比例就会触发自动平衡。leader.imbalance.check.interval.seconds
:默认值 300s,检查 Leader 是否平衡的间隔时间。增加分区副本:创建副本分配文件:vim increase-replication-factor.json
,将 topic-1 主题的副本进行重新规划,然后执行计划。
Broker 数据存储:一个 topic 可以分在多个 partition 上进行存储,一个 topic 下的分区有一个topic名-partition号的 log 文件夹,在这个文件夹下存储着生产者产生的数据,生产者生产的数据会不断追加在 log 文件末尾。
为了防止 log 文件过大导致数据定位效率低下,kafka 采取分片、索引机制。将 log 分片成一个个 Segment,每个Segment 默认大小是 1GB,每个 Segment 由 .index、.log、.timeindex
以及其它文件组成。(文件名称以当前 Segment 的第一条消息的 offset 命名)
.index
:作为稀疏索引,每往 log 文件中写入 4KB 数据,就会向 index 文件中添加一条索引。.log
:存放数据文件。.timeindex
:时间戳索引文件,默认 kafka 数据保留7天,会根据这个文件去清除数据。数据删除策略:kafka 默认数据保留7天,可以设置对应参数修改数据删除时间。
log.retention.hours
:单位小时,默认168小时(7天),优先级最低。log.retention.minutes
:单位分钟,如果设置这个值,小时单位就失效。log.retention.ms
:单位毫秒,如果设置这个值,分钟单位失效。log.retention.check.interval.ms
:设置检查周期,默认5分钟检查数据是否过期。log.cleanup.policy=delete/compact
:设置数据的删除策略。delete:将过期数据删除。
compact:数据压缩,将相同 key 的数据只保留最后一个版本数据,压缩后的 offset 不是连续的,如果不存在对应 offset 的数据就会拿去下一个 offset 的数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N0W88klu-1692610909246)(https://jx-image-storage.oss-cn-hangzhou.aliyuncs.com/image/image-20230817161834876.png)]
Kafka 高效读写:
.index
文件中存放了数据索引,可以快速定位数据。.log
文件写入数据是追加数据到文件末端,顺序写数据速度快。消费方式:
消费者工作流程:
offset:每个消费者对于每个分区都有一个消息偏移量,记录消费者消费到哪个位置了,这个 offset 数据会被持久化到 kafka 的 __consumer_offsets
这个主题中,即使消费者重启,也会从下一个消息进行消费。
消费者组:由多个 consumer 组成,当消费者的 groupId 相同时这些消费者就属于同一个消费者组。
消费者组初始化:每个 broker 节点都有一个 coordinator 协调器组件,辅助实现消费者组的初始化和分区分配,指定消费者组中消费者应该消费哪个分区。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N5lznpgr-1692610909246)(https://jx-image-storage.oss-cn-hangzhou.aliyuncs.com/image/image-20230818104642229.png)]
注意:所有消费者都和协调器保存 3s 的心跳包,也会有一个连接超时时间,默认 45 s 超过 45s 没有心跳包,那么这个消费者就会被移除,就会触发自动平衡重新分配任务。如果某个消费者某次处理数据时间超过 5 分钟,也会触发自动平衡,将任务交给其它消费者。
消费者消费流程:
消费者消费一个主题:
public static void main(String[] args) {
Properties properties = new Properties();
// 连接集群
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.32.135:9092,192.168.32.136:9092");
// 设置消费者的消费组id
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test2");
// 设置key和value的反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
ArrayList<String> topics = new ArrayList<>();
topics.add("topic-1");
// 设置订阅的主题
consumer.subscribe(topics);
// 进行拉取数据
while (true){
// 间隔多少秒拉取一次数据
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
// 拉取的数据
for (ConsumerRecord<String, String> record : records) {
System.out.println(record);
}
}
}
消费某一个分区:
// 消费某个主题下的某个分区
List<TopicPartition> topicPartitions = new ArrayList<>();
// 指定主题和分区
TopicPartition partition = new TopicPartition("topic-1", 0);
topicPartitions.add(partition);
consumer.assign(topicPartitions);
// 执行消费
while (true){
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> record : records) {
System.out.println(record);
}
}
消费分区策略:在消费者 Leader 分配消费任务时,会根据对应的分配策略分配任务。kafka 中主要分配策略:Range、RoundRobin、Sticky、CooperativeSticky,默认使用
Range + CooperativeSticky
,可以使用组合分配策略。
// 设置消费分区策略
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, RangeAssignor.class.getName() + "," + CooperativeStickyAssignor.class.getName());
Range:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NM0FNFcW-1692610909247)(https://jx-image-storage.oss-cn-hangzhou.aliyuncs.com/image/image-20230818145123076.png)]
注意:如果消费者组去消费多个主题,就可能存在数据倾斜问题,每个主题多出来的分区就会全部由前面的消费者进行消费。
RoundRobin:轮询消费,所有主题的所有分区和所有消费者进行排序,然后针对分区轮询指定消费者进行消费。
Sticky:粘性分配,尽量均衡分配分区,与 Range 相似,但是不是按照顺序进行分配分区,而是随机将分区分配给消费者。
消费者 offset :表示消费者消费分区已经消费的位置,0.9版本之前,offset 是存放在 Zookeeper 中,o.9 版本之后是存放在 kafka 的 __consumer_offset 这个主题下的。主题的 key:groupId + tpoic + 分区号,value:offset 值,每隔一段时间就会将这个 topic 进行 compact 数据压缩。
自动 offset 维护:kafka 提供了自动提交 offset 功能,每当消费者消费数据,消费者可以自动向 __consumer_offset 主题提交 offset 数据。
enbale.auto.commit
:是否开启自动提交 offset 功能,默认是 true。auto.commit.interval.ms
:自动提交 offset 的时间间隔,默认是 5s,单位是毫秒。// 设置是否自动提交 offset
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, true);
// 设置自动提交的时间
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 1000);
手动提交 offset 维护:自动提交 offset 不能掌握提交的时间,有时候需要手动去提交 offset。
// 关闭自动提交
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, false);
// 进行拉取数据
while (true){
// 间隔多少秒拉取一次数据
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
// 拉取的数据
for (ConsumerRecord<String, String> record : records) {
System.out.println(record);
}
// 提交 offset
consumer.commitSync(); // 同步提交
consumer.commitAsync(); // 异步提交,可以指定异步提交后的回调方法
}
指定 offset 消费:
指定 offset 进行消费:直接指定 offset 不行,需要等消费者分区完成后再指定 offset 才会生效。
// 设置订阅的主题,设置消费者分区事件
consumer.subscribe(topics, new ConsumerRebalanceListener() {
// 消费者分区前,例如提交偏移量、释放资源
@Override
public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
}
// 设置偏移量、初始化资源
@Override
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
// 指定消费的偏移量
for (TopicPartition partition : partitions) {
// 手动指定消费者从分区哪个 offset 开始消费
consumer.seek(partition, 100);
}
}
});
// 进行拉取数据
while (true){
// 间隔多少秒拉取一次数据
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
// 拉取的数据
for (ConsumerRecord<String, String> record : records) {
System.out.println(record);
}
}
按照时间消费:指定开始消费的时间,可以根据时间去获取 offset 值。
Map<TopicPartition, Long> timeForOffset = new HashMap<>();
// 设置消费者分区事件
consumer.subscribe(topics, new ConsumerRebalanceListener() {
// 消费者分区前,例如提交偏移量、释放资源
@Override
public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
}
// 设置偏移量、初始化资源
@Override
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
// 指定消费的偏移量
for (TopicPartition partition : partitions) {
// 设置对应消费的时间戳,key:指定分区,value:消费开始位置的时间戳,从当前时间前一天的消息进行消费
timeForOffset.put(partition, System.currentTimeMillis() - 1000 * 60 * 60 * 24 * 10);
}
// 将时间转换成 offset
Map<TopicPartition, OffsetAndTimestamp> offsets = consumer.offsetsForTimes(timeForOffset);
// 将转换后的 offset 指定给消费者
for (TopicPartition partition : partitions) {
OffsetAndTimestamp offsetAndTimestamp = offsets.get(partition);
consumer.seek(partition, offsetAndTimestamp.offset());
}
}
});
// 进行拉取数据
while (true){
// 间隔多少秒拉取一次数据
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
// 拉取的数据
for (ConsumerRecord<String, String> record : records) {
System.out.println(record);
}
}
重复消费:消费者消费消息后但是没有到自动提交时间,这时消费者宕机后面重启后就会从上一次自动提交的位置进行消费,就会出现重复消费。
漏消费:设置为手动提交时,当消费者拉取数据后就手动提交 offset,但是消费者在进行处理数据时出现宕机,并没有正常消费数据,但是已经手动提交了 offset 下一次重启就会跳过没有正常消费的数据。
数据积压:当 kafka 中数据过多,消费者端不能够及时消费,导致数据时间过期会删除数据。例如:kafka 有三天数据需要消费,但是消费者消费这些数据需要4天,有些数据消费不及时就会丢失。
fetch.min.bytes
单次拉取大小,提高拉取效率。max.poll.records
单次最多拉取消息条数,默认 500 条并且对应修改拉取的最大大小。# 修改启动命令
vim ./bin/kafka-server-stop.sh
# 修改对应内存
# 内存参数
export KAFKA_ HEAP_OPTS="-server -Xms2G -Xmx2G --XX:PermSize=128m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=8 -XX:ConcGCThreads=5 -XX:InitiatingHeapOccupancyPercent=70"
# Eagle 监控端口
export JMX_PORT="9999"
# java 环境变量
vi /etc/profile
export JAVA_HOME=/usr/java/jdk1.8
export PATH=$PATH:$JAVA_HOME/bin
# EFAK 环境变量
vi /etc/profile
export KE_HOME=/data/soft/new/efak
export PATH=$PATH:$KE_HOME/bin
vim ./config/system-config.properties
。# Zookeeper 配置方式
efak.zk.cluster.alias=cluster2
cluster2.zk.list=xdn10:2181,xdn11:2181,xdn12:2181
# 端口
efak.webui.port=8048
######################################
# kafka mysql jdbc driver address
######################################
efak.driver=com.mysql.cj.jdbc.Driver
efak.url=jdbc:mysql://192.168.32.143:3306/ke?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
efak.username=root
efak.password=xxxx
cluster1.efak.offset.storage=kafka
./bin/ke.sh start
,然后通过 ip地址:端口可以直接访问。注意:kafka-eagle 暂时只支持 kafka 的 Zookeeper 方式,不支持 Kraft 协议的方式。
Docker 安装 kafka-ui:
# 安装命令
docker run -p 9876:8080 \
--name kafka-ui \
-e KAFKA_CLUSTERS_0_NAME=kafka-1 \
-e KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=192.168.32.135:9092,192.168.32.136:9092 \
-e TZ=Asia/Shanghai \
-e SERVER_SERVLET_CONTEXT_PATH="/" \
-e AUTH_TYPE="LOGIN_FORM" \
-e SPRING_SECURITY_USER_NAME=admin \
-e SPRING_SECURITY_USER_PASSWORD="admin" \
-e LANG=C.UTF-8 \
-d provectuslabs/kafka-ui:latest
导入依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>2.7.13version>
dependency>
<dependency>
<groupId>org.springframework.kafkagroupId>
<artifactId>spring-kafkaartifactId>
<version>2.8.0version>
dependency>
修改配置:
spring:
# kafka 相关配置
kafka:
bootstrap-servers: 192.168.32.135:9092,192.168.32.136:9092,192.168.32.136:9092
# 生产者配置
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
retries: 3
acks: -1
compression-type: snappy
buffer-memory: 64MB
batch-size: 32KB
编写生产者代码:
// 注入kafka
@Resource
private KafkaTemplate<String, String> kafkaTemplate;
@GetMapping("/test1")
public void produce(String msg){
for (int i=0;i<1000;i++){
kafkaTemplate.send("topic-boot", UUID.randomUUID().toString() + i, UUID.randomUUID().toString());
}
}
修改配置:
spring:
# kafka 相关配置
kafka:
bootstrap-servers: 192.168.32.135:9092,192.168.32.136:9092,192.168.32.137:9092
# 消费者配置
consumer:
group-id: test
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
编写代码:
// 消费者进行消费,id:全局唯一标识
@KafkaListener(id = "consumer1", groupId = "test-1", topics = {"topic-boot","topic-1"})
public void consumer(ConsumerRecord<?, ?> record){
// msg:接收到的数据
System.out.println(record);
}