Kafka基本介绍和操作

本文详细介绍了Kafka在Windows和Linux上的安装过程,以及如何启动Zookeeper和创建Topic。接着讲解了生产者和消费者的创建与使用,包括异步发送、回调函数、自定义分区器、影响吞吐量的参数、幂等性和事务。此外,还探讨了Kafka的有序性、Broker的故障处理机制和消费者的工作原理,特别是消费者组和提交模式。最后提到了SpringBoot集成Kafka的相关内容。
摘要由CSDN通过智能技术生成

1 安装

windows安装
linux安装
其他学习笔记链接

2 启动及创建topic(windows)

启动zookeeper:
   zookeeper-server-start.bat ..\..\config\zookeeper.properties
   
创建两个broker:
   kafka-server-start.bat ..\..\config\server.properties
   kafka-server-start.bat ..\..\config\server1.properties
   
创建一个topic,两个分区,每个分区两个副本:
   kafka-topics.bat --create --bootstrap-server localhost:9092 --replication-factor 2 --partitions 2 --topic test2
   
查看主题:
   kafka-topics.bat --list --bootstrap-server localhost:9092
   
创建一个生产者:
   kafka-console-producer.bat --broker-list localhost:9092 --topic test2

创建一个消费者:
  kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic test2 --from-beginning
    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LMDAufWv-1657845759222)(http://image.huawei.com/tiny-lts/v1/images/af18d613fbf90c02c52875cea106ab0c_1227x495.png)]

3 生产者

引入maven依赖

        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>3.0.0</version>
        </dependency>

3.1 生产者的基本使用

下面的示例演示生产者的基本使用,发送为异步发送

    @Test
    void customProducer() {
        Properties properties = new Properties();
        // 1. 给 kafka 配置对象添加配置信息:bootstrap.servers
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
                "localhost:9092,localhost:9093");

        // 2. key,value 序列化(必须):key.serializer,value.serializer
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getCanonicalName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getCanonicalName());

        // 3. 创建 kafka 生产者对象
        KafkaProducer<String,  String> kafkaProducer = new KafkaProducer<>(properties);

        // 4. 调用 send 方法,发送消息
        for (int i = 0; i < 5; i++) {
            kafkaProducer.send(new
                    ProducerRecord<>("test2","ceshi " + i));
        }

        // 5. 关闭资源
        kafkaProducer.close();
    }

执行结果:消费者收到消息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2iOzF3S0-1657845759223)(http://image.huawei.com/tiny-lts/v1/images/b6898547b2ca7e747fc45041a6f9e2ae_223x131.png)]

3.2 带回调函数的 异步发送

    @Test
    void customProducer2() throws Exception {
        Properties properties = new Properties();
        // 1. 给 kafka 配置对象添加配置信息:bootstrap.servers
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
                "localhost:9092,localhost:9093");

        // 2. key,value 序列化(必须):key.serializer,value.serializer
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getCanonicalName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getCanonicalName());

        // 设置自定义的分区器
        // properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.atgui gu.kafka.producer.MyPartitioner");

        // 设置ack的模式,0表示生产者不等待服务器响应;1表示leader落盘后响应;
        // all或者-1表示等待所有的isr(in-sync replicas)同步副本都落盘后响应,默认为all
        properties.put(ProducerConfig.ACKS_CONFIG, "all");

        // 3. 创建 kafka 生产者对象
        KafkaProducer<String,  String> kafkaProducer = new KafkaProducer<>(properties);

        // 4. 调用 send 方法,发送消息
        for (int i = 0; i < 5; i++) {
            // (1)send返回Future对象,如果想要改成同步调用,对send的返回结果调用get()即可
            kafkaProducer.send(new  ProducerRecord<>("test2","key" + i, "sishen " + i), new Callback() {
                // 收到ack时调用
                @Override
                public void onCompletion(RecordMetadata metadata, Exception exception) {
                    if (exception == null) {
                        // 返回的信息包括主题、分区等
                        // 发送的信息默认分区策略,ProducerRecord如果
                        // (1)指定partition时,按照指定分区发送
                        // (2)没有指定partition,指定key时,按照key的hash值求余topic的分区数选择partition
                        // (3)没有指定partition和key:随机选择分区,并且采用粘性分区(安装发送批次,不是每个都随机)
                        //  发送分区的默认策略参考类 DefaultPartitioner 实现
                        System.out.println(" 主 题 : "  +
                                metadata.topic() + "->" + "分区:" + metadata.partition());
                    } else {
                        // 出现异常打印
                        exception.printStackTrace();
                    }
                }
            });

            // 延迟一会会看到数据发往不同分区
            Thread.sleep(2);
        }
        // 5. 关闭资源
        kafkaProducer.close();
    }

