跳至主要內容

微服務 vs 單體架構:如何做出正確的技術選擇

6 分鐘閱讀 1,100 字

微服務 vs 單體架構:如何做出正確的技術選擇

「我們要改成微服務架構!」這句話在過去幾年的技術討論中頻繁出現。微服務被描繪成現代化、可擴展的架構,彷彿不用就落伍了。但真相是:許多採用微服務的團隊正在悄悄地把服務合併回去。本文試圖客觀地分析兩種架構的優缺點,幫助你做出適合自己團隊的選擇。

什麼是單體架構?

單體架構(Monolith)是把所有功能打包在同一個部署單元的架構:

單體應用
├── 使用者模組
├── 訂單模組
├── 商品模組
├── 支付模組
└── 通知模組

↓ 統一建置、統一部署、共用資料庫

單體不等於「舊」或「差」。Google、Facebook、Twitter 都是從單體開始的,Netflix 直到流量大到一定規模才開始拆分。

什麼是微服務架構?

微服務把每個業務功能拆分為獨立運行的服務,透過 API 或訊息佇列通訊:

微服務架構
├── user-service (port 3001)
├── order-service (port 3002)
├── product-service (port 3003)
├── payment-service (port 3004)
└── notification-service (port 3005)

↓ 各自獨立部署、可獨立擴展、各自的資料庫

單體架構的優勢

1. 開發效率高

在單體中,跨模組的函式呼叫就是直接調用:

// 單體中的訂單服務
class OrderService {
  constructor(
    private readonly productService: ProductService,
    private readonly paymentService: PaymentService,
    private readonly notificationService: NotificationService
  ) {}

  async createOrder(userId: string, items: OrderItem[]) {
    // 直接呼叫,沒有網路開銷、沒有序列化
    const products = await this.productService.validateStock(items);
    const payment = await this.paymentService.charge(userId, products.total);
    await this.notificationService.sendConfirmation(userId, payment.id);

    return this.orderRepository.create({ userId, items, paymentId: payment.id });
  }
}

2. 除錯和測試簡單

一個 stack trace 就能追蹤整個請求的生命週期,不需要分散式追蹤工具。

3. 部署簡單

# 單體的 docker-compose.yml
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://...
  db:
    image: postgres:16

4. ACID 事務天然支援

// 在同一個資料庫事務中完成所有操作
async function checkout(userId: string, cartItems: CartItem[]) {
  return await db.transaction(async (trx) => {
    await trx('inventory').decrement('stock', cartItems);
    const order = await trx('orders').insert({ userId });
    await trx('payments').insert({ orderId: order.id, amount: total });
    // 任何步驟失敗,整個事務回滾
  });
}

微服務的優勢

1. 獨立擴展

# Kubernetes 中只擴展高負載的服務
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: product-service-hpa
spec:
  scaleTargetRef:
    name: product-service
  minReplicas: 2
  maxReplicas: 50
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        averageUtilization: 60

2. 技術多樣性

每個服務可以選擇最適合的技術棧:

  • 搜尋服務:Elasticsearch + Python
  • 即時通訊:Node.js + WebSocket
  • 資料分析:Spark + Scala
  • 一般業務:TypeScript + PostgreSQL

3. 獨立部署

支付團隊可以在不影響其他服務的情況下部署更新。

4. 故障隔離

// 使用 Circuit Breaker 防止級聯故障
import { CircuitBreaker } from 'opossum';

const breaker = new CircuitBreaker(callPaymentService, {
  timeout: 3000,
  errorThresholdPercentage: 50,
  resetTimeout: 30000
});

breaker.fallback(() => ({ status: 'queued', message: '付款將稍後處理' }));

async function processOrder(order: Order) {
  // 即使 payment-service 掛掉,訂單系統仍可運行
  const paymentResult = await breaker.fire(order.paymentInfo);
  ...
}

微服務的隱藏成本

這些成本常被忽略:

分散式系統的複雜性

// 微服務中的「簡單」訂單建立
async function createOrder(userId: string, items: OrderItem[]) {
  // 每一步都可能失敗、超時、回傳過時的資料
  let stockReserved = false;
  let paymentCharged = false;

  try {
    // 1. 檢查庫存(可能失敗)
    await inventoryService.reserve(items); // 跨網路呼叫
    stockReserved = true;

    // 2. 扣款(可能失敗,但庫存已被鎖定)
    await paymentService.charge(userId, total); // 跨網路呼叫
    paymentCharged = true;

    // 3. 建立訂單(可能失敗,但庫存和款項都動了)
    return await orderService.create({ userId, items });
  } catch (error) {
    // Saga 補償事務:需要逆向操作來恢復狀態
    if (paymentCharged) await paymentService.refund(userId, total);
    if (stockReserved) await inventoryService.release(items);
    throw error;
  }
}

基礎設施成本

你需要:

  • 服務發現:Consul, etcd
  • API 閘道:Kong, AWS API Gateway
  • 分散式追蹤:Jaeger, Zipkin
  • 集中式日誌:ELK Stack
  • 訊息佇列:Kafka, RabbitMQ
  • 服務網格:Istio(可選)

這些工具本身的學習成本和維護成本相當可觀。

決策框架

你的團隊有多少工程師?
├── < 10 人 → 先做單體,等真的需要再拆
├── 10-50 人 → 模組化單體可能更合適
└── > 50 人,且業務界限清晰 → 考慮微服務

你的主要瓶頸是什麼?
├── 部署頻率衝突 → 微服務有幫助
├── 特定功能需要大量擴展 → 微服務有幫助
├── 開發速度慢 → 微服務通常讓事情更慢
└── 技術棧限制 → 微服務有幫助

中間路線:模組化單體

模組化單體(Modular Monolith)是一個被低估的選擇:

// 嚴格的模組邊界,但仍是單一部署
// 每個模組只能透過公開介面存取

// modules/orders/index.ts(公開 API)
export { OrderService } from './OrderService';
export type { Order, CreateOrderDTO } from './types';

// modules/orders/internal/(外部不可存取)
// 資料庫 schema、內部邏輯等

// 如果未來需要拆分,邊界已經清晰,遷移成本最小

真實案例參考

  • Amazon:大約在 2002 年開始拆分微服務,當時有數千名工程師
  • Netflix:2008-2012 年漸進式拆分,由於 DVD 業務和串流業務有本質差異
  • Shopify:直到 2020 年還是模組化單體,後來才引入部分微服務
  • Stack Overflow:至今仍是單體,每分鐘處理數百萬請求

小結

微服務解決的是規模問題,而不是技術問題。如果你的團隊還在為「功能開發速度太慢」煩惱,微服務只會讓它更慢。先建立好的工程實踐、清晰的模組邊界、完善的測試覆蓋率——這些在任何架構下都有價值。當你真的感受到單體的瓶頸時,再做有針對性的拆分,才是務實的工程決策。

分享這篇文章