kafka入门(千峰)学习笔记

前言

视频链接

https://www.bilibili.com/video/BV1Xy4y1G7zA

一、kafka介绍

1、为什么使用消息队列

实现异步通信

2、消息队列的流派

消息队列解决了通信问题

(1)、有broker(类似消息中转站)

a.重topic:kafka、activemq、rocketmq

b.轻topic:rabbitmq

(2)、无broker:zeromq

3、kafka安装(linux)

下载地址

https://kafka.apache.org/downloads
// 使用2.4.0

解压目录

drwxr-xr-x 3 root root  4096 12月 10 2019 bin
drwxr-xr-x 2 root root  4096 12月 10 2019 config
drwxr-xr-x 2 root root  4096 8月  28 15:46 libs
-rw-r--r-- 1 root root 32216 12月 10 2019 LICENSE
-rw-r--r-- 1 root root   337 12月 10 2019 NOTICE
drwxr-xr-x 2 root root  4096 12月 10 2019 site-docs

配置文件

vi /config/server.properties

修改内容

listeners=PLAINTEXT://172.30.0.3:9092
// 当前主机地址
advertised.listeners=PLAINTEXT://公网ip:9092
// 云服务器公网ip,外部连接需要使用公网

log.dirs=../kafka-logs
// 日志地址 即消息地址 默认保存七天

zookeeper.connect=123.45.6.78:2181
// zk地址

启动服务

sh kafka-server-start.sh -daemon ../config/server.properties
// -daemon为后台启动

观察进程和日志

ps -ef|grep kafka

// tail -f logs/server.log
[2023-08-28 16:00:21,294] INFO [TransactionCoordinator id=0] Starting up. (kafka.coordinator.transaction.TransactionCoordinator)
[2023-08-28 16:00:21,295] INFO [Transaction Marker Channel Manager 0]: Starting (kafka.coordinator.transaction.TransactionMarkerChannelManager)
[2023-08-28 16:00:21,295] INFO [TransactionCoordinator id=0] Startup complete. (kafka.coordinator.transaction.TransactionCoordinator)
[2023-08-28 16:00:21,311] INFO [ExpirationReaper-0-AlterAcls]: Starting (kafka.server.DelayedOperationPurgatory$ExpiredOperationReaper)
[2023-08-28 16:00:21,324] INFO [/config/changes-event-process-thread]: Starting (kafka.common.ZkNodeChangeNotificationListener$ChangeEventProcessThread)
[2023-08-28 16:00:21,483] INFO [SocketServer brokerId=0] Started data-plane processors for 1 acceptors (kafka.network.SocketServer)
[2023-08-28 16:00:21,485] INFO Kafka version: 2.4.0 (org.apache.kafka.common.utils.AppInfoParser)
[2023-08-28 16:00:21,485] INFO Kafka commitId: 77a89fcf8d7fa018 (org.apache.kafka.common.utils.AppInfoParser)
[2023-08-28 16:00:21,485] INFO Kafka startTimeMs: 1693209621484 (org.apache.kafka.common.utils.AppInfoParser)
[2023-08-28 16:00:21,488] INFO [KafkaServer id=0] started (kafka.server.KafkaServer)

观察zk中有kafka节点信息

./zkCli.sh

ls /brokers/ids

[0]

二、kafka基本使用

1、kafka基本概念

名称 解释

broker	消息中间件处理节点,一个kafka节点就是一个broker,多个broker组成一个集群
topic	kafka通过topic对消息进行归类,发布到kafka集群的每条消息都需要指定一个topic
producer	消息生产者,向broker发送消息的客户端
consumer	消息消费者,从broker读取消息的客户端

2、创建主题topic

使用脚本创建主题

sh kafka-topics.sh --create --zookeeper 123.45.6.78:2181 --replication-factor 1 --partitions 1 --topic test

Created topic test.

使用脚本查看创建是否成功

sh kafka-topics.sh --list --zookeeper 123.45.6.78:2181

test

在zk查看是否创建成功

./zkCli.sh

ls /brokers/topics

[test]

topic等信息存储在zk,保证kafka是无状态的

3、发送消息

sh kafka-console-producer.sh --broker-list 172.30.0.3:9092 --topic test
>123
>456
>qwe

4、接收消息

(1)、从最后一条消息的偏移量+1消费

sh kafka-console-consumer.sh --bootstrap-server 172.30.0.3:9092 --topic test
111

