mcpField
R
roblox-datastores

roblox-datastores

github.com/nonlooped数据与数据库25327 次安装1 周前更新
代码仓库

关于 roblox-datastores

roblox-datastores 是面向 Roblox 体验开发者的数据持久化技能,介绍存储类型选择、安全写入、恢复和运维限制,涵盖 Roblox DataStore API。

主要功能

  1. 01选择 DataStore 类型
  2. 02安全的 UpdateAsync 写入模式
  3. 03玩家档案保存流程
  4. 04版本历史与恢复
  5. 05缓存、配额与监控

适用场景

  1. 01为玩家档案选择存储方式
  2. 02构建排行榜查询
  3. 03写入错误后的数据恢复

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/nonlooped/roblox-suite --skill roblox-datastores

安装后在 Claude Code 中输入/roblox-datastores确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
best-practices-and-gotchas.md
core-operations-and-patterns.md
limits-quotas-throttling-error-codes.md
types-of-datastores.md
versioning-metadata-recovery.md
scripts/
BudgetMonitor.lua
SafeDataStore.lua
tests/
SafeDataStore.spec.lua

详细介绍

roblox-datastores

将持久化操作保留在服务端。对于并发写入,请使用纯 UpdateAsync 转换函数;写入出错后,先协调后端状态,再重试或进行补偿。

官方来源:

对于临时队列、大厅和缓存,请使用 MemoryStoreService。选择存储时,请阅读 types-of-datastores.md。

交叉参考:

选择存储

  1. 数据是否临时/短暂、高频变更,或仅在服务器运行期间需要(队列、大厅、速率计数器、短期缓存)?
    → 使用 MemoryStoreService(队列、有序映射等)。DataStore 大材小用,且成本更高、速度更慢。请参阅参考资料和官方对比。

  2. 是否需要对排行榜或排名进行数值排序查询?
    → 使用 OrderedDataStore(通过 GetOrderedDataStore)。值必须为整数。没有版本控制/元数据。需要特殊处理 GetSortedAsync 和 DataStorePages 迭代。限制有所不同(请参阅参考资料/限制等)。

  3. 是否需要用于恢复/审计的版本控制、用户自定义元数据、键列表,或更丰富的 DataStore API(ListKeysAsync、ListVersionsAsync、GetVersionAsync、GetVersionAtTimeAsync)? → 使用通过 GetDataStore 和 DataStoreOptions 创建的完整 DataStore(此方法会返回现代 DataStore 类)。这是重要玩家数据的首选。

  4. 只需要简单的键值持久化存储,不需要上述功能?
    → 使用 DataStoreService:GetDataStore()(返回标准 DataStore)或 DataStoreService:GetGlobalDataStore()(旧版全局存储,作用域为 "u")。新项目优先使用命名的 GetDataStore 存储。

作用域与前缀(现代建议): 新项目优先使用键前缀(例如 "profiles/User_1234")并通过 ListKeysAsync 进行筛选,而非旧版作用域。作用域会自动添加到键前缀。要跨作用域列出键,请使用 DataStoreOptions.AllScopes=true 和空作用域字符串(请参阅参考资料)。

Studio 访问: 如果 Studio 需要调用后端 API,请使用专用测试体验。不要允许 Studio 会话读取或写入生产数据。

推荐的安全工作流

玩家数据档案模式(最常见)

完整示例和变体(包括会话锁定和档案模式)请参阅 references/best-practices-and-gotchas.md。

服务器高层流程(位于由 ServerScriptService 脚本 require 的 ModuleScript 中):

  1. PlayerAdded 时:

    • 使用 pcall + GetAsync 加载(如果之前可能发生过服务器问题,请考虑使用 UseCache=false,以确保读取到最新数据)。
    • 与默认值合并。
    • 将数据存储在服务器端表或档案对象中(例如 playerData[player.UserId] = data)。
    • 应用任何 gamepass 权益(交叉参阅变现技能)或其他状态。
    • 可选:向客户端触发 Remote,以同步初始 UI(绝不要信任客户端数据)。
  2. 游戏过程中:

    • 修改内存中的档案。
    • 定期自动保存(每 30-60s),使用 UpdateAsync 或 SetAsync,并提供适当的转换函数。
    • 在重要事件发生时(购买、升级、交易),立即使用 UpdateAsync 保存以确保安全。
  3. PlayerRemoving / BindToClose / 角色死亡时:

    • 使用 pcall 执行最终保存。
    • 清理内存中的数据。
    • 对于 BindToClose,最多等待约 30 秒以完成最终保存(Roblox 提供的宽限期有上限)。
  4. 写入出错时:

    • 使用验证模式:写入失败后,立即使用 UseCache=false 执行 GetAsync,检查后端的实际状态,再决定重试、退款或回滚。

