Kafka自学笔记(架构、producer运行原理)

Kafka相关概念

kafka定义

  1. 分布式、发布/订阅模式、消息队列、实时处理。
  2. 分布式、事件流平台、数据管道、流分析、数据集成。

消息队列(”数据超市“)

消息队列 - 三大特点

1)缓冲/消峰

解决数据流的产生速度与处理速度不一致的问题,控制数据流经过系统的速度,将其缓存在消息队列中。

​ 2)解耦

​ 允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。(可以将消息队列中的数据进行处理分类,供消费者选择)

​ 3)异步通信

​ 允许用户把一个消息放入队列,但并不立即处理它,然后在需要的时候再去处理它们。(根据用户需求,侧重点处理数据,不必要的数据放到消息队列中异步处理,提高效率)

小结:可以把消息队列理解为一个超市,这个超市有三个特点:

  1. 根据消费者的消费能力,控制货物的输出,不让消费者来不及消费;
  2. 将货物进行分类、清洗,让消费者可以根据需求获取数据;
  3. 可以帮消费者进行代购,提高消费者的消费效率;

消息队列 - 两种模式

1)点对点模式

  • 消费者主动拉取数据,消息收到后清除消息

2)发布/订阅模式

  • 队列中,可以有多个topic主题的一批批数据(如浏览、点赞、收藏、评论等);
  • 消费者消费数据后,不删除数据

Kafka架构

架构图

  • Producer:消息生产者,就是向 Kafka broker 发消息的客户端。
  • Consumer:消息消费者,向 Kafka broker 取消息的客户端。
  • Consumer Group(CG):消费者组,由多个 consumer 组成。消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费;消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
  • Broker:一台 Kafka 服务器就是一个 broker。一个集群由多个 broker 组成。一个 broker 可以容纳多个topic。
  • Topic:可以理解为一个队列,生产者和消费者面向的都是一个topic。
  • Partition:为了实现扩展性,一个非常大的 topic 可以分布到多个 broker(即服务器)上,一个 topic可以分为多个partition,每个 partition 是一个有序的队列。
  • Replica:副本。一个 topic 的每个分区都有若干个副本,一个 Leader 和若干个 Follower。
  • Leader:每个分区多个副本的“主”,生产者发送数据的对象,以及消费者消费数 据的对象都是 Leader。
  • Follower:每个分区多个副本中的“从”,实时从 Leader 中同步数据,保持和 Leader 数据的同步。Leader 发生故障时,某个 Follower 会成为新的 Leader。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

小结

  1. kafka分为三大部分:producer、cluster、consumer。
  2. cluster => 由于要处理大量数据,所以kafka集群cluster中是采用分布式,将数据存储到多个broker(类似于Hadoop的节点服务器)中,进行存储处理。
  3. consumer => 由于cluster是以分区存储,所以为了协调以提高效率,消费者进行分组,提高消费效率,注意每个消费者只能消费一个数据分区的数据。
  4. 为了防止broker挂掉,数据丢失,所以会在不同的broker中备份数据,同时将其分为leader数据与follower数据,消费者优先消费leader数据。leader数据丢失时,follow变成新的leader数据。
  5. leader记录在与kafka连接的zookeeper中。
  6. product与consumer源码是java,cluster中的broker源码是scala;
  7. kafka2.8以后可以不连接zookeeper。

Kafka - 命令

命令 - Topic

通过bin/kafka-topics.sh脚本中的命令实现。

  • 连接
 bin/kafka-topics.sh --bootstrap-server master:9092
  • 指定topic名称
