为什么我们需要Kafka?
订单发生时通知其他服务
在我们深入研究之前,有必要重新审视一下 Kafka 最初被发明的原因。
想象一下为一家电子商务公司维护一组微服务。
下订单时,订单服务要通知很多服务,如下:
- 钱包服务从用户账户中扣除金额
- 仓库服务扣除物品的库存数量
- 物流服务发货
当订单服务要通知更多的服务时,复杂度就要进一步提高。
订单服务器需要做以下事情:
- 跟踪都通知了谁
- 确保所有其他服务确实收到并处理
- 和其他服务建立直接的连接和处理不同的响应
发现没有,这很难扩展,如果接入更多的系统,订单系统的开发天天996了。
因此,Kafka就很好的解决这样的问题。
消息队列与发布订阅
订单服务器只是将消息发布到 Pub-Sub/Message Queue
消息队列和发布订阅系统都是解决上述问题的关键。
也就是说,不是让订单服务维护直接和各种系统打交道,而是将事件发布或者推送到中间队列中,对队列感兴趣的服务器(通常称为消费者)订阅队列并相应地消费事件。
那么消息队列和发布订阅系统有什么区别呢?
消息队列
消息队列是一种类似队列的结构,其中消息被发布并且仅被消费一次。这对于非幂等的进程很方便,事件应该只由一个消费者处理,RabbitMQ 最初被设计成一个消息队列。
发布-订阅系统
另一方面,发布订阅系统允许多个消费者多次使用一条消息。订单事件被多个系统订阅消费,所以更适合发布订阅系统模式。Kafka 被设计为既是消息队列又是发布订阅系统。
Kafka组件
为了充分理解 Kafka 的工作原理,让我们剖析 下Kafka 的各个组件。
Kafka Broker 和集群
Kafka 代理和集群
Kafka 只不过是一个管理数据发布和消费的服务。
一个Kafka Broker就是一个Kafka服务。维护同一组主题的一组Broker称为 Kafka 集群。
发布者Publisher
发布者发布到 Kafka 代理
将数据发布到 Kafka Broker的服务称为发布者。我们之前提到的Order服务是发布者的一个例子。
消费者Consumer
消费者从 Kafka 代理消费
另一方面,消费者是订阅和消费来自 Kafka 主题的数据的服务。
在我们前面的示例中,Wallet服务器、Warehouse服务器和Logistic服务器充当Order主题的消费者。
主题Topic
Kafka 代理中的不同主题
Kafka 代理维护不同类型的事件,例如:
- 订单创建事件
- 订单取消事件
- 缺货事件
这些事件中的每一个都是大量的数据流。主题只是一种事件或数据流。
发布到 Kafka 时,发布者指定消息应发布到的主题。
主题是一个只能追加的日志。将消息附加到主题类似于将数据附加到队列,它需要 O(1) 常数时间,因此速度非常快。
分区Partition
主题被分片成分区
主题是存储在 Kafka Broker上的追加的日志。
随着消息数量的增加,Broker在特定主题上存储的数据量是有限的,那怎么办呢?
可以将一个主题拆分为多个分区,而不是将所有数据一直追加到同一个主体日志中,而是每个分区存储特定主题的一部分数据,这类似于数据库分片。
主题基于分区进行分片。同一主题的分区可以存储在相同或不同的 Kafka Broker上。这使得 Kafka 具有高度可扩展性。
发布者在发布之前指定消息的主题和分区。因此,发布者有责任确保分区逻辑不会导致热分区。
偏移量offset
分区中的偏移量
偏移量是分区中消息的唯一索引。
当 Kafka 将数据推送给消费者时,它会增加并跟踪当前的偏移量。
有两种类型的偏移量值得强调:
- 当前偏移量:保存在Consumer客户端中,它表示Consumer希望收到的下一条消息的序号。
- 提交的偏移量: 保存在Broker上,它表示Consumer已经确认消费过的消息的序号。
消费者组
如前所述,Kafka 既是消息队列又是发布订阅系统。这是通过消费者群体优雅地设计的。
Consumer可以消费多个partition,但是每个partition只能被同组的一个consumer消费
消费者组由一组消费相同主题的消费者组成。
一个消费者一次可以消费多个分区。但是,每个分区只能由同一组中的一个且只有一个消费者使用。
一个分区可以被来自不同消费者组的多个消费者消费
消费者组是相互独立的,不同的组可以同时使用同一主题并使用不同的偏移量。
通过将所有消费者放在同一组中来实现队列,同一分区中的消息不会被来自相似组的不同消费者并发消费。
在分区级别实现队列。因此,如果想要保证顺序处理数据流,发布者必须确保数据始终被推送到同一个分区。
另一方面,发布订阅系统是通过多个消费者组实现的。消费者群体彼此之间一无所知,并使用单独的偏移量消费数据。
在前面的例子中,Wallet服务器和Logistic服务器分别属于不同的消费者组,分别消费数据。
重新平衡和分区分配
当新消费者加入时,Kafka 会重新平衡
如果一组中只有一个消费者,则该消费者将负责消费所有可用分区。
当一个新的consumer加入group时,比如增加了一个新的server实例,Kafka会进行rebalancing,将一部分partitions分配给新的consumer。
这确保了每个消费者共享相同数量的工作,从而使 Kafka 具有可扩展性。
Kafka 使用自己的重新平衡策略进行分区重新分配,这值得另一篇单独的文章来介绍。
复制Replica
副本在分区级别创建,可以存储在相同/不同的代理中
单点故障是每个分布式系统的噩梦,Kafka也不例外。
如果Broker出现故障,存储在代理上的分区可能不可用。因此,副本是在分区级别创建的。
为每个分区创建副本,并存储在不同的 Kafka 代理上。为每个分区选举一个领导者来为发布者和消费者服务。
副本不断从leader同步数据。当 leader 宕机时,Zookeeper 会加入进来帮助进行 leader 的选举。
Zookeeper
正如您可能正在思考的那样,我们的难题中缺少一些部分。
- 我们如何知道每个分区的领导者?
- 如何知道每个主题的分区数?
- 我们如何知道每个消费者组的最新偏移量?
- 我们如何知道每个消费者组中有多少消费者?
这就是Zookeeper发挥作用的地方。它是一个分布式协调服务系统,用于存储元数据并协调 Kafka 中的分布式系统。
主要涉及以下方面:
- 领导者选举——确保每个分区都有一个领导者
- 集群成员资格——跟踪集群中的所有功能代理
- 主题配置——跟踪所有可用主题、分区及其副本
- 访问控制列表——跟踪每个组中消费者的数量及其访问权限
- 配额——跟踪每个客户端可以读取和写入的数据量
长轮询
Kafka 如何向消费者推送消息?
RabbitMQ 采用推送模型。代理与消费者保持持久的 TCP 连接,并在有可用数据时将数据推送给他们。
然而,推送模型可能会淹没消费者。如果代理推送数据的速度快于消费者处理数据的速度,消费者可能会落后。RabbitMQ 确实有一个解决方案,这边就不展开讨论了。
长轮询等待方式方法
Kafka 使用拉模型,也就是长轮询。消费者定期从代理拉取数据。因此,消费者只有在准备好时才能拉取数据。但是,如果分区上没有数据,来自消费者的定期轮询可能会导致资源浪费。
Kafka通过使用“long polling”等待模式的方式解决了这个问题。简而言之,如果分区上没有数据,Kafka 不会返回空响应。相反,broker保持连接并等待数据进入,然后再将其返回给消费者。
这减轻了当分区上没有数据时消费者频繁轮询并防止资源浪费。
总结
本文总结了Kafka这个组件的基础知识,希望让大家对Kafka有一个宏观的认识,感兴趣的再深入分析底层的实现机制。