ADR 002 遊戲伺服器是否應該知道交易伺服器的節點
- Status: 📝Proposed
- Maker:
GameServer,Transaction ServerDeveloper - Refs:
none
Context
若採取轉帳錢包,我們就必須負責維護用戶的餘額。本質上,我們也成為了一個平台。
為了設計抗併發系統,我認為可以使用 K:N:M 的概念來描述整個系統下的節點:
- :Player(玩家連線數)
- :Game Server(遊戲邏輯節點)
- :Transaction Server(交易/帳務節點)
且預期是 。
玩家數量()可能來自我們平台或是外部平台:數量極大、行為不可控,系統上主要的交易來源。
如果交易服務 是一群主機構成的叢集,則遊戲伺服器是否應該直接知道交易伺服器的位置,還是統一由 Loading Balancer 進行分發?
而遊戲節點()比交易節點()多的原因是因為:
- 承接玩家連線
- 維護遊戲狀態
- 處理遊戲邏輯
先假定遊戲伺服器是個純函數1,則 Scale 會十分簡單,因為同樣的輸入,無論在什麼時間、哪一台機器上計算,結果都一樣。
傳統上,Game Server 困難在於它保存了玩家進度、當前局面與中間狀態。一旦節點掛掉,狀態就丟失,必須靠狀態同步或複寫等機制來維持連續性。Pure Function 把這些責任移除。Server 只負責計算,不擁有任何不可重建的資訊,因此節點可以隨時被替換。
若能做到,則一致性問題被自然消解,Scale 的目標變成「吞吐量足夠」,也就是每秒能算多少次:負載高時多加節點,負載低時縮減節點,系統行為保持不變。
但實務上,玩家餘額確實是狀態,且通常在整個系統裡都是不允許混亂。
如果遊戲邏輯只處理:在這個 seed 與下注條件下,結果是什麼?
- 玩家現在有沒有錢
- 這筆錢能不能扣
- 這次扣款是不是重複請求
- 這筆交易要不要入帳、可不可以回滾
這些就是交易語意,不是遊戲的語意。
Options
考慮到系統的併發數量,K:N:M 的設計中,我認為只要關注遊戲節點 N 的 Scale 行為,因為交易應該只是維護玩家的餘額,相對起來應該僅僅是記憶體操作。
主要的 Scale 行為發生在遊戲節點,交易節點也可能會 Scale,但是對於 N 的敏感性應該要小很多。
- 每個玩家同一時間只允許一筆交易
- 每筆交易只做餘額加減
- 每次處理非常快
當玩家數量大幅增長時,要考慮的行為是:
- 成千上萬個「彼此獨立、但同時到達」的交易流
- 每個玩家的交易要序列處理
- 不同玩家之間不需要序列化
我想導入的是Lock-Free 設計,將鎖的使用與系統間的狀態同步最小化。在過往 C++ 經驗設計,我傾向於使用 Bucket Sharding 機制:

假定玩家透過 Loading Balancer 到任意的遊戲節點,會透過「某種算法」,確保玩家只會到同一個交易節點上。
玩家或錢包的集合:
交易節點的集合:
從玩家的屬性中可選出一個key:例如 UUID
為每一個交易節點計算權重:
存在一個 routing 函數,用以計算玩家的交易去哪個節點:
同時考慮分配到 的玩家交易集合
對同一個 Key,所有交易是存在交易順序的:
且不同的 Key(=不同玩家),所有的交易是可平行化的:
這樣就可以做到「不使用鎖、減低主機之間狀態」同步,因為特定玩家提交的交易只會在特定的主機上。
考慮到節點變動時:,交易節點的集合從 變成 時,應該進可能保證遷移節點的玩家數量遠小於總玩家數量
遊戲伺服器應該知道交易伺服器的節點
在這個方案中,Game Server(N)能取得 Transaction Server(M)的節點清單,並在本地執行 routing 決策。Bucket 的歸屬與節點選擇,都發生在遊戲層。
遊戲伺服器不應該知道交易伺服器的節點
在這個方案中,Game Server 只把交易請求送到一個固定入口,例如 Gateway、Router 或 Transaction Selector。
Comparison
| 面向 | Option 1 | Option 2:Gateway Routing |
|---|---|---|
| Routing 決策點 | 遊戲層(N) | 交易層入口(Gateway / Frontend) |
| Game Server 是否知道 M 節點 | 必須持有完整的節點清單 | 不需要,只知道固定入口 |
| HRW 執行位置 | 每一台 Game Server | 集中在交易層 |
| Bucket 歸屬決定時間 | 請求送出前(N 已決定) | 請求進入交易層後 |
| Bucket 語意可見性 | N 與 M 共同承擔 | 僅 M 內部承擔 |
| 交易 Scale 影響範圍 | 跨層事件:N 與 M 都受影響 | 單層事件:僅 M 內部 |
| Scale 時的需求 | 所有 N 必須同步看到同一個 M | 只需 Gateway / M 看到一致 M |
| In-flight transaction 處理 | 複雜,需定義切換語意 | 相對單純,入口可序列化 |
| 送錯節點的風險 | 存在,需補救策略 | 幾乎不存在 |
| Network hop | 少一跳 | 多一跳 |
| 延遲特性 | 最低延遲 | 可預期但略高 |
| Gateway 成為 SPOF | 否 | 是(需 HA) |
| 遊戲的責任複雜度 | 高(routing + discovery +一致性假設) | 低(只送請求) |
| 交易的內部是否好重構 | 低(需對外同步) | 高(可透明重組) |
| 適合的系統文化 | 去中心化、基礎設施成熟 | 風險保守、邊界清楚 |
| 出事時影響半徑 | 大(容易跨層) | 小(侷限於交易層) |
SPOF = single point of failure, 單點故障