SpringBoot整合RabbitMQ之典型應(yīng)用場景實(shí)戰(zhàn)一

實(shí)戰(zhàn)前言

RabbitMQ 作為目前應(yīng)用相當(dāng)廣泛的消息中間件,在企業(yè)級應(yīng)用、微服務(wù)應(yīng)用中充當(dāng)著重要的角色。特別是在一些典型的應(yīng)用場景以及業(yè)務(wù)模塊中具有重要的作用,比如業(yè)務(wù)服務(wù)模塊解耦、異步通信、高并發(fā)限流、超時業(yè)務(wù)、數(shù)據(jù)延遲處理等。

RabbitMQ 官網(wǎng)拜讀

首先,讓我們先拜讀 RabbitMQ 官網(wǎng)的技術(shù)開發(fā)手冊以及相關(guān)的 Features,感興趣的朋友可以耐心的閱讀其中的相關(guān)介紹,相信會有一定的收獲,地址可見:www.rabbitmq.com/getstarted.…

在閱讀該手冊過程中,我們可以得知 RabbitMQ 其實(shí)核心就是圍繞 “消息模型” 來展開的,其中就包括了組成消息模型的相關(guān)組件:生產(chǎn)者,消費(fèi)者,隊(duì)列,交換機(jī),路由,消息等!而我們在實(shí)戰(zhàn)應(yīng)用中,實(shí)際上也是緊緊圍繞著 “消息模型” 來展開擼碼的!

下面,我就介紹一下這一消息模型的演變歷程,當(dāng)然,這一歷程在 RabbitMQ 官網(wǎng)也是可以窺覽得到的!

送福利了,現(xiàn)在私信我“資料”即可獲取Java工程化、高性能及分布式、高性能、高架構(gòu)。性能調(diào)優(yōu)、Spring,MyBatis,Netty源碼分析和大數(shù)據(jù)等多個知識點(diǎn)高級進(jìn)階干貨的直播免費(fèi)學(xué)習(xí)權(quán)限及相關(guān)資料

上面幾個圖就已經(jīng)概述了幾個要點(diǎn),而且,這幾個要點(diǎn)的含義可以說是字如其名!

生產(chǎn)者:發(fā)送消息的程序

消費(fèi)者:監(jiān)聽接收消費(fèi)消息的程序

消息:一串二進(jìn)制數(shù)據(jù)流

隊(duì)列:消息的暫存區(qū)/存儲區(qū)

交換機(jī):消息的中轉(zhuǎn)站,用于接收分發(fā)消息。其中有 fanout、direct、topic、headers 四種

路由:相當(dāng)于密鑰/第三者,與交換機(jī)綁定即可路由消息到指定的隊(duì)列!

正如上圖所展示的消息模型的演變,接下來我們將以代碼的形式實(shí)戰(zhàn)各種典型的業(yè)務(wù)場景!

SpringBoot 整合 RabbitMQ 實(shí)戰(zhàn)

工欲善其事,必先利其器。我們首先需要借助 IDEA 的 Spring Initializr 用 Maven 構(gòu)建一個 SpringBoot 的項(xiàng)目,并引入 RabbitMQ、Mybatis、Log4j 等第三方框架的依賴。搭建完成之后,可以簡單的寫個 RabbitMQController 測試一下項(xiàng)目是否搭建是否成功(可以暫時用單模塊方式構(gòu)建)

緊接著,我們進(jìn)入實(shí)戰(zhàn)的核心階段,在項(xiàng)目或者服務(wù)中使用 RabbitMQ,其實(shí)無非是有幾個核心要點(diǎn)要牢牢把握住,這幾個核心要點(diǎn)在擼碼過程中需要“時刻的游蕩在自己的腦海里”,其中包括:

我要發(fā)送的消息是什么

我應(yīng)該需要創(chuàng)建什么樣的消息模型:DirectExchange+RoutingKey?TopicExchange+RoutingKey?等

我要處理的消息是實(shí)時的還是需要延時/延遲的?

消息的生產(chǎn)者需要在哪里寫,消息的監(jiān)聽消費(fèi)者需要在哪里寫,各自的處理邏輯是啥

基于這樣的幾個要點(diǎn),我們先小試牛刀一番,采用 RabbitMQ 實(shí)戰(zhàn)異步寫日志與異步發(fā)郵件。當(dāng)然啦,在進(jìn)行實(shí)戰(zhàn)前,我們需要安裝好 RabbitMQ 及其后端控制臺應(yīng)用,并在項(xiàng)目中配置一下 RabbitMQ 的相關(guān)參數(shù)以及相關(guān) Bean 組件。

