在微服務架構中,服務間的訊息傳遞至關重要。相較於直接使用 HTTP POST 請求,引入 RabbitMQ 作為訊息代理,能有效降低服務間耦合度,提升系統容錯能力。本文將逐步說明如何整合 RabbitMQ,並以 Node.js 和 Docker Compose 為例,展示實際應用場景和程式碼範例。首先,我們會分析 HTTP POST 請求在微服務溝通上的限制,接著介紹 RabbitMQ 的優勢,並提供具體的整合步驟和程式碼片段,最後透過圖表清楚呈現訊息在 RabbitMQ 中的傳遞流程,讓讀者更輕鬆理解如何運用 RabbitMQ 進行間接訊息傳遞,並在微服務架構中發揮其最大效益。
HTTP POST 請求與直接訊息傳遞
在微服務架構中,服務之間的溝通是一個重要的議題。HTTP POST 請求是一種常見的方法,用於在微服務之間傳遞訊息。在本文中,我們將探討如何使用 HTTP POST 請求直接傳遞訊息給目標微服務。
直接訊息傳遞
當我們想要將訊息傳遞給特定的微服務時,我們需要使用 HTTP POST 請求。這個請求需要包含目標微服務的主機名稱(hostname),以便 DNS 可以將其解析為 IP 地址。如圖 5.8 所示,HTTP POST 請求可以直接傳遞給目標微服務。
DNS 查詢
當我們傳送 HTTP 請求時,DNS 會自動進行查詢,以將主機名稱解析為 IP 地址。這個過程是自動且透明的,讓我們可以使用主機名稱而不是 IP 地址來傳遞訊息。
傳送 HTTP POST 請求
要傳送 HTTP POST 請求,我們需要使用適合的程式函式庫或框架。在 Node.js 中,我們可以使用內建的 HTTP 程式函式庫來傳送 HTTP 請求。以下是範例程式碼,示範如何傳送 HTTP POST 請求:
function sendViewedMessage(videoPath) {
const postOptions = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
};
const requestBody = {
videoPath: videoPath,
};
const req = http.request(
// 目標微服務的 URL
"http://history-microservice/viewed",
postOptions,
(res) => {
// 處理回應
}
);
req.write(JSON.stringify(requestBody));
req.end();
}
在這個範例中,我們定義了一個 sendViewedMessage
函式,該函式傳送一個 HTTP POST 請求給目標微服務。請求體包含了 videoPath
引數,該引數表示使用者正在觀看的影片路徑。
HTTP POST 請求的運用
在微服務架構中,HTTP POST 請求是一種常見的用於傳送訊息的方法。以下是使用 Node.js 的 http
模組來傳送 HTTP POST 請求的範例。
傳送 HTTP POST 請求
首先,我們需要組態 HTTP 請求的選項,包括主機名稱、路由、HTTP 方法和內容型別等。然後,我們可以使用 http.request
函式來傳送 HTTP POST 請求。
const http = require('http');
const options = {
hostname: 'history',
port: 3000,
path: '/viewed',
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
};
const requestBody = {
// 訊息內容
};
const req = http.request(options, (res) => {
// 處理回應
});
req.on('close', () => {
// 處理連線關閉
});
req.on('error', (err) => {
// 處理錯誤
});
req.write(JSON.stringify(requestBody));
req.end();
接收 HTTP POST 訊息
在另一端,微服務可以使用 HTTP 伺服器來接收 HTTP POST 訊息。以下是使用 Node.js 的 http
模組來接收 HTTP POST 訊息的範例。
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/viewed') {
// 處理 HTTP POST 訊息
let body = '';
req.on('data', (chunk) => {
body += chunk;
});
req.on('end', () => {
const message = JSON.parse(body);
// 處理訊息內容
});
} else {
res.statusCode = 404;
res.end();
}
});
server.listen(3000, () => {
console.log('伺服器啟動於 port 3000');
});
處理 HTTP POST 請求的 Direct Messaging
在本文中,我們將探討如何使用 HTTP POST 路由接收傳入的訊息。以下是相關程式碼:
app.post("/viewed", async (req, res) => {
const videoPath = req.body.videoPath;
await historyCollection.insertOne({
videoPath: videoPath
});
res.sendStatus(200);
});
這段程式碼定義了一個 HTTP POST 路由,當收到請求時,會將請求體中的 videoPath
欄位存入資料函式庫中。
處理請求體
在上述程式碼中,我們使用 req.body
來存取請求體中的資料。由於我們使用了 JSON 解析中介軟體,因此可以直接存取請求體中的 JSON 資料。
測試更新的應用程式
現在,我們可以測試更新的應用程式了。首先,開啟終端機,切換到 example-2
目錄,並啟動應用程式:
cd chapter-5/example-2
docker compose up --build
如果您遇到任何錯誤,可能是因為之前的容器尚未關閉。您可以使用 docker compose down
來關閉容器。
驗證訊息儲存
啟動應用程式後,開啟瀏覽器並導航到 http://localhost:4001/video
。然後,切換回終端機並檢視 Docker Compose 的輸出。您應該看到輸出確認視訊串流微服務已發送了一個「Viewed」訊息。
檢查資料函式庫
最後,我們可以直接檢查資料函式庫以確保「Viewed」訊息已儲存。您可以使用 MongoDB Shell 或 Studio 3T 來檢視資料函式庫中的資料。
內容解密:
上述程式碼使用 app.post()
來定義一個 HTTP POST 路由。當收到請求時,會將請求體中的 videoPath
欄位存入資料函式庫中。然後,會傳送一個 200 狀態碼回應給使用者端。
圖表翻譯:
以下是上述程式碼的 Mermaid 圖表:
flowchart TD A[收到 HTTP POST 請求] --> B[提取請求體中的 videoPath] B --> C[存入資料函式庫] C --> D[傳送 200 狀態碼回應]
這個圖表展示了收到 HTTP POST 請求後的處理流程。首先,提取請求體中的 videoPath
欄位,然後存入資料函式庫中。最後,傳送一個 200 狀態碼回應給使用者端。
微服務之間的溝通:直接訊息的力量
在微服務架構中,各個服務之間的溝通是非常重要的。直接訊息(Direct Messages)是一種讓微服務之間可以直接溝通的方式,允許一個控制器微服務可以協調多個其他微服務的行為。
直接訊息的優點
直接訊息的優點在於它可以讓微服務之間的溝通更加簡單和直接。當一個微服務需要與另一個微服務溝通時,它可以直接傳送訊息給對方,然後等待回應。這種方式可以讓微服務之間的溝通更加同步和可控。
直接訊息的應用場景
直接訊息可以用於協調多個微服務的行為。例如,在一個電子商務系統中,當使用者下單時,系統需要通知庫存微服務、物流微服務和支付微服務等。這時候,可以使用直接訊息讓訂單微服務直接通知其他微服務,從而實作整個系統的協調。
直接訊息的實作
要實作直接訊息,需要有一個訊息佇列(Message Queue)或是一個代理伺服器(Broker),負責接收和轉發訊息。各個微服務可以透過這個佇列或代理伺服器傳送和接收訊息。
範例程式碼
以下是使用 Node.js 和 RabbitMQ 實作直接訊息的範例程式碼:
// 訊息傳送者
const amqp = require('amqplib');
async function sendMessage() {
const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();
const queueName = 'my_queue';
await channel.assertQueue(queueName, { durable: true });
const message = { type: 'order_created', data: { orderId: 123 } };
channel.sendToQueue(queueName, Buffer.from(JSON.stringify(message)));
console.log('訊息已傳送');
}
// 訊息接收者
async function receiveMessage() {
const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();
const queueName = 'my_queue';
await channel.assertQueue(queueName, { durable: true });
channel.consume(queueName, (msg) => {
if (msg!== null) {
const message = JSON.parse(msg.content.toString());
console.log(`接收到訊息:${message.type}`);
// 處理訊息
}
});
}
圖表翻譯:
sequenceDiagram participant 微服務A participant 微服務B participant 微服務C Note over 微服務A,微服務B: 直接訊息 微服務A->>微服務B: 訊息1 微服務B->>微服務C: 訊息2 微服務C->>微服務A: 回應
在這個圖表中,微服務A 是控制器微服務,它直接傳送訊息給微服務B 和微服務C。微服務B 和微服務C 分別處理訊息並回應給微服務A。這個圖表展示了直接訊息如何用於協調多個微服務的行為。
微服務間的間接訊息傳遞:使用 RabbitMQ
在上一節中,我們探討瞭如何使用 HTTP POST 請求進行直接訊息傳遞。雖然這種方法可以實作微服務間的溝通,但它也有一些限制。例如,直接訊息傳遞需要知道接收訊息的微服務的具體地址,而且只有一個微服務可以接收到訊息。此外,直接訊息傳遞也會導致微服務間的耦合度增加。
為瞭解決這些問題,我們可以使用間接訊息傳遞。間接訊息傳遞使用了一個中間的訊息代理(message broker)來接收和轉發訊息。這樣,傳送訊息的微服務不需要知道接收訊息的微服務的具體地址,從而減少了微服務間的耦合度。
RabbitMQ 介紹
RabbitMQ 是一個流行的訊息代理軟體。它支援多種程式語言,包括 Node.js、Java、Python 等。RabbitMQ 的核心功能是提供了一個中間的訊息代理,允許微服務之間進行間接訊息傳遞。
RabbitMQ 的優點
RabbitMQ 有以下優點:
- 解耦: RabbitMQ 可以解耦微服務之間的依賴關係,從而提高系統的可擴充套件性和可靠性。
- 彈性: RabbitMQ 可以處理大量的訊息,並且可以自動調整訊息處理的速度。
- 安全: RabbitMQ 支援多種安全機制,包括 SSL/TLS 加密和認證。
- 高用性: RabbitMQ 支援叢集佈署,從而可以提高系統的可用性。
使用 RabbitMQ 進行間接訊息傳遞
要使用 RabbitMQ 進行間接訊息傳遞,需要以下步驟:
- 安裝 RabbitMQ: 需要在伺服器上安裝 RabbitMQ。
- 建立 RabbitMQ 連線: 需要在微服務中建立 RabbitMQ 連線。
- 傳送訊息: 需要在微服務中傳送訊息到 RabbitMQ。
- 接收訊息: 需要在微服務中接收 RabbitMQ 的訊息。
以下是使用 Node.js 和 amqplib 進行間接訊息傳遞的範例:
// 傳送訊息
const amqp = require('amqplib');
async function sendMessage() {
const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();
const queueName = 'my_queue';
await channel.assertQueue(queueName, { durable: true });
const message = 'Hello, world!';
channel.sendToQueue(queueName, Buffer.from(message));
console.log('Message sent!');
}
// 接收訊息
async function receiveMessage() {
const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();
const queueName = 'my_queue';
await channel.assertQueue(queueName, { durable: true });
channel.consume(queueName, (msg) => {
if (msg!== null) {
console.log(`Received message: ${msg.content.toString()}`);
channel.ack(msg);
}
});
}
圖表翻譯:
graph LR A[Microservice A] -->| Send message | B[RabbitMQ] B -->| Route message | C[Microservice B] C -->| Process message | D[Database]
在這個圖表中,Microservice A 發送了一個訊息到 RabbitMQ,RabbitMQ 然後將這個訊息路由到 Microservice B,Microservice B 處理這個訊息並將結果儲存到 Database 中。這個過程展示瞭如何使用 RabbitMQ 進行間接訊息傳遞。
5.8.2 不直接針對微服務的訊息傳遞
在間接訊息傳遞中,我們不會直接針對特定的微服務,但我們仍需要將訊息導向某個目標:RabbitMQ 伺服器。在 RabbitMQ 伺服器中,我們將訊息導向具名佇列(named queue)或訊息交換器(message exchange)。佇列和交換器的組合提供了許多彈性,以便我們結構化訊息架構。
注意:訊息傳送者使用 DNS 解析 RabbitMQ 伺服器的 IP 位址。然後,訊息傳送者與 RabbitMQ 進行溝通,以在特定的具名佇列或交換器上發布訊息。接收者也使用 DNS 來定位並與 RabbitMQ 伺服器進行溝通,以從佇列中檢索訊息。在任何時候,傳送者和接收者都不會直接進行溝通。
開發電腦
Docker 執行環境
RabbitMQ 視訊串流 視訊儲存
歷史 視訊串流微服務發布 「已觀看」訊息到「已觀看」佇列。
應用程式包含單一 RabbitMQ 伺服器例項。
…
RabbitMQ 伺服器包含 多個具有不同名稱的佇列。
歷史微服務從 「已觀看」佇列中提取「已觀看」訊息。 圖 5.10 使用 RabbitMQ 透過訊息佇列間接將訊息傳遞給其他微服務
5.8 使用 RabbitMQ 進行間接訊息傳遞 要發布訊息到佇列或交換器,我們必須先將 RabbitMQ 伺服器增加到應用程式中。然後,我們可以使用 AMQP 程式函式庫(稱為 amqplib)來傳送和接收訊息。 在底層,DNS 將 RabbitMQ 主機名稱解析為 IP 位址。現在,與其直接將訊息導向特定的微服務(如直接傳送時所做的那樣),我們改為將訊息導向 RabbitMQ 伺服器。這樣,訊息就可以被發布到具名佇列或交換器上,從而實作間接訊息傳遞。
內容解密:
在這個過程中,RabbitMQ 伺服器扮演了訊息代理人的角色,允許微服務之間進行間接的訊息傳遞。這種方法提供了更大的彈性和可擴充套件性,因為微服務不需要直接知道彼此的存在或位置。相反,它們只需要知道如何與 RabbitMQ 伺服器進行溝通,即可將訊息發布到適當的佇列或交換器上。
flowchart TD A[視訊串流微服務] -->|發布「已觀看」訊息|> B[RabbitMQ 伺服器] B -->|將訊息加入「已觀看」佇列|> C[「已觀看」佇列] D[歷史微服務] -->|提取「已觀看」訊息|> C
圖表翻譯:
這個流程圖顯示了視訊串流微服務如何將「已觀看」訊息發布到 RabbitMQ 伺服器上的「已觀看」佇列中。歷史微服務可以從同一佇列中提取這些訊息,實作間接的訊息傳遞。這種架構允許微服務之間進行解耦合的溝通,提高了系統的可擴充套件性和可維護性。
使用 RabbitMQ 進行間接訊息傳遞
在微服務架構中,間接訊息傳遞是一種常見的溝通方式。與直接使用 HTTP POST 請求不同,間接訊息傳遞允許微服務之間進行非同步通訊。這種方法可以提高系統的可擴充套件性和容錯性。
使用佇列進行間接訊息傳遞
佇列(Queue)是一種先進先出的資料結構,常用於訊息傳遞。當一個微服務想要傳送訊息給另一個微服務時,它可以將訊息推播到佇列中。然後,接收微服務可以從佇列中提取訊息。
以下是使用佇列進行間接訊息傳遞的步驟:
- 傳送訊息:傳送微服務使用 amqplib API 將訊息推播到 RabbitMQ 伺服器的佇列中。
- 提取訊息:接收微服務使用 amqplib 程式碼函式庫從 RabbitMQ 伺服器的佇列中提取訊息。
使用交換器進行間接訊息傳遞
交換器(Exchange)是一種更高階的訊息路由機制。它允許微服務將訊息傳送到多個佇列或其他交換器。
以下是使用交換器進行間接訊息傳遞的步驟:
- 傳送訊息:傳送微服務使用 amqplib API 將訊息推播到 RabbitMQ 伺服器的交換器中。
- 路由訊息:交換器根據路由鍵將訊息路由到多個佇列或其他交換器。
- 提取訊息:接收微服務使用 amqplib 程式碼函式庫從 RabbitMQ 伺服器的佇列中提取訊息。
圖示說明
圖 5.11 顯示了影片串流媒體微服務將訊息推播到「已檢視」佇列的過程。圖 5.12 顯示了歷史記錄微服務從佇列中提取訊息的過程。
flowchart TD A[影片串流媒體微服務] -->|推播訊息|> B[RabbitMQ 伺服器] B -->|路由訊息|> C[已檢視佇列] C -->|提取訊息|> D[歷史記錄微服務]
內容解密:
在上述流程中,影片串流媒體微服務使用 amqplib API 將訊息推播到 RabbitMQ 伺服器的「已檢視」佇列中。然後,歷史記錄微服務使用 amqplib 程式碼函式庫從佇列中提取訊息。這種間接訊息傳遞方式允許微服務之間進行非同步通訊,提高了系統的可擴充套件性和容錯性。
圖表翻譯:
圖 5.11 和圖 5.12 分別顯示了影片串流媒體微服務將訊息推播到「已檢視」佇列和歷史記錄微服務從佇列中提取訊息的過程。這兩個圖表展示了使用佇列進行間接訊息傳遞的步驟。
5.8 微服務之間的間接訊息傳遞
在間接訊息傳遞中,歷史微服務獲得了更多控制權。它現在可以根據自己的需求從佇列中提取訊息。當歷史微服務不堪負荷,無法接受新的訊息時,它可以忽略這些訊息,讓它們在佇列中堆積積,直到微服務有能力處理它們。
5.8.3 建立 RabbitMQ 伺服器
現在,我們將向應用程式增加一個 RabbitMQ 伺服器。 RabbitMQ 是使用 Erlang 語言編寫的。雖然設定 RabbitMQ 曾經可能很困難,但現在已經變得很簡單,多虧了您已經學習的 Docker 和 Docker Compose 技能。
以下是從 example-3
目錄中的 docker-compose.yaml
檔案中提取的程式碼片段,展示瞭如何向應用程式增加 RabbitMQ 伺服器:
version: '3'
services:
#...
rabbit:
image: rabbitmq:3.12.4-management
container_name: rabbit
ports:
- "5672:5672"
- "15672:15672"
restart: always
這個程式碼片段定義了一個名為 rabbit
的容器,該容器使用 rabbitmq:3.12.4-management
映像檔。這個映像檔包含 RabbitMQ 的管理儀錶板。
5.8.4 檢視 RabbitMQ 儀錶板
您可能已經注意到,在上面的程式碼片段中,RabbitMQ 的埠號是如何設定的。埠號 5672
是我們稍後將使用 amqplib
傳送和接收 RabbitMQ 訊息的埠號。另一個埠號 15672
是我們將用來存取 RabbitMQ 管理儀錶板的埠號。
RabbitMQ 的儀錶板是學習 RabbitMQ 工作原理和更好地瞭解應用程式中傳遞的訊息的好方法。
我們啟動了 RabbitMQ 伺服器,使用的是帶有內建管理儀錶板的 rabbitmq:3.12.4-management
映像檔。儀錶板如圖 5.13 所示,提供了一種圖形化的方式來探索應用程式中的訊息流程。
現在,讓我們啟動應用程式並檢視儀錶板:
cd chapter-5/example-3
docker compose up --build
除了資料函式庫和微服務的輸出外,您還應該看到 RabbitMQ 伺服器的輸出。給它一些時間來啟動,然後使用預設使用者名稱 guest
和預設密碼 guest
來存取儀錶板。
您現在應該可以看到 RabbitMQ 儀錶板。但是,與圖 5.13 不同,您尚未看到任何佇列或交換器。我在建立了 viewed
佇列後截取了那個螢幕截圖。稍後我們將觸發佇列的建立,然後您可以傳回儀錶板檢視它的外觀。
RabbitMQ 儀錶板是除錯的有用工具。能夠視覺化發生的事情總比假設自己知道發生了什麼要好。儀錶板是那些使應用程式實際正在做什麼變得明顯的偉大視覺化工具之一。
您可能會注意到,我們不需要包含 RabbitMQ 儀錶板。相反,我們可以使用不包含儀錶板的 rabbitmq:3.12.4
映像檔。這可能是您構建精簡、生產就緒應用程式或有特定安全問題時的偏好。但一般而言,我們使用帶有儀錶板的映像檔,因為它提供了有用的除錯工具。
圖表翻譯:
graph LR A[RabbitMQ 伺服器] -->|傳送訊息|> B[佇列] B -->|接收訊息|> C[歷史微服務] C -->|處理訊息|> D[資料函式庫]
這個圖表顯示了 RabbitMQ 伺服器、佇列、歷史微服務和資料函式庫之間的訊息流程。RabbitMQ 伺服器傳送訊息到佇列,歷史微服務從佇列接收訊息並將其處理,然後將結果儲存到資料函式庫中。
微服務之間的溝通:使用 RabbitMQ 的間接訊息傳遞
在微服務架構中,各個服務之間的溝通是一個重要的議題。為了實作微服務之間的溝通,我們可以使用 RabbitMQ 這個訊息佇列系統。RabbitMQ 可以讓我們實作間接訊息傳遞,讓微服務之間可以更好地溝通。
連線微服務到 RabbitMQ
要連線微服務到 RabbitMQ,我們需要先安裝 amqplib
這個 npm 套件。然後,我們可以使用 amqplib
來連線到 RabbitMQ 伺服器。
以下是 history 微服務的 index.js
檔案的一部分,展示瞭如何連線到 RabbitMQ 伺服器:
const amqp = require("amqplib");
const RABBIT = process.env.RABBIT;
async function main() {
//...
const messagingConnection = await amqp.connect(RABBIT);
const messageChannel = await messagingConnection.createChannel();
//...
}
在這個範例中,我們使用 amqplib
來連線到 RabbitMQ 伺服器,並建立一個訊息頻道。
設定 RabbitMQ 連線引數
要設定 RabbitMQ 連線引數,我們可以使用環境變數 RABBIT
來設定連線 URI。以下是 Docker Compose 檔案的一部分,展示瞭如何設定 RABBIT
環境變數:
version: '3'
services:
#...
history:
#...
environment:
- RABBIT=amqp://guest:guest@rabbit:5672
在這個範例中,我們設定 RABBIT
環境變數為 amqp://guest:guest@rabbit:5672
,這裡包括了 RabbitMQ 伺服器的主機名、連線埠號和認證資訊。
使用 RabbitMQ 的間接訊息傳遞
使用 RabbitMQ 的間接訊息傳遞,可以讓微服務之間更好地溝通。以下是使用 RabbitMQ 的間接訊息傳遞的流程:
- 微服務 A 將訊息傳送到 RabbitMQ 伺服器。
- RabbitMQ 伺服器將訊息儲存在訊息佇列中。
- 微服務 B 連線到 RabbitMQ 伺服器,並從訊息佇列中取出訊息。
- 微服務 B 處理訊息,並將結果發送回 RabbitMQ 伺服器。
- 微服務 A 連線到 RabbitMQ 伺服器,並從訊息佇列中取出結果。
使用 RabbitMQ 的間接訊息傳遞,可以讓微服務之間更好地溝通,並提高系統的可擴充套件性和可靠性。
圖表翻譯:
graph LR A[微服務 A] -->|傳送訊息|> B[RabbitMQ 伺服器] B -->|儲存訊息|> C[訊息佇列] D[微服務 B] -->|取出訊息|> C D -->|處理訊息|> E[結果] E -->|傳送結果|> B A -->|取出結果|> B
這個圖表展示了使用 RabbitMQ 的間接訊息傳遞的流程。
微服務架構下的服務間通訊方式選擇,直接影響系統的彈性與效率。本文深入探討了以 HTTP POST 請求實作直接訊息傳遞,以及運用 RabbitMQ 進行間接訊息傳遞的兩種策略。直接訊息傳遞的優勢在於簡易直觀,適用於服務間同步溝通和行為協調,但服務地址的依賴性提高了耦合度。相對地,RabbitMQ 等訊息代理的引入,實作了間接訊息傳遞,解耦了服務間的直接依賴,提升了系統的擴充套件性和容錯能力,尤其在高負載和非同步通訊場景下更具優勢。然而,引入訊息代理也增加了系統的複雜度和維運成本。技術團隊需權衡訊息傳遞的效率、可靠性及系統複雜度,選擇最適合自身業務場景的方案。玄貓認為,隨著微服務架構的普及,精細化的訊息傳遞策略將成為系統設計的關鍵,RabbitMQ 等訊息代理的應用將更加廣泛,未來結合服務網格等技術的整合方案也值得關注。