結論先講
重寫是最高風險的選項。 一邊維護舊系統一邊寫新系統,兩邊的功能要同步,團隊人力要分成兩半。Strangler Fig 讓你不需要重寫——在 nginx 層做路由切換,把 /api/auth/* 導向新的 Auth Service,其他路由繼續走舊單體。一次搬一塊,零 downtime。
Strangler Fig Pattern
名字來自植物——絞殺榕從宿主樹上發芽,慢慢包圍宿主,最終宿主死亡、絞殺榕成為新的樹幹。
應用到軟體:新服務從舊單體旁邊長出來,一個功能一個功能地接管,最終舊單體被完全替換。
實作方式
# nginx.conf — 路由切換的控制中心
upstream monolith {
server old-app:3000;
}
upstream auth-service {
server auth-service:3001;
}
upstream file-service {
server file-service:3002;
}
server {
# Phase 1: Auth 拆出去
location /api/auth/ {
proxy_pass http://auth-service;
}
# Phase 2: File 拆出去
location /api/files/ {
proxy_pass http://file-service;
}
# 其他全部走舊單體
location / {
proxy_pass http://monolith;
}
}每拆一個服務,加一行 nginx 配置。不需要改前端的任何 code——前端還是打同一個 domain,nginx 在背後做路由分發。
優點
- 零 downtime:改 nginx config + reload,幾毫秒完成
- 可以回滾:新服務出問題?把 nginx 路由改回舊單體,10 秒恢復
- 漸進式:一次搬一塊,風險可控
- 團隊不用分兩半:不是「維護舊 + 開發新」,而是「一次搬一塊」
搬遷順序建議
- 第一刀:拆最獨立、最痛的模組(通常是 Auth 或 File)
- 第二刀:拆讀取密集的模組(加 Redis cache 效果最好)
- 第三刀:拆寫入密集的模組(可能需要 event-driven)
- 最後:拆剩下的小模組,關掉舊單體
搬遷期間的資料處理
Phase 1:共用 DB
最簡單的起步方式。新服務和舊單體連同一個 DB。
nginx → Auth Service → DB ←
→ Monolith → DB ← 同一個 DB
優點:不需要資料同步。缺點:schema 變更要兩邊同步。
Phase 2:API 代理
新服務有自己的 DB,但遷移期間透過 API 存取舊 DB 的資料。
Auth Service → Auth DB(新)
→ GET /api/users/:id → Monolith → 舊 DB
Phase 3:完全分離
每個服務有自己的 DB,服務間用 event 同步資料。
這是最終形態,但需要 event-driven 架構 支撐。不要急著到這一步。
常見的搬遷陷阱
1. 搬遷期間功能凍結
「等新服務寫好了再加功能」——然後新服務寫了 6 個月還沒好,舊系統 6 個月沒更新,客戶跑了。
解法:搬遷和功能開發並行。Strangler Fig 就是為此設計的——舊單體繼續改、新服務慢慢接管。
2. 過早分 DB
共用 DB 的階段很多人急著分——「教科書說每個 service 要有自己的 DB」。
但分 DB 意味著:
- 跨服務查詢要走 API(原本一個 JOIN 搞定)
- 資料一致性要靠 event(原本一個 transaction 搞定)
- 運維要管多個 DB instance
第 28 篇 的經驗:共用 DB 撐了好幾個月才開始分。先拆 process 再分 DB。
3. 忘記測試搬遷後的效能
每拆一個服務,跑一次壓測(k6 腳本 5 分鐘就寫好)。確認:
- 新服務的效能 ≥ 舊單體中的相同功能
- nginx 路由沒有引入額外延遲
- 跨服務呼叫的 latency 可接受
下一篇
Docker Compose vs K8s:1000 人分水嶺 — 拆完微服務要部署到哪裡?壓測顯示 k3s 在 100 人以上就大量報錯,Docker Compose 500 人零錯誤。K8s 的價值在自動化運維,不在效能。
本系列文章
完整 68 篇目錄見 系列首頁
← 上一篇:微服務拆分時機:什麼 UV 該拆、什麼時候不該拆 → 下一篇:Docker Compose vs K8s:1000 人是分水嶺