根據數據結構類型,MemoryStoreService 會對數據結構中的內存和項目數量施加 限制。所有數據結構也受到每個分區的全局請求限制約束。
每個 Roblox 遊戲都有 Memory Store 可觀察性儀表板,該儀表板包含一組可以用來監控內存存儲使用情況的圖表。
排序映射和隊列
排序映射和隊列都有最大項目數和最大總內存的限制。此外,這些數據結構中的項目始終位於單個分區上。對這些數據結構的每個請求都是對同一分區的請求。
當排序映射或隊列達到其項目或內存限制時,最佳的做法是手動刪除不必要的項目或通過添加項目的過期策略來解決。此外,如果僅僅是內存限制造成的節流,您可以通過刪除鍵和值中的不必要信息來嘗試減少項目的大小。
如果您需要所有項目或因請求吞吐量而遭遇節流,唯一的解決方案是分片。
分片
分片是指將一組相關數據存儲在多個數據結構中。換句話說,就是將現有的高吞吐量數據結構替換為多個更小的數據結構,而這些數據結構共同包含與原始數據結構相同的一組數據。
分片的關鍵挑戰是找到一種方法,在不改變原始功能的情況下,將數據分散到多個數據結構中。
對於哈希映射,儘管該數據結構已經進行了分區,但可以通過在多個鍵之間分散請求來進行分片。
分片排序映射
要對排序映射進行分片,考慮將數據分為字符範圍的字母子集。例如,假設您只有 A-Z 首字母的鍵,並且您認為四個排序映射足以滿足當前用例和未來增長:
- 第一個映射可以涵蓋 A-G,第二個 H-N,第三個 O-T,第四個 U-Z。
- 要插入或檢索項目,請根據項目的起始字符使用相應的映射。
分片排序映射
-- 初始化 MemoryStore 服務
local MemoryStoreService = game:GetService("MemoryStoreService")
-- 創建排序映射桶
local sm_AtoG = MemoryStoreService:GetSortedMap("AtoG")
local sm_HtoM = MemoryStoreService:GetSortedMap("HtoM")
local sm_NtoT = MemoryStoreService:GetSortedMap("NtoT")
local sm_UtoZ = MemoryStoreService:GetSortedMap("UtoZ")
-- 輔助函數從項目鍵檢索正確的桶
local function getSortedMapBucket(itemKey)
if (itemKey >= "a" and itemKey < "h") then
return sm_AtoG
elseif (itemKey < "n") then
return sm_HtoM
elseif (itemKey < "u") then
return sm_NtoT
else
return sm_UtoZ
end
end
-- 用預設值 0 初始化玩家名稱
for _, player in game:GetService("Players"):GetPlayers() do
local bucket = getSortedMapBucket(player)
bucket:SetAsync(player, 0, 600)
end
-- 獲取玩家的值
local player = "myPlayer"
local bucket = getSortedMapBucket(player)
local playerScore = bucket:GetAsync(player)
print(playerScore)
分片隊列
分片隊列比分片排序映射更為複雜。儘管您想將請求吞吐量分散到多個隊列中,但添加、讀取和移除的操作只在隊列的前面或後面進行。
一種解決方案是使用旋轉隊列,這意味著創建多個隊列並在添加或讀取項目時在它們之間輪流:
創建幾個隊列並將它們添加到數組中。
創建兩個本地指針。一個表示您要從中讀取和移除項目的隊列。另一個表示您要添加項目的隊列:
- 對於讀取操作,計算您需要從每個隊列中讀取的項目數,以及將讀取指針移動到何處。
- 對於移除操作,將 ID 從讀取到每個隊列。
- 對於添加操作,將項目添加到添加指針的隊列中並增加該指針。
分片隊列
-- 初始化 MemoryStore 服務
local MemoryStoreService = game:GetService("MemoryStoreService")
-- 創建您的隊列
local q1 = MemoryStoreService:GetQueue("q1")
local q2 = MemoryStoreService:GetQueue("q2")
local q3 = MemoryStoreService:GetQueue("q3")
local q4 = MemoryStoreService:GetQueue("q4")
-- 將隊列放入數組中
local queueArr = { q1, q2, q3, q4 }
-- 創建兩個指針,表示讀取和添加隊列的索引
local readIndex = 1
local addIndex = 1
-- 創建一個本地函數,適當地更新索引
local function rotateIndex(index, n)
return (index + n - 1) % 4 + 1
end
-- 創建一個本地函數,從隊列中讀取 n 項
local function readFromQueue(count, allOrNothing, waitTimeout)
local endIndex = count % 4
local countPerQueue = count // 4
local items = {}
local ids = {}
-- 遍歷每個隊列
for i = 1, 4, 1 do
-- 確定這個隊列是否會讀取額外的項目
local diff = i - readIndex
if diff < 0 then
diff += 4
end
local queue = queueArr[i]
-- 從每個隊列中讀取項目
-- +1 項目如果符合額外讀取標準
if diff < endIndex then
items[i], ids[i] = queue:ReadAsync(countPerQueue + 1, allOrNothing,waitTimeout)
else
items[i], ids[i] = queue:ReadAsync(countPerQueue, allOrNothing,waitTimeout)
end
end
readIndex = rotateIndex(readIndex, count)
return items, ids
end
-- 創建一個本地函數,從隊列中移除 n 項
local function removeFromQueue(ids)
for i = 1, 4, 1 do
local queue = queueArr[readIndex]
queue:RemoveAsync(ids[i])
end
end
-- 創建一個本地函數,向隊列中添加項目
local function addToQueue(itemKey, expiration, priority)
local queue = queueArr[readIndex]
queue:AddAsync(itemKey, expiration, priority)
addIndex = rotateIndex(addIndex, 1)
end
-- 編寫一些代碼!
for _, player in game:GetService("Players"):GetPlayers() do
addToQueue(player, 600, 0)
end
local players, ids = readFromQueue(20, true, -1)
removeFromQueue(ids)
哈希映射
哈希映射沒有單獨的內存或項目數量限制,並且會自動進行分片,但如果使用不當,您仍然可能會遇到節流。
例如,考慮一個遊戲中有一個哈希映射數據,作為名為 metadata 的單個鍵的值存儲。如果這個元數據包含嵌套對象的信息,例如地點 ID、玩家數量等,那麼每當需要元數據時,您都別無選擇,只能調用 GetAsync("metadata") 並檢索整個對象。在這種情況下,所有請求都會進入單個鍵,因而只會落在單個分區上。
與其將所有元數據作為單個嵌套對象存儲,不如將每個字段作為自己的鍵存儲,以便哈希映射可以利用自動分片。如果需要在元數據和哈希映射的其餘部分之間進行分隔,請添加命名前綴(例如 metadata_user_count 而不僅僅是 user_count)。
此外,如果一個或少數幾個鍵被頻繁訪問,則必須在多個鍵之間進行這些調用的分片。例如,如果所有遊戲服務器必須從某個哈希映射鍵中檢索一個值,您可能會遇到分區節流。為了防止這種情況,您可以將這些調用分散在幾個具有相同值的鍵之間,直到不再發生分區節流。