bin/kafka-topics.sh --bootstrap-server master:9092 --topic myTopic 
  • 操作

    • 查:

      1. –list(查看topic)

      2. –create(创建topic主题)

        –partitions 分区数x

        –replication-factor 分区副本数x

      3. –alter(编辑topic参数)

        –partitions 分区数x

      4. delete(删除topic)

    2)查看当前服务器中的所有 topic

    bin/kafka-topics.sh --bootstrap-server  hadoop102:9092 --list 
    

    3)创建 first topic

    bin/kafka-topics.sh --bootstrap-server  
    
    hadoop102:9092 --topic first --create --partitions 1 --replication-factor 3 
    

    选项说明:

    ​ --topic 定义 topic 名

    ​ --replication-factor 定义副本数

    ​ --partitions 定义分区数

    4)查看 first 主题的详情

    bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --topic first --describe 
    

    5)修改分区数(注意:分区数只能增加,不能减少

    bin/kafka-topics.sh --bootstrap-server  hadoop102:9092 --topic first --alter --partitions 3 
    

    6)再次查看 first 主题的详情

    bin/kafka-topics.sh --bootstrap-server  hadoop102:9092 --describe --topic first 
    

    7)删除 topic

    bin/kafka-topics.sh --bootstrap-server  hadoop102:9092  --topic first --delete
    

命令 - Product

bin/kafka-console-producer.sh脚本,product用于向broker中的topic发送数据。)

  • 发送消息
bin/kafka-console-producer.sh --bootstrap-server master:9092 --topic first
//向broker中的topic发送数据

命令 - consumer

bin/kafka-console-producer.sh脚本,用于向broker消费存入的数据。)

  • 接收数据
bin/kafka-console-console.sh --bootstrap-server master:9092 --topic first
//接收broker中的数据

--from-beginning
//该选项会获取对应的topic的所有历史数据

运行效果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

小结:

  • productor是用来产生数据的,产生的数据会放置在broker的topic中,等待消费者消费
  • 总体的使用流程就是,需要创建produtor和consumer,一个产生数据,一个对数据进行消费。

Kafka Producer

(生产者将外部数据发送到broker的具体过程)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

工作原理

  • main线程
    1. 生产者中有一个main线程(加工与筛选数据)
    2. 调用send方法,传递数据
    3. 随后调用拦截器,筛选或加工数据(工作少)
    4. 调用序列化器,不用Java的序列化器,原因是数据量大。
    5. 调用分区器,数据发送到集群中的broker,其中有分区与副本的概念,分区器会负责制定规则,将大量数据发送到哪个分区给消费者消费。(一个分区创建一个队列,一个broker的总分区位置是内存,大小32M,16k一批)。
    6. 可以把分区器看成一个缓存区空间(一个个的缓存队列与内存池双端队列),里面有多个分区,这些分区的数据未来会由下一个线程负责发送到对应的broker及其对应分区
  • sender线程
    1. 启动sender线程(负责读取main处理后的数据,随后发送给集群)
    2. 每次只有当一批次装满了之后,sender才会开始拉数据,如果一批次中数据迟迟未装满,可以设置等待时间,让批次未装满的数据也可以被拉取。(相关参数:batch.size默认16k,linger.ms单位ms默认是0没有延迟,此时batch.size参数无效,来一点就马上发送。此处参数与调优的吞吐量有关。)
    3. sender拉取数据的条件是当一批次数据达到batch.size大小时,或未达到时,延迟达到了linger.ms所设置的ms。
    4. sender拉取数据的方式:将数据以键值对的形式发送,以节点为键,以消息请求为值。其中又将一个节点中,发送到同一个broker的同一个分区的数据分成一个消息队列。
    5. 发送时,对应的broker可能不会对发送的数据进行应答,这种情况下仍然可以拉取第二个数据进行发送,最多接受默认5个数据未应答(request)。达到第6个时,sender不会再拉取数据。此处整个过程在sender的NetworkClient中。(此处与应答机制有关)
    6. sender的底层是需要打通IO链路(selector),相当于公路,数据相当于交通工具。
    7. 应答机制:数据发送给broker后,集群会发给selector一个应答ack(0表示告诉product不需要等集群是否将数据落盘安置完毕直接发送下一个;1表示需要等leader的应答不需要等flower;-1表示需要等leader和flower应答)
    8. 若集群的应答是成功,则清理累积的request和分区器中的数据(理解为发生的数据已经备份好在集群了,可以清理了)
    9. 若集群的应答是失败,可以重试(retries),次数相当于Int的最大值,死磕到成功为止。(该参数可以设置)

小结:

  • 原理中的总体流程:main线程中处理数据,最后将数据放在分区器中等待sender线程的拉取,sender线程会将数据发送给对应的broker。

producter - Send

