消息队列 | 青训营笔记

108 阅读9分钟

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

前言

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

前世今生

定义

消息队列(MQ),指保存消息的一个容器,本质是个队列。但这个队列需要支持高吞吐,高并发,并且高可用

消息队列发展历程

image.png

消息中间件其实诞生的很早, 早在1983年互联网应用还是一片荒芜的年代, 有个在美国的印度小哥Vivek就设想了一种通用软件总线,世界上第一个现代消息队列软件The Information Bus(TIB),他受到了企业的欢迎, 这家公司的业务发展引起了当时最牛气的IT公司IBM的注意, 于是他们开始研发了自己消息队列软件, 于是才有了后来的wesphere mq, 再后来微软也加入了战团。

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

但是时代的洪流不可逆转,有壁垒就有打破壁垒的后来者,2001年sun发布了jms技术,试图在各大厂商的层面上再包装一层统一的 java规范。java程序只需要针对jms api编程就可以了,不需要再关注使用了什么样的消息中间件,但是jms仅仅适用于java。

2004年AMQP(高级消息队列协议)诞生了,才是真正促进了消息队列的繁荣发展,任何人都可以针对AMQP的标准进行编码。有好的协议指导,再加上互联网分布式应用的迅猛发展成为了消息中间件一飞冲天的最大动力,程序应用的互联互通,发布订阅,最大契合了消息中间件的最初的设计初衷。

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

业界消息队列对比

  • Kafka:分布式的、分区的、 多副本的日志提交服务, 在高吞吐场景下发挥较为出色

  • RocketMQ:低延迟、强一致、高性能、高可靠、 万亿级容量和灵活的可扩展性,在一些 实时场景中运用较广

  • Pulsar:是下一代云原生分布式消息流平台, 集消息、存储、轻量化函数式计算为一体、 采用存算分离的架构设计

  • BMQ:和Pulsar架构类似,存算分离, 初期定位是承接高吞吐的离线业务场景, 逐步替换掉对应的Kafka集群

消息队列—Kafka

使用场景

image.png

Kafka的使用

image.png

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

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

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

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

基本概念

image.png

  • Topic:逻辑队列, 不同 Topic 可以建立不同的 Topic

  • Cluster: 物理集群,每个集群中可以建立多个不同的 Topic

  • Producer:生产者,负责将业务消息发送到 Topic 中

  • Consumer: 消费者,负责消费 Topic 中的消息

  • ConsumerGroup:消费者组,不同组 Consumer 消费进度互不干涉

  • Offset:消息在 partition 内的相对位置信息,可以理解为唯一ID,在 partition 内部严格递增。

  • Replica:分片的副本, 分布在不同的机器上,可用来容灾, Leader对外服务,FOllower异步去拉取leader的数据进行一个同步,如果 leader挂掉了, 可以将Follower提升成leader再堆外进行服务

  • ISR:意思是同步中的副本,对于Follower来说, 始终和leader是有一定差距的, 但当这个差距比较小的时候,我们就可以将这个follower副本加入到ISR中, 不在ISR中的副本是不允许提升成Leader的

  • Offect:消息在partition内的相对信息位置,可以理解为唯一ID,在partition内严格递增。

image.png

  • Replica:每个分片有多个Replica,Leader Replica 将会从 ISR 中选出

image.png

Replica:分片的副本,分布在不同的机器上,可用来容灾,Leader对外服务,Follower异步去拉取leader的数据进行一个同步,如果leader挂掉了,可以将Follower提升成leader再对外进行服务
ISR:意思是同步中的副本,对于Follower来说,始终和leader是有一定差距的,但当这个差距比较小的时候,我们就可以将这个follower副本加入到ISR中,不在ISR中的副本是不允许提升成Leader的

数据复制

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

image.png

Kafka架构

image.png

Consumer-消息的接收端

对于一个ConsumerGroup来说,多个分片可以并发的消费,这样可以大大提高消费的效率,但需要解决的问题是,Consumer和Partition的分配问题,也就是对于每一个Partition来讲,该由哪一个Consumer来消费的问题。对于这个问题,我们一般有两种解决方法,手动分配和自动分配

第一,手动分配,也就是Kafka中所说的LowLevel消费方式进行消费,这种分配方式的一个好处就是启动比较快,因为对于每一个Consumer来说,启动的时候就已经知道了自己应该去消费哪个消费方式,就好比图中的ConsumerGroup1来说,Consumer1去消费Partition123 Consumer2,去消费456 Consumer3去消费78。这些Consumer再启动的时候就已经知道分配方案了,但这样这种方式的缺点又是什么呢,想象一下,如果我们的Consumer3挂掉了,我们的7.8分片是不是就停止消费了。又或者,如果我们新增了一台Consumer4,那是不是又需要停掉整个集群,重新修改配置再上线,保证Consumer4也可以消费数据,其实上面两个问题,有时候对于线上业务来说是致命的

image.png

第二,自动分配。Kafka也提供了自动分配的方式,这里也叫做HighLevel的消费方式,简单的来说,就是在我们的Broker集群中,对于不同的ConsumerGroup来讲,都会选取一台Broker当做Coordinator,而Coordinator的作用就是帮助ConsumerGroup进行分片的分配,也叫做分片的rebalance,使用这种方式,如果ConsumerGroup中有发生岩机,或者有新的Consumer加入,整个partition和Consumer都会重新进行分配来达到一个稳定的消费状态

image.png

重启操作

image.png

举个例子来说,如果我们对一个机器进行重启口首先,我们会关闭一个Broker,此时如果该Broker上存在副本的Leader,那么该副本将发生leader切换,切换到其他节点上面并且在ISR中的Follower副本,可以看到图中是切换到了第二个Broker上面。

而此时,因为数据在不断的写入,对于刚刚关闭重启的Broker来说,和新Leader之间一定会存在数据的后,此时这个Broker会追赶数据,重新加入到ISR当中。

当数据追赶完成之后,我们需要回切leader,这一步叫做preferleader,这一步的目的是为了避免,在一个集群长期运行后,所有的leader都分布在少数节点上,导致数据的不均衡。

通过上面的一个流程分析,我们可以发现对于一个Broker的重启来说,需要进行数据复制,所以时间成本会比较大,比如一个节点重启需要10分钟,一个集群有1000个节点,如果该集群需要重启升级,则需要10000分钟,那差不多就是一个星期,这样的时间成本是非常大的。

那么可以不可以并发多台重启呀?不可以。为什么呢,在一个两副本的集群中,重启了两台机器,对某一分片来讲,可能两个分片都在这台机器上面,则会导致该集群处于不可用的状态。这是更不能接受的。

Kafka问题总结

  1. 运维成本高

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

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

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