在分散式架構的系統中,會有多個應用程式在運行各自的服務。而程式間會有傳遞資料的行為,也就是「通訊」。訊息佇列讓我們在請另一方的服務處理任務時,能以非同步的方式進行。且這些任務是需要「排隊」的,因此在面臨高流量時,不至於讓 server 承擔巨大壓力。
本文將介紹什麼是訊息佇列,以及它帶來的好處。接著在 Spring Boot 連接知名的 RabbitMQ。而後續的文章會實作引進訊息佇列時,在架構上可採用的 6 種設計模式。
此篇亦轉載到個人部落格。
筆者參考了 RabbitMQ 的官方教學,來比喻訊息佇列(Messages Queue,MQ)。
我們可以把 MQ 想像成現實生活中的「郵局」,寄件人會把信帶去郵局,讓郵局保管。而郵局再把一堆信分送給大家。這個情境中有三個角色:寄件人、郵局與收件人。如果寄件人親自將每封信送到收件人手上,將會是一件成本很高的事,所以透過郵局這個「中介」來幫忙。
MQ 是各個服務之間通訊時,所傳遞資料的暫存之處。所謂的「服務之間」,讀者可以想像成:有個大產品,它跑了 1 個 Spring Boot、2 個 Flask、1 個 Node.js,它們都有各自的職責,也會傳遞資料,組成一個系統。
與上述「分散式架構」相對的,是「單體式架構」。例如整個產品只有 1 個 Spring Boot 在跑,它囊括全部的產品功能。
請以單體式架構為基礎,假想一個情境:社群平台上的文章被留言,系統會發送「站內通知」與「Email 通知」給作者,以及追蹤該文章的人。然而要通知的對象一多,將會導致每次留言,其 web API 的回應時間被拖長。
雖說可透過非同步的方式(開新的執行緒)去進行通知的工作,讓 API 早點回應,但工作終究還是集中在同一臺 server 上進行。若短時間要發送通知給很多人,對 server 是一大負擔。
針對這種情況,初步的想法可能是將通知的程式,切分到另一臺 server。留言的程式執行完後,以非同步方式,呼叫新 server 處理通知。雖然把壓力轉嫁到新 server,然而當通知的請求一多,該 server 能否承受還是個未知數。
在分散式架構中引進 MQ 的目的,是為了避免大量的請求湧進 server,使其壓力太大,甚至導致程式中止。MQ 身為服務之間的中介,可以將資料暫存於身上。等到 server 有餘裕,再拿取新的資料來處理。此時 MQ 起到了「緩衝」的效果。
再者,若這些新服務不幸掛掉了,由於未處理的資料還暫存在 MQ 裡面,因此只要重新啟動,就能繼續消化資料。所以引進 MQ 能提高系統的容錯能力。
以下舉例 3 個可以使用 MQ 的場景,取材自參考資料。
第一個例子是前面提到的發送通知,在此繼續補充。通常是使用者做了什麼事,才引發後續的通知。舉例來說,我們在銀行 App 登入、轉帳、買外幣、申請定存等,都會收到通知。或者在購物網站下單、商品到貨、買賣方傳送訊息也是如此。通知的種類可包括站內、Email、App 推播,甚至是手機簡訊或 Line 等等。
進行程式開發時,通知訊息晚個幾秒鐘、幾分鐘送到,有時是可以被使用者或開發團隊所接受的。那麼在程式碼中,我們就不必拘泥於一定要等發送通知的程式做完,才能執行下一行 code。採非同步方式也未嘗不可。
假想在系統中,文章資料有支援「關鍵字搜尋」,那麼當文章內容更新後,理應也要同步到全文檢索引擎(如 Elasticsearch)中。與第一個例子同理,有時新內容的關鍵字晚一點才生效,是可以被接受的。因此也能設計為非同步的工作。
筆者之前使用銀行 App,有個功能是匯出電子存摺封面及近三個月的交易紀錄,會寄到 Email 信箱。如果這個功能沒有設計成非同步工作,那麼當匯出所需時間較長,恐怕使用者就得一直看著等待畫面了!
上面的例子強調可採取「非同步」。至於為何要特地切分出專門做通知、同步與匯出服務的 server,並在服務之間加入 MQ 呢?切分出新 server 是為了分擔原 server 的壓力。而加入 MQ 是提供緩衝,讓資料暫時在緩衝區排隊,避免新 server 短時間面對太多請求會承受不了。
筆者透過 Docker 安裝 RabbitMQ,版本為 3.12.4。其他版本請參考它的 Docker Hub。
docker pull rabbitmq:3.12.4-management
其中 tag 的「management」,代表它包含了 GUI 管理介面。而以下是啟動 container 的指令。
docker run -d -p 5672:5672 -p 15672:15672 --name DemoRabbitMQ -e RABBITMQ_DEFAULT_USER=user -e RABBITMQ_DEFAULT_PASS=123456 4c62a24d32fc
啟動 RabbitMQ 的 container 後,前往 http://localhost:15672
,登入後即可看到管理介面。
本節要在 Spring Boot 專案連接上剛剛啟動的 RabbitMQ 服務。
讓我們在 Spring Boot 專案中引進 RabbitMQ。本文使用的版本如下。
請在 pom.xml 檔案添加 RabbitMQ 的依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
請在 application.properties 檔案,添加連線設定。
# RabbitMQ 的地址
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
# RabbitMQ 服務的帳密
spring.rabbitmq.username=user
spring.rabbitmq.password=123456
添加好後,我們就要接觸 RabbitMQ 了。首先要建立一個 queue,用來存放資料才行。有兩種做法能建立 queue,讀者擇一即可。
第一種做法是在程式中,使用 @Bean
方法建立 Queue
的元件。
import org.springframework.amqp.core.Queue;
@Configuration
public class RabbitConfig {
@Bean
public Queue commentNotificationQueue() {
return new Queue("Comment Notification Queue");
}
}
它的建構子可傳入 queue 的名稱。為因應後續文章的實作範例,此處筆者取名為「Comment Notification Queue
」。
在啟動 Spring Boot 後,即可在 RabbitMQ 管理介面的「Queue and Streams」頁籤,看到該名稱的 queue,同時也代表連接成功!
第二種做法是在管理介面的在「Add a new queue」區塊建立。
僅僅在管理介面手動建立 Queue,並不代表 Spring Boot 有成功連結上 RabbitMQ 喔!
本文介紹了 MQ 的概念,並安裝 RabbitMQ。從明天的文章開始,會實作「將資料傳送至 MQ,與從 MQ 取得資料做處理」的過程,共 6 種設計模式。
RabbitMQ 系列的完成後專案,會在鐵人賽結束後上傳到 Github,並轉貼到這裡。
今日文章到此結束!
最後推廣一下自己的部落格,我是「新手工程師的程式教室」的作者,請多指教