聯網戰鬥同步優化

回顧

在上篇文章,主要講述

  1. 聯網戰鬥的簡介
  • 網絡傳輸協議
  • 網絡同步模型
  • 網絡拓撲模型
  1. 實現聯網戰鬥的方案
  2. 實現時的一些重點處理
  3. 實現後的一些優化改進暢想
  4. 我的感悟

更詳細的內容,請跳轉:聯網戰鬥同步實現




正文

之前實現了一版聯網戰鬥方案,還比較粗糙,存在許多不足的地方。
秉承着 先實現,再持續交付、快速迭代 的理念。
由於實踐的效果不是很好,所以需要做一版優化。


優化方案

總覽:

  1. 輪詢機制都改爲即時
  2. 幀推送間隔時間修改
  3. 建立幀緩存機制
  4. 擴充操作幀上傳時機
  5. 完善的統計機制
  6. 自動化工具完善

輪詢機制改爲即時

在捋清各個流程步驟時,發現有些輪詢機制,會導致延遲的增高。

  • 邏輯處的輪詢
  • 協議收發的輪詢

輪詢的作用是減輕各模塊功能的壓力,降低成本。
我個人是覺得,在實現功能的時候,對於這種可控性成本需求,先按最優版本實現,做出我們最優效果。
之後,再根據成本等問題,進行打折簡化。
我們做好對比數據的提供,交由項目負責人決策。


幀推送間隔時間修改

之前,爲了讓客戶端壓力小一些,處於“飢餓”狀態,服務器推幀間隔設置爲 35ms。
但是,在有些模塊進行邏輯處理依舊以 30幀/秒(固定間隔 33.3ms)處理數據。
會導致部分客戶端邏輯 與 實際有差異,造成抖動。

因此,將幀推送間隔時間修改爲 33ms,減緩抖動。


建立幀緩存機制[重點]

基礎方案,過於依賴網絡狀況。
顯然這樣是不現實的,所以需要做一套幀緩存機制,來應對網絡的波動。


釋放幀的時機:

  • 邏輯幀執行時
  • 繪製幀執行時
  • 邏輯幀執行時釋放一次,繪製幀執行時根據需要補充釋放

釋放幀策略:

  • 釋放一次,每次釋放一幀
    • 可能導致幀積壓,越來越卡
  • 釋放一次,每次釋放幀數量可變
    • 每次釋放幀數量根據幀緩存池內幀數變化
  • 釋放多次,每次釋放數量可變
  • 緩存幀模式,緩存幀數量可配置
  • 緩存幀模式,緩存一幀
    • 如果緩存幀數量可配置,就需要做一個讓所有配置相對平衡的策略;單做緩存一幀,可以更細緻的制定策略
    • 但是經用戶體驗,發現這個策略並沒有比大鍋飯更好,甚至更差。。
  • 釋放一次,每次釋放所有幀

可配置緩存

第一版做的是可配置緩存。
緩存幀的數量可配,通過實際體驗來決定具體緩存幀的數量。
(最開始設想是,先做成可配;之後再根據實際網絡狀況,動態修改配置緩存的幀數量,類似於流媒體領域的 JitterBuffer)
對於釋放幀的數量方案是:

緩存幀數量爲 n
幀池內幀數量爲 m

  • 若 m <= (n+1), 釋放1幀
  • 若 m <= (n+1) * 2, 釋放2幀
  • 若 m <= (n+1) * 3, 釋放3幀
  • 若 m > (n+1) * 3, 釋放 (m-1)幀

流程圖:
緩存配置幀


緩存一幀

經過實際體驗,緩存一幀是比較理想的,大於一幀的延遲,大家接受不了。
之前的釋放幀策略是針對緩存n幀的普適規則,既然定下來緩存1幀,就做一個更爲詳細的策略。

先說結論再說步驟:
幀池內幀數量爲 m

