kafka基础入门(二)

三. kfk架构深入

3.1 kfk工作流程

在这里插入图片描述

  • 上面每个分区中的数字代表消息的偏移量(offset), 所有的分区没有全局的偏移量, 每个分区维护以及的消息偏移量;
    kfk0.9开始消费者中存储offset, kfk0.9之前zk中存储offset;
  • follower主动连接leader同步数据. 可能出现数据丢失;
  • kfk中消息按topic分类, 生产者需指定往哪个topic发消息, 消费者需指定从哪个topic消费消息;
    topic是逻辑上的概念, partition是物理上的概念. 因此磁盘中找不到topic对应的目录, 只能找到partition对应的目录.
    partition对应目录的名字是"主题名-分区编号";
  • 每个partition对应一个.log文件, 该文件中存储生产者发送的消息. 生产者生产的消息会被不断追加到该文件末尾, 并且每条消息都有自己的offset(每条消息自己的偏移量, 可以理解为这条消息占几行). 消费者组中的每个消费者都会实时记录自己消费到了哪个offset(消息的偏移量, 可以理解为第几条消息), 以便消费出错恢复时继续从上次的位置继续消费(从上条消息开始继续消费).

3.2 kfk文件存储机制

在这里插入图片描述

  • 00000000000000000000.log用来存放生产者发送的消息. 默认存7天. 该文件最大1G, 超过1G将生成一个新的.log文件.

  • .log文件名是存储的第一条消息的offset(第一条消息的行号). 第一个.log文件的名字是0000, 可见消息的偏移量是从0开始计算而不是1;

    ######### Log Retention Policy #########
    log.retention.hours=168 # 7天
    log.segment.bytes=1073741824 # 1G
    
  • .log.index合起来叫做1个segment片段. 指1个partition中的1个片段的数据. 由于.log和.index可以有多个, 所以说1个partition分为多个segment片段;

在这里插入图片描述

  • 由于生产者生产的消息会不断追加到.log文件末尾, 为防止.log文件过大导致数据定位效率低下, kfk采取分片索引机制, 将每个partition分为多个segment. 每个segment对应两个文件, .log和.index. 这些文件位于同一个文件夹下, 文件命名规则为topic名+分区序号. 例如topic01这个topic有3个partition, 则其对应的文件夹命名为topic01-0, topic01-1, topic01-2.

  • .log.index命名规则相同. 都是当前文件中存储的消息的最小偏移量. 例如第一个.log文件存了10000条消息就到达了1G, 则新生成的.log文件的命名为10001.log.

  • .log.index文件结构示意图
    在这里插入图片描述

    3指第三条消息, 就是生产者发送的消息的偏移量是3, 理解为生产者发送来的消息的序号;

    756指第三条消息的物理偏移量, 理解为第三条消息在.log文件中第756行开始;

    查找一条消息时, 拿着消息偏移量查询.log文件的名字, 通过二分查找定位这条消息在哪个.log文件中, 再从.log文件中通过这条消息的偏移量(这条消息子.log中的起始行数)就找到这条消息了.

    .index文件中存的内容是固定的, 即消息偏移量(第几条消息)这条消息的偏移量(.log文件中第几行是这条消息). 所以.index文件中每个"键值对(实际上不是键值对)"的大小都是相同的, 这样有利于快速定位索引. 索引的值中不仅存这条消息的物理偏移量, 也保存这条消息的大小(可以理解为这条消息的总行数). 例如值中存的是756,也存了这条消息一共1000行, 那么到log中查询756-1756行就能取出这条消息了.

  • 只有保存到磁盘的消息,消费者才能消费的到。

  • 注意

    • 在kfk存储数据的目录中有两个文件: recovery-point-offset-checkpointreplication-offset-checkpoint
    • recovery-point-offset-checkpoint: 已经被确认写入磁盘的offset
    • replication-offset-checkpoint: 已经确认复制给其他replica的offset,也就是HW。(用来存储每一个replica的HighWatermark。由ReplicaManager负责写。)

3.3 kfk的生产者

3.3.1 topic分区的原因

  1. 方便在集群中扩展. partition可以存在不同的机器(broker)中, 这样就能存储任意大小的数据了.
  2. 提高并发能力. 能以partition为单位进行读写. 即多个生产者可以同时将消息发往多个partition, 多个消费者能同时从多个partition读取数据.

