什么是消息队列
“消息队列(Message Queue)”是在消息的传输过程中保存消息的容器。在消息队列中,通常有生产者和消费者两个角色。
消息队列的两种模式
- 点对点
- 消息进队列,一个或者多个消费者同时消费这一个队列里面的消息;
- 如:订单处理
- 发布订阅模式
- 消息进队列,消费者可以订阅一个或多个主题并使用该主题中的所有消息。
- 如:头条app中订阅多个频道(房产、汽车、健康),你就会收到这些频道的信息推送
什么是Kafka
Kafka 是由 Linkedin 公司开发的,它是一个分布式的,支持多分区、多副本,基于 Zookeeper 的分布式消息流平台 它同时也是一款开源的基于发布订阅模式的消息引擎系统。
重要概念
1)生产者(producer)
发出消息的服务
2)消费者(consumer)
读取消息的服务
3)主题(topic)
概念类比:数据库表
相当于消息的分类,一个topic里存放同一类消息,消费者订阅这个topic就会消费里面的消息
4)分区(partition)
概念类比:数据库分表中的一张表,包含部分数据
Topic 被分成多个 Partition 分区。 Topic 是一个逻辑概念,Partition 是最小的存储单元,掌握着一个 Topic 的部分数据。 每个 Partition 都是一个单独的 log 文件,每条记录都以追加的形式写入。
5)副本(Follower)
类比:从库中的备份表
Kafka 为一个 Partition 生成多个副本,并且把它们分散在不同的 Broker。
如果一个 Broker 故障了,Consumer 可以在其他 Broker 上找到 Partition 的副本,继续获取消息。
6)偏移量(Offset)
概念类比:分布式系统中的自己生成的ID
Partition 中的每条记录都会被分配一个唯一的序号,称为 Offset(偏移量)。 Offset 是一个递增的、不可变的数字,由 Kafka 自动维护。 当一条记录写入 Partition 的时候,它就被追加到 log 文件的末尾,并被分配一个序号,作为 Offset。
如上图,这个 Topic 有 3 个 Partition 分区,向 Topic 发送消息的时候,实际上是被写入某一个 Partition,并赋予 Offset。
- 消息的顺序性需要注意,一个 Topic 如果有多个 Partition 的话,那么从 Topic 这个层面来看,消息是无序的。
- 但单独看 Partition 的话,Partition 内部消息是有序的。
- 所以,一个 Partition 内部消息有序,一个 Topic 跨 Partition 是无序的。
- 如果强制要求 Topic 整体有序,就只能让 Topic 只有一个 Partition。
7)Broker
一个 Kafka 集群由多个 Broker(就是 Server) 构成,其实broker就是一个线程,每个 Broker 中含有集群的部分数据。
Kafka 把 Topic 的多个 Partition 分布在多个 Broker 中。
8)消费者组
Kafka 中有一个 Consumer Group(消费组)的概念,多个 Consumer 组团去消费一个 Topic。
同组的 Consumer 有相同的 Group ID。
Consumer Group 机制会保障一条消息只被组内唯一一个 Consumer 消费,不会重复消费。
消费组这种方式可以让多个 Partition 并行消费,大大提高了消息的消费能力,最大并行度为 Topic 的 Partition 数量。
例如一个 Topic 有 3 个 Partition,你有 4 个 Consumer 负责这个 Topic,也只会有 Consumer 工作,另一个作为后补队员,当某个 Consumer 故障了,它再补上去,是一种很好的容错机制。
工作流程分析
1)发送数据
Producer在写入数据的时候永远的找leader,不会直接将数据写入follower,消息写入leader后,follower是主动的去leader进行同步的!
-
使用 Partition Key 写入特定 Partition
Producer 发送消息的时候,可以指定一个 Partition Key,这样就可以写入特定 Partition 了。 -
由 kafka 决定
如果没有使用 Partition Key,Kafka 就会使用轮询的方式来决定写入哪个 Partition。
这样,消息会均衡的写入各个 Partition。
但这样无法确保消息的有序性。 -
自定义规则
Kafka 支持自定义规则,一个 Producer 可以使用自己的分区指定规则。
2)保存数据
Kafka将数据保存在磁盘,初始会单独开辟一块磁盘空间,顺序写入数据(效率比随机写入高)。
-
Partition 结构
partition有三组segment文件,每个log文件的大小是一样的,但是存储的message数量是不一定相等的(每条的message大小不一致)。文件的命名是以该segment最小offset来命名的,如000.index存储offset为0~368795的消息,kafka就是利用分段+索引的方式来解决查找效率的问题。 - Message结构
上面说到log文件就实际是存储message的地方,我们在producer往kafka写入的也是一条一条的message,那存储在log中的message是什么样子的呢?消息主要包含消息体、消息大小、offset、压缩类型……等等!我们重点需要知道的是下面三个:- offset:offset是一个占8byte的有序id号,它可以唯一确定每条消息在parition内的位置!
- 消息大小:消息大小占用4byte,用于描述消息的大小。
- 消息体:消息体存放的是实际的消息数据(被压缩过),占用的空间根据具体的消息而不一样。
-
存储策略
无论消息是否被消费,kafka都会保存所有的消息。那对于旧数据有什么删除策略呢?- 基于时间,默认配置是168小时(7天)。
- 基于大小,默认配置是1073741824。
需要注意的是,kafka读取特定消息的时间复杂度是O(1),所以这里删除过期的文件并不会提高kafka的性能!
3)消费数据
Kafka采用的是发布订阅模式,消费者主动的去kafka集群拉取消息,消费者在拉取消息的时候也是找leader去拉取。
多个消费者可以组成一个消费者组(consumer group),每个消费者组都有一个组id!同一个消费组者的消费者可以消费同一topic下不同分区的数据,但是不会组内多个消费者消费同一分区的数据!!!我们看下图:
- partition数量=4 大于 consumer数量=3:有一个consumer消费两个partition
- partition数量=2 小于 consumer数量=3:有一个consumer不消费处于替补状态
- partition数量=3 等于 consumer数量=3:每个consumer对应消费一个partition
所以在实际的应用中,建议消费者组的consumer的数量与partition的数量一致!
4)查找消息
在保存数据的小节里面,我们聊到了partition划分为多组segment,每个segment又包含.log、.index、.timeindex文件,存放的每条message包含offset、消息大小、消息体……我们多次提到segment和offset,查找消息的时候是怎么利用segment+offset配合查找的呢?假如现在需要查找一个offset为368801的message是什么样的过程呢?我们先看看下面的图:
- 先找到offset的368801message所在的segment文件(利用二分法查找),这里找到的就是在第二个segment文件。
- 打开找到的segment中的.index文件(也就是368796.index文件,该文件起始偏移量为368796+1,我们要查找的offset为368801的message在该index内的偏移量为368796+5=368801,所以这里要查找的相对offset为5)。由于该文件采用的是稀疏索引的方式存储着相对offset及对应message物理偏移量的关系,所以直接找相对offset为5的索引找不到,这里同样利用二分法查找相对offset小于或者等于指定的相对offset的索引条目中最大的那个相对offset,所以找到的是相对offset为4的这个索引。
- 根据找到的相对offset为4的索引确定message存储的物理偏移位置为256。打开数据文件,从位置为256的那个地方开始顺序扫描直到找到offset为368801的那条Message。
这套机制是建立在offset为有序的基础上,利用segment+有序offset+稀疏索引+二分查找+顺序查找等多种手段来高效的查找数据!
核心API
Kafka 有四个核心API,它们分别是
-
Producer API,
它允许应用程序向一个或多个 topics 上发送消息记录 -
Consumer API,
允许应用程序订阅一个或多个 topics 并处理为其生成的记录流 -
Streams API,
它允许应用程序作为流处理器,从一个或多个主题中消费输入流并为其生成输出流,有效的将输入流转换为输出流。 -
Connector API,
它允许构建和运行将 Kafka 主题连接到现有应用程序或数据系统的可用生产者和消费者。例如,关系数据库的连接器可能会捕获对表的所有更改
架构设计分析
- Partition设计
- 方便扩展
- 提高并发
- 高吞吐,Partition的拓展提供高吞吐量
- Broker设计
- 高可用
- 分布在不同Broker上的Partition副本设计
- 高可靠
Kafka 为何如此之快
Kafka 实现了零拷贝原理来快速移动数据,避免了内核之间的切换。Kafka 可以将数据记录分批发送,从生产者到文件系统(Kafka 主题日志)到消费者,可以端到端的查看这些批次的数据。
批处理能够进行更有效的数据压缩并减少 I/O 延迟,Kafka 采取顺序写入磁盘的方式,避免了随机磁盘寻址的浪费,更多关于磁盘寻址的了解,请参阅 程序员需要了解的硬核知识之磁盘 。
总结一下其实就是四个要点
- 顺序读写
- 零拷贝
- 消息压缩
- 分批发送
应用场景
-
活动跟踪:
Kafka 可以用来跟踪用户行为,比如我们经常回去淘宝购物,你打开淘宝的那一刻,你的登陆信息,登陆次数都会作为消息传输到 Kafka ,当你浏览购物的时候,你的浏览信息,你的搜索指数,你的购物爱好都会作为一个个消息传递给 Kafka ,这样就可以生成报告,可以做智能推荐,购买喜好等。 -
传递消息:
Kafka 另外一个基本用途是传递消息,应用程序向用户发送通知就是通过传递消息来实现的,这些应用组件可以生成消息,而不需要关心消息的格式,也不需要关心消息是如何发送的。 -
度量指标:
Kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据,生产各种操作的集中反馈,比如报警和报告。 -
日志记录:
Kafka 的基本概念来源于提交日志,比如我们可以把数据库的更新发送到 Kafka 上,用来记录数据库的更新时间,通过kafka以统一接口服务的方式开放给各种consumer,例如hadoop、Hbase、Solr等。 -
流式处理:
流式处理是有一个能够提供多种应用程序的领域。 -
限流削峰:
Kafka 多用于互联网领域某一时刻请求特别多的情况下,可以把请求写入Kafka 中,避免直接请求后端程序导致服务崩溃。