Kafka 杂谈
开始之前
首先,此篇文章会有很多地方会和 RocketMQ 比较,不太熟悉 RocketMQ 可以去看看我之前写的RocketMQ基础概念剖析&源码解析,先有个大概的印象,可能会帮助你更好的理解 Kafka。
概览
什么是 Kafka?
这里先给出结论,我不太希望在解释概念 X 的时候,说到「为了了解 X,我们需要先了解一下 Y」,阅读的人思绪会被迁到另一个地方。既然小标题里说了要解释什么是 Kafka,那么我们就只说什么是 Kafka。
专业点讲,Kafka 是一个开源的分布式事件流的平台。通俗点讲,Kafka 就是一个消息队列。
事件流的定义
这才是一个正常的抛概念的顺序,而不是「我们要了解 Kafka,就需要先了解一下 事件流...」
怎么理解这个事件流呢?拿人来类比的话,你可以简单的把它理解成人的中枢神经系统,它是人体神经系统最主要的部分。中枢神经接收全身各个部位的信息输入,然后再发出命令,让身体执行适当的反应。甚至可以说,神经系统可以控制整个生物的行为。
通过这个类比相信你能够理解件流的重要性。
而切回到技术视角来看,事件流其实就是从各种类型的数据源收取实时数据。对应到我们平时对消息队列的用途来说,可以理解为有很多个不同的、甚至说不同种类的生产者,都能够向同一个 Topic 写入消息。
收集到这些事件流后,Kafka 会将它们持久化起来,然后根据需要,将这些事件路由给不同的目标。也换个角度理解,一个 Topic 中所存放的消息(或者说事件)可以被不同的消费者消费。
事件流的用途
现在我们知道了事件流的重要性,上面也拿中枢神经系统做了对比,我们清楚中枢神经系统可以做些什么,那么事件流呢?它能拿来做啥呢?
举例来说,像我们平时网购东西,上面会显示你的快递现在走到哪里了。这就是通过事件流来实时跟踪、监控汽车、卡车或者船只,在物流、汽车行业这样用的比较多;比如,持续的捕获、分析来自物联网设备或者其他设备的传感器数据;通过监测住院病人的数据,来预测病人的病情变化等等这些。
那这个跟 kafka 有啥关系呢?因为除了这些,还有一个比较重要的用途那就是作为一个数据平台、事件驱动架构的基石,而 Kakfa 刚好就是这么一个平台。
Kafka 由来
这块,之前的文章有过介绍,为了避免赘述我就直接贴过来了
Kafka 最初来自于 LinkedIn,是用于做日志收集的工具,采用Java和Scala开发。其实那个时候已经有 ActiveMQ了,但是在当时 ActiveMQ 没有办法满足 LinkedIn 的需求,于是 Kafka 就应运而生。
在 2010 年底,Kakfa 的0.7.0被开源到了Github上。到了2011年,由于 Kafka 非常受关注,被纳入了 Apache Incubator,所有想要成为 Apache 正式项目的外部项目,都必须要经过 Incubator,翻译过来就是孵化器。旨在将一些项目孵化成完全成熟的 Apache 开源项目。
你也可以把它想象成一个学校,所有想要成为 Apache 正式开源项目的外部项目都必须要进入 Incubator 学习,并且拿到毕业证,才能走入社会。于是在 2012 年,Kafka 成功从 Apache Incubator 毕业,正式成为 Apache 中的一员。
Kafka 拥有很高的吞吐量,单机能够抗下十几w的并发,而且写入的性能也很高,能够达到毫秒级别。而且 Kafka的功能较为简单,就是简单的接收生产者的消息,消费者从 Kafka 消费消息。
既然 Kafka 作为一个高可用的平台,那么肯定需要对消息进行持久化,不然一旦重启,所有的消息就都丢了。那 Kafka 是怎么做的持久化呢?
设计
持久化
当然是磁盘了,并且还是强依赖磁盘。
不了解的可能会认为:「磁盘?不就是那个很慢很慢的磁盘?」这种速度级的存储设备是怎么样和 Kafka 这样的高性能数据平台沾上边的?
确实我们会看到大量关于磁盘的描述,就是慢。但实际上,磁盘同时集快、慢于一身,其表现具体是快还是慢,还得看我们如何使用它。
举个例子,我们可能都听过,内存的顺序 IO 是慢于内存的随机 IO 的,确实是这样。磁盘自身的随机 IO 和顺序 IO 也有非常大的差异。比如在某些情况下,磁盘顺序写的速度可能是 600MB/秒,而对于磁盘随机写的速度可能才 100KB/秒,这个差异达到了恐怖的 6000 倍。
对磁盘的一些原理感兴趣可以看看我之前写的文章
Kafka 其实就是用实际行动来告诉我们「Don't fear the filesystem」,现在顺序写、读的性能表现是很稳定的,并且我们的大哥操作系统也对此进行了大量的优化。
了解了持久化,解决了消息的存、取问题,还有什么更重要呢?
效率
当然是效率,持久化能保证你的数据不丢,这可能只做到了一半,如果对消息的处理效率不高,仍然不能满足实际生产环境中海量的数据请求。
举个例子,现在请求一个系统的一个页面都有可能会产生好几十条消息,这个在复杂一些的系统里丝毫不夸张。如果投递、消费的效率不提上去,会影响到整个核心链路。
影响效率的大头一半来说有两个:
- 大量零散的小 IO
- 大量的数据拷贝
这也是为啥大家都要搞 Buffer,例如 MySQL 里有 Log Buffer,操作系统也有自己的 Buffer,这就是要把尽量减少和磁盘的交互,减少小 IO 的产生,提高效率。
比如说,Consumer 现在需要消费 Broker 上的某条消息,Broker 就需要将此消息从磁盘中读取出来,再通过 Socket 将消息发送给 Consumer。那通常拷贝一个文件再发送会涉及到哪些步骤?
- 用户态切换到内核态,操作系统将消息从磁盘中读取到内核缓冲区
- 内核态切换到用户态,应用将内核缓冲区的数据 Copy 到用户缓冲区
- 用户态切换到内核态,应用将用户缓冲区的内容 Copy 到 Socket 缓冲区
- 将数据库 Copy 到网卡,网卡会将数据发送出去
- 内核态切换到用户态
可能你看文字有点懵逼,简单总结就是,涉及到了 4 次态的切换,4 次数据的拷贝,2次系统调用。