3.3 自定义分区器

想要实现之定义分区器,需要实现Partitioner接口,并在创建生产者对象时,设置ProducerConfig.PARTITIONER_CLASS_CONFIG属性
Partitioner接口定义:

public interface Partitioner extends Configurable, Closeable {
    // 返回分区编号,可以根据key、value等相关特性,发送到指定分区
    int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster);


    void close();

    // 通知创建一个新的发送批次
    default void onNewBatch(String topic, Cluster cluster, int prevPartition) {
    }
}

3.4 影响吞吐量的相关参数

  1. batch.size:批次大小,默认 16K

    properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
  2. linger.ms:等待时间,默认 0

    properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);
  3. RecordAccumulator:缓冲区大小,默认 32M:buffer.memory

    properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
  4. compression.type:压缩,默认 none,可配置值 gzip、snappy、lz4 和 zstd

    properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, “snappy”);

3.5 数据去重

3.5.1 数据传递语义

  1. 至少一次(at least once):acks级别为-1 + 分区副本数大于等于2 + ISR中最小副本数量大于等于2, 可以保证数据不丢失,无法保证数据不重复
  2. 最多一次(at most once): acks级别设置为0;可以保证数据不重复,无法保证数据不丢失。
  3. 精确一次(exactly once):既不重复,也不丢失;依靠幂等性 + 至少一次保证。

3.5.2 幂等性

Producer 的幂等性指的是当发送同一条消息时,数据在 Server 端只会被持久化一次,数据不丟不重,但是这里的幂等性是有条件的:

  • 只能保证 Producer 在单个会话内不丟不重,如果 Producer 出现意外挂掉再重启是无法保证的(幂等性情况下,是无法获取之前的状态信息,因此是无法做到跨会话级别的不丢不重);
  • 幂等性不能跨多个 Topic-Partition,只能保证单个 partition 内的幂等性,当涉及多个 Topic-Partition 时,这中间的状态并没有同步。

重复数据的判断标准:PID+partition+seqNum; 每个Producer 在初始化时都会被分配一个唯一的 PID,这个 PID 对应用是透明的,完全没有暴露给用户。对于一个给定的 PID,sequence number 将会从0开始自增,每个 Topic-Partition 都会有一个独立的 sequence number。

幂等性开启参数 enable.idempotence 默认为 true,false关闭。

3.5.4 事务

Kafka事务性主要是为了解决幂等性无法跨Partition运作的问题,事务性提供了多个Partition写入的原子性,即写入多个Partition要么全部成功,要么全部失败,不会出现部分成功部分失败这种情况。

    @Test
    void customProducerTransation() throws Exception {
        Properties properties = new Properties();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
                "localhost:9092,localhost:9093");

        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getCanonicalName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getCanonicalName());

        // 1.设置事务 id(必须),名字任意,但要唯一
        properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "transaction_id_0");

        KafkaProducer<String,  String> kafkaProducer = new KafkaProducer<>(properties);

        // 2. 初始化事务
        kafkaProducer.initTransactions();

        // 3. 开启事务
        kafkaProducer.beginTransaction();

        try {
            for (int i = 0; i < 5; i++) {
                kafkaProducer.send(new ProducerRecord<>("test2", "key" + i, "transation " + i), new Callback() {
                    @Override
                    public void onCompletion(RecordMetadata metadata, Exception exception) {
                        if (exception == null) {
                            System.out.println(" 主 题 : " +
                                    metadata.topic() + "->" + "分区:" + metadata.partition());
                        } else {
                            // 出现异常打印
                            exception.printStackTrace();
                        }
                    }
                });

                // 构造异常
                // int ab = 1 / 0;

                Thread.sleep(1);
            }

            // 4. 提交事务
            kafkaProducer.commitTransaction();
        } catch (Exception e) {
            // 5. 终止事务
            kafkaProducer.abortTransaction();
        }

        kafkaProducer.close();
    }

3.6 有序性

  1. 多个分区间无法保证有序性
  2. 单个分区内有条件有序:(生产者发送消息到broker,有失败重试,可能会导致失序)
  • kafka在1.x版本之前保证数据单分区有序,条件如下:
    max.in.flight.requests.per.connection=1(不需要考虑是否开启幂等性)
    max.in.flight.requests.per.connection参数指定了生产者在收到服务器响应之前可以发送多少个消息。它的值越高,就会占用越多的内存,同时也会提升吞吐量。把它设为1就可以保证消息是按照发送的顺序写入服务器的

  • kafka在1.x及后续版本保证数据单分区有序,条件如下:
    未开启幂等性:max.in.flight.request.per.conection需要设置为1
    开启幂等性:max.in.flight.requests.per.connection需要设置小于等于5
    说明:在kafka1.x以后,启用幂等性后,kafka服务端会缓存producer发来的最近5个request的元数据,故无论如何,都可以保证最近5个request的数据都是有序的

