Những thực tiễn tốt nhất khi thiết kế cấu trúc dữ liệu MemoryStore

*Nội dung này được dịch bằng AI (Beta) và có thể có lỗi. Để xem trang này bằng tiếng Anh, hãy nhấp vào đây.

Tùy thuộc vào loại cấu trúc dữ liệu, MemoryStoreService thực thi các giới hạn trên bộ nhớ và số lượng mục trong một cấu trúc dữ liệu. Tất cả các cấu trúc dữ liệu cũng bị giới hạn bởi một giới hạn yêu cầu toàn cầu theo phân vùng.

Mỗi trò chơi Roblox đều có Bảng điều khiển Quan sát Bộ nhớ, bao gồm một loạt các biểu đồ mà bạn có thể sử dụng để theo dõi mức sử dụng bộ nhớ.

Bản đồ đã sắp xếp và hàng đợi

Bản đồ đã sắp xếp và hàng đợi đều có giới hạn về số lượng mục tối đa và tổng bộ nhớ tối đa. Thêm vào đó, các mục trong một trong những cấu trúc dữ liệu này luôn nằm trên một phân vùng duy nhất. Mỗi yêu cầu đến một trong những cấu trúc dữ liệu đó là một yêu cầu đến cùng một phân vùng.

Khi một bản đồ đã sắp xếp hoặc hàng đợi đạt đến giới hạn mục hoặc bộ nhớ của nó, hành động tốt nhất là xóa các mục không cần thiết một cách thủ công hoặc bằng cách thêm chính sách hết hạn cho các mục. Ngoài ra, nếu chỉ giới hạn bộ nhớ gây ra tình trạng bị giới hạn, bạn có thể cố gắng giảm kích thước của các mục bằng cách loại bỏ thông tin không cần thiết khỏi các khóa và giá trị của bạn.

Nếu bạn cần tất cả các mục của mình hoặc đang trải qua tình trạng bị giới hạn do thông lượng yêu cầu, giải pháp duy nhất là chia phần.

Chia phần

Chia phần là quá trình lưu trữ một tập hợp dữ liệu liên quan trên nhiều cấu trúc dữ liệu khác nhau. Nói cách khác, nó có nghĩa là thay thế một cấu trúc dữ liệu có thông lượng cao hiện có bằng nhiều cấu trúc dữ liệu nhỏ hơn mà cùng chứa tập hợp dữ liệu giống như bản gốc.

Thách thức chính của việc chia phần là tìm cách phân bổ dữ liệu trên nhiều cấu trúc dữ liệu theo cách duy trì chức năng giống như bản gốc.

Đối với các bản đồ băm, mặc dù cấu trúc dữ liệu đã được phân vùng, nhưng chia phần được thực hiện bằng cách phân bổ các yêu cầu giữa nhiều khóa.

Chia phần một bản đồ đã sắp xếp

Để chia phần một bản đồ đã sắp xếp, hãy xem xét việc chia dữ liệu của bạn thành các phân đoạn alphabet với các khoảng ký tự. Ví dụ, giả sử bạn chỉ có các khóa với chữ cái đầu tiên từ A-Z, và bạn tin rằng bốn bản đồ đã sắp xếp là đủ cho trường hợp sử dụng hiện tại và sự phát triển trong tương lai:

  • Bản đồ đầu tiên có thể bao phủ A-G, bản đồ thứ hai là H-N, bản đồ thứ ba là O-T, và bản đồ thứ tư là U-Z.
  • Để chèn hoặc lấy một mục, hãy sử dụng bản đồ thích hợp dựa trên ký tự đầu tiên của mục đó.
Chia phần một Bản đồ đã Sắp xếp

-- Khởi tạo Dịch vụ MemoryStore
local MemoryStoreService = game:GetService("MemoryStoreService")
-- Tạo các Thùng Bản đồ đã Sắp xếp của bạn
local sm_AtoG = MemoryStoreService:GetSortedMap("AtoG")
local sm_HtoM = MemoryStoreService:GetSortedMap("HtoM")
local sm_NtoT = MemoryStoreService:GetSortedMap("NtoT")
local sm_UtoZ = MemoryStoreService:GetSortedMap("UtoZ")
-- Hàm trợ giúp để lấy Thùng chính xác từ Khóa Mục
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
-- Khởi tạo tên người chơi với giá trị mặc định là 0
for _, player in game:GetService("Players"):GetPlayers() do
local bucket = getSortedMapBucket(player)
bucket:SetAsync(player, 0, 600)
end
-- Lấy giá trị của một người chơi
local player = "myPlayer"
local bucket = getSortedMapBucket(player)
local playerScore = bucket:GetAsync(player)
print(playerScore)

Chia phần một hàng đợi

Chia phần một hàng đợi khó hơn so với chia phần một bản đồ đã sắp xếp. Mặc dù bạn muốn phân bổ thông lượng yêu cầu trên nhiều hàng đợi, việc thêm, đọc và xóa chỉ xảy ra ở phía trước hoặc phía sau của hàng đợi.