(2)、从开头开始消费

sh kafka-console-consumer.sh --bootstrap-server 172.30.0.3:9092 --from-beginning --topic test
123
456
qwe
111

几个需要注意的点

a.消息会被存储

b.消息是顺序存储

c.消息是有偏移量的

d.消费时可以指定偏移量进行消费

三、消息的顺序存储

1、查看消息存储的位置

[root@1669305868239 kafka-logs]# ls
cleaner-offset-checkpoint  __consumer_offsets-33
__consumer_offsets-0       __consumer_offsets-34
__consumer_offsets-1       __consumer_offsets-35
__consumer_offsets-10      __consumer_offsets-36
__consumer_offsets-11      __consumer_offsets-37
__consumer_offsets-12      __consumer_offsets-38
__consumer_offsets-13      __consumer_offsets-39
__consumer_offsets-14      __consumer_offsets-4
__consumer_offsets-15      __consumer_offsets-40
__consumer_offsets-16      __consumer_offsets-41
__consumer_offsets-17      __consumer_offsets-42
__consumer_offsets-18      __consumer_offsets-43
__consumer_offsets-19      __consumer_offsets-44
__consumer_offsets-2       __consumer_offsets-45
__consumer_offsets-20      __consumer_offsets-46
__consumer_offsets-21      __consumer_offsets-47
__consumer_offsets-22      __consumer_offsets-48
__consumer_offsets-23      __consumer_offsets-49
__consumer_offsets-24      __consumer_offsets-5
__consumer_offsets-25      __consumer_offsets-6
__consumer_offsets-26      __consumer_offsets-7
__consumer_offsets-27      __consumer_offsets-8
__consumer_offsets-28      __consumer_offsets-9
__consumer_offsets-29      log-start-offset-checkpoint
__consumer_offsets-3       meta.properties
__consumer_offsets-30      recovery-point-offset-checkpoint
__consumer_offsets-31      replication-offset-checkpoint
__consumer_offsets-32      test-0

// __consumer_offsets-*为偏移量  test-0为存储消息的位置

进到test-0中

[root@1669305868239 test-0]# ls
00000000000000000000.index  00000000000000000000.log  00000000000000000000.timeindex  leader-epoch-checkpoint

2、查看消费者组及信息息

通过命令创建两个消费者,他们在同一个消费者组中

sh kafka-console-consumer.sh --bootstrap-server 172.30.0.3:9092  --consumer-property group.id=testGroup --topic test
只有一个消费者消费到消息

通过命令创建两个消费者,他们不在同一个消费者组中

sh kafka-console-consumer.sh --bootstrap-server 172.30.0.3:9092  --consumer-property group.id=testGroup1 --topic test

sh kafka-console-consumer.sh --bootstrap-server 172.30.0.3:9092  --consumer-property group.id=testGroup --topic test

结果两个消费者都收到消息

前者为单播消息(消费者同消费者组)

后者为多播消息(消费者不同消费者组)

3、查看消费者组及信息

查看当前主题下有哪些消费者组

sh kafka-consumer-groups.sh --bootstrap-server 172.30.0.3:9092 --list
testGroup1
testGroup

查看某一消费者组的具体信息

sh kafka-consumer-groups.sh --bootstrap-server 172.30.0.3:9092 --describe --group testGroup

GROUP           TOPIC           PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG             CONSUMER-ID                                               HOST            CLIENT-ID
testGroup       test            0          7               7               0               consumer-testGroup-1-84dd47e0-1acb-4cd1-a7b0-95f063b90989 /172.30.0.3     consumer-testGroup-1
CURRENT-OFFSET:当前消费者组的已消费偏移量

LOG-END-OFFSET:主题对应分区消息的结束偏移量(HW)

LAG:当前未被消费的消息数量

四、主题、分区的概念

1、主题topic

主题topic可以理解为一个类别划分

2、partition分区

区分分区,可以解决统一存储文件过大问题

提高吞吐量,可以多个分区并行读写

3、创建多分区的主题

创建两个分区一个副本的主题

sh kafka-topics.sh --create --zookeeper 123.45.6.78:2181 --replication-factor 1 --partitions 2  --topic test1

Created topic test1.

查看主题存放目录信息

