消息队列-Kafka

821 阅读29分钟

毛大分享 大数据直接连接KAFKA 业务上用的 直接创建了一个proxy

  • 一天千亿级别的行为 消息量 200M/s 几百台kafka机器

中间件

  • rocketmq
    • 理念和思路 借鉴KAFKA
    • 强顺序不好做
  • pulsar 用的人少
  • kafka
  • rabbitmq erlang
  • ZEROMQ
  • active MQ
  • datahub(ali)
  • AWS

Kafka 基础概念

行为流数据是几乎所有站点在对其网站使用情况做报表时都要用到的数据中最常规的部分 (学习一些行为分析的东西 例如漏斗分析 常用的业务指标关注一下 了解一下业务的东西)

  • 是一种分布式的,基于发布/订阅的消息系统
  • Kafka 已被多家不同类型的公司作为多种类型的数据管道和消息系统使用。 分布式、高吞吐 社区活跃
  • 包括页面访问量 PV(page view)、页面曝光(Expose)、页面点击(Click) 等行为事件
    • 利用FLINK做一些实时计算 SOURce用的是KAFKA
  • 实时计算中的 Kafka Source,Dataflow Pipeline
  • 业务的消息系统,通过发布订阅消息解耦多组微服务,消除峰值

Kafka 是由 LinkedIn 开发并开源的分布式消息系统,因其分布式及高吞吐率而被广泛使用,现已与Cloudera Hadoop,Apache Storm,Apache Spark集成。

Kafka简介

image.png

Kafka设计目标

  • 以时间复杂度为 O(1) 的方式提供消息持久化能力,即使对 TB 级以上数据也能保证常数时间复杂度的访问性能;
    • 每条消息都是追加到每个TOPIC的partion 读写操作非常快顺序IO
    • KAFKA可以落盘
  • 高吞吐率。
    • 对机器要求不高 即使在非常廉价的商用机器上也能做到单机支持每秒 100K 条以上消息的传输;
    • 机械盘 单价便宜
    • HDD为啥慢 可以阅读大话存储 可以了解一些硬盘的基础知识 7200转 可以达到100MB
  • 支持 Kafka Server 间的消息分区
    • 及分布式消费,同时保证每个 Partition 内的消息顺序传输;
    • 创建一个TOPIC的时候 会有多个文件 分布在多个机器的多块硬盘
  • 支持离线数据处理和实时数据处理;
    • 可以立刻消费
    • 离线消费 例如模型训练的场景 offset重置为最早、进行回放replay, 这样就不用重新构建数据 或者从某个渠道重新搂出来
    • 很多MQ是producer--customer--ACK ACK后数据就没了 没办法回溯
  • Scale out: 支持在线水平扩展;
    • 随时增加减少 BROKER 分区
  • 历史消息可以无限回放

为何不用REDIS 当作 message bussiness?

  • 作者都不建议当作消息队列 发过文章
  • 业务流的场景 使用redis 下游消费出现问题的时候/或者慢的时候, 内存无限上涨 容易OOM
  • redis cache 易失性 重启存在丢失数据的风险
    • REDIS的RDS AOF可以让数据持久化, 即便持久化 但是OOM的问题无法
    • REDIS的数据放在内存的
    • REDIS使用双线程的模型 流量密集型的场景 容易出现CPU过载的问题
    • KAFKA的数据是在磁盘的

为何使用消息系统?

解耦

  • 基于订阅发布 可以联动下游的多个流程 保证主系统和子系统 数据一致性的问题
  • 消息系统在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口。这允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
  • 而基于消息发布订阅的机制,可以联动多个业务下游子系统,能够不侵入的情况下分步编排和开发,来保证数据一致性。
  • TOPIC 要有归属的业务、管理人, 知道谁在用 、要有生命周期的管理
    • 里面的内容不知道是什么
    • 要有schema注册 这块自己去研究一下定义和实现
    • TOPIC是一张表 对标的PB protocol buffer 要有统一的定义个约束
    • 判断投入的数据 是否符合定义 是否合法
      • 例如:上游 加减字段 导致下游的消费出问题 自己退出了 导致故障
    • 这个地方需要大家约定好君子协议 数据的格式是什么 下游要保证足够的健壮性 例如做一些数据的合法性判断、异常捕捉、监控告警

