yivgame
Yivgame是用go語言基於go-kit寫的一套微服務架構遊戲服務器方案,它不是一個框架,是一整套遊戲服務器實例,每個模塊只保留了一份示例代碼實現。除了遊戲服務器(長連接),還包含針對前端和後台運營的API接口服務器,運營後台的界面會使用Angular實現。 除了服務器本身之外,還會涉及docker部署的詳細配置。
特性
- 微服務架構
- 客戶端與遊戲服務器通過grpc 雙向流(bidirectional streaming)實現透傳
- 客戶端與服務端websocket通信
- 實現http endpoints 和websocket endpoints 度量衡和日誌
設計實踐
微服務架構
- 通過微服務架構,將傳統的遊戲服務器拆分成了不同的微服務
- 不同的微服務器可通過grpc進行同步通信和通過kafka進行異步通信
領域驅動模型
- 為了實現軟件不同層次的解耦,各服務統一都參照領域驅動模型進行設計
- 將微服務的軟件結構由內至外分成遊戲領域業務層、用例層、接口層和設施依賴層
- 各層之間嚴格遵守由外向內的單向依賴
業務層主要實現遊戲或服務器的是核心邏輯,不關心外部實現,對文件系統、數據庫等的依賴,業務層使用interface定義接口,由依賴層實現接口方法,並在main中通過依賴注入的方式傳遞給業務層調用。所以業務層除了引用一些基本的標準庫外,幾乎不引用第三方包。
事件驅動模型與Kafka
- 整個微服務系統之間的核心通信方式是grpc同步調用和以kafka作為流平台的異步事件通信
- 所有微服務內的被關注的活動都會以產生事件的方式發佈到kafka,具有不同關注點的消費者分別訂閱各自感興趣的事件,kafka將事件推送給消費者,消費者做相以的事件處理和響應
事件驅動模型與數據分析
- 以前做遊戲數據分析的時候,都是通過聯表查詢
- 使用事件驅動模型後,可把我們關注的玩家行為都作為事件記錄下來,為不同的事件設計不同的屬性,實現極具擴展性的數據分析


工程目錄結構
- 由於由於go-kit實現的微服務架構,所以在目錄結構上盡量與go-kit的官方示例保持一致
- 由於領域驅動模型是分層的,所以在設計工程目錄結構的時候很自然的會把內層包的目錄包含在外層內,由於我比較喜歡大多數go工程的偏平目錄結構,所以沒有嚴格按著領域層次來設計目錄結構,反而是把不同層次包的目錄放在了同級的目錄下面,對我來說,這樣顯得直觀、簡單一些
無全局變量
- 為了讓軟件在代碼邏輯上面更加清晰,嚴格避免全局變量
數據緩存、數據存盤與Kafka
- 所以玩家數據直接保存在服務內存中,便於直接進行數據處理
- 玩家數據的修改通過WAL方式寫入kafka,再由存盤服務異步地寫入數據庫
- 由於使用了WAL方式,則玩家數據的redo和undo則很容易實現
NewSQL CockroachDB
- 數據持久化使用支持分佈式事務的關係數據庫CockroachDB
- 使用CockroachDB可以很輕鬆的實現水平擴展、故障容錯和自動恢復、負載均衡
我從v1.0開始使用CockroachDB,從v1.0到v1.0.6,CockroachDB在特定情況和壓力下,一直存在崩潰的問題,自從v1.1發布,崩潰問題沒再出現,但是性能一直沒有大的改善。因為yivgame的數據幾乎都存在內存中,只有存盤的時候需要寫db,所以對整個yivgame系統來說,不存在db性能瓶頸。
模型
通信圖

- 通信方式
- HTTP:http為作短連接,主要用於後台運營系統的通信,另外,遊戲中涉及到強交互的數據通信部分,也可以用http來通信
- WebSocket:客戶端使用cocos creator開發,長連接通信支持WebSocket,WebSocket主要用於遊戲中實時和強交互的難通信
- GRPC:基於HTTP/2協議GRPC,可以實現在一個socket連接上進行多stream通信,是go微服務生態中比較通用的通信方式
- 數據格式
- JSON:由於json格式的自解釋性,主要將它作在遊戲中短連接和後台運營系統接口的數據交換
- Protobuf:主要用於客戶端與服務端websocket間和微務間的數據交換
服務組件圖