对于可能由多个服务器并发修改的任何值,都应使用 UpdateAsync 和纯(不产生 yield)转换函数。从最后写入者的角度看,它会以原子方式先读取再写入。

序列化规则(至关重要): 仅支持 nil、布尔值、数字(不含 inf/-inf/nan)、字符串(有效 UTF-8)、buffer,以及由上述类型组成的表。不能包含函数、已保存表上的元表或循环引用。开发期间可对可疑数据使用 HttpService:JSONEncode,预览实际存储的内容。请参阅 references/core-operations... 。

使用 OrderedDataStore 的排行榜

在相关值发生变化时或保存时填充(使用 SetAsync 或 Increment)。
使用 GetSortedAsync(ascending, pageSize, min?, max?) 查询 → DataStorePages。使用 GetCurrentPage() + AdvanceToNextPageAsync() 遍历页面。
对于批量读取,使用 BatchGetAsync(keys) 在一个请求中获取多个有序条目。每个键计为一次 OrderedRead(配额请参阅 error-codes-and-limits;缺失的键会被省略)。 请记住:只支持数字;键为字符串;页面遍历有自身的限制;不支持版本控制。

可在 ScrollingFrame 中显示,或按需通过 UI 显示。如果希望快速进行全局访问,且不必重复查询,可在标准 DataStore 中缓存前 N 条。

完整的页面遍历示例和限制差异请参阅参考资料。

版本控制和恢复

对标准 DataStore(非 Ordered)执行的每次 Set/Update/Increment 都会创建按小时划分版本的备份:某个 UTC 小时内对某个键的首次写入会创建新的版本快照,该小时内的后续写入会覆盖这个小时的版本。版本备份在被替代约 30 天后过期;当前版本永不过期。

  • ListVersionsAsync(key, sort, minDate?, maxDate?, pageSize?)
  • GetVersionAsync(key, version)
  • RemoveVersionAsync 已弃用。不要将其作为当前的清理 API;应保留版本以供恢复,并使用 ListVersionsAsync、GetVersionAsync 或 GetVersionAtTimeAsync 检查版本。
  • GetVersionAtTimeAsync(key, timestampMillis):查找特定过去时间点的当前版本。
  • 若要回滚:读取所需版本,然后使用 SetAsync 将值和元数据写回(这会创建一个新的当前版本)。

在进行有风险的发布之前,也应使用每日 Snapshot Data Stores Open Cloud API。

Creator Hub 中的 Data Stores Manager 可用于浏览键、查看元数据/版本历史、比较版本并回滚(需具备适当权限)。

完整代码示例(包括“回滚到事故发生时刻”模式)请参阅 references/versioning-metadata-recovery.md。

缓存控制

默认情况下:GetAsync 的结果会在本地缓存 4 秒。在此时间窗口内后续的 Get 会返回缓存结果,且不会计入限制。写入会立即更新缓存。

如需在写入后验证(尤其是在发生错误后),或怀疑其他服务导致数据过时:

local opts = Instance.new("DataStoreGetOptions")
opts.UseCache = false
local value, info = store:GetAsync(key, opts)

这始终会访问后端,并计入配额。请谨慎但正确地使用。

参见 references/versioning-metadata-recovery.md(有关列举、缓存和序列化)以及 references/best-practices-and-gotchas.md。

限制、配额、限流和预算

限制分为体验级(随整个体验中的并发用户数变化)和每服务器级(可通过 DataStoreService:SetRateLimitForRequestType 配置)。体验级配额池由游戏服务器和 Open Cloud v2 的 Data Store 流量共享。大批量外部任务可能会限制在线玩家的请求(反之亦然)。

UpdateAsync 同时计入读取和写入预算。

进行繁重操作前,使用 DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.XXX) 检查预算,并在必要时循环等待。

队列大小为 30;队列满时会产生限流错误(301-306 范围内的错误,或较新的 *Throttled 错误)。

所有错误代码的完整表格(从 101 KeyNameEmpty 到所有 *ExperienceThrottled 和 *GameServerThrottled 变体)、体验级公式(基础值 300 + 并发用户数 × N)以及服务器端错误,见 references/limits-quotas-throttling-error-codes.md。

可观测性仪表板(Creator Hub → Monitoring → Data Stores)会实时显示按 API/状态划分的请求数、配额使用百分比,以及存储字节数与上限的对比。

Data Stores Manager 会显示总大小与存储上限的对比(500 MB + 1 MB × lifetime users,按压缩后的最新版本大小计算)、每个 DS 的键数等。写入前请勿预先压缩值。

在服务器初始化早期调用 SetRateLimitForRequestType(每种类型调用一次),以提高迁移脚本或繁忙服务器的限制。计算公式为基础值 +(每玩家数值 × 玩家数)。各类型的限制条件见文档。

处理结果不确定的写入

每次数据存储调用都必须放在 pcall 中。