分爲四個階段

  1. 正常運行階段
  • 0 < m <= 2, 每次釋放1幀
  1. 大步調優階段
  • m <= 5, 釋放1次釋放2幀
  • m <= 8, 釋放1次釋放3幀
  1. 小步快跑階段
  • m <= 14, 進入快跑模式, 往後每次釋放k幀, 直到恢復正常, k = math.ceil((m+1) / 2)
  • 當不足以釋放k幀時,釋放 k-1 幀, 退出快跑模式
  1. 瞬間傳送階段
  • m > 14, 釋放 (m-1)幀

流程圖:
緩存一幀

這個是怎麼算出來的呢?
我列了一個表格,

列爲:
幀池內幀數量 | 延遲時間 | 釋放幀數量 | 釋放後幀數量 | 下次釋放幀時,幀池內可能幀數量 | 恢復正常所需要快播次數 | 往後每次釋放幀數量

行爲:
從1-15, 每幀33ms,15幀大約爲 495ms

接下來開始填釋放幀數量,依據以下原則:

  • 每次釋放幀數量要保證在3幀內
  • 恢復正常所需要的快播數,保證在3次內
  • 儘可能平緩的恢復
  • 若無法滿足,更傾向於保證3次快播,恢復正常

然後就開始填表格。
有一點遺憾的是,沒有卡到300ms 和 500ms,都差一幀。



擴充幀上傳時機

之前方案中,客戶端收集完幀,上傳時機在於執行邏輯幀前:先上傳之前的操作,再去執行幀操作。
擴充爲三個方案:

  • 執行邏輯幀前
  • 執行邏輯幀後
  • 執行繪製幀前

完善的統計機制 [重點]

我個人一直特別推崇數據驅動,數據不會說謊,除非數據收集的粒度與廣度不夠。
建立一套完善的統計機制,通過數據來分析用戶狀態、行爲,進而輔助設計,優化體驗,做到有依據有目的有驗證標準的方式的去解決問題是非常必要的。

統計數值大概包括:

  • 基本信息
    • 機型
    • 操作系統
    • 位置信息
    • 連接的服務器位置
    • 戰鬥開始時間
    • 戰鬥類型
    • 掉線標記
  • 服務器項(分爲兩部分,客戶端發往服務器&服務器發往客戶端)
    • 丟包率
    • 重發率
    • 協議平均延遲
  • 客戶端項
    • 快播率
      • 釋放幀時幀數大於1幀
    • 卡死率
      • 釋放幀時幀數等於0幀
    • 正常率
      • 釋放幀時幀數等於1幀
    • 幀間隔值
      • 收到幀間隔
    • 幀間隔波動值
      • 距離幀應到時間的差值
    • PING值
    • FPS值
  • 幀操作的一生
    • 操作的產生
    • 操作的上傳
    • 操作的下發
    • 操作的執行

針對每個數值,都可以包括更詳細的項:

  • 最大值
  • 最小值
  • 閾值達到次數
    • 100%
    • 70%
    • 50%
  • 平均值

注意:

  • 計算平均值的時候,超出閾值的取閾值,避免一些極端數值影響平均值。
  • 這裏我採用了python的 pandas模塊去處理並分析數據

自動化工具

判定&收集不同步

首先對戰鬥產生日誌分級存儲:

  1. 【同步】幀處理,隨機數,實體狀態 相關
  2. 【部分同步】實體添加刪除,BUFF添加刪除等(包含1的所有內容)
  3. 【不同步】時間戳,其他同步信息打印(包含1,2)

注意:

  • 包含關係,越往下越詳細,而且下一級的內容包含上一級的所有內容,即當一條信息在 2級日誌內時,不會在1級日誌內出現,但會在3的日誌內出現。這樣做方便定位更詳細的日誌的各級位置
  • 【同步】、【部分同步】、【不同步】 代表同樣的戰鬥在不同客戶端上的日誌是否一致,【部分同步】表示不一致的內容只會在開頭&結尾,不會在中間出現