[root@1669305868239 kafka-logs]# ls
cleaner-offset-checkpoint  __consumer_offsets-34
__consumer_offsets-0       __consumer_offsets-35
__consumer_offsets-1       __consumer_offsets-36
__consumer_offsets-10      __consumer_offsets-37
__consumer_offsets-11      __consumer_offsets-38
__consumer_offsets-12      __consumer_offsets-39
__consumer_offsets-13      __consumer_offsets-4
__consumer_offsets-14      __consumer_offsets-40
__consumer_offsets-15      __consumer_offsets-41
__consumer_offsets-16      __consumer_offsets-42
__consumer_offsets-17      __consumer_offsets-43
__consumer_offsets-18      __consumer_offsets-44
__consumer_offsets-19      __consumer_offsets-45
__consumer_offsets-2       __consumer_offsets-46
__consumer_offsets-20      __consumer_offsets-47
__consumer_offsets-21      __consumer_offsets-48
__consumer_offsets-22      __consumer_offsets-49
__consumer_offsets-23      __consumer_offsets-5
__consumer_offsets-24      __consumer_offsets-6
__consumer_offsets-25      __consumer_offsets-7
__consumer_offsets-26      __consumer_offsets-8
__consumer_offsets-27      __consumer_offsets-9
__consumer_offsets-28      log-start-offset-checkpoint
__consumer_offsets-29      meta.properties
__consumer_offsets-3       recovery-point-offset-checkpoint
__consumer_offsets-30      replication-offset-checkpoint
__consumer_offsets-31      test-0
__consumer_offsets-32      test1-0
__consumer_offsets-33      test1-1

发现有两个test1,test1-0和test1-1为test1的两个分区

即如某一个分区目录内

[root@1669305868239 test1-0]# ls
00000000000000000000.index  00000000000000000000.timeindex
00000000000000000000.log    leader-epoch-checkpoint

其中index保存的为稀疏索引,timeindex为时间索引,log为消息

五、kafka集群和副本

1、搭建kafka三个broker的集群(伪集群)

复制两个server.properties,分别命名为server1.properties,server2.properties

并且修改以下内容

// server1.properties

broker.id=1
listeners=PLAINTEXT://172.30.0.3:9093
advertised.listeners=PLAINTEXT://公网ip:9093
log.dirs=../kafka-logs-1
和

// server2.properties

broker.id=2
listeners=PLAINTEXT://172.30.0.3:9094
advertised.listeners=PLAINTEXT://公网ip:9094
log.dirs=../kafka-logs-2

启动三个节点

[root@1669305868239 bin]# sh kafka-server-start.sh -daemon ../config/server.properties
[root@1669305868239 bin]# sh kafka-server-start.sh -daemon ../config/server1.properties
[root@1669305868239 bin]# sh kafka-server-start.sh -daemon ../config/server2.properties

在zk上查看是否都启动成功

[zk: localhost:2181(CONNECTED) 3] ls /brokers/ids
[0, 1, 2]

2、副本的概念

副本是对分区的备份。在集群中,不同的副本会被部署在不同的broker上。

下面例子:创建一个主题,两个分区,三个副本

sh kafka-topics.sh --create --zookeeper 123.45.67.89:2181 --replication-factor 3 --partitions 2 --topic my-replicated-topic
Created topic my-replicated-topic.

查看topic

sh kafka-topics.sh --describe --zookeeper 123.45.67.89:2181 --topic my-replicated-topic
Topic: my-replicated-topic	PartitionCount: 2	ReplicationFactor: 3	Configs:
	Topic: my-replicated-topic	Partition: 0	Leader: 1	Replicas: 1,2,0	Isr: 1,2,0
	Topic: my-replicated-topic	Partition: 1	Leader: 2	Replicas: 2,0,1	Isr: 2,0,1

图解

图解

a.leader:kafka读和写的操作,都发生在leader身上。leader负责把数据同步给follower。当leader挂了,经过主从选举,从多个follower中选举产生一个新leader

b.follower:接收leader同步的数据

c.isr:可以同步和已同步的节点会被存入到isr集合中。这里有一个细节:如果isr中的节点性能较差会被isr踢出集合

3、向kafka集群发消息

sh kafka-console-producer.sh --broker-list 172.30.0.3:9092,172.30.0.3:9093,172.30.0.3:9094  --topic my-repliacated-topic
>111222333

接收消息

