如何应对谷歌云功能突发高流量?游戏云存档加载卡顿问题咨询
游戏大版本更新后谷歌云存档拉取卡顿故障排查
问题背景
游戏支持玩家通过谷歌云存储保存游戏进度,近期推送大型内容更新后大量玩家回流,集中拉取个人存档导致流量过载,大量用户卡在存档加载流程,无法读取进度进入游戏。相关佐证材料包括近4天云API控制面板截图、更早的12小时周期云API控制面板截图。
项目基础信息
- 游戏会在部分玩法节点后台调用云存档功能,为玩家提供多设备进度互通能力
- 基于Unity 2019.3.9f1开发,游戏服务功能通过资源包Easy Mobile Pro 2.17.3实现
- 使用的Google Play Games Plugin版本为0.10.12
云服务配置情况
- OAuth用户类型为External,无法修改
- OAuth用户上限显示为0/100
- OAuth速率限制页面的令牌授予速率数据显示,最高平均令牌授予速率为3.33,上限为10000
- 所有已使用配额均未超出限制,项目配额使用情况如下:
- 每日查询量使用量为上限的1/10(上限1000000000次)
- 每100秒查询量使用量为上限的1/2(上限20000次)
云API错误追踪情况
- 为获取更详细的错误日志,尝试在Google Cloud Platform控制台查找Cloud Logging工具,所有板块均无数据显示:
- 运维工具Logging为空
- Cloud Logging API提示“所选时间段无可用数据”
- Cloud Debugger API提示“所选时间段无可用数据”
- 能找到的最详细错误信息仅来自Google Drive API的Metrics板块
- 目前不确定是否遗漏了相关配置,导致无法获取更详细的错误洞察
核心代码逻辑说明
- 使用EasyMobilePro实现云存档功能,定义了1个SaveGame变量,以及分别适配谷歌和苹果的8个调用,已联系EasyMobile官方支持,对方确认这些调用不可修改且非常稳定,问题不会出在他们的代码上,若我方无问题建议联系谷歌咨询
- EasyMobile提供的云存档调用共5个:
bool GameServices.IsInitialized()void GameServices.OpenWithAutomaticConflictResolutionvoid GameServices.WriteSavedGameDatavoid GameServices.ReadSavedGameDatavoid GameServices.DeleteSavedGame
- EasyMobile提供的云登录调用共3个:
void GameServices.Init()- 委托
GameServices.UserLoginSucceeded - 委托
GameServices.UserLoginFailed
- 触发问题的流程:
- 调用
GameService.Init(),用户登录无异常 - 在LoginSuccess回调中调用自定义函数
HandleFirstCloudOpening
- 调用
- 核心精简代码如下(已移除大部分错误处理逻辑,可提供完整复杂版本):
//This Method is Called, after the player Pressed "Save/ Load" on the StartScreen //The button is disabled imidiately (and will be re-enabled if an error/fail happens) public void TryCallUserLogin() { if (!IsLoginInit) { EasyMobile.GameServices.UserLoginFailed += HandleLoginFail; EasyMobile.GameServices.UserLoginSucceeded += HandleFirstCloudOpening; IsLoginInit = true; } if (!IsGameServiceInitialized) { EasyMobile.GameServices.Init(); } else { //This "else" is only be called, if the "Init" was successfull, but the player don't have a connected savegame HandleFirstCloudOpening(); } } private void HandleLoginFail() { //(...) Show ErrorPopup, let the player try to login again } private void HandleFirstCloudOpening() { if (currentSaveState != CloudSaveState.NONE) { CloudStateConflictDebug(CloudSaveState.OPENING); return; } currentSaveState = CloudSaveState.OPENING; EasyMobile.GameServices.SavedGames.OpenWithAutomaticConflictResolution(cloudSaveNameReference, UseFirstTimeOpenedSavedGame); } private void UseFirstTimeOpenedSavedGame(EasyMobile.SavedGame _savedGame, string _error) { currentSaveState = CloudSaveState.NONE; if (string.IsNullOrEmpty(_error)) { cloudSaveGame = _savedGame; ReadDataFromCloud(cloudSaveGame); } else { ErrorPopupWithCloseButton("cloud_open", "failed with error: " + _error); } } private void ReadDataFromCloud(EasyMobile.SavedGame _savedGame) { if (_savedGame.IsOpen) { currentSaveState = CloudSaveState.LOADING; EasyMobile.GameServices.SavedGames.ReadSavedGameData(_savedGame, UseSucessfullLoadedCloudSaveGame); } else { //backup function if the fresh-opened savegame is "closed" for some reason (can happen later while "saving" ingame) HandleFirstCloudOpening(); } } private void UseSucessfullLoadedCloudSaveGame(EasyMobile.SavedGame _game, byte[] _cloudData, string error) { if (!string.IsNullOrEmpty(error)) { ErrorPopupWithCloseButton("cloud_read", "Reading saved game data failed: " + error); return; } if (_cloudData.Length > 0) { //A function, that converts the saved bytes to my useable Savegame-Data //having a "try&catch": if it fails, it useses the callback with the param "null" SaveGameToByteConverter.LoadFromBytes<CoreSaveData>(_cloudData, UseSucessfullConvertedSavegameData); } else { //this will "fail", causing the use of the callback with the param "null" SaveGameToByteConverter.LoadFromBytes<CoreSaveData>(null, UseSucessfullConvertedSavegameData); } } private void UseSucessfullConvertedSavegameData(CoreSaveData _convertedSaveGame) { //Has a Loaded & normal SaveGame in his cloud if (_convertedSaveGame != null) { //Loaded Save matches verify-conditions if (CheckLoadedSaveIsVerified(_convertedSaveGame)) { OverrideGameSaveDatawithLoaded(_convertedSaveGame); ReloadCurrentScene(); return; } else { //This happens if the cloud-save doesn't pass my verification-process ErrorPopupWithCloseButton("cloud_loadedSave", "Couldn't find a compatible Savegame!"); return; } } else { //User uses Cloud-save for the frist Time or has an unusable savegame and gets a "new" (lost his old data) TrySaveGameToCloud((bool _saved) => { SaveAllGameFilesLocally(); }); } }
当前排查结论
未在业务侧发现任何无法通过重启游戏修复、或未被用户端错误弹窗覆盖的问题。目前的表现类似用户因数量过多被加入队列,需要等待极长时间才能得到响应:部分用户反馈等待2-36小时后可成功读取进度进入游戏,也有部分玩家表示第二天仍然无法进入游戏,疑似access-token有效期仅为1天导致问题复现。
编辑历史:
- (1)更新第一张控制面板截图,匹配当前的情况
- (1)补充了云服务相关情况板块内容
- (1)补充无法获取更详细错误日志的相关说明
- (2)移除了大部分可直接展示的图片,保留相关内容说明
- (2)补充了云API错误追踪相关信息板块内容
- (2)补充了核心代码相关信息板块和可复现示例
- (2)补充了当前结论板块内容
内容的提问来源于stack exchange,提问作者Mest
相关产品推荐
相关产品推荐