1級日誌內容會比較少(因爲會在戰鬥中生成,每場戰鬥要重置),是重點的同步信息。這份日誌主要是用來判斷各客戶端戰鬥是否同步使用的,同步判斷包括兩部分:

  • 隨機數取值一致
  • 實體狀態一致

2級日誌,是用來輔助1級日誌查找不同步問題,但是內容會相對3級日誌更偏重聯網戰鬥一些

3級日誌,就是更廣範圍的日誌,包含遊戲其他功能模塊的處理邏輯日誌打印

不同步判定&收集:

  1. 戰鬥結束後,客戶端戰鬥產生的1級日誌內容,壓縮爲MD5,上傳給服務器

  2. 服務器收集各客戶端MD5,進行比較;判定戰鬥不同步即分版本存儲本場戰鬥錄像(所有的幀操作)

  3. 任意客戶端可在debug模式中,拉取不同步的戰鬥進行回放現場


重現不同步

通過不同步收集,可以獲取到不同步的戰鬥信息。

剩下的就是重現不同,這裏採用的方式很簡單,就是不斷的跑這場戰鬥。

比如,跑100次戰鬥,將MD5不同的日誌收集起來,進行比對,進而修改,直至這100場戰鬥的日誌產生的MD5均一致。

其實,只要能重現BUG,就離解決不遠了,而且是能無限重現的BUG。


其他項

  • 前期測試時期,可以讓服務器把同步的戰鬥操作也存儲,然後拉去下來本地跑100場。

  • 100場也只是一個樣例,嫌多可以跑30場,200場,都隨意。

  • 善於利用閒置電腦,讓所有客戶端都能替你跑不同步測試

    • 我是在遊戲內開發一套debug工具,配合服務器,可以拉取各個角色的回放信息,並進行不同步測試。在戰鬥時,把自己帶入爲雙方中的任何一方視角進行戰鬥並輸出日誌。
    • 後期也支持,只跑邏輯,不跑繪製的戰鬥,這樣使得測試時間大大縮短。(還順便檢測出了一些BUG)


優化效果

通過最新一期的線上測試;大概近6000場戰鬥,總的不同步率 及 各戰鬥模式的不同步率 均已經降低到了 0.1%以下。(包含 1V1 PK,多人組隊戰鬥等)。

可能還是戰鬥場次不夠多,需要更大量的數據來檢測,但是,起碼目前已經到及格線,下一步就是調整優化延遲了。

聯網戰鬥就是這樣:

延遲調完調同步,同步調完調延遲。



下一步方向

現在已經算是一個閉環的方案了。

  1. 發現問題
  2. 重現問題
  3. 解決問題
  4. 驗證問題
  5. 收集問題
    • 主動收集
    • 被動收集
  6. 提前發現問題

各個方面都有解決方案了,剩下的就是基於這個基礎上再去不斷的優化完善。

在不同步上,目前待測試的:

  • 浮點數問題

不知不覺已經做了這麼多,可惜最後無法見證最終的效果,時也命也。

僅願:

​ 功成須獻捷,未必去經年。




番外

這些是在優化過程中遇到的一些問題,採用的一些方法,一些策略


番外:痛苦的不同步

每次接到不同步的反饋,都是異常痛苦的,就如之前所說。
同步模塊做的很多的事情都是大方向框架性的,具體的流暢問題、不同步問題,往往是負責最繁瑣複雜的發現問題的角色,
然後帶着發現的問題找相應模塊負責人,去反饋。(最麻煩的就是發現問題,一旦定位問題,距離解決基本不遠了)

但是,
同步方面的問題,往往是上線前無人問津,上線後銖錙必較。
一旦出了問題,雖然能把鍋分分鐘甩出去,但是最終留下來加班修改的還是自己;所以,解決這個問題纔是關鍵。

分析