异步发送

  • 定义:

    外部数据无需关注kafka集群的回应,就可以一直向productor发送数据的一个过程。

  • 创建生产者的流程:

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;

public class CustomProducer {
    public static void main(String[] args) {
        // TODO: 设置配置信息
        // 1) 上下文环境的配置信息
        Properties properties = new Properties();
        // 2) 连接集群(必须配置)
      properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"master:9092");
        // 3) 指定对应的key和value序列化类型(供后续的序列化器使用,必须配置)
        // String的序列化类型对应的类为:StringSerializer
        // StringSerializer.class.getName()为获取包名
  properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
  properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());

        // TODO: 创建生产者对象
        // 数据格式:“” hello  (键值为null)
        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);

        // TODO: 发送数据
        // ProducerRecord 可以指定发送的主题、分区、数据的键值等参数
        for (int i = 0; i < 5; i++) {
            kafkaProducer.send(new ProducerRecord<>("atguigu","hello"+ i ));
        }

        // TODO: 关闭资源
        kafkaProducer.close();

    }
}

小结:

  • 代码思路:

–> 创建生产者对象(需要含配置信息的参数,此时会自动分配资源给生产者对象)

–> 需要传入java的上下文环境对象,并配置好集群信息与键值对的序列类型

–> 调用send方法,在主线程中发送数据

–> 关闭生产者对象分配的资源

带回调函数异步发送

send有一个callback函数参数,用来返回主题、分区等信息。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 代码实现
// TODO: 发送数据
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<>("atguigu", "hello" + i), new 			Callback() {
                @Override
	public void onCompletion(RecordMetadata metadata, Exception exception) {
  	//若发送成功:
    if (exception==null){
    System.out.println("主题:"+ metadata.topic() + " 分区:" + 											metadata.partition());    
  		}      
	}
});
        
}

代码思路:给send方法添加参数,重写callback实现类函数(可以用lambda):

同步发送

等main与sender线程将数据完整发送给集群后,才会发送下一批外部数据。

  • 代码实现

    // TODO: 发送数据
            for (int i = 0; i < 5; i++) {
                try {
                    kafkaProducer.send(new ProducerRecord<>("atguigu", "hello" + i), new Callback() {
                        @Override
                        public void onCompletion(RecordMetadata metadata, Exception exception) {
                            if (exception==null){
                                System.out.println("主题:"+ metadata.topic() + " 分区:" + metadata.partition());
                            }
                        }
                    }).get();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
    

    在send方法使用后调用get()即可。

分区器 - Partition

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

优点与作用

  • 便于合理存储资源:
    • 将大量的数据分割,合理化使用服务器的内存资源。
    • 可以根据需要以及具体服务器的内存大小,设置数据的分配流向。
  • 提高并行度:
    • 也就是提高大量的数据IO速度,分区的概念可以让多个producer与consumer同时存入与消费数据,提高效率。

分区策略

  • 若发送数据时,指定了发送数据的分区,则优先使用;

  • 若没有指定,但在实例化[ProducerRecord](public ProducerRecord( String topic,
    Integer partition, K key, V value))时,存在健,则根据键的哈希值选择一个分区;

  • 若均没有,则采用Sticky Partition(黏性分区器),会随机选择一个分区,并尽可能一直

    使用该分区,待该分区的batch已满或者数据发送已完成,Kafka再随机一个分区进行使用(和上一次的分区不同,随机到上一个分区,就会再随机一次直到不同)。

  • 分区策略由send的参数new ProducerRecord<>()决定:

public ProducerRecord(@NotNull String topic,
                      p partition,
                      K key,
                      V value)

自定义分区器

根据需求,对数据进行筛选,满足要求的放到指定的分区中。

  • 实现步骤:

    1)定义类,实现Paritioner接口。

    2)重写partition()方法,该方法返回分区号,相当于指定了producerrecord构造器中的分区号。

  • 代码实现:

    1)自定义分区类:

package producer;
import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import java.util.Map;

public class MyPartitioner implements Partitioner {
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        // 获取数据:
        String msgValues = value.toString();
        int partition = 0;
        if (msgValues.contains("hello")) partition=1;
        else partition=0;
        return partition;
    }
    @Override
    public void close() {
    }
    @Override
    public void configure(Map<String, ?> configs) {
    }
}