3.3.2 生产者的分区策略

讨论一个topic中有多个partition, 那么生产者发送数据发送到哪个partition的问题.

  • api中将生产者发送的消息封装到ProducerRecord中, 该对象的构造器如下:
    在这里插入图片描述

  • String topic: 上述所有的构造器都有topic参数, 可见生产者发送消息, 必须指定发往哪个topic;

  • Integer partition: 消息发送到哪个partition分区;

  • K key, V value: kfk的消息是k/v形式的;

  • Iterable<Header> headers: 发送消息时指定头信息, 非必须;

  • ProducerRecord(String topic, K key, V value)中, 不需要指定分区号, kfk会通过hash(key)%partition数量来决定消息发往哪个partition;

  • ProducerRecord(String topic, V value)中, 不指定k, 那么k=null, null不能hash()不能%, 所以kfk会在所有的partition分区中随机选择一个发送消息. 如果一个topic一共4个partition, 分区编号分别为0123, 随机指定往2号分区发, 那么下次发送时将会往3号分区发, 因此该api的分区策略是轮询, 即round-robin算法;

3.3.3 生产者发送数据的可靠性

讨论生产者发送消息后如何确认kfk接收到消息的问题; 涉及到ACK和ISR;

  • 什么是ack确认机制

    • 为了保证producer发送的消息能可靠地到达指定的topic(能被topic接收到), topic的每个partition收到消息后都要向producer发送ack(acknowledgement 确认收到), 如果producer收到ack就会进行下一轮发送, 否则重发;
  • 何时发送ack

    • leader和follower同步完成后leader再发送ack, 这样能保证leader挂掉后能在follower中选举出新的leader. 即保证在数据不丢失的情况下让leader发送ack.
    • follower可能有多个, 多少个follower同步完成后发送ack呢? 现有两个方案:
      • 方案1: 半数以上的follower同步完成后让leader发送ack;
      • 方案2: 所有的follower同步完成后让leader发送ack; – kfk选择该方案.
  • 何时发送ack的方案比较

    方案优点缺点
    半数以上的follower同步完成后让leader发送ack;延迟低;选举新leader时, 容忍n台节点故障, 需要n+(n+1)个副本;
    所有的follower同步完成后让leader发送ack;选举新leader时, 容忍n台节点故障, 需要n+1个副本;延迟高;
  • kfk选择了方案2, 但是该方案仍然有问题. “同步所有的follower才会发送ack”, 假如有一台follower同步的时候挂掉
    了, 那么永远不会发送ack了. 因此kfk引入了 ISR 的概念.

  • 查看ISR
    在这里插入图片描述

  • 什么是ISR (in-sync replica set 同步副本)

    • ISR的作用就是为了解决leader发送ack的问题.
    • 假如有10个副本, 1个leader和9个follower, 那么kfk必须等9个follower同步完leader的数据才会让leader向producer发送ack, 那么如果同步数据的时候有1个follower挂掉了, 那么就不可能发送ack了. 所以我们可以将9个follower中的部分follower放到ISR中, 假如将4个follower放到ISR中, 那么只需要等ISR中的这4个follower同步完数据, leader就会向producer发送ack, 如果leader挂了, 那么选新leader的时候从这4个follower中选即可. 除非这4个follower全挂了, 才会考虑选没加入ISR的其他follower作为leader.
  • 小结: 为了保证生产者发送数据的数据可靠性, 就引入了ack, 那么什么时候发送ack呢? 为了不丢失数据, 有两种策略, 一是半数以上的follower同步数据完成才会发送ack, 二是全部的follower同步完数据完成才会发送ack. kfk选择了方案二, 方案二存在问题, 就是万一同步数据的时候有一台follower挂了, 那么永远不会发送ack了, 问了解决这个问题, 引入了ISR(同步副本). ISR的作用就是当leader挂掉后, 从ISR中选择一台follower作为新的leader.

  • 当ISR中的follower完成数据同步之后, leader就会发送ack. 如果follower长时间没有从leader同步数据, 则该follower会被踢出ISR. 这个时间阈值由replica.lag.time.max.ms指定.

  • follower满足什么条件才会被加入ISR

    • 条件1: 通信速度快的follower才会被选入ISR. producer和follower发心跳, follower能够快速响应. 这样丢数据的可能性更小. 这里说的通信速度,通过上述的replica.lag.time.max.ms参数设定
    • 条件2: 同步条数多的follower才会被选入ISR. producer向leader发送数据, 所有的follower异步同步数据, 有的follower同步了8条, 有
      的follower同步了6条, 那么同步数据多个follower才能被选入ISR. 这样丢数据的可能性小. 这里说的同步的消息条数, 通过 replica.lag.time.max.message参数设定. 这个参数实际上是leader中数据和follower中数据的差值.
  • kfk0.9开始, 条件2被去掉了.

    • 生产者是批量(batch)向leader发送数据的. ISR队列维护在kfk内存中, zk中也维护一份ISR. 如果replica.lag.time.max.message设置为10, 但是生产者一次batch向leader发送12条数据, 生产者不断向leader发送batch, 那么会有follower频繁加入ISR踢出ISR, 那么就需要频繁操作kfk内存和zk.

      在这里插入图片描述

  • 综上, kfk0.9开始, leader向producer发送ack的步骤是

    • producer发送消息到leader, leader向ISR中的follower同步数据, ISR中follower同步完数据后向leader做出响应, leader接收到响应后向producer发送ack.
  • 这种发送ack的步骤还有有问题

    • leader必须收到ISR中所有follower(同步完数据后)的响应之后才会发送ack, 这明显比较慢
  • 为了解决上述问题, ack的发送有很多机制. 对于不太重要的数据, 对于数据可靠性要求不高(消息丢失率), 能容忍少量数据丢失, 所以没必要等到ISR中所有的follower都同步完数据并都向leader响应数据同步成功才让leader发送ack. 所以kfk中有3种数据可靠性级别, 能让用户对可靠性和延迟要求进行权衡.

    • 可靠性级别通过producer的acks参数配置. 取值0, 1, -1.
    • acks:0 producer不等待broker的ack. 即broker一接收到生产者消息, 还没写入磁盘就返回成功. 延迟最低, 最容易丢数据, leader挂掉的话刚才发送的数据就丢了, 不会重发消息.
    • acks:1 leader将数据落盘成功后就发送ack. 如果leader延迟过大或者leader挂掉, 即如果producer没收到ack, 那么重发消息. 有可能丢数据, 因为leader收到数据后可能挂掉了, 没来得及向ISR中的follower同步完数据.
    • acks:-1acks:all leader和ISR中的follower全部写入同步数据完成之后由leader向producer发送ack.
      • 这种情况下会造成 数据重复 .这种情况也会丢失数据, 就是ISR中没有一个副本的时候, 那么只有一个leader, 这个leader挂了就会丢失数据. 我们重点讨论这种情况中数据重复的问题.
      • 为什么会有重复数据? 当producer向leader中写完数据后, ISR中的follower也同步完数据, leader向producer发送ack之前挂掉了. 那么此时将选ISR中的一个follower作为leader, 同时producer由于长时间没收到ack会重发数据.
    • acks=0和acks=1会丢失数据, acks=-1时我们更多的讨论重复数据的问题.