冗余

  • 有些情况下,处理数据的过程会失败。
    • 除非数据被持久化,否则将造成丢失。
    • 消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。
    • 落地为安
  • 许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。
    • 如果删除之前 需要处理系统明确的指出消息被消费过了 这个时候才能安全的删除
      • 这个机制不方便数据的回溯
    • 如果消息处理失败 可以不确认 直接往下消费

扩展性

  • 消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。
    • 类似计算存储分离的设计思想
    • 入队的速度可以很快 消费的速度可以自己去调整和扩展
  • 不需要改变代码、不需要调节参数。扩展就像调大电力按钮一样简单。

灵活性 & 峰值处理能力

  • 在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见;
  • 如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。
  • 使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
  • 直接写数据库的复杂度和消息队列O1不一样

可恢复性

  • 系统的一部分组件失效时,不会影响到整个系统。
    • 消费者挂掉的话 重启可以继续消费 异步的一些逻辑可以解藕掉
  • 消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。

顺序保证

  • 在大多使用场景下,数据处理的顺序都很重要。
    • A先点赞 B再点赞的业务逻辑不一样的
  • 大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。
  • Kafka 保证一个TOPIC 一个Partition 内的消息的有序性。

缓冲

  • 在任何重要的系统中,都会有需要不同的处理时间的元素。
  • 消息队列通过一个缓冲层来帮助任务最高效率的执行———写入队列的处理会尽可能的快速。
  • 该缓冲有助于控制和优化数据流经过系统的速度。

异步通讯

  • 很多时候,用户不想也不需要立即处理消息。
    • 主干流程和分支流程
    • 例如系统通知 可以在子系统中慢慢的取出来
  • 消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。
  • 想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。

Topic & Partition介绍

Topic

image.png

  • Topic 在逻辑上可以被认为是一个 queue,每条消费都必须指定它的 Topic,可以简单理解为必须指明把这条消息放进哪个queue 里
  • 我们把一类消息按照主题来分类,有点类似于数据库中的表。 可以理解mySQL的一个表 一定要有一个scheme的指定

Partition

  • 为了使得 Kafka 的吞吐率可以线性提高,物理上把 Topic 分成一个或多个 Partition。
  • 对应到系统上就是一个或若干个目录
  • 可以扩展多个机器的多个磁盘 image.png

Broker

image.png

  • Kafka 集群包含一个或多个服务器,每个服务器节点称为一个 Broker
  • Broker 存储 Topic 的数据。
  • 如果某 Topic 有 N 个 Partition,集群有 N 个 Broker,那么每个 Broker 存储该 Topic 的一个 Partition。
    • 一个机器可以有多块盘 每块盘 负责一个Partition的映射
  • 从scale out 的性能角度思考,通过 Broker Kafka server 的更多节点,带更多的存储,建立更多的 Partition 把IO负载到更多的物理节点,提高总吞吐 IOPS。

从 scale up 的角度思考,一个 Node 拥有越多的 Physical Disk,也可以负载更多的 Partition,提升总吞吐 IOPS。

image.png

不均衡的场景介绍1

如果某 Topic 有 N 个 Partition,集群有(N+M)个 Broker,那么其中有 N 个 Broker 存储该 Topic 的一个 Partition,剩下的 M 个 Broker 不存储该 Topic 的 Partition 数据。

不均衡的场景介绍2

如果某 Topic 有 N 个 Partition,集群中 Broker 数目少于 N 个,那么一个 Broker 存储该 Topic 的一个或多个 Partition。

解决方案

  • Topic 只是一个逻辑概念,真正在 Broker间分布式的 Partition。每一条消息被发送到 Broker 中,会根据 Partition 规则选择被存储到哪一个 Partition。(默认的HASH)
  • sharding的规则可以自己去配置,如果 Partition 规则设置的合理,所有消息可以均匀分布到不同的 Partition中
  • client的配置 数据具体存放的Partition也可以自己去配置 可以根据场景 将不同场景的数据分配到不通的Partition中

压测 Broker & Partition的最佳配比

image.png 横轴Partition的数量 纵轴TPS

实验条件:3个 Broker,1个 Topic,无Replication,异步模式,3个 Producer,消息 Payload 为100字节:

测试结论

  • 当 Partition 数量小于 Broker个数时,Partition 数量越大,吞吐率越高,且呈线性提升。
  • 当Partition 数量大于 Broker个数时 吞吐率不再线性提升 反而存在性能抖动