- Agent:主要用於客戶端的接入,它直接將數據報文透傳、轉發到後端微服務,是一個傻網關、薄網關,幾乎不參與業務邏輯和編解碼業務數據,所以其代碼邏輯相對簡單,也極易進行水平擴展
- UserCenter:所有玩家數據集中在user center 進行管理,由user center 負責遊戲數據的讀寫刪改查,它提供grpc接口供apigate、game server等其它需要請求玩家數據微服務使用
- Game Server:主要負責遊戲業務邏輯的處理
身份認證與鑑權

- 服務與服務之間使用jwt進行身份認證
- 通過API gateway實現單點登陸
- 全局認證,每服務鑑權(do authentication globally, and authorization in every microservice)
設施依賴
- docker:所有依賴設施服務和遊戲實例通過docker社區版進行佈署
- rockcoach:作為持久化數據庫
- kafka:作為message queue和stream platform
- etcd:用於服務發現
- gogs:使用gogs進行版本管理
- bind9:域名服務器,通過切換域名解析實現開發、測試網絡的無縫切換
go-kit generator
- yiv/gk: go-kit 代碼生成器是一把手電鑽,go-kit目前發現的唯一的不爽就是寫一個服務太囉嗦,它設計了一套很優雅的服務輸出方式,但為了寫一個服務接口,每個都要寫一整套endpoint、set和transport,代碼模式都是相同的,這一大坨大坨的代碼基本相似,寫多了就會很煩燥,感覺在做重複的工作,而且極易出錯,找了幾遍都沒有發現完全符合go-kit官方示例的generator,最後選中了kujtimiihoxha/gk,但它還沒有很多地方並不完善,也不完全適用於我,於是fork下來自己改,通過它自動生成代碼,在寫服務接口的時候可以美減少60%的重複代碼,更重要的是,它極大地降低了出錯的概率。
系統環境
參考
- gonet/2: yivgame從gonet吸取了很多設計,如使用stream進行透傳、引入kafka等
- go-kit: yivgame基於go-kit開發
- goddd: 一個用go寫的基於領域模型的樣例APP
- Practical Persistence in Go: Organising Database Access
- The Clean Architecture
- Applying The Clean Architecture to Go applications
- 一篇文章讀懂分層架構
關於設計的一些思考
- 系統的複雜性只會轉移,不會消失,直白簡單背後都是臟活累活,簡單都是有成本的,要么降低性能、要么迴避一些特性如擴展性,要么由其它人來做,使用go-kit 的好處是它看起來不那麼簡單,把設計目標直接體現在代碼裡,學習使用go-kit 有助於提高軟件設計能力
- go-kit 不適合追求易上手、短平快的目標,它使用分層來分離關注,必定引入複雜性,代碼看起來可能穿插囉嗦,但是關注分離的設計有助於邏輯解耦
- go-kit 始於service interface,始於關注業務領域,http 或是grpc 只是對外發布的方式,放在最後處理
- 不追求寫法上的自由,要追求軟件適應性的自由,自由不是說我想怎麼寫就怎麼寫,代碼要有規範,團隊要統一編程風格,才便於溝通,go-kit 不自由,因為他定義了自己軟件的設計範式,導致的結果就是用它寫出來的服務,看起來都長得差不多。牛逼到把代碼寫得像幅畫,還能編譯運行,只適合玩,不適合拿來做工程。
- 微服務調用的編解碼和通訊,引入的延時成本大概為2 毫秒,國內互Ping 延時通常為40ms
- 無論是框架還是語言,都只是工具,挑一個業界優秀的、趁手的、合適的,用好用熟用深,沒必要把時間花在爭論和糾結最好的幾個當中哪個是才是最好的,即使工具是最好的,用得不好也是爛,關鍵是學習和掌握它們的設計精髓,才能萬變不離其宗