3.3.4 数据一致性问题

  • 场景

    • 假设ISR中有3个副本, 一个leader两个follower. 如果leader写了15条数据, 两个follower异步从leader同步数据, 同步数据过程中leader挂了, 假设此时follower_a同步了8条数据, follower_b同步了12条数据. 假设follower_b被选为leader. follower_b被选为leader之后挂掉的leader活了. leader如果多挂几次, 那么3个副本中存储的数据就会不同, 消费时就会出现问题.
    • 这就涉及到了数据一致性问题. 即消费者消费的数据的内容和顺序必须是正确的, 磁盘中存储的各副本中的数据的内容和顺序必须是正确的.
  • LEO 和 HW. 下面三个副本的LEO分别是15 8 12; 下面三个副本的HW全是8;
    在这里插入图片描述

  • 假设消费者消费Leader中第13条数据, 此时Leader挂掉了, B被选为Leader, 此时B才同步到第8条数据, 消费者接下来应该消费第14条数据, B根本没有这条数据, 报错. 引入HW后, 消费者最多只能看到第8条数据, 此时即便是Leader挂了, 无论哪个Follower被选为Leader, 消费者都不会报错. 所以说HW保证了消费一致性.

  • 如果B被选为Leader. 此时生产者又发了一条msg来, 那么msg将是B的第9条数据, msg是A的第16条数据.
    于是kfk使用了多退少补: 如果B被选为Leader, 那么A中和C中大于8的消息将会被删除, 如果C被选为Leader, 那么B中会补上9 10 11 12, A中会删除8后的数据然后补上9 10 11 12. 即截取高水位. 这样就保证了副本数据存储在log文件中的存储一致性.

  • HW不能保证数据丢失, 数据不丢失不重复是通过acks来保证的. 如果Leader挂了, C被选为Leader, 由于producer没有收到ack, 那么会重发消息, 这就导致了数据重复.

  • 小结: HW解决的是一致性问题: 消费一致性和存储一致性; ACK解决的是重复和丢失问题;

