消息队列原理与实践 | 青训营笔记

96 阅读7分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天。

前言

相信大家或多或少都听过消息队列这个组件,对于消息队列来说,在互联网的各种业务中发挥着至关重要的作用,但是在大学的课程中却少有提及,因此希望通过此次分享帮助大家去了解工业界比较成熟消息队列的底层 原理、架构设计、以及它们的一些高级特性,在以后的学习中或者工作中更加熟练地去使用消息队列或者进行技术选型,本次分享将会由浅入深的去介绍消息队列的相关内容。

消息队列

什么是消息队列?

消息队列(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的标准进行编码。有好的协议指导,再加上互联网分布式应用的迅猛发展成为了消息中间件一飞冲天的最大动力,程序应用的互联互通,发布订阅,最大契合了消息中间件的最初的设计初衷。除了刚才介绍过的收费中间件,后来开源消息中间件开始层出不穷,常见比较流行的有ActiveMQ、RabbitMQ、Katak、阿里的RocketMQ,以及目前存算分离的Puisar,在目前互联网应用中消息队列中间件基本上成为标配。

image.png

消息队列-Kafka

如何使用Kafka

第一步:首先需要创建一个Kafka集群,但如果你是在字节工作,恭喜你这一步消息团队的小伙伴已经帮你完成了

第二步:需要在这个集群中创建一个Topic, 并且设置好分片数量

第三步:引入对应语言的SDK,配置好集群和Topic等参数, 初始化一个生产者,调用Send方法,将你的Hello World发送出去

第四步:引入对应语言的SDK,配置好集群和Topic等参数, 初始化一个消费者,调用Poll方法, 你将收到你刚刚发送的Hello World

graph TD
创建集群-->新增Topic-->编写生产者逻辑-->编写消费者逻辑

基本概念

  • Topic: Kakfa中的逻辑队列, 可以理解成每一个不同的业务场景 就是一个不同的topic, 对于这个业务来说,所有的数据都存储在这个topic中

  • Cluster: Kafka的物理集群,每个集群中可以新建多个不同的topic

  • Producer: 顾名思义,也就是消息的生产端,负责将业务消息发送到Topic当中

  • Consumer: 消息的消费端,负责消费已经发送到topic中的消息

  • Partition: 通常topic会有多个分片,不同分片直接消息是可以并发来处理的,这样提高单个Topic的吞吐

数据复制

下面这幅图代表着Kafka中副本的分布图。途中Broker代表每一 个Kafka的节点, 所有的Broker节点最终组成了一个集群。整个图表示,图中整个集群,包含了4个Broker机器节点,集群有两个Topic, 分别是Topic1和Topic2,Topic1有两个分片,Topic2有1个分片,每个分片都是三副本的状态。这里中间有一个Broker同时也扮演了Controller的角色,Controller是整 个集群的大脑,负责对副本和Broker进行分配。

image.png

Kafka-问题总结

  1. 运维成本高

  2. 对于负载不均衡的场景,解决方案复杂

  3. 没有自己的缓存,完全依赖Page Cache

  4. Controller 和Coordinator和Broker在同一进程中,大量IO会造成其性能下降

消息队列-BMQ

BMQ简介

BMQ兼容Kafka协议,存算分离,云原生消息队列,初期定位是承接高吞吐的离线业务场景,逐步替换掉对应的Kafka集群,我们来了解一下BMQ的架构特点。

BMQ架构图 image.png

Producer -> Consumer -> Proxy -> Broker -> HDFS -> Controller -> Coordinator -> Meta

着重强调一下Proxy和Broker无状态,为下面运维比较做铺垫0这里简单介绍一下存算分离,适配Kafka协议, 为什么不选择Pulsar的原因。

BMQ文件结构

image.png

对于Kafka分片数据的写入,是通过先在Leader 上面写好文件,然后同步到ollower上,所以对于同一个副本的所有Segment都在同一台机器上面。就会存在之前我们所说到的单分过大导致负载不均衡的问题,但在BMQ集群中,因为对于单个副本来讲,是随机分配到不同的节点上面的,因此不会存在Kafka的负载不均问题。

Broker--写文件流程

数据校验: CRC ,参数是否合法

校验完成后,会把数据放入Buffer中

通过一个异步的Write Thread线程将数据最终写入到底层的存储系统当中。

这里有一个地方需要注意一下,就是对于业务的写入来说,可以配置返回方式,可以在写完缓存之后直接返回,另外我也可以数据真正写入存储系统后再返回,对于这两个来说前者损失了数据的可靠性,带来了吞吐性能的优势,因为只写入内存是比较快的,但如果在下一次flush前发生宕机了,这个时候数据就有可能丢失了,后者的话,因为数据已经写入了存储系统,这个时候也不需要担心数据丢失,相应的来说吞吐就会小一些。

我们再来看看Thread的具体逻辑,首先会将Buffer中的数据取出来, 调用底层写入逻辑,在一 定的时间周期上去fush, flush完成后开始建 立Index,也就是ffset和timestamp对于消 息具体位置的映射关系。

Index建立好以后,会save- 次checkpoint,也就表示,checkpoint后的数据是可以被消费的辣,我们想一下 ,如果没有checkpoint的情况下会发生什么问题,如果flush完成之后宕机,index还没有建立,这个数据是不应该被消费的。

最后当文件到达一定大小之后,需要建立一个新的segment文件来写入。

image.png

引用参考

字节跳动内部课程:juejin.cn/course/byte…