# 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 綁定好。官方教學的架構圖如下。

以生活情境來比喻。學校有重要事項傳遞給同學們,若召集所有相關的同學到現場,會很麻煩。
於是校方選擇將公告送至各科系的辦公室。而系辦再依據公告內容的適用對象,放到「班級櫃」。班級幹部有空會來領取公告,並協助執行。
從這個情境來看,校方是 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。
以下是官方教學的架構圖。

6. Topic
前面的 Routing 模式,是要求 routing key 完全符合,exchange 才會將訊息轉發到對應的 queue。而本節的 Topic 模式,則是放寬成「模糊比對」。
也就是說,queue 在綁定到 exchange 時所提供的 routing key,將是一個類似正規表示式的字串。只要 producer 送出訊息時,其攜帶的 routine key 有符合該表示式的格式(pattern),exchange 便會轉發給所有符合的 queue。

## 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
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中

接著需要建立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);
}
}
}
```
最後輸出結果:

## 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/