跳至正文

🧩 微服務架構

📖 定義

微服務架構(MSA, Microservices Architecture)是將一個大型應用程式拆分為多個小型且獨立的服務來開發和部署的架構模式。每個服務負責特定的業務功能,可以獨立部署和擴展。與單體式(Monolithic)架構不同,透過服務間的鬆散耦合提供靈活性和可擴展性。

🎯 透過比喻理解

大型企業 vs 新創公司

單體式 = 大型企業
├─ 所有部門在同一棟建築
├─ 集中式管理
├─ 一個部門問題 → 影響整體
├─ 變更困難
└─ 決策緩慢

微服務 = 新創公司聯盟
├─ 每個團隊有獨立辦公室
├─ 自主決策
├─ 一個團隊問題 → 其他團隊正常運作
├─ 快速變更
└─ 靈活擴展

樂高 vs 黏土

單體式 = 黏土塊
┌──────────────────────────────┐
│ 使用者 │ 商品 │ 訂單 │
│ 管理 │ 管理 │ 管理 │
│ 全部一體 │
└──────────────────────────────┘
- 需要重新製作整體
- 修改一部分 → 影響整體
- 擴展困難

微服務 = 樂高積木
┌─────┐ ┌─────┐ ┌─────┐
│使用者│ │ 商品│ │ 訂單│
│服務 │ │服務 │ │服務 │
└─────┘ └─────┘ └─────┘
- 易於更換積木
- 獨立修改
- 只擴展需要的部分

⚙️ 運作原理

1. 單體式 vs 微服務

========== 單體式 ==========
┌─────────────────────────────────────┐
│ 一個應用程式 │
│ │
│ ┌─────────────────────────────┐ │
│ │ 使用者管理模組 │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ 商品管理模組 │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ 訂單管理模組 │ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ 付款模組 │ │
│ └─────────────────────────────┘ │
│ │
│ 一個資料庫 │
│ 一個程式碼庫 │
│ 一個部署單位 │
└─────────────────────────────────────┘

優點:
✅ 開發初期快速
✅ 測試簡單
✅ 部署簡單(只有一個)
✅ 除錯容易

缺點:
❌ 規模變大後複雜
❌ 部署時整體中斷
❌ 無法部分擴展
❌ 技術堆疊變更困難

========== 微服務 ==========
┌──────────┐ ┌──────────┐ ┌──────────┐
│使用者 │ │商品 │ │訂單 │
│服務 │ │服務 │ │服務 │
│ │ │ │ │ │
│Node.js │ │Java │ │Go │
│MongoDB │ │MySQL │ │PostgreSQL│
└──────────┘ └──────────┘ └──────────┘
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│付款 │ │通知 │ │評論 │
│服務 │ │服務 │ │服務 │
│ │ │ │ │ │
│Python │ │Node.js │ │Ruby │
│Redis │ │Kafka │ │Cassandra │
└──────────┘ └──────────┘ └──────────┘

優點:
✅ 獨立部署
✅ 技術堆疊自由
✅ 可部分擴展
✅ 團隊獨立性
✅ 故障隔離

缺點:
❌ 初期複雜度高
❌ 網路通訊開銷
❌ 分散式交易困難
❌ 測試複雜
❌ 運營成本增加

2. 服務間通訊

========== 同步通訊(HTTP/REST)==========
訂單服務 → 商品服務
"商品 123 有庫存嗎?"

"是的,有 5 個"

訂單服務 → 付款服務
"請付款 10,000 元"

"付款完成"

訂單完成

優點:簡單、直觀
缺點:一個服務故障時整體失敗

========== 非同步通訊(訊息佇列)==========
訂單服務 → Message Queue
發布「訂單已建立」訊息

┌──────────────────┐
│ Message Queue │
│ (RabbitMQ, │
│ Kafka 等) │
└──────────────────┘

┌────┴────┬────────┐
↓ ↓ ↓
付款 通知 庫存
服務 服務 服務
各自獨立處理

優點:鬆散耦合、故障隔離
缺點:複雜度增加、除錯困難

========== API Gateway ==========
客戶端(行動裝置/網頁)

┌──────────────────┐
│ API Gateway │
│ - 路由 │
│ - 認證 │
│ - 負載平衡 │
│ - 日誌記錄 │
└──────────────────┘
┌────┴────┬────────┐
↓ ↓ ↓
服務A 服務B 服務C

