Kafka内容分享(四):Kafka 架构和幂等性与事务

目录

一、架构

1.1、Kafka重要概念

1.1.1、broker

1.1.2、zookeeper

1.1.3、producer(生产者)

1.1.4、consumer(消费者)

1.1.5、consumer group(消费者组)

1.1.6、分区(Partitions)

1.1.7、副本(Replicas)

1.1.8、主题(Topic)

1.1.9、偏移量(offset)

1.2、消费者组

二、Kafka生产者幂等性与事务

2.1、幂等性

2.1.1、简介

2.1.2、Kafka生产者幂等性

2.1.3、配置幂等性

2.1.4、幂等性原理

2.2、Kafka事务

2.2.1、简介

2.2.2、事务操作API

2.3、【理解】Kafka事务编程

2.3.1、事务相关属性配置

2.3.1.1、生产者

2.3.1.2、消费者

2.3.2、Kafka事务编程

2.3.2.1、需求

2.3.2.2、启动生产者控制台程序模拟数据

2.3.2.3、编写创建消费者代码

2.3.2.4、编写创建生产者代码

2.3.2.5、编写代码消费并生产数据

2.3.2.6、测试

2.3.2.7、模拟异常测试事务


一、架构

1.1、Kafka重要概念

1.1.1、broker
  • Kafka服务器进程,生产者、消费者都要连接broker
  • 一个Kafka的集群通常由多个broker组成,这样才能实现负载均衡、以及容错
  • broker是无状态(Sateless)的,它们是通过ZooKeeper来维护集群状态
  • 一个Kafka的broker每秒可以处理数十万次读写,每个broker都可以处理TB消息而不影响性能

1.1.2、zookeeper
  • ZK用来管理和协调broker,并且存储了Kafka的元数据(例如:有多少topic、partition、consumer)
  • ZK服务主要用于通知生产者和消费者Kafka集群中有新的broker加入、或者Kafka集群中出现故障的broker。

注意:Kafka正在逐步想办法将ZooKeeper剥离,维护两套集群成本较高,社区提出KIP-500就是要替换掉ZooKeeper的依赖。“Kafka on Kafka”——Kafka自己来管理自己的元数据

1.1.3、producer(生产者)
  • 生产者负责将数据推送给broker的topic
1.1.4、consumer(消费者)
  • 消费者负责从broker的topic中拉取数据,并自己进行处理
1.1.5、consumer group(消费者组)
  • consumer group是kafka提供的可扩展且具有容错性的消费者机制
  • 一个消费者组可以包含多个消费者
  • 一个消费者组有一个唯一的ID(group Id),配置group.id一样的消费者是属于同一个组中
  • 组内的消费者一起消费主题的所有分区数据

1.1.6、分区(Partitions)
  • 在Kafka集群中,主题被分为多个分区
  • Kafka集群的分布式就是由分区来实现的。一个topic中的消息可以分布在topic中的不同partition中

1.1.7、副本(Replicas)
  • 实现Kafkaf集群的容错,实现partition的容错。一个topic至少应该包含大于1个的副本
  • 副本可以确保某个服务器出现故障时,确保数据依然可用
  • 在Kafka中,一般都会设计副本的个数>1

1.1.8、主题(Topic)
  • 主题是一个逻辑概念,用于生产者发布数据,消费者拉取数据
  • 一个Kafka集群中,可以包含多个topic。一个topic可以包含多个分区
  • Kafka中的主题必须要有标识符,而且是唯一的,Kafka中可以有任意数量的主题,没有数量上的限制
  • 在主题中的消息是有结构的,一般一个主题包含某一类消息
  • 一旦生产者发送消息到主题中,这些消息就不能被更新(更改)

1.1.9、偏移量(offset)
  • offset记录着下一条将要发送给Consumer的消息的序号
  • 默认Kafka将offset存储在ZooKeeper中
  • 在一个分区中,消息是有顺序的方式存储着,每个在分区的消费都是有一个递增的id。这个就是偏移量offset
  • 偏移量在分区中才是有意义的。在分区之间,offset是没有任何意义的
  • 相对消费者、partition来说,可以通过offset来拉取数据

