MemoryStoreService 是一个高吞吐量和低延迟的数据服务,提供快速的内存数据存储,所有服务器在实时会话中均可访问。内存存储 适合频繁和短暂的数据,这些数据变化迅速且不需要持久化,因为它们访问更快,并在达到最大生命周期时消失。对于需要在会话之间持久化的数据,请使用 数据存储。
数据结构
内存存储有三种原始数据结构,供服务器共享以快速处理,而不是直接访问原始数据:排序映射、队列 和 哈希映射。每种数据结构适合某些用例:
- 基于技能的匹配 - 在服务器之间的共享 队列 中保存用户信息,例如技能等级,并使用大厅服务器定期进行匹配。
- 跨服务器交易和拍卖 - 启用不同服务器之间的通用交易,用户可以对实时变化价格的物品进行竞标,使用 排序映射 的键值对。
- 全球排行榜 - 在共享的 排序映射 中存储和更新用户排名。
- 共享库存 - 在共享的 哈希映射 中保存库存物品和统计数据,用户可以同时使用库存物品。
- 持久数据的缓存 - 将您的持久数据同步并复制到可以作为缓存的内存存储 哈希映射 中,以提高游戏性能。
一般来说,如果您需要根据特定键访问数据,请使用哈希映射。如果您需要数据有序,请使用排序映射。如果您需要以特定顺序处理数据,请使用队列。
限制和配额
为了维护可扩展性和系统性能,内存存储对内存大小、API 请求和数据结构大小有数据使用配额。
内存存储有基于过期时间的驱逐策略,也称为生存时间(TTL)。项目在过期后被驱逐,内存配额被释放以供新条目使用。当您达到内存限制时,所有后续写入请求都会失败,直到项目过期或您手动删除它们。
内存大小配额
内存配额限制游戏可以消耗的总内存量。它不是固定值;而是根据游戏中用户数量随时间变化,公式为 64 KB + 1.2 KB * [用户数量]。配额适用于游戏级别,而不是服务器级别。
当用户加入游戏时,额外的内存配额会立即可用。当用户离开游戏时,配额不会立即减少。配额在八天的追溯期后重新评估为较低值。
在您的游戏达到内存大小配额后,任何增加内存大小的 API 请求都会失败。减少或不改变内存大小的请求仍然成功。
通过 可观察性 仪表板,您可以实时查看游戏的内存大小配额,使用 内存使用情况 图表。
API 请求限制
请求单位 配额适用于所有 MemoryStoreService API 调用。此配额为 1000 + 120 * [并发用户数量] 请求单位每分钟。
大多数 API 调用只消耗一个请求单位,少数例外:
MemoryStoreSortedMap:GetRangeAsync()
根据返回的项目数量消耗单位。例如,如果此方法返回 10 个项目,则调用计为 10 个请求单位。如果返回空响应,则计为一个请求单位。
根据返回的项目数量消耗单位,类似于 MemoryStoreSortedMap:GetRangeAsync(),但在读取时每两秒消耗一个额外单位。使用 waitTimeout 参数指定最大读取时间。
MemoryStoreHashMap:UpdateAsync()
消耗至少两个单位。
MemoryStoreHashMap:ListItemsAsync()
消耗 [扫描的分区数量] + [返回的项目数量] 单位。
请求配额也适用于游戏级别,而不是服务器级别。这提供了灵活性,可以在服务器之间分配请求,只要总请求速率不超过配额。如果您超过配额,当服务限制您的请求时,您会收到错误响应。
通过可用的 可观察性 功能,您可以实时查看游戏的请求单位配额。
数据结构大小限制
对于单个排序映射或队列,适用以下大小和项目数量限制:
- 最大项目数量:1,000,000
- 最大总大小(包括排序映射的键):100 MB
每分区限制
请参见 每分区限制。
最佳实践
为了保持您的内存使用模式最佳,并避免达到 限制,请遵循以下最佳实践:
移除已处理的项目。 使用 MemoryStoreQueue:RemoveAsync() 方法定期清理队列中的读取项目,以及使用 MemoryStoreSortedMap:RemoveAsync() 清理排序映射中的项目,可以释放内存并保持数据结构的最新状态。
在添加数据时将过期时间设置为尽可能短的时间。 尽管 MemoryStoreQueue:AddAsync() 和 MemoryStoreSortedMap:SetAsync() 的默认过期时间为 45 天,但设置尽可能短的时间可以自动清理旧数据,以防止它们填满您的内存使用配额。
- 不要存储大量数据并设置长过期时间,因为这可能会导致超过您的内存配额,并可能导致问题,甚至破坏您的整个游戏。
- 始终显式删除不需要的项目或设置短的项目过期时间。
- 通常,您应该使用显式删除来释放内存,并将项目过期作为安全机制,以防止未使用的项目长时间占用内存。
仅在内存中保留必要的值。
例如,对于拍卖行游戏,您只需维护最高出价。您可以在一个键上使用 MemoryStoreSortedMap:UpdateAsync() 来保持最高出价,而不是在数据结构中保留所有出价。
使用 指数退避 来帮助保持在 API 请求限制之下。
例如,如果您收到 DataUpdateConflict,您可以在两秒后重试,然后是四秒、八秒等,而不是不断向 MemoryStoreService 发送请求以获取正确的响应。
通过 分片 将巨大的数据结构拆分为多个较小的结构。
管理较小结构中的数据通常比将所有内容存储在一个大型数据结构中更容易。这种方法还可以帮助避免使用和速率限制。例如,如果您有一个使用前缀作为键的排序映射,请考虑将每个前缀分离到自己的排序映射中。对于特别受欢迎的游戏,您甚至可以根据用户 ID 的最后几位数字将用户分成多个映射。
分片 在哈希映射中频繁访问的键,使用多个键的副本来分配负载。
压缩存储的值。
例如,考虑使用 LZW 算法来减少存储值的大小。
注册扩展服务。
您可以通过加入 扩展服务 来增加您的存储和请求限制配额。
可观察性
可观察性仪表板 提供了监控和故障排除内存存储使用情况的见解和分析。通过实时更新的图表,您可以跟踪游戏的内存使用模式,查看当前分配的配额,监控 API 状态,并识别潜在的性能优化问题。
以下表格列出了可在可观察性仪表板的 按状态的请求计数 和 按 API x 状态的请求 图表中找到的所有 API 响应状态代码及其描述。有关如何解决这些错误的更多信息,请参见 故障排除。有关错误相关的特定配额或限制,请参见 限制和配额。
| 状态代码 | 描述 |
|---|---|
| 成功 | 成功。 |
| DataStructureMemoryOverLimit | 超过数据结构级别内存大小限制(100 MB)。 |
| DataUpdateConflict | 由于并发更新而发生冲突。 |
| AccessDenied | 无权访问游戏数据。此请求不消耗请求单位或使用配额。 |
| InternalError | 内部错误。 |
| InvalidRequest | 请求没有所需的信息或信息格式错误。 |
| DataStructureItemsOverLimit | 超过数据结构级别项目数量限制(1M)。 |
| NoItemFound | 在 MemoryStoreQueue:ReadAsync() 或 MemoryStoreSortedMap:UpdateAsync() 中未找到项目。ReadAsync() 每 2 秒轮询一次,并在找到队列中的项目之前返回此状态代码。 |
| DataStructureRequestsOverLimit | 超过数据结构级别请求单位限制(每分钟 100,000 请求单位)。 |
| PartitionRequestsOverLimit | 超过分区请求单位限制。 |
| TotalRequestsOverLimit | 超过宇宙级别请求单位限制。 |
| TotalMemoryOverLimit | 超过宇宙级别内存配额。 |
| ItemValueSizeTooLarge | 值大小超过限制(32 KB)。 |
以下表格列出了客户端状态代码,这些状态代码当前在可观察性仪表板上不可用。
| 状态代码 | 描述 |
|---|---|
| InternalError | 内部错误。 |
| UnpublishedPlace | 您必须发布此地点才能使用 MemoryStoreService。 |
| InvalidClientAccess | MemoryStoreService 必须从服务器调用。 |
| InvalidExpirationTime | 字段 'expiration' 时间必须在 0 和 3,888,000 之间。 |
| InvalidRequest | 无法将值转换为 json。 |
| InvalidRequest | 无法将 sortKey 转换为有效的数字或字符串。 |
| TransformCallbackFailed | 调用转换回调函数失败。 |
| RequestThrottled | 最近的 MemoryStores 请求触及一个或多个限制。 |
| UpdateConflict | 超过最大重试次数。 |
故障排除
以下表格列出了每个响应状态代码的推荐解决方案:
| 错误 | 故障排除选项 |
|---|---|
| DataStructureRequestsOverLimit / PartitionRequestsOverLimit |
|
| TotalRequestsOverLimit | |
| DataStructureItemsOverLimit |
|
| DataStructureMemoryOverLimit | |
| TotalMemoryOverLimit | |
| DataUpdateConflict |
请求中止示例 |
| 内部错误 |
|
| InvalidRequest |
|
| ItemValueSizeTooLarge |
|
在工作室中测试和调试
MemoryStoreService 中的数据在工作室和生产环境之间是隔离的,因此在工作室中更改数据不会影响生产行为。这意味着您在工作室中的 API 调用不会访问生产数据,从而允许您在投入生产之前安全地测试内存存储和新功能。
工作室测试具有与生产相同的 限制和配额。对于基于用户数量计算的配额,结果配额可能非常小,因为您是工作室测试的唯一用户。在工作室测试时,您可能还会注意到与生产使用相比,延迟略高且错误率上升,因为执行了一些额外检查以验证访问和权限。
有关如何在实时游戏或在工作室中测试内存存储进行调试的信息,请使用 开发者控制台。