kafka 基础知识 第三讲

本文深入探讨了Kafka的数据可靠性和一致性,介绍了如何实现幂等性,包括幂等性解决的问题、开启幂等性的步骤及其实现机制。此外,还讲解了Kafka的事务应用、高水位线概念以及高性能高可用的原理,如顺序写、零拷贝和日志分段存储。最后,讨论了关键参数的设置对确保数据一致性和可靠性的意义。

1.数据可靠性和一致性

数据可靠性-不丢失
一致性-读写一致性
可靠性:
1.topic 分区副本:kafka的分区多副本是kafka的可靠性保证的核心,把消息写入到多个副本kafka某个broker挂了也保证可靠性
2.producer 往broker发送消息:kafka在produce里面提供了消息确认机制(ack)ack=0,ack=1,ack=-1。
在这里插入图片描述
如果原来leader挂了,旧的8,9的可能被丢失了。isr。按顺序找leader。osr就从新的leader同步数据。当原来的leader挂了之后,会保留hw,水位线,然后重启把水位线后面的截取掉。再同步。
真正的可靠性是:ack=all/-1,然后配合min.insync.replicas 参数结合在一起。
3.isr同步副本列表以及leader选举机制
ISR(同步副本)每个分区的leader会维护一个isr列表,isr列表里面就是follower副本的broker编号,只有跟的leader的follower副本才能加入isr里面
只有ISR的成员才能选举成leader.
4.高水位线
hw(已经提交的最小值),当leader挂了之后,在其重新启动后,要清楚之前多余的数据跟现在的leader保持一致(offset)。保证数据的一致性。
5.不清洁选举
当不清洁选举成为true。不是isr的副本也可以竞选leader,当它成为leader的时候就会出现数据不一致性。followe的leo不能大于leader副本的
在这里插入图片描述
不能设置为true。
保证数据可靠性和一致性
producer:设置ack=-1
topic 设置 副本数大于3.
设置不清洁选举为false 设置unclean.leader.election.enable=false

2.kafka如何实现幂等性

2.1 幂等性解决的问题

当生产者发送消息失败重试的时候,可能是会出现重复写入。

2.2 如何操作开启kafka幂等性

开启幂等性

 pros.put('enable.idempotence',true)

在开启幂等性:
重启次数retires>0
max.in.flight.requests.per.connection<=5 飞行中请求
ack=-1
configException异常

2.2 kafka 幂等性实现机制

1.每个producer在初始化会生成一个producer_id并为每个目标partition维护一个序列号。(重试的序列号不变)
2.producer 每发送一条消息,会将<producer_id,分区> 对应的序列号+1
3.broker也是维护的序列号,每收到一条消息,会判断服务端会对新老消息的序列号对比。如果序列号不变,说明重复写入,直接丢弃,如果旧的大于新的数据。
在这里插入图片描述
producer.send(“aaa”)–aaa拥有了一个唯一的序列号
如果这条消息发送失败,produce内部重试,此时序列号不变
producer.send(“aaa”) ----aaa拥有新的序列号

3.kafka 事务

3.1 kafka 事务应用场景

实际使用是consumer->transform->produce,事务。
在这里插入图片描述

3.2 事务api

为了实现事务,程序必须提供transtional.id 并且幂等性

 pro.put("transtional.id","transtionalid001")
 pros.put('enable.idempotence',true)
 pros.setProperty(ProducerConfig.TRANSACTION_TIMEOUT_CONFIG)

事务实验
在这里插入图片描述


import org.apache.kafka.clients.consumer.*;
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.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;

import java.time.Duration;
import java.util.*;

public class KafkaTransactionDemo {

    private static final String SERVERS="dev0:9092,dev1:9092,dev2:9092";
    public static void main(String[] args) {

      //构造一个消费者去拉取数据
        Properties pros = new Properties();
        pros.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,SERVERS);
        pros.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest");
        pros.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        pros.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
        pros.put(ConsumerConfig.GROUP_ID_CONFIG,"g21");
        pros.put(ConsumerConfig.CLIENT_ID_CONFIG,"c1");


        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(pros);
        consumer.subscribe(Arrays.asList("doit18"));