sh kafka-console-consumer.sh --bootstrap-server 172.30.0.3:9092,172.30.0.3:9093,172.30.0.3:9094 --from-beginning --topic my-replicated-topic
111222333

4、关于分区消费组和消费者的细节

按相同消费组消费

sh kafka-console-consumer.sh --bootstrap-server 172.30.0.3:9092,172.30.0.3:9093,172.30.0.3:9094 --from-beginning --consumer-property group.id=testGroup1 --topic my-replicated-topic

相同消费者组只有一个消费者收到消息,即单播消息

关于分区消费组和消费者的细节

a:一个partition只能被一个消费组中的一个消费者消费,目的是为了保证消费的顺序性,但是多个partition的多个消费者消费的总的顺序性是得不到保证的。

b:partition的数量决定了消费组中消费者的数量,建议同一个消费组中消费者的数量不超过partition的数量,否者多的消费者消费不到消息

c:如果消费者挂了,那么会触发Rebalance机制,会让其他消费者来消费该分区

六、kafka的Java客户端-生产者

1、引入依赖

        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
            <version>2.4.1</version>
        </dependency>

2、生产者发消息的基本实现

代码

public class KafkaProducerTest {

    public static final String TOPIC_NAME = "my-replicated-topic";

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 1.设置参数
        Properties properties = new Properties();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "ip1:9092,ip2:9093,ip3:9094");
        // key序列化方式为字节数组
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        // value序列化方式为字节数组
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

        // 2.创建生产消息的客户端,传入参数
        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
        Map<String, Object> map = new HashMap<>();

        map.put("jjj11", "11111");

        // 3.创建消息
        ProducerRecord<String, String> record = new ProducerRecord<>(
                TOPIC_NAME, UUID.randomUUID().toString(), JSON.toJSONString(map));

        // 4.发送消息,得到消息发送的元数据并输出
        RecordMetadata recordMetadata = kafkaProducer.send(record).get();
        String topic = recordMetadata.topic();
        System.err.println("topic = " + topic);
        int partition = recordMetadata.partition();
        System.err.println("partition = " + partition);
        long offset = recordMetadata.offset();
        System.err.println("offset = " + offset);
    }
}

控制台输出

topic = my-replicated-topic
partition = 1
offset = 3

3、发送到指定分区

        // 3.创建消息 指定0号分区
        ProducerRecord<String, String> record = new ProducerRecord<>(
                TOPIC_NAME, 0, UUID.randomUUID().toString(), JSON.toJSONString(map));

4、同步异步发送消息

同步消息图解

同步消息图解
生产者异步发送消息,不管是否发送成功即进行后面逻辑

        // 异步发消息 没有ack容易丢消息
        kafkaProducer.send(record, (recordMetadata1, e) -> {
            if (e != null) {
                //失败
            }
            if (recordMetadata1 != null) {
                //成功
            }
        });

关于ack的配置

a:ack=0,kafka集群不需要任何broker收到消息,就立即返回ack给生产者,最容易丢失消息,效率是最高的。

b:ack=1(默认),多副本之间的leader已经收到消息,把消息写入到本地的log中,才会返回ack给生产者,性能和安全性是最均衡的。

c:ack=-1/all,里面有默认的配置min.insync.replicas=2(默认是1,效果同b,推荐配置大于等于2),此时需要leader和follower同步完后,才会返回ack给生产者(此时集群中有2个broker已经完成数据的接收),这种方式最安全,但性能最差。

代码

        // ack
        properties.put(ProducerConfig.ACKS_CONFIG, "1");
        // 多少时间(ms)没有ack回复,就重试
        properties.put(ProducerConfig.RETRY_BACKOFF_MS_CONFIG, 300);
        // 重试次数
        properties.put(ProducerConfig.RETRIES_CONFIG, 3);
消费者的其他细节配置

        // 应用发送消息缓冲区(占用应用服务内存)大小,默认32MB
        properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
        // kafka本地线程会从缓冲区取数据,批量发送到broker,默认16kb,就是说一个batch满了16kb就发送出去
        properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
        // 默认值是0,即消息立即被发送出去,但这样会影响性能,一般设置10毫秒左右,即满足BATCH_SIZE_CONFIG,和超时两个满足一个即发送
        properties.put(ProducerConfig.LINGER_MS_CONFIG, 10);

图解

其他参数细节图解
补充:CountDownLatch可以合并多线的执行结果

