KAFKA

320 阅读24分钟

image.png

  • kafka,rocketmq 高可用,分布式架构,activemq,rabbitmq主从架构
  • 单机吞吐量,kafka,rocketmq 10万级,activemq,rabbitmq 万级
  • rabbitmq 时效性在微妙级别,kafka在毫秒以内级以内。另外两个毫秒级别
  • 一般来说,kafka功能简单,在大数据领域或者实时计算及日志采集被大规模使用,rocketmq功能较为完善,rocketmq能支持更多的topic,成百上千。active功能及其完善。
KAFKA(官网kafka.apache.org/documentati…
  • 定义:
  • Kafka 是一个分布式的基于发布/订阅模式的消息队列(Message Queue),主要应用于大数据实时处理领域。
- 为什么要使用消息队列kafka(好处):
  • [服务间的解耦,消息的积压]
  • 1)解耦
  • 允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
  • 2)异步通信
  • 很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们
  • 3)灵活性 & 峰值处理能力
  • 在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。(也称之为去峰)
  • 4)可恢复性
  • 系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
  • 缺点:
  • 1、、系统复杂度提高,问题,消息丢了怎么办,重复消费消息问题,消息的顺序问题。
  • 2、引入MQ 会导致处理结果的最终一致性问题。因为模块之间是异步的。【异步的通病】
- Kafka 基础架构