        //构建生产者
        Properties pros2 = new Properties();
        pros2.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        pros2.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
        pros2.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG,"txn000000001");
        pros2.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG,true);
        pros2.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,SERVERS);

        //
        KafkaProducer<String, String> producer = new KafkaProducer<>(pros2);
        //初始化事务
        producer.initTransactions();
        while(true) {
            try {


                //消费者订阅主题拉取数据
                ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));

                //获取本消费者所分配的分区
                Set<TopicPartition> assignment = consumer.assignment();


                //如果本次没有拉取到,继续下次循环
                if (records.isEmpty()) continue;
                //构造记录偏移量的hashmap 记录分区和偏移量
                HashMap<TopicPartition, OffsetAndMetadata> offsets = new HashMap<>();

                //开启事务
                producer.beginTransaction();
               //遍历每个分区,来处理分区内的数据
                for (TopicPartition topicPartition : assignment) {
                    //获取当前指定分区的数据

                    List<ConsumerRecord<String, String>> partitionRecords = records.records(topicPartition);
                    //做一些业务逻辑的处理
                    //遍历分区中每一条数据
                    for (ConsumerRecord<String, String> record : partitionRecords) {
                        //取出数据
                        String newValue = record.value().toUpperCase(Locale.ROOT);
                        //构造新的数据
                        ProducerRecord<String, String> result = new ProducerRecord<>("app_processed", record.key(), newValue);

                        //将结果写出
                        producer.send(result);
                    }
                    long lastConsumerOffset = partitionRecords.get(partitionRecords.size() - 1).offset();
                    offsets.put(topicPartition, new OffsetAndMetadata(lastConsumerOffset + 1));

                }

                //提交偏移量
                producer.sendOffsetsToTransaction(offsets, "g21");

                //提交事务
                producer.commitTransaction();
            } catch (Exception e) {
                //如果有异常放弃事务回滚
                producer.abortTransaction();
            }
        }

    }
}

4. kafka的高性能高可用原理

1.顺序写
Kafka仅仅是追加数据到文件末尾,磁盘顺序写,性能极高。
在这里插入图片描述
2.零拷贝
生产者把消息发送到KAFKA, 写入到内存,定期的刷写到磁盘。消费者在消费消息的时候,先从内存开始找,如果没有找到那么直接从磁盘把数据读到内存中。然后直接发送到网卡
在这里插入图片描述
3.日志分段存储
每个分区对应的目录,就是“topic-分区号”的格式,比如说有个topic叫做“order-topic”,那么假设他有3个分区,每个分区在一台机器上,那么3台机器上分别会有3个目录,“order-topic-0”,“order-topic-1”,“order-topic-2”。每个分区目录里面就是很多的log segment file,也就是日志段文件。
分区目录下有三类文件:
1.index --索引文件
2.log --kafka里面的数据文件,一条数据叫一个message --默认一个log文件有一个G
3.time index --时间索引文件
136371195之类的数字,就是代表了这个日志段文件里包含的起始offset
4.日志二分查找
日志段文件,.log文件会对应一个.index和.timeindex两个索引文件。而且索引文件里的数据是按照位移和时间戳升序排序的,所以kafka在查找索引的时候,会用二分查找,时间复杂度是O(logN),找到索引,就可以在.log文件里定位到数据了。

5.常见参数

在这里插入图片描述
LEO 日志末尾下一条消息
HW 高水位。isr的中最小的leo。消费者最多消费到
lso:对未完成的事务,lso是事务中第一条消息的位置。对已经完成的和hw一样
lw 低水位。ar集合最小的logstartoffset值。