Một giải pháp là sử dụng hàng đợi quay vòng, có nghĩa là tạo nhiều hàng đợi và luân phiên giữa chúng khi bạn thêm hoặc đọc một mục:

  1. Tạo một vài hàng đợi và thêm chúng vào một mảng.

  2. Tạo hai con trỏ cục bộ. Một đại diện cho hàng đợi mà bạn muốn đọc và xóa các mục từ. Cái còn lại đại diện cho hàng đợi mà bạn muốn thêm các mục vào:

    • Đối với các thao tác đọc, tính toán số lượng mục bạn cần từ mỗi hàng đợi, cũng như nơi di chuyển con trỏ đọc.
    • Đối với các thao tác xóa, truyền các ID từ việc đọc đến mỗi hàng đợi.
    • Đối với các thao tác thêm, thêm vào hàng đợi tại con trỏ thêm và tăng con trỏ.
Chia phần một Hàng đợi

-- Khởi tạo Dịch vụ MemoryStore
local MemoryStoreService = game:GetService("MemoryStoreService")
-- Tạo các Hàng đợi của bạn
local q1 = MemoryStoreService:GetQueue("q1")
local q2 = MemoryStoreService:GetQueue("q2")
local q3 = MemoryStoreService:GetQueue("q3")
local q4 = MemoryStoreService:GetQueue("q4")
-- Đưa các Hàng đợi vào một Mảng
local queueArr = { q1, q2, q3, q4 }
-- Tạo hai con trỏ đại diện cho các chỉ số của hàng đợi đọc và thêm
local readIndex = 1
local addIndex = 1
-- Tạo một hàm cục bộ làm cập nhật các chỉ số một cách thích hợp
local function rotateIndex(index, n)
return (index + n - 1) % 4 + 1
end
-- Tạo một hàm cục bộ đọc n mục từ hàng đợi
local function readFromQueue(count, allOrNothing, waitTimeout)
local endIndex = count % 4
local countPerQueue = count // 4
local items = {}
local ids = {}
-- lặp qua từng hàng đợi
for i = 1, 4, 1 do
-- xác định xem hàng đợi này có đọc một mục thêm hay không
local diff = i - readIndex
if diff < 0 then
diff += 4
end
local queue = queueArr[i]
-- đọc các mục từ mỗi hàng đợi
-- +1 mục nếu thỏa mãn tiêu chí đọc thêm
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
-- Tạo một hàm cục bộ xóa n mục từ hàng đợi
local function removeFromQueue(ids)
for i = 1, 4, 1 do
local queue = queueArr[readIndex]
queue:RemoveAsync(ids[i])
end
end
-- Tạo một hàm cục bộ thêm một mục vào hàng đợi
local function addToQueue(itemKey, expiration, priority)
local queue = queueArr[readIndex]
queue:AddAsync(itemKey, expiration, priority)
addIndex = rotateIndex(addIndex, 1)
end
-- Viết một số mã!
for _, player in game:GetService("Players"):GetPlayers() do
addToQueue(player, 600, 0)
end
local players, ids = readFromQueue(20, true, -1)
removeFromQueue(ids)

Bản đồ băm

Các bản đồ băm không có giới hạn bộ nhớ hoặc số lượng mục riêng lẻ và được tự động chia phần, nhưng bạn vẫn có thể gặp tình trạng bị giới hạn nếu sử dụng chúng không đúng cách.

Ví dụ, hãy xem xét một trò chơi với một bản đồ băm dữ liệu, được lưu trữ dưới dạng giá trị của một khóa duy nhất có tên là metadata. Nếu metadata này chứa một đối tượng lồng với thông tin như ID địa điểm, số lượng người chơi và nhiều hơn nữa, thì mỗi khi cần metadata, bạn không có lựa chọn nào khác ngoài việc gọi GetAsync("metadata") và lấy toàn bộ đối tượng. Trong trường hợp này, tất cả các yêu cầu đều đến một khóa duy nhất và do đó là một phân vùng duy nhất.

Thay vì lưu trữ toàn bộ metadata dưới dạng một đối tượng lồng đơn, cách tiếp cận tốt hơn là lưu mỗi trường dưới dạng khóa riêng của nó để bản đồ băm có thể tận dụng việc chia phần tự động. Nếu bạn cần sự tách biệt giữa metadata và phần còn lại của bản đồ băm, hãy thêm một tiền tố tên (ví dụ: metadata_user_count thay vì chỉ user_count).

Ngoài ra, nếu một hoặc một vài khóa được truy cập thường xuyên, thì rất quan trọng để chia phần các cuộc gọi này giữa nhiều khóa. Ví dụ, nếu tất cả các máy chủ trò chơi đều phải lấy một giá trị từ một khóa bản đồ băm, bạn có thể gặp phải tình trạng bị giới hạn phân vùng. Để ngăn điều này xảy ra, bạn có thể phân bổ các cuộc gọi đó giữa nhiều khóa với cùng giá trị cho đến khi tình trạng bị giới hạn phân vùng không còn xảy ra nữa.

©2026 Roblox Corporation. Roblox, logo Roblox và Powering Imagination là các nhãn hiệu đã đăng ký và chưa đăng ký của chúng tôi tại Hoa Kỳ và các quốc gia khác.