使用這些實踐來組織臨時數據、分配負載並應對記憶體儲存問題。
根據資料結構類型,MemoryStoreService 強制執行 限制 記憶體和資料結構中項目的數量。所有資料結構也受到每個分區的全局請求限制的約束。
設計鍵和資料結構
請參見 使用靜態鍵模式和前綴 在 資料儲存的最佳實踐 中。將這些模式應用於鍵和資料結構名稱,以便每個伺服器將相同的邏輯數據路由到相同的位置。
選擇與數據保持有用的時間相匹配的過期時間。不要將記憶體儲存用於持久的玩家記錄或必須在過期後存活的數據。要幫助選擇服務,請參見 資料儲存與記憶體儲存。
處理請求失敗
偏好使用 UpdateAsync 而非 SetAsync
請參見 偏好使用 UpdateAsync 而非 SetAsync 在 資料儲存的最佳實踐 中。哈希映射和排序映射提供 MemoryStoreHashMap:UpdateAsync() 和 MemoryStoreSortedMap:UpdateAsync() 來實現這種模式。
錯開重複請求
監控使用情況
使用 記憶體儲存可觀察性儀表板 來監控配額使用情況、請求量和響應狀態。查看內置的電子郵件警報,為重要的記憶體儲存指標配置 自定義警報,並使用 錯誤報告 來調查失敗。
在增加容量之前,減少請求、項目大小和過期時間。如果合法使用超過默認配額,請評估 擴展服務。
管理排序映射和佇列限制
排序映射和佇列都有最大項目數和最大總記憶體的限制。此外,這些資料結構中的項目始終位於單個分區上。對這些資料結構的每個請求都是對同一分區的請求。
當排序映射或佇列達到其項目或記憶體限制時,手動刪除不必要的項目或通過添加過期策略來處理。如果只有記憶體限制導致節流,則通過從鍵和值中刪除不必要的信息來減少項目大小。
如果您需要所有項目或因請求吞吐量而經歷節流,唯一的解決方案是分片。
通過分片分配負載
分片是將一組相關數據存儲在多個資料結構中的過程。換句話說,這意味著將現有的高吞吐量資料結構替換為多個較小的資料結構,這些資料結構共同包含與原始資料結構相同的一組數據。
分片的主要挑戰是找到一種方法,將數據分散到多個資料結構中,以保持與原始資料結構相同的功能。
儘管 Roblox 已經對哈希映射進行了分區,但您可以通過在幾個鍵之間分散請求來進一步分片。
分片排序映射
要在排序映射中分片玩家記錄,使用模運算將每個 Id 分配給固定數量的映射之一。以下示例使用四個映射,並始終將相同的用戶路由到相同的映射:
-- 初始化記憶體儲存服務
local MemoryStoreService = game:GetService("MemoryStoreService")
-- 創建您的排序映射桶
local sm1 = MemoryStoreService:GetSortedMap("sm1")
local sm2 = MemoryStoreService:GetSortedMap("sm2")
local sm3 = MemoryStoreService:GetSortedMap("sm3")
local sm4 = MemoryStoreService:GetSortedMap("sm4")
local sortedMaps = { sm1, sm2, sm3, sm4 }
-- 輔助函數從項目鍵檢索正確的桶
local function getSortedMapBucket(userId)
local bucketIndex = (userId % #sortedMaps) + 1
return sortedMaps[bucketIndex]
end
-- 用默認值 0 初始化玩家
for _, player in game:GetService("Players"):GetPlayers() do
local userId = player.User.Id
local bucket = getSortedMapBucket(userId)
bucket:SetAsync(tostring(userId), 0, 600)
end
-- 獲取玩家的值
local player = game:GetService("Players"):GetPlayers()[1]
local userId = player.User.Id
local bucket = getSortedMapBucket(userId)
local playerScore = bucket:GetAsync(tostring(userId))
print(playerScore)分片佇列
分片佇列比分片排序映射更棘手。儘管您希望將請求吞吐量分散到多個佇列中,但添加、讀取和刪除僅在佇列的前面或後面發生。
一種解決方案是使用旋轉佇列,這意味著創建多個佇列並在添加或讀取項目時在它們之間輪換:
- 創建幾個佇列並將它們添加到數組中。
- 創建兩個本地指針。一個表示您要從中讀取和刪除項目的佇列。另一個表示您要添加項目的佇列:
- 對於讀取操作,計算您需要從每個佇列中獲取的項目數,以及要移動讀取指針的位置。
- 對於刪除操作,將 ID 從讀取傳遞到每個佇列。
- 對於添加操作,將項目添加到添加指針的佇列中並遞增指針。
-- 初始化記憶體儲存服務
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[i]
queue:RemoveAsync(ids[i])
end
end
-- 創建一個本地函數,將項目添加到佇列
local function addToQueue(itemKey, expiration, priority)
local queue = queueArr[addIndex]
queue:AddAsync(itemKey, expiration, priority)
addIndex = rotateIndex(addIndex, 1)
end
-- 寫一些代碼!
for _, player in game:GetService("Players"):GetPlayers() do
addToQueue(player.User.Id, 600, 0)
end
local players, ids = readFromQueue(20, true, -1)
removeFromQueue(ids)哈希映射
哈希映射沒有單獨的記憶體或項目計數限制,並且會自動進行分片,但如果使用不當,仍然可能會遇到節流。
例如,考慮一個遊戲,其中有一個哈希映射的數據,存儲為名為 metadata 的單個鍵的值。如果這個元數據包含一個嵌套對象,其中包含如地點 ID、玩家數量等信息,每次需要元數據時,您別無選擇,只能調用 GetAsync("metadata") 並檢索整個對象。在這種情況下,所有請求都會發送到單個鍵,因此會發送到單個分區。
與其將所有元數據存儲為單個嵌套對象,不如將每個獨立訪問的字段存儲為自己的鍵,以便哈希映射可以利用自動分片。如果您需要元數據與哈希映射的其餘部分之間的分離,請添加命名前綴,例如 metadata_user_count 而不是 user_count。
如果一個或幾個鍵接收到頻繁的請求,則將這些調用分片到多個鍵上。例如,如果所有遊戲伺服器從一個哈希映射鍵檢索值,則請求可能會導致分區節流。為了減少負載,將值複製到多個鍵並將每個伺服器路由到穩定的分片。