Verwenden Sie diese Praktiken, um temporäre Daten zu organisieren, die Last zu verteilen und auf Probleme mit Speicherdiensten zu reagieren.
Je nach Art der Datenstruktur setzt MemoryStoreService Grenzen für den Speicher und die Anzahl der Elemente in einer Datenstruktur. Alle Datenstrukturen unterliegen auch einer globalen Anforderungsgrenze pro Partition.
Entwerfen von Schlüsseln und Datenstrukturen
Siehe Verwenden Sie statische Schlüsselmustern und Präfixe in Best Practices für Datenspeicher. Wenden Sie diese Muster sowohl auf Schlüssel als auch auf Namen von Datenstrukturen an, damit jeder Server dieselben logischen Daten an denselben Ort leitet.
Wählen Sie Ablaufzeiten, die der Zeit entsprechen, in der die Daten nützlich bleiben. Verwenden Sie Speicherdienste nicht für persistente Spieleraufzeichnungen oder Daten, die die Ablaufzeit überstehen müssen. Für Hilfe bei der Auswahl zwischen Diensten siehe Datenspeicher versus Speicherdienste.
Umgang mit Anforderungsfehlern
Siehe Wiederholen Sie vorübergehende Fehler in Best Practices für Datenspeicher.
Bevorzugen Sie UpdateAsync über SetAsync
Siehe Bevorzugen Sie UpdateAsync über SetAsync in Best Practices für Datenspeicher. Hash-Maps und sortierte Maps bieten MemoryStoreHashMap:UpdateAsync() und MemoryStoreSortedMap:UpdateAsync() für dieses Muster.
Gestaffelte wiederkehrende Anfragen
Siehe Gestaffelte wiederkehrende Anfragen in Best Practices für Datenspeicher.
Nutzung überwachen
Verwenden Sie das Memory Store Observability Dashboard, um die Nutzung des Kontingents, das Anfragevolumen und die Antwortstatus zu überwachen. Überprüfen Sie die integrierten E-Mail-Benachrichtigungen, konfigurieren Sie benutzerdefinierte Benachrichtigungen für wichtige Metriken von Speicherdiensten und verwenden Sie den Fehlerbericht, um Fehler zu untersuchen.
Reduzieren Sie Anfragen, Elementgrößen und Ablaufzeiten, bevor Sie die Kapazität erhöhen. Wenn die legitime Nutzung die Standardkontingente überschreitet, bewerten Sie Erweiterte Dienste.
Verwalten von Grenzen für sortierte Maps und Warteschlangen
Sortierte Maps und Warteschlangen haben sowohl Grenzen für die maximale Anzahl von Elementen als auch für den maximalen Gesamtspeicher. Darüber hinaus befinden sich die Elemente in einer dieser Datenstrukturen immer auf einer einzigen Partition. Jede Anfrage an eine dieser Datenstrukturen ist eine Anfrage an dieselbe Partition.
Wenn eine sortierte Map oder Warteschlange ihre Element- oder Speicherkapazität erreicht, entfernen Sie unnötige Elemente manuell oder durch Hinzufügen einer Ablaufpolitik. Wenn nur die Speicherkapazität die Drosselung verursacht, reduzieren Sie die Elementgrößen, indem Sie unnötige Informationen aus Schlüsseln und Werten entfernen.
Wenn Sie alle Ihre Elemente benötigen oder Drosselungen aufgrund des Anfragevolumens erleben, ist die einzige Lösung Sharding.
Lastverteilung durch Sharding
Sharding ist der Prozess, eine Menge verwandter Daten über mehrere Datenstrukturen zu speichern. Mit anderen Worten, es bedeutet, eine bestehende, hochgradig belastbare Datenstruktur durch mehrere kleinere zu ersetzen, die zusammen denselben Datensatz wie das Original enthalten.
Die größte Herausforderung beim Sharding besteht darin, einen Weg zu finden, die Daten über mehrere Datenstrukturen zu verteilen, sodass die gleiche Funktionalität wie das Original erhalten bleibt.
Obwohl Roblox bereits Hash-Maps partitioniert, können Sie sie weiter sharden, indem Sie Anfragen auf mehrere Schlüssel verteilen.
Sharding einer sortierten Map
Um Spieleraufzeichnungen in einer sortierten Map zu sharden, verwenden Sie Modulo-Arithmetik, um jede Id einem von einer festen Anzahl von Maps zuzuweisen. Das folgende Beispiel verwendet vier Maps und leitet denselben Benutzer konsequent an dieselbe Map:
-- Initialisieren Sie den MemoryStore-Dienst
local MemoryStoreService = game:GetService("MemoryStoreService")
-- Erstellen Sie Ihre sortierten Map-Eimer
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 }
-- Hilfsfunktion zum Abrufen des richtigen Eimers aus dem Elementschlüssel
local function getSortedMapBucket(userId)
local bucketIndex = (userId % #sortedMaps) + 1
return sortedMaps[bucketIndex]
end
-- Initialisieren Sie Spieler mit dem Standardwert 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
-- Abrufen des Wertes eines Spielers
local player = game:GetService("Players"):GetPlayers()[1]
local userId = player.User.Id
local bucket = getSortedMapBucket(userId)
local playerScore = bucket:GetAsync(tostring(userId))
print(playerScore)Sharding einer Warteschlange
Das Sharding einer Warteschlange ist komplizierter als das Sharding einer sortierten Map. Obwohl Sie die Anforderungsdurchsatz über mehrere Warteschlangen verteilen möchten, erfolgen Hinzufügungen, Lesevorgänge und Entfernungen nur an der Vorder- oder Rückseite der Warteschlange.
Eine Lösung besteht darin, eine rotierende Warteschlange zu verwenden, was bedeutet, mehrere Warteschlangen zu erstellen und zwischen ihnen zu rotieren, wenn Sie ein Element hinzufügen oder lesen:
- Erstellen Sie mehrere Warteschlangen und fügen Sie sie einem Array hinzu.
- Erstellen Sie zwei lokale Zeiger. Einer repräsentiert die Warteschlange, aus der Sie Elemente lesen und entfernen möchten. Der andere repräsentiert die Warteschlange, in die Sie Elemente hinzufügen möchten:
- Für Lesevorgänge berechnen Sie die Anzahl der Elemente, die Sie aus jeder Warteschlange benötigen, sowie wo Sie den Lesezeiger verschieben müssen.
- Für Entfernungen übergeben Sie die IDs von der Lese- zu jeder Warteschlange.
- Für Hinzufügungen fügen Sie an der Stelle des Hinzufügens zur Warteschlange hinzu und erhöhen den Zeiger.
-- Initialisieren Sie den MemoryStore-Dienst
local MemoryStoreService = game:GetService("MemoryStoreService")
-- Erstellen Sie Ihre Warteschlangen
local q1 = MemoryStoreService:GetQueue("q1")
local q2 = MemoryStoreService:GetQueue("q2")
local q3 = MemoryStoreService:GetQueue("q3")
local q4 = MemoryStoreService:GetQueue("q4")
-- Fügen Sie die Warteschlangen einem Array hinzu
local queueArr = { q1, q2, q3, q4 }
-- Erstellen Sie zwei Zeiger, die die Indizes der Lese- und Hinzufügungswarteschlangen darstellen
local readIndex = 1
local addIndex = 1
-- Erstellen Sie eine lokale Funktion, die die Indizes entsprechend aktualisiert
local function rotateIndex(index, n)
return (index + n - 1) % 4 + 1
end
-- Erstellen Sie eine lokale Funktion, die n Elemente aus der Warteschlange liest
local function readFromQueue(count, allOrNothing, waitTimeout)
local endIndex = count % 4
local countPerQueue = count // 4
local items = {}
local ids = {}
-- Schleife durch jede Warteschlange
for i = 1, 4, 1 do
-- Bestimmen, ob diese Warteschlange ein zusätzliches Element lesen wird
local diff = i - readIndex
if diff < 0 then
diff += 4
end
local queue = queueArr[i]
-- Elemente aus jeder Warteschlange lesen
-- +1 Elemente, wenn die Kriterien für zusätzliches Lesen erfüllt sind
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
-- Erstellen Sie eine lokale Funktion, die n Elemente aus der Warteschlange entfernt
local function removeFromQueue(ids)
for i = 1, 4, 1 do
local queue = queueArr[i]
queue:RemoveAsync(ids[i])
end
end
-- Erstellen Sie eine lokale Funktion, die ein Element zur Warteschlange hinzufügt
local function addToQueue(itemKey, expiration, priority)
local queue = queueArr[addIndex]
queue:AddAsync(itemKey, expiration, priority)
addIndex = rotateIndex(addIndex, 1)
end
-- Schreiben Sie etwas Code!
for _, player in game:GetService("Players"):GetPlayers() do
addToQueue(player.User.Id, 600, 0)
end
local players, ids = readFromQueue(20, true, -1)
removeFromQueue(ids)Hash-Maps
Hash-Maps haben keine individuellen Speicher- oder Elementanzahlgrenzen und sind automatisch sharded, aber Sie können dennoch auf Drosselungen stoßen, wenn Sie sie schlecht verwenden.
Betrachten Sie beispielsweise ein Spiel mit einer Hash-Map von Daten, die als Wert eines einzelnen Schlüssels namens metadata gespeichert sind. Wenn diese Metadaten ein verschachteltes Objekt mit Informationen wie Platz-ID, Spieleranzahl und mehr enthalten, müssen Sie jedes Mal, wenn die Metadaten benötigt werden, GetAsync("metadata") aufrufen und das gesamte Objekt abrufen. In diesem Fall gehen alle Anfragen an einen einzigen Schlüssel und damit an eine einzige Partition.
Anstatt alle Metadaten als ein einzelnes, verschachteltes Objekt zu speichern, speichern Sie jedes unabhängig abgerufene Feld als eigenen Schlüssel, damit die Hash-Map die automatische Sharding-Funktion nutzen kann. Wenn Sie eine Trennung zwischen Metadaten und dem Rest der Hash-Map benötigen, fügen Sie ein Namenspräfix hinzu, wie z.B. metadata_user_count anstelle von user_count.
Wenn ein oder mehrere Schlüssel häufige Anfragen erhalten, sharden Sie diese Aufrufe über mehrere Schlüssel. Wenn beispielsweise alle Spieleserver einen Wert von einem Hash-Map-Schlüssel abrufen, können die Anfragen eine Partition-Drosselung verursachen. Um die Last zu reduzieren, kopieren Sie den Wert auf mehrere Schlüssel und leiten Sie jeden Server zu einem stabilen Shard.