概述什么是Kafka?我先在这里做个总结。我不太想解释X这个概念,说“要理解X,我们需要先理解Y”,读者的思绪会转移到另一个地方。既然副标题说解释什么是Kafka,那我们就直接说说Kafka是什么吧。从专业上讲,Kafka是一个开源的分布式事件流平台。通俗地说,Kafka就是一个消息队列。事件流的定义是抛出概念的正常顺序,而不是“理解Kafka,我们需要理解事件流……”如何理解这个事件流?如果用人来类比,可以简单理解为成年人的中枢神经系统,这是人体神经系统中最重要的部分。中枢神经系统接收来自身体各部分的输入,然后发出命令让身体执行适当的反应。甚至可以说,神经系统可以控制整个生物体的行为。通过这个类比,相信大家就能明白邮件流量的重要性了。回到技术层面,事件流其实就是从各种类型的数据源中收集实时数据。对应我们平时使用的消息队列,可以理解为有很多种不同的,甚至是不同类型的生产者,可以向同一个Topic写入消息。收集完这些事件流后,Kafka会将它们持久化,然后根据需要将这些事件路由到不同的目标。从另一个角度来看,存储在一个Topic中的消息(或事件)可以被不同的消费者消费。事件流的用途现在我们知道了事件流的重要性,对比上面的中枢神经系统,我们知道中枢神经系统能做什么,那么事件流呢?它可以用来做什么?比如我们平时网购,它会显示你的快递员现在去了哪里。这是通过事件流实时跟踪和监控汽车、卡车或轮船,常用于物流和汽车行业;例如,持续捕获和分析来自物联网设备或其他设备的传感器数据;数据,预测患者病情变化等等。那么这和Kafka有什么关系呢?因为除了这些之外,还有一个重要的目的,就是作为一个数据平台和事件驱动架构的基石,而Kakfa恰好就是这样一个平台。Kafka的由来在上一篇已经介绍过了。为了避免重复,我就直接贴出来了。Kafka最初来自LinkedIn。它是一个日志收集工具,使用Java和Scala开发。其实ActiveMQ在那个时候就已经存在了,只是那个时候ActiveMQ不能满足LinkedIn的需求,于是Kafka应运而生。2010年底,Kakfa0.7.0在Github上开源。2011年,因为Kafka受到了很多关注,被收录在ApacheIncubator中。所有想要成为官方Apache项目的外部项目都必须经过孵化器,即孵化器。旨在将一些项目孵化为完全成熟的Apache开源项目。您也可以将其视为一所学校。所有想成为Apache官方开源项目的外部项目,都必须进入孵化器学习,拿到文凭,才能进入社会。于是在2012年,Kafka顺利从ApacheIncubator毕业,正式成为Apache的一员。Kafka的吞吐量非常高,单机可以承受十几瓦的并发,写入性能也非常高,达到毫秒级别。而且Kafka的功能比较简单,就是简单的接收来自生产者的消息,消费者消费来自Kafka的消息。由于Kafka是高可用平台,消息必须持久化,否则重启后消息全部丢失。那么Kafka是如何持久化的呢?设计持久化当然是磁盘,而且还是强依赖磁盘。不明白的人可能会想:“磁盘?不是很慢的磁盘吗?这种速度级别的存储设备如何与Kafka这样的高性能数据平台取得联系?确实,我们会看到很多关于磁盘的描述,磁盘很慢。但实际上,磁盘是同时快和慢的。它的性能是快还是慢取决于我们如何使用它。比如我们可能听说过内存的顺序IO比内存的随机IO要慢,事实确实如此。磁盘本身的随机IO和顺序IO也有很大区别。例如,在某些情况下,磁盘的顺序写入速度可能是600MB/s,而磁盘的随机写入速度可能只有100KB/s,相差是恐怖的6000倍。如果你对磁盘的一些原理感兴趣,可以看看我之前写的那篇文章。Kafka其实是在用实际行动告诉我们“Don'tfearthefilesystem”。现在顺序写入和读取的性能已经很稳定了,我们的大哥操作系统也为此进行了大量的优化。理解了持久化,解决了消息的存、取问题,还有什么更重要的呢?效率当然是效率。持久化可以保证你的数据不丢失。这可能只完成了一半。如果消息的处理效率不高,仍然无法满足实际生产环境中的海量数据请求。比如现在请求一个系统的一个页面,可能会产生几十条消息,这在更复杂的系统中并不夸张。如果传递和消费的效率得不到提升,就会影响到整个核心环节。影响效率的因素主要有两个:大量分散的小IO,大量的数据副本。这就是为什么每个人都使用缓冲区的原因。比如MySQL中有LogBuffer,操作系统也有自己的Buffer。这是为了尽量减少与磁盘的交互,减少小IO的产生,提高效率。比如Consumer现在需要在Broker上消费某条消息,Broker需要从磁盘中读取这条消息,然后通过Socket将消息发送给Consumer。复制和发送文件通常涉及哪些步骤?用户态切换到内核态,操作系统从磁盘读取消息到内核缓冲区,内核态切换到用户态,应用程序将内核缓冲区中的数据复制到用户缓冲区。将区域内容复制到Socket缓冲区,将数据库复制到网卡,网卡将数据从内核态发送到用户态。阅读文本时,您可能会有些困惑。简单概括就是涉及到4个状态开关和4个数据传输。复制,2个系统调用。红色的是状态切换,绿色的是数据拷贝。如果不知道什么是用户态和内核态,可以去看《用户态和内核态的区别》模式切换和数据拷贝,这些都是比较耗时的操作。Kafka是如何解决这个问题的?其实就是我们常说的零拷贝,但是大家不要看到零就误解了零拷贝,以为根本就没有拷贝,那么想一想,不拷贝怎么从磁盘读取数据呢?所谓零拷贝,就是用户态和内核态之间的数据拷贝数为0。最初,内核态从磁盘读取数据。最后,读数据发送出去的时候,也是在内核态。在读发送的过程中,不需要把数据从内核态拷贝到用户态吗?Linux中打包的系统调用sendfile已经帮我们做了这件事。简要说明:“在内核态将数据从磁盘读入缓冲区(即pagecache)后,直接拷贝到网卡,然后发送。“严格来说这里还是有offset的副本,只是影响小到可以忽略不计,如果不先讨论一下,你会发现这也证明了我上面说的。”零副本不代表没有copy."算出来,零拷贝一共有2次状态切换,2次数据拷贝。不过这样效率大大提高了。至此,我们讲了消息发送完毕,接下来就是consumer收到消息开始处理,这部分会不会有效率问题?答案是有的,以现在计算机的发展,系统的瓶颈往往不是CPU或者磁盘,而是网络带宽。如果你不懂带宽,可以把带宽理解为道路的宽度。道路越宽,同时容纳的车越多,堵车的概率就会越小。所以在相同的基础上道路宽度,我们怎么能跑更多的车?让车变小(不要这样做我n现实,手动狗头)。也就是说,有必要对发送给Consumer的信息进行压缩。而且,也不能一一压缩,为什么呢?因为一批相同类型的消息之间会有很多重复,压缩这批消息可以大大减少重复。相反,压缩单个消息的效果并不理想,因为你没有办法提取公共冗余部分。Kafka通过批处理对消息进行批量压缩。PushvsPull关于这个老生常谈的问题,确实可以简单的说说。我们都知道Consumer消费数据无非是pull或者push。可能在大多数情况下,两者并没有什么区别,但实际上,在大多数情况下,还是会使用pull方式。那为什么要拉?假设现在采用push的方式,那么当Broker内部出现问题,减少了向Consumer推送的频率,这时候消费者就只能着急了。想象一下,现在消息堆积如山,我们真的无能为力,只能等待Broker恢复,继续向Consumer推送消息。那么如果是pull我们怎么解决呢?我们可以增加新的消费者来提高消费率。当然,增加新的消费者并不总是有效的。比如在RocketMQ中,如果consumer的数量大于MessageQueue的数量,多出的consumer将无法消费消息,资源就会被浪费。Kafka中的Partition也是如此。添加新的消费者时,还需要注意消费者数量和分区数量。另外,pull的使用可以让消费者更加灵活,可以根据自己的情况决定什么时候消费,消费多少。消费的问题其实在消息系统中是非常经典的。Consumer从Broker那里拉取数据消费,那么Consumer怎么知道自己消费到哪里了呢?Broker怎么知道Consumer在哪里消费呢?双方是如何达成共识的?假设Broker收到并发送了消费者拉取消息的请求后,删除了刚刚发送的消息。这个可以吗?废话,当然不行,假设Broker把消息发给了Consumer,但是因为Consumer挂了,没有收到消息,那么消息就会丢失。这就是为什么我们有我们都熟悉的ACK(确认)机制。Broker发送消息后,标记为“已发送|未消费”。Broker会等待Consumer返回ACK,然后将刚才的消息标记为“已被消费”。这种机制在一定程度上解决了上面提到的消息丢失问题,但是事情总是有两个方面的,ACK机制引入了新的问题。例如,假设Consumer收到并正确消费了消息,但是在返回ACK时出现问题,导致Broker收不到。在Broker端,消息的状态仍然是“已发送|未消费”,下次Consumer来拉取时,仍会拉取消息,此时会发生重复消费。