1.RabbitMQ 安裝完成后,打開后端控制臺應(yīng)用:http://localhost:15672/ guest guest 登錄,看到下圖即表示安裝成功

2.然后是項(xiàng)目配置文件層面的配置 application.properties

spring.rabbitmq.host=127.0.0.1spring.rabbitmq.port=5672spring.rabbitmq.username=guestspring.rabbitmq.password=guestspring.rabbitmq.listener.concurrency=10spring.rabbitmq.listener.max-concurrency=20spring.rabbitmq.listener.prefetch=5復(fù)制代碼

其中,后面三個參數(shù)主要是用于“并發(fā)量的配置”,表示:并發(fā)消費(fèi)者的初始化值,并發(fā)消費(fèi)者的最大值,每個消費(fèi)者每次監(jiān)聽時可拉取處理的消息數(shù)量。

接下來,我們需要以 Configuration 的方式配置 RabbitMQ 并以 Bean 的方式顯示注入 RabbitMQ 在發(fā)送接收處理消息時相關(guān) Bean 組件配置其中典型的配置是 RabbitTemplate 以及 SimpleRabbitListenerContainerFactory,前者是充當(dāng)消息的發(fā)送組件,后者是用于管理RabbitMQ監(jiān)聽器 的容器工廠,其代碼如下:

@ConfigurationpublicclassRabbitmqConfig{ privatestaticfinalLogger log= LoggerFactory.getLogger(RabbitmqConfig.class);@Autowiredprivate Environment env;@Autowiredprivate CachingConnectionFactory connectionFactory;@Autowiredprivate SimpleRabbitListenerContainerFactoryConfigurer factoryConfigurer;/**

* 單一消費(fèi)者

* @return

*/@Bean(name ="singleListenerContainer") public SimpleRabbitListenerContainerFactory listenerContainer(){ SimpleRabbitListenerContainerFactoryfactory=newSimpleRabbitListenerContainerFactory();factory.setConnectionFactory(connectionFactory);factory.setMessageConverter(newJackson2JsonMessageConverter());factory.setConcurrentConsumers(1);factory.setMaxConcurrentConsumers(1);factory.setPrefetchCount(1);factory.setTxSize(1);factory.setAcknowledgeMode(AcknowledgeMode.AUTO);returnfactory; }/**

* 多個消費(fèi)者

* @return

*/@Bean(name ="multiListenerContainer") public SimpleRabbitListenerContainerFactory multiListenerContainer(){ SimpleRabbitListenerContainerFactoryfactory=newSimpleRabbitListenerContainerFactory(); factoryConfigurer.configure(factory,connectionFactory);factory.setMessageConverter(newJackson2JsonMessageConverter());factory.setAcknowledgeMode(AcknowledgeMode.NONE);factory.setConcurrentConsumers(env.getProperty("spring.rabbitmq.listener.concurrency",int.class));factory.setMaxConcurrentConsumers(env.getProperty("spring.rabbitmq.listener.max-concurrency",int.class));factory.setPrefetchCount(env.getProperty("spring.rabbitmq.listener.prefetch",int.class));returnfactory; }@Beanpublic RabbitTemplate rabbitTemplate(){ connectionFactory.setPublisherConfirms(true); connectionFactory.setPublisherReturns(true); RabbitTemplate rabbitTemplate =newRabbitTemplate(connectionFactory); rabbitTemplate.setMandatory(true); rabbitTemplate.setConfirmCallback(newRabbitTemplate.ConfirmCallback() {@Overridepublicvoidconfirm(CorrelationData correlationData, boolean ack,Stringcause) { log.info("消息發(fā)送成功:correlationData({}),ack({}),cause({})",correlationData,ack,cause); } }); rabbitTemplate.setReturnCallback(newRabbitTemplate.ReturnCallback() {@OverridepublicvoidreturnedMessage(Message message,intreplyCode,StringreplyText,Stringexchange,StringroutingKey) { log.info("消息丟失:exchange({}),route({}),replyCode({}),replyText({}),message:{}",exchange,routingKey,replyCode,replyText,message); } });returnrabbitTemplate; }}復(fù)制代碼

RabbitMQ 實(shí)戰(zhàn):業(yè)務(wù)模塊解耦以及異步通信

在一些企業(yè)級系統(tǒng)中,我們經(jīng)常可以見到一個執(zhí)行 function 通常是由許多子模塊組成的,這個 function 在執(zhí)行過程中,需要?同步?的將其代碼從頭開始執(zhí)行到尾,即執(zhí)行流程是 module_A -> module_B -> module_C -> module_D,典型的案例可以參見匯編或者 C 語言等面向過程語言開發(fā)的應(yīng)用,現(xiàn)在的一些 JavaWeb 應(yīng)用也存在著這樣的寫法。

