在微服務架構中,開發流程的效率至關重要。本文將介紹如何利用 Docker、Docker Compose 和 Live Reload 等技術,構建一個支援快速迭代開發的微服務環境。同時,也將探討微服務之間的直接和間接訊息傳遞機制,以及如何在開發和生產環境中最佳化 Dockerfile,確保應用程式在不同環境下的最佳效能。

建立歷史微服務的 Stub

為了實作歷史微服務的即時多載組態,我們首先只針對新的歷史微服務進行組態。之後,我們將同樣的組態應用於每個微服務,以支援整個應用的即時多載功能。

在開始之前,請參考清單 5.1(chapter-5/example-1/history/src/index.js),並熟悉新生的歷史微服務。這個微服務的目的是提供一個簡單的歷史記錄功能。

歷史微服務的 Docker 執行時

以下是歷史微服務的 Docker 執行時組態:

  • history:歷史微服務的源程式碼目錄。
  • src:歷史微服務的源程式碼目錄。
  • index.js:歷史微服務的入口檔案。
  • Dockerfile-dev:歷史微服務的開發環境 Docker 檔案。
  • Dockerfile-prod:歷史微服務的生產環境 Docker 檔案。
  • docker-compose.yml:歷史微服務的 Docker Compose 組態檔案。

歷史微服務的源程式碼目錄

歷史微服務的源程式碼目錄如下:

history
├── src
│   └── index.js
├── example-2
│   └── docker-compose.yml
├── node_modules
├── package.json
└── package-lock.json

歷史微服務的 Docker Compose 組態

以下是歷史微服務的 Docker Compose 組態檔案(docker-compose.yml):

version: '3'
services:
  history:
    build:.
    ports:
      - "3000:3000"
    volumes:
      -./:/app
    environment:
      - NODE_ENV=development

這個組態檔案定義了一個名為 history 的服務,該服務從當前目錄(.)構建,並將主機連線埠 3000 對映到容器連線埠 3000。此外,該組態檔案還將主機端目錄(.)掛載到容器中的 /app 目錄,以便實作即時多載功能。

內容解密:

在上述 Docker Compose 組態檔案中,我們定義了一個名為 history 的服務,並指定了其構建上下文、連線埠對映、卷掛載和環境變數。其中,build:. 指定了構建上下文為當前目錄(.),而 ports: - "3000:3000" 則將主機連線埠 3000 對映到容器連線埠 3000。此外,volumes: -./:/app 將主機端目錄(.)掛載到容器中的 /app 目錄,以便實作即時多載功能。

圖表翻譯:

以下是歷史微服務的 Docker Compose 組態檔案的 Mermaid 圖表:

  graph LR
    A[主機端目錄] -->|掛載|> B[容器中的 /app 目錄]
    B -->|對映|> C[容器連線埠 3000]
    C -->|對映|> D[主機連線埠 3000]
    D -->|存取|> E[歷史微服務]

這個圖表展示了主機端目錄與容器中的 /app 目錄之間的掛載關係,以及容器連線埠 3000 與主機連線埠 3000 之間的對映關係。同時,它還展示了存取歷史微服務的流程。

匯入微服務架構的影音串流系統

系統概覽

在設計一個影音串流系統時,採用微服務架構是一種有效的方式,可以提高系統的可擴充套件性、維護性和容錯性。微服務架構將系統分解成多個小型、獨立的服務,每個服務負責一個特定的功能。這種架構使得系統可以更容易地進行更新、維護和擴充套件。

專案結構

下面是專案的結構:

src
index.js
...
videos
...
node_modules
...
package.json
package-lock.json
Dockerfile-dev
Dockerfile-prod
README.MD
tree.txt

在這個結構中,src 目錄包含了系統的源程式碼,包括 index.js 和其他相關檔案。videos 目錄存放了影音檔案。node_modules 目錄包含了系統所需的第三方套件。package.jsonpackage-lock.json 檔案記錄了系統的依賴套件和版本資訊。Dockerfile-devDockerfile-prod 檔案分別用於開發和生產環境的 Docker 容器組態。

微服務架構