解释:

当Partition 数量小于 Broker个数时

  • Kafka 会将所有 Partition 均匀分布到所有Broker 上,所以当只有2个 Partition 时,会有2个 Broker 为该 Topic 服务。3个 Partition 时同理会有3个 Broker 为该 Topic 服务。
  • 这个时候最大化的利用了IO资源

当Partition 数量多于 Broker个数时

  • Broker的整体性能是有限的
    • 磁盘的IO能力到达瓶颈了
    • 一块盘的Partition多的情况下 退化成随机IO
  • 当 Partition 数量多于 Broker 个数时,总吞吐量并未有所提升,甚至还有所下降。
  • 可能的原因是,当 Partition 数量为4和5时,不同 Broker 上的 Partition 数量不同,而 Producer 会将数据均匀发送到各 Partition 上,这就造成各Broker的负载不同,不能最大化集群吞吐量。

存储原理

image.png

kafka高存储得益于

  • Kafka的消息是存在于文件系统之上的。Kafka 高度依赖文件系统来存储和缓存消息,一般的人认为 “磁盘是缓慢的”

    • 文件系统推荐XFS、EXT4
    • 如果是顺序IO 单块7200转的HDD 可以100-200MB/s
  • 操作系统还会将主内存剩余的所有空闲内存空间(free -m中的freed cached buffer)都用作磁盘缓存,所有的磁盘读写操作都会经过统一的磁盘缓存(除了直接 I/O 会绕过磁盘缓存)

  • Kafka 正是利用顺序 IO,以及 Page Cache的加速特性(linux 内核态 有预读策略和刷盘操作)达成的超高吞吐。

    • 如果有读的话 刚好可以命中cache
    • 这个地方可以参考 褚霸的文章 非业余研究alone
    • Page Cache的污染也会导致 性能衰减
  • 任何发布到 Partition 的消息都会被追加到 Partition 数据文件的尾部,这样的顺序写磁盘操作让 Kafka 的效率非常高。

  • write back 先写内存 然后过一定时间 通过内核进行写操作

image.png

对象存储 是将多个小文件合成大文件进行存储的 ,meta 中有大文件---小文件的映射关系 删除的时候进行delete=1 空洞文件通过comback进行整理 Kafka也是参考这块的原理

  • Kafka 集群保留所有发布的 message,不管这个 message 有没有被消费过,数据都是落盘的
    • Kafka 提供可配置的保留策略去删除旧数据(还有一种策略根据分区大小删除数据)。
    • 例如,如果将保留策略设置为两天,在 message 写入后两天内,它可用于消费,之后它将被丢弃以腾出空间。
    • Kafka 的性能跟存储的数据量的大小无关, 所以将数据存储很长一段时间是没有问题的。
  • 这块的数据可以设置成文件滚动 类似MYSQLbinlog

什么是Offset

  • Offset:偏移量。
  • 每条消息都有一个当前 Partition 下 唯一的64 字节的 Offset,它是相当于当前分区第一条消息的偏移量,即第几条消息。
  • 消费者可以指定消费的位置信息,当消费者挂掉再重新恢复的时候,可以从消费位置继续消费。
  • 这个地方修改auto_offset_reset参数可以指定消费的策略

假设我们现在 Kafka 集群只有一个 Broker,我们创建 2 个 Topic 名称分别为:「Topic1」和「Topic2」,Partition 数量分别为 1、2。 那么我们的根目录下就会创建如下三个文件夹:

image.png

在 Kafka 的文件存储中,

  • 每一个 Topic中有多个不同的 Partition,每个Partition都为一个目录。
  • 每一个Partition(目录)又被平均分配成多个大小相等的 Segment File
  • 每一个Segment File 又由 index file 和 data file 组成,他们总是成对出现,后缀 ".index" 和 ".log" 分表表示 Segment 索引文件和数据文件。

image.png

索引文件的格式

image.png

offest的来源

其中以索引文件中元数据 <3, 497> 为例,依次在数据文件中表示第 3 个 Message(在全局 Partition 表示第 368769 + 3 = 368772 个 message)以及该消息的物理偏移地址为 497。