角色:
- 單一入口點
- 簡化客戶端
- 處理通用功能

3. 資料管理

========== 單體式:共享資料庫 ==========
┌─────────────────────────────────────┐
│ 應用程式 │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │模組A│ │模組B│ │模組C│ │
│ └──┬──┘ └──┬──┘ └──┬──┘ │
└─────┼────────┼───────┼──────────────┘
└────────┼───────┘

┌──────────────────┐
│ 一個資料庫 │
│ ┌────┬────┬────┐│
│ │TA│TB│TC││
│ └────┴────┴────┘│
└──────────────────┘

優點:易於 JOIN、保證一致性
缺點:耦合度高、擴展困難

========== 微服務:每個服務一個資料庫 ==========
┌──────────┐ ┌──────────┐ ┌──────────┐
│服務 A │ │服務 B │ │服務 C │
└────┬─────┘ └────┬─────┘ └────┬─────┘
↓ ↓ ↓
┌─────────┐ ┌─────────┐ ┌─────────┐
│ DB A │ │ DB B │ │ DB C │
│ (MySQL) │ │(MongoDB)│ │(PostgreSQL)│
└─────────┘ └─────────┘ └─────────┘

優點:獨立性、技術選擇自由
缺點:無法 JOIN、一致性困難

========== 資料一致性 ==========
// Saga 模式
1. 訂單服務:建立訂單
2. 付款服務:處理付款
3. 庫存服務:扣除庫存
4. 配送服務:開始配送

如果在步驟 3 失敗?
→ 補償交易(Compensating Transaction)
4. 庫存扣除失敗
3. 取消付款 ← 補償
2. 取消訂單 ← 補償

💡 實際範例

單體式範例(Express.js)

// ========== 單體式應用程式 ==========
// server.js - 一個檔案包含所有功能

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

app.use(express.json());

// 一個資料庫
const db = require('./database');

// ========== 使用者管理 ==========
app.post('/api/users', async (req, res) => {
const { username, email, password } = req.body;
const user = await db.users.create({ username, email, password });
res.json(user);
});

app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json(user);
});

// ========== 商品管理 ==========
app.post('/api/products', async (req, res) => {
const { name, price, stock } = req.body;
const product = await db.products.create({ name, price, stock });
res.json(product);
});

app.get('/api/products', async (req, res) => {
const products = await db.products.findAll();
res.json(products);
});

// ========== 訂單管理 ==========
app.post('/api/orders', async (req, res) => {
const { userId, productId, quantity } = req.body;

// 以交易保證一致性
const transaction = await db.sequelize.transaction();

try {
// 1. 確認庫存
const product = await db.products.findById(productId, { transaction });
if (product.stock < quantity) {
throw new Error('庫存不足');
}

// 2. 扣除庫存
await product.update(
{ stock: product.stock - quantity },
{ transaction }
);

// 3. 建立訂單
const order = await db.orders.create(
{ userId, productId, quantity, total: product.price * quantity },
{ transaction }
);

// 4. 處理付款
await processPayment(order.total);

await transaction.commit();
res.json(order);
} catch (error) {
await transaction.rollback();
res.status(400).json({ error: error.message });
}
});

// ========== 付款處理 ==========
app.post('/api/payments', async (req, res) => {
const { orderId, amount } = req.body;
const payment = await db.payments.create({ orderId, amount });
res.json(payment);
});

// 以一個伺服器執行
app.listen(3000, () => {
console.log('單體式伺服器執行:http://localhost:3000');
});

/*
優點:
- 程式碼集中在一處
- 開發快速
- 除錯容易
- 交易簡單

缺點:
- 規模變大後複雜
- 部署時整體重啟
- 無法部分擴展
- 一個功能故障 → 影響整體
*/

微服務範例

// ========== 1. 使用者服務(user-service.js)==========
// 連接埠:3001
const express = require('express');
const app = express();
const mongoose = require('mongoose');

app.use(express.json());

// 獨立的資料庫
mongoose.connect('mongodb://localhost/users-db');

const User = mongoose.model('User', {
username: String,
email: String,
password: String
});

// 建立使用者
app.post('/users', async (req, res) => {
const { username, email, password } = req.body;

try {
const user = new User({ username, email, password });
await user.save();

// 發布事件(通知其他服務)
await publishEvent('user.created', { userId: user._id, email });

res.json(user);
} catch (error) {
res.status(400).json({ error: error.message });
}
});

