这是我参与「第五营」笔记创作活动的的第16天。
走进消息队列(上)
消息队列 (MQ),指保存消息的一个容器,本质是个队列。但这个队列呢,需要支持高吞吐,高并发,并且高可用。
消息队列的前世
发展历程
业界消息队列对比
Kafka: 分布式的、分区的、多副本的日志提交服务,在高吞吐场景下发挥较为出色。
RocketMQ: 低延迟、强一致、高性能、高可靠、万亿级容量和灵活的可扩展性,在一些实时场景中运用较广。
Pulsar: 是下一代云原生分布式消息流平台,集消息、存储、轻量化函数式计算为一体.采用存算分离的架构设计。
BMQ: 和Pulsar架构类似,存算分离,初期定位是承接高吞吐的离线业务场景,逐步替换掉对应的Kafka集群。
Kafka
使用场景:
- 业务日志信息
- 搜索服务、直播服务、订单服务和支付服务的Metrics数据
- 用户行为数据——搜索、点赞、评论、收藏、
如何使用
- 首先需要创建一个Kafka集群。
- 需要在这个集群中创建一个Topic,并且设置好分片数量。
- 编写生产者逻辑,初始化一个生产者,调用Send方法,将你的Hello World发送出去。
- 编写消费者逻辑,初始化一个消费者,调用Poll方法,你将收到你刚刚发送的Hello World。
基本概念
Topic: 逻辑队列,不同 Topic 可以建立不同的 Topic
Cluster: 物理集群,每个集群中可以建立多个不同的 Topic
Producer: 生产者,负责将业务消息发送到 Topic 中
Consumer: 消费者,负责消费 Topic 中的消息
ConsumerGroup: 消费者组,不同组 Consumer 消费进度互不干涉
Offset : 对于每一个Partition来说,每一条消息都有一个唯一的Offset,消息在 partition 内的相对位置信息,可以理解为唯一ID,在 partition 内部严格递增。
Replca: 分片的副本,分布在不同的机器上,可用来容灾,Leader对外服务,Follower异步去拉取Leader的数据进行同步,如果Leader掉了,可以将Follower提升Leader再外进行服务。
ISR: 意思是同步中的副本,对于Follower来说,始终和Leader是有一定差距的,但当这差距比较小的时候,我们就可以将这Follower副本加入ISR中,ISR中的副本是不允许提升成Leader的。
数据复制
架构
Producer
批量发送:批量发送可以减少IO次数,从而加强发送能力。
数据压缩:如果消息量很大,网络带宽不够用,通过压缩,减少消息大小,目前支持Snappy、 Gzip、LZ4、ZSTD压缩算法。
Broker
消息文件结构:在每一个Broker中,都分布着不同Topic的不同分片。
磁盘结构:移动磁头找到对应磁道,磁盘转动,找到对应扇区,最后写入。寻道成本比较高,因此顺序写减少寻道所带来的时间成本。
如何找到消息:
偏移量索引文件:通过二分找到小于目标offset最大索引位置,再遍历找到目标offset。
时间戳索引文件:和offset相比只是多加了以及索引,也就是通过二分找到时间戳对应的offset,再重复之前的步骤找到相应的文件数据。
传统数据拷贝
零拷贝:Consumer从Broker中读取数据,通过sendfile的方式,将磁盘读到os内核缓冲区后,直接转到socket buffer进行网络发送;Producer生产的数据持久化到broker,采用mmap文件映射,实现顺序的快速写入。
Consumer
如何解决 Partition 在 Consumer Group 中的分配问题?
对于一个Consumer Group来说,多个分片可以并发的消费,这样可以大大提高消费的效率,但需要解决的问题是, Consumer和Partition的分配问题,该由哪一个Consumer来消费的问题。对于这个问题,我们一般有两种解决方法,手动分配和自动分配。
Low Level——手动分配
- 优点:启动比较快,因为对于每一个Consumer来说,启动的时候就已经知道了自己应该去消费哪个消费式,就好比图中的Consumer Group1来说,Consumer1去消费Partition1,2,3;Consumer2去消费Partition4,5,6;Consumer3去消费Partition7,8。这些Consumer在启动的时候就已经知道分配方案了。
- 缺点:如果我们的Consumer3挂掉了,我们的7,8分片是不是就停止消费了。又或者,如里我们新增了一台Consumer4,那是不是又需要停掉整个集群,重新修改配置再上一线,保Consumer4也可以消费数据,其实上面两个问题,有时对于线上业务来说是致命的。
High Level——自动分配
- Kafka也提供了自动分配的方式,这里也叫做 High Level的消费方式,简单来说,就是在我们的Broker集群中,对于不同的Consumer Group来讲,都会选取一台Broker当做Coordinator,而Coordinator的作用就是帮助Consumer Group进行分片的分配,也叫做分片的Rebalance,使用这种方式,如果Consumer Group中有发生宕机,或者有新的Consumer加入,整个Partition和Consumer都会重新进行分配来达到一个稳定的消费状态。
问题总结
- 运维成本高。因为有数据复制的问题,所以Kafka运维的时间成本和人力成本都不低。
- 对于负载不均衡的场景,我们需要有一个较为复杂的解决方案进行数据迁移,从而来权衡IO升高的问题。
- Kafka没有自己的缓存,在进行数据读取的时候,只有Page Cache可以用,所以不是很灵活。
- Kafka的Controller、Coordinator和Broker在同一进程中,Broker因为承载大量IO的原因,会导致Controller和Coordinator性能下降,如果到了一定程度,可能会影响整个集群的可用性。