#14 消息队列 | 青训营笔记

104 阅读7分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 14 天
本次编程学习的基本环境配置如下
OS: macOS 13.1
IDE: Goland 2022.3
Go Version: 1.18
Python Version: 3.6

主要内容

消息队列, Kafka和RocketMQ

详细介绍

消息队列

消息队列(MQ), 即用于保存“消息”的队列, 提供高吞吐、高并发、高可用特性。

image.png

发展历程

消息中间件其实诞生的很早,早在1983年互联网应用还是一片荒芜的年代,有个在美国的印度小哥Vivek就设想了一种通用软件总线,世界上第一个现代消息队列软件The Information Bus(TIB)。

TIB受到了企业的欢迎,这家公司的业务发展引起了顶尖IT公司IBM的注意,于是他们一开始研发了自己消息队列,后来发展成为wesphere mq

接近2000年的时候,互联网时代已经初见曙光,全球的应用程序得到了极大地丰富,对于程序之间互联互通的需求越来越强烈,但是各大IT公司之间还是牢牢建立着各种技术壁垒,以此来保证自己的商业利益,所以消息中间件在那个时候是大型企业才能够用的起的高级玩意。

时代的洪流不可逆转,有壁垒就有打破壁垒的后来者,2001年sun发布了jms技术,试图在各大厂商的层面上再包装一层统一的java规范。

java程序只需要针对jms api编程就可以了,不需要再关注使用了什么样的消息中间件,但是jms仅仅适用于java。2004年 AMQP(高级消息队列协议)诞生了,才是真正促进了消息队列的繁荣发展,任何人都可以针对AMQP的标准进行编码。

有好的协议指导,再加上互联网分布式应用的迅猛发展成为了消息中间件一飞冲天的最大动力,程序应用的互联互通,发布订阅,最大契合了消息中间件的最初的设计初衷。

除了刚才介绍过的收费中间件,后来开源消息中间件开始层出不穷,常见比较流行的有ActiveMQRabbitMQKafak、阿里的RocketMQ,以及目前存算分离的Pulsar,在目前互联网应用中消息队列中间件基本上成为标配。

image.png

流行的消息队列软件介绍

  1. Kafka: 分布式的、分区的、多副本的日志提交服务, 在高吞吐场景下发挥较为出色
  2. RocketMQ: 低延迟、强一致、高性能、高可靠、万亿级容量和灵活的可扩展性, 在实时场景中运用较广
  3. Pulsar: 下一代云原生分布式消息流平台, 集消息、存储、轻量化函数式计算为一体, 采用存算分离的架构设计
  4. BMQ: 和Pulsar架构类似, 存算分离, 初期定位是承接高吞吐的离线业务场景, 逐步替换掉对应的Kafka集群

消息队列 Kafka

架构示意图

image.png

Kafka使用流程

  1. 创建一个Kafka集群
  2. 在集群中创建一个Topic,并且设置好分片数量
  3. 引入对应语言的SDK,配置好集群和Topic等参数,初始化一个生产者,调用Send方法,将你的Hello World发送出去
  4. 引入对应语言的SDK,配置好集群和Topic等参数,初始化一个消费者,调用Poll方法,你将收到你刚刚发送的Hello World

Kafka基本概念

image.png

  1. Topic: 逻辑队列, 不同的业务员场景可以建立不同的逻辑队列
  2. Cluster: Kafka的物理集群, 每个集群中可以新建不同的Topic
  3. Producer: 生产者, 负责将业务消息发送到Topic中
  4. Consumer: 消息的消费端, 负责消费已经发送到Topic中的消息
  5. Partition: 通常Topic会有多个分片, 不同分片的消息可以并发处理, 以提高单个Topic的吞吐量

image.png

  1. Offset: 消息在partition内的相对位置信息, 可以理解为唯一ID, 在Partition内部严格递增(ID用完之后怎么办?)

