# Message Queue訊息佇列 ## 簡介 Message Queue是中介管道,兩個系統之間溝通的訊息管道,可以讓訊息緩衝、依序地被處理。 我們可以把 MQ 想像成現實生活中的「郵局」,寄件人會把信帶去郵局,讓郵局保管。 而郵局再把一堆信分送給大家。這個情境中有三個角色:寄件人、郵局與收件人。 如果寄件人親自將每封信送到收件人手上,將會是一件成本很高的事,所以透過郵局這個「中介」來幫忙。 ### MQ的種類 Apache底下有ActiveMQ與ActiveMQ Artemis,可將Artemis視為ActiveMQ的進階版,有更多功能。 其他還有:RabbitMQ...等 ### 名詞介紹 Producer生產者:將資料放進Queue Consumer消費者:處理Queue裡的資料 ### 使用MQ的好處 1. 解耦 2. 分散壓力 例如同時間一萬筆資料湧進伺服器,當機可能資料就遺失。 但使用Queue的好處則是,資料會留在Queue中,等到要被處理時才取走。 3. 非即時需要處理的請求可以被延後處理 例如:到貨簡訊、匯出資料後要寄到電子信箱...等 ### 使用MQ會有的幾種設計模式 1. 簡單Simple模式 **一個 producer、一個 MQ,跟一個 consumer 而已。** 程式實作中,會有 4 個東西要準備,分別是:queue、訊息 model、producer 與 consumer。 2. Worker 模式 **一個 producer、一個 MQ,跟多個 consumer。** 水平擴展,增加更多消費者處理訊息。 #### Exchange交換機 交換機能夠消除 producer 與 queue 之間的依賴。將 exchange 加入 MQ 的架構後,producer 只要傳送訊息到 exchange,它就會幫忙轉發到 queue。 而 queue 必須事先與 exchange 綁定好。官方教學的架構圖如下。 ![20131107cnC5ZKtife](https://hackmd.io/_uploads/S1XtHGuDp.png) 以生活情境來比喻。學校有重要事項傳遞給同學們,若召集所有相關的同學到現場,會很麻煩。 於是校方選擇將公告送至各科系的辦公室。而系辦再依據公告內容的適用對象,放到「班級櫃」。班級幹部有空會來領取公告,並協助執行。 從這個情境來看,校方是 producer,而「召集相關的同學」相當於寫出好幾行發送訊息到 queue 的程式。 至於系辦則是 exchange,會負責轉發訊息,看是要給特定班級、年級,還是整個科系都要通知。 而存放公告的班級櫃是 queue;拿取公告的班級幹部是 consumer。 3. Fanout發散/廣播 模式 將訊息發送給所有與該 exchange 綁定的 queue 4. Direct 模式 在 Direct 模式,producer 會在發送訊息給 exchange 時,指定一個代表 queue 的 key。該 key 會在 queue 一開始綁定到 exchange 時設定好。 以生活情境來比喻,就像向郵局申請的「郵政信箱」。如果自己或家人有服兵役,可能會有點印象,寄信到所屬單位時,收件地址會寫「XX郵政幾號」,例如「桃園龍潭郵政91004號」。而這個郵政信箱的代號,就相當於 queue 的 key。 這麼做的好處是,當 queue 的名稱改變了,producer 的程式並不需要做調整,因為它是對著 key 發送訊息。 5. Routing Routing 模式簡單來說,就是 queue 在綁定時,會告訴 exchange 自己想要接收什麼種類的訊息。這個「種類」,我們稱之為「routing key」。而 producer 在傳送訊息時,也會一併附帶 routing key,讓 exchange 知道要轉發給哪些 queue。 以下是官方教學的架構圖。 ![201311071RTde4FdJG](https://hackmd.io/_uploads/SJsLdG_Da.png) 6. Topic 前面的 Routing 模式,是要求 routing key 完全符合,exchange 才會將訊息轉發到對應的 queue。而本節的 Topic 模式,則是放寬成「模糊比對」。 也就是說,queue 在綁定到 exchange 時所提供的 routing key,將是一個類似正規表示式的字串。只要 producer 送出訊息時,其攜帶的 routine key 有符合該表示式的格式(pattern),exchange 便會轉發給所有符合的 queue。 ![20131107lFEypYeGch](https://hackmd.io/_uploads/Sy4xjMOwa.png) ## Apache ActiveMQ使用方式: 大致可分成:啟動MQ Server→ 建立Queue → 準備訊息Model → producer放入訊息 → consumer處理訊息 ### 啟動MQ Server 1. [官網下載](https://activemq.apache.org/) 注意一下版本 目前我使用此版本ActiveMQ 5.18.2 (Jul 2nd, 2023),需要搭配的是JAVA 11的版本 2. 啟動 可以使用裡面的installservice,5.18新版本裡面自帶wrapper去包成windows服務 使用CMD指令: ``` cd [activemq_install_dir] 例如: CD C:\Java\ActiveMQ\apache-activemq-6.0.1 執行:bin\activemq start ``` 啟動後使用[URL](http://127.0.0.1:8161) 看到此畫面即表示activemq已啟動 Login: admin Passwort: admin 即成功啟動activemq 若要關閉 ``` cd [activemq_install_dir]/bin ./activemq stop ``` ### 專案準備 使用spirngboot專案,引入active MQ依賴 ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-activemq</artifactId> </dependency> ``` 1. 建立Producer ```java= @Component public class Producer { @Autowired private JmsMessagingTemplate jmsTemplate; public void sendMessage(Destination des,String message) { jmsTemplate.convertAndSend(des,message); } } ``` 2. 建立Consumer ```java= @Component public class Consumer { @JmsListener(destination = "myQueues") public void receiveMsg(String text) { System.out.println(text + "......."); } } ``` 3. 建立Controller ```java= @Controller public class TestController { @Autowired private Producer producer; @RequestMapping("/activemq") @ResponseBody public String tests() { Destination des = new ActiveMQQueue("myQueues"); for (int i = 0; i <= 3; i++) { producer.sendMessage(des, "TestMsg"); } return "OK"; } } ``` ## apache-artemis使用方式: 建立broker 我使用的版本為apache-artemis-2.32.0,環境變數使用JDK17 使用cmd cd到artemis的bin資料夾: `artemis create "broker的名字"` ![建立broker](https://hackmd.io/_uploads/rJ8RPMJTa.png) 執行該broker cd到broker資料夾中的bin,輸入 `artemis run` 即可啟動。 若要部屬成windows服務,使用系統管理員身分打開cmd輸入: `cd C:\Java\ActiveMQ\apache-artemis-2.32.0\bin\"Broker名稱"""\bin` `artemis-service install` 即完成將apache-artemis安裝成windows服務,若要刪除服務則是輸入: `artemis-service uninstall` ### 專案準備 使用springboot專案 POM引入 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-artemis</artifactId> </dependency> ``` 建立發送訊息的起點 ```java= import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.core.JmsTemplate; import org.springframework.stereotype.Service; @Service public class MyMessagingService { @Autowired private JmsTemplate jmsTemplate; public void sendMessage(String destination, String message) { jmsTemplate.convertAndSend(destination, message); } } ``` 繼承CommandLineRunner去讓springboot專案啟動時帶起來的應用 ```java= import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class MyRunner implements CommandLineRunner { private static final Logger logger = LoggerFactory.getLogger(MyRunner.class); @Autowired private MyMessagingService messagingService; @Override public void run(String... args) throws Exception { logger.info("正在MyRunner中,執行發送mesage到test.queue中"); // 發送消息到名為 "test.queue" 的佇列 messagingService.sendMessage("test.queue", "Hello, Artemis!"); } } ``` 這樣每次啟動時就會發送訊息到queue中 ![寫入queue後](https://hackmd.io/_uploads/rJ1cDvZCT.png) 接著需要建立listenser ```java= import jakarta.jms.Message; import jakarta.jms.TextMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jms.annotation.JmsListener; import org.springframework.stereotype.Component; @Component public class MyMessagingListener { private static final Logger logger = LoggerFactory.getLogger(MyMessagingListener.class); @JmsListener(destination = "test.queue",concurrency = "10") public void onMessagingRecevied(Message message)throws Exception{ // 這邊的message是JMS message物件,需要透過getTEXT()轉換成String再印出。 if (message instanceof TextMessage){ String text = ((TextMessage) message).getText(); logger.info("message= " + text); } else { System.out.println("Received message of unsupported type: " + message); } } } ``` 最後輸出結果: ![有發送訊息與收到處理訊息](https://hackmd.io/_uploads/rJEbOv-Ra.png) ## Rabbit MQ 還沒用過!!! ## 參考資料: Artemis https://www.cnblogs.com/wuyongyin/p/15043113.html https://blog.csdn.net/weixin_42799222/article/details/124702162 https://www.cnblogs.com/wuyongyin/p/15043113.html https://ithelp.ithome.com.tw/articles/10333620 https://morosedog.gitlab.io/springboot-20190426-springboot37/