​ 2)与自定义关联:

package producer;

import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
import java.util.concurrent.ExecutionException;

public class CustomProducer03_Partitions {
    public static void main(String[] args) {
        // TODO: 设置配置信息
        Properties properties = new Properties();
 properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"master:9092");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); 										      properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
        
        // 关联自定义分区器
  properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"producer.MyPartitioner");
        // TODO: 创建生产者对象
        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(properties);
        // TODO: 发送数据
        for (int i = 0; i < 5; i++) {
            try {
                kafkaProducer.send(new ProducerRecord<>("first","f","zzz" + i), new Callback() {
                    @Override
                    public void onCompletion(RecordMetadata metadata, Exception exception) {
                        if (exception==null){
                            System.out.println("主题:"+ metadata.topic() + " 分区:" + metadata.partition());
                        }
                    }
                }).get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

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

小结:

在企业中,自定义分区器可以过滤脏数据到指定分区中,常用,非常重要。

分区器缓存区

  • 分区器缓存区(RecordAccumulator)是producer的main线程sender交接的地方,默认大小是32M。
  • 其中由一个个消息队列(DQuene)组成,每个队列代表即将发往对应分区的数据。
  • 一批次(ProducerBatch)发送给sender的大小默认是16K。
  • 相关的重要参数:
    • batch.size控制批次大小;
    • linger.ms控制一批次的数据数据拉取延迟(单位为ms);
    • compression.type:压缩(snappy)数据;
    • RecordAccumulator:缓冲区大小;
  • 利用该区的参数来提高producer的数据吞吐量的方法:

    1)适当提高batch.size,增大一批次的数据量;

    2)适当增长延迟,尽量等一批次装备完再拉取数据(一般设置为5~100ms)

    3)适当提高缓冲区大小,使足够多的并行度可行,例如设置为32M。

    4)压缩数据。

  • 代码实现:

            // todo 0.配置信息
            Properties properties = new Properties();
            // 连接
            properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "master:9092");
            // 序列化
            properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
            properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
            // 设置缓冲区大小(buffer.memory)
            properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
    
            // 批次大小(batch.size)
            properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
    
            // 延迟时长(linger.ms)
            properties.put(ProducerConfig.LINGER_MS_CONFIG, 1);
    
            // 压缩类型(comperssion)
            // compression.type:压缩,默认 none,可配置值 gzip、snappy、lz4 和 zstd
            properties.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
    

生成经验 - 数据可靠性

也就是当数据被sender发送到集群中,数据的落盘情况是如何,有三种情况:

​ 1)不知道集群的leader和flower是否已经将数据落盘完毕;

​ 2)只知道leader将数据落盘完毕;

​ 3)能知道数据被leader和flower都落盘完毕。

这三种需求情况由 acks 参数的值0、1、-1(all)控制。

  • 三种情况的可靠性分析:

    • acks=0,生产者发送过来数据就不管了,可靠性差,效率高;

      数据可靠性分析:

      producer发送数据给集群后,不管leader与flower是否已经备份完毕,都会继续发下下一批次的数据,并且会将上一个批次的数据默认为发送到位后直接丢弃,若leader落盘失败或broker挂掉,则直接导致数据丢失。

    • acks=1,生产者发送数据后只等Leader应答,可靠性中等,效率中等;

      数据可靠性分析:

      这种情况可以保证leader已经落盘完毕,但无法保证flower是否落盘成功,当leader所在的broker挂掉时,如flower没有备份成功,则会导致数据没有备份,数据丢失无法恢复。

    • acks=-1/all,生产者发送数据后要等Leader和 [ISR](Leader维护了一个动态的in-sync replica set(ISR),意为和 Leader 保持同步的 Follower+Leader 集合(leader:0,isr:0,1,2)。 如果Follower长时间未向Leader发送通信请求或同步数据,则该Follower将被踢出ISR。该时间阈值由 replica.lag.time.max.ms 参数设定,默认30s。例如2超时,(leader:0, isr:0,1)。 这样就不用等长期联系不上或者已经故障的节点。 ) 队列里面所有Follwer应答,可靠性高,效率低;

      数据可靠性分析:

      当ISR队列只剩下一个副本时,acks=-1与acks=1的情况一直,当ISR队列副本大于或等于2时,可靠性最高。

      数据重复的情况分析

      当集群将发送的数据备份到follower后,准备向producer应答时,leader出现故障,此时producer会默认数据发送失败,将follower设置为新的leader,再落盘数据,此时会出现一个broker有重复数据的情况,重复的数据需要到后续处理。

  • 代码实现

		// 应答(acks)
        properties.put(ProducerConfig.ACKS_CONFIG,"-1");

        // 应答失败后的重试次数(retries)
        properties.put(ProducerConfig.RETRIES_CONFIG,)