1.2、消费者组

  • 一个消费者组中可以包含多个消费者,共同来消费topic中的数据
  • 一个topic中如果只有一个分区,那么这个分区只能被某个组中的一个消费者消费
  • 有多少个分区,那么就可以被同一个组内的多少个消费者消费

二、Kafka生产者幂等性与事务

2.1、幂等性

2.1.1、简介

拿http举例来说,一次或多次请求,得到地响应是一致的(网络超时等问题除外),换句话说,就是执行多次操作与执行一次操作的影响是一样的。

如果,某个系统是不具备幂等性的,如果用户重复提交了某个表格,就可能会造成不良影响。例如:用户在浏览器上点击了多次提交订单按钮,会在后台生成多个一模一样的订单。

2.1.2、Kafka生产者幂等性

在生产者生产消息时,如果出现retry时,有可能会一条消息被发送了多次,如果Kafka不具备幂等性的,就有可能会在partition中保存多条一模一样的消息。

  • 生产者消息重复问题
    • Kafka生产者生产消息到partition,如果直接发送消息,kafka会将消息保存到分区中,但Kafka会返回一个ack给生产者,表示当前操作是否成功,是否已经保存了这条消息。如果ack响应的过程失败了,此时生产者会重试,继续发送没有发送成功的消息,Kafka又会保存一条一模一样的消息

2.1.3、配置幂等性
props.put("enable.idempotence",true);
2.1.4、幂等性原理

为了实现生产者的幂等性,Kafka引入了 Producer ID(PID)和 Sequence Number的概念。

  • PID:每个Producer在初始化时,都会分配一个唯一的PID,这个PID对用户来说,是透明的。
  • Sequence Number:针对每个生产者(对应PID)发送到指定主题分区的消息都对应一个从0开始递增的Sequence Number。

  • 在Kafka中可以开启幂等性
    • 当Kafka的生产者生产消息时,会增加一个pid(生产者的唯一编号)和sequence number(针对消息的一个递增序列)
    • 发送消息,会连着pid和sequence number一块发送
    • kafka接收到消息,会将消息和pid、sequence number一并保存下来
    • 如果ack响应失败,生产者重试,再次发送消息时,Kafka会根据pid、sequence number是否需要再保存一条消息
    • 判断条件:生产者发送过来的sequence number 是否小于等于 partition中消息对应的sequence

2.2、Kafka事务

2.2.1、简介

Kafka事务是2017年Kafka 0.11.0.0引入的新特性。类似于数据库的事务。Kafka事务指的是生产者生产消息以及消费者提交offset的操作可以在一个原子操作中,要么都成功,要么都失败。尤其是在生产者、消费者并存时,事务的保障尤其重要。(consumer-transform-producer模式)

2.2.2、事务操作API

Producer接口中定义了以下5个事务相关方法:

  • initTransactions(初始化事务):要使用Kafka事务,必须先进行初始化操作
  • beginTransaction(开始事务):启动一个Kafka事务
  • sendOffsetsToTransaction(提交偏移量):批量地将分区对应的offset发送到事务中,方便后续一块提交
  • commitTransaction(提交事务):提交事务
  • abortTransaction(放弃事务):取消事务

2.3、【理解】Kafka事务编程

2.3.1、事务相关属性配置
2.3.1.1、生产者
// 配置事务的id,开启了事务会默认开启幂等性
props.put("transactional.id", "first-transactional");
  • 生产者

    • 初始化事务
    • 开启事务
    • 需要使用producer来将消费者的offset提交到事务中
    • 提交事务
    • 如果出现异常回滚事务

如果使用了事务,不要使用异步发送

2.3.1.2、消费者
// 1. 消费者需要设置隔离级别
props.put("isolation.level","read_committed");
//  2. 关闭自动提交
props.put("enable.auto.commit", "false");
2.3.2、Kafka事务编程
2.3.2.1、需求