// 查詢使用者
app.get('/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
res.json(user);
});

app.listen(3001, () => {
console.log('使用者服務:http://localhost:3001');
});

// ========== 2. 商品服務(product-service.js)==========
// 連接埠:3002
const express = require('express');
const app = express();
const { Pool } = require('pg');

app.use(express.json());

// 使用 PostgreSQL(不同的資料庫!)
const pool = new Pool({
host: 'localhost',
database: 'products-db',
port: 5432
});

// 商品列表
app.get('/products', async (req, res) => {
const result = await pool.query('SELECT * FROM products');
res.json(result.rows);
});

// 商品詳情
app.get('/products/:id', async (req, res) => {
const result = await pool.query(
'SELECT * FROM products WHERE id = $1',
[req.params.id]
);
res.json(result.rows[0]);
});

// 確認庫存
app.get('/products/:id/stock', async (req, res) => {
const result = await pool.query(
'SELECT stock FROM products WHERE id = $1',
[req.params.id]
);
res.json({ stock: result.rows[0].stock });
});

// 扣除庫存
app.post('/products/:id/decrease-stock', async (req, res) => {
const { quantity } = req.body;

const client = await pool.connect();
try {
await client.query('BEGIN');

// 確認目前庫存
const result = await client.query(
'SELECT stock FROM products WHERE id = $1 FOR UPDATE',
[req.params.id]
);

const currentStock = result.rows[0].stock;
if (currentStock < quantity) {
throw new Error('庫存不足');
}

// 扣除庫存
await client.query(
'UPDATE products SET stock = stock - $1 WHERE id = $2',
[quantity, req.params.id]
);

await client.query('COMMIT');
res.json({ success: true });
} catch (error) {
await client.query('ROLLBACK');
res.status(400).json({ error: error.message });
} finally {
client.release();
}
});

app.listen(3002, () => {
console.log('商品服務:http://localhost:3002');
});

// ========== 3. 訂單服務(order-service.js)==========
// 連接埠:3003
const express = require('express');
const axios = require('axios');
const app = express();

app.use(express.json());

const orders = []; // 實際上應使用資料庫

// 建立訂單
app.post('/orders', async (req, res) => {
const { userId, productId, quantity } = req.body;

try {
// 1. 確認使用者(呼叫使用者服務)
const userResponse = await axios.get(
`http://localhost:3001/users/${userId}`
);
const user = userResponse.data;

if (!user) {
return res.status(404).json({ error: '找不到使用者' });
}

// 2. 查詢商品資訊(呼叫商品服務)
const productResponse = await axios.get(
`http://localhost:3002/products/${productId}`
);
const product = productResponse.data;

// 3. 確認庫存
const stockResponse = await axios.get(
`http://localhost:3002/products/${productId}/stock`
);
const { stock } = stockResponse.data;

if (stock < quantity) {
return res.status(400).json({ error: '庫存不足' });
}

// 4. 扣除庫存
await axios.post(
`http://localhost:3002/products/${productId}/decrease-stock`,
{ quantity }
);

// 5. 處理付款(呼叫付款服務)
const total = product.price * quantity;
const paymentResponse = await axios.post(
'http://localhost:3004/payments',
{ userId, amount: total }
);

// 6. 建立訂單
const order = {
id: orders.length + 1,
userId,
productId,
quantity,
total,
status: 'completed',
createdAt: new Date()
};
orders.push(order);

// 7. 發布事件
await publishEvent('order.created', order);

res.json(order);
} catch (error) {
// Saga 模式:補償交易
console.error('訂單失敗:', error.message);

// 恢復庫存
try {
await axios.post(
`http://localhost:3002/products/${productId}/increase-stock`,
{ quantity }
);
} catch (rollbackError) {
console.error('恢復庫存失敗:', rollbackError.message);
}

res.status(500).json({ error: '訂單處理失敗' });
}
});

// 查詢訂單
app.get('/orders/:id', (req, res) => {
const order = orders.find(o => o.id === parseInt(req.params.id));
res.json(order);
});

app.listen(3003, () => {
console.log('訂單服務:http://localhost:3003');
});

// ========== 4. 付款服務(payment-service.js)==========
// 連接埠:3004
const express = require('express');
const app = express();

app.use(express.json());

const payments = [];