小结:

1)数据完全可靠条件 =

ACK级别设置为-1 + 分区副本大于等于2 + ISR里应答的最小副本数量大于等于2

2)在生产环境中,acks=0很少使用;acks=1,一般用于传输普通日志,允许丢个别数据;而acks=-1,一般用于传输和钱相关的数据对可靠性要求比较高的场景。

3)另外的参数:

​ 当应答发送失败时,会进行重试,这个重试有次数限制可以设置,默认是INT类型的最大值,可以修改。

生产经验 - 数据单一性

数据重复原因是当acks的级别为-1时出现的情况。

数据传递语义 - 概念引入

1)至少一次(AtLeast Once) :

ACK级别设置为-1 + 分区副本大于等于2 + ISR里应答的最小副本数量大于等于2

2)最多一次(At Most Once):

ACK级别设为0

3)精确一次(Exactly Once,使用kafka 0.11版本引入的幂等性事务):

对于一些非常重要的信息,比如和钱相关的数据,要求数据既不能重复也不丢失。

幂等性:

保证同一个会话中,同一个broker的一个分区中不会出现重复数据。

  • 原理

    1)幂等性就是指Producer不论向Broker发送多少次重复数据,Broker端都只会持久化一条,保证了不重复。

    2)精确一次 (Exactlv Once) = 幂等性 + AtLeast Once

    3)重复数据的判断标准:具有 <PID,Partition, SeqNumber> 相同主键的消息提交时,Broker只会持久化一条。其中 PID 是 Kafka 每次重启都会分配一个新的;Partition 表示分区号; Sequence Number是单调自增的。

    4)所以幂等性只能 保证在单分区单会话内不重复

  • 代码配置:

    enable.idempotence 默认为 true,false 关闭。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

事务:

  • 原理

kafka中的事务,是一种用于将多个 Kafka 操作(例如生产者发送消息或消费者读取消息)组合成原子操作的机制。它提供了可靠的消息传递和数据一致性,确保在多个主题和分区之间执行的操作要么全部成功,要么全部失败。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 相关概念
  1. 事务性生产者:事务性生产者是指能够参与到事务中的生产者。使用事务性生产者,您可以将一系列发送到不同主题和分区的消息组合到一个事务中。如果事务中的任何一个操作失败,整个事务将被回滚,所有已发送的消息都将撤消。
  2. 事务协调器:事务协调器是 Kafka 集群中的一个特殊角色,负责协调和管理事务。它为每个事务分配唯一的事务 ID,并跟踪每个事务中的所有参与者(例如生产者和消费者)。
  3. 事务性日志:事务性生产者将事务中的操作写入事务性日志。事务日志记录了事务的开始、提交或回滚等操作。这些日志条目与 Kafka 的消息一起存储在主题中。
  4. 事务隔离:Kafka 采用分区级别的事务隔离。这意味着在同一个分区中的消息只能由一个事务处理,不同分区之间的事务不会相互影响。这样可以确保消息在分区之间的顺序和原子性。
  5. 事务提交:当事务中的所有操作(例如消息发送)都成功完成后,事务性生产者将提交事务。提交后,事务性日志中的相关条目将被标记为已提交,并且消息将变为可消费。此时,消费者可以开始读取并处理这些消息。
  6. 幂等性:事务性生产者的幂等性是指同一条消息重复发送时的结果与发送一次的结果相同。在 Kafka 中,事务性生产者默认启用了幂等性,确保消息在发送过程中不会被重复或乱序处理,即使遇到网络故障或重试情况。
  • 事务执行的流程:(每发送一个消息,就提交一次事务,发送一个消息的动作为一个事务)
    0. 自定义事务ID(供出错恢复时等情况使用)
    1. 初始化事务;
    2. 开启事务;
    3. 在事务内提交已经消费的偏移量(主要用于消费者)
    4. 提交事务;
    5. 放弃事务(当出错时,撤回事务操作)
  • 代码:
        // todo 1.创建producer对象
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
        // 事务操作
        // a.初始化事务
        producer.initTransactions();

        // b.开启事务(接下来需要进行发送数据操作)
        producer.beginTransaction();
        // todo 2.发送数据
        try {
            for (int i = 0; i < 5; i++) {
                producer.send(new ProducerRecord<>("first", "hello:" + i));
            }
            //模拟提交失败的情况(则在同一个事务中将发送的数据无论是否发送到一半都会撤回或停止发送)
            int i = i/0;
            
            // c.提交事务(同一个broker和分区的所有数据发送完毕后,则提交)
            producer.commitTransaction();
        }catch (Exception e){
            // d.撤回事务(若发生错误)
            producer.abortTransaction();
        }

        // todo 3.关闭资源
        producer.close();