kafka索引

  • Index 文件并不是从0开始,也不是每次递增 1 的,这是因为 Kafka 采取稀疏索引存储的方式,每隔一定字节的数据建立一条索引。

  • 它减少了索引文件大小,使得能够把 Index 映射到内存,降低了查询时的磁盘 IO 开销,同时也并没有给查询带来太多的时间消耗。

  • 因为其文件名为上一个 Segment 最后一条消息的 Offset ,所以当需要查找一个指定 Offset 的 - Message 时,通过在所有 Segment 的文件名中进行二分查找就能找到它归属的 Segment。

  • 再在其 Index 文件中找到其对应到文件上的物理位置,就能拿出该 Message。

image.png

假设我们现在 Kafka 集群只有一个 Broker,我们创建 2 个 Topic 名称分别为:「Topic1」和「Topic2」,Partition 数量分别为 1、2。

那么我们的根目录下就会创建如下三个文件夹:

image.png

在 Kafka 的文件存储中,同一个 Topic 下有多个不同的 Partition,每个 Partition 都为一个目录。 而每一个目录又被平均分配成多个大小相等的 Segment File 中,Segment File 又由 index file 和 data file 组成,他们总是成对出现,后缀 ".index" 和 ".log" 分表表示 Segment 索引文件和数据文件。

image.png

image.png

  • 其中以索引文件中元数据 <3, 497> 为例,依次在数据文件中表示第 3 个 Message(在全局 Partition 表示第 368769 + 3 = 368772 个 message)以及该消息的物理偏移地址为 497。
  • 注意该 Index 文件并不是从0开始,也不是每次递增 1 的,这是因为 Kafka 采取稀疏索引存储的方式,每隔一定字节的数据建立一条索引。
  • 它减少了索引文件大小,使得能够把 Index 映射到内存,降低了查询时的磁盘 IO 开销,同时也并没有给查询带来太多的时间消耗。
  • 因为其文件名为上一个 Segment 最后一条消息的 Offset ,所以当需要查找一个指定 Offset 的 Message 时,通过在所有 Segment 的文件名中进行二分查找就能找到它归属的 Segment。
  • 再在其 Index 文件中找到其对应到文件上的物理位置,就能拿出该 Message。

image.png Kafka 是如何准确的知道 Message 的偏移的呢? 这是因为在 Kafka 定义了标准的数据存储结构,在 Partition 中的每一条 Message 都包含了以下三个属性: Offset:表示 Message 在当前 Partition 中的偏移量,是一个逻辑上的值,唯一确定了 Partition 中的一条 Message,可以简单的认为是一个 ID。 MessageSize:表示 Message 内容 Data 的大小。 Data:Message 的具体内容。

image.png Kafka 从0.10.0.0版本起,为分片日志文件中新增了一个 .timeindex 的索引文件,可以根据时间戳定位消息。同样我们可以通过脚本 kafka-dump-log.sh 查看时间索引的文件内容。

  • 首先定位分片,将 1570793423501 与每个分片的最大时间戳进行对比(最大时间戳取时间索引文件的最后一条记录时间,如果时间为 0 则取该日志分段的最近修改时间),直到找到大于或等于 1570793423501 的日志分段,因此会定位到时间索引文件00000000000003257573.timeindex,其最大时间戳为 1570793423505。
  • 重复 offset 找到 log 文件的步骤。

Producer & Consumer

Producer

image.png

Producer 发送消息到 Broker 时,会根据Paritition 机制选择将其存储到哪一个Partition。 如果 Partition 机制设置合理,所有消息可以均匀分布到不同的 Partition里,这样就实现了负载均衡。

  • 指明 Partition 的情况下,直接将给定的 Value 作为 Partition 的值。
  • 没有指明 Partition 但有 Key 的情况下,将 Key 的 Hash 值与分区数取余得到 Partition 值。
  • 既没有 Partition 有没有 Key 的情况下,第一次调用时随机生成一个整数(后面每次调用都在这个整数上自增),将这个值与可用的分区数取余,得到 Partition 值,也就是常说的 Round-Robin 轮询算法。

image.png

为保证 Producer 发送的数据,能可靠地发送到指定的 Topic,Topic 的每个 Partition 收到 Producer 发送的数据后,都需要向 Producer 发送 ACK。如果 Producer 收到 ACK,就会进行下一轮的发送,否则重新发送数据。

  • 选择完分区后,生产者知道了消息所属的主题和分区,它将这条记录添加到相同主题和分区的批量消息中,另一个线程负责发送这些批量消息到对应的 Kafka Broker。
  • 当 Broker 接收到消息后,如果成功写入则返回一个包含消息的主题、分区及位移的 RecordMetadata 对象,否则返回异常。
  • 生产者接收到结果后,对于异常可能会进行重试。