在這個系統中,我們可以將其分解成多個微服務,例如:

  • 影音串流服務:負責處理影音檔案的串流和播放。
  • 使用者管理服務:負責管理使用者的帳戶和許可權。
  • 訂閱管理服務:負責管理使用者的訂閱和付款。

每個微服務都可以獨立開發、測試和佈署,這使得系統可以更容易地進行更新和維護。

自動化佈署

為了提高開發效率和系統的可靠性,我們可以使用 Docker 和 Kubernetes 來自動化佈署過程。當開發人員在本地機器上修改程式碼時,系統可以自動將修改推播到 Git 版本控制系統。然後,Docker 和 Kubernetes 會自動將修改佈署到生產環境中。

這種自動化佈署過程可以提高系統的可靠性和效率,並使得開發人員可以更專注於程式碼開發和測試。

內容解密:

下面是 index.js 檔案的內容:

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.listen(3000, () => {
  console.log('Server started on port 3000');
});

這個檔案使用 Express.js 框架建立了一個簡單的 Web 伺服器,當使用者存取根 URL 時,伺服器會傳回 “Hello World!” 的字串。

圖表翻譯:

下面是系統架構的 Mermaid 圖表:

  graph LR
    A[使用者] -->|存取|> B[Web 伺服器]
    B -->|請求|> C[影音串流服務]
    C -->|串流|> D[使用者]
    D -->|播放|> E[影音播放器]

這個圖表描述了使用者存取 Web 伺服器、請求影音串流服務、播放影音檔案的過程。

實作快速迭代的Live Reload

在開發微服務時,實作快速迭代是非常重要的。Live Reload是一種可以自動多載程式碼的功能,讓開發者可以快速地看到程式碼變化的效果。

啟用Live Reload

要啟用Live Reload,需要安裝nodemon套件。nodemon是一個可以監視程式碼變化並自動多載的工具。安裝nodemon可以使用以下命令:

npm install --save-dev nodemon

然後,在package.json檔案中增加一個新的指令碼:

"scripts": {
  "start:dev": "nodemon --legacy-watch./src/index.js"
}

這個指令碼會啟動nodemon並監視程式碼變化。

執行Live Reload

要執行Live Reload,可以使用以下命令:

npm run start:dev

這個命令會啟動nodemon並開始監視程式碼變化。

分離Dockerfile

在開發和生產環境中,需要分離Dockerfile以最佳化不同環境的需求。在開發環境中,需要快速迭代,而在生產環境中,需要最佳化效能和安全性。

# 開發環境Dockerfile
FROM node:14

WORKDIR /app

COPY package*.json./

RUN npm install

COPY..

RUN npm run build

EXPOSE 3000

CMD ["npm", "run", "start:dev"]
# 生產環境Dockerfile
FROM node:14

WORKDIR /app

COPY package*.json./

RUN npm install

COPY..

RUN npm run build

EXPOSE 3000

CMD ["npm", "run", "start"]

這兩個Dockerfile分別用於開發和生產環境,可以根據不同的需求進行最佳化。

圖表翻譯:

  graph LR
    A[開發環境] -->|啟用Live Reload|> B[自動多載程式碼]
    B -->|最佳化快速迭代|> C[開發效率提高]
    C -->|分離Dockerfile|> D[最佳化不同環境的需求]
    D -->|生產環境|> E[最佳化效能和安全性]

這個圖表展示了Live Reload的工作流程和分離Dockerfile的優點。

微服務的 Docker 化與開發環境最佳化

在微服務架構中,為了確保每個服務的獨立性和靈活性,我們會為每個微服務建立多個 Dockerfile。這些 Dockerfile 分別用於不同的環境,例如開發環境和生產環境。這樣做的好處是可以根據不同的環境需求進行最佳化和組態。

生產環境 Dockerfile

首先,我們來看一下生產環境的 Dockerfile。這個 Dockerfile 的內容相對簡單,主要是安裝 Node.js 的依賴包,複製程式碼到容器中,並設定啟動命令。

FROM node:18.5.0
WORKDIR /usr/src/app

COPY package*.json./

RUN npm ci --omit=dev
COPY./src./src

CMD npm start