app.post('/payments', async (req, res) => {
const { userId, amount } = req.body;

// 呼叫外部付款 API(例如:Stripe、Toss Payments)
try {
// 實際付款處理
const payment = {
id: payments.length + 1,
userId,
amount,
status: 'success',
createdAt: new Date()
};
payments.push(payment);

// 發布事件
await publishEvent('payment.completed', payment);

res.json(payment);
} catch (error) {
res.status(400).json({ error: '付款失敗' });
}
});

app.listen(3004, () => {
console.log('付款服務:http://localhost:3004');
});

// ========== 5. API Gateway(gateway.js)==========
// 連接埠:3000
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();

// 認證中介軟體
function authenticate(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: '需要認證' });
}
// JWT 驗證等
next();
}

// 日誌記錄中介軟體
app.use((req, res, next) => {
console.log(`${req.method} ${req.path}`);
next();
});

// 使用者服務代理
app.use('/api/users', authenticate, createProxyMiddleware({
target: 'http://localhost:3001',
pathRewrite: { '^/api/users': '/users' },
changeOrigin: true
}));

// 商品服務代理
app.use('/api/products', createProxyMiddleware({
target: 'http://localhost:3002',
pathRewrite: { '^/api/products': '/products' },
changeOrigin: true
}));

// 訂單服務代理
app.use('/api/orders', authenticate, createProxyMiddleware({
target: 'http://localhost:3003',
pathRewrite: { '^/api/orders': '/orders' },
changeOrigin: true
}));

// 付款服務代理
app.use('/api/payments', authenticate, createProxyMiddleware({
target: 'http://localhost:3004',
pathRewrite: { '^/api/payments': '/payments' },
changeOrigin: true
}));

app.listen(3000, () => {
console.log('API Gateway:http://localhost:3000');
});

// ========== 6. 事件匯流排(event-bus.js)==========
const amqp = require('amqplib');

let connection, channel;

// RabbitMQ 連接
async function connect() {
connection = await amqp.connect('amqp://localhost');
channel = await connection.createChannel();
}

// 發布事件
async function publishEvent(eventType, data) {
await channel.assertQueue(eventType);
channel.sendToQueue(
eventType,
Buffer.from(JSON.stringify(data))
);
console.log(`事件發布:${eventType}`, data);
}

// 訂閱事件
async function subscribeEvent(eventType, callback) {
await channel.assertQueue(eventType);
channel.consume(eventType, (msg) => {
const data = JSON.parse(msg.content.toString());
console.log(`事件接收:${eventType}`, data);
callback(data);
channel.ack(msg);
});
}

connect();

module.exports = { publishEvent, subscribeEvent };

使用 Docker Compose 執行微服務

# docker-compose.yml
version: '3.8'

services:
# API Gateway
gateway:
build: ./gateway
ports:
- "3000:3000"
depends_on:
- user-service
- product-service
- order-service
- payment-service

# 使用者服務
user-service:
build: ./user-service
ports:
- "3001:3001"
environment:
- MONGO_URL=mongodb://mongo:27017/users
depends_on:
- mongo
- rabbitmq

# 商品服務
product-service:
build: ./product-service
ports:
- "3002:3002"
environment:
- POSTGRES_URL=postgres://postgres:password@postgres:5432/products
depends_on:
- postgres
- rabbitmq

# 訂單服務
order-service:
build: ./order-service
ports:
- "3003:3003"
environment:
- MYSQL_URL=mysql://root:password@mysql:3306/orders
depends_on:
- mysql
- rabbitmq

# 付款服務
payment-service:
build: ./payment-service
ports:
- "3004:3004"
depends_on:
- rabbitmq

# 資料庫們
mongo:
image: mongo:6
volumes:
- mongo-data:/data/db

postgres:
image: postgres:15
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_DB=products
volumes:
- postgres-data:/var/lib/postgresql/data

mysql:
image: mysql:8
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=orders
volumes:
- mysql-data:/var/lib/mysql

# 訊息佇列
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672" # 管理 UI

volumes:
mongo-data:
postgres-data:
mysql-data:

Service Mesh(Istio 範例)

# istio-config.yaml
# 服務網格 - 管理服務間通訊

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: order-service
spec:
hosts:
- order-service
http:
# 流量分配(金絲雀部署)
- match:
- headers:
user-type:
exact: beta
route:
- destination:
host: order-service
subset: v2 # 新版本
weight: 20
- destination:
host: order-service
subset: v1 # 現有版本
weight: 80