kafka broker性能差 通过nmon 发现上下文切换很大 发现大量的网络连接中断

Producer Exactly Once

0.11 版本的 Kafka,引入了幂等性:Producer 不论向 Server 发送多少重复数据,Server 端都只会持久化一条。

  • 要启用幂等性,只需要将 Producer 的参数中 enable.idompotence 设置为 true 即可。 开启幂等性的 Producer 在初始化时会被分配一个 PID,发往同一 Partition 的消息会附带 Sequence Number。
  • 而 Borker 端会对 <PID,Partition,SeqNumber> 做缓存,当具有相同主键的消息提交时,Broker 只会持久化一条。
  • 但是 PID 重启后就会变化,同时不同的 Partition 也具有不同主键,所以幂等性无法保证跨分区会话的 Exactly Once。

无法做到跨会话的Exactly Once

Consumer

image.png

假设这么个场景:我们从 Kafka 中读取消息,并且进行检查,最后产生结果数据。 我们可以创建一个消费者实例去做这件事情,但如果生产者写入消息的速度比消费者读取的速度快怎么办呢? 这样随着时间增长,消息堆积越来越严重。对于这种场景,我们需要增加多个消费者来进行水平扩展。 Kafka 消费者是消费组的一部分,当多个消费者形成一个消费组来消费主题时,每个消费者会收到不同分区的消息。 假设有一个 T1 主题,该主题有 4 个分区;同时我们有一个消费组 G1,这个消费组只有一个消费者 C1。 那么消费者 C1 将会收到这 4 个分区的消息。

image.png 如果我们增加新的消费者 C2 到消费组 G1,那么每个消费者将会分别收到两个分区的消息。 相当于 T1 Topic 内的 Partition 均分给了 G1 消费的所有消费者,在这里 C1 消费 P0 和 P2,C2 消费 P1 和 P3。

image.png 如果增加到 4 个消费者,那么每个消费者将会分别收到一个分区的消息。 这时候每个消费者都处理其中一个分区,满负载运行。

image.png

但如果我们继续增加消费者到这个消费组,剩余的消费者将会空闲,不会收到任何消息。

  • 总而言之,我们可以通过增加消费组的消费者来进行水平扩展提升消费能力。 这也是为什么建议创建主题时使用比较多的分区数,这样可以在消费负载高的情况下增加消费者来提升性能。
  • 另外,消费者的数量不应该比分区数多,因为多出来的消费者是空闲的,没有任何帮助。
  • 如果我们的 C1 处理消息仍然还有瓶颈,我们如何优化和处理?

把 C1 内部的消息进行二次 sharding,开启多个 goroutine worker 进行消费,为了保障 offset 提交的正确性,需要使用 watermark 机制,保障最小的 offset 保存,才能往 Broker 提交。

Consumer Group

image.png

Kafka 一个很重要的特性就是,只需写入一次消息,可以支持任意多的应用读取这个消息。 换句话说,每个应用都可以读到全量的消息。为了使得每个应用都能读到全量消息,应用需要有不同的消费组。 对于上面的例子,假如我们新增了一个新的消费组 G2,而这个消费组有两个消费者如图。 在这个场景中,消费组 G1 和消费组 G2 都能收到 T1 主题的全量消息,在逻辑意义上来说它们属于不同的应用。 最后,总结起来就是:如果应用需要读取全量消息,那么请为该应用设置一个消费组;如果该应用消费能力不足,那么可以考虑在这个消费组里增加消费者。

  • 消费组之间互相隔离 自己消费自己的 类似一个从头开始 一个从尾开始

踩坑

