掘金 后端 ( ) • 2024-06-29 16:43

image

前言

  公司用到的很多技术,自己之前都没学过(尬),于是只能慢慢补了。这次给大家写写我学习消息队列的笔记,希望对大家有帮助。

消息队列概述

1.1 什么是消息队列

  消息队列不知道大家看到这个词的时候,会不会觉得它是一个比较高端的技术,反正我是觉得它好像是挺牛逼的。消息队列,一般我们会简称它为MQ(Message Queue),嗯,就是很直白的简写。

  我们先不管消息(Message)这个词,来看看队列(Queue)。这一看,队列大家应该都熟悉吧。队列是一种先进先出的数据结构,如下图所示:

image

  消息队列可以简单理解为:把要传输的数据放在队列中,把数据放到消息队列叫做生产者,从消息队列里边取数据叫做消费者。

1.2 消息中间件比较

特性 ActiveMQ RabbitMQ RocketMQ 开发语⾔ java erlang java 单机吞吐量 万级 万级 10万级 时效性 ms级 us级 ms级 可⽤性 ⾼(主从架构) ⾼(主从架构) ⾮常⾼(分布式架构) 功能特性 成熟的产品,在很多公司得到应⽤;
有较多的⽂档;
各种协议⽀持较好 基于erlang开发,并发能⼒很强
性能极其好,延时很低;
管理界⾯较丰富 MQ功能⽐较完备
扩展性佳

二、为什么要用消息队列

  为什么要用消息队列,也就是在问:用了消息队列有什么好处。在开始之初,由于系统业务体量很小,所以直接单机一把梭啥都能搞定了,但是后面业务体量不断扩大,采用微服务的设计思想、分布式的部署方式,所以拆分了很多的服务。随着体量的增加以及业务场景越来越复杂了,很多场景单机的技术栈和中间件以及不够用了,而且对系统的友好性也下降了,最后做了很多技术选型的工作,我们决定引入消息队列中间件。消息队列的经典场景莫非不过于解耦、异步、削峰等。

2.1 应用解耦

  解耦是消息队列要解决的最本质问题。所谓解耦,简单点讲就是一个事务,只关心核心的流程。而需要依赖其他系统但不那么重要的事情,有通知即可,无需等待结果。换句话说,基于消息的模型,关心的是“通知”,而非“处理”。

  比如,现有一个系统A,系统A可以产生一个userId。系统B和系统C都需要这个 userId 去做相关的操作。业务需求很简单,系统A给系统B和系统C传入即可,如下图所示:

image

  ok,一切平安无事度过了几个天。某一天,系统B的负责人告诉系统A的负责人,现在系统B接口不再使用了,让系统A别去调它了。于是,系统A的负责人说"好的,那我就不调用你了"。又过了几天,系统D的负责人接了个需求,也需要用到系统A的userId,于是就跑去跟系统A的负责人说:"老哥,我要用到你的userId,你调一下我的接口吧"!于是系统A说:"没问题的,这就搞"。如下图所示:

image

  然后又过了几天,系统E的负责人过来了,告诉系统A,需要userId。又过了几天,系统B的负责人过来了,告诉系统A,还是重新调那个接口吧。又过了几天,系统F的负责人过来了,告诉系统A,需要userId……于是系统A的负责人,每天都被这给骚扰着,改来改去,改来改去…….

image

  还有另外一个问题,调用系统C的时候,如果系统C挂了,系统A还得想办法处理。如果调用系统D时,由于网络延迟,请求超时了,那系统A是反馈fail还是重试?最后,系统A的负责人,觉得隔一段时间就改来改去,没意思,于是就跑路了。然后,公司招来一个大佬,大佬经过几天熟悉,上来就说:将系统A的userId写到消息队列中,这样系统A就不用经常改动了。为什么呢?下面我们来一起看看:

image

  系统A将userId写到消息队列中,系统C和系统D从消息队列中拿数据。系统A只负责把数据写到队列中,至于谁想要或不想要这个数据(消息),系统A一点都不关心。即便现在系统D不想要userId这个数据了,系统B又突然想要userId这个数据了,都跟系统A无关,系统A一点代码都不用改。系统D拿userId不再经过系统A,而是从消息队列里边拿。系统D即便挂了或者请求超时,都跟系统A无关,只跟消息队列有关。这样一来,系统A与系统B、C、D都解耦了。

2.2 异步处理

  如果我方系统A需要同时推送消息到BCD三个系统中,而BCD系统接收到消息后需要进行复杂的逻辑处理,以及读库写库处理。比如用户注册后,需要发注册邮件和注册短信。传统的做法有两种:

  • 串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端。

    image

  • 并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间。

    image

  假设三个业务节点每个使用50毫秒钟,不考虑网络等其他开销,则串行方式的时间是150毫秒,并行的时间可能是100毫秒。如以上案例描述,传统的方式系统的性能(并发量,吞吐量,响应时间)会有瓶颈,那么如何解决这个问题呢?那就是引入消息队列,将不是必须的业务逻辑进行异步处理。改造后的架构如下:

image

  按照以上约定,用户的响应时间相当于是注册信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20QPS。比串行提高了3倍,比并行提高了两倍。

2.3 流量削峰/限流

  我们再来一个场景,现在我们每个月要搞一次大促,大促期间的并发可能会很高的,比如每秒3000个请求。假设我们现在有两台机器处理请求,并且每台机器只能每次处理1000个请求。如下图所示:

image

  那多出来的1000个请求,可能就把我们整个系统给搞崩了…所以,有一种办法,我们可以写到消息队列中。系统B和系统C根据自己的能够处理的请求数去消息队列中拿数据,这样即便有每秒有8000个请求,那只是把请求放在消息队列中,去拿消息队列的消息由系统自己去控制,这样就不会把整个系统给搞崩。如下图所示:

image

三、使用消息队列有什么问题

  经过我们上面的阐述中,不难可以发现,消息队列能做的事其实还是蛮多的。但正如谚语所说“人无完人,事无完事”,本来蛮简单的一个系统,我代码随便写都没事,现在你凭空接入一个中间件在那,我是不是要考虑去维护它,而且使用的过程中是不是要考虑各种问题,比如消息重复消费、消息丢失等等,反正用了之后就是贼烦。

3.1 系统可用性降低

  无论是我们使用消息队列来做解耦、异步还是削峰,消息队列肯定不能是单机的。试着想一下,如果是单机的消息队列,万一这台机器挂了,那我们整个系统几乎就是不可用了。

image

  所以,当我们项目中使用消息队列,都是得集群(分布式)的。要做集群(分布式)就必然希望该消息队列能够提供现成的支持,而不是自己写代码手动去实现。

3.2 数据一致性

  这个其实是分布式服务本身就存在的一个问题,不仅仅是消息队列的问题。就像上面提到的,系统A的业务逻辑成功处理,保证自己的业务数据对的就好了,至于系统B和C等系统,它们究竟是成功还是失败就不管了。

20240602202250.jpeg

3.3 数据丢失问题

  我们将数据写到消息队列上,系统B和C还没来得及取消息队列的数据,就挂掉了。如果没有做任何的措施,我们的数据就丢了。学过Redis的都知道,Redis可以将数据持久化磁盘上,万一Redis挂了,还能从磁盘从将数据恢复过来。同样地,消息队列中的数据也需要存在别的地方,这样才尽可能减少数据的丢失。

四、结语

把今天最好的表现当作明天最新的起点…...~

  本文主要讲解了什么是消息队列,消息队列可以为我们带来什么好处,以及一个消息队列可能会涉及到哪些问题。虽然消息队列给我们带来了那么多的好处,但同时我们发现引入消息队列也会提高系统的复杂性。市面上现在已经有不少消息队列轮子了,每种消息队列都有自己的特点,选取哪种MQ还得好好斟酌。

image