這個 Dockerfile 做了以下幾件事:

  1. 使用 Node.js 18.5.0 作為基礎映象。
  2. 設定工作目錄為 /usr/src/app
  3. 複製 package.jsonpackage-lock.json 到容器中。
  4. 安裝生產環境所需的依賴包。
  5. 複製應用程式程式碼到容器中。
  6. 設定啟動命令為 npm start

開發環境 Dockerfile

接下來,我們來看一下開發環境的 Dockerfile。這個 Dockerfile 與生產環境的 Dockerfile 有一些不同,主要是為了支援 live reload 功能。

FROM node:18.5.0
WORKDIR /usr/src/app

COPY package*.json./

CMD npm install --prefer-offline && \
    npm run start:dev

這個 Dockerfile 做了以下幾件事:

  1. 使用 Node.js 18.5.0 作為基礎映象。
  2. 設定工作目錄為 /usr/src/app
  3. 複製 package.jsonpackage-lock.json 到容器中。
  4. 設定啟動命令為 npm install --prefer-offline && npm run start:dev

注意到,開發環境的 Dockerfile 沒有複製應用程式程式碼到容器中,這是因為我們想要使用 live reload 功能,需要在容器外部維護程式碼。

Live Reload 的實作

live reload 是一個很有用的功能,可以在我們修改程式碼後自動重啟應用程式。要實作 live reload,我們需要使用 nodemon 這個工具。

在開發環境的 Dockerfile 中,我們設定了啟動命令為 npm run start:dev。這個命令會啟動 nodemon,並監視程式碼的變化。如果程式碼有變化,nodemon 會自動重啟應用程式。

內容解密:
  • 我們使用 FROM node:18.5.0 指令來指定基礎映象。
  • 我們使用 WORKDIR /usr/src/app 指令來設定工作目錄。
  • 我們使用 COPY package*.json./ 指令來複製 package.jsonpackage-lock.json 到容器中。
  • 我們使用 RUN npm ci --omit=dev 指令來安裝生產環境所需的依賴包。
  • 我們使用 COPY./src./src 指令來複製應用程式程式碼到容器中。
  • 我們使用 CMD npm start 指令來設定啟動命令。
  • 我們使用 CMD npm install --prefer-offline && npm run start:dev 指令來設定開發環境的啟動命令。

圖表翻譯:

  graph LR
    A[建立 Dockerfile] --> B[設定基礎映象]
    B --> C[設定工作目錄]
    C --> D[複製 package.json 和 package-lock.json]
    D --> E[安裝依賴包]
    E --> F[複製應用程式程式碼]
    F --> G[設定啟動命令]
    G --> H[啟動應用程式]

這個圖表展示了建立 Dockerfile 的過程,從設定基礎映象到啟動應用程式。

啟動微服務

微服務的啟動是透過 CMD 指令在 Dockerfile 中實作的。該指令呼叫 npm 指令碼 start:dev 來啟動微服務,並啟用了 live reload 功能以便於開發過程中的快速迭代。

實作快速迭代的 Live Reload

為了實作應用程式級別的 live reload,我們需要對 Docker Compose 檔案進行必要的修改,以便在主機作業系統和容器之間分享程式碼和 npm 快取。這樣,我們就可以在 Visual Studio Code (VS Code) 中編輯程式碼,並且能夠立即在透過 Docker Compose 執行的微服務中看到變化。

更新 Docker Compose 檔案

以下是從示例 1 的 Docker Compose 檔案 (chapter-5/example-1/docker-compose.yaml) 中提取出的 history 微服務的組態。這個組態與我們在第 4 章中建立的 Docker Compose 檔案類別似,但有一些不同之處和新增的內容。

version: '3'
services:
  --snip--
  history:
    image: history
    build:
      context:./history
      dockerfile: Dockerfile-dev
    container_name: history
    volumes:
      - /tmp/history/npm-cache:/root/.npm:z
      -./history/src:/usr/src/app/src:z
    ports:
      - "4002:80"
    environment:
      - PORT=80
      - NODE_ENV=development
    restart: "no"

在這個組態中,首先引入的是 Dockerfile-dev,它是 Dockerfile 的開發版本。在第 4 章中,我們曾經提到可以省略 dockerfile 欄位,它將預設為 Dockerfile。現在,我們明確地將其設定為 Dockerfile-dev 以使用開發版本的 Dockerfile。