未指定partition时,发到哪个partition的源码

    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        if (keyBytes == null) {
            return this.stickyPartitionCache.partition(topic, cluster);
        } else {
            List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
            int numPartitions = partitions.size();
            return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
        }
    }

// 通过key hash后取模partition个数,即相同的key在一个分区

七、Java消费者的实现细节

1、消费者的基本实现

public class MySimpleConsumer {

    public static final String TOPIC_NAME = "my-replicated-topic";
    public static final String GROUP_NAME = "testGroup111";

    public static void main(String[] args) {

        Properties properties = new Properties();
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "116.198.205.107:9092,116.198.205.107:9093,116.198.205.107:9094");
        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, GROUP_NAME);
        // 1.创建消费者客户端
        KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(properties);

        // 2.订阅消费消息
        kafkaConsumer.subscribe(Arrays.asList(TOPIC_NAME));

        while (true) {
            /**
             * 3.拉取消息长轮训
             */
            ConsumerRecords<String, String> poll = kafkaConsumer.poll(Duration.ofMillis(1000L));

            for (ConsumerRecord<String, String> record : poll) {

                // 4.解析消息
                int partition = record.partition();
                System.err.println("partition = " + partition);
                long offset = record.offset();
                System.err.println("offset = " + offset);
                String key = record.key();
                System.err.println("key = " + key);
                String value = record.value();
                System.err.println("value = " + value);
            }
        }
    }
}

2、自动和手动提交offset

图解

自动和手动提交offset图解
开启自动提交

        // 开启自动提交 (默认)
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
        // 自动提交时间间隔 (默认)
        properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");

消费者poll到消息后,默认情况下,会自动向broker的consumer_offsets主题提交当前主题-分区消费的偏移量

需要注意的是:自动提交不管是否消费成功(消息poll下来)就提交offset,消息poll下来后,此时消费者挂了,于是下一个消费者会从已经提交的offset的下一个位置开始消费消息,之前未被消费的消息就丢失了,所以存在丢失消息的风险。

开启手动提交

        // 开启手动提交
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);

手动提交分为两种:

(1)、手动同步提交

在消费完消息后,调用同步提交的方法,当集群返回ack前一直阻塞,返回ack后表示提交成功,执行之后的逻辑。

代码

      while (true) {
            /**
             * 3.拉取消息长轮训
             */
            ConsumerRecords<String, String> poll = kafkaConsumer.poll(Duration.ofMillis(1000L));

            for (ConsumerRecord<String, String> record : poll) {

                // 4.解析消息
                int partition = record.partition();
                System.err.println("partition = " + partition);
                long offset = record.offset();
                System.err.println("offset = " + offset);
                String key = record.key();
                System.err.println("key = " + key);
                String value = record.value();
                System.err.println("value = " + value);

            }
            // 所有消息已经消费完
            if (poll.count() > 0) {
                kafkaConsumer.commitAsync();
            }
        }

(2)、手动异步提交

在消息消费完后提交,不需要等到集群ack,直接执行之后的逻辑,可以设置一个回调方法,供集群调用。

代码

        while (true) {
            /**
             * 3.拉取消息长轮训
             */
            ConsumerRecords<String, String> poll = kafkaConsumer.poll(Duration.ofMillis(1000L));

            for (ConsumerRecord<String, String> record : poll) {

                // 4.解析消息
                int partition = record.partition();
                System.err.println("partition = " + partition);
                long offset = record.offset();
                System.err.println("offset = " + offset);
                String key = record.key();
                System.err.println("key = " + key);
                String value = record.value();
                System.err.println("value = " + value);

            }
            // 异步提交
            kafkaConsumer.commitAsync(new OffsetCommitCallback() {
                @Override
                public void onComplete(Map<TopicPartition, OffsetAndMetadata> map, Exception e) {
                    // 回调
                }
            });
        }

3、消费者poll消息的过程

消费者建立了与broker之前的长连接,开始poll消息。

默认一次poll500条消息

        // 一次消费最大拉取消息数
        properties.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 500);

可以根据消费速度的快慢设置,因为如果两次poll的时间如果超过了30s的时间间隔,kafka会认为其消费能力过弱,将其踢出消费组,将分区分配给其他消费者消费。触发rebalance机制,要消耗一些性能。

可以通过这个值进行设置

        // 30s内没消费掉poll消息会被kafka集群的消费者组踢掉 触发Rebalance
        properties.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 30 * 1000);

