消息队列是什么?

化繁为简一下,分拆一下这个词汇,首先,先来看队列(queue)

队列是一种常用的数据结构,特点是先进先出

消息队列,顾名思义一下,就是把消息放在队列当中进行处理

这个消息,肯定是等待处理的消息,我们应该要获取消息做什么事

比如获取一个返回结果,接受一个参数等等。

所以这里引出了新的概念,消费者生产者

生产者就是往队列里面塞数据

消费者就是往队列里面取数据

这就是消息队列的基础概念。

消息队列解决了什么问题

按理说,消息队列只是一个队列类数据结构,这个我们完全可以自己实现

例如Python的队列queue,或者java的Deque都可以实现队列的功能

再去实现一个消费者/生产者逻辑就好了,那为什么要用消息队列呢?

这边举几个我开发中遇见的例子

异步

拿初始化服务来说,初始化服务的步骤如下

1
初始化开始 --> 服务A初始化 --> 服务B初始化 --> 服务C初始化 --> 返回成功

来看个图,大概类似这样。

初始化的流程类似是一个链表,一走到底,这样你让人家客户等很久,客户心理也不开心啊,不能这么搞,所以得想个好办法处理一下。

如果有了消息队列,我们就可以把这个处理方法优化一下

如图所示

现在我们利用了消息队列作为中间件,当传入ip给消息队列之后,可以直接返回一个初始化进行中的状态,立马返回,减少等待。

各个服务会自己去消息队列中获取ip进行操作,这样就避免了一步步调用造成的时间浪费,这里就将整个流程异步化。

这是消息队列的第一个好处。

解耦/复用

解耦,这个翻译一下,说人话就是,解除相互的依赖,让大家能独立运行。

还是老样子,举个例子。

现在我们有一个处理订单的程序

主要的功能有获取订单,订单入库,订单校验,费用计算,记录日志等

这个画个图,流程如下

现在可以看出,订单入库,订单校验,费用计算,日志记录这几个功能都依赖订单处理这个模块

写一个伪代码来看的话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Order_process 订单处理 
func Order_process() {
//获取订单的函数
Order, _ := GetOrder()
// 获取到订单去做几个功能
// 订单入库
_ := OrderStorage(Order)
// 订单校验
_ := OrderVer(Order)
// 订单金额
_ := OrderAmount(Order)
// 日志
- := OrderLog(Order)
}

现在只有四个功能,如果后序功能越来越多,那么Order_process这个函数也会越来越长,并且每次都要重新编译主函数,这个是非常不可取的。

假设我们现在用了消息队列,它的流程就变成了这样

首先,订单处理模块,解除了其他四个功能对它的依赖,只将订单传输进消息队列当中

其他几个功能模块要做的就是去消息队列中获取订单,分别处理就好了

这样我们的伪代码就变成了

1
2
3
4
5
6
7
//Order_process 订单处理 
func Order_process() {
//获取订单的函数
Order, _ := GetOrder()
// 订单入消息队列
mq.put(Order)
}

完全破处了其他几个功能模块的依赖,如果未来需要进行功能添加,只需要单独开发,并且订阅mq队列就可以。

也降低了维护成本,处理问题也针对到模块本身,减少了排错占用的人力成本。

这是消息队列的第二个好处。

削峰/限流

说人话就是,当访问量激增,咱们的数据库或者是服务顶不住的时候,先往消息队列里面写,等着后面数据库/服务根据自身设置,从消息队列里面取数据消费。