接下來,新增的是 volumes 欄位,透過它,我們建立了 Docker 卷以連線開發電腦上的檔案系統和容器的檔案系統。這使得我們的源程式碼直接鏈結到容器中,這也是為什麼我們沒有直接將程式碼烘焙到映像中的原因。

使用 Docker 卷實作 Live Reload

透過這種方式,我們可以在不重新構建映像的情況下對程式碼進行修改,並且能夠立即看到變化。這大大提高了開發效率,使得開發過程更加敏捷和高效。

微服務之間的溝通

在開發微服務架構的應用程式時,各個微服務之間的溝通是一個非常重要的議題。為了實作微服務之間的溝通,我們需要使用容器化技術,例如Docker,來封裝和佈署微服務。

容器化技術

Docker是一種流行的容器化技術,允許我們將應用程式封裝成容器,並在不同的環境中佈署。使用Docker,我們可以建立一個容器,包含了應用程式的所有依賴項和組態檔案。

微服務的溝通

在微服務架構中,各個微服務之間需要溝通,以實作業務邏輯。為了實作微服務之間的溝通,我們可以使用RESTful API或訊息佇列等技術。

RESTful API

RESTful API是一種根據HTTP的API,允許微服務之間透過HTTP請求進行溝通。使用RESTful API,我們可以定義API端點和請求方法,以實作微服務之間的溝通。

訊息佇列

訊息佇列是一種允許微服務之間透過訊息進行溝通的技術。使用訊息佇列,我們可以將訊息傳送到佇列中,然後由其他微服務接收和處理。

實作 live reload

為了實作 live reload,我們需要使用 Docker Compose 來管理容器,並使用 nodemon 來監視程式碼變化。當程式碼發生變化時,nodemon 會自動重啟容器,以實作 live reload。

實作步驟

  1. 安裝 nodemon 並組態 package.json。
  2. 建立 Dockerfile 並組態容器。
  3. 使用 Docker Compose 來管理容器。
  4. 組態 live reload 並測試。

測試 live reload

  1. 啟動 Docker Compose 並測試 live reload。
  2. 修改程式碼並儲存,觀察容器是否自動重啟。
  3. 測試 live reload 並觀察結果。

微服務之間的溝通機制

在微服務架構中,各個服務之間的溝通是非常重要的。這章節將探討微服務之間的溝通機制,包括直接訊息傳遞和間接訊息傳遞。

直接訊息傳遞

直接訊息傳遞是指一個微服務直接將訊息傳遞給另一個微服務,並立即收到回應。這種方式通常用於觸發某個動作或通知某個事件。直接訊息傳遞的優點是可以快速收到回應,但是它也需要兩個微服務之間有緊密的耦合關係。

間接訊息傳遞

間接訊息傳遞是指在兩個微服務之間加入一個中間層,讓訊息透過中間層傳遞。這種方式可以減少微服務之間的耦合關係,使得系統更加靈活和可擴充套件。間接訊息傳遞的缺點是需要額外的中間層,增加了系統的複雜度。

微服務溝通的風格

微服務溝通可以分為兩種風格:直接訊息傳遞和間接訊息傳遞。直接訊息傳遞適合用於需要立即收到回應的情況,而間接訊息傳遞適合用於需要減少耦合關係的情況。

實踐中的應用

在實踐中,微服務溝通可以透過各種方式實作,例如使用API、訊息佇列或事件驅動架構等。選擇合適的溝通方式需要考慮系統的需求和限制。

內容解密:
  • 微服務架構中,各個服務之間的溝通是非常重要的。
  • 直接訊息傳遞是指一個微服務直接將訊息傳遞給另一個微服務,並立即收到回應。
  • 間接訊息傳遞是指在兩個微服務之間加入一個中間層,讓訊息透過中間層傳遞。
  • 微服務溝通可以分為兩種風格:直接訊息傳遞和間接訊息傳遞。
  • 選擇合適的溝通方式需要考慮系統的需求和限制。

圖表翻譯:

  graph LR
    A[微服務A] -->|直接訊息傳遞|> B[微服務B]
    B -->|回應|> A
    C[微服務C] -->|間接訊息傳遞|> D[中間層]
    D -->|轉發|> E[微服務E]