如果每隔1s没有poll到任何消息,则继续poll消息,循环往复,直到poll到消息。如果超出了1s,则此长轮训结束。

ConsumerRecords<String, String> poll = kafkaConsumer.poll(Duration.ofMillis(1000L));

4、消费者的健康状态检查

消费者每隔1s向kafka集群发送心跳,如果集群发现超过10s没有续约的消费者,将被踢出消费者组,触发该消费者组的Rebalance机制,将该分区交给消费组里的其他消费者进行消费。

        // consumer给broker发送心跳的间隔时间
        properties.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, 1000);

5、消费者指定分区消费

        // 指定分区消费
        kafkaConsumer.assign(Arrays.asList(new TopicPartition(TOPIC_NAME, 0)));

6、消息回溯消费

        // 消息回溯消费
        kafkaConsumer.seekToBeginning(Arrays.asList(new TopicPartition(TOPIC_NAME, 0)));

7、指定offset消费

        // 指定offset消费
        kafkaConsumer.seek(new TopicPartition(TOPIC_NAME, 0), 10);

8、从指定时间点消费

        // 从指定时间开始消费
        List<PartitionInfo> partitionInfos = kafkaConsumer.partitionsFor(TOPIC_NAME);

        long fetchDataTime = System.currentTimeMillis() - 1000 * 60 * 60;

        Map<TopicPartition, Long> map = new HashMap<>();

        for (PartitionInfo partitionInfo : partitionInfos) {
            map.put(new TopicPartition(TOPIC_NAME, partitionInfo.partition()), fetchDataTime);
        }

        Map<TopicPartition, OffsetAndTimestamp> partitionOffsetAndTimestampMap = kafkaConsumer.offsetsForTimes(map);

        for (Map.Entry<TopicPartition, OffsetAndTimestamp> entry : partitionOffsetAndTimestampMap.entrySet()) {
            TopicPartition key = entry.getKey();
            OffsetAndTimestamp value = entry.getValue();

            if (key == null || value == null) {
                continue;
            }

            long offset = value.offset();

            int partition = key.partition();
            System.err.println("partition = " + partition);

            System.err.println("offset = " + offset);

            kafkaConsumer.assign(Arrays.asList(key));

            kafkaConsumer.seek(key, offset);

        }

9、新消费者组的消费规则

        // 当加入一个新消费者组的时候,可以指定offset开始消费,如果不指定,应该怎样消费的配置
        // earliest:没消费过的最近一次消费的位置,并不完全是从头消费
        // latest(默认):从结尾
        properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");

八、springboot整合kafka

1、引入依赖

        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
        </dependency>

2、生产者配置及代码

yml配置

spring:
  application:
    name: kafka-product-demo
  kafka:
    bootstrap-servers: ip:9092,ip:9093,ip:9094
    producer: # producer 生产者
      retries: 3 # 没有收到ack的重试次数(默认3)
      batch-size: 16384 # 批量大小(默认16KB)
      buffer-memory: 33554432 # 生产端缓冲区大小(默认32MB)
      acks: 1 # 应答级别:多少个分区副本备份完成时向生产者发送ack确认(可选0、1、all/-1)
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer

代码

    @Resource
    private KafkaTemplate<String, Object> kafkaTemplate;

    /**
     * 同步发送
     *
     * @param msg
     * @throws Exception
     */
    @GetMapping("/kafka/sync/{msg}")
    public void sync(@PathVariable("msg") String msg) throws Exception {
        Message message = new Message();
        message.setMessage(msg);
        log.info("kafka send start....");
        ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send("test", JSON.toJSONString(message));
        //注意,可以设置等待时间,超出后,不再等候结果
//        SendResult<String, Object> result = future.get(2, TimeUnit.SECONDS);
        SendResult<String, Object> result = future.get();
        log.info("kafka send end....");
        log.info("send result:{}", result.getProducerRecord().value());
    }

3、消费者配置及代码

yml配置

spring:
  application:
    name: kafka-consumer-demo
  kafka:
    bootstrap-servers: 119.96.216.192:9092,119.96.216.192:9093,119.96.216.192:9094
    consumer: # consumer消费者
      group-id: javagroup # 默认的消费组ID
      enable-auto-commit: false # 是否自动提交offset
