# 消息队列之Kafka
作者:徐方友
审稿人:徐江河&李亮
## 消息队列
### 为什么使用消息队列?
* **异步通信**:在高并发业务环境下,常常会因为请求量超出负载而使系统发生阻塞。借助消息队列,发布/订阅模式允许生产者不断向队列中put/pubish数据和消费者主动/被动从队列中get/subscribe数据,这样可以异步处理请求,从而缓解系统的压力;
* **应用解耦**:作为中间件,消息队列可以作为分布式应用间的基于数据的接口层,这样我们可以在应用处理过程加入消息队列,应用程序就可以在不知道彼此位置的情况下独立处理消息;
* **应用扩展**:我们可以在遵循接口约束的前提下独立扩展或修改两边的处理过程;
* **冗余**:在有些情况下,处理数据的过程会失败,除非数据被持久化,否则将造成数据丢失,而消息队列把数据进行持久化直到它们已经被处理完;
* **数据缓冲**:数据不用存入数据库,减少数据库的压力;
* **峰值处理**:使关键应用能够抵挡住访问峰值,不会因为超负载请求而崩溃。
其中,异步通信、应用解耦和峰值处理是消息队列最突出的特性。
### 常见的消息队列系统
* **Kafka**:只有发布/订阅模式,当生产者将消息放在消息队列后,消费者在需要的时候主动去取,Kafka不会主动推送;
* **Redis**:提供基于分布式发布/订阅的消息推送,多用于实时性要求较高的环境;
* **RabbitMq**:生产者通过连接Channel和Server进行通信,消费者从队列中获取消息(长连接,队列有消息会推送给消费者,消费者循环从输入流读取消息);
* **JMS**:Java平台提供的面向消息中间件(MOM)的API,分为P2P和发布/订阅两种模式,后者用于在分布式系统中发送消息,进行异步通信(客户不需要主动发送请求,JMS会自动将消息发送给可用的客户端)。
## Kafka
Kafka是由Linked开发的基于发布/订阅的分布式消息系统,使用Scala语言编写。
### Kafka特性
* **高性能**:采用时间复杂度O(1)的磁盘存储结构,即使TB级以上数据也能保证常数时间的访问速度;
* **高可用/高容错**:多分区、多副本,允许集群中部分节点故障;
* **高吞吐**:即使在廉价商用机器上,也能达到单机传输10w条每秒的传输速度;
* **高并发**:支持数千个客户端同时读写;
* **可伸缩/热拓展**:新增机器时,集群无需停机、自动感知;
* 支持离线/实时数据处理;
* 可以实时处理大量数据以满足各种需求场景:Hadoop的批处理系统、低延迟的实时系统、Strom/Spark流式处理引擎。
### Kafka原理
#### Kafka基本概念
* **Broker**(代理)是Kafka的一个实例或节点,一个或多个Broker组成一个Kafka集群;
* **Topic**(主题)是Kafka中同类数据的集合,相当于SQL中的表;Topic是逻辑概念,即只需要指定Topic就可以写入/读取数据,而不必关心数据的存储位置;
* **Producer**(生产者)将同类数据写入同一个Topic(producer自己控制将消息推送哪些partition);
* **Consumer**(消费者)从同一个Topic中读取同类数据(consumer自己维护读取消息的offset);
* **Consumer Group**(CG,消费者组),一个Consumer都隶属于一个特定的CG;
* 一条消息可以发送给多个不同的CG,但是一个CG中只能有一个Consumer读取该消息;
* **Partition**(分区)是一个有序的、不可修改的MQ,分区内消息有序存储;Partition是物理概念,每个分区对应一个文件夹,其中存储分区的数据和索引文件;
* 一个Topic可分为多个Partition,即把一个数据集合分为多个部分,分别存储在不同的分区中;
* **Replication**(副本)是Partition的拷贝;
* 一个Partition可以设置多个Replication,Replication存储在多个Broker中;
* **ZooKeeper**负责Kafka集群管理,包括配置管理、动态扩展、Broker负载均衡、Laeder选取以及CG变化时的Rebalance,Kafka将元数据存储在ZooKeeper中。
#### Kafka工作机制
Kafka生产者客户端采用push模式将消息发送到broker,而消费者客户端采用pull模式从broker订阅并消费消息。消息在broker中按照topic进行分类,一个topic分为多个partition,一个partition有多个replication,消息存储在broker的topic的partition中,并且同时存在多个副本。partition是一个FIFO队列,在队列尾追加写入消息,在队列头顺序读取消息,而且仅保证消息在统一partition中有序,并不保证topic整体有序。
为了加快读取速度,多个consumer可归为一个consumer group,并行消费一个topic。一个topic可以被多个CG订阅,CG之间是平等的,即一个消息可同时被多个CG消费。一个CG中有多个consumer,CG中的consumer之间是竞争的,即一个消息在一个CG中只能被一个consumer消费。
Kafka集群会保存所有的消息,不管消息有没有被消费,只有当消息过期时,Kafka将其清除以释放磁盘空间。
### kafka为何高效
#### 类日志文件的消息持久化数据结构
不同于常规消息系统,Kafka并没有声明维护一个与消费队列有关的B树或者其它能够随机存取结构的元数据信息(B树不适用于并行化磁盘操作,磁盘寻道一次只能寻一个)。Kafka采用日志文件的方式存取消息,读和写都是常数时间并且不会相互阻塞。这样的设计具有极大的性能优势:最终系统性能和数据大小完全无关,服务器可以充分利用廉价的硬盘来提供高效的消息服务。此外,磁盘空间容量大小也不会影响到性能,因此,Kafka可以做到常规消息系统无法提供的特性:消息被消费后不用立马被删除,可以在磁盘中保留一段时间。
#### 消息分批
为了解决大量小I/O操作的问题,Kafka支持Producer将消息在内存中累计到一定数量后作为一个batch发布,减少请求次数,提高处理效率;在Broker中,消息是以消息块的形式追加到log中;Consumer在查询数据时也是一次获取大量的线性数据块。Kakfa实现了MessageSet来将一个字节数组或者文件进行打包。
#### 消息预处理
为了解决byte copy的问题,Kafka设计了一种“标准字节消息”,producer,broker和consumer共用这种消息格式。Kafka的log文件是以MessageSet的“标准字节消息”的格式写入到磁盘中的。
维持通用的格式对持久化log块的网络传输操作的优化尤为重要。我们可以采sendfile方法实现页面缓存和socket之间的数据传递。
一般的,我们将数据从文件传到socket的步骤如下:
1. OS将数据从磁盘读到内核空间的页缓存中;
2. 应用程序将数据从内核空间读到用户空间的缓存中;
3. 应用程序将数据写回内核空间的socket缓存中;
4. 操作系统将数据从socket缓存写到网卡缓存中。
这种操作方式明显是非常低效的,这里有四次拷贝,两次系统调用。如果使用sendfile,就可以避免两次拷贝:操作系统将数据直接从页缓存发送到网络上。
我们期望一个主题上有多个消费者是一种常见的应用场景。利用上述的zero-copy,数据只被拷贝到页缓存一次,然后就可以在每次消费时被重复利用,而不需要将数据存在内存中,然后在每次读的时候拷贝到内核空间中。这使得消息消费速度可以达到网络连接的速度。这样以来,通过页面缓存和sendfile的结合使用,整个Kafka集群几乎都已以缓存的方式提供服务,而且即使下游的consumer很多,也不会对整个集群服务造成压力。
### Kafka应用场景
* 日志收集:用Kafka收集各项服务的log,再通过Kafka以统一接口服务的方式开放给各种消费者;
* 消息系统:解耦生产者和消费者、缓存消息;
* 用户活动跟踪:使用Kafka将用户的各种活动记录发布到相关的Topic中,然后消费者通过订阅这些Topic来做实时的监控分析,或者装载到Hadoop中做离线分析;
* 流式处理:Strom、Spark Streaming。
## 参考
[1] [为什么使用kafka?](https://blog.csdn.net/SJF0115/article/details/78480433)
[2] [Java消息队列-JMS概述](https://www.cnblogs.com/jaycekon/p/6220200.html)
[3] [Kafka核心特性](https://blog.csdn.net/suifeng3051/article/details/48053965)
网友评论