红色的是态的切换,绿色的是数据拷贝。
不清楚什么是用户态、内核态的可以去看看《用户态和内核态的区别》
态的切换、数据的拷贝,都是耗时的操作,那 Kafka 是怎么解决这个问题的呢?
其实就是我们常说的零拷贝了,但是不要看到零就对零拷贝有误解,认为就是一次都没有拷贝,那你想想,不拷贝怎么样把磁盘的数据读取出来呢?
所谓的零拷贝是指数据在用户态、内核态之间的拷贝次数是 0。
最初,从磁盘读取数据的时候是在内核态。
最后,将读取到的数据发送出去的时候也在内核态。
那读取——发送这中间,是不是就没有必要再将数据从内核态拷贝到用户态了?Linux 里封装好的系统调用 sendfile 就已经帮我们做了这件事了。
简单描述一下:「在从磁盘将数据读取到内核态的缓冲区内之后(也就是 pagecache),直接将其拷贝到网卡里,然后发送。」
这里严格上来说还有 offset 的拷贝,但影响太小可以忽略不就,就先不讨论
你会发现,这里也应证了我上面说的「零拷贝并不是说没有拷贝」。算下来,零拷贝总共也有 2 次态的切换,2 次数据的拷贝。但这已经能大大的提升效率了。
到此为止,我们聊到了消息已经被发送出去了,接下来就是消费者接收到这条消息然后开始处理了。那这部分会有效率问题吗?
答案是肯定的,随着现在的计算机发展,系统的瓶颈很多时候已经不是 CPU 或者磁盘了,而是网络带宽。对带宽不理解的你就把带宽理解成一条路的宽度。路宽了,就能同时容纳更多的车行进,堵车的概率也会小一些。
那在路宽不变的基础上,我们要怎么样跑更多的车呢?让车变小(现实中别这么干,手动狗头)。
换句话说,就是要对发送给 Consumer 的信息进行压缩。并且,还不能是来一条压缩一条,为啥呢?因为同类型的一批消息之间会有大量的重复,将这一批进行压缩能够极大的减少重复,而相反,压缩单条消息效果并不理想,因为你没有办法提取公共冗余的部分。Kafka 通过批处理来对消息进行批量压缩。
Push vs Pull
关于这个老生常谈的问题,确实可以简单的聊聊。我们都知道 Consumer 消费数据,无非就是 pull 或者 push。可能在大多数的情况下,这两个没啥区别,但实际上大多数情况下还是用的 pull 的方式。
那为啥是 pull?
假设现在是采取的 push 的方式,那么当 Broker 内部出现了问题,向 Consumer push 的频率降低了,此时作为消费方是不是只能干着急。想象一下,现在产生了消息堆积,我们确啥也干不了,只能等着 Broker 恢复了继续 push 消息到 Consumer。
那如果是 pull 我们怎么解决呢?我们可以新增消费者,以此来增加消费的速率。当然新增消费者并不总是有效,例如在 RocketMQ 中,消费者的数量如果大于了 MessageQueue 的数量,多出来的这部分消费者是无法消费消息的,资源就被白白浪费了。
Kafka 中的 Partition 也是同理,在新增消费者的时候,也需要注意消费者、Partition 的数量。
除此之外,采用 pull 能使 Consumer 更加的灵活,能够根据自己的情况决定什么时候消费,消费多少。
关于消费
这个问题其实在消息系统里也很经典。
Consumer 从 Broker 里拉取数据消费,那 Consumer 如何知道自己消费到哪儿了?Broker 如何知道 Consumer 消费到哪儿了?双方如何达成共识?
我们假设,Broker 在收到 Consumer 的拉取消息请求并发送之后,就将刚刚发送的消息给删除了,这样 OK 吗?
废话,这当然不行,假设 Broker 把消息发给 Consumer 了,但由于 Consumer 挂了并没有收到这些消息,那这些消息就会丢失。
所以才有了我们都熟悉的 ACK(Acknowlegement)机制,Broker 在将消息发出后,将其标识为「已发送|未消费」,Broker 会等待 Consumer 返回一个 ACK,然后再将刚刚的消息标识为「已消费」。
这个机制在一定程度上解决了上面说的消息丢失的问题,但事情总有双面性, ACK 机制又引入了新的问题。
举个例子,假设 Consumer 收到了、并且正确的消费了消息,但偏偏就是在返回 ACK 时出了问题,导致 Broker 没有收到。则在 Broker 侧,消息的状态仍然是「已发送|未消费」,下次 Consumer 来拉,仍然会拉取到这条消息,此时就发生了重复消费。
欢迎 wx 关注「SH的全栈笔记」
Kafka 杂谈的更多相关文章
- Java后台服务慢优化杂谈
		Java后台服务慢优化杂谈 前言 你是否遇到过这样的场景,当我们点击页面某个按钮后,页面一直loading,要等待好几分钟才出结果的画面,有时直接502或504,作为一个后台开发,看到自己开发的系统是 ... 
