当前位置: 首页 > 后端技术 > Java

什么是消息队列?

时间:2023-04-01 18:03:34 Java

以前公司用的很多技术我都没有学过(尴尬),只能慢慢补了。这次把我学习消息队列的笔记写给大家,希望对大家有所帮助。1.什么是消息队列?消息队列不知道大家看到这个词是不是觉得是一个比较高端的技术。无论如何,我认为它似乎非常棒。消息队列,一般我们会简称为MQ(MessageQueue),嗯,是一个很直白的简称。让我们忽略Message这个词,让我们看看Queue。从这点来看,队列大家应该不陌生了。队列是一种先进先出的数据结构。Java中已经实现了很多队列:为什么还需要消息队列(MQ)这样的中间件???其实这个问题和我之前学Redis的时候很像。Redis是一个以key-value形式存储的内存数据库。很明显,我们可以使用HashMap这样的实现类来实现类似的效果,那为什么还需要Redis呢?《Redis合集》到这里,大家可以先猜到为什么需要使用消息队列(MQ)等中间件了,下面我会继续补充。消息队列可以简单理解为:把要传输的数据放在队列中。)科普:往消息队列中放入数据称为生产者,从消息队列中取数据称为消费者2、为什么要使用消息队列?为什么使用消息队列就是要问:使用消息队列有什么好处。我们来看看下面的场景2.1解耦现在我有一个系统A,系统A可以生成一个userId,现在系统B和系统C都需要这个userId来做相关的操作伪代码写的可能是这样的:publicclassSystemA{//SystemB和SystemC的依赖SystemBsystemB=newSystemB();SystemCsystemC=newSystemC();//SystemA唯一数据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,请调整我的接口。”于是系统A说:“没问题,开始吧。”那么,系统A的代码如下:publicclassSystemA{//不再需要系统B的依赖//SystemBsystemB=newSystemB();//系统C和系统D的依赖SystemCsystemC=newSystemC();SystemDsystemD=newSystemD();//系统唯一数据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.命令();//如果下单成功,安排其他系统做某事systemB.SystemBNeed2do(userId);systemC.SystemCNeed2do(userId);systemD.SystemDNeed2do(userId);}}复制代码假设A系统计算userId的具体值需要50ms,调用系统B的接口需要300ms,调用系统C的接口需要300ms,需要300ms调用D系统的接口,那么这个请求需要50+300+300+300=950ms,我们知道A系统做的是主业务,B、C、D系统是非主业务。比如A系统处理下单,B系统如果下单成功,则发短信告诉具体用户下单成功,而C系统和D系统只处理一些琐碎的事情。这时为了提高用户体验和吞吐量,系统B、C、D的接口其实可以异步调用。因此,我们可以这样写:系统A执行完后,将userId写入消息队列,然后直接返回(至于其他操作,异步处理)。本来整个请求需要950ms(同步),现在会异步调用其他系统接口,只有100ms(异步)(例子可能不是很好,但我觉得足以说明问题,见谅.)2.3peakclipping/让我们有另一种限流场景。现在我们每个月都有大促销。大促的时候可能并发很高,比如每秒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。最后,本文主要讲解什么是消息队列,消息队列能给我们带来什么好处,以及消息队列可能会涉及到哪些问题。希望能给你带来一些帮助。