这个其实说起来比较简单,画个图吧(突然发觉我已经不讨厌画图了

现在有个大批量的访问来了,直接去访问服务A,服务A假设上限是1000,结果你访问量是10000,好了,直接凉凉

现在加上消息队列,访问先存放到消息队列里面,然后服务A直接去消息队列取1000条出来先消费了,然后继续从队列里面取就行了,避免服务直接挂掉。

不过这不是缓存嘛????

我寻思redis好像也是干这个的。。。。不过的确还是有不一样的地方,我在下面的会详细说下。

为什么要选择消息队列中间件?

这个其实是我曾经一直思考过的一个问题

首先

在Python里,我们有collections 的 queue

在Java里,我们有JDK 的 Deque

在Golang里,我们有slice作为简单队列

我们还可以多线程,多进行处理一些问题。

如果只是为了实现一些功能,我们甚至可以用redis这个更简单的玩意儿。

那么,为什么我们还是要选择例如rabbitmq,rocketmq,kafka这类做消息队列服务呢?

下面我将从我自己收集到的一些信息,总结一些自己的感悟,希望可以帮助大家。

持久化

首先,持久化就可以把前面说的那几种干死一大片

对,你可以用queue实现一个队列,但是万一断电了,你队列里面的消息不就没了吗?

对,你也可以往硬盘里写,你甚至可以另写一个定时同步的脚本去做这件事

但是。。。这不麻烦吗。。

万一你是分布式的,你存一份同步一份,妈耶,那工作量。

类似redis这种,可以把数据存在磁盘上。同理,消息队列的数据也是要存起来的。这样才可以减少意外情况带来的损失。

所以,你要自己写的话,简单的队列还是可以滴,但是消息队列嘛,免了。

高可用

用消息队列的都是他娘的分布式服务,如果你是个自己写的单机的消息队列,你这台机器挂了,岂不是消息队列服务就没了?

像rabbitmq这种,带着主从模式的,可以保证多少个节点down掉之后,消息服务依旧可用

一般的内存消息队列可没有这个功能哈,you know?

Redis Vs 消息队列

上面两个还是比较好理解,完全排除了那些内存数据库,也就是断了你自己写消息队列的这条心

因为你的多线程,多进程,queue,deque,永远都是单节点内存队列

但是有个特殊的玩意儿,Redis

上面我说过了,其实削峰的功能,还挺像Redis的,因为Redis也有这个功能,而且用的人还不少。

在互联网,大家都把这个叫做Redis缓存。

Redis其实可以覆盖很多消息队列的功能,但是!它终究有些功能是做不到的,下面我还是来详细讲一下redis和消息队列的对比不同吧。

首先,Redis也可以做消息队列

这个毋庸置疑,并且Redis的消息队列实现起来还挺简单的。

老版本的Redis,功能需求不多的,直接可以上 Redis Pub/Sub

如果Redis的版本大于5.0, 可以直接上Redis Stream,这个就是Redis专门用来做消息队列的实现方案。

Redis Stream的概念如下

1
2
3
4
5
Redis Stream 提供了消息的持久化和主备复制功能

可以让任何客户端访问任何时刻的数据

并且能记住每一个客户端的访问位置,还能保证消息不丢失

既然,Redis已经这么厉害了,那为什么还要用消息队列的中间件呢,例如rabbitmq,rocketmq

其实Redis和消息队列中间件还是有区别的,下面我把我总结的几个点拿出来,给大家讲讲

  1. Redis的机制,本来就是做缓存的
  2. Redis严格上来讲是一个内存数据库
  3. Redis如果pop任务从队列出去,失败了不会回到队列重试,需要手动重新push
  4. Redis自己本身有一个基于Redis源码的消息队列disque
  5. 一般的消息队列中间件都有持久化设置,Redis需要手动设置
  6. 一般的消息队列中间件处理之后可以手动ack,也可以自动ack
  7. 以rabbitmq为例,如果rabbitmq没有收到ack,会将消息放回消息队列进行处理,保证消息不丢失
  8. 一般的消息队列中间件具有更完善的MQ机制。

消息队列在项目上的使用

一般什么情况下才会用消息队列呢,这个问题我也反复问过我自己,下面我就从我自己做过的几个项目来分析一下一般什么情况才会上消息队列吧,可能并不完善,不过胜在都是自己的经验,也算有一点点可取之处吧。

需要异步的情况下

这种情况我在上面的章节举过例子,但是我举一个我开发的项目的例子吧,这样更加直观。

我开发了一个文件同步系统,类似百度网盘,需要从远端下载文件到本地,并且要对每个文件的同步结果进行日志记录。

分析一下,我的大概步骤就是

1
获取文件 -> 文件传输 -> 下发文件 -> 同步文件 -> 记录状态 -> 结束同步 -> 日志记录

可以看到,这是一个很长的步骤,并且可以通过异步逻辑去改装这个步骤

所以这里调用了消息队列做异步服务

修改过的服务步骤如下

1
2
3
                     Rabbitmq服务
——————————————————————————————
获取文件 -> |文件传输 -> 下发文件 -> 同步文件| -> 记录状态 -> 结束同步 -> 日志记录

首先,从获取文件开始,后面的任务状态直接修改为“待同步”,等rabbitmq服务跑外内部的步骤,再去修改服务状态,然后结束同步,记录日志。

这样做就可以减少业务在前端的等待时间。

这样就是我的大概想法。

微服务的架构下

公司的微服务架构我参与了设计,其实主要的核心就是解耦,这个我上面也说过了

主要的核心就是降低每个业务之间的互相依赖,比如A依赖B,B依赖C,这样如果A丢失或者挂掉,那么B,C都会一起完蛋,这是高可用的系统中非常不可取的。

所以,消息队列中间件,起到一个链接各方,传递消息的作用,以前是互相依赖传递消息,现在是用电话(消息队列)去传递消息,并且只用记住电话号码(订阅的消息队列)就可以了。

实时性要求不高的情况下

以rabbitmq为例,rabbitmq在上传消息到队列的时候是有一些延迟的,并不是实时操作的

而且rabbitmq还有ack机制消费者获取消息之后才会ack通知消息队列,如果没有ack通知,消息会重新放入消息队列,再次等待消费

所以消息队列,例如rabbitmq,kafka等,都不是实时的,如果需要追求实时性操作,你需要redis。

几种常见的消息队列盘点

这里直接上个图,这个图的原地址是

https://zhuanlan.zhihu.com/p/60288391

感谢您!您的图让我醍醐灌顶!

并且将消息队列优缺点总结如下

ActiveMQ

优点

  1. 单机吞吐量:万级
  2. topic数量都吞吐量的影响:
  3. 时效性:ms级
  4. 可用性:高,基于主从架构实现高可用性
  5. 消息可靠性:有较低的概率丢失数据
  6. 功能支持:MQ领域的功能极其完备

    缺点

ActiveMQ 5.x维护越来越少,较少在大规模吞吐的场景中使用。

Kafka

号称大数据的杀手锏,谈到大数据领域内的消息传输,则绕不开Kafka,这款为大数据而生的消息中间件,以其百万级TPS的吞吐量名声大噪,迅速成为大数据领域的宠儿,在数据采集、传输、存储的过程中发挥着举足轻重的作用。

Apache Kafka它最初由LinkedIn公司基于独特的设计实现为一个分布式的提交日志系统( a distributed commit log),之后成为Apache项目的一部分。

目前已经被LinkedIn,Uber, Twitter, Netflix等大公司所采纳。

优点

  1. 性能卓越,单机写入TPS约在百万条/秒,最大的优点,就是吞吐量高。
  2. 时效性:ms级
  3. 可用性:非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用
  4. 消费者采用Pull方式获取消息, 消息有序, 通过控制能够保证所有消息被消费且仅被消费一次
  5. 有优秀的第三方Kafka Web管理界面Kafka-Manager
  6. 在日志领域比较成熟,被多家公司和多个开源项目使用
  7. 功能支持:功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用

缺点

  1. Kafka单机超过64个队列/分区,Load会发生明显的飙高现象,队列越多,load越高,发送消息响应时间变长
  2. 使用短轮询方式,实时性取决于轮询间隔时间
  3. 消费失败不支持重试
  4. 支持消息顺序,但是一台代理宕机后,就会产生消息乱序
  5. 社区更新较慢

RabbitMQ

RabbitMQ 2007年发布,是一个在AMQP(高级消息队列协议)基础上完成的,可复用的企业消息系统,是当前最主流的消息中间件之一。

优点

  1. 由于erlang语言的特性,mq 性能较好,高并发;
  2. 吞吐量到万级,MQ功能比较完备
  3. 健壮、稳定、易用、跨平台、支持多种语言、文档齐全
  4. 开源提供的管理界面非常棒,用起来很好用
  5. 社区活跃度高

缺点:

  1. erlang开发,很难去看懂源码,基本职能依赖于开源社区的快速维护和修复bug,不利于做二次开发和维护。
  2. RabbitMQ吞吐量相比其他消息队列低
  3. 需要学习比较复杂的接口和协议,学习和维护成本较高。
  4. 性能差,每秒只能处理几万到十几万
  5. 消息堆积的时候,性能会马上下降

RocketMQ

RocketMQ阿里的开源,用Java语言实现,在设计时参考了Kafka,并做出了自己的一些改进。

RocketMQ在阿里集团被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理,binglog分发等场景。

优点

  1. 单机吞吐量:十万级
  2. 可用性:非常高,分布式架构
  3. 消息可靠性:经过参数优化配置,消息可以做到0丢失
  4. 功能支持:MQ功能较为完善,还是分布式的,扩展性好
  5. 支持10亿级别的消息堆积,不会因为堆积导致性能下降
  6. 源码是java,可以自己阅读源码,定制MQ

缺点

  1. 支持的客户端语言不多,目前是java及c++,其中c++不成熟
  2. 社区活跃度一般
  3. 没有在 mq 核心中去实现JMS等接口,迁移需要修改大量代码

选择消息队列的建议

如果你的只是轻量级使用mq消息队列

数据量可能每秒10W以下,对于持久化要求不高

Redis

如果你只是轻量级使用mq消息队列

数据量10w以下,需要一定的持久化操作

Rabbitmq

如果你是互联网业务,并且数据量时高时低,高的时候特别高,低的时候几乎没有(好像都是这样。。)

你要求数据不能丢失

你要求分布式

你要求高吞吐

kafka

结尾

差不多就这样些,作为5月第一篇blog,我要加油!大家也要加油!

我们一起刷题,一起离开!