- canal+mysql+kafka实时数据同步安装、配置
		canal+mysql+kafka安装配置 概述 简介 canal译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费. 基于日志增量订阅和消费的业务包括 数 ... 
- Spark踩坑记——Spark Streaming+Kafka
		[TOC] 前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark strea ... 
- 消息队列 Kafka 的基本知识及 .NET Core 客户端
		前言 最新项目中要用到消息队列来做消息的传输,之所以选着 Kafka 是因为要配合其他 java 项目中,所以就对 Kafka 了解了一下,也算是做个笔记吧. 本篇不谈论 Kafka 和其他的一些消息 ... 
- kafka学习笔记:知识点整理
		一.为什么需要消息系统 1.解耦: 允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束. 2.冗余: 消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险. ... 
- .net windows Kafka 安装与使用入门(入门笔记)
		完整解决方案请参考: Setting Up and Running Apache Kafka on Windows OS 在环境搭建过程中遇到两个问题,在这里先列出来,以方便查询: 1. \Jav ... 
- kafka配置与使用实例
		kafka作为消息队列,在与netty.多线程配合使用时,可以达到高效的消息队列 
- kafka源码分析之一server启动分析
		0. 关键概念 关键概念 Concepts Function Topic 用于划分Message的逻辑概念,一个Topic可以分布在多个Broker上. Partition 是Kafka中横向扩展和一 ... 
