去年公司核心業務系統面臨瓶頸:單體應用代碼量突破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%,最後全量切換。

切換完成後,單體應用暫時保留作為降級方案,觀察一週無問題後再下線。

三、遷移過程中的坑與解決方案

  1. 服務依賴混亂:剛開始拆分時沒理清依賴關係,導致服務間循環調用。後來我們畫了詳細的服務調用圖譜,明確每個服務的入參出參,避免無意義的依賴。
  2. 數據庫拆分後遺症:拆分後部分查詢需要跨庫,性能下降。我們通過冗餘表和定時同步數據的方式,將跨庫查詢轉化為單庫查詢。
  3. K8s資源配置不合理:初期給服務分配的CPU和內存不足,導致頻繁OOM。後來通過Prometheus監控資源使用率,動態調整Deployment的resources配置。

四、總結

微服務遷移不是“一蹴而就”的工程,而是一個“循序漸進”的過程。核心原則是“小步快跑、快速驗證”,先從簡單模塊入手積累經驗,再逐步攻克核心業務。遷移過程中不僅要關注技術實現,還要協調團隊協作,比如開發、運維、測試的聯動。

遷移完成後,我們的發佈週期從一週縮短到一天,故障影響範圍也縮小到單個服務,集羣的擴展性和可維護性大幅提升。如果你所在的團隊也面臨單體應用的瓶頸,不妨按這個步驟試試,關鍵是勇敢邁出第一步,在實踐中不斷調整優化。