網路遊戲中的分散式事務

因為我們新開發的遊戲是全球通服,所以最近一直在跟分散式事務的數據一致性作鬥爭,來回琢磨了很多種方案,比如引入消息隊列,或者強化現有RPC框架,或者打散數據用map-reduce。

來回折騰琢磨的原因是,始終找不到徹底解決問題的方案。我所希望的徹底解決是開發人員在進行業務邏輯開發的時候不需要心裡掛著分散式事務可能會有不一致或者消息重傳要有冪等性這樣的事情,就像開發單機功能一樣好像數據就在手邊,事務也在手邊。

這裡我分享一下我對幾種方案的思考,每個項目的情況不太一樣,說不定在我們項目里是個問題的事情,在別的項目里不是個問題,有的方案就可以派上用場。

前提條件

開始之前,我得先說一下網路遊戲為什麼會有分散式事務,並且會是個問題。

假設我們做遊戲服務端的時候一開始用的就是一個可以橫向擴容並支持分散式事務的資料庫,比如TiDB,很可能是不需要糾結這個問題的。

但是奈何遊戲實時性要求高,所以遊戲通常會有一層緩存,而我們為了讓實時性更好,並且也為了讓緩存的事務控制更簡單,選擇了將緩存做在業務進程內的方式。

這樣導致的結果就是各業務進程間是互相不共享數據的,當一個進程需要操作到其它進程管轄範圍內的數據的時候,就需要走RPC。

舉個簡單的例子,比如說遊戲裡面工會管理員通過一條加入工會的申請,這時候會有兩步操作,一步是在工會伺服器上刪除申請記錄並添加一條工會成員記錄,接著會發起RPC到玩家所屬伺服器上更新玩家所屬工會。

如果上述過程中RPC因為網路故障而沒有執行成功,或者玩家所屬伺服器上更新數據因為程序BUG而失敗了,就會出現數據不一致的情況,而數據不一致又會進一步的引發像一個玩家加入了兩個工會這樣的BUG,所以數據不一致會是個問題。

換個角度看,如果緩存是獨立的,並且是業務進程間共享的,就可以在緩存面實現分散式事務,而不需要把分散式事務的存在感傳遞到業務邏輯這邊。

支持分散式事務的緩存系統是個美好的願望,我自己也還沒想清楚,所以今天要講的是怎麼躲債而不是怎麼還債。

消息隊列

我最先想到的是引入消息隊列,通過消息隊列的重傳機制來實現事務的一致性。

但是消息隊列的重傳機制是很傻的,它能做的就是沒收到回應就一直重發。這個實現方式會導致消息的消費端需要實現冪等性,冪等性用人話說就是允許重複執行。

作為一個以給開發人員減輕心智負擔為己任的CTO,顯然是不符合品味的,這相當於是給手下的程序員們挖坑嘛,從此以後每天就得掛著這個事情,哪天精神狀態不好可能就產生了嚴重的BUG。

於是我就想到統一生成ID,統一記錄下消息是否已處理,這樣程序員不用知道有這樣一個事情,功能開發的難度應該跟目前用RPC實現是同樣的複雜度。

但是運維的兄弟就得填坑了,得維護一套消息隊列群集。(說得好像真有運維一樣,其實我們這種小作坊早就是DevOp了,我們還CtoDevOp呢)

我想來想去想到個偷工減料的法子,增強現有的RPC。

增強RPC

增強RPC這個事情我是這樣看的。

之所以要引入消息隊列來做重傳是因為現在項目里用的RPC框架太簡陋了,網路故障就放棄調用了,沒有重傳機制。

如果我在現有RPC框架中加入重傳,不就不需要消息隊列了嘛。並且對已開發的功能修改量極小,甚至可能都不需要修改。

進一步我想到消息持久化的問題,如果是把消息放內存里排隊,萬一進程掛了那不還是丟消息了。正好我們自己有實現一套緩存事務機制,在事務提交時順便把需要保證送達的RPC消息落地了,不就讓整個系統更穩固了。