#      auto-commit-interval: 100  # 提交offset延时(接收到消息后多久提交offset)

      # earliest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
      # latest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据
      # none:topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常
      auto-offset-reset: earliest
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      max-poll-records: 500 #一次拉取多少条数据消费
    listener:
      # 当每一条记录被消费者监听器处理后提交
      # record
      # 当每一批poll()的数据被消费者监听器处理后提交
      # batch
      # 当每一批poll()的数据被消费者监听器处理后,距离上次提交时间大于time时提交
      # time
      # 当每一批poll()的数据被消费者监听器处理后,距离上次提交时间大于等于count时提交
      # count
      # time | count 有一个满足时提交
      # count_time
      # 当每一批poll()的数据被消费者监听器处理后,手动调用akc.acknowledge()后提交
      # manual
      # 手动调用akc.acknowledge()后立即提交,最常用
      ack-mode: manual_immediate

代码

    /**
     * 不指定group,默认取yml里配置的
     *
     * @param consumerRecord
     */
    @KafkaListener(topics = {"test"}, groupId = "javagroup")
    public void onMessage1(ConsumerRecord<String, String> consumerRecord, Acknowledgment ack) {
        // 单条消息消费逻辑
        log.info("KafkaListener start.....");
        String msg = consumerRecord.value();
        log.info("message:{}", msg);
        log.info("KafkaListener end.....");
        // 手动提交不指定ack会有重复消费问题
        ack.acknowledge();
    }

消费者的详细配置

    /**
     * 指定消费分区,及多个主题等
     *
     * @param consumerRecord
     * @param ack
     */
    @KafkaListener(groupId = "javagroup", topicPartitions = {
            @TopicPartition(topic = "topic1", partitions = {"0", "1"}),
            @TopicPartition(topic = "topic2", partitions = {"0"},
                    partitionOffsets = @PartitionOffset(partition = "1", initialOffset = "100"))
    }, concurrency = "3")
    public void onMessage2(ConsumerRecord<String, String> consumerRecord, Acknowledgment ack) {
        log.info("KafkaListener start.....");
        String msg = consumerRecord.value();
        log.info("message:{}", msg);
        log.info("KafkaListener end.....");

        ack.acknowledge();
    }

九、kafka集群Controller、Rebalance和HW

1、Controller

集群中谁是controller

每个broker启动时会向zk创建一个临时序号节点,获得的序号最小的那个broker将会作为集群中的controller,负责管理集群中的所有分区和副本的状态:

a:当某个分区的leader副本出现故障时,由控制器负责为该分区选举新的leader副本。

b:当检测到某个分区的ISR集合发生变化时,由控制器负责所有broker更新其元数据信息。

c:当使用kafka.topic.sh脚本为某个topic增加分区数量时,同样还是由控制器负责让新分区被其他节点感知到。

2、Rebalance

前提是:消费者没有指明分区消费。当消费组里消费者和分区的关系发生变化,那么就会触发rebalance机制。

这个机制会重新调整消费者消费那个分区。

在触发rebalance机制之前,消费者消费分区有三种策略:

a:range:通过公式来计算某个消费者消费哪个分区。

图解

range图解

b:轮询:大家轮着消费。

图解

轮询图解

c:sticky:在触发了rebalance后,在消费者消费原分区不变的基础上进行调整。

如果不使用粘合策略,将重新打散消费关系,重新分配消费关系,消耗性能。

3、HW和LEO

HW俗称高水位,HighWatermarker的缩写,取一个partition对应的ISR中最小的LEO(log-end-offset)作为HW,consumer最多只能消费到HW所在的位置。另外每个replica(副本)都有HW,leader和follower各自负责更新自己的HW状态。对于leader新写入的消息,consumer不能立刻消费,leader会等待该消息被所有ISR中的replica同步后更新HW,此时消息才能被consumer消费。这样就保证了如果leader所在的broker失效,该消息仍然可以从新选举的leader中获取。

图解

高水位概念图解

十、kafka问题线上优化

1、如果防止消息丢失

a:发送方,ack是1或者-1/all可以防止消息丢失,如果要做成99.9999999%(N个9),ack设置成all,把min.insync.replicas配置成分区被分数

b:消费方,把自动提交改成手动提交

2、如何防止消息的重复消费

一条消息被消费者消费多次。如果为了消息的不重复消费,而把生产端的重试机制关闭、消费端的手动提交改成自动提交,这样反而会出现消息丢失,那么可以直接在防止消息的手段上再加上消费消息时的幂等性保证,就能解决消息重复消费的问题。

