本文主要学记录了kafka3.x的一些知识。
Kafka是分布式的基于发布/订阅模式的消息队列,主要应用于大数据实时处理领域。
一个生产者对应一个消费者,消费者主动拉取消息,确认收到消息之后,删除对应的消息。
一个生产者可以对应多个消费者,多个消费者之间相互独立,消息按照主题(topic)分类,消费者主动拉取消息后不删除消息,其他消费者依旧可以再次消费这个消息。
架构特点:
下载kafka-3.1.0压缩包,解压后即可完成安装,修改以下配置文件,修改后,分发到node-01,node-02,node-03三台机器上,即可完成kafka集群的安装:
// kafka每个broker的身份标识,必须唯一
broker.id=2
// kafka数据存放地址
log.dirs=/orkasgb/data/kafka
// 指定zookeeper的地址
zookeeper.connect=node-01:2181,node-02:2181,node-03:2181
// 安装好集群之后,分别执行以下命令启动kafka
$KAFKA_HOME/bin/kafka-server-start.sh -daemon $KAFKA_HOME/config/server.properties
基本参数 | 说明 |
---|---|
–bootstrap-server |
指定kafka集群地址,可以有多个,必选,格式为:node-01:9092,node-02:9092 |
–topic |
指定链接的topic(topic) |
–create | 创建主题 |
–delete | 删除主题 |
–alter | 修改主题,一般只能修改分区数 |
–list | 列出所有的主题 |
–describe | 描述当前主题的详细信息 |
–partitions |
创建主题时指定分区数,必选 |
–replication-factor |
创建主题时指定副本数,必选 |
–config |
指定配置文件位置 |
# 创建一个topic-orkasgb-test主题,分区数为2,副本数为3
[root@node-01 ~]# kafka-topics.sh --bootstrap-server node-01:9092,node-02:9092 --topic topic-orkasgb-test --create --partitions 2 --replication-factor 3
Created topic topic-orkasgb-test.
[root@node-01 ~]#
# 查看当前kafka集群中的topic
[root@node-01 ~]# kafka-topics.sh --bootstrap-server node-01:9092,node-02:9092 --list
__consumer_offsets
topic-orkasgb-test
[root@node-01 ~]#
# 查看topic-orkasgb-test主题的详细信息
[root@node-01 ~]# kafka-topics.sh --bootstrap-server node-01:9092,node-02:9092 --topic topic-orkasgb-test --describe
Topic: topic-orkasgb-test TopicId: k6OqKVFcQgCFrahkJOyWbQ PartitionCount: 2 ReplicationFactor: 3 Configs: segment.bytes=1073741824
Topic: topic-orkasgb-test Partition: 0 Leader: 2 Replicas: 2,1,3 Isr: 2,1,3
Topic: topic-orkasgb-test Partition: 1 Leader: 3 Replicas: 3,2,1 Isr: 3,2,1
[root@node-01 ~]#
基本参数 | 说明 |
---|---|
–bootstrap-server |
指定kafka集群地址,可以有多个,必选,格式为:node-01:9092,node-02:9092 |
–topic |
指定链接的topic(topic) |
–producer.config |
指定配置文件位置 |
# 创建一个消费者,将数据发送到topic-orkasgb-test主题中
[root@node-03 bin]# kafka-console-producer.sh --bootstrap-server node-01:9092,node-02:9092 --topic topic-orkasgb-test --producer.config /orkasgb/software/kafka-3.1.0/config/producer.properties
>hello kafka
>
基本参数 | 说明 |
---|---|
–bootstrap-server |
指定kafka集群地址,可以有多个,必选,格式为:node-01:9092,node-02:9092 |
–topic |
指定链接的topic(topic) |
–consumer.config |
指定配置文件位置 |
# 创建一个消费者,用于消费发往topic-orkasgb-test主题中的数据
[root@node-01 ~]# kafka-console-consumer.sh --bootstrap-server node-01:9092,node-02:9092 --topic topic-orkasgb-test --consumer.config /orkasgb/software/kafka-3.1.0/config/consumer.properties
hello kafka
# 列出整个集群中的消费组
[root@node-02 bin]# kafka-consumer-groups.sh --bootstrap-server node-01:9092,node-02:9092 --list
test-consumer-group
[root@node-02 bin]#
# 查看test-consumer-group属组中的主题消费情况
[root@node-02 bin]# kafka-consumer-groups.sh --bootstrap-server node-01:9092,node-02:9092 --group test-consumer-group --describe
GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG CONSUMER-ID HOST CLIENT-ID
test-consumer-group topic-orkasgb-test 0 0 0 0 console-consumer-b234bb08-4374-498d-9cfe-192de9f9cdd3 /192.168.137.223 console-consumer
test-consumer-group topic-orkasgb-test 1 1 1 0 console-consumer-b234bb08-4374-498d-9cfe-192de9f9cdd3 /192.168.137.223 console-consumer
[root@node-02 bin]#
异步发送消息代码:
@Test
public void producter() throws ExecutionException, InterruptedException {
// 创建生产者配置文件
HashMap<String, Object> config = new HashMap<>();
// 集群地址
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "node-01:9092,node-02:9092,node-03:9092");
// KEY的序列化器
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
// VALUE的序列化器
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
// 创建Producter对象
DefaultKafkaProducerFactory<String, String> producerFactory = new DefaultKafkaProducerFactory<>(config);
Producer<String, String> producer = producerFactory.createProducer();
for (int i = 0; i < 5; i++) {
// 同步发送数据
// RecordMetadata recordMetadata = producer.send(new ProducerRecord<>("topic-orkasgb-test", "test" + i)).get();
// System.out.println(recordMetadata.toString());
// 同步发送数据
// RecordMetadata recordMetadata = producer.send(new ProducerRecord<>("topic-orkasgb-test", "test" + i)).get();
// System.out.println(recordMetadata.toString());
// 异步发送数据
Future<RecordMetadata> send = producer.send(new ProducerRecord<>("topic-orkasgb-test", "哈哈哈哈" + i));
producer.flush();
System.out.println(send.toString());
// 带回调函数的异步发送数据
// Future send = producer.send(new ProducerRecord<>("topic-orkasgb-test", "哈哈哈哈" + i), new Callback() {
// @Override
// public void onCompletion(RecordMetadata metadata, Exception exception) {
//
// }
// });
// System.out.println(send.toString());
}
producer.close();
}
kafka生产者存在默认的分区策略,如果记录中指定了分区,则使用指定的分区策略,如果未指定分区但是在发送消息时指定了key,则根据key的hash选择分区,如果未指定分区或key,则选择在批处理已满时才更改的粘性分区策略。
自定义分区器:
// 在生产者配置文件中指定自定义分区器
config.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, UserPartition.class);
/**
* 自定义分区器,继承Partitioner类,重写partition方法即可。
*/
public class UserPartition implements Partitioner {
@Override
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
// 如果消息中包含了orkasgb字符串,就将这条消息发送到0好分区,否则就发送到1号分区
return StringUtils.contains(value.toString(), "orkasgb") ? 0 : 1;
}
@Override
public void close() {
}
@Override
public void configure(Map<String, ?> configs) {
}
}
幂等性是指,不管producter向broker发送多少条重复数据,broker只会持久化一条。幂等性判断数据重复的标准为以
服役一台新的节点:
{
"topics" : [
{"topic" : "xxxx"}
],
"version" : 1
}
kafka-reassign-partitions.sh --bootstrap-server node-01:9092 --topics-to-move-json-file xxxx.json --broker-list "0,1,2,3" --generate
kafka-reassign-partitions.sh --bootstrap-server node-01:9092 --reassignment-json-file xx.json --execute
kafka-reassign-partitions.sh --bootstrap-server node-01:9092 --reassignment-json-file xx.json --verify
退役一台旧节点:
思想,将旧节点上的数据转移到其他的节点上后删除该节点即可,方法类似于服役新节点的方式,在生成负载均衡计划的时候,将–broker-list "0,1,2,3"中要退役的的broker.id取消掉,其他步骤按照顺序依次执行即可将数据到其他的节点上。
Follower故障处理步骤:
Leader故障处理步骤:
kafka的数据存储是按照分区进行存储的,每个分区目录的命名格式为topic+分区号,每个分区目录下有三个类型的文件,.log,.index,.timeindex。因为kafka生产端为数据是不断的追加到.log文件中,所以.index用来记录数据的索引,.timeindex文件主要用来删除过期数据。kafka默认数据过期时间为7天。
.index为稀疏索引,文件中存储的是相对offset,.log文件中每写入大约4kb(log.index.interval.bytes)的数据,就会往.index文件中写入一条索引,这样能确保文件占用空间不会太大。
# 查看kafka的.log文件
[root@node-02 topic-orkasgb-test-1]# kafka-run-class.sh kafka.tools.DumpLogSegments --files ./00000000000000000000.log
Dumping ./00000000000000000000.log
Starting offset: 0
baseOffset: 0 lastOffset: 0 count: 1 baseSequence: -1 lastSequence: -1 producerId: -1 producerEpoch: -1 partitionLeaderEpoch: 0 isTransactional: false isControl: false position: 0 CreateTime: 1649067089350 size: 79 magic: 2 compresscodec: none crc: 384684249 isvalid: true
[root@node-02 topic-orkasgb-test-1]#
{
"partitions" : [
{"topic" : "xxx","partition": 0,"replicas": [1,3]},
{"topic" : "xxx","partition": 1,"replicas": [2,3]},
{"topic" : "xxx","partition": 2,"replicas": [1,2]}
],
"version" : 1
}
kafka-reassign-partitions.sh --bootstrap-server node-01:9092 --reassignment-json-file xxxx.json --execute
kafka-reassign-partitions.sh --bootstrap-server node-01:9092 --reassignment-json-file xxxx.json --verify
kafka中的文件清除,由以下参数控制:
日志清楚策略:
delete删除日志:删除过期的数据,log.cleanup.policy = delete。
假如一个segment中的日志有一部分数据过期了,一部分没有过期(记录中最大时间戳的那部分),那么这个segment是不会被删除掉的。
compact:日志压缩,log.cleanup.policy = compact。
对于相同的key,只保留最后一个版本。压缩后会存在offset不是连续的,如果要找的某个offset不存在,那么就会返回一个最近的比它大的offset,然从这个offset开始消费。
kafka提供了四种消费者分配分区策略(partition.assignment.strategy),分别是:
kafka0.9版本之前的offset存放在zookeeper中,kafka0.9版本之后的offset存放在kafka系统主题(__consumer_offset)中,在consumer.properties中设置参数exclude.internal.topic=false才可以查看系统主题,默认为true。__consumer_offset中是以key-value的形式存放数据,key就是group.id+topic+分区号,value就是当前主题所在分区号的消费到的offset。每日过一段时间,kafka会对主题中的内容进行compact,所以consumer_offset中保留的就是最新的offset。
# 查看系统主题__consumer_offset中的主题信息
kafka-console-consumer.sh --bootstrap-server node-01:9092,node-02:9092 --topic __consumer_offset --consumer.config /orkasgb/software/kafka-3.1.0/config/consumer.properties --formatter "kafka.coordinator.group.GroupMetadataManager\$OffsetsMessageFormatter" --from-beginning
kafka中提供了auto.offset.reset=earliest | latest | none三种offset消费方式,默认latest。
将时间转化为对应的offset,即可做到按照时间消费。
要避免漏消费和重复消费,那么就需要使用事务的方式,并且要做到整个消费到下游的所有链路上都支持事务。比如消费到存储数据到mysql,都必须支持事务才可以做到精确一次性消费。
**问题分析:**当数据发送之后,因为不用等待leader应答就立即返回,假如数据发送到leader之后,leader还未处理数据就发生故障,那么此时数据已经全部丢失。
**数据可靠性分析:**数据丢失,但是效率最高。
**问题分析:**当数据发送之后,leader应答成功后立即返回,假如数据发送到leader之后,leader还未处理数据就发生故障,此时根据内部的leader选择策略,某一个follower称为新的leader,生产者会立即和新的leader交互,但是因为之前的leader应答成功,producter会认为之前的数据已经发送完成,不会再次发送,那么此时数据已经全部丢失。
当数据发送之后,leade和ISR队列中的所有follower都已经处理完数据,正准备acks应答时,leader发生故障,此时根据内部的leader选择策略,某一个follower称为新的leader,生产者会立即和新的leader交互,但是因为之前的leader没有应答成功,producter会认为之前的数据没有发送完成,继续再次发送,那么此时数据已经重复处理。
**数据可靠性分析:**数据丢失,数据重复。
**问题分析:**当数据发送之后,producter再等待leader和其他follower应答,但是其中一个follower发生故障,导致无法acks应答,此时就会发生producter发送完数据却无法返回的情况。kafka为解决这种问题,让leader维护了一个动态的leader和follower的同步信息集合,形式为:(leader:1,isr:[1,2,3])。如果某一个follower超过30s(replica.lag.time.max.ms,默认30s)没有和leader进行通信,那么leader就认为该follower已经发生故障,此时就会将该follower从ISR队列中移除。
**数据可靠性分析:**如果副本数为1,任然存在数据丢失。
至少一次 = (ACKS级别=-1) + (分区副本数>=2)+ (ISR队列中至少要保障存在两个节点信息)。
精确一次 = 幂等性 + 至少一次。
{
"partitions" : [
{"topic" : "xxx","partition": 0,"replicas": [1,3]},
{"topic" : "xxx","partition": 1,"replicas": [2,3]},
{"topic" : "xxx","partition": 2,"replicas": [1,2]}
],
"version" : 1
}
kafka-reassign-partitions.sh --bootstrap-server node-01:9092 --reassignment-json-file xxxx.json --execute
kafka-reassign-partitions.sh --bootstrap-server node-01:9092 --reassignment-json-file xxxx.json --verify