這時候進一步給自己挖坑了,數據二進位格式落地不方便查問題啊,弄個JSON落地吧。噢,還有個問題,萬一在系統更新的時候把RPC參數給改了呢?這個得記下了,系統更新時候得等RPC隊列都空了才開始。

開發人員輕鬆了,我可不輕鬆了,邊界情況老多,我也不喜歡做有心智負擔的事情啊。正準備開始改RPC代碼的時候,又想到一個事情。

RPC可以處理網路故障,但是處理不了業務故障,如果一個消息送到對方那,對方業務邏輯BUG導致執行失敗了,一直重傳也沒用啊。

發警報人工處理是可以,但是實時性不高,這期間不還是數據不一致嘛。

然後又開始想怎麼偷工減料了。

Map Reduce

跟組裡兄弟討論這個事情的時候我開了個大腦洞,如果每個人只拿到局部的數據,只操作自己的局部數據,不就沒有分散式事務了嘛。

比如工會成員每個人都記錄著自己是哪個工會的,當我需要查詢工會成員列表的時候,就發起一個Map Reduce,到所有節點上查詢並匯總,就能得出一份工會成員列表。

工會管理員獲取工會加入申請列表的時候也是一個Map Reduce,匯總回來一個申請列表。當管理員通過其中一條申請的時候,發起一個RPC到這條申請所在伺服器上刪除申請並修改玩家數據,事務只發生在一個節點上。

多麼美好啊!但是Map Reduce過程中如果有個節點掛了呢?我是等它恢復還是不等它恢復呢?不等它恢復業務數據就不完整了,等它恢復我的下場就跟曹操打赤壁之戰一樣了。。。

數據集中

討論的過程中一開始就有兄弟提到要不把數據集中起來,業務相關數據存到對應伺服器上,而不是玩家進程有一部分,業務進程有一部分。

我一開始直接否掉了這個想法。

當時的判斷是,如果要知道玩家屬於哪個工會,一樣需要記錄在玩家所屬伺服器上,那必然還是有一個分散式事務,工會伺服器和玩家所屬伺服器都要參與。

但後來結合Map Reduce這個腦洞之後,想到一個可能性,就是不在玩家伺服器上記錄玩家屬於哪個工會,而是通過Map Reduce查詢玩家屬於哪個工會。

這樣Map Reduce只用在局部,比較容易針對性的解決邊界情況。而業務這邊就可以做到數據集中,這樣可以讓一些本來要RPC才能完成的業務變成本進程內業務。

單點設計

上面說了好幾種思路,但是終歸還是避免不了分散式事務,因為根源我前面說了,架構不改是不可能完全避免分散式事務的,就算集中業務的設計,也還是會有需要RPC的業務,這是債得認,出來混就遲早要還。

但是既然現在已經是靠躲債過日子了,有沒有辦法讓這樣的日子過得瀟洒一點呢?

辦法還是有的,神經大條一點把業務設計成單點,邊界問題最少,心智負擔最輕,RPC量最少。

比如幫派設計成一個單點的幫派進程,好友設計成單點的好友進程,現在伺服器配置這麼高,內存大大地有,這種業務又不是CPU密集型的,單點唯一的問題就是掛了大家都不能玩了,但是這跟前面說的火燒連營不一樣,隻影響單一業務,並且因為是單點,故障了反而最容易處理。

搞遊戲又不是搞核電站,單點說不定真的挺好,沒必要自己過不去:)

最後

上面說了幾種方案,總之都不是徹底根治的方案,我目前在項目里只能對症下藥,比如一些RPC的消息是不需要可靠性的,像跨服聊天之類的;有些RPC是需要可靠性的,就走可靠RPC和重傳機制;有些業務是比較容易做成數據集中的;有些業務可以接受單點故障;

具體怎麼做見招拆招...

P.S:此外還有一點,我在開發過程中發現,架構也沒辦法完全磨平互動功能的天生複雜性。

比如工會管理員通過申請這個事情,就有可能存在並發的情況,兩個管理員同時點了同一條申請,如果開發者沒有考慮到會有這種情況就有可能出問題。

推薦閱讀:

TAG:游戏服务端 | 游戏服务器 | 服务端 |