Xây Dựng Dịch Vụ Cảnh Trong Game MMO Chỉ Dựa Trên Mô Hình Yêu Cầu-Phản Hồi - nói dối e blog

Xây Dựng Dịch Vụ Cảnh Trong Game MMO Chỉ Dựa Trên Mô Hình Yêu Cầu-Phản Hồi

Ở bài viết trước, mình đã chia sẻ rằng hệ thống máy chủ game hoàn toàn có thể vận hành hiệu quả chỉ với mô hình yêu cầu-phản hồi (request/response). Tuy nhiên, một số bạn vẫn còn băn khoăn và cho rằng cần phải sử dụng mô hình xuất bản-theo dõi (publish/subscribe) để máy chủ chủ động đẩy dữ liệu tới client. Sau khi trao đổi kỹ, mình nhận thấy đây là vấn đề cần làm rõ.

Thực chất, nếu chỉ xét về mặt chức năng truyền tải chuỗi thông điệp tới client, mô hình yêu cầu-phản hồi và xuất bản-theo dõi không khác biệt nhiều. Có thể hiểu yêu cầu-phản hồi như một hình thức “theo dõi một lần” với giới hạn số lượng thông điệp. Để duy trì việc nhận dữ liệu liên tục, client cần gửi yêu cầu mới sau mỗi lần nhận phản hồi - giống như việc đặt đồng hồ báo thức định kỳ: Thay vì sử dụng chức năng định kỳ sẵn có, bạn có thể thiết lập đồng hồ mới sau mỗi lần báo thức.

Tuy nhiên, cách tiếp cận này sẽ ảnh hưởng đến tính thời gian thực nếu quy định “phải chờ phản hồi cũ mới được gửi yêu cầu mới”. Ví dụ, nếu vừa xử lý xong một sự kiện, ngay lập tức xảy ra sự kiện mới, việc truyền tải sẽ bị trễ gấp đôi (2 lần độ trễ mạng). Khắc phục vấn đề này rất đơn giản: Cho phép client gửi nhiều yêu cầu song song mà không cần chờ phản hồi trước đó. Tuy nhiên, mức độ song song cần được kiểm soát hợp lý theo yêu cầu thời gian thực của từng loại thông điệp.

Trong hệ thống MMO, client chủ yếu là thiết bị hiển thị trạng thái thay đổi của thế giới ảo được máy chủ cung cấp. Cả mô hình yêu cầu-phản hồi và xuất bản-theo dõi đều sử dụng phản hồi để kích hoạt logic nghiệp vụ. Điểm khác biệt nằm ở cách quản lý luồng dữ liệu:

Lợi thế của mô hình yêu cầu-phản hồi:

  1. Tự động cân bằng tải: Khi client chưa xử lý xong phản hồi cũ, nó sẽ không gửi thêm yêu cầu mới. Điều này giúp máy chủ không bị quá tải do gửi thừa dữ liệu mà client không thể xử lý.
  2. Giảm độ phức tạp thuật toán: Thay vì máy chủ phải tính toán “ai cần nhận thông tin gì” (độ phức tạp O(n²)), client sẽ chủ động yêu cầu những dữ liệu mình cần. Ví dụ: Người dùng điện thoại yếu có thể chỉ theo dõi 5 đối tượng gần nhất, trong khi thiết bị mạnh hơn có thể theo dõi 50 đối tượng.
  3. Quản lý độ ưu tiên: Client có thể ưu tiên nhận các sự kiện quan trọng (ví dụ: động tác tấn công của đối thủ) và trì hoãn các sự kiện ít cần thiết (tin nhắn chat).
  4. Mã nguồn rõ ràng: Khi thiết kế đúng, mô hình này giúp logic xử lý sự kiện trở nên trực quan và dễ bảo trì hơn.

Giải pháp triển khai cụ thể:

  1. Trừu tượng hóa đối tượng:

    • Tất cả người chơi, NPC, vật thể trong game đều được biểu diễn dưới dạng đối tượng chung.
    • Chia bản đồ thành lưới ô vuông (ví dụ mỗi ô 100x100m) để quản lý không gian. Mỗi ô cũng được xem là một đối tượng đặc biệt.
  2. Quản lý sự kiện qua hàng đợi:

    • Mỗi đối tượng duy trì danh sách sự kiện có đánh dấu thời gian (timestamp).
    • Sự kiện di chuyển, giao chiến, sử dụng kỹ năng được ghi vào hàng đợi theo chu kỳ cố định (ví dụ 10 lần/giây).
  3. Giao thức truy vấn hiệu quả:

    • Loại 1: Yêu cầu trạng thái hiện tại + thời gian sự kiện gần nhất (dùng khi lần đầu truy vấn).
    • Loại 2: Yêu cầu danh sách sự kiện từ thời điểm cụ thể (dùng để cập nhật tiếp theo).
  4. Luồng xử lý khi người chơi vào cảnh:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    def khi_vao_canh():
        # Bước 1: Xác định vị trí hiện tại
        toa_do = query_lo_trinh()
    
        # Bước 2: Truy vấn ô đang đứng và ô lân cận
        ds_o = query_luoi(toa_do)  
    
        # Bước 3: Lấy danh sách đối tượng xung quanh
        ds_doi_tuong = []
        for o in ds_o:
            ds_doi_tuong.extend(query_doi_tuong(o))
    
        # Bước 4: Theo dõi các đối tượng quan trọng
        for dt in ds_doi_tuong:
            if dt.dang_quan_tam():
                query_chi_tiet(dt)  # Gửi yêu cầu liên tục để cập nhật trạng thái
  5. Các tối ưu hóa nên áp dụng:

    • Gộp sự kiện: Kết hợp nhiều sự kiện di chuyển liên tiếp thành một gói duy nhất.
    • Cache phản hồi: Lưu trữ các gói dữ liệu phổ biến để tái sử dụng, tránh xử lý trùng lặp.
    • Gộp truy vấn: Cho phép client gửi yêu cầu cho nhiều ô lưới cùng lúc để giảm số lần giao tiếp.

Ví dụ về quản lý phiên truy vấn:

1
2
3
4
5
# Client gửi yêu cầu gộp cho 9 ô lưới
query_multi_grid([grid1, grid2, grid3...grid9]) 

# Máy chủ phản hồi riêng lẻ từng ô nhưng theo thứ tự liên tiếp
response_order = [grid1_data, ..., grid9_data] 

Cơ chế treo yêu cầu thông minh: Khi client truy vấn một khu vực trống, máy chủ sẽ giữ yêu cầu đó và chỉ phản hồi khi có sự kiện mới. Điều này giúp tiết kiệm tài nguyên mạng và xử lý.

So sánh hiệu quả:

Tiêu chí Mô hình Yêu cầu-Phản hồi Mô hình Xuất bản-Theo dõi
Tính thời gian thực Trễ 1-2 lần độ trễ mạng Gần như tức thời
Tiêu thụ băng thông Tối ưu theo nhu cầu client Gửi thừa dữ liệu không cần thiết
Độ phức tạp máy chủ O(n) O(n²) hoặc cao hơn
Khả năng mở rộng Dễ dàng tăng/giảm tải động Cần kiến trúc phức tạp để cân bằng

Với cách tiếp cận này, bạn có thể xây dựng hệ thống cảnh game MMO quy mô lớn chỉ với mô hình yêu cầu-phản hồi truyền thống. Đổi lại, bạn sẽ nhận được hệ thống ổn định, dễ mở rộng và tối ưu tài nguyên hơn nhiều so với các mô hình phức tạp khác.

0%