### Kafka 基础概念与入门知识 #### 1. 背景介绍 Apache Kafka 是由 LinkedIn 开发并于 2011 年开源的一个分布式流处理平台[^1]。它的设计初衷是为了满足大规模日志聚合的需求,后来逐渐发展成为一种高性能的消息队列系统。相比其他消息队列系统(如 RabbitMQ 或 ActiveMQ),Kafka 更适合于高吞吐量、低延迟的场景,并能够支持实时数据管道和流式应用开发。 #### 2. 核心术语与概念 以下是 Kafka 中的核心术语及其定义: - **Topic**: 主题是一个逻辑上的分类单元,用于存储特定类型的记录集合。生产者向 Topic 发送消息,而消费者则订阅该主题以获取消息[^1]。 - **Partition**: 每个 Topic 都可以被划分为多个 Partition(分区)。这种划分使得 Kafka 能够实现并行化处理,从而提高系统的扩展性和性能。 - **Producer**: 生产者负责将消息发送到指定的 Topic 和 Partition 上。它可以通过自定义策略决定每条消息的目标位置[^2]。 - **Consumer**: 消费者从某个 Topic 的一个或多个 Partition 中拉取消息进行处理。为了跟踪已消费的数据,每个 Consumer 组都会维护自己的偏移量 (Offset)[^2]。 - **Broker**: Broker 是运行在单台服务器上的 Kafka 实例。整个集群通常由若干个这样的节点组成。 - **Leader & Follower**: 对于每一个 Partition 来,只有一个 Leader 复制副本对外提供服务请求,其余均为 Followers,后者同步前者的数据以便发生故障转移时接管职责[^3]。 - **Replica**: Replica 表示同一份数据的不同拷贝形式,其中包括 Leader 和所有的 Followers。通过设置合适的复制因子可增强容错能力[^1]。 #### 3. 工作流程概述 当一条新的消息进入 Kafka 后,经过一系列复杂的调度过程才能到达目标消费者的手中。简单来说,这个链条主要包括以下几个环节: 1. Producer 将待投递的信息序列化成字节数组并通过网络协议提交给对应的 Brokers; 2. 接收到的新纪录会被追加至相应 Partitions 的末端文件中去; 3. Consumers 定期轮询自己关心的主题列表并向对应 Leaders 请求最新可用批次直至完成全部迭代为止[^5]。 #### 4. 数学模型分析 虽然官方文档并未给出太多显式的数学表达式来描述内部运作细节,但我们仍可以从理论上推测某些方面存在潜在关联之处。例如,在负载均衡算法里可能会涉及到概率论中的随机抽样方法;而在一致性哈希环的设计当中也可能运用到了离散几何领域内的相关理论成果等等。 #### 5. 示例代码展示 下面给出了几个简单的 Python 程序片段演示如何利用 kafka-python 库来进行基本操作: ```python from kafka import KafkaProducer, KafkaConsumer # 创建生产者的实例 producer = KafkaProducer(bootstrap_servers='localhost:9092') # 发送消息到名为 'test' 的 topic 下的第一个 partition future = producer.send('test', b'my first message') result = future.get(timeout=60) print(result.topic) print(result.partition) ``` 对于消费者端而言,则有如下方式实现持续监听功能: ```python consumer = KafkaConsumer( 'my_favorite_topic', group_id='my_group', bootstrap_servers=['localhost:9092']) for msg in consumer: print ("%s:%d:%d: key=%s value=%s" % (msg.topic, msg.partition, msg.offset, msg.key, msg.value)) ``` #### 6. 展望与发展前景 随着大数据技术栈日益成熟完善,作为其中不可或缺的一员角色——Kafka 不断推陈出新适应新时代需求变化趋势。一方面继续优化现有架构提升效率降低成本开销;另一方面积极探索新兴方向诸如机器学习在线训练框架集成等领域可能性探索更多商业价值变现途径。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大数据学习爱好者

你的鼓励是我最大的动力

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

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

打赏作者

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

抵扣说明:

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

余额充值