大数据开发Kafka高级特性(第三十九篇)

99 阅读4分钟

一、Broker扩展

kafka.apache.org/documentati…

  1. 配置文件:server.properties
  2. Log Flush Policy:设置数据flush到磁盘的时机
  3. Log Retention Policy:设置数据保存周期,默认7天

二、Producer扩展

  1. partitioner:根据用户设置的算法来计算发送到哪个分区,默认是随机发送到不同分区
  2. 数据通讯方式:同步发送和异步发送

2.1、Kafka如何保证数据不丢

  1. acks:默认是1,表示需要Leader节点回复收到消息
  2. acks:all,表示需要所有Leader+副本节点回复收到消息(acks=-1)
  3. acks:0,表示不需要任何节点回复

三、Topic、Partition扩展

  1. 每个Partition在存储层面是Append Log文件,新消息都会被直接追加到log文件的尾部,每条消息在log文件中的位置称为Offset(偏移量)
  2. 越多Partition可以容纳更多的Consumer,有效提升并发消费的能力
  3. 业务类型增加需要增加Topic、数据量大需要增加Partition

四、Message扩展

  1. offset,类型为long,表示此消息在一个Partition中的起始位置,可以认为Offset是Partition中Message的id,自增

  2. MessageSize,类型为int32,表示此消息的字节大小

  3. data,类型为bytes,表示message的具体内容

    Kafka的写机制

五、存储策略

  1. 在Kafka中每个Topic包含1到多个Partition,每个Partition存储一部分Message。每条Message包含三个属性,其中一个是Offset
  2. Offset相当于Partition中这个Message的唯一id,那么如何通过id高效的找到Message?
  3. 通过分段+索引

六、容错机制

  1. 当Kafka集群中的一个Broker节点宕机,会出现什么现象?

    zookeeper会自动选举其他机器

七、代码演示

package com.strivelearn.java.kafka;
​
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;
​
import java.util.Properties;
​
/**
 * @author strivelearn
 * @version ProducerDemo.java, 2022年12月05日
 */
public class ProducerDemo {
    public static void main(String[] args) {
        Properties properties = new Properties();
        //指定Kafka的broker地址
        properties.put("bootstrap.servers", "192.168.234.100:9092");
        //指定key-value数据的序列化格式
//        properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.put("key.serializer", StringSerializer.class.getName());
        properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.put("acks", "all");
        //创建Kafka生产者
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
        //向topic中生产数据
        String topic = "my-topic";
        producer.send(new ProducerRecord<String, String>(topic, "hello java kafka producer"));
        //关闭链接
        producer.close();
    }
}
package com.strivelearn.java.kafka;
​
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.Collection;
import java.util.Properties;
​
/**
 * @author strivelearn
 * @version ConsumerDemo.java, 2022年12月05日
 */
public class ConsumerDemo {
    public static void main(String[] args) {
        Properties properties = new Properties();
        //指定Kafka的broker地址
        properties.put("bootstrap.servers", "192.168.234.100:9092");
        //指定key-value数据的反序列化格式
//        properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.put("key.deserializer", StringDeserializer.class.getName());
        properties.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        //指定消费组
        properties.put("group.id", "con-1");
        //创建Kafka消费者
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
        Collection<String> topic = new ArrayList<String>();
        topic.add("my-topic");
        consumer.subscribe(topic);
        while (true) {
            ConsumerRecords<String, String> poll = consumer.poll(Duration.ofSeconds(1));
            for (ConsumerRecord<String, String> stringStringConsumerRecord : poll) {
                System.out.println(stringStringConsumerRecord);
            }
        }
    }
}
ConsumerRecord(topic = my-topic, partition = 0, leaderEpoch = 0, offset = 4, CreateTime = 1670298623231, serialized key size = -1, serialized value size = 25, headers = RecordHeaders(headers = [], isReadOnly = false), key = null, value = hello java kafka producer)
7.1消费者扩展

上面的方式,我们只能消费消费者起来后的数据,没办法获取消费者起来之前的数据,需要进行改造。改成earliest

//开启自动提交offset功能,默认是开启的
properties.put("enable.auto.commit", "true");
//自动提交offset的时间间隔,单位是毫秒
properties.put("auto.commit.interval.ms", "5000");
​
/**
 * 正常情况下,Kafka消费数据的流程是这样的
 * 先根据group.id指定的消费组到kafka中查找之前保存的offset信息
 * 如果查找到了,说明之前使用这个消费组消费过数据,则根据之前保存的offset继续进行消费
 * 如果没查找到(说明第一次消费),或者查找到了,但是查找到的那个offset对应的数据已经不存在了
 * 这个时候消费者该如何消费数据?
 * (因为kafka默认只会保存7天的数据,超过时间数据会被删除,此时会根据auto.offset.reset的值执行不同的消费逻辑)
 * 这个参数有三个值:
 * 1.earliest:表示从最早的数据开始消费(从头消费)
 * 2.latest:默认。从最新的数据开始消费
 * 3.none:根据指定的group.id没有找到之前消费的offset信息,则抛出异常
 *///一般在实时计算场景,使用latest
properties.put("auto.offset.reset", "latest");
7.2、Consumer消费offset查询
  1. kafka0.9版本之前,消费者的offset信息保存在zookeeper中
  2. 从kafka0.9开始,使用了新的消费api,消费者的信息会保存在kafka里面的_consumer_offsets这个topic中

image-20221206122917707

7.3、Consumer消费顺序
  1. 当一个消费者消费一个partition时候,消费的数据顺序和此partition数据的生产顺序是一致的
  2. 当一个消费者消费多个partition时候,消费者按照partition的顺序,首先消费一个partition,当消费完一个partition最新的数据后再消费其他partition中的数据
  3. 一个消费者消费多个partition,只能保证消费的数据顺序在一个partition内是有序的
7.4、Kafka的三种语义
  1. 至少一次:at-least-once

    这种情况就存在消费多次,那么我们可以把properties.put("enable.auto.commit", "false");设置为false。在我们消费完之后,我们手动进行提交consumer.commitSync()

  2. 至多一次:at-most-once

  3. 仅一次:exactly-once