在Kafka的topic 「ods_user」中有一些用户数据,数据格式如下:

姓名,性别,出生日期
张三,1,1980-10-09
李四,0,1985-11-01

我们需要编写程序,将用户的性别转换为男、女(1-男,0-女),转换后将数据写入到topic 「dwd_user」中。要求使用事务保障,要么消费了数据同时写入数据到 topic,提交offset。要么全部失败。

2.3.2.2、启动生产者控制台程序模拟数据
# 创建名为ods_user和dwd_user的主题
bin/kafka-topics.sh --create --bootstrap-server node1.angyan.cn:9092 --topic ods_user
bin/kafka-topics.sh --create --bootstrap-server node1.angyan.cn:9092 --topic dwd_user
# 生产数据到 ods_user
bin/kafka-console-producer.sh --broker-list node1.angyan.cn:9092 --topic ods_user
# 从dwd_user消费数据
bin/kafka-console-consumer.sh --bootstrap-server node1.angyan.cn:9092 --topic dwd_user --from-beginning  --isolation-level read_committed
2.3.2.3、编写创建消费者代码

编写一个方法 createConsumer,该方法中返回一个消费者,订阅「ods_user」主题。注意:需要配置事务隔离级别、关闭自动提交。

实现步骤:

创建Kafka消费者配置

 Properties props = new Properties();
 props.setProperty("bootstrap.servers", "node1.angyan.cn:9092");
 props.setProperty("group.id", "ods_user");
 props.put("isolation.level","read_committed");
 props.setProperty("enable.auto.commit", "false");
 props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

创建消费者,并订阅 ods_user 主题

// 1. 创建消费者
public static Consumer<String, String> createConsumer() {
    // 1. 创建Kafka消费者配置
    Properties props = new Properties();
    props.setProperty("bootstrap.servers", "node1.angyan.cn:9092");
    props.setProperty("group.id", "ods_user");
    props.put("isolation.level","read_committed");
    props.setProperty("enable.auto.commit", "false");
    props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
    props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

    // 2. 创建Kafka消费者
    KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);

    // 3. 订阅要消费的主题
    consumer.subscribe(Arrays.asList("ods_user"));
    
    return consumer;
}
2.3.2.4、编写创建生产者代码

编写一个方法 createProducer,返回一个生产者对象。注意:需要配置事务的id,开启了事务会默认开启幂等性。

创建生产者配置

Properties props = new Properties();
props.put("bootstrap.servers", "node1.angyan.cn:9092");
props.put("transactional.id", "dwd_user");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

创建生产者对象

public static Producer<String, String> createProduceer() {
    // 1. 创建生产者配置
    Properties props = new Properties();
    props.put("bootstrap.servers", "node1.angyan.cn:9092");
    props.put("transactional.id", "dwd_user");
    props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
    props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

    // 2. 创建生产者
    Producer<String, String> producer = new KafkaProducer<>(props);
    return producer;
}
2.3.2.5、编写代码消费并生产数据

实现步骤:

  1. 调用之前实现的方法,创建消费者、生产者对象
  2. 生产者调用initTransactions初始化事务
  3. 编写一个while死循环,在while循环中不断拉取数据,进行处理后,再写入到指定的topic
    (1) 生产者开启事务
    (2) 消费者拉取消息
    (3) 遍历拉取到的消息,并进行预处理(将1转换为男,0转换为女)
    (4) 生产消息到dwd_user topic中
    (5) 提交偏移量到事务中
    (6) 提交事务
    (7) 捕获异常,如果出现异常,则取消事务