- Kafka副本管理—— 为何去掉replica.lag.max.messages参数
		今天查看Kafka 0.10.0的官方文档,发现了这样一句话:Configuration parameter replica.lag.max.messages was removed. Partiti ... 
- Kafka:主要参数详解(转)
		原文地址:http://kafka.apache.org/documentation.html ############################# System ############### ... 
随机推荐
- RTE NG-Lab:一起探索下一代实时互动新世界
			互联网已经彻底改变了我们的工作和生活.从纸书信笺,到智能手机中的 App,再到 VR 头显,实时互动体验逐代升级,已经成为了我们生活的一部分.随着元宇宙的爆火,新增的实时互动场景日益颠覆着我们的想象力 ... 
- 基于Vue 使用threejs导入gltf动画模型
			被老师要求学习这个完全不懂的领域的知识,代码东拼西凑终于搞定了,可能写的不好,但这方面的教程很少 某CS**平台的教程都是互相抄,看着烦死. <template> <div id=& ... 
- Synchronized 关键字详解
			更多内容,前往 IT-BLOG Synchronized原理分析 加锁和释放锁的原理 深入JVM看字节码,创建如下的代码: 1 public class SynchronizedDemo2 { 2 O ... 
- .NET生成MongoDB中的主键ObjectId
			前言 因为很多场景下我们需要在创建MongoDB数据的时候提前生成好主键为了返回或者通过主键查询创建的业务,像EF中我们可以生成Guid来,本来想着要不要实现一套MongoDB中ObjectId的,结 ... 
- C++类的构造函数、析构函数、拷贝构造函数、赋值函数和移动构造函数详细总结
			目录 1. 五种函数介绍 2. 左值&右值怎么区分?怎么看? 3. 匿名对象的3种使用情况 4. 代码详细验证每个函数调用情况 4.1 测试 f_1 函数(函数形参测试 -- 值传递) 4.2 ... 
- VUE零碎小技巧1
			1.回顾 创建项目 vue create myapp 准备 scss 库 修改了页面的主结构 App.vue 构建页面的基本结构 分离页面主结构,创建各个页面组件 views views/home/i ... 
- 计网学习笔记六 Network Layer Overview
			这节课开始进入了网络层的学习,讲述了网络层提供的功能,还有路由器内部是什么样子的,以及virtual circuit网络和datagram网络的一点比较. 网络层有什么作用呢?用一句话来说,就是需要负 ... 
- C++ 测试框架 GoogleTest 初学者入门篇 乙
			*以下内容为本人的学习笔记,如需要转载,请声明原文链接微信公众号「ENG八戒」https://mp.weixin.qq.com/s/aFeiOGO-N9O7Ab_8KJ2wxw 开发者虽然主要负责工程 ... 
- Redis读书笔记(二)
			Redis对象系统 Redis对象 字符串(String)的底层实现方式 直接保存整数值:字符串对象保存的是整数值,且可以用long类型来表示. embstr编码的SDS:字符串对象保存的是一个长度小 ... 
- 深度学习--魔法类nn.Module
			深度学习--魔法类nn.Module 作用 pytorch 封装了一些基本的网络类,可以直接调用 好处: 可以直接调用现有的类 容器机制:self.net = nn.Sequential() 参数返回 ... 
