Triển Khai Đơn Giản Cho STM
STM là viết tắt của Software Transactional Memory (Bộ nhớ giao dịch phần mềm).
Hai năm trước trong một dự án, tôi đã xây dựng một cơ chế tương tự như vậy. Khi hệ thống skynet ngày càng hoàn thiện, tôi mong muốn tìm kiếm một phương pháp đơn giản và hiệu quả hơn để đáp ứng các yêu cầu này.
Đối với dữ liệu ít cập nhật, tôi đã thêm vào skynet một mô-đun có tên gọi là sharedata, chuyên dùng để chia sẻ dữ liệu cấu hình. Mỗi lần cập nhật dữ liệu, toàn bộ dữ liệu sẽ được đóng gói thành một cấu trúc dạng cây chỉ đọc, cho phép nhiều máy ảo Lua chia sẻ việc đọc dữ liệu. Khi có thay đổi, hệ thống sẽ tạo một bản mới hoàn toàn và đánh dấu bản cũ là “bẩn”, thông báo cho các bên đọc dữ liệu cần cập nhật lại phiên bản.
Giải pháp này tồn tại hai nhược điểm lớn, không phù hợp cho cập nhật dữ liệu thời gian thực:
- Chi phí cho mỗi lần cập nhật quá cao.
- Việc thông báo phiên bản mới có độ trễ đáng kể.
Do đó, tôi quyết định thiết kế một giải pháp mới nhằm giải quyết vấn đề về tính thời gian thực, phục vụ cho các trường hợp cần trao đổi dữ liệu thường xuyên (ví dụ: trong game MMORPG, có thể áp dụng cho việc chia sẻ dữ liệu giữa nhiều đối tượng cùng một bản đồ).
Ý tưởng ban đầu là xây dựng một cấu trúc dạng cây hỗ trợ giao dịch. Đối với bên ghi dữ liệu, mỗi thay đổi lên cây sẽ đồng thời cập nhật bảng Lua cục bộ và lưu lại các thay đổi dưới dạng chuỗi tuần tự hóa (serialization) nhỏ gọn nhất có thể. Khi kết thúc giao dịch, việc gọi hàm commit sẽ nhanh chóng gộp các thay đổi lại và chia sẻ bản tuần tự hóa mới này như một bản chụp (snapshot) nhanh.
Về phía người đọc, mỗi lần truy xuất sẽ tăng thêm một tham chiếu đến bản chụp mới nhất, sau đó giải nén một phần dữ liệu cần thiết thành bảng Lua cục bộ.
Tôi đã dành cả ngày để triển khai ý tưởng này. Tuy nhiên, sau khi viết xong vài trăm dòng mã, tôi nhận ra thiết kế quá phức tạp. Việc xây dựng một cấu trúc dữ liệu phức tạp trong Lua và cung cấp các giao diện C phức tạp để thao tác không mang lại hiệu suất tốt như mong đợi. Giải pháp tối ưu hơn là chia dữ liệu thành các phần nhỏ (một nhánh của cây), thực hiện tuần tự hóa và giải nén theo nhu cầu.
Vì quá trình tuần tự hóa là không thể tránh khỏi, chúng ta hoàn toàn không cần quan tâm đến cấu trúc dữ liệu phức tạp. Điều STM cần quản lý chỉ đơn giản là phiên bản của các thông điệp đã tuần tự hóa. Mặc dù việc quản lý vòng đời của từng phiên bản không hề đơn giản, nhưng cấu trúc tổng thể sẽ gọn nhẹ hơn nhiều.
Tôi đã đưa vào nhánh dev của skynet một mô-đun C cho Lua tên là stm.
- Hàm
obj = stm.new(msg, sz)
dùng để tạo ra một đối tượng thông điệp dùng cho trao đổi dữ liệu. Thường sử dụngskynet.pack(...)
để tạo msg và sz. - Muốn gửi thông điệp cho dịch vụ khác, hãy dùng
copy = stm.copy(obj)
để tạo bản sao. Bản sao này là một lightuserdata, có thể gửi trực tiếp. - Bên nhận sử dụng
reader = stm.newcopy(copy)
để lấy đối tượng. - Gọi
reader(function(msg, sz) ... end)
để giải nén dữ liệu. Thông thường dùngreader(skynet.unpack)
là đủ. - Nếu tham số đầu tiên trả về là
true
, việc đọc dữ liệu thành công, tiếp theo là các giá trị trả về của hàm giải mã. - Nếu là
false
, có thể đối tượng stm chưa có dữ liệu hoặc phiên bản chưa được cập nhật.
Phiên bản được đề cập ở đây ám chỉ việc bên sản xuất có thể dùng obj(msg, sz)
để cập nhật dữ liệu, trong khi bên nhận có thể nhận biết chính xác sự thay đổi này.
Tập tin test/teststm.lua
chứa một ví dụ đơn giản minh họa cách sử dụng.