关于 roblox-data-stores
面向 Roblox 开发者的数据服务技能,涵盖玩家持久化数据、跨服务器临时状态、排行榜和消息传递,并说明如何选择服务及处理会话锁、重试、迁移和平台限制。
主要功能
- 01选择 Roblox 存储服务
- 02使用 ProfileStore 锁定玩家数据
- 03DataStore 写入、重试与迁移
- 04OrderedDataStore 排行榜模式
- 05MemoryStore 队列与临时状态
- 06MessagingService 使用说明
适用场景
- 01保存玩家进度与物品栏
- 02构建全局或周期排行榜
- 03协调跨服务器匹配队列
- 04发送跨服务器公告
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ el4cteo/ roblox-skills --skill roblox-data-stores安装后在 Claude Code 中输入/roblox-data-stores确认已加载。
附带文件references/memory-and-messaging.mdreferences/raw-datastore.md
详细介绍
Roblox 数据存储与云服务
选择服务
| 需求 | 服务 | 备注 |
|---|---|---|
| 玩家进度、物品栏、设置 | 通过 ProfileStore 使用 DataStore(会话锁定) | 每个玩家一个键(User_{UserId}),≤ 4 MB。 |
| 全局排行榜 | OrderedDataStore | 仅支持整数值;定期更新,读取有缓存。 |
| 快速、临时、跨服状态 | MemoryStoreService | 有序映射、队列、哈希映射;所有内容都会过期(TTL ≤ 45 天)。 |
| 匹配队列、实时活动、服务器列表 | MemoryStore 队列 / 有序映射 | 非持久化——绝不要作为宝贵数据的唯一副本。 |
| 广播到所有服务器 | MessagingService | 每条消息 ≤ 1 kB,尽力而为,不保证送达。 |
| 无需发布即可通过 live-ops 更改的值 | 体验配置(ConfigService) | 参见 roblox-analytics-liveops。 |
玩家数据:使用会话锁定
两台服务器绝不能同时拥有同一玩家的数据(传送、快速重新加入),否则物品会重复
且进度会丢失。ProfileStore(loleris)实现了会话锁定、自动保存和
BindToClose 刷新,是推荐的默认方案。从 Wally/创作者商店安装它,并将其放在
ServerScriptService 中。
-- ServerScriptService/Server/Services/DataService.luau (ProfileStore)
local Players = game:GetService("Players")
local ServerScriptService = game:GetService("ServerScriptService")
local ProfileStore = require(ServerScriptService.ServerPackages.ProfileStore)
local TEMPLATE = {
version = 1,
coins = 0,
inventory = {} :: { [string]: number },
}
type PlayerData = typeof(TEMPLATE)
local PlayerStore = ProfileStore.New("PlayerData", TEMPLATE)
local profiles: { [Player]: typeof(PlayerStore:StartSessionAsync("")) } = {}
local function onPlayerAdded(player: Player)
local profile = PlayerStore:StartSessionAsync(`User_{player.UserId}`, {
Cancel = function()
return player.Parent ~= Players -- stop waiting if the player left
end,
})
if profile == nil then
player:Kick("Could not load your data. Please rejoin.")
return
end
profile:AddUserId(player.UserId) -- GDPR / right-to-be-forgotten linkage
profile:Reconcile() -- fill in fields added to TEMPLATE since this profile was created
profile.OnSessionEnd:Connect(function()
profiles[player] = nil
player:Kick("Your data was loaded on another server. Please rejoin.")
end)
if player.Parent == Players then
profiles[player] = profile
else
profile:EndSession() -- left while loading
end
end
for _, player in Players:GetPlayers() do
task.spawn(onPlayerAdded, player)
end
Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(function(player)
local profile = profiles[player]
if profile then
profile:EndSession() -- saves and releases the lock
end
end)
local DataService = {}
function DataService.get(player: Player): PlayerData?
local profile = profiles[player]
return profile and profile.Data
end
return DataService
在服务器上直接修改 profile.Data(它缓存在内存中并会自动保存)。不要
每次更改都 SetAsync。在依赖较少使用的功能(全局更新、MessageAsync、Studio 的模拟存储)之前,
请查阅 ProfileStore 文档以了解当前确切的 API。
原始 DataStoreService 规则(不使用封装时)
- 在内存中缓冲:加入时加载一次,修改内存中的表,定期保存(例如每
2–5 分钟加上随机抖动)、离开时、检查点(购买)时,以及
game:BindToClose中保存。 - 当写入依赖于旧值或多个服务器可能写入该键时,使用
UpdateAsync而非SetAsync。转换函数必须是纯函数且快速(不能 yield);返回nil以取消。 - 重试瞬时错误,使用指数退避 + 抖动,限制尝试次数;对某个键的重试按顺序处理 (旧的重试绝不能覆盖较新的数据)。将写入失败视为“结果未知”。
- 绝不要保存
Instance、Vector3、CFrame、函数或混合/稀疏表:存储普通表、 数字、字符串、布尔值和buffer。序列化数据类型({ x, y, z })。验证 UTF-8 字符串。 - 键和数据存储名称 ≤ 50 个字符;值在 JSON 编码后 ≤ 4,194,304 个字符。
- 稳定的键模式(
User_{UserId}),绝不要使用显示名称。少量数据存储,每个玩家少量键。 - Studio 需要 File → Experience Settings → Security → Enable Studio Access to API Services;使用单独的测试 universe 或不同的数据存储名称,以确保测试永远不会触及生产数据。
完整的原始实现(会话锁定、自动保存、BindToClose、重试): references/raw-datastore.md。
预算和限制(2026)
| 限制 | 值 |
|---|---|
| 体验范围的标准读取 / 写入 | 每分钟 300 + 40 × CCU / 300 + 20 × CCU(与 Open Cloud 共享) |
| 默认每服务器读取/写入 | 每分钟各 60 + 40 × players(可配置) |
| 每服务器有序写入 | 每分钟 30 + 5 × players |
| 每键吞吐量 | 每分钟读取 25 MB,每分钟写入 4 MB |
| 存储(最新版本,压缩后) | 每个体验 500 MB + 1 MB × 生命周期用户数 |
| 每种请求类型的队列 | 30 个待处理请求,之后报错 301–306 |
UpdateAsync同时消耗读取和写入预算。- 在批量工作前检查
DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.StandardWrite);启动时使用SetRateLimitForRequestType一次性调整每服务器限制。 - 不要预先压缩数据;Roblox 会自动压缩。仅对真正热门的键进行分片。
- 使用版本(
ListVersionsAsync、GetVersionAtTimeAsync)恢复数据,而不是写入备份键。
架构版本控制与迁移
存储一个 version 字段。加载时,按顺序运行迁移函数,直到数据达到当前版本,然后协调默认值。在同一版本中停止写入某个字段时,不要删除该字段;不要用不同含义复用字段名。
--!strict
type Data = { [string]: any }
local MIGRATIONS: { (Data) -> () } = {
[1] = function(data) -- v1 -> v2: "gold" renamed to "coins"
data.coins = data.gold or 0
data.gold = nil
end,
[2] = function(data) -- v2 -> v3: inventory list -> counts
local counts: { [string]: number } = {}
for _, itemId in data.inventory or {} do
counts[itemId] = (counts[itemId] or 0) + 1
end
data.inventory = counts
end,
}
local CURRENT_VERSION = #MIGRATIONS + 1
local function migrate(data: Data): Data
local version = data.version or 1
while version < CURRENT_VERSION do
MIGRATIONS[version](data)
version += 1
end
data.version = CURRENT_VERSION
return data
end
print(migrate({ gold = 5, inventory = { "Sword", "Sword" } }))
排行榜(OrderedDataStore)
- 值必须为整数。按计划或在发生有意义的变化时写入,不要每次递增都写入。
- 在一个服务端循环中(例如每 60 秒)使用
GetSortedAsync(false, 100)读取前 N 名,缓存结果并复制到客户端。不要让每个服务端/客户端都不断读取。 - 对于“本周”排行榜,将周期加入存储名称(
Wins_2026W39),或使用 MemoryStore 排序映射。 - Roblox 原生排行榜(已全面发布):在 Creator Hub(游戏 → 排行榜)中注册
OrderedDataStore,并开启“Active leaderboard”。Roblox 会在玩家列表中显示持久化的“Global”和“Friends”选项卡。该存储仍是事实来源(每个游戏只能有一个启用的排行榜)。玩家加入时初始化其分数,确保选项卡正确显示。
MemoryStore 要点
- 配额按整个体验计算:内存为
64 KB + 1.2 KB × 用户数;请求为每分钟1000 + 120 × CCU个单位。 MemoryStoreSortedMap:用于排名数据、服务端浏览器和实时排行榜(SetAsync并设置过期时间、GetRangeAsync、UpdateAsync)。MemoryStoreQueue:用于匹配队列/工作队列(AddAsync、ReadAsync,成功处理后使用返回的 ID 调用RemoveAsync;设置不可见超时时间以应对崩溃)。MemoryStoreHashMap:用于无需排序的高吞吐量键值存储(SetAsync、GetAsync、UpdateAsync、ListItemsAsync)。- 始终设置过期时间;处理限流(
pcall+ 重试);保持条目精简。
MessagingService 要点
- 消息大小 ≤ 1 kB。每个服务端:每分钟发送
600 + 240 × 玩家数条;订阅数为20 + 8 × 玩家数。 - 消息传递是尽力而为且不保证顺序:将其用于通知(“刷新缓存”、全局公告),绝不要将其作为事实来源。状态数据应配合 DataStore/MemoryStore 使用。
- 用
pcall包装SubscribeAsync/PublishAsync;这些操作可能失败或挂起。
更多代码(MemoryStore 队列匹配、跨服务端公告、RTBF):references/memory-and-messaging.md。
被遗忘权
使用静态键模式,以便自动 RTBF(在 Creator Dashboard 中配置)能够删除 PlayerData/User_{userId} 这类玩家数据模板。否则,应处理删除权 Webhook(参见 roblox-open-cloud)。将键关联到用户 ID:调用 SetAsync(key, value, { userId }),在 UpdateAsync 转换函数中将其作为第二个值返回,或调用 ProfileStore 的 AddUserId。
相关技能
roblox-security(复制与交易漏洞利用)、roblox-monetization(收据)、roblox-open-cloud(外部访问、批量操作)、roblox-teleport-matchmaking。