跳至主要內容

07 / WORK / CASE STUDY

大型 Django 微服務全通路商務平台

33 個 Django 微服務支撐電商、多租戶門市 POS、B2B 採購、業務 CRM 與獎金、線上課程與設計師共創六大業務域;營運中。

TYPE
BACKEND
SCOPE
公司內部 · 營運中
STACK
DJANGO · DRF · CELERY · MYSQL · REDIS · NGINX
LINKS
公司內部專案(不公開)

01 / PROBLEM

同一家零售業者同時經營線上商城、連鎖門市、企業批發與外勤業務團隊:四個通路各自產生訂單、動用同一批庫存、面對同一群會員、走同一套金流。各建一套系統的結局是庫存超賣、會員身分割裂與對帳災難。這個平台以領域切分的微服務承載:每個業務域一個 Django 服務、一個獨立資料庫,跨域以帶重試的同步 REST 與 Celery 非同步任務整合;金流、電子發票、物流等對外整合集中在專責服務,其他服務不觸碰第三方憑證。

02 / CONSTRAINTS

  • 公司內部專案,程式碼不公開;所有規模數字皆可在 repo 內以指令重跑驗證。
  • 裸機部署、無雲端託管預算:沒有 K8s,健康檢查、依賴順序三階段啟動與 Celery worker 管理全由自製啟動腳本承擔。
  • 多租戶隔離是硬性要求:門市 POS 每租戶一庫,租戶 header 只能當「聲明」,權威必須來自 JWT 簽章內的 claim。
  • 營運中系統不可停機:金流與訂單鏈路的修復必須在不中斷交易下上線。

03 / ARCHITECTURE

多服務架構——不是單體拆分,而是逐域生長:Django 4.2 LTS + DRF,33 個服務、807 個資料模型、729 個 ViewSet 註冊加 2,203 個自訂 action,Python 主體約 153 萬行(不含 migrations)。選多服務的核心理由是發布節奏與風險容忍度:金流服務的變更風險和內容服務完全不同,獨立部署邊界讓高風險域能單獨小步發布。

認證採 RS256 非對稱 JWT:單一簽發服務持私鑰、其餘服務只驗公鑰、不回查使用者庫,權限碼在簽發時注入 token。每服務一個 MySQL 庫、三個 Redis 實例分工(快取/Session 與分散鎖/Celery 佇列);跨服務只存對方主鍵、不設資料庫外鍵,ID 用 Snowflake 並以字串序列化防 JS 精度問題。每服務一個 Celery worker、5 個服務帶 Beat 排程;金流、電子發票、超商與宅配物流、FCM 推播、社群登入集中在專責服務。CI 之外另自建每週四層回歸:健康閘 → 218 端點唯讀煙霧基線比對 → pytest → 跨服務 E2E。

04 / RESPONSIBILITIES

主導開發:2025 年 1 月專案創始即參與,2025 年 9 月起為唯一主力;全期 731/825 個 commit(89%,兩組 git 身分皆為我)。範圍涵蓋服務架構與新業務域建置(POS 多租戶、B2B、業務 CRM 與獎金歸因、課程、設計共創)、跨平台資安/效能/正確性稽核修復、回歸測試基建與部署維運。

05 / CHALLENGES → SOLUTIONS

CHALLENGE

稽核發現金流回調的驗簽實作與金流商規範不符——所有合法回調都會被拒,付款成功但訂單狀態不同步;且付款成功只更新電商主訂單,B2B 與 POS 通路沒有任何同步路徑。

SOLUTION

重寫簽章演算法對齊規範;新建「付款後通路同步」單一入口,依訂單來源分流到電商/B2B/POS/訂閱四條路徑,並加入冪等分支讓重複回調自癒而非重複入帳;以金流沙盒的真實回調驗證閉環後上線。

CHALLENGE

多個服務的非同步任務看似正常註冊、無任何錯誤日誌,實際上從未執行:任務投遞到 TASK_ROUTES 宣告的佇列,worker 卻監聽另一組佇列名,訊息安靜堆在無人消費的佇列裡。同類錯配在 4 個服務獨立發生——告警長期未寄出、發獎鏈整條斷裂、圖片變體全停。

SOLUTION

逐服務比對 TASK_ROUTES 與啟動腳本的 -Q 參數,統一為服務專屬佇列名慣例並修正啟動腳本;修復後以 Beat 實跑驗證告警真實寄達、發獎鏈 E2E 冪等通過。

CHALLENGE

平台長到 30+ 服務後,熱路徑劣化無人統測:登入實測 4.08 秒(每次登入同步遠端查權限),庫存統計端點因 threading.Lock 重入自我死鎖直接卡死 20 秒。

SOLUTION

先建 curl 基線入庫,再分波修復 28 個服務、逐項複測:登入 4.08s → 0.55~0.75s(約 5–7 倍),庫存統計 20 秒卡死 → 0.014s(Lock 改 RLock 解死鎖),全程零新增 5xx;並確立 gevent worker 下 CONN_MAX_AGE=0 為正解、全平台統一。

06 / SYSTEM FACTS

33

微服務(每服務獨立 DB)

807

資料模型

16,433

後端測試函式

89%

個人 commit(731/825)

07 / LESSONS

佇列名是一種契約,而人工同步的契約一定會斷:同一類 routes 與 worker 錯配在四個服務獨立重現,因為佇列名寫在兩個檔案裡靠人肉對齊。真正的修法不是修四次,而是讓啟動腳本從服務自身設定生成佇列參數——單一事實來源。

效能要有人擁有基線,否則劣化是安靜的:登入 4 秒、統計 20 秒不是一夜發生,而是在沒有量測的日常中逐步累積,直到專項稽核才被看見。重來會在服務數突破十個時就建立熱路徑基線與定期複測,而不是等到 30 個。

NEXT

多租戶零售收銀系統