4 Broker

LEO(Log End Offset):每个副本的最后一个offset,LEO其实就是最新的offset + 1
HW(High Watermark):所有ISR中最小的LEO

4.1 follower故障处理

当follower出现故障时,由于leader并没有任何影响,整个集群仍然能够对外提供读写服务,内部大致的处理步骤如下:

  1. follower 发生故障后会被临时踢出ISR;
  2. 这个期间Leader和Follower继续接收数据;
  3. 待该follower恢复后,Follower会读取本地磁盘记录的上次的HW,并将log文件高于HW的部分截取掉,从HW开始向Leader进行同步;
  4. 等到该Follower的LEO大于等于该Partition的HW,即follower追上Leader之后,就可以重新加入ISR了

4.2 leader故障处理

leader处理故障的过程相对来说要简单一些,主要步骤如下:

  1. Leader发生故障之后,会从ISR中选出一个新的Leader;
  2. 为保证多个副本之间的数据一致性,其余的Follower会先将各自的log文件高于HW的部分截掉,然后从新的Leader同步数据。

也就是说,leader故障时,会优先保证数据的一致性,可能会出现数据丢失。

5 消费者

对于消费者,不是以单独的形式存在的,每一个消费者属于一个 consumer group,一个 group 包含多个 consumer。特别需要注意的是:订阅 Topic 是以一个消费组来订阅的,发送到 Topic 的消息,只会被订阅此 Topic 的每个group中的一个consumer消费。同一个消费组的两个消费者不会同时消费一个 partition。

    @Test
    void customConsumer() throws Exception {
        Properties properties = new Properties();

        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
                "localhost:9092,localhost:9093");
        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, "consumer1");

        // 创建消费者对象
        KafkaConsumer<String,  String> kafkaConsumer  =  new KafkaConsumer(properties);

        // 注册要消费的主题(可以消费多个主题)
        ArrayList<String> topics = new ArrayList<>();
        topics.add("test2");
        kafkaConsumer.subscribe(topics);
        // 拉取数据打印
        while (true) {
            // 设置 1s 中消费一批数据
            ConsumerRecords<String,  String> consumerRecords  = kafkaConsumer.poll(Duration.ofSeconds(1));
            // 打印消费到的数据
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord);
            }
        }
    }

5.1 提交模式

DMQ消费者从服务端收到消息后,需要告诉服务端收到了该消息,以便我们下次再去取的时候,知道从哪个位置(offset)开始

  1. 自动异步提交:

    定时提交,定时器触发,由于每一次去DMQ获取消息,都是批量获取,再逐一回调业务的onMessage方法,因此如果业务处理慢,或者定时器间隔设置太短,都有可能造成,客户端内存消息还没有消费完,进程被杀,导致消息丢失(不是真正意义上的丢失,只是说没法再消费之前收下来未处理的消息)

    优点:性能高,IO少

    缺点:丢消息(概率低)

    适合业务场景:比如日志处理,push消息回执等,并发要求高,消息可丢(一般是客户端kill重启的时候,有可能会丢,正常运行过程中不丢)

    配置:

    auto.commit.enable=true

    auto.commit.interval.ms=60000

  2. 手动同步提交
    DMQ的消息获取也是批量的,只是消费位置(offset)的提交,需要手动完成(我们的SDK做了),即业务的onMessage方法处理完成,保证成功后,才告诉服务端我收到,提交当前消息的offset

    优点:消息不丢,且消息重复消费的概率、比例最低

    缺点:性能差,每处理一条消息,就要提交一次offset,网络IO多,导致性能上不去

    适合业务场景:用户订单消息,处理并发要求不高,但是消息不能丢,允许重复消费(一般是客户端kill重启的是,最后处理的消息还没有处理完,下次重启重复消费该条消息)

    配置:

    auto.commit.enable=fasle

    dmq.manualCommit.async.enable=false

  3. 手动异步提交(推荐)
    与同步提交不同的时,提交Offset的动作是异步的,提交的offset会等到业务的onMessage方法处理完成,保证成功后才提交。

    优点:消息不丢,性能比同步提交高很多,比异步提交差一点

    缺点:重复消费的概率较高。(一般是客户端Kill重启,或者新加入客户端的时候,分区分配发生rebalance)

    适合业务场景:数据同步,允许重复,不允许丢失,且对性能要求较高

    配置:

    auto.commit.enable=fasle

    dmq.manualCommit.async.enable=true

6 spring boot集成

参考链接

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值