幂等性如何保证:

a:MySQL插入业务id作为主键,主键是唯一的,所以一次只能插入一条

b:使用redis或zk的分布式锁(主流方案)

图解

解决消息重复发送图解

3、如何做到顺序消费

发送方:在发送时将ack不能设置成0,关闭重试,使用同步发送,等消息发送成功再发送下一条。确保消息是顺序发送的。

接收方:消息是发送到一个分区中,只能有一个消费组的消费者接收消息。

因此,kafka的顺序消费会牺牲掉性能。

4、解决消息积压问题

消息积压会导致很多问题,比如磁盘被打满、生产端发消息导致kafka性能过慢,就容易发生服务雪崩,就需要有相应的手段:

方案一:在一个消费者中启动多个线程,让多个线程同时消费。--提升一个消费者的消费能力。

方案二:如果方案一还不够的话,这个时候可以启动多个消费者,多个消费者启动在不同的服务器上。其实多个消费者部署在同一服务器上也可以提高消能力。--充分利用服务器的CPU资源。

方案三:让一个消费者去把接收的消息往另一个topic上发,另一个topic设置多个分区和多个消费者,进行具体的业务消费。

图解

方案三图解

5、延迟队列

延迟队列的应用场景:在订单创建成功后如果超过30分钟没有付款,则需要取消订单,此时可以用延迟队列来实现。

a:创建多个topic,每个topic表示延时的间隔。

i:topic_5s,延时5秒执行的队列。

ii:topic_1m,延时1分钟执行的队列。

iii:topic_30m,延时30分钟执行的队列。

b:消息发送者发送消息到相应的topic,并带上消息的发送时间。

c:消费者订阅相应的topic,消费时轮询消费整个topic中的消息

i:如果消息的发送时间,和消费的当前时间超过预设的值,比如30分钟。

ii:如果消息的发送时间,和消费的当前时间没有超过预设的值,则不消费当前的offset及之后的offset的所有消息都消费。

iii:下次继续消费该offset处的消息,判断时间是否已满足预设值。

图解

延迟队列图解

十一、kafka-eagle监控平台

1、搭建

下载地址

https://github.com/smartloli/kafka-eagle-bin/archive/v3.0.1.tar.gz

需要jdk环境

配置kafka-eagle环境变量

vim /etc/profile

export KE_HOME=xxx/efak-web-3.0.1
export PATH=$PATH:$KE_HOME/bin

解压目录

drwxr-xr-x 2 root root 4096 8月  31 16:10 bin
drwxr-xr-x 2 root root 4096 8月  31 16:10 conf
drwxr-xr-x 2 root root 4096 9月  13 2021 db
drwxr-xr-x 2 root root 4096 8月  31 16:10 font
drwxr-xr-x 9 root root 4096 2月  23 2022 kms
drwxr-xr-x 2 root root 4096 4月   2 2022 logs

进入配置文件

cd conf
vi system-config.properties

修改配置

efak.zk.cluster.alias=cluster1
// kafka zk地址
cluster1.zk.list=123.45.67.89:2181

// 修改数据库信息
efak.driver=com.mysql.cj.jdbc.Driver //8的驱动 5.7用com.mysql.jdbc.Driver
efak.url=jdbc:mysql://127.0.0.1:3306/ke?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
efak.username=root
efak.password=123456

库和表都会自己创建

启动

cd bin
sh ke.sh start

日志

Welcome to
    ______    ______    ___     __ __
   / ____/   / ____/   /   |   / //_/
  / __/     / /_      / /| |  / ,<
 / /___    / __/     / ___ | / /| |
/_____/   /_/       /_/  |_|/_/ |_|
( Eagle For Apache Kafka® )

Version v3.0.1 -- Copyright 2016-2022
*******************************************************************
* EFAK Service has started success.
* Welcome, Now you can visit 'http://172.30.0.3:8048'
* Account:admin ,Password:123456
*******************************************************************
* <Usage> ke.sh [start|status|stop|restart|stats] </Usage>
* <Usage> https://www.kafka-eagle.org/ </Usage>
*******************************************************************

2、访问

xxx:8048
posted @ 2024-09-11 09:28  大列巴同学  阅读(12)  评论(0编辑  收藏  举报