消息队列
业界消息队列的对比
Kafka :分布式的、分区的、多副本的日志提交服务,在 高吞吐场景 下发挥出色
RocketMQ: 低延迟,强一致,高性能,高可靠,万亿级容量和灵活的扩展性,在 实时场景 运用广泛
Pulsar: 下一代云原生分布式消息流平台,集消息、存储、轻量化函数式计算为一体,采用 存算分离 的架构设计
BMQ: 和 pulsar 相似,存算分离。初期定位是承接高吞吐的离线业务场景,逐步替换掉 Kafka 集群。
消息队列 - Kafka
Kafka 使用场景:
- 一般用于离线消息处理中,比如日志信息,
- Metrics数据:在程序运行中,对程序的状态进行采集,程序qps,运行耗时等等
- 搜索服务,直播服务,订单服务,支付服务,满足一些用户行为(搜索,点赞,评论,收藏)
如何使用 Kafka:
graph LR
A[创建集群] --> B[新增topic] --> C[编写生产者逻辑] --> D[编写消费者逻辑]
Kafka 基本概念
Topic : 逻辑队列,不同 topic 可以建立不同的 Partition,所有数据都存储到 topic 中
Cluster : 物理集群,每个集群中可以建立多个不同的 Topic
Producer : 生产者,负责将业务消息发送到 Topic 中
Consumer : 消费者,负责消费 Topic 中的消息
ConsumerGroup : 消费组,不同消费组的 Consumer 进度不一样。
partition : topic 的分区,一个 topic 有多个分区,不同分区的消息可以并发处理,提高单个 topic 的吞吐能力。
Topic
一个 Topic 里可以建立不同的 Partiton, partition 内存储不同的消息。
Offset:消息在 partition 内的相对位置信息,可以理解为唯一ID,在 partition 内部严格递增。
Partition
每个分区有多个 Replica , 多个 Replica 分布在集群不同的机器上,以此达到容灾的作用,且副本还有不同的角色 (Leader, Fllower),其中Leader Replica 将会从 ISR (红色框内的In-Sync Replicas)中选出
消息队列的读写都是从 Leader 开始,然后 Follower 会从 Leader 中拉取消息出来,努力和 Leader 保持一致。 当有的 Follower 和 Leader 之间消息差距过大时,就会被踢出 ISR 。比如上图中的 Replica3。
在过往的 kafka 中,差距是根据 Offset 的差距判断了,现在的版本中是根据时间差距来判断的。
如果 Leader 发生宕机,那么可以立刻从 ISR 中选举出新的 Leader 继续服务。这保证了高可用。
Kafka 架构
- Zookeeper:复杂存储集群元信息
Kafa 为什么支持高吞吐?
graph LR
A[Producer] --生产--> B[Broker] --消费--> C[Consumer]
Producer - 批量发送 + 数据压缩
将多个 Producer 发送的消息,用一个 Batch 存储起来,然后定期发送给节点(Broker),这就是批量发送。批量发送可以减少 I\O 次数,从而加强发送能力。如果遇到消息太大,就会出现网络带宽不够用的情况,这个时候,Kafka通过压缩,减少消息大小。目前支持 Snappy/Gzip/LZ4/ZSTD 压缩算法。默认选择 Snappy 或者 ZSTD 。
Broker - 数据的存储
Broker 里存储了 partition 的副本,而副本以日志形式写入到磁盘上。将日志切分成有序的日志段。每个日志段有四个文件,包含真正的日志文件和偏移量索引文件(offset和真实数据位置的映射)、时间戳索引文件、其他文件。
Broker - 磁盘结构
在机械盘中,写入数据的时候,最耗时的过程是磁头的寻道时间,而Kafka 中采用顺序写的方式写入,提高写入效率,对每条消息都是 末位添加 中途不改变磁头。
如何找到消息?
Consumer 通过发送 FetchRequest 请求消息数据,Broker 会将制定 Offset 处的消息,按照时间窗口和消息大小窗口发送给 Consumer。
因为日志文件的命名是以日志段切分后的第一条数据的Offset命名的。所以对于一条消息,如果我们知道了他的Offset,就可以通过二分来找到他所在的具体位置。
时间戳索引文件相当于在 offset 上加了一层二级索引,首先根据时间戳寻找到文件的 offset,然后再根据 offset 寻找文件。
Broker - 零拷贝
传统数据拷贝中分为以下几步:
- 从磁盘中将数据读到内核空间中,然后将数据拷贝到应用空间的
ApplicationBuffer - 由
ApplicationBuffer将数据传送到内核空间的Socket - 再从
Socket发送到网卡。
而 kafka 采用了send File技术, 可以从磁盘读到内核空间后,内核空间直接将信息发送到网卡,不经过应用空间,减少了三次拷贝过程,增加了性能。在写的时候,Kafka利用操作系统的 Page 将磁盘文件映射到内存空间,则用户对内存的修改,会在适当的时间被操作系统同步到硬盘上。这个过程叫做 Memory Mapped File 。使用这种方式可以获取很大的I/O提升,省去了用户空间到内核空间复制的开销。Kafka 提供了同步写入和异步写入两种方式。
Consumer
通过手动进行分配,对每一个 Consumer 消费哪一个 Parition 完全由业务决定,但是缺点很明显,当其中一个服务挂掉后,会导致数据中断,也就是不能处理容灾。并且拓展性不好。
kafa 提供了自动分配,他会在不同的消费者集群中选取一台 Broker 作为 Coordinator(协调者) ,其作用就是帮助消费者集群中的消费者自动选取 partition ,这个过程也叫做 rebalanced。这样处理方式下,一旦由服务宕机,则 Coordinator 会自动剔除宕机服务,并且重新分配 Consumer。当有新的服务加入集群的时候,也会对服务进行重新分配。达到一个均衡的结果。
重启操作
kafka的重启节点的操作分为以下步骤:
- 关闭并重启一个节点,如果该节点是
Leader节点。那么集群此时没有Leader - 于是重新选举一个
Leader,需要注意的是,此时外部的写入操作是一直没有停止的,则当原来的节点重启后,会处于一个数据落后的装态, - 重启的节点需要从
Leader那里同步数据,直到Offset判断为数据同步。 - 然后再回切
Leader将原来的节点重新设置为Leader
同样的,kafka 的运维中,替换、扩容、缩容都是需要经过以上流程,区别在于同步的数据的多少。这个过程中,消耗的时间成本会比较高。
负载不均衡
如果某一个 Partition 数据比较多,因为一个 Partition 的数据都是写入到一个 boker 中,会导致一个 broker 的数据特别多,为了减少负载,需要将集群中的 Partiton 迁移,而数据迁移会导致 IO 升高,所以需要在两台集群中做平衡。
Kafka问题总结:
- 运维成本高
- 对于负载不均衡的场景,需要设计复杂的策略来在多台机器中平衡
- 没有自己的缓存,完全依赖于 Page Cache, 降低了可拓展性
- Controller 和 Coordinator 和 Broker 在同一个进程中,大量的 io e会造成其性能下降。