# 重試策略
- route:
- destination:
host: order-service
retries:
attempts: 3
perTryTimeout: 2s

# 逾時
timeout: 10s

---
# Circuit Breaker
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: payment-service
spec:
host: payment-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
maxRequestsPerConnection: 2
outlierDetection:
consecutiveErrors: 5
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 50

🤔 常見問題

Q1. 何時應該使用微服務?

A:

✅ 適合使用微服務的情況:

1. 大規模應用程式
- 團隊:10 人以上
- 程式碼:10 萬行以上
- 使用者:數十萬人以上

2. 需要快速部署
- 一天部署多次
- 獨立功能發布
- 頻繁執行 A/B 測試

3. 需要多樣化技術堆疊
- 為每個服務選擇最佳技術
- 與舊系統整合

4. 需要獨立擴展
- 特定功能流量較大
- 各服務資源需求不同

5. 團隊獨立性重要
- 多個團隊同時開發
- 最小化團隊間依賴

範例:
- Netflix:數百個微服務
- Amazon:2-pizza team(每個團隊負責一個服務)
- Uber:按地區、功能分離服務

❌ 適合使用單體式的情況:

1. 小型應用程式
- 團隊:5 人以下
- 功能:明確且簡單
- 流量:較少

2. 初期新創公司
- 需要快速開發 MVP
- 需求經常變更
- 資源有限

3. 簡單的 CRUD
- 沒有複雜的業務邏輯
- 服務邊界不明確

4. 缺乏運營經驗
- 沒有 DevOps 團隊
- 沒有分散式系統經驗

範例:
- 部落格、作品集
- 小型電子商務
- 內部工具

📊 決策檢查清單:

□ 團隊規模 10 人以上?
□ 程式碼庫 10 萬行以上?
□ 經常需要獨立部署?
□ 需要部分擴展?
□ 有 DevOps 團隊?
□ 有分散式系統經驗?

3 個以上勾選 → 考慮微服務
2 個以下 → 保持單體式

Q2. 微服務最大的挑戰是什麼?

A:

// ========== 1. 分散式交易 ==========

// 單體式:簡單的交易
await db.transaction(async (t) => {
await createOrder(data, t);
await decreaseStock(productId, t);
await processPayment(amount, t);
// 任何一個失敗都會整體回滾
});

// 微服務:複雜的 Saga 模式
async function createOrderSaga(data) {
try {
// 步驟 1
const order = await orderService.create(data);

// 步驟 2
await productService.decreaseStock(data.productId);

// 步驟 3
await paymentService.process(order.total);

return order;
} catch (error) {
// 補償交易(反向)
await paymentService.refund(order.total);
await productService.increaseStock(data.productId);
await orderService.cancel(order.id);

throw error;
}
}

// ========== 2. 資料一致性 ==========

// 問題:資料分散在多個服務
// 使用者服務:userId、name
// 訂單服務:userId、orders
// 付款服務:userId、payments

// 解決方案 1:事件溯源
eventBus.on('user.updated', async (event) => {
// 當使用者資訊變更時,更新其他服務
await orderService.updateUserInfo(event.userId, event.name);
await paymentService.updateUserInfo(event.userId, event.name);
});

// 解決方案 2:CQRS(Command Query Responsibility Segregation)
// 分離寫入和讀取
// 寫入:各服務獨立
// 讀取:整合的檢視(Read Model)

// ========== 3. 網路延遲 ==========

// 單體式:函式呼叫(快速)
const user = getUser(userId); // 1ms

// 微服務:HTTP 請求(緩慢)
const user = await axios.get(`http://user-service/users/${userId}`); // 50ms

// 解決方案:快取
const redis = require('redis');
const cache = redis.createClient();

async function getUser(userId) {
// 1. 檢查快取
const cached = await cache.get(`user:${userId}`);
if (cached) return JSON.parse(cached);

// 2. 呼叫服務
const response = await axios.get(`http://user-service/users/${userId}`);
const user = response.data;

// 3. 儲存快取
await cache.setex(`user:${userId}`, 3600, JSON.stringify(user));

return user;
}

// ========== 4. 服務故障處理 ==========

// Circuit Breaker 模式
const CircuitBreaker = require('opossum');

const options = {
timeout: 3000, // 3 秒逾時
errorThresholdPercentage: 50, // 50% 失敗時
resetTimeout: 30000 // 30 秒後重試
};