小结:

当发送数据的操作,以事务的方式提交了之后,就在每次的会话中,不会产生重复数据。原因是什么?

  1. 事务的原子性,可以通过事务ID,保证当发送数据的操作即使在当前会话中断,也会在事务存储主题中保存下来,供下次其他会话开启时,根据事务ID继续上一次未完成的操作。
  2. 当数据发送过来的时候,如果其中的某一流程出现错误,在提交之前,都可以通过撤回事务,撤销之前的操作,防止后续产生的副作用。
  3. Q:如果一个数据发送提交完毕后,下一个一样的数据发送过来,事务机制如何知道这个数据是重复的从而不让其重复存储在主题的分区中?通过幂等性机制
  4. 数据安全总结:

1)不同会话间的数据非重复性可以通过事务机制进行保证;

2)同一个会话的数据非重复性可以通过幂等性机制保证;

3)而数据的可靠性(即保证数据在发送过程中不会丢失)则由[AtLeast Once][ACK级别设置为-1 + 分区副本大于等于2 + ISR里应答的最小副本数量大于等于2]保证,他们互保证了数据的安全;

4)如果这数据在这三个条件都不会达成,则会一直retries,数据一直在分区器中等待sender线程的拉取,直到AtLeast Once的条件达到为止。

生产经验 - 数据顺序

数据乱序的原因

  • 由于在producer的sender中的NetworkClient是缓存失败请求的地方,当失败请求达到五个时,数据将不会再发送数据。
  • 这个机制会导致前面的数据失败了,后面的数据请求成功了,形成后面的数据比前面的数据先发送的情况,造成数据乱序。

保证数据有序

即保证consumer在消费数据时,数据是有序的被其消费的。

  • 单分区内有序:

    启用幂等后,kafka服务端会缓存producer发来的最近5个request的元数据,并根据幂等中的单调递增函数,对发送完毕的五个数据进行重新排序。所以这样无论如何,都可以保证最近5个request的数据都是有序的。

  • max.in.flight.requests.per.connection参数:

    设置最大允许请求失败数。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 未开启幂等性

**max.in.flight.requests.per.connection **需要设置为 1,这个参数是设置单分区最多能等待的请求数,最多允许多少个请求失败也可以继续发送下一个数据。设置为1保证了每次等前一个请求就算失败,也要等待到其发送完成,才进行下一个数据的发送,避免插队。

  • 开启幂等性

max.in.flight.requests.per.connection 需要设置小于等于 5。

原因说明:因为在kafka1.x以后,启用幂等后,kafka服务端会缓存producer发来的最近5个request的元数据,故无论如何,都可以保证最近5个request的数据都是有序的。

  • 多分区间有序:

    可以将数据发送后统一到一个中间空间中,比如streaming的窗口等,排好序后再送给consumer消费。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值