圖表描述了微服務之間的兩種溝通方式:直接訊息傳遞和間接訊息傳遞。直接訊息傳遞是指一個微服務直接將訊息傳遞給另一個微服務,並立即收到回應。間接訊息傳遞是指在兩個微服務之間加入一個中間層,讓訊息透過中間層傳遞。

微服務間的直接與間接訊息傳遞

在微服務架構中,服務之間的溝通是非常重要的。有兩種主要的溝通方式:直接訊息傳遞和間接訊息傳遞。

直接訊息傳遞

直接訊息傳遞是指一個微服務直接將訊息傳送給另一個微服務。這種方式通常用於需要即時回應的場景,例如使用者觀看影片時,影片串流微服務需要立即通知歷史記錄微服務進行更新。

直接訊息傳遞的優點

  • 即時回應:直接訊息傳遞可以實作即時回應,這對於需要快速處理的業務邏輯非常重要。
  • 簡單實作:直接訊息傳遞的實作相對簡單,只需要知道接收方微服務的地址和連線埠即可。

直接訊息傳遞的缺點

  • 耦合度高:直接訊息傳遞會導致微服務之間的耦合度增加,如果一個微服務出現問題,可能會影響到其他微服務。
  • 不適合廣播式通知:直接訊息傳遞不適合廣播式通知,因為需要逐一傳送給每個接收方微服務。

間接訊息傳遞

間接訊息傳遞是指一個微服務將訊息傳送給一個中介軟體(如訊息佇列),然後由中介軟體將訊息轉發給其他微服務。這種方式通常用於不需要即時回應的場景,例如使用者觀看影片時,影片串流微服務可以將「觀看」訊息傳送給訊息佇列,然後由歷史記錄微服務從訊息佇列中取出訊息進行處理。

間接訊息傳遞的優點

  • 耦合度低:間接訊息傳遞可以降低微服務之間的耦合度,各個微服務只需要與中介軟體進行互動。
  • 適合廣播式通知:間接訊息傳遞適合廣播式通知,可以將訊息傳送給多個接收方微服務。

間接訊息傳遞的缺點

  • 實作複雜:間接訊息傳遞的實作相對複雜,需要引入中介軟體並進行組態。
  • 可能產生延遲:間接訊息傳遞可能會產生延遲,因為訊息需要經過中介軟體才能到達接收方微服務。

使用 HTTP 進行直接訊息傳遞

HTTP 可以用於直接訊息傳遞,例如使用者觀看影片時,影片串流微服務可以使用 HTTP 將「觀看」訊息傳送給歷史記錄微服務。

POST /viewed HTTP/1.1
Host: history-microservice.com
Content-Type: application/json

{
    "videoId": "123",
    "userId": "456"
}

使用訊息佇列進行間接訊息傳遞

訊息佇列可以用於間接訊息傳遞,例如使用者觀看影片時,影片串流微服務可以將「觀看」訊息傳送給訊息佇列,然後由歷史記錄微服務從訊息佇列中取出訊息進行處理。

  flowchart TD
    A[影片串流微服務] -->|傳送「觀看」訊息|> B[訊息佇列]
    B -->|轉發「觀看」訊息|> C[歷史記錄微服務]

圖表翻譯:

此圖表示影片串流微服務將「觀看」訊息傳送給訊息佇列,然後由歷史記錄微服務從訊息佇列中取出訊息進行處理。這是一種間接訊息傳遞的方式,可以降低微服務之間的耦合度。

微服務間的溝通:直接與間接訊息傳遞

在微服務架構中,各個微服務之間的溝通是非常重要的。這種溝通可以透過直接或間接的方式進行。在本文中,我們將探討微服務間的直接與間接訊息傳遞。

直接訊息傳遞

直接訊息傳遞是指一個微服務直接將訊息傳送給另一個微服務。這種方式通常使用HTTP請求,例如使用HTTP POST請求直接將訊息傳送給目標微服務。如下圖所示,影片串流微服務使用HTTP POST請求直接將「Viewed」訊息傳送給歷史記錄微服務。

為什麼使用HTTP?

HTTP是全球資訊網的基礎協定,也是建立Web服務的 facto 標準。HTTP是一種可靠且被廣泛理解的協定,特別是在建立REST API時。由於其廣泛的應用和支援,使用HTTP進行微服務間的溝通是一種自然的選擇。

直接定址訊息

在直接傳遞訊息之前,需要有一種機制來定位目標微服務。這裡引入了另一個重要的網際協定:網域名稱系統(DNS)。DNS提供了一種簡單自動的方式來將主機名稱轉換為IP地址。這種機制在Docker Compose中得到了自動支援,也可以在生產環境的Kubernetes叢集中輕鬆實作。

間接訊息傳遞

間接訊息傳遞則是指訊息不直接傳送給目標微服務,而是放入一個佇列中,以便稍後處理。這種方式允許微服務之間的解耦,使得系統更加靈活和可擴充套件。如下圖所示,影片串流微服務傳送的「Viewed」訊息不直接傳送給歷史記錄微服務,而是放入一個佇列中,歷史記錄微服務可以在適當時候處理這條訊息。

間接訊息傳遞的優點

間接訊息傳遞提供了多種優點,包括:

  • 解耦:微服務之間不再緊密耦合,可以獨立發展和佈署。
  • 可擴充套件性:系統可以更容易地擴充套件,因為訊息處理可以在多個節點上進行。
  • 容錯性:如果一個微服務暫時不可用,訊息仍然可以被處理,只要目標微服務稍後還原可用即可。
圖表翻譯:

上述圖表展示了影片串流微服務和歷史記錄微服務之間的直接訊息傳遞過程。影片串流微服務使用HTTP POST請求直接將「Viewed」訊息傳送給歷史記錄微服務。歷史記錄微服務透過 /viewed 路由處理這條訊息。這種直接的訊息傳遞方式簡單易行,但也可能導致微服務之間的緊密耦合。

內容解密:

在上述內容中,我們探討了微服務間的直接與間接訊息傳遞。直接訊息傳遞使用HTTP請求直接將訊息傳送給目標微服務,而間接訊息傳遞則將訊息放入一個佇列中,以便稍後處理。瞭解這兩種方式的優缺點,有助於我們設計出更合理、更可擴充套件的微服務架構。

  flowchart TD
    A[影片串流微服務] -->|HTTP POST|> B[歷史記錄微服務]
    B --> C[/viewed路由]
    C --> D[處理「Viewed」訊息]

圖表翻譯:

上述Mermaid圖表展示了影片串流微服務和歷史記錄微服務之間的直接訊息傳遞過程。影片串流微服務使用HTTP POST請求直接將「Viewed」訊息傳送給歷史記錄微服務。歷史記錄微服務透過 /viewed 路由處理這條訊息。這種直接的訊息傳遞方式簡單易行,但也可能導致微服務之間的緊密耦合。

  sequenceDiagram
    participant 影片串流微服務
    participant 歷史記錄微服務
    影片串流微服務->>歷史記錄微服務: HTTP POST /viewed
    歷史記錄微服務->>歷史記錄微服務: 處理「Viewed」訊息

從技術架構視角來看,微服務間的通訊機制是構建分散式系統的基本。本文深入探討了直接訊息傳遞(例如根據 HTTP 的 RESTful API)和間接訊息傳遞(例如利用訊息佇列)兩種主要模式,並分析了它們各自的優缺點。直接訊息傳遞的優勢在於簡潔高效,易於實作,但服務間耦合度較高。間接訊息傳遞則透過引入中間層,降低了服務間的依賴性,提升了系統的彈性和可擴充套件性,但也增加了系統的複雜度和潛在的延遲。技術團隊在選擇通訊機制時,應根據業務場景的需求,權衡效能、耦合度、可靠性等多重因素。對於需要即時回應的場景,直接訊息傳遞是更佳的選擇;而對於非同步、高吞吐量的場景,則應優先考慮間接訊息傳遞。展望未來,隨著 Service Mesh 等技術的發展,微服務間的通訊將更加便捷和安全,進一步釋放微服務架構的潛力。玄貓認為,深入理解不同通訊機制的特性,並根據實際需求靈活運用,是構建高效能、高可靠性微服務系統的關鍵。