当新的消费者加入消费组,它会消费一个或多个分区,而这些分区之前是由其他消费者负责的。

  • 当消费者离开消费组(比如重启、宕机等)时,它所消费的分区会分配给其他分区。这种现象称为重平衡(Rebalance)。
  • 重平衡是 Kafka 一个很重要的性质,这个性质保证了高可用和水平扩展。不过也需要注意到,在重平衡期间,所有消费者都不能消费消息,因此会造成整个消费组短暂的不可用。
  • 将分区进行重平衡也会导致原来的消费者状态过期,从而导致消费者需要重新更新状态,这段期间也会降低消费性能。
  • 消费者通过定期发送心跳(Hearbeat)到一个作为组协调者(Group Coordinator)的 Broker 来保持在消费组内存活。这个 Broker 不是固定的,每个消费组都可能不同。
  • 当消费者拉取消息或者提交时,便会发送心跳。如果消费者超过一定时间没有发送心跳,那么它的会话(Session)就会过期,组协调者会认为该消费者已经宕机,然后触发重平衡。

每当消费者重新加入 消费信息需要做reblance 会短时间的不可用

  • 可以看到,从消费者宕机到会话过期是有一定时间的,这段时间内该消费者的分区都不能进行消息消费。
  • 通常情况下,我们可以进行优雅关闭,这样消费者会发送离开的消息到组协调者,这样组协调者可以立即进行重平衡而不需要等待会话过期。
  • 在 0.10.1 版本,Kafka 对心跳机制进行了修改,将发送心跳与拉取消息进行分离,这样使得发送心跳的频率不受拉取的频率影响。
  • 另外更高版本的 Kafka 支持配置一个消费者多长时间不拉取消息但仍然保持存活,这个配置可以避免活锁(livelock)。活锁,是指应用没有故障但是由于某些原因不能进一步消费。
  • 但是活锁也很容易导致连锁故障,当消费端下游的组件性能退化,那么消息消费会变的很慢,会很容易出发 livelock 的重新均衡机制,反而影响力吞吐。

Leader & Follower

数据丢失场景

image.png

  • 初始情况为主副本 A 已经写入了两条消息,对应 HW=1,LEO=2,LEOB=1,从副本 B 写入了一条消息,对应 HW(high water mark)=1,LEO=1。
  • 此时从副本 B 向主副本 A 发起 fetchOffset=1 请求,主副本收到请求之后更新 LEOB=1,表示副本 B 已经收到了消息0,然后尝试更新 HW 值,min(LEO,LEOB)=1,即不需要更新,然后将消息1以及当前分区 HW=1 返回给从副本 B,从副本 B 收到响应之后写入日志并更新 LEO=2,然后更新其 HW=1,虽然已经- 写入了两条消息,但是 HW 值需要在下一轮的请求才会更新为2。
  • 此时从副本 B 重启,重启之后会根据 HW 值进行日志截断,即消息1会被删除。
  • 从副本 B 向主副本 A 发送 fetchOffset=1 请求,如果此时主副本 A 没有什么异常,则跟第二步骤一样没有什么问题,假设此时主副本也宕机了,那么从副本 B 会变成主副本。
  • 当副本 A 恢复之后会变成从副本并根据 HW 值进行日志截断,即把消息1丢失,此时消息1就永久丢失了。

数据不一致场景

image.png

  • 初始状态为主副本 A 已经写入了两条消息对应 HW=1,LEO=2,LEOB=1,从副本 B 也同步了两条消息,对应HW=1,LEO=2。
  • 此时从副本 B 向主副本发送 fetchOffset=2 请求,主副本 A 在收到请求后更新分区 HW=2 并将该值返回给从副本 B,如果此时从副本 B 宕机则会导致 HW 值写入失败。
  • 我们假设此时主副本 A 也宕机了,从副本 B 先恢复并成为主副本,此时会发生日志截断,只保留消息0,然后对外提供服务,假设外部写入了一个消息1(这个消息与之前的消息1不一样,用不同的颜色标识不同消息)。
  • 等副本 A 起来之后会变成从副本,不会发生日志截断,因为 HW=2,但是对应位移1的消息其实是不一致的。

为了解决这个问题 引入了一个机制

Leader epoch

HW 值被用于衡量副本备份成功与否以及出现失败情况时候的日志截断依据可能会导致数据丢失与数据不一致情况,因此在新版的 Kafka(0.11.0.0)引入了 leader epoch 概念。

epoch 其实就是一个版本号的意思 leader epoch 表示一个键值对<epoch, offset>,其中 epoch 表示 leader 主副本的版本号,从0开始编码,当 leader 每变更一次就会+1,offset 表示该 epoch 版本的主副本写入第一条消息的位置。

