Ghi Chú Phát Triển (25): Nâng Cấp RPC - nói dối e blog

Ghi Chú Phát Triển (25): Nâng Cấp RPC

Từ khi nhen nhóm ý định tái hiện thực hóa skynet, thời gian gần đây tôi bận rộn như chó chạy ăn vụng. Mỗi ngày sau 10 giờ sáng thức giấc là lại lao vào viết code, từ đầu đến cuối không thèm nói lấy một câu thừa thãi cho đến lúc trở về nhà lúc 23 giờ đêm. Cứ thế liên tục làm không ngừng nghỉ gần một tháng trời.

Cho đến hôm qua, cuối cùng toàn bộ mã nguồn cơ bản đã được chuyển đổi thành công sang khung kiến trúc mới và hệ thống có thể khởi động ổn định. Vậy là hoàn thành một giai đoạn công việc quan trọng. May mắn thay, khối lượng công việc khổng lồ tôi thực hiện suốt tháng qua không hề ảnh hưởng đến tiến độ phát triển logic trò chơi của các đồng nghiệp khác. Đây hoàn toàn là cuộc chạy đua một mình tôi chống đỡ để đồng bộ hóa liên tục những logic code mới phát sinh.

Thực tế, việc viết lại skynet đã hoàn tất từ nửa tháng trước. Lúc ấy hệ thống mới đã có thể vận hành trơn tru hệ thống xác thực người dùng độc lập nguyên bản. Hai tuần tiếp theo dành cho những công việc chuyển đổi logic trò chơi rườm rà còn lại.

Thiết kế gốc skynet về cơ bản có giao diện API tương đối tinh giản, về mặt nguyên tắc có thể thay thế minh bạch. Nhưng trong thực tế sử dụng đã phát sinh nhiều “góc khuất” bất tiện. Những thay đổi nhỏ ở tầng giao diện, các đặc tính ngầm định mới thêm vào khiến độ tương thích không đạt mức 100%. Thêm nữa, một số giao thức truyền thông ban đầu thiết kế chưa hợp lý, trong lần tái cấu trúc này tôi đã thay đổi một số giải pháp, nhưng phải xây dựng lớp liên kết tương thích.

Điển hình như trước đây chúng tôi đồng nhất các client kết nối qua TCP và các dịch vụ nội bộ trong cùng tiến trình thành một kiểu xử lý. Có nghĩa là đều dùng chung giao thức gói dữ liệu nhị phân. Tuy nhiên rõ ràng giao tiếp giữa các dịch vụ nội bộ có thể tối ưu hóa mạnh mẽ hơn. Chúng có thể trao đổi thông tin thông qua cấu trúc C thay vì các gói dữ liệu đã mã hóa, cho phép phía phát起了 yêu cầu tự phân bổ bộ nhớ và phía nhận giải phóng, từ đó giảm thiểu sao chép dữ liệu vô ích. Phiên bản cũ cứng nhắc ép buộc đồng nhất hai mô hình này khiến mất đi nhiều cơ hội tối ưu. Trong phiên bản mới, tôi chỉ điều chỉnh nhẹ giao diện và thêm ít quy ước, đã nâng cao rõ rệt hiệu suất giao tiếp giữa các dịch vụ nội tiến trình.

Về khía cạnh khác, khi xác định dùng mô hình đa luồng đơn tiến trình, các mô-đun chia sẻ dữ liệu đa tiến trình trước đây trở nên quá cồng kềnh. Giải pháp mới nhẹ nhàng hơn nhiều, cũng phù hợp hơn với Lua. Công việc này từng đề cập ở bài blog trước. Ban đầu đây là hai việc riêng biệt với việc viết lại skynet, nhưng tôi cố tình gộp chung quá trình di chuyển, làm tăng độ phức tạp. Tuy nhiên xét đến việc bản thân tôi cần rà soát toàn bộ code backend (bao gồm nhiều phần chưa từng kiểm tra kỹ), kết hợp xử lý hai việc này là hợp lý.