失败时:

  • 记录具体的错误代码/消息。
  • 使用有界指数退避和抖动重试读取。
  • 将返回错误的已调用写入视为未知,而非已拒绝:使用 UseCache = false 执行新的 GetAsync 以核对结果。
  • 仅当调用方已证明整个操作具有幂等性时,才重放写入。切勿自动重放 IncrementAsync。
  • 返回明确的 committed、rejected 或 unknown 状态,以免购买/恢复代码将模糊结果误认为失败。

切勿假设失败的 Set/Update/Increment 意味着“未发生任何更改”。UpdateAsync 转换函数可能运行多次,不得让其产生挂起,也不得产生外部副作用。

RemoveAsync 会创建一个“墓碑”(Get 返回 nil),但旧版本仍可查询 30 天(除非明确移除)。

管理和监控
  • 使用 Data Stores Manager(Creator Hub)进行检查、按前缀搜索键、比较/还原版本以及安排删除(带有冷却期)。Manager 的 Revert 按钮可将单个键还原到以前的版本。Restore 按钮用于恢复标记为待删除的数据存储,并非用于还原键值。在冷却期内恢复已删除的键,需要通过 Engine API 调用 UpdateAsync,或通过 Open Cloud 调用 UpdateDataStoreEntry。
  • 使用可观测性仪表板查看趋势并预测配额。
  • 批量删除/迁移:使用 Open Cloud Data Stores API 或官方 Batch Processor CLI/工具。
  • 设置存储上限即将达到或已超出时的通知。
  • 定期检查用量。

参见 references/versioning-metadata-recovery.md(涵盖列举/缓存),以及管理器/可观测性页面(链接见 best-practices-and-gotchas.md)。

检查清单

每次执行关键写入前:

  • 使用 pcall 包装,并明确处理已提交、已拒绝和未知状态
  • 在可能发生竞争时使用 UpdateAsync,并使用纯转换函数
  • 适当时提供 userIds 数组,以便进行 RTBF/GDPR 跟踪
  • 使用完整 DataStore 时,每次写入都提供元数据(即使元数据未变)
  • 确保值可序列化(开发期间使用 JSONEncode 测试)
  • 统一键名规则并使用前缀,以便轻松列出

玩家加入/加载时:

  • 根据实际情况,进行全新加载或考虑缓存
  • 安全地合并默认值
  • 在 PlayerAdded 时重新验证所有权(gamepasses 等),并重新发放尚未拥有的一次性消耗品奖励
  • 仅向客户端同步非敏感的派生状态

发布有风险的数据逻辑变更前:

  • 如果可用,通过 Open Cloud 创建每日快照
  • 在体验副本中进行充分测试

持续进行:

  • 监控 dashboard 和 manager
  • 清理测试数据/临时数据
  • 对无需在服务器停止后保留的数据使用 MemoryStores
  • 优先采用更少的数据存储、规模更大的整体对象和键前缀

常见错误

  • 从客户端/LocalScript 调用(会立即报错并造成安全漏洞)。
  • 存储函数、元表或不可序列化的数据。
  • 对有竞争的值使用 SetAsync(竞态条件会导致更新丢失或重复)。
  • 忽略节流错误并持续发起请求(会导致请求被丢弃,让玩家感到沮丧)。
  • 在正式运行的场所中硬编码 studio API 访问权限。
  • 创建数百个细小的数据存储,而不是通过键/前缀进行组织。
  • 依赖缓存的 Gets 来验证写入后的结果。
  • 忘记 OrderedDataStore 没有版本控制且只支持整数。
  • 未处理 RemoveAsync 已被调用的情况(nil + tombstone)。
  • 超出存储配额却不进行监控(会导致意外费用,也可能造成写入失败)。

scripts 文件夹用法

scripts/ 目录包含标注成熟度等级的示例:

  • SafeDataStore.lua:经过审查的示例,包含自动读取重试、明确的写入状态、可选的幂等重放,以及不使用缓存的协调。
  • BudgetMonitor.lua:用于检查预算的实验性示例。

请在专用的测试体验中调整并验证这些示例;此目录并非有版本控制的生产级库。

实际操作步骤

  1. 阅读相关的 references/ 文件,了解你要实现的功能。
  2. 使用此处和 references 中的模式与代码示例进行实现。
  3. 在负载下测试预算行为(使用多个测试服务器,或使用速率限制设置器进行模拟)。
  4. 尽早添加可观测性和告警。
  5. 使用 Manager/Dashboard 分析存储增长情况。

在信任持久化系统之前,请将本指南与静态分析、针对性测试、Studio 集成、可观测性和当前官方资料结合使用。

对于任何特定方法的权威类/属性参考,请务必交叉查阅 https://create.roblox.com/docs/reference/engine(搜索 DataStoreService、GlobalDataStore 等)。

参考索引

评论

支持 Markdown