使用版本控制、列出和缓存管理您的数据。
版本控制
版本控制发生在您 设置、更新 和 递增 数据时。函数 SetAsync()、UpdateAsync() 和 IncrementAsync() 创建数据的版本备份,使用每个 UTC 小时内每个键的第一次写入。对同一 UTC 小时内的键的后续写入将永久覆盖先前的数据。
版本备份在新的写入覆盖它们后的 30 天后过期。最新版本永远不会过期。
以下函数执行版本控制操作:
| 函数 | 描述 |
|---|---|
列出某个键的所有版本,返回一个 DataStoreVersionPages 实例,您可以用它枚举所有版本号。您可以使用时间范围过滤版本。 | |
使用键的版本号检索特定版本的键。 | |
删除特定版本的键。 此函数还创建一个墓碑版本,同时保留先前的版本。例如,如果您调用 RemoveAsync("User_1234"),然后尝试调用 GetAsync("User_1234"),您将返回 nil。但是,您仍然可以使用 ListVersionsAsync() 和 GetVersionAsync() 检索数据的旧版本。 |
您可以使用版本控制来处理用户请求。如果用户报告在 2020-10-09T01:42 发生了问题,您可以使用以下示例将数据恢复到之前的版本:
local DataStoreService = game:GetService("DataStoreService")
local gameStore = DataStoreService:GetDataStore("PlayerGame")
local DATA_STORE_KEY = "User_1234"
local maxDate = DateTime.fromUniversalTime(2020, 10, 09, 01, 42)
-- 获取最接近给定时间的版本
local listSuccess, pages = pcall(function()
return gameStore:ListVersionsAsync(DATA_STORE_KEY, Enum.SortDirection.Descending, nil, maxDate.UnixTimestampMillis)
end)
if listSuccess then
local items = pages:GetCurrentPage()
if #items > 0 then
-- 读取最接近的版本
local closestEntry = items[1]
local success, value, info = pcall(function()
return gameStore:GetVersionAsync(DATA_STORE_KEY, closestEntry.Version)
end)
-- 通过用最接近的版本覆盖当前值来恢复
if success then
local setOptions = Instance.new("DataStoreSetOptions")
setOptions:SetMetadata(info:GetMetadata())
gameStore:SetAsync(DATA_STORE_KEY, value, nil, setOptions)
end
else
-- 未找到条目
end
end
快照
快照数据存储开放云 API 允许您每天对游戏中的所有数据存储进行一次快照。在您发布任何更改数据存储逻辑的游戏更新之前,请务必先进行一次快照。快照确保您拥有来自游戏先前版本的最新数据。
例如,如果您在 3:30 UTC 发布的更新导致数据损坏,而没有快照,则损坏的数据会覆盖在 3:00-3:30 UTC 之间写入的任何数据。然而,如果您在 3:29 UTC 进行快照,则损坏的数据不会覆盖任何在 3:29 UTC 之前写入的数据,并且在 3:00-3:29 UTC 之间写入的所有键的最新数据得以保留。
列出和前缀
数据存储允许您按前缀列出。例如,可以通过名称的前 n 个字符进行列出,例如" d"、" do" 或 " dog",对于任何前缀为 "dog" 的键或数据存储。
您可以在列出所有数据存储或键时指定一个前缀,并仅返回与该前缀匹配的对象。ListDataStoresAsync() 和 ListKeysAsync() 函数返回一个 DataStoreListingPages 对象,您可以用它枚举列表。
| 函数 | 描述 |
|---|---|
| ListDataStoresAsync() | 列出所有数据存储。 |
| ListKeysAsync() | 列出数据存储中的所有键。 |
范围
您可以通过为 GetDataStore() 的第二个参数设置唯一字符串作为范围来进一步组织数据存储中的键。默认范围(如果未给出范围)为 global。范围会自动添加到在数据存储上执行的所有操作的所有键开头。
| 键 | 范围 |
|---|---|
| houses/User_1234 | houses |
| pets/User_1234 | pets |
| inventory/User_1234 | inventory |
数据存储名称、范围和键的组合唯一标识一个键。这三个值都是识别带范围的键所必需的。例如,您可以将一个名为 User_1234 的 global 范围键读取为:
local DataStoreService = game:GetService("DataStoreService")
local inventoryStore = DataStoreService:GetDataStore("PlayerInventory")
local success, currentGold = pcall(function()
return inventoryStore:GetAsync("User_1234")
end)
如果键 User_1234 的范围是 gold,那么您只能将其读取为:
local DataStoreService = game:GetService("DataStoreService")
local inventoryStore = DataStoreService:GetDataStore("PlayerInventory", "gold")
local success, currentGold = pcall(function()
return inventoryStore:GetAsync("User_1234")
end)
AllScopes 属性
DataStoreOptions 包含一个 AllScopes 属性,允许您从列表中返回所有 范围 的键。然后,您可以使用 KeyName 列表项属性进行常见的数据存储操作,如使用 GetAsync() 读取数据 和使用 RemoveAsync() 删除数据。
当您使用 AllScopes 属性时,GetDataStore() 的第二个参数必须是一个空字符串("")。
local DataStoreService = game:GetService("DataStoreService")local options = Instance.new("DataStoreOptions")options.AllScopes = truelocal ds = DataStoreService:GetDataStore("DS1", "", options)
如果您启用 AllScopes 属性并在数据存储中创建一个新键,则必须始终以范围/键名的格式指定该键的范围。如果不这样做,API 将抛出错误。例如,gold/player_34545 是可以接受的,范围为 gold,但 player_34545 将导致错误。
| global/K1 | house/K1 |
| global/L2 | house/L2 |
| global/M3 | house/M3 |
缓存
使用缓存可以暂时存储来自数据存储的数据,以提高性能并减少对服务器的请求次数。例如,游戏可以缓存其数据的副本,以快速访问这些数据,而无需再次调用数据存储。
缓存适用于您对数据存储键所做的修改,使用:
- GetAsync() 来 读取数据。
- SetAsync() 来 创建数据。
GetVersionAsync()、ListVersionsAsync()、ListKeysAsync() 和 ListDataStoresAsync() 不实现缓存,始终从服务后端获取最新数据。
默认情况下,引擎使用 GetAsync() 将您从后端检索的值存储在本地缓存中四秒钟。此外,默认情况下,GetAsync() 对于缓存键的请求返回缓存值,而不是继续到后端。您的 GetAsync() 返回缓存值的请求不计入您的 服务器限制 和 吞吐量限制。
所有从后端检索未被缓存的值的 GetAsync() 调用立即更新缓存并重新启动四秒计时器。
禁用缓存
要禁用缓存并选择不使用缓存从服务器检索最新值,请向您的 GetAsync() 调用添加 DataStoreGetOptions 参数,并将 UseCache 属性设置为 false,以使您的请求忽略缓存中的任何键。
禁用缓存在多个服务器以高频率写入一个键并需要获取服务器上的最新值时非常有用。但是,它可能会导致您消耗更多的 数据存储限制和配额,因为绕过缓存的 GetAsync() 请求始终计入您的吞吐量和服务器限制。
在执行写入后,立即验证读取,请使用 GetAsync() 和 DataStoreGetOptions.UseCache = false。默认情况下,GetAsync() 使用本地四秒缓存,因此正常的验证读取可能会返回过时数据。
这一点在执行写入操作后尤为重要,例如 UpdateAsync()、SetAsync()、IncrementAsync() 或 RemoveAsync() 返回错误时。在这些情况下,您的代码需要确定是重试、退款还是采取其他纠正措施。
以下示例演示了如何在验证写入结果时绕过缓存:
local ok = pcall(function()
store:UpdateAsync(key, transform)
end)
if not ok then
local options = Instance.new("DataStoreGetOptions")
options.UseCache = false
local success, value = pcall(function()
return store:GetAsync(key, options)
end)
if success then
-- 根据后端状态而不是缓存状态来决定
end
end
序列化
DataStoreService 以 JSON 格式存储数据。当您在 Studio 中保存 Luau 数据时,Roblox 使用称为序列化的过程将数据转换为 JSON,以便将其保存在数据存储中。然后,Roblox 将您的数据转换回 Luau,并通过称为反序列化的过程返回给您。
序列化和反序列化支持以下 Luau 数据类型:
- 数字
- 您不应存储特殊数字值 inf、-inf 和 nan,因为这些值不符合 JSON 标准。您无法使用开放云访问包含这些值的键。
- 表
- 表只能包含其他支持的数据类型
- 如果表的长度为 0,则数字键将被转换为字符串
如果您尝试存储一个不受支持的序列化数据类型,您将:
- 未能存储该数据类型,并获取错误消息。
- 成功存储该数据类型为 nil。
要调试为什么您的数据类型被存储为 nil,您可以使用 JSONEncode 函数。当您将 Luau 数据类型传递到此函数时,您将以 Roblox 将其存储在数据存储中的格式接收该数据,使您能够预览和调查返回的数据。