去年公司核心業務系統面臨瓶頸:單體應用代碼量突破50萬行,每次發佈都要全量部署,上線週期長達一週,還經常出現“牽一髮而動全身”的故障。痛定思痛後,我們決定啓動微服務遷移,最終選擇Kubernetes作為部署平台。整個過程踩了不少坑,也總結出一套可落地的步驟,分享給正在準備遷移的團隊。
一、遷移前的準備工作
遷移不是拍腦袋決定的,必須先打好基礎。首先要做的是業務拆分梳理,我們用領域驅動設計(DDD)的思路,把單體應用按業務域拆分成用户中心、訂單服務、支付服務等獨立模塊。每個模塊要滿足“高內聚、低耦合”,比如用户相關的註冊、登錄、信息查詢都歸到用户中心,避免跨模塊頻繁調用。
然後是技術棧選型,考慮到團隊現有技術積累,我們選擇Spring Cloud Alibaba作為微服務框架,配合Nacos做服務註冊與配置中心,Sentinel做限流熔斷,這些組件都能和K8s無縫兼容。數據庫方面,將原來的單體庫按業務拆分,用户庫、訂單庫獨立部署,同時引入Sharding-JDBC處理分庫分表場景。
最後是環境準備,搭建K8s集羣(生產環境用3主6從,測試環境2主4從),部署Ingress-Nginx作為入口網關,Prometheus+Grafana做監控,ELK負責日誌收集。這些基礎設施要提前就緒,避免遷移過程中因環境問題卡殼。
二、核心遷移步驟
1. 增量拆分,先易後難
不建議一次性把整個單體應用拆完,我們採用“增量遷移”策略,先從最獨立、改動最小的模塊下手。比如先拆分“數據統計服務”,這個服務只讀取訂單數據,不修改核心業務表,風險最低。
拆分時先抽離該模塊的代碼,獨立創建微服務項目,然後通過HTTP接口和單體應用通信。示例代碼如下(Spring Boot應用):
@RestController
@RequestMapping("/stats")
public class OrderStatsController {
@Autowired
private OrderStatsService orderStatsService;
// 從單體應用的訂單表查詢數據進行統計
@GetMapping("/daily")
public ResultDTO<DailyStatsDTO> getDailyStats(@RequestParam LocalDate date) {
return ResultDTO.success(orderStatsService.calcDailyStats(date));
}
}
將拆分後的服務打包成Docker鏡像:
FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/order-stats-service.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
2. 部署微服務到K8s
為拆分後的服務編寫K8s部署文件order-stats-deploy.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-stats-service
namespace: business
spec:
replicas: 2
selector:
matchLabels:
app: order-stats-service
template:
metadata:
labels:
app: order-stats-service
spec:
containers:
- name: order-stats-service
image: registry.company.com/business/order-stats-service:v1.0
ports:
- containerPort: 8080
resources:
requests:
cpu: 200m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: order-stats-service
namespace: business
spec:
selector:
app: order-stats-service
ports:
- port: 80
targetPort: 8080
type: ClusterIP
執行部署命令:
kubectl apply -f order-stats-deploy.yaml
通過Ingress暴露接口供外部訪問:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: business-ingress
namespace: business
spec:
rules:
- host: api.company.com
http:
paths:
- path: /stats
pathType: Prefix
backend:
service:
name: order-stats-service
port:
number: 80
3. 核心服務拆分與依賴治理
完成簡單服務拆分後,開始處理核心業務模塊,比如用户中心和訂單服務。這一步要解決服務間通信問題,我們用OpenFeign做同步調用:
// 訂單服務調用用户服務接口
@FeignClient(name = "user-center-service", fallback = UserServiceFallback.class)
public interface UserServiceClient {
@GetMapping("/users/{userId}")
ResultDTO<UserDTO> getUserById(@PathVariable("userId") Long userId);
}
同時要處理數據一致性,比如訂單創建後需要扣減庫存,我們採用“本地消息表+RabbitMQ”的方案實現最終一致性,避免分佈式事務的複雜邏輯。
4. 逐步替換單體應用流量
拆分後的微服務先承接部分流量,通過Nacos配置路由權重,逐步加大微服務的流量佔比。比如先讓10%的用户請求走新的微服務,監控無異常後再提升到50%,最後全量切換。
切換完成後,單體應用暫時保留作為降級方案,觀察一週無問題後再下線。
三、遷移過程中的坑與解決方案
- 服務依賴混亂:剛開始拆分時沒理清依賴關係,導致服務間循環調用。後來我們畫了詳細的服務調用圖譜,明確每個服務的入參出參,避免無意義的依賴。
- 數據庫拆分後遺症:拆分後部分查詢需要跨庫,性能下降。我們通過冗餘表和定時同步數據的方式,將跨庫查詢轉化為單庫查詢。
- K8s資源配置不合理:初期給服務分配的CPU和內存不足,導致頻繁OOM。後來通過Prometheus監控資源使用率,動態調整Deployment的resources配置。
四、總結
微服務遷移不是“一蹴而就”的工程,而是一個“循序漸進”的過程。核心原則是“小步快跑、快速驗證”,先從簡單模塊入手積累經驗,再逐步攻克核心業務。遷移過程中不僅要關注技術實現,還要協調團隊協作,比如開發、運維、測試的聯動。
遷移完成後,我們的發佈週期從一週縮短到一天,故障影響範圍也縮小到單個服務,集羣的擴展性和可維護性大幅提升。如果你所在的團隊也面臨單體應用的瓶頸,不妨按這個步驟試試,關鍵是勇敢邁出第一步,在實踐中不斷調整優化。