3.3.5 Exactly Once 语义

  • 精准一次性 解决重复消费问题. kfk0.11才引入了消息幂等性. kfk0.11之前通过Redis搞日志表进行消息去重.
    kfk0.11之后引入的精准一次性, 在broker中解决了幂等性问题.
  • acks=-1的时候, 消息不会丢失(实际上可能会丢失), 但是可能会有重复数据, 消息可能会重发, 即at leastonce(至少一次).
    acks=0的时候, 可以保证消息只发送一次, 消息会丢失, 消息不会重发, 即at mostonce(至多一次).
    消息即不重复也不丢失, 这就叫Exactly Once, 即精准一次性.
  • at least once + 幂等性 = Exactly Once. 即 至少发送一次消息+幂等性=精准一次性.
  • kfk开启幂等性: 只需要将producer的参数中的 enable.idompotence 设置为 true 即可. 并且设置为true后, acks自动被设置为-1.
  • kfk的幂等性实现实际上就是将消费者要做的事情放到了broker中做. (消费者通过Redis搞日志表进行消息去重).
  • 开启幂等性的producer在初始化的时候会被分配一个PID, 发往同一个Partition的消息会附带Sequence Number. 而Broker端会对<PID, Partition, Sequence> 做缓存, 当具有相同主键的消息提交时, Broker只会持久化一条.
  • 但是pid重启就会发生变化, 而且不同的partition有不同的pid, 因此幂等性无法保证跨分区(partition)跨会话的精确一次性.
  • 跨会话指的是producer重启, 那么pid会重新生成. 生产者不挂掉不重启就能保证精准一次性. pid是生产者producer的id. sequence number即序列化号, 就是每条消息的id.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然可以!以下是一些步骤,可以帮助你快速入门Kafka: 1. 下载并安装Kafka:首先,你需要下载并安装Kafka。你可以从官方网站上下载Kafka:https://kafka.apache.org/downloads。根据你的操作系统选择正确的版本下载并安装。 2. 启动Kafka服务器:安装完成后,你需要启动Kafka服务器。使用终端,进入Kafka文件夹并运行以下命令: ```bash bin/zookeeper-server-start.sh config/zookeeper.properties ``` 然后,在另一个终端窗口中,启动Kafka服务器: ```bash bin/kafka-server-start.sh config/server.properties ``` 3. 创建一个主题:在Kafka中,消息被发送到主题。你需要创建一个主题,以便你可以在其中发布和消费消息。使用以下命令创建一个名为“test”的主题: ```bash bin/kafka-topics.sh --create --topic test --bootstrap-server localhost:9092 ``` 4. 发布消息:现在,你可以向“test”主题发布消息。使用以下命令将消息“Hello, Kafka!”发布到主题: ```bash bin/kafka-console-producer.sh --topic test --bootstrap-server localhost:9092 ``` 5. 消费消息:你可以使用以下命令从“test”主题中消费消息: ```bash bin/kafka-console-consumer.sh --topic test --from-beginning --bootstrap-server localhost:9092 ``` 这将从主题的开头开始读取消息,并将它们打印到控制台上。 这些是入门Kafka的基本步骤。当你掌握了这些基础知识后,你可以进一步了解Kafka的更高级功能和用法。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值