image.png

  • Producer

  • 主题(消息)生产者,发布消息的对象称之为主题生产者(Kafka topic producer)

  • Consumer

  • 主题(消息)消费者,订阅消息并处理发布的消息的对象称之为主题消费者

  • Consumer Group

  • 1、消费者组,由多个consumer组成,他们共享一个公共的ID,即group ID。

  • 2、一个分区只能有一个组内消费者消费;但反之并不成立,即一个consumer线程可以消费多个分区的数据。(注意为什么www.jianshu.com/p/a1b635f99…,和分区一起提高并发。

  • Broker

  • 已发布的消息保存在一组服务器中,称之为Kafka集群。集群中的每一个服务器都是一个代理(Broker)。消费者可以订阅一个或者多个主题(topic),并从Broker拉取数据,从而消费这些已经发布的数据。

  • Topic

  • Kafka将这些消息分门别类,每一类消息称之为主题(Topic),是进行生产发布和消费订阅的基本单位。

  • Partition

  • 为了实现扩展性,一个非常大的topic可以分布到多个broker(即服务器)上,一个topic可以分为多个partition,每个partition都是有序的队列。其实分区的作用就是提供负载均衡的能力,或者说对数据进行分区的主要原因,就是为了实现系统的可伸缩性(Scalability)。不同的分区能够被放置到不同节点的机器上,数据的读写操作也都是针对分区这个粒度进行的,这样每个节点的机器都能独里地执行各个分区的读写请求处理。还可以通过添加新的节点机器来增加整体系统的吞吐量。

消息发送流程

image.png

  • 整个生产者客户端由两个线程协调运行,这两个线程分别为主线程和Sender线程(发送线程)。这两个线程有一个共享变量RecordAccumulator。在主线程中由KafkaProducer创建消息,然后通过可能的拦截器、序列化器和分区器的作用之后缓存到消息累加器(RecordAccumulator,也称为消息收集器)中。

  • Sender线程负责从RecordAccumulator中获取批量消息并将其发送到Kafka中。batch.size:只有数据积累到 batch.size 之后,sender 才会发送数据。

  • linger.ms:如果数据迟迟未达到 batch.size,sender 等待 linger.time 之后就会发送数据。

  • Kafka 中的分区器、序列化器、拦截器是否了解?它们之间的处理顺序是什么?

  • 拦截器:interceptor使得用户在消息发送前以及producer回调逻辑前有机会对消息做一些定制化需求,比如修改消息等。producer允许用户指定多个interceptor按序作用于同一条消息从而形成一个拦截链(interceptor chain)。

  • 序列化器:数据对象在进行网络传输时候,需要把对象序列化为二进制或者JSON。Kafka序列化器分为,键序列化器和值序列化器,将键和值都转为二进制流。

  • 分区器:按消息分区算法,把序列化好的数据分配到某个分区。默认是用轮询算法。

  • 执行顺序:拦截器doSend() -> 序列化器 -> 分区器

消息发生到不同分区(rebalance负载均衡)
  • 我们需要将 producer 发送的数据封装成一个 ProducerRecord 对象。
  • (1) 指明 partition 的情况下,直接将指明的值直接作为 partiton 值;
  • (2) 没有指明 partition 值但有 key 的情况下,将 key 的 hash 值与 topic 的 partition数进行取余得到 partition 值;
  • (3)既没有 partition 值又没有 key 值的情况下,第一次调用时随机生成一个整数(后面每次调用在这个整数上自增),将这个值与 topic 可用的 partition 总数取余得到 partition值,也就是常说的 round-robin 轮训
- 幂等性:【密等方式唯一标识符】
  • 所谓的幂等性就是指 Producer 不论向 Server 发送多少次重复数据,Server 端都只会持久化一条。enable.idompotence设置为true
  • At Least Once (ack=-1)+ 幂等性 = Exactly Once
  • 开启幂等性的 Producer 在初始化的时候会被分配一个producerID(PID),发往同一 Partition 的消息会附带 Sequence Number。
  • 而Broker 端会对<PID, Partition, SeqNumber>做缓存,当具有相同主键的消息提交时,Broker 只会持久化一条。幂等性只保证单次连接去重
  • (因为重启PID会发生变法,不同的Partition也有不同主键)
  • 但是幂等性无法保证跨分区会话的Exactly Once。
Kafka 工作流程及文件存储机制

image.png

  • topic 是逻辑上的概念,而 partition 是物理上的概念。
  • 每个 partition 对应于一个 log 文件,该 log 文件中存储的就是 producer 生产的数据。Producer 生产的数据会被不断追加到该 log 文件末端,且每条数据都有自己的 offset。消费者组中的每个消费者,都会实时记录自己消费到了哪个 offset,以便出错恢复时,从上次的位置继续消费。
  • 由于生产者生产的消息会不断追加到 log 文件末尾,为防止 log 文件过大导致数据定位 效率低下,Kafka 采取了分片和索引机制,将每个 partition 分为多个 segment。
  • 每个 segment 对应两个文件——“.index”文件和“.log”文件,文件名是当前segment的最小偏移量。这些文件位于一个文件夹下,(文件夹的命名 规则为:topic 名称+分区序号)。“.index”文件存储大量的索引信息,“.log”文件存储大量的数据,索引文件中的元数据指向对应数据文件中 message 的物理偏移地址。Log 里的数据大小不一样,很难快速定位,index 数据大小一样,能很快定位到。
【kafka高可用的原因】
  • 副本,做容灾处理。为保证集群中的某个节点发生故障时,该节点上的 partition 数据不丢失,且kafka任然可以正常工作,kafka提供副本机制。一个topic的每个分区都有若干个副本,一个leader和若干个follower。
  • • 所有的读写请求都是由leader副本来进行处理。
  • • follower副本会从leader副本同步消息日志
数据可靠性保证
  • 数据同步方式
  • ISR:【副本同步队列,SIR既包括leader 又包含follower】每个partition都对应一个Leader,Leader 维护了一个动态的 in-sync set (ISR),意为和 leader 保持同步的 follower 集合。当 ISR 中的 follower 完成数据的同步之后,follower就会给leader发送 ack。如果 follower 长时间 未 向 leader 同 步 数 据 , 则 该 follower 将 被 踢 出 ISR , 该 时 间 阈 值 由replica.lag.time.max.ms 参数设定。Leader 发生故障之后,就会从 ISR 中选举新的 leader。
  • OSR:Outof-Sync Replicas,被踢出的follower会存入到OSR列表,新加入的follower也会先存放在OSR中,AR=ISR+OSR
  • AR:Assigned Replicas 所有副本
  • 数据接收方式(是指broker向producer发起ack)
  • 对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据的少量丢失。
  • 可以通过设置参数acks=0,1,-1 控制
  • 0:producer 不等待 broker 的 ack,这一操作提供了一个最低的延迟,broker 一接收到还没有写入磁盘就已经返回,当 broker 故障时有可能丢失数据;(at most Once)
  • 1:producer 等待 broker 的 ack,partition 的 leader 落盘成功后返回 ack,如果在 follower 同步成功之前 leader 故障,那么将会丢失数据;
  • -1(all):producer 等待 broker 的 ack,partition 的 leader 和 follower 全部落盘成功后才 返回 ack。但是如果在 follower 同步完成后,broker 发送 ack 之前,leader 发生故障,那么会造成数据重复。当SIR中某个分区中的follower全部挂掉后,-all 退化为1(at Least Once)
- 发生故障:(ZK(ZAB)-->Mraft)
  • LEO(Log End Offset):指的是每个副本(follower)最大的 offset(下一个要插入的位置=(长度));
  • HW(High Watermark):指的是消费者能见到的最大的 offset,ISR 队列中最小的 LEO。
  • (1)follower 故障
  • follower 发生故障后会被临时踢出 ISR,待该 follower 恢复后,follower 会读取本地磁盘记录的上次的 HW,并将 log 文件高于 HW 的部分截取掉,从 HW 开始向 leader 进行同步。 等该 follower 的 LEO 大于等于该 Partition 的 HW,即 follower 追上 leader 之后,就可以重 新加入 ISR 了。
  • (2)leader 故障
  • leader 发生故障之后,会从 ISR 中选出一个新的 leader,之后,为保证多个副本之间的数据一致性,其余的 follower 会先将各自的 log 文件高于 HW 的部分截掉,然后从新的 leader 同步数据。
consumer是推还是拉
  • Kafka遵循了一种大部分消息系统共同的传统的设计:producer将消息推送到broker,consumer从broker拉取消息,由消费者根据自己的消费能力进行速度拉取。(为什么采用拉去的方式:一些消息系统比如Scribe和Apache Flume采用了push模式,将消息推送到下游的consumer。这样做有好处也有坏处:由broker决定消息推送的速率,对于不同消费速率的consumer就不太好处理了。消息系统都致力于让consumer以最大的速率最快速的消费消息,但不幸的是,push模式下,当broker推送的速率远大于consumer消费的速率时,consumer恐怕就要崩溃了。最终Kafka还是选取了传统的pull模式)。
  • Pull有个缺点是,如果broker没有可供消费的消息,将导致consumer不断在循环中轮询,直到新消息到t达。为了避免这点,Kafka有个参数可以让consumer阻塞知道新消息到达(当然也可以阻塞知道消息的数量达到某个特定的量这样就可以批量发送)。
  • 哪个group consumer里的consumer去消费哪个分区。
  • blog.csdn.net/qq_26838315…
  • offset的维护:
  • 从 0.9 版本开, consumer 默认将 offset 保存在 Kafka 一个内置的 topic
  • 中,该 topic 为**__consumer_offsets**
  • blog.csdn.net/qq_33446500…
Zookeeper 在 Kafka 中的作用
  • zookeeper的作用。新的consumer使用了kafka内部的group coordination协议,也减少了对zookeeper的依赖,但是broker依然依赖于ZK,zookeeper 在kafka中还用来选举controller 和检测broker是否存活等等
  • Controller作用:负责管理集群broker的上下线,所有topic的分区副本分配和leader选举等工作。
  • zhuanlan.zhihu.com/p/505306466
- 消息消费流程
  • Consumer 消费数据时的可靠性是很容易保证的,因为数据在 Kafka 中是持久化的,故不用担心数据丢失问题。出现故障后,只需要从故障前的位置offset继续消费就可以恢复。consumer 需要自己实时记录自己消费到了哪个 offset

  • Offset 提交:

  • 分别是 commitSync(同步提交)和 commitAsync(异步提交)。两者的相同点是,都会将本次 poll 的一批数据最高的偏移量提交;不同点是,

  • commitSync 阻塞当前线程,一直到位点提交完成提交成功;

  • commitAsync 不阻塞当前线程,消费完成后,立马消费下一个消息。另一个异步线程去提交位点;也就是说kafka允许你不提交位点就去消费下一个消息

  • Kafka 会确保位点按提交顺序更新,也就是说你先提交位点大的,在提交个位点小的;kafka并不会把位点小的给丢掉,而是用小的位点去覆盖大的位点,这是因为要支持消息回放,位点重置机制;

数据消费和重复消费:

  • 无论是同步提交还是异步提交 offset,都有可能会造成数据的漏消费或者重复消费。(典型异步提交)先提交offset 后消费,有可能造成数据的漏消费;(同步提交)而先消费后提交 offset,有可能会造成数据的重复消费。

  • mq回放也会造成重复消费,所以消费mq一定要做好密等。

  • 另外消费者在消费异常或者消费超时的时候,会触发重试,本身也就是一个任务管理模块

- Kafka 高性能的原因

1、* Producer积累消息批量发送(减少网络I/O次数)。消息先存入缓冲区,按时间或大小阈值批量发送,减少网络抖动影响。 2、* 对批量消息统一压缩(GZIP/Snappy),减少网络带宽占用(压缩后体积降至30%~50%) 3、* Reactor网络模型:基于Java NIO,主Acceptor线程接收连接,多Processor线程处理请求,避免阻塞。 4、* 消息先写入操作系统的页缓存(内存),而非直接刷盘,由内核异步刷盘,减少用户态与内核态切换。避免JVM堆内存管理(减少GC压力),直接利用OS高效缓存机制 5、* 内存映射文件(MMAP):将磁盘文件映射到内存地址空间,读写操作直接操作内存,减少系统调用。 6、* 分段日志 + 索引文件 7、* 顺序写磁盘: Kafka 的 producer 生产数据,要写入到 log 文件中,写的过程是一直追加到文件末端, 为顺序写。顺序写比随机写要快6倍,是因为其省去了大量磁头寻址的时间。 8、* 分区并发读写 9、* 零复制技术:零拷技术减少拷贝次数,传统数据流:磁盘 → 内核缓存 → 用户缓存 → Socket缓存 → 网卡(4次拷贝,2次CPU切换)。Kafka优化:通过sendfile系统调用,直接从页缓存传输到网卡(仅1次内核态拷贝),减少CPU与内存占用。

Kafka优化:通过sendfile系统调用,直接从页缓存传输到网卡(仅1次内核态拷贝),减少CPU与内存占用。

  • Pull 拉模式:使用拉模式进行消息的获取消费,与消费端处理能力相符
Kafka 中是怎么体现消息顺序性的?
  • Kafka 只能保证分区内消息顺序有序,无法保证全局有序。要保证全局顺序性,一个 topic,一个 partition,一个 consumer,内部单线程消费。
  • 数据库主键分区消费,特别是以binlog做数据源的时候,要保证同一条数据库记录的变更先后顺序进行消费。要保证按某个规则进行hash,确保同一个主键id要分到一个分区内。而不是随机分区。
消费组中的消费者个数如果超过 topic 的分区,那么就会有消费者消费不到数据”这句话是否正确?
  • 消费组内的一个消费者负责消费一个分区,消费组中的消费者个数如果超过 topic 的分区,那么就会有消费者消费不到数据
* topic 的分区数可不可以增加?如果可以怎么增加?如果不可以,那又是为什么?
  • 当分区数增加时,就会触发订阅该主题的所有 Group 开启 Rebalance。 Rebalance 过程对 Consumer Group 消费过程有极大的影响。在 Rebalance 过程中,所有 Consumer 实例都会停止消费,等待 Rebalance 完成。这是 Rebalance 为人诟病的一个方面。 为什么,因为没有采用一致性hash或者redis槽的模式,或者habse基于行键的模式。需要重新计算hash重新移动到对应的分区里。且需要同步元数据到客服端。
* topic的分区数可不可以减少?如果可以怎么减少?如果不可以,那又是为什么?
  • 不可以减少,减少的分区消息如何保留?不保留消息丢失,保留,怎么保留,直接插到现有分区的尾部消息的时间戳就不会递增,如果分散插入到现有的分区中,那么在消息量很大的时候,内部的数据复制会占用很大的资源。顺序性问题、事务性问题、以及分区和副本的状态机切换问题都是不得不面对的。反观这个功能的收益点却是很低
* Kafka中有那些地方需要选举?这些地方的选举策略又有哪些?
  • partition leader(ISR),controller控制器(先到先得)
kafka producer如何优化打入速度
  • 增加线程

  • 提高 batch.size

  • 增加更多 producer 实例

  • 增加 partition 数

  • 设置 acks=-1 时,如果延迟增大:可以增大 num.replica.fetchers(follower 同步数据的线程数)来调解;

  • 跨数据中心的传输:增加 socket 缓冲区设置以及 OS tcp 缓冲区设置。

* 数据传输的事务定义通常有以下三种级别:
  • (1)最多一次: 消息不会被重复发送,最多被传输一次,但也有可能一次不传输

  • (2)最少一次: 消息不会被漏发送,最少被传输一次,但也有可能被重复传输.

  • (3)精确的一次(Exactly once): 不会漏传输也不会重复传输,每个消息都传输被一次而且仅仅被传输一次,这是大家所期望的

  • Mq 和redis 实现队列的区别:

  • zhuanlan.zhihu.com/p/137388362

kafka 使用的是长链接还是长轮训

既不是传统的长链接,也不是传统的长轮训。而是基于tcp长链接,kafka自定义的长轮训

  • 传输层:基于 TCP 长连接(消费者与 Broker 建立持久连接)
  • 应用层:使用 Kafka 自定义二进制协议(非 HTTP/HTTPS)

四、为什么 Kafka 不采用 HTTP 协议?

  1. 性能考量

    • HTTP 头部开销大(Kafka 二进制协议更紧凑)
    • 无法实现高效的零拷贝传输
  2. 状态管理

    • Kafka 需要维护消费位移(Offset),TCP 长连接更适合状态跟踪
  3. 吞吐需求

    • HTTP 难以支撑 Kafka 设计的高吞吐(百万级 QPS)

场景:Kafka 消费者

  • 长轮询:消费者发送 FetchRequest,Broker 无消息时阻塞,直到新消息到来或超时。
  • 长连接:消费者与 Broker 通过 TCP 长连接复用请求,减少连接开销。

各种链接区别

特性WebSocketSSEHTTP 长连接(Keep-Alive)HTTP 长轮询(Long Polling)
协议类型独立协议(基于 TCP,全双工)HTTP 长连接(单向流)HTTP 连接复用HTTP 短连接 + 阻塞等待
数据方向双向实时通信服务端→客户端(单向)客户端→服务端(请求-响应)客户端→服务端(请求-响应)
连接建立HTTP 升级协议(Upgrade 头部)普通 HTTP 请求(text/event-stream复用 TCP 连接每次请求独立建立连接
实时性毫秒级延迟准实时(服务端推送)依赖轮询频率【客服端需要不断轮训】(如 1秒/次)依赖服务端阻塞时间(如 30秒)
数据格式二进制/文本帧文本(UTF-8)任意(JSON/XML等)任意(JSON/XML等)
兼容性需浏览器支持 WebSocket API需浏览器支持 EventSource API所有 HTTP/1.1+ 支持所有 HTTP 支持

延迟队列

(1)kafka 本身不支持延迟队列,但是RocketMQ支持:

1. 核心机制,这个地方其实就是搞个定时任务。【kafka本身就是一个任务管理模块,相当于定时任务和任务管理模块结合了。】

RocketMQ 的延迟消息不支持任意时间精度,而是通过预定义的 18 个延迟级别(Level 1~18)实现,每个级别对应固定延迟时间:

延迟级别12345618
延迟时间1s5s10s30s1m2m2h

2. 实现原理

2.1 消息发送阶段

  • 生产者发送消息时指定延迟级别(如 Level=3 表示延迟 10s):

    ``

    Message msg = new Message("Topic", "Tag", "Hello RocketMQ".getBytes()); // 设置延迟级别为3(对应10秒) msg.setDelayTimeLevel(3); producer.send(msg);

2.2 Broker 存储延迟消息

  • Broker 收到延迟消息后,不会直接存入目标 Topic,而是转发到内部 Topic SCHEDULE_TOPIC_XXXX

  • 每个延迟级别对应一个队列

    • Level 1 → SCHEDULE_TOPIC_XXXX 的 Queue 0
    • Level 2 → Queue 1
    • Level 18 → Queue 17

2.3 定时任务扫描

  • RocketMQ Broker 启动 定时调度线程ScheduleMessageService),每个延迟级别对应一个定时任务。

  • 任务逻辑:

    1. 计算投递时间:根据延迟级别的时间定义,计算消息的目标投递时间(如 Level=3 则当前时间 + 10s)。
    2. 轮询队列:定时(默认每秒)扫描 SCHEDULE_TOPIC_XXXX 的每个队列。
    3. 检查消息到期:从队列中拉取消息,若消息的存储时间已超过目标延迟时间,则执行重投递。
    4. 转发到目标 Topic:将到期消息重新存入原始目标 Topic(如 Topic),此时消费者才能消费到该消息。


4. 设计优缺点

优点

  • 简单高效:基于预定义级别和队列分片,避免复杂的时间轮或优先级队列。
  • 资源可控:每个延迟级别独立处理,避免单队列压力过大。
  • 持久化可靠:消息存储与普通消息一致,Broker 宕机后恢复可继续处理。

缺点

  • 不灵活:延迟时间固定为 18 个级别,不支持任意时间(如 7秒、15分钟等)。
  • 最大延迟限制:最高延迟为 2 小时(Level 18),无法满足更长延迟需求。

5. 适用场景

  • 标准化延迟需求:如订单超时关闭(30分钟)、提醒消息(5秒后推送)等。
  • 无需精准时间:接受固定延迟级别的业务场景。

(2)如果想支持任意时间延迟

生产者发送消息时携带时间戳

消费者拉取消息时,服务端根据当前时间和延迟时间戳对比,满足就给,不满足就不给

缺点

  • 消息会多次被拉取,直到满足延迟时间,浪费资源。