当前位置: 首页 > 科技观察

面试官问你什么是消息队列?丢给他!

时间:2023-03-21 16:09:05 科技观察

1.什么是消息队列?消息队列不知道大家看到这个词是不是觉得是一个比较高端的技术。无论如何,我认为它似乎非常棒。消息队列,一般我们会简称为MQ(MessageQueue),嗯,是一个很直白的简称。让我们忽略Message这个词,让我们看看Queue。从这点来看,队列大家应该不陌生了。队列是一种先进先出的数据结构。Java中已经实现了很多队列:为什么还需要消息队列(MQ)这样的中间件???至此,大家可以先猜到为什么需要使用消息队列(MQ)等中间件,下面我们继续补充。消息队列可以简单理解为:把要传输的数据放在队列中。图片来源:https://www.cloudamqp.com/blog/2014-12-03-what-is-message-queuing.html科普:将数据放入消息队列称为生产者,从消息队列获取数据称为消费者2.为什么要使用消息队列?为什么使用消息队列就是要问:使用消息队列有什么好处。下面我们来看看以下几个场景2.1解耦现在我有一个系统A,系统A可以生成一个userId,现在系统B和系统C都需要这个userId来做相关操作。伪代码可以这样写:publicclassSystemA{//系统B和系统C依赖于SystemBsystemB=newSystemB();SystemCsystemC=newSystemC();//系统A唯一数据userIdprivateStringuserId="Java3y";publicvoiddoSomething(){//系统B和系统C都需要持有系统A的userId,用来操作其他东西systemB.SystemBNeed2do(userId);systemC.SystemCNeed2do(userId);}}结构图如下:ok,这几天一切顺利。有一天,B系统的负责人告诉A系统的负责人,B系统的SystemBNeed2do(StringuserId)接口已经不用了,A系统不要再调整了。于是,A系统的负责人说:“好吧,那我就不给你打电话了。”,所以删除了调用系统B的接口的代码:publicvoiddoSomething(){//系统A不再调用系统B的接口//systemB.SystemBNeed2do(userId);systemC.SystemCNeed2do(userId);}之后几天,D系统的负责人接到一个请求,也需要用到A系统??的userId,就跑到A系统的负责人那里说,“大哥,我要用你的userId,你call看看我的接口吧。”然后系统A说:“没问题,我们开始吧。”然后,系统A的代码如下:publicclassSystemA{//不再需要系统B的依赖//SystemBsystemB=newSystemB();//系统C和系统D依赖SystemCsystemC=newSystemC();SystemDsystemD=newSystemD();//系统A唯一数据privateStringuserId="Java3y";publicvoiddoSomething(){//系统B依赖是不再需要//systemB.SystemBNeed2do(userId);//系统C和系统D都需要使用系统A的userId来操作其他东西systemC.SystemCNeed2do(userId);systemD.SystemDNeed2do(userId);几天后,系统E的负责人过来告诉系统A,需要userId。几天后,B系统的负责人过来告诉A系统,需要重新设置接口。过了几天,系统F的负责人过来告诉系统A,需要userId。。。于是系统A的负责人就天天被这个骚扰,改啊,改啊。。。还有一个问题,调用系统C,如果系统C挂了,系统A就得想办法处理。如果调用系统D,由于网络延迟导致请求超时,那么系统A会反馈失败或者重试??***,系统A负责人觉得每隔一段时间改一次很无聊,所以他逃跑了。然后,公司招了一个大老板,他熟悉了几天,上来就说:把系统A的userId写到消息队列中,这样系统A就不用经常改了。为什么?我们一起来看看:系统A将userId写入消息队列,系统C和系统D从消息队列中获取数据。那有什么好处?系统A只负责往队列中写入数据,这个数据(消息)谁要谁不要,系统A根本不管。就算系统D现在不要userId的数据,系统B突然要userId的数据,也和系统A没有关系,系统A也不需要改任何代码。系统D并没有通过系统A获取userId,而是从消息队列中获取。即使系统D挂了或者请求超时,也跟系统A没有关系,只跟消息队列有关。这样系统A就和系统B、C、D解耦了。2.2异步我们来看下面这种情况:系统A还是直接调用系统B、C、D代码如下:publicclassSystemA{SystemBsystemB=newSystemB();SystemCsystemC=newSystemC();SystemDsystemD=newSystemD();//系统A单独一些数据privateStringuserId;publicvoiddoOrder(){//下单userId=this.order();//如果下单成功,安排其他系统做某事systemB.SystemBNeed2do(userId);systemC.SystemCNeed2do(userId);systemD.SystemDNeed2do(userId);}}假设A系统计算userId的具体值需要50ms,调用B系统接口需要300ms,调用C系统接口需要300ms,调用系统D接口需要300ms。那么这个请求需要50+300+300+300=950ms,我们知道A系统做的是主业务,B、C、D系统是非主业务。比如A系统处理下单,B系统如果下单成功,则发短信告诉具体用户下单成功,而C系统和D系统只处理一些琐碎的事情。这时为了提高用户体验和吞吐量,系统B、C、D的接口其实可以异步调用。因此,我们可以这样写:系统A执行完后,将userId写入消息队列,然后直接返回(至于其他操作,异步处理)。本来整个请求耗时950ms(同步),现在会异步调用其他系统接口,从请求到返回只需要100ms(异步)(例子可能不是很好,但我觉得足以说明点,原谅我。)2.3调峰/限制让我们有另一种情况。现在我们每个月都有大促销。大促的时候可能并发很高,比如每秒3000个请求。假设我们现在有两台机器处理请求,每台机器一次只能处理1000个请求。多出来的1000个请求可能会把我们整个系统搞垮。。。所以,有一个办法,我们可以写入消息队列:B系统和C系统根据自己能处理的请求数去消息队列取数据,所以即使每秒有8000个请求,它也只是将请求放到消息队列中,系统控制消息获取消息队列,这样整个系统就不会崩溃。3、使用消息队列有什么问题?经过我们上面的场景,我们已经可以发现消息队列可以做的事情还是蛮多的。说了这么多,让我们回到文章开头,“明明JDK已经实现了很多队列,我们??还需要消息队列中间件吗?”其实很简单。虽然JDK实现的队列种类很多,但都是简单的内存队列。为什么我说JDK是一个简单的内存队列呢?下面我们来看看实现消息队列(中间件)可能会考虑哪些问题。3.1高可用无论我们使用消息队列是为了解耦、异步还是调峰,消息队列都不能是单机的。试想一下,如果是单机的消息队列,如果这台机器挂了,那我们整个系统就几乎不可用了。所以我们在项目中使用消息队列的时候,都是集群/分布式的。如果要做集群/分布式,肯定希望消息队列能够提供现成的支持,而不是自己写代码去手动实现。3.2数据丢失问题我们向消息队列写入数据,系统B和C还没来得及从消息队列中取数据就挂了。如果什么都不做,我们的数据将会丢失。学过Redis的人都知道,Redis可以持久化磁盘上的数据。万一Redis挂了,还可以从磁盘恢复数据。同样,消息队列中的数据也需要存储在别处,这样才能最大限度地减少数据丢失。它存在于何处?磁盘?数据库?雷迪斯?分布式文件系统?同步存储还是异步存储?3.3消费者如何获取消息队列的数据?消费者如何从消息队列中获取数据?有两种方式:生产者把数据放到消息队列中,消息队列有数据,要求消费者去取(俗称push)。消费者不断地训练消息队列,看看有没有新的数据。消费(俗称pull)3.4其他除了这些,我们在使用的时候还要考虑各种问题:消息重复消费怎么办?我想保证消息绝对是有序的怎么办呢?.....虽然消息队列给我们带来了如此多的好处,但同时我们发现引入消息队列也会增加系统的复杂度。市面上已经有很多消息队列的轮子,每个消息队列都有自己的特点。您必须仔细考虑选择哪个MQ。***本文主要讲解什么是消息队列,消息队列能给我们带来什么好处,消息队列可能会涉及到哪些问题。希望能给你带来一些帮助。