歷史包袱重構向來棘手。前幾日討論《夢幻西遊》伺服器優化,這幾天赴廣州駐留一周專攻。過去只網聊,面對面才能真懂問題。
現況與痛點
- 單機架構,最高配 8 CPU + 8GB RAM
- 最熱門伺服器僅用滿 3 CPU、一半記憶體 (同時在線 1.2 萬)
- 核心程式沿用《大話西遊》十年前代碼,近年靠硬體升級免費紅利維持
- 核心問題:定期出現回應遲鈍,週期性超過 1000ms
- 元兇:磁碟 I/O 嚴重擁塞——定期存檔玩家資料服務 + SA 定期備份腳本佔用大量 I/O,導致負載過重
舊系統架構剖析
代碼量小 (相關服務僅數千行 C),因牽涉百萬用戶資料與計費流程,極度重視穩定,無人敢動。「歷史原因」只能飯局吐槽,重設計絕不會這樣寫。近兩年對重構興趣索然,新想法留給下一代系統。穩定運行多年的陳舊系統,重構意義不大;關鍵在最小影響現有系統前提下增強效能。模組清晰劃分與服務獨立性至關重要。現況:資料服務、計費、鑑權竟在同一程式,導致資料讀寫剝離極難。
資料服務架構缺陷
- C/S 架構,但無資料庫,直接用本地檔案系統
- C (遊戲主進程) 單一;S (資料服務) 可多實例並發
- C-S 間用管道傳命令、共享記憶體交換資料 (為 IPC 效能)
- 協定設計缺陷:C 直接操控資料區且唯一,導致資料區塊管理邏輯置於 C 端而非 S 端
- 例:C 載入玩家資料時,先在資料區找空位,再通知 S 載入至指定位置;清理也由 C 負責
- 結果:S 無法直接在資料區做 Cache;玩家離線資料需 C 自行 Cache 或額外建 Cache 服務 (雙倍記憶體+複製開銷)
- 最終:全無 Cache,完全仰賴 OS。萬級在線後問題暴露——為最終需求定制才能榨乾硬體效能
自研記憶體 Key/Value 資料庫設計
為實現「僅存差異資訊」策略 (實測減 90% 寫 I/O),必須統一資料讀寫服務位置,脫離本地檔案系統。評估 Redis 等後決定自研:高度定制演算法、極致發揮硬體、代碼量可控 (目標 500 行 C,實際 300 行)。
需求分析
- 服務每周停機一次
- 每周涉及 10 萬組玩家資料
- 單組 4KB~32KB 文本資料
- 視為 id→資料串的 K/V 存儲
- 總資料可全載入記憶體
- 資料頻繁更新、長度變動
核心設計
- 預分配整塊記憶體,切 1KB 區塊,單向鏈表串聯
- 鏈表指標用序號而非記憶體位址:64 位系統仍用 4 字節索引 (可管 4TB),省空間
- 指標區與資料區分離:多數操作只訪問指標,命中率高
- 單向鏈表省一半指標操作與記憶體,代價代碼稍繁
- 區塊分空閒區與已用區:初始全空閒;寫入從空閒鏈表取區塊移至已用鏈表尾;Cache 滿則從已用區頭部回收 (最久未訪問);讀取時將區塊移至已用區尾 (LRU)
- Hash 表映射 id → 區塊段頭 (存前驅節點),O(1) 查詢
- 資料在記憶體無需連續,類磁碟叢集管理;記憶體隨機/順序訪問差異小,有利空間利用率