而我們知道,這個執(zhí)行流程其實(shí)對于整個 function 來講是有一定的弊端的,主要有兩點(diǎn):

整個 function 的執(zhí)行響應(yīng)時間將很久;

如果某個 module 發(fā)生異常而沒有處理得當(dāng),可能會影響其他 module 甚至整個 function 的執(zhí)行流程與結(jié)果;

整個 function 中代碼可能會很冗長,模塊與模塊之間可能需要進(jìn)行強(qiáng)通信以及數(shù)據(jù)的交互,出現(xiàn)問題時難以定位與維護(hù),甚至?xí)萑?“改一處代碼而動全身”的尷尬境地!

故而,我們需要想辦法進(jìn)行優(yōu)化,我們需要將強(qiáng)關(guān)聯(lián)的業(yè)務(wù)模塊解耦以及某些模塊之間實(shí)行異步通信!下面就以兩個場景來實(shí)戰(zhàn)我們的優(yōu)化措施!

場景一:異步記錄用戶操作日志

對于企業(yè)級應(yīng)用系統(tǒng)或者微服務(wù)應(yīng)用中,我們經(jīng)常需要追溯跟蹤記錄用戶的操作日志,而這部分的業(yè)務(wù)在某種程度上是不應(yīng)該跟主業(yè)務(wù)模塊耦合在一起的,故而我們需要將其單獨(dú)抽出并以異步的方式與主模塊進(jìn)行異步通信交互數(shù)據(jù)。

下面我們就用 RabbitMQ 的 DirectExchange+RoutingKey 消息模型也實(shí)現(xiàn)“用戶登錄成功記錄日志”的場景。如前面所言,我們需要在腦海里回蕩著幾個要點(diǎn):

消息模型:DirectExchange+RoutingKey 消息模型

消息:用戶登錄的實(shí)體信息,包括用戶名,登錄事件,來源的IP,所屬日志模塊等信息

發(fā)送接收:在登錄的 Controller 中實(shí)現(xiàn)發(fā)送,在某個 listener 中實(shí)現(xiàn)接收并將監(jiān)聽消費(fèi)到的消息入數(shù)據(jù)表;實(shí)時發(fā)送接收

首先我們需要在上面的 RabbitmqConfig 類中創(chuàng)建消息模型:包括 Queue、Exchange、RoutingKey 等的建立,代碼如下:

上圖中 env 獲取的信息,我們需要在 application.properties 進(jìn)行配置,其中 mq.env=local:

此時,我們將整個項(xiàng)目/服務(wù)跑起來,并打開 RabbitMQ 后端控制臺應(yīng)用,即可看到隊(duì)列以及交換機(jī)及其綁定已經(jīng)建立好了,如下所示:

接下來,我們需要在 Controller 中執(zhí)行用戶登錄邏輯,記錄用戶登錄日志,查詢獲取用戶角色視野資源信息等,由于篇幅關(guān)系,在這里我們重點(diǎn)要實(shí)現(xiàn)的是用MQ實(shí)現(xiàn) “異步記錄用戶登錄日志” 的邏輯,即在這里 Controller 將充當(dāng)“生產(chǎn)者”的角色,核心代碼如下:

@RestControllerpublicclassUserController{privatestaticfinalLogger log= LoggerFactory.getLogger(HelloWorldController.class);privatestaticfinalString Prefix="user";@AutowiredprivateObjectMapper objectMapper;@AutowiredprivateUserMapper userMapper;@AutowiredprivateUserLogMapper userLogMapper;@AutowiredprivateRabbitTemplate rabbitTemplate;@AutowiredprivateEnvironment env;@RequestMapping(value = Prefix+"/login",method = RequestMethod.POST,consumes = MediaType.MULTIPART_FORM_DATA_VALUE)publicBaseResponse login(@RequestParam("userName") String userName,@RequestParam("password") String password){ BaseResponse response=newBaseResponse(StatusCode.Success);try{//TODO:執(zhí)行登錄邏輯User user=userMapper.selectByUserNamePassword(userName,password);if(user!=null){//TODO:異步寫用戶日志try{ UserLog userLog=newUserLog(userName,"Login","login",objectMapper.writeValueAsString(user)); userLog.setCreateTime(newDate()); rabbitTemplate.setMessageConverter(newJackson2JsonMessageConverter()); rabbitTemplate.setExchange(env.getProperty("log.user.exchange.name")); rabbitTemplate.setRoutingKey(env.getProperty("log.user.routing.key.name")); Message message=MessageBuilder.withBody(objectMapper.writeValueAsBytes(userLog)).setDeliveryMode(MessageDeliveryMode.PERSISTENT).build(); message.getMessageProperties().setHeader(AbstractJavaTypeMapper.DEFAULT_CONTENT_CLASSID_FIELD_NAME, MessageProperties.CONTENT_TYPE_JSON);? rabbitTemplate.convertAndSend(message);? }catch(Exception e){ e.printStackTrace(); }//TODO:塞權(quán)限數(shù)據(jù)-資源數(shù)據(jù)-視野數(shù)據(jù)}else{ response=newBaseResponse(StatusCode.Fail); } }catch(Exception e){ e.printStackTrace(); }returnresponse; }}復(fù)制代碼

在上面的“發(fā)送邏輯”代碼中,其實(shí)也體現(xiàn)了我們最開始介紹的演進(jìn)中的幾種消息模型,比如我們是將消息發(fā)送到 Exchange 的而不是 Queue,消息是以二進(jìn)制流的形式進(jìn)行傳輸?shù)鹊取.?dāng)用 postman 請求到這個 controller 的方法時,我們可以在 RabbitMQ 的后端控制臺應(yīng)用看到一條未確認(rèn)的消息,通過 GetMessage 即可看到其中的詳情,如下:

最后,我們將開發(fā)消費(fèi)端的業(yè)務(wù)代碼,如下:

@ComponentpublicclassCommonMqListener{privatestaticfinalLogger log= LoggerFactory.getLogger(CommonMqListener.class);@AutowiredprivateObjectMapper objectMapper;@AutowiredprivateUserLogMapper userLogMapper;@AutowiredprivateMailService mailService;/** * 監(jiān)聽消費(fèi)用戶日志 *@parammessage */@RabbitListener(queues ="${log.user.queue.name}",containerFactory ="singleListenerContainer")publicvoidconsumeUserLogQueue(@Payloadbyte[] message){try{ UserLog userLog=objectMapper.readValue(message, UserLog.class); log.info("監(jiān)聽消費(fèi)用戶日志 監(jiān)聽到消息: {} ",userLog);//TODO:記錄日志入數(shù)據(jù)表userLogMapper.insertSelective(userLog); }catch(Exception e){ e.printStackTrace(); } }復(fù)制代碼

將服務(wù)跑起來之后,我們即可監(jiān)聽消費(fèi)到上面 Queue 中的消息,即當(dāng)前用戶登錄的信息,而且,我們也可以看到“記錄用戶登錄日志”的邏輯是由一條異于主業(yè)務(wù)線程的異步線程去執(zhí)行的:

“異步記錄用戶操作日志”的案例我想足以用于詮釋上面所講的相關(guān)理論知識點(diǎn)了,在后續(xù)篇章中,由于篇幅限制,我將重點(diǎn)介紹其核心的業(yè)務(wù)邏輯!

場景二:異步發(fā)送郵件

發(fā)送郵件的場景,其實(shí)也是比較常見的,比如用戶注冊需要郵箱驗(yàn)證,用戶異地登錄發(fā)送郵件通知等等,在這里我以 RabbitMQ 實(shí)現(xiàn)異步發(fā)送郵件。實(shí)現(xiàn)的步驟跟場景一幾乎一致!

1. 消息模型的創(chuàng)建

2. 配置信息的創(chuàng)建

3. 生產(chǎn)端

4. 消費(fèi)端

彩蛋:本博文就先介紹RabbitMQ實(shí)戰(zhàn)的典型業(yè)務(wù)場景之業(yè)務(wù)服務(wù)模塊異步解耦與通信吧,下篇博文將繼續(xù)講解RabbitMQ實(shí)戰(zhàn)在高并發(fā)系統(tǒng)的場景的應(yīng)用記憶消息確認(rèn)機(jī)制跟并發(fā)量的配置實(shí)戰(zhàn),相關(guān)源碼數(shù)據(jù)庫可以來這里下載

https://pan.baidu.com/s/1KUuz_eeFXOKF3XRMY2Jcew

最后送波福利。現(xiàn)在加群即可獲取Java工程化、高性能及分布式、高性能、高架構(gòu)。性能調(diào)優(yōu)、Spring,MyBatis,Netty源碼分析和大數(shù)據(jù)等多個知識點(diǎn)高級進(jìn)階干貨的直播免費(fèi)學(xué)習(xí)權(quán)限及相關(guān)資料,群號:835638062 點(diǎn)擊鏈接加入群聊【Java高級架構(gòu)學(xué)習(xí)交流】:https://jq.qq.com/?_wv=1027&k=5S3kL3v

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內(nèi)容