没用过消息队列?一文带你体验RabbitMQ收发消息
楔子
先给大家说声抱歉,最近一周都没有发文,有一些比较要紧重要的事需要处理。
今天正好得空,本来说准备写SpringIOC相关的东西,但是发现想要梳理一遍还是需要很多时间,所以我打算慢慢写,先把MQ给写了,再慢慢写其他相关的,毕竟偏理论的东西一遍要比较难写,像MQ这种偏实战的大家可以clone代码去玩一玩,还是比较方便的。
同时MQ也是Java进阶不必可少的技术栈之一,所以Java开发从业者对它是必须要了解的。
现在市面上有三种消息队列比较火分别是:RabbitMQ,RocketMQ和Kafka。
今天要讲的消息队列中我会以RabbitMQ作为案例来入门,因为SpringBoot的amqp中默认只集成了RabbitMQ,用它来讲会方便许多,且RabbitMQ的性能和稳定性都很不错,是一款经过时间考验的开源组件。
祝有好收获。1。消息队列?
消息队列(MQ)全称为MessageQueue,是一种应用程序对应用程序的通信方法。
翻译一下就是:在应用之间放一个消息组件,然后应用双方通过这个消息组件进行通信。
好端端的为啥要在中间放个组件呢?
小系统其实是用不到消息队列的,一般分布式系统才会引入消息队列,因为分布式系统需要抗住高并发,需要多系统解耦,更需要对用户比较友好的响应速度,而消息队列的特性可以天然解耦,方便异步更能起到一个顶住高并发的削峰作用,完美解决上面的三个问题。
然万物抱阳负阴,系统之间突然加了个中间件,提高系统复杂度的同时也增加了很多问题:消息丢失怎么办?消息重复消费怎么办?某些任务需要消息的顺序消息,顺序消费怎么保证?消息队列组件的可用性如何保证?
这些都是使用消息队列过程中需要思考需要考虑的地方,消息队列能给你带来很大的便利,也能给你带来一些对应的麻烦。
上面说了消息队列带来的好处以及问题,而这些不在我们今天这篇的讨论范围之内,我打算之后再写这些,我们今天要做的是搭建出一个消息队列环境,让大家感受一下基础的发消息与消费消息,更高级的问题会放在以后讨论。2。RabbitMQ一览
RabbitMQ是一个消息组件,是一个erlang开发的AMQP(AdvancedMessageQueue)的开源实现。
AMQP,即AdvancedMessageQueuingProtocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。
RabbitMQ采用了AMQP协议,至于这协议怎么怎么样,我们关心的是RabbitMQ结构如何且怎么用。
还是那句话,学东西需要先观其大貌,我们要用RabbitMQ首先要知道它整体是怎么样,这样才有利于我们接下来的学习。
我们先来看看我刚画的架构图,因为RabbitMQ实现了AMQP协议,所以这些概念也是AMQP中共有的。
Broker:中间件本身。接收和分发消息的应用,这里指的就是RabbitMQServer。Virtualhost:虚拟主机。出于多租户和安全因素设计的,把AMQP的基本组件划分到一个虚拟的分组中,类似于网络中的namespace概念。当多个不同的用户使用同一个RabbitMQserver提供的服务时,可以划分出多个vhost,每个用户在自己的vhost创建exchangequeue等。Connection:连接。publisherconsumer和broker之间的TCP连接。断开连接的操作只会在client端进行,Broker不会断开连接,除非出现网络故障或broker服务出现问题。Channel:渠道。如果每一次访问RabbitMQ都建立一个Connection,在消息量大的时候建立TCPConnection的开销会比较大且效率也较低。Channel是在connection内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的channel进行通讯,AMQPmethod包含了channelid帮助客户端和messagebroker识别channel,所以channel之间是完全隔离的。Channel作为轻量级的Connection极大减少了操作系统建立TCPconnection的开销。Exchange:路由。根据分发规则,匹配查询表中的routingkey,分发消息到queue中去。Queue:消息的队列。消息最终被送到这里等待消费,一个message可以被同时拷贝到多个queue中。Binding:绑定。exchange和queue之间的虚拟连接,binding中可以包含routingkey。Binding信息被保存到exchange中的查询表中,用于message的分发依据。
看完了这些概念,我再给大家梳理一遍其流程:
当我们的生产者端往Broker(RabbitMQ)中发送了一条消息,Broker会根据其消息的标识送往不同的Virtualhost,然后Exchange会根据消息的路由key和交换器类型将消息分发到自己所属的Queue中去。
然后消费者端会通过Connection中的Channel获取刚刚推送的消息,拉取消息进行消费。
Tip:某个Exchange有哪些属于自己的Queue,是由Binding绑定关系决定的。3。RabbitMQ环境
上面讲了RabbitMQ大概的结构图和一个消息的运行流程,讲完了理论,这里我们就准备实操一下吧,先进行RabbitMQ安装。
官网下载地址:www。rabbitmq。comdownload。ht
由于我还没有属于自己MAC电脑,所以这里的演示就按照Windows的来了,不过大家都是程序员,安装个东西总归是难不倒大家的吧
Windows下载地址:www。rabbitmq。cominstallwin
进去之后可以直接找到DirectDownloads,下载相关EXE程序进行安装就可以了。
由于RabbitMQ是由erlang语言编写的,所以安装之前我们还需要安装erlang环境,你下载RabbitMQ之后直接点击安装,如果没有相关环境,安装程序会提示你,然后会让你的浏览器打开erlang的下载页面,在这个页面上根据自己的系统类型点击下载安装即可,安装完毕后再去安装RabbitMQ。
这两者的安装都只需要一直NEXT下一步就可以了。
安装完成之后可以按一下Windows键看到效果如下:
Tip:其中RabbitCommand后面会用到,是RabbitMQ的命令行操作台。
安装完RabbitMQ我们需要对我们的开发环境也导入RabbitMQ相关的JAR包。
为了方便起见,我们可以直接使用Springbootstart的方式导入,这里面也会包含所有我们需要用到的RabbitMQ相关的JAR包。dependenciesdependencygroupIdorg。springframework。bootgroupIdspringbootstarteramqpartifactIddependencydependencies
直接引入springbootstarteramqp即可。4。HelloWorld
搭建好环境之后,我们就可以上手了。
考虑到这是一个入门文章,读者很多可能没有接触过RabbitMQ,直接使用自动配置的方式可能会令大家很迷惑,因为自动配置会屏蔽很多细节,导致大家只看到了被封装后的样子,不利于大家理解。
所以在本节HelloWorld这里,我会直接使用最原始的连接方式就行演示,让大家看到最原始的连接的样子。
Tip:这种方式演示的代码我都在放在prototype包下面。4。1生产者
先来看看生产者代码,也就是我们push消息的代码:publicstaticfinalStringQUEUENAMEerduo;创建连接工厂ConnectionFactoryconnectionFactorynewConnectionFactory();连接到本地serverconnectionFactory。setHost(127。0。0。1);通过连接工厂创建连接ConnectionconnectionconnectionFactory。newConnection();通过连接创建通道Channelchannelconnection。createChannel();创建一个名为耳朵的队列,该队列非持久(RabbitMQ重启后会消失)、非独占(非仅用于此链接)、非自动删除(服务器将不再使用的队列删除)channel。queueDeclare(QUEUENAME,false,false,false,null);Stringmsghello,我是耳朵。LocalDateTime。now()。toString();发布消息四个参数为:指定路由器,指定key,指定参数,和二进制数据内容channel。basicPublish(,QUEUENAME,null,msg。getBytes(StandardCharsets。UTF8));System。out。println(生产者发送消息结束,发送内容为:msg);channel。close();connection。close();
代码我都给了注释,但是我还是要给大家讲解一遍,梳理一下。
先通过RabbitMQ中的ConnectionFactory配置一下将要连接的serverhost,然后创建一个新连接,再通过此连接创建通道(Channel),通过这个通道创建队列和发送消息。
这里看上去还是很好理解的,我需要把创建队列和发送消息这里再拎出来说一下。
创建队列AMQP。Queue。DeclareOkqueueDeclare(Stringqueue,booleandurable,booleanexclusive,booleanautoDelete,MapString,Objectarguments)throwsIOException;
创建队列的方法里面有五个参数,第一个是参数是队列的名称,往后的三个参数代表不同的配置,最后一个参数是额外参数。durable:代表是否将此队列持久化。exclusive:代表是否独占,如果设置为独占队列则此队列仅对首次声明它的连接可见,并在连接断开时自动删除。autoDelete:代表断开连接后是否自动删除此队列。arguments:代表其他额外参数。
这些参数中durable经常会用到,它代表了我们可以对队列做持久化,以保证RabbitMQ宕机恢复后此队列也可以自行恢复。
发送消息voidbasicPublish(Stringexchange,StringroutingKey,AMQP。BasicPropertiesprops,byte〔〕body)throwsIOException;
发送消息的方法里是四个参数,第一个是必须的指定exchange,上面的示例代码中我们传入了一个空字符串,这代表我们交由默认的匿名exchange去帮我们路由消息。
第二个参数是路由key,exchange会根据此key对消息进行路由转发,第三个参数是额外参数,讲消息持久化时会用到一下,最后一个参数就是我们要发送的数据了,需要将我们的数据转成字节数组的方式传入。
测试
讲完了这些API之后,我们可以测试一下我们的代码了,run一下之后,会在控制台打出如下:
这样之后我们就把消息发送到了RabbitMQ中去,此时可以打开RabbitMQ控制台(前文安装时提到过)去使用命令rabbitmqctl。batlistqueues去查看消息队列现在的情况:
可以看到有一条message在里面,这就代表我们的消息已经发送成功了,接下来我们可以编写一个消费者对里面的message进行消费了。4。2消费者
消费者代码和生产者的差不多,都需要建立连接建立通道:创建连接工厂ConnectionFactoryconnectionFactorynewConnectionFactory();连接到本地serverconnectionFactory。setHost(127。0。0。1);通过连接工厂创建连接ConnectionconnectionconnectionFactory。newConnection();通过连接创建通道Channelchannelconnection。createChannel();创建消费者,阻塞接收消息com。rabbitmq。client。ConsumerconsumernewDefaultConsumer(channel){OverridepublicvoidhandleDelivery(StringconsumerTag,Envelopeenvelope,AMQP。BasicPropertiesproperties,byte〔〕body)throwsIOException{System。out。println();System。out。println(consumerTag:consumerTag);System。out。println(exchangeName:envelope。getExchange());System。out。println(routingKey:envelope。getRoutingKey());StringmsgnewString(body,StandardCharsets。UTF8);System。out。println(消息内容:msg);}};启动消费者消费指定队列channel。basicConsume(Producer。QUEUENAME,consumer);channel。close();connection。close();
建立完通道之后,我们需要创建一个消费者对象,然后用这个消费者对象去消费指定队列中的消息。
这个示例中我们就是新建了一个consumer,然后用它去消费队列erduo中的消息。
最后两句代码我给注释掉了,因为一旦把连接也关闭了,那我们的消费者就不能保持消费状态了,所以要开着连接,监听此队列。
ok,运行这段程序,然后我们的消费者会去队列erduo拿到里面的消息,效果如下:
consumerTag:是这个消息的标识。exchangeName:是这个消息所发送exchange的名字,我们先前传入的是空字符串,所以这里也是空字符串。exchangeName:是这个消息所发送路由key。
这样我们的程序就处在一个监听的状态下,你再次调用生产者发送消息消费者就会实时的在控制上打印消息内容。5。消息接收确认(ACK)
上面我们演示了生产者和消费者,我们生产者发送一条消息,消费者消费一条信息,这个时候我们的RabbitMQ应该有多少消息?
理论上来说发送一条,消费一条,现在里面应该是0才对,但是现在的情况并不是:
消息队列里面还是有1条信息,我们重启一下消费者,又打印了一遍我们消费过的那条消息,通过消息上面的时间我们可以看出来还是当时我们发送的那条信息,也就是说我们消费者消费过了之后这条信息并没有被删除。
这种状况出现的原因是因为RabbitMQ消息接收确认机制,也就是说一条信息被消费者接收到了之后,需要进行一次确认操作,这条消息才会被删除。
RabbitMQ中默认消费确认是手动的,也可以将其设置为自动删除,自动删除模式消费者接收到消息之后就会自动删除这条消息,如果消息处理过程中发生了异常,这条消息就等于没被处理完但是也被删除掉了,所以这里我们会一直使用手动确认模式。
消息接受确认(ACK)的代码很简单,只要在原来消费者的代码里加上一句就可以了:com。rabbitmq。client。ConsumerconsumernewDefaultConsumer(channel){OverridepublicvoidhandleDelivery(StringconsumerTag,Envelopeenvelope,AMQP。BasicPropertiesproperties,byte〔〕body)throwsIOException{System。out。println();System。out。println(consumerTag:consumerTag);System。out。println(exchangeName:envelope。getExchange());System。out。println(routingKey:envelope。getRoutingKey());StringmsgnewString(body,StandardCharsets。UTF8);System。out。println(消息内容:msg);消息确认channel。basicAck(envelope。getDeliveryTag(),false);System。out。println(消息已确认);}};
我们将代码改成如此之后,可以再run一次消费者,可以看看效果:
再来看看RabbitMQ中的队列情况:
从图中我们可以看出消息消费后已经成功被删除了,其实大胆猜一猜,自动删除应该是在我们的代码还没执行之前就帮我们返回了确认,所以这就导致了消息丢失的可能性。
我们采用手动确认的方式之后,可以先将逻辑处理完毕之后(可能出现异常的地方可以trycatch起来),把手动确认的代码放到最后一行,这样如果出现异常情况导致这条消息没有被确认,那么这条消息会在之后被重新消费一遍。后记
今天的内容就到这里,下一篇将会我们将会撇弃传统的手动建立连接的方式进行发消息收消息,而转用Spring帮我们定义好的注解和Spring提供的RabbitTemplate,更方便的收发消息。
消息队列呢,其实用法都是一样的,只是各个开源消息队列的侧重点稍有不同,我们应该根据我们自己的项目需求来决定我们应该选取什么样的消息队列来为我们的项目服务,这个项目选型的工作一般都是开发组长帮你们做了,一般是轮不到我们来做的,但是面试的时候可能会考察相关知识,所以这几种消息队列我们都应该有所涉猎。
好了,以上就是本期的全部内容,感谢你能看到这里,欢迎对本文点赞收藏与评论,你们的每个点赞都是我创作的最大动力。
我是耳朵,一个一直想做知识输出的伪文艺程序员,我们下期见。
作者:和耳朵
链接:https:juejin。impost6856571028496351239
加强保护!OPPO注册系列商标至Reno12如今手机的更新换代速度非常快,每个月都会有厂商发布新款手机抢占市场。为了有更有针对性的刺激消费者,各大手机厂商都会将自家的产品定位加以区分,其中高端旗舰机型是兵家必争之地。……
大型洗碗机流水线的特点使用洗碗机流水线在很大程度上解决了人力成本投入大的问题,尤其是实现自动清洗、烘干消毒一体化,大大节省了人力成本。消毒餐具设备的产品特点:1。使用304食品级不锈钢板……
音乐也有自研芯片,看看HIFIMANHM901R的喜马拉雅有4月份的时候错过了一场很重要的发布会,没能去现场见证HIFIMAN自研DAC芯片的诞生,直到现在仍然觉得遗憾。都知道芯片是一种代表着技术和实力都非常顶尖的产品,真正能够做到芯片……
致敬时代先锋ThinkPad家族系列新品正式发布,引领商务笔4月22日,联想以你好先锋为主题,在北京正式召开了ThinkPadFamily2021春季新品发布会。在这场堪称PC行业盛典的发布会上,ThinkPad推出了备受瞩目的至薄之刃……
企业云服务器备案指南详细的备案流程指导攻略为什么要备案国家为了规范互联网信息服务活动,促进互联网信息服务健康有序发展,对经营性互联网信息服务实行许可制度,对非经营性互联网信息服务实行备案制度。未取得许可或者未履行……
在微博,新锐品牌如何破解流量困局?新消费是什么?新消费,新在哪?是新人群?新产品?还是新媒体?新渠道?生于互联网的新消费品牌,主要有三个显著特点:新渠道:以微博等新媒体为主要营销平台;以电商渠……
某程序员吐槽太尴尬!四年不见的前女友来面试,自己还是面试官分手四年的前女友,再见面却在公司的面试现场,更尴尬的自己还是她的面试官这么狗血的情节不是电视剧,而是发生在一个程序员身上的真实故事。楼主说,第一眼看到前女友感觉她变化挺大……
贝因美携绿爱亮相进博会大国品牌魅力凸显11月4日晚,第四届中国国际进口博览会(下称进博会)开幕式在国家会展中心(上海)举行。入选央视CCTV大国品牌的贝因美(002570。SZ)携旗下高端产品绿爱惊艳亮相进博会,爱……
猫咪肾衰竭肾衰竭是猫较多发的疾病,猫的祖先在沙漠地区,少水,肾单元较少,易得肾衰竭。我家大橘在春节期间发病,几天不吃不喝,躲在角落睡觉。送医化验发现肌酐尿素氮偏高,确诊肾衰竭。大橘只有四……
贵阳市总多举措强化搬迁群众服务工作工会组织给我介绍了工作,现在我当上了超市营业员,妻子是一名环卫工人,孩子就近上幼儿园,日子过得还算安稳。在贵阳市花溪区易地扶贫搬迁集中安置点南溪苑,工会会员安伦红高兴地说。……
无惧337调查,FIBBR积极应诉最终获胜2021年8月18日,ITC(UnitedStatesInternationalTradeCommission美国国际贸易委员会)对长芯盛智连科技有限公司及旗下品牌FIBBR涉……
比冠道大一圈2021款本田Pilot特别版7座SUV发布日前,从海外消息了解到,本田将在海外推出2021款本田Pilot特别版7座SUV,新车外观内饰与广汽本田冠道车型相类似,可以看成是大一号的冠道。2021款本田Pilot特……