Quá trình này giúp loại bỏ hàng loạt mã nguồn dư thừa, gỡ bỏ các mô-đun từng tưởng dùng được nhưng hóa ra không cần thiết. Đồng thời giải quyết triệt để những vấn đề tồn đọng từ các thay đổi lịch sử. Quá trình thực hiện tuy đau đớn nhưng vô cùng xứng đáng. Mã nguồn mới áp dụng kiểm tra kiểu nghiêm ngặt hơn, qua đó phát hiện nhiều bug ẩn giấu trong logic cũ. Một số mô-đun từng triển khai bằng Erlang được viết lại hoàn toàn bằng Lua. Việc kết hợp nhiều ngôn ngữ lập trình gây ra nhiều đau đầu, ai từng trải qua đều hiểu rõ. Về sau nếu không thực sự cần thiết, tôi sẽ hạn chế dùng ngôn ngữ khác ngoài Lua cho hệ thống này.

À quên, hệ thống mới vẫn chưa qua kiểm tra áp lực, các tối ưu tiềm năng cũng chưa triển khai đầy đủ. Tuy nhiên kết quả ban đầu rất khả quan. Nhờ cải tiến mô-đun chia sẻ dữ liệu và loại bỏ nhiều thành phần dư thừa, lượng bộ nhớ tiêu thụ hiện tại giảm xuống dưới 1/5 so với trước. CPU cũng được giảm tải đáng kể. Cần nhấn mạnh đây không phải kết quả của việc dùng C hay Erlang, mà là thành quả từ việc tôi đã tổng kết nhu cầu nửa năm và cải tiến toàn diện sau khi rà soát đa số mô-đun hệ thống.

Hôm nay tôi muốn tập trung chia sẻ cải tiến mong muốn thực hiện trong thời gian tới - liên quan đến RPC giữa các dịch vụ.

So với ghi chép đầu năm nay, sau hơn nửa năm phát triển, hệ thống giờ đây đã khác biệt nhiều. Tuy nhiên tư duy cốt lõi vẫn giữ nguyên.

Giải pháp hiện tại có ưu điểm dùng giao thức Google Protocol Buffer làm định nghĩa RPC nghiêm ngặt. Nhưng nhược điểm cũng rõ rành rành, đặc biệt trong môi trường ứng dụng đòi hỏi tương tác đa chiều giữa nhiều dịch vụ, việc triển khai bộ RPC mới tốn rất nhiều công sức của lập trình viên.

Quy trình cụ thể yêu cầu:

  1. Định nghĩa tên giao thức và mã lệnh tương ứng trong tệp mô tả giao thức
  2. Trong tệp proto buffer cùng tên, khai báo danh sách tham số đầu vào/ra
  3. Tạo tệp Lua với tên đặc biệt tại vị trí nhất định, triển khai logic giao thức đó

Dù quy trình này giúp kiểm soát lỗi tiềm ẩn trong hệ thống yếu kiểu như Lua, nhưng rõ ràng làm chậm tiến độ phát triển. Các công đoạn dài dòng cùng công việc mã hóa/giải mã liên quan cũng gây tổn thất hiệu năng.

Tôi kỳ vọng xây dựng cơ chế RPC tiện lợi và tinh gọn hơn trong hệ thống tương lai.

Nói ngắn gọn, mục tiêu là lập trình viên không cần viết code khác nhau cho gọi RPC và gọi cục bộ. Họ cần hiểu rõ đâu là gọi từ xa, nhưng không phải làm bất kỳ việc gì đặc biệt để triển khai. Khi không muốn một phương thức là hàm từ xa, chỉ cần di chuyển tệp mã nguồn hoặc tổ chức lại quá trình tải code là xong. Đối với người gọi, đối tượng từ xa và địa phương cần được minh bạch tuyệt đối.

Ngày hôm nay tôi đã cơ bản hoàn thành triển khai ý tưởng này.

Qua việc viết lại skynet, tôi dần hình thành mô hình xử lý dạng này. Trước tiên, phải phân biệt rõ ràng RPC nội tiến trình và liên tiến trình.

Liên kết gọi liên tiến trình (hoặc liên máy) có thể xử lý thông qua dịch vụ trung gian ở tầng liên kết. Tập trung tối ưu RPC hiệu năng cao giữa các dịch vụ nội tiến trình mới là trọng tâm. Khi bước này hoàn tất, việc tạo bản sao địa phương cho đối

0%