public static void main(String[] args) {
    Consumer<String, String> consumer = createConsumer();
    Producer<String, String> producer = createProducer();
    // 初始化事务
    producer.initTransactions();

    while(true) {
        try {
            // 1. 开启事务
            producer.beginTransaction();
            // 2. 定义Map结构,用于保存分区对应的offset
            Map<TopicPartition, OffsetAndMetadata> offsetCommits = new HashMap<>();
            // 2. 拉取消息
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(2));
            for (ConsumerRecord<String, String> record : records) {
                // 3. 保存偏移量
                offsetCommits.put(new TopicPartition(record.topic(), record.partition()),new OffsetAndMetadata(record.offset() + 1));
                // 4. 进行转换处理
                String[] fields = record.value().split(",");
                fields[1] = fields[1].equalsIgnoreCase("1") ? "男":"女";
                String message = fields[0] + "," + fields[1] + "," + fields[2];
                // 5. 生产消息到dwd_user
                producer.send(new ProducerRecord<>("dwd_user", message));
            }
            // 6. 提交偏移量到事务
            producer.sendOffsetsToTransaction(offsetCommits, "ods_user");
            // 7. 提交事务
            producer.commitTransaction();
        } catch (Exception e) {
            // 8. 放弃事务
            producer.abortTransaction();
        }
    }
}
2.3.2.6、测试
2.3.2.7、模拟异常测试事务
// 3. 保存偏移量
offsetCommits.put(new TopicPartition(record.topic(), record.partition()),new OffsetAndMetadata(record.offset() + 1));
// 4. 进行转换处理
String[] fields = record.value().split(",");
fields[1] = fields[1].equalsIgnoreCase("1") ? "男":"女";
String message = fields[0] + "," + fields[1] + "," + fields[2];

// 模拟异常
int i = 1/0;

// 5. 生产消息到dwd_user
producer.send(new ProducerRecord<>("dwd_user", message));

启动程序一次,抛出异常。
再启动程序一次,还是抛出异常。
直到我们处理该异常为止。

我们发现,可以消费到消息,但如果中间出现异常的话,offset是不会被提交的,除非消费、生产消息都成功,才会提交事务。

  • 28
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 《深入理解kafka:核心设计与实践原理》是一本介绍Kafka的书籍,主要涵盖了Kafka的核心设计和实践原理。Kafka是一个分布式的消息队列系统,被广泛应用于大数据领域。本书从Kafka的基本概念入手,详细介绍了Kafka架构、消息存储、消息传输、消息消费等方面的内容。同时,本书还介绍了Kafka的高级特,如事务、流处理、安全等方面的内容。对于想要深入了解Kafka的读者来说,这本书是一本不可多得的好书。 ### 回答2: ### 回答3: Kafka是一个高能的分布式消息系统,可以承载海量数据流,支持高可靠、高吞吐量的消息传递。它具有良好的扩展、稳定和可管理,在现代数据架构中占据了非常重要的地位。本文将深入探讨Kafka的核心设计与实践原理,让读者更全面地了解这个流行的消息系统。 1. 消息模型 Kafka的消息模型以消息为中心,将数据分为多个Topic,每个Topic可以有多个Partition。Producer将消息发送到指定的Topic,Consumer可以订阅特定的Topic并接收其中的消息。在每个Partition中,Kafka将消息以offset为单位进行存储,保证数据的可靠和顺序。 2. 存储机制 Kafka使用分布式的文件存储机制,将消息以Segment为单位进行存储。每个Segment包含一个或多个消息,使用mmap技术将数据加载到内存中,提高读写速度。Kafka还支持消息的压缩和索引优化,使得数据的存储更加高效。 3. 管理机制 Kafka的管理机制由Controller、Broker、Zookeeper三个组件构成。Controller负责管理整个Kafka集群的状态和各个Broker之间的主从关系,Broker则负责存储消息和处理数据。而Zookeeper则提供了集群的元数据管理和Leader选举功能。 4. 能优化 Kafka通过异步IO和Zero-copy等技术提高数据的读写能,同时支持消息的批量处理和预取机制,减少磁盘操作和网络开销。此外,Kafka还支持动态分区和分区再平衡等高可用机制,确保数据的可靠和可用。 总之,深入理解Kafka的核心设计和实践原理,可以帮助用户更好地应用这一消息系统,提升系统的可靠能。同时,了解Kafka的原理也有助于用户更好地进行系统的调优和排错,提高系统的稳定和可扩展

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

之乎者也·

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值