比如<0,0>表示第一个主副本从位移0开始写入消息,<1,100>表示第二个主副本版本号为1并从位移100开始写入消息,主副本会将该信息保存在缓存中并定期写入到checkpoint 文件中,每次发生主副本切换都会去从缓存中查询该信息。

数据可靠性

Producer required.acks

image.png

对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据的少量丢失,所以没必要等 ISR 中的 Follower 全部接受成功。只有被 ISR 中所有 Replica 同步的消息才被 Commit,但Producer 发布数据时,Leader 并不需要 ISR 中的所有 Replica 同步该数据才确认收到数据。

  • 0:Producer 不等待 Broker 的 ACK,这提供了最低延迟,Broker 一收到数据还没有写入磁盘就已经返回,当 Broker 故障时有可能丢失数据。
  • 1:Producer 等待 Broker 的 ACK,Partition 的 Leader 落盘成功后返回 ACK,如果在 Follower 同步成功之前 Leader 故障,那么将会丢失数据。
  • -1(all):Producer 等待 Broker 的 ACK,Partition 的 Leader 和 Follower 全部落盘成功后才返回 ACK。但是在 Broker 发送 ACK 时,Leader 发生故障,则会造成数据重复。

如果要提高数据的可靠性,在设置request.required.acks=-1的同时,也要min.insync.replicas 这个参数(可以在 Broker 或者 Topic 层面进行设置)的配合,这样才能发挥最大的功效。 min.insync.replicas这个参数设定 ISR 中的最小副本数是多少,默认值为1,当且仅当 request.required.acks 参数设置为-1时,此参数才生效。如果 ISR 中的副本数少于 min.insync.replicas 配置的数量时,客户端会返回异常:org.apache.kafka.common.errors.NotEnoughReplicasExceptoin: Messages are rejected since there are fewer in-sync replicas than required。

request.required.acks=1

Producer 发送数据到 Leader,Leader 写本地日志成功,返回客户端成功;此时 ISR 中的副本还没有来得及拉取该消息,Leader 就宕机了,那么此次发送的消息就会丢失。

image.png

request.required.acks=-1

image.png

同步(Kafka 默认为同步,即 producer.type=sync)的发送模式,replication.factor>=2 且 min.insync.replicas>=2 的情况下,不会丢失数据。

有两种典型情况。acks=-1 的情况下,数据发送到 Leader, ISR 的 Follower 全部完成数据同步后,Leader此时挂掉,那么会选举出新的 Leader,数据不会丢失。

image.png

acks=-1 的情况下,数据发送到 Leader 后 ,部分 ISR 的副本同步,Leader 此时挂掉。 比如 follower1 和 follower2 都有可能变成新的 Leader, Producer 端会得到返回异常,Producer 端会重新发送数据,数据可能会重复。

Kafka 高性能

架构层面:

  • Partition 级别并行:Broker、Disk、Consumer 端;
    • 一台机器可以挂多块磁盘 7200转的顺序IO
    • 保证数据一次性消费
  • ISR
    • 假设3个副本 追不上的那个 直接踢出去
  • Broker级别是负载均衡的

IO 层面:

  • Batch 读写

    • 减少网络往返 就像redis的pipline
    • custormer是长轮询 可以设置一次拉取多少条数据
  • 磁盘顺序 IO

    • 大量partion的场景中 可能退化成随机IO
  • Page Cache

  • Zero Copy( 磁盘 --- 内核 -- 网络)

  • 压缩

  • 抓慢盘

    • MAD算法
    • dbscan算法

References

https://mp.weixin.qq.com/s/fX26tCdYSMgwM54_2CpVrw
https://www.jianshu.com/p/bde902c57e80
https://mp.weixin.qq.com/s?__biz=MzUxODkzNTQ3Nw==&mid=2247486202&idx=1&sn=23f249d3796eb53aff9cf41de6a41761&chksm=f9800c20cef785361afc55298d26e8dc799751a472be48eae6c02b508b7cb8c62ba3ac4eb99b&scene=132#wechat_redirect
https://zhuanlan.zhihu.com/p/27551928
https://zhuanlan.zhihu.com/p/27587872
https://zhuanlan.zhihu.com/p/31322316
https://zhuanlan.zhihu.com/p/31322697
https://zhuanlan.zhihu.com/p/31322840
https://zhuanlan.zhihu.com/p/31322994
https://mp.weixin.qq.com/s/X301soSDWRfOemQhk9AuPw