image.png

  1. Replica:分片的副本,分布在不同的机器上,可用来容灾。其中Leader对外服务,Follower异步去拉取leader的数据并同步. 如果Leader崩溃,可以将Follower提升成Leader再向外服务
  2. ISR(In-Sync Replicas):意思是同步中的副本,对于Follower来说,始终和Leader是有一定差距的,但当这个差距比较小的时候,我们就可以将这个follower副本加入到ISR中.
    • 不在ISR中的副本是不允许提升成Leader的

image.png

  1. 上图是Kafka中副本的分布图, 每一个Broker代表一个Kafka的节点, 所有的Broker节点最终组成了一个集群. 途中整个集群包含了4个Broker机器节点, 集群有两个Topic(1和2). 每个Topic的每个分片都是3副本状态. 第二个Broken同时也扮演了Controller的角色, 也就是整个集群的大脑, 负责对副本和Broker进行分配。
  2. Zookeeper: 在整个Kafka集群之上, 还有一个Zookeeper,存储了集群的元数据信息,比如副本的分配信息等, Controller会把计算好的方案上传到Zookeeper上

image.png

  1. 为了增强消息的发送能力, 可以采用批量发送和数据压缩的方法, 减少IO次数。Kafka目前支持Snappy, Gzip, LZ4, ZSTD压缩算法

Broker消息文件结构

image.png

数据路径: /Topic/Partition/Segment/(log|index|timeindex|...)

针对磁盘做的优化

由于磁盘的特殊机械结构, 寻道成本很高, 顺序写比随机写更可以减少寻道所带来的时间成本, Kafka采用末尾追加的方式增大吞吐量

Kafka如何找到对应的消息

image.png

Consumer通过发送FetchRequest请求消息数据, Broker会将指定Offset处的消息, 按照时间窗口和消息大小窗口发送到Consumer.

  1. 文件是用文件中第一条消息的offset命名的, 二分法找到小于目标offser的最大文件(log、index、timeindex)
  2. 在文件中找到小于目标offset的最大索引位置
  3. 二分找到小于目标时间戳的最大的索引位置, 然后通过遍历的方式找到最终数据

数据拷贝的优化

image.png

磁盘 -> 内核读缓冲区 -> 应用空间 -> socket缓冲区 -> 网卡(NIC)缓冲区 -> 消费者进程

零拷贝技术

直接从 内核读缓冲区 拷贝到 NIC 缓冲区 再发送到消费者进程。省去了中间一系列的拷贝过程. 通过Sendfile的方式, 将磁盘读到OS内核缓冲区后, 直接转到socket buffer进行网络发送; 通过mmap的方式, 实现顺序的快速写入

消息的接收端

image.png

如何解决Partition在Consumer Group中的分配问题? 手动分配和自动分配

  1. 手动分配: 启动比较快, 但是如果有新的消费者上线或者现有的消费者下线, 就需要停掉集群,重新修改配置再上线。这对于在线服务来说是致命的
  2. 自动分配(High Level): 在Broker集群中,对于不同的Consumer Group来讲,都会选取一台Broker当做Coordinator,而Coordinator的作用就是帮助Consumer Group进行分片的分配,也叫做分片的rebalance,使用这种方式,如果Consumer Group中有Consumer发生宕机,或者有新的Consumer加入,整个partition和Consumer都会重新进行分配来达到一个稳定的消费状态

Kafka的问题

  1. 运维成本高
  2. 对于负载不均衡的场景, 解决方案复杂
  3. 没有字节的缓存, 依赖于Page Cache
  4. Controller和Coordinator和Broker在同一进程中, 大量IO会造成性能下降

RocketMQ简介

名称KafkaRocketMQ
逻辑队列TopicTopic
消息体MessageMessage
标签Tag
分区PartitionConsumerQueue
生产者ProducerProducer
生产者集群Producer Group
消费者ConsumerConsumer
消费者集群Consumer GroupConsumer Group
集群控制器ControllerNameServer

image.png

架构 image.png

存储模型

image.png

高级特性

  1. 最终一致性
  2. 延迟发送

image.png

  1. 处理失败,消费重试好死信队列

image.png

引用

字节内部课: 走进消息队列