
簡介
在單體架構中,所有模組共享一個資料庫。在微服務中,每個服務擁有自己的資料庫。這項原則是實現松耦合的基礎,但同時也帶來了許多新的數據一致性挑戰。
1. 每個服務模式的資料庫
1.1 原則
✅ Database per Service:
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Order │ │ Payment │ │ Catalog │
│ Service │ │ Service │ │ Service │
└─────┬──────┘ └─────┬──────┘ └─────┬──────┘
│ │ │
┌─────▼──────┐ ┌─────▼──────┐ ┌─────▼──────┐
│ PostgreSQL │ │ PostgreSQL │ │ MongoDB │
│ (orders) │ │ (payments) │ │ (products) │
└────────────┘ └────────────┘ └────────────┘
Quy tắc: KHÔNG truy cập DB của service khác trực tiếp.
Muốn data từ Payment? → Gọi Payment API.
1.2 為什麼不共享資料庫?
❌ Shared Database:
┌────────────┐ ┌────────────┐
│ Order │ │ Payment │
│ Service │ │ Service │
└─────┬──────┘ └─────┬──────┘
│ │
└────────┬────────┘
┌─────▼──────┐
│ Shared DB │
│ (all tables)│
└────────────┘
Vấn đề:
├── Schema coupling: Payment đổi schema → Order bị broken
├── Performance coupling: Query nặng từ Order → Payment bị chậm
├── Deployment coupling: DB migration phải coordinate cả 2 team
├── Scaling coupling: Không thể scale DB riêng cho từng service
└── Technology coupling: Tất cả phải dùng cùng DB engine
1.3 隔離策略
Strategy 1: Separate Database (khuyến nghị)
├── Mỗi service một database instance
├── Cách ly hoàn toàn
└── Chi phí cao hơn nhưng an toàn nhất
Strategy 2: Separate Schema
├── Cùng database instance, khác schema
├── Cách ly ở mức schema
└── Chi phí thấp hơn, phù hợp start
Strategy 3: Separate Tables
├── Cùng schema, khác tables
├── Cách ly yếu nhất
└── Chỉ phù hợp giai đoạn đầu migration
2. 多語言持久性
2.1 選擇合適的資料庫
每個服務都會根據其資料特徵選擇最佳資料庫:
| 服務 | 資料庫 | 原因 |
|---|---|---|
| 訂單 | PostgreSQL | ACID 事務、關聯式資料、複雜查詢 |
| 產品目錄 | MongoDB | 靈活的架構、嵌套的文檔、多樣化的產品類型 |
| 使用者會話 | Redis | 記憶體中、亞毫秒級存取、自動過期 (TTL) |
| 搜尋 | 彈性搜尋 | 全文檢索、倒排索引、分面檢索 |
| 活動提要 | 阿帕契卡桑德拉 | 高寫入吞吐量、時間序列、分散式 |
| 推薦 | Neo4j | 圖關係(「購買 X 的用戶也購買了 Y」) |
| 購物車 | Redis/DynamoDB | 鍵值、快速存取、臨時資料 |
| 分析 | 點擊屋 | 列式、OLAP、聚合查詢 |
| 檔案/影像 | S3/MinIO | 物件存儲,無限規模 |
2.2 實際範例:電子商務
┌──────────┐ PostgreSQL ┌──────────┐ MongoDB
│ Order │──────────────▶│ Catalog │──────────▶
│ Service │ (orders, │ Service │ (products,
└──────────┘ line_items) └──────────┘ variants)
┌──────────┐ Redis ┌──────────┐ Elasticsearch
│ Cart │──────────────▶│ Search │──────────▶
│ Service │ (cart:{uid}) │ Service │ (products index)
└──────────┘ └──────────┘
┌──────────┐ PostgreSQL ┌──────────┐ ClickHouse
│ Payment │──────────────▶│Analytics │──────────▶
│ Service │ (payments, │ Service │ (events,
└──────────┘ refunds) └──────────┘ aggregates)
3.跨服務資料查詢
3.1 問題
當您需要顯示訂單詳細資訊(包括客戶和產品資訊)時:
❌ Trước (monolith):
SELECT o.*, c.name, p.title
FROM orders o
JOIN customers c ON o.customer_id = c.id
JOIN products p ON oi.product_id = p.id
✅ Sau (microservices):
Order, Customer, Product ở databases khác nhau → Không thể JOIN!
3.2 解:API組成
API Gateway / BFF / Composite Service:
1. GET /orders/O-001 → Order Service → {order_id, customer_id, items}
2. GET /customers/C-042 → Customer Service → {name, email}
3. GET /products/P-100 → Product Service → {title, image}
4. Compose response:
{
"order": {
"id": "O-001",
"customer": {"name": "Nguyen Van A", "email": "[email protected]"},
"items": [
{"product": {"title": "iPhone 16", "image": "..."}, "quantity": 1}
]
}
}
3.3 解決方案:CQRS + 物化視圖
Tạo read-optimized view bằng cách subscribe events:
Order.Created ──▶ ┌─────────────────────┐
Customer.Updated ──▶│ Order Detail View │
Product.Updated ──▶│ (Elasticsearch) │
│ │
│ {order + customer │
│ + product details}│
└─────────────────────┘
Query: GET /order-details/O-001 → Trả kết quả đã composed sẵn
3.4 比較策略
| 戰略 | 優點 | 缺點 | 使用案例 |
|---|---|---|---|
| API 成分 | 簡單、即時的資料 | 延遲(多次呼叫)、部分失敗 | 儀表板、管理 UI |
| CQRS + 物化視圖 | 快速閱讀,預作 | 最終一致性、複雜性 | 面向客戶,搜尋 |
| 資料複製(事件) | 快速的本地查詢 | 陳舊資料、儲存重複 | 重讀服務 |
4. 資料所有權
4.1 規則
Mỗi piece of data có MỘT owner duy nhất:
Customer data → Customer Service (owner)
├── Order Service: giữ customer_id (reference)
├── Payment Service: giữ customer_id (reference)
└── Notification Service: subscribe CustomerUpdated event
Price data → Catalog Service (owner)
└── Order Service: snapshot giá tại thời điểm order
(không query lại, vì giá có thể thay đổi)
4.2 資料快照模式
Khi tạo Order, snapshot data cần thiết:
Order {
id: "O-001",
customer_snapshot: { ← Copy tại thời điểm order
name: "Nguyen Van A",
address: "123 ABC"
},
items: [{
product_id: "P-100",
title_snapshot: "iPhone", ← Copy tại thời điểm order
price_snapshot: 25000000 ← Giá tại thời điểm order
}]
}
→ Customer đổi address sau đó? Order vẫn giữ address cũ (đúng)
→ Product tăng giá? Order vẫn giữ giá cũ (đúng)
5. 資料庫遷移策略
5.1 從共享資料庫到每服務資料庫
Phase 1: Identify boundaries
Shared DB → Xác định tables thuộc service nào
Phase 2: Create APIs
Service A gọi Service B qua API thay vì JOIN
Phase 3: Sync data
Dual-write hoặc CDC để sync trong quá trình migration
Phase 4: Split databases
Move tables sang database riêng
Phase 5: Remove old connections
Xoá direct DB access, chỉ giữ API calls
6. 總結
| 概念 | 重點 |
|---|---|
| 每個服務的資料庫 | 每個服務擁有自己的資料庫,不共享 |
| 多語言持久性 | 為每項服務選擇合適的資料庫 |
| API 成分 | 透過聚合API呼叫進行跨服務查詢 |
| CQRS + 檢視 | 為複雜查詢建立讀取最佳化檢視 |
| 資料所有權 | 每個資料都有一個唯一的服務擁有者 |
| 資料快照 | 複製交易時所需的資料 |
下一篇文章:事件溯源與 CQRS — 將狀態儲存為事件和單獨的讀取/寫入模型。