https://www.cnblogs.com/wxd0108/p/6519973.html
https://tech.meituan.com/2015/01/13/kafka-fs-design-theory.html
https://mp.weixin.qq.com/s/fX26tCdYSMgwM54_2CpVrw
https://mp.weixin.qq.com/s/TUFNictt8XXLmmyWlfnj4g
https://mp.weixin.qq.com/s/EY6-rA5DJr28-dyTh5BP8w
https://mp.weixin.qq.com/s/ByIqEgKIdQ2CRsq4_rTPmA
https://zhuanlan.zhihu.com/p/77677075?utm_source=wechat_timeline&utm_medium=social&utm_oi=670706646783889408&from=timeline
https://mp.weixin.qq.com/s/LRM8GWFQbxQnKoq6HgCcwQ
https://www.slidestalk.com/FlinkChina/ApacheKafka_in_Meituan
https://tech.meituan.com/2021/01/14/kafka-ssd.html
https://www.infoq.cn/article/eq3ecYUJSGgWVDGqg5oE?utm_source=related_read_bottom&utm_medium=article
https://mp.weixin.qq.com/s/Zz35bvw7Sjdn3c8B12y8Mw
https://tool.lu/deck/pw/detail?slide=20
https://www.jiqizhixin.com/articles/2019-07-23-11
https://www.jianshu.com/p/c987b5e055b0
https://blog.csdn.net/u013256816/article/details/71091774
https://zhuanlan.zhihu.com/p/107705346
https://www.cnblogs.com/huxi2b/p/7453543.html
https://blog.csdn.net/qq_27384769/article/details/80115392
https://blog.csdn.net/u013256816/article/details/80865540
https://tech.meituan.com/2021/01/14/kafka-ssd.html
https://www.infoq.cn/article/eq3ecYUJSGgWVDGqg5oE?utm_source=related_read_bottom&utm_medium=article
https://mp.weixin.qq.com/s/Zz35bvw7Sjdn3c8B12y8Mw
https://tool.lu/deck/pw/detail?slide=20
https://www.jiqizhixin.com/articles/2019-07-23-11
https://mp.weixin.qq.com/s/LRM8GWFQbxQnKoq6HgCcwQ
https://mp.weixin.qq.com/s/EY6-rA5DJr28-dyTh5BP8w

毛大分享

  • 聪明人 毛毛躁躁

  • 架构过于完美 无法落地

  • 内核很懂 代码写的太抽象 别人驾驭不了

  • 隔一段时间 重新去看自己的设计 会发现很多问题 补充一些改进的东西

    • 每次看都会有新的收获
  • A little coppying is better than a little dependency

  • 研发效能

    • 分布式编译 增量编译 bazel
    • 自动化测试
  • 薪资问题

    • 高绩效 高产出 比同level的产出要高一截
    • 地位重要的 对团队有帮助的
    • 老黄牛 体力活 脏活 累活
    • 讲工资 不要撒谎 可以说之前的公司 薪资不好 但是i自己的能力足够
  • 如何做好技术招聘

    • 一定要找比自己厉害的人
    • 先去 聊 认识一些大牛 持续关注优秀人才 不要指望HR
      • 那个大牛想要走的时候 一定第一时间想到你
  • 要想做好事情 跟着大家去做事 了解业务 耳濡目染 才能看清问题

    • 站在别人的角度 发现问题的痛点
  • 成为领导和同事喜欢的人?

    • 不要试图成为人人都喜欢的人
    • 少废话 多干活 多回报 给老大分忧
      • 老大的担忧 立马出一个方案 给他办了
      • 少承诺 多兑现 多出业绩 领导就相信你了
      • 高调做事 低调做人
    • 拍板的时候叫上老大就行了 细节展开的时候/过程的东西 不要拉着老大 只需要把结论 结果回报给老大就行
    • 能从老大的角度 去理解问题
  • 跳槽

    • 考虑个人发展的
    • 情谊在 好聚好散 来日方长
    • 走的时候 请兄弟们吃吃饭
  • 团队建设如何开展?

    • 抓流程 抓结果

    • 帮兄弟们铺路 服务好兄弟们 帮兄弟们解决合作、资源、需求整理、个人成长的问题