上線前爲什麼會無人問津?

  • 代碼尚在修改,測試收效甚微
  • 數據尚未到位,測試範圍不足
  • 待到代碼和數據雙雙齊備,距離上線不足彈指一揮間,甚至上線後依然在修改調整
  • 測試方法繁瑣複雜

解決

  • 針對代碼修改問題;可以提高版本內相關功能優先級,先修改相關代碼,再做其他相關功能
  • 針對數據到位問題;可以分批次到位,分批次測試,而非最終一口吃個胖子
  • 針對測試方法繁瑣複雜;可以實現自動化測試,提供測試工具

自動化測試,本不應該屬於這片內容。
作爲聯網戰鬥的實現,處理同步問題,是重中之重。
處理方向有兩個:

  • 預防不同步,在出現前,扼殺在搖籃中
  • 解決不同步,當出現時,如何快速定位並解決

預防不同步,就是使用自動化測試的方案,針對策劃所配置的所有項都提前做好測試。做一個改動後,就相關影響的戰鬥,都多跑幾遍;就是把人力做的事情,通過技術來自動化跑。

解決不同步,可以分爲三個部分:

  1. 收集
  2. 重現
  3. 驗證

收集,就是當出現不同步問題的時候,我們一定要知道;

重現,就是我們要對現場進行重現,這樣就方便查找問題,進而驗證是否解決問題。

這些通過上面優化方案的自動化工具都已經初具模型。


番外:集思廣益

以前做東西,習慣悶頭開發,因爲爲了做這方面的內容,查閱資料,實驗功能,做了各種各樣的努力,對這方面的認知和理解還是有一定的把控的。

但是,“不識廬山真面目,只緣身在此山中”,有時候往往會自己把自己限制住,對一個問題不同高度,不同角度,不同層次的解析,也是值得嘗試的。

於是,這次優化,準備了以下內容,然後先在內部範圍開了個會,收集大家的意見:

  1. 整個機制的框架
  2. 上次測試的數據
  3. 針對測試數據分析的原因
  4. 針對分析的原因,準備做的優化方向

事實證明,通過這次會議,收集到了很多有用的建議與方案,回去做了針對性的調整,再去實施。


番外:延遲與平滑的博弈

聯網戰鬥的效果,最終都是歸咎與延遲與平滑的博弈,效果平滑,完全可以通過最粗暴的高延遲實現,但是動作遊戲的高延遲還是比較影響玩家體驗的,所以就需要不斷的調整優化,找到那個平衡點。

下面是一個經典的樣例:

  1. 以當前幀池數量 n 爲準,差值釋放,假定平均3次釋放完畢,每次釋放 n/3
  2. 注重延遲,當需要快播,每次選擇最大釋放幀數釋放(3幀),保證3次釋放即可恢復(100ms內)
    • 假設幀池中有5幀,選擇
      1. 釋放3幀 => 5-3=2 ,已恢復現場
  3. 注重平滑,當需要快播,每次選擇儘可能平滑的方式去釋放幀
    • 假設幀池中有5幀,選擇
      1. 釋放2幀 => 5-2=3
      2. 釋放2幀 => 3-2=1 ,已恢復現場
  4. 平衡平滑與延遲,指定每個數值緩存幀時的緩存策略,

注意:

  • 快播時,是否重新計算該釋放的幀數

番外:電腦與收集MD5不一致

在重現不同步時,手機產生的MD5與PC產生的MD5不一致。

經過一系列實驗檢測發現,在時間種子傳輸過程中(Lua -> C++),種子的數據類型發生變化,導致種子數值發生了改變。


番外:卡頓反饋

爲了更近一步手機玩家反饋,同時也不影響玩家正常體驗。

在聯網戰鬥後,結算界面添加反饋按鈕,收集第一手玩家信息。

然後,設定一個卡頓閾值來自動彈出反饋面板(該面板允許玩家選擇永久不自動彈出)。






參考:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章