Ghi Chú Phát Triển (29): Vấn Đề Đồng Bộ ID Agent Khi Di Chuyển Giữa Các Máy Chủ - nói dối e blog

Ghi Chú Phát Triển (29): Vấn Đề Đồng Bộ ID Agent Khi Di Chuyển Giữa Các Máy Chủ

Thiết kế hệ thống hiện tại chủ yếu tập trung vào mô hình đơn tiến trình, nơi mọi dữ liệu có thể được chia sẻ dễ dàng trong cùng một không gian tiến trình, miễn là các cấu trúc dữ liệu đảm bảo an toàn đa luồng. Tuy nhiên, trong môi trường vận hành thực tế, toàn bộ máy chủ trò chơi sẽ không chạy trên một máy vật lý duy nhất. Điều này đòi hỏi chúng ta phải tính đến bài toán di chuyển người chơi giữa các máy chủ vật lý khác nhau.

Dù chưa bắt đầu triển khai các chức năng liên quan đến việc di chuyển giữa các máy chủ, nhưng nhiều dịch vụ hệ thống đã cần chuẩn bị cho kịch bản này. Hiện tại, mỗi khi người chơi kết nối và hoàn tất xác thực, hệ thống sẽ khởi tạo một máy ảo Lua dành riêng cho họ - thứ mà tôi gọi là “agent”.

Agent và dịch vụ bản đồ (map) có tần suất tương tác rất cao, do đó bắt buộc chúng phải cùng tồn tại trong một tiến trình. Khi người chơi chuyển sang bản đồ thuộc tiến trình khác, agent cần được di chuyển (migrate) sang tiến trình mới tương ứng. Việc di chuyển agent về bản chất không quá phức tạp - chỉ cần lưu trữ trạng thái hiện tại của agent và khởi tạo lại ở tiến trình mới. Tuy nhiên, thách thức nằm ở việc handle (con trỏ định danh) giao tiếp của agent sẽ thay đổi sau khi di chuyển.

Thay vì xây dựng một hệ thống ID duy nhất xuyên suốt các máy chủ (điều ảnh hưởng đến hiệu năng), tôi muốn giải quyết bài toán: làm thế nào để thông báo sự thay đổi handle đến các dịch vụ đang giữ handle cũ một cách hiệu quả?

Các dịch vụ như map không cần giữ handle từ xa vì chúng chỉ xử lý các agent cùng tiến trình. Tuy nhiên, những dịch vụ ít yêu cầu về độ trễ mạng như quản lý đội nhóm hay phòng chat lại cần cơ chế này. Một giải pháp đơn giản là xây dựng hệ thống ID độc lập, nhưng tôi không muốn làm phức tạp hóa tầng底层 (low-level) bằng cách tích hợp thêm ID hệ thống. Dù skynet cung cấp cơ chế đặt tên dịch vụ dưới dạng chuỗi, nhưng thiết kế này không tối ưu cho việc quản lý hàng nghìn tên dịch vụ.

Giải pháp tôi chọn là sử dụng một bảng băm (hash table) an toàn đa luồng với 64K slot, dùng player ID làm khóa để ánh xạ đến handle hiện tại của agent. Đây là một module C, cho phép các module Lua khác truy vấn và cập nhật dữ liệu. Đối với các dịch vụ liên máy chủ, tôi áp dụng mô hình tương tự như cơ chế multicast giữa các máy: mỗi máy sẽ có một dịch vụ đại diện, đồng thời tồn tại một dịch vụ quản lý trung tâm. Mỗi khi bảng băm được cập nhật, thông tin sẽ được đồng bộ đến tất cả các máy trong hệ thống.

Trong quá trình thiết kế bảng băm an toàn đa luồng, tôi từng cân nhắc sử dụng cấu trúc dữ liệu không khóa (lock-free). Tuy nhiên, việc giải phóng các node không còn sử dụng trong môi trường không có garbage collection là cực kỳ phức tạp. Thay vào đó, tôi tối ưu bằng cách áp dụng khóa riêng biệt cho từng slot - xác suất xung đột thực tế rất thấp.

Một phương án thay thế tôi từng xem xét là không dùng hệ thống ID mới, mà lưu trữ mối quan hệ giữa các handle trong bảng băm. Ví dụ: khi handle 1 di chuyển thành handle 2, cả hai sẽ cùng trỏ đến handle 2 hiện tại. Dù agent di chuyển bao nhiêu lần, bất kỳ handle lịch sử nào cũng có thể truy vấn để tìm handle mới nhất. Tính toán sơ bộ cho thấy phương án này khả thi về mặt hiệu suất và tiêu hao bộ nhớ.

Tuy nhiên, hệ thống trò chơi của chúng ta đã có sẵn hệ thống ID người chơi, nên việc xây dựng một hệ thống ID bổ sung để ánh xạ là hoàn toàn tự nhiên (ưu điểm của phương án này là không cần thêm ID mới).

0%