const breaker = new CircuitBreaker(async (userId) => {
return await axios.get(`http://user-service/users/${userId}`);
}, options);

breaker.fallback(() => ({
id: userId,
name: '未知', // 備援資料
cached: true
}));

// 使用
breaker.fire(userId)
.then(console.log)
.catch(console.error);

// ========== 5. 監控和除錯 ==========

// 分散式追蹤(Distributed Tracing)
// 使用 Jaeger、Zipkin

const tracer = require('jaeger-client').initTracer(config);

app.use((req, res, next) => {
const span = tracer.startSpan('http_request');
span.setTag('http.method', req.method);
span.setTag('http.url', req.url);

req.span = span;
next();
});

// 服務間呼叫時傳遞追蹤 ID
await axios.get('http://order-service/orders', {
headers: {
'x-trace-id': req.span.context().toTraceId()
}
});

Q3. API Gateway 的角色是什麼?

A:

// ========== API Gateway 主要功能 ==========

const express = require('express');
const rateLimit = require('express-rate-limit');
const jwt = require('jsonwebtoken');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();

// ========== 1. 路由 ==========
// 客戶端只需知道一個端點
app.use('/api/users', createProxyMiddleware({
target: 'http://user-service:3001',
changeOrigin: true
}));

app.use('/api/products', createProxyMiddleware({
target: 'http://product-service:3002',
changeOrigin: true
}));

// ========== 2. 認證和授權 ==========
function authenticate(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];

if (!token) {
return res.status(401).json({ error: '需要權杖' });
}

try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: '無效的權杖' });
}
}

app.use('/api/orders', authenticate, createProxyMiddleware({
target: 'http://order-service:3003'
}));

// ========== 3. Rate Limiting ==========
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分鐘
max: 100 // 最多 100 個請求
});

app.use('/api/', limiter);

// ========== 4. 負載平衡 ==========
const productServiceInstances = [
'http://product-service-1:3002',
'http://product-service-2:3002',
'http://product-service-3:3002'
];

let currentIndex = 0;

app.use('/api/products', createProxyMiddleware({
target: productServiceInstances[currentIndex],
router: () => {
// 輪詢
const target = productServiceInstances[currentIndex];
currentIndex = (currentIndex + 1) % productServiceInstances.length;
return target;
}
}));

// ========== 5. 請求/回應轉換 ==========
app.use('/api/legacy', createProxyMiddleware({
target: 'http://legacy-service:8080',
onProxyReq: (proxyReq, req) => {
// 轉換請求
proxyReq.setHeader('X-API-Version', '2.0');
},
onProxyRes: (proxyRes, req, res) => {
// 轉換回應
proxyRes.headers['X-Custom-Header'] = 'Gateway';
}
}));

// ========== 6. 快取 ==========
const redis = require('redis');
const cache = redis.createClient();

app.get('/api/products/:id', async (req, res) => {
const cacheKey = `product:${req.params.id}`;

// 檢查快取
const cached = await cache.get(cacheKey);
if (cached) {
return res.json(JSON.parse(cached));
}

// 呼叫服務
const response = await axios.get(
`http://product-service:3002/products/${req.params.id}`
);

// 儲存快取
await cache.setex(cacheKey, 3600, JSON.stringify(response.data));

res.json(response.data);
});

// ========== 7. 日誌記錄和監控 ==========
app.use((req, res, next) => {
const start = Date.now();

res.on('finish', () => {
const duration = Date.now() - start;
console.log({
method: req.method,
path: req.path,
status: res.statusCode,
duration: `${duration}ms`,
user: req.user?.id
});
});

next();
});

// ========== 8. 錯誤處理 ==========
app.use((err, req, res, next) => {
console.error('Gateway Error:', err);

if (err.code === 'ECONNREFUSED') {
return res.status(503).json({
error: '服務無法使用'
});
}

res.status(500).json({
error: '發生伺服器錯誤'
});
});

// ========== 9. 服務發現 ==========
const consul = require('consul')();

async function getServiceUrl(serviceName) {
const result = await consul.health.service({
service: serviceName,
passing: true // 只選擇通過健康檢查的實例
});

if (result.length === 0) {
throw new Error(`找不到 ${serviceName} 服務`);
}

// 隨機選擇
const instance = result[Math.floor(Math.random() * result.length)];
return `http://${instance.Service.Address}:${instance.Service.Port}`;
}

app.listen(3000);

Q4. 微服務部署策略是什麼?

A:

# ========== 1. Blue-Green 部署 ==========
# 部署新版本(Green)並一次性切換流量

# Blue(目前版本)
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
version: blue # 目前流量
ports:
- port: 80

---
# Green(新版本)部署
kubectl apply -f order-service-green.yaml

# 測試後切換流量
kubectl patch service order-service -p '{"spec":{"selector":{"version":"green"}}}'

# 如有問題立即回滾
kubectl patch service order-service -p '{"spec":{"selector":{"version":"blue"}}}'

# ========== 2. 金絲雀部署 ==========
# 只將部分流量導向新版本,逐步擴大

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: order-service
spec:
hosts:
- order-service
http:
- route:
- destination:
host: order-service
subset: v1 # 現有版本
weight: 90 # 90% 流量
- destination:
host: order-service
subset: v2 # 新版本
weight: 10 # 10% 流量

# 逐步增加
# 10% → 25% → 50% → 75% → 100%

# ========== 3. 滾動更新 ==========
# Kubernetes 預設策略

apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 最多額外建立 1 個
maxUnavailable: 1 # 最多允許 1 個中斷
template:
spec:
containers:
- name: order-service
image: order-service:v2

# 順序:
# 1. 啟動 1 個新 Pod
# 2. 健康檢查通過後終止 1 個現有 Pod
# 3. 重複(直到全部 5 個都更換)

# ========== 4. Docker Compose 部署 ==========
# docker-compose.yml

version: '3.8'

services:
order-service:
image: order-service:latest
deploy:
replicas: 3
update_config:
parallelism: 1 # 一次 1 個
delay: 10s # 間隔 10 秒
failure_action: rollback # 失敗時回滾
restart_policy:
condition: on-failure

# 部署
docker stack deploy -c docker-compose.yml myapp

# ========== 5. CI/CD 管線 ==========
# .github/workflows/deploy.yml

name: Deploy Microservices

on:
push:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
# 獨立部署每個服務
- name: 偵測變更的服務
uses: dorny/paths-filter@v2
id: changes
with:
filters: |
user-service:
- 'services/user/**'
product-service:
- 'services/product/**'

- name: 部署使用者服務
if: steps.changes.outputs.user-service == 'true'
run: |
docker build -t user-service:${{ github.sha }} services/user
docker push user-service:${{ github.sha }}
kubectl set image deployment/user-service user-service=user-service:${{ github.sha }}

- name: 部署商品服務
if: steps.changes.outputs.product-service == 'true'
run: |
docker build -t product-service:${{ github.sha }} services/product
docker push product-service:${{ github.sha }}
kubectl set image deployment/product-service product-service=product-service:${{ github.sha }}

Q5. 如何從單體式遷移到微服務?

A:

// ========== 階段式遷移策略 ==========

// ========== 第 1 階段:Strangler Fig 模式 ==========
// 新功能使用微服務,保留現有功能

┌─────────────────────────────────┐
│ 單體式應用程式 │
│ ┌──────────┬──────────┬──────┐ │
│ │使用者管理 │商品管理 │訂單 │ │
│ │ │ │管理 │ │
│ └──────────┴──────────┴──────┘ │
└─────────────────────────────────┘

// 步驟 1:新功能(通知)使用微服務
┌─────────────────────┐ ┌──────────────┐
│ 單體式 │ │通知服務 │
│ 使用者│商品│訂單 │ │(新建立) │
└─────────────────────┘ └──────────────┘

// 步驟 2:分離訂單功能
┌─────────────────────┐ ┌──────────────┐
│ 單體式 │ │訂單服務 │
│ 使用者│商品 │ │(分離) │
└─────────────────────┘ └──────────────┘
┌──────────────┐
│通知服務 │
└──────────────┘

// 步驟 3:分離所有功能
┌──────────┐ ┌──────────┐ ┌──────────┐
│使用者 │ │商品 │ │訂單 │
│服務 │ │服務 │ │服務 │
└──────────┘ └──────────┘ └──────────┘
┌──────────┐
│通知服務 │
└──────────┘

// ========== 第 2 階段:引入 API Gateway ==========

// 原本:客戶端直接呼叫單體式
const response = await fetch('http://monolith/api/orders');

// 變更:透過 API Gateway 呼叫
const response = await fetch('http://api-gateway/api/orders');

// API Gateway 進行路由
if (route === '/api/orders') {
// 路由到新服務
proxy('http://order-service/orders');
} else {
// 仍然路由到單體式
proxy('http://monolith/api');
}

// ========== 第 3 階段:分離資料庫 ==========

// 問題:共享資料庫
┌────────────────┐
│ 單體式 DB
│ ┌──────────┐ │
│ │Users │ │
│ │Products │ │
│ │Orders │ │
│ └──────────┘ │
└────────────────┘

// 解決方案:每個服務一個資料庫

// 1)雙重寫入(Dual Write)
async function createOrder(data) {
// 寫入單體式 DB
await monolithDB.orders.create(data);

// 也寫入新服務 DB
await orderServiceDB.orders.create(data);
}

// 2)變更資料捕獲(CDC)
// 自動同步單體式 DB 的變更
const debezium = require('debezium');

debezium.on('orders.insert', async (change) => {
// 反映到新服務 DB
await orderServiceDB.orders.create(change.data);
});

// 3)完全分離
┌──────────┐ ┌──────────┐ ┌──────────┐
│Users DB │ │Products │ │Orders DB
(MongoDB) │ │DB(MySQL)(Postgres)
└──────────┘ └──────────┘ └──────────┘

// ========== 第 4 階段:逐步切換流量 ==========

// 在 API Gateway 調整比例
const MIGRATION_PERCENTAGE = 10; // 只有 10% 到新服務

app.use('/api/orders', (req, res, next) => {
if (Math.random() * 100 < MIGRATION_PERCENTAGE) {
// 到新服務
proxy('http://order-service/orders')(req, res, next);
} else {
// 到單體式
proxy('http://monolith/api/orders')(req, res, next);
}
});

// 逐步增加
// 10% → 25% → 50% → 75% → 100%

// ========== 第 5 階段:監控和準備回滾 ==========

const NEW_SERVICE_ERROR_THRESHOLD = 0.05; // 5% 錯誤率

async function monitorNewService() {
const errorRate = await getErrorRate('order-service');

if (errorRate > NEW_SERVICE_ERROR_THRESHOLD) {
// 錯誤率高則回滾
console.error('錯誤率高!回滾到單體式');
MIGRATION_PERCENTAGE = 0;

// 通知
await sendAlert('發生遷移回滾');
}
}

// ========== 實戰檢查清單 ==========

遷移檢查清單:

1. 識別邊界
- 領域驅動設計(DDD
- 按業務功能劃分

2. 從最獨立的功能開始
- 依賴少的
- 業務影響小的
- 例如:通知、日誌記錄、搜尋

3. 引入 API Gateway
- 逐步切換流量

4. 資料庫分離策略
- 雙重寫入 → CDC → 完全分離

5. 強化監控
- 錯誤率、回應時間、流量
- 準備回滾

6. 團隊培訓
- 微服務架構
- DevOps 工具(Docker、Kubernetes)

7. 文件化
- 服務目錄
- API 文件
- 部署指南

🎓 下一步

理解了微服務架構後,請學習以下內容:

  1. 什麼是 Docker?(文件撰寫中)- 容器化
  2. 什麼是 CI/CD? - 自動化部署
  3. REST API vs GraphQL - API 設計

實作練習

# ========== 1. 簡單的微服務實作 ==========

# 專案結構
mkdir microservices-demo
cd microservices-demo

# 建立服務
mkdir -p services/{user,product,order}
mkdir gateway

# 撰寫 Docker Compose
cat > docker-compose.yml

# 執行
docker-compose up -d

# 確認日誌
docker-compose logs -f

# ========== 2. Kubernetes 部署 ==========

# 安裝 minikube(本地 K8s)
brew install minikube
minikube start

# 部署
kubectl apply -f kubernetes/

# 確認服務
kubectl get pods
kubectl get services

# 確認日誌
kubectl logs <pod-name>

# ========== 3. 安裝 Istio(Service Mesh)==========

# 安裝 Istio
istioctl install --set profile=demo -y

# 在服務中注入 sidecar
kubectl label namespace default istio-injection=enabled

# Istio 儀表板
istioctl dashboard kiali

🎬 總結

微服務架構提供可擴展性和靈活性:

  • 獨立性:服務可獨立開發、部署、擴展
  • 技術多樣性:為每個服務選擇最佳技術
  • 故障隔離:一個服務故障不影響整體
  • 團隊自主性:團隊對服務負責

但複雜度會增加,請根據專案規模和團隊能力選擇!🧩