这是我参与「第五届青训营 」伴学笔记创作活动的第25天。主要讲了消息队列的特性和应用,并依次介绍了Kafka、BMQ、RocketMQ三个消息队列架构。
使用场景
一般用在离线消息处理中
- 日志信息处理
- Metrics数据(程序运行时状态的采集信息比如QPS、查询写入的耗时等)
- 搜索、点赞、评论、收藏等用户行为
使用Kafka
- 创建集群
- 新增Topic
- 编写生产者逻辑
- 编写消费者逻辑
基本概念
- Topic: 逻辑队列,不同Topic可以建立不同的Topic
- Cluster:物理集群,每个集群中可以建立多个不同的Topic
- Producer:生产者,负责将业务消息发送到Topic中
- Consumer:消费者,负责消费Topic中的消息
- ConsumerGroup:消费者组,不同组的Consumer消费进度互不干涉
- Partition:Topic的分区,不同分区的消息可以并发处理
关系如下图:
-
Offser:消息在Partition内的相对位置信息,可以理解为唯一ID(自增)
-
Peplica:Partition的副本,有两个状态Leader和Follower,Leader用于对外进行写入和读取,Follower不断拉取Leader的数据,与之保持同步(一个机制,Follower与Leader的差距差太多会被踢掉)
-
Controller:如下图所示,Broker表示集群中的节点,集群中有一个关键的节点Controller,负责对集群中副本的分配
Kafka架构
如下图所示,一个集群中有一个Zookeeper组件,负责存储集群元信息,包括分区分配信息等。
消息从生产到消费的流程
- Producer发送消息:为了支持高吞吐的特性,Kafka支持Producer向Broker批量发送消息以减少IO次数,加强发送能力,为了防止消息量很大以至于达到网络带宽的瓶颈的情况,Kafka会对消息数据进行压缩(Snappy、Gzip、LZ4、ZSTD等压缩算法)。
- Broker存储数据:Broker消息文件结构如下图所示
一个Log日志会切分成多个有序的LogSegment,每个Segment包括图上的一些文件,写入文件时采用顺序写的方式以减少磁盘寻道时间提高效率。
-
Broker寻找消息:Consumer通过发送FetchRequest请求消息数据,Broker会将指定Offset处的消息按照时间窗口和消息大小窗口发送给Consumer,Broker通过二分找到小于目标offset的最大文件,然后在.index文件里通过二分找到小于目标offset的最大索引位置拿到batch后顺序遍历,时间戳索引查找同理在.timeindex文件里做相同的事。
-
Broker发送数据:Consumer从Broker中读取数据,通过sendfile的方式,将磁盘读到os内核缓冲区后,直接把数据发送到网卡而不需要经过应用空间,流程如下图所示:
-
Consumer接收消息:Kafka有对Consumer自动分配Partition的机制,如下图每个Group有一个Coordinator来管理Partiton在该Group的分配。
综上,Kafka一共提供了以下机制来提高吞吐率和稳定性
- Producer:批量发送、数据压缩
- Broker:顺序写、消息索引、零拷贝
- Consumer:Rebalance
Kafka的问题
数据复制问题
当集群中的Leader重启后,集群进行Leader切换,旧Leader启动后会从新Leader中追赶数据,为了防止集群中的机器依次重启导致Leader频繁切换的情况发生,当数据同步完成后会进行Leader的回切。当集群成员变更(替换、扩容、缩容) 时也存在数据复制的问题。
负载不均衡
当一个Broker数据过多,要把一个Partition迁走时,迁走Partiton时会带来数据赋值问题,从而增加该Broker的IO负载。
问题总结
- 运维成本高
- 对于负载不均衡的场景,解决方案复杂
- 没有自己的缓存,完全依赖Page Cache
- Controller和Coordinator和Broker在同一个进程中,大量IO会造成性能下降