Kiến Trúc Cụm Máy Chủ Skynet Và Giao Thức RPC
Gần đây do liên tục phải tham gia các cuộc họp, tiến độ phát triển bị gián đoạn và chậm lại đáng kể. Đến tận hôm nay tôi mới hoàn thành thiết kế và triển khai phần cụm máy chủ cũng như giao thức RPC cho hệ thống Skynet.
Về thiết kế cụm máy chủ
Mục tiêu ban đầu của chúng tôi là xây dựng hệ thống Skynet có khả năng triển khai linh hoạt trên nhiều máy vật lý. Điều này cho thấy mô hình tiến trình đơn truyền thống không còn phù hợp. Tôi quyết định giữ thiết kế nút Skynet đơn lẻ xoay quanh mô hình tiến trình đơn để tối ưu hóa hiệu suất truyền nhận dữ liệu giữa các dịch vụ nội bộ. Về phần truyền thông giữa các tiến trình (thường nằm trên các máy vật lý khác nhau), chúng tôi sẽ xử lý như một thành phần phụ trợ.
Để quản lý hệ thống hiệu quả, tất cả các nút dịch vụ cần có định danh duy nhất. Giải pháp tối ưu là giới hạn số lượng máy chủ và sử dụng một máy chủ trung tâm để điều phối. Tôi sử dụng ID 32-bit, trong đó 8-bit cao biểu thị định danh máy chủ và 24-bit thấp là ID dịch vụ nội bộ của máy đó. Khi cần xác định gói tin có phải là đích nội bộ hay không, hệ thống chỉ cần so sánh 8-bit định danh máy.
Vai trò của máy chủ Master
Chúng tôi xây dựng máy chủ Master như một cơ sở dữ liệu key-value đơn giản trong bộ nhớ. Máy này có nhiệm vụ đồng bộ thông tin định tuyến và quản lý các dịch vụ có tên gọi toàn cục. Ví dụ: Khi một dịch vụ tên DATABASE được đăng ký trên nút A, Master sẽ cập nhật bản ghi “DATABASE -> ID nút A” và lan truyền thông tin này đến toàn bộ hệ thống. Bất kỳ nút nào muốn truy cập DATABASE đều phải qua Master để xác định địa chỉ đích.
Mỗi Skynet tiến trình có một Harbor ID từ 1 đến 255 (0 được dành riêng cho hệ thống). Khi khởi động, nút sẽ báo cáo Harbor ID cho máy chủ Master. Nếu phát hiện xung đột, kết nối sẽ bị từ chối. Các nút trong hệ thống sẽ thiết lập kênh truyền điểm-điểm một chiều với nhau - ví dụ với 100 nút, hệ thống sẽ tạo ra 10.000 kênh truyền đồng thời.
Giải pháp truyền thông giữa các Harbor
Ban đầu tôi sử dụng thư viện ZeroMQ để triển khai giao tiếp giữa các Harbor do tính tiện lợi và rút ngắn thời gian phát triển. Tuy nhiên, theo đánh giá của bạn Ngưu Tần, giải pháp tối ưu hơn là viết lại bằng C và tích hợp với vòng lặp epoll hiện có trong gate. Tôi hoàn toàn đồng ý với quan điểm này nhưng ưu tiên hàng đầu vẫn là có một bản triển khai nhanh để kiểm chứng ý tưởng.
Thay đổi trong cách xử lý Client
So với phiên bản Erlang trước đây coi client là một nút Skynet đặc biệt, phiên bản hiện tại đơn giản hóa thiết kế bằng cách để dịch vụ gate xử lý trực tiếp client. Gate sẽ thu thập luồng dữ liệu từ client, đồng thời tạo động một nút ảo cho mỗi kết nối mới. Bất kỳ gói tin nào gửi đến nút ảo này sẽ tự động được đóng gói theo giao thức đã định nghĩa rồi truyền về client tương ứng. Cách tiếp cận này giúp chúng tôi tập trung hoàn thiện cơ chế RPC mà không bị sa lầy vào các vấn đề phụ trợ.
Thiết kế giao thức RPC
Truyền thông giữa các nút Skynet chủ yếu tuân theo mô hình request-response. Phiên bản trước chỉ tập trung xử lý bài toán truyền gói tin đơn lẻ từ nút này sang nút khác. Tuy nhiên khi các dịch vụ cần hợp tác chặt chẽ, việc thống nhất một số nguyên tắc truyền thông cơ bản là không thể tránh khỏi. Thay vì áp dụng giao thức đa tầng phức tạp, tôi quyết định tích hợp Google Protocol Buffer như một chuẩn chung, đảm bảo cấu trúc dữ liệu nhất quán trên toàn hệ thống.
Một cải tiến quan trọng là bổ sung session ID 31-bit. Mỗi nút duy trì bộ đếm session tăng dần, với quy ước:
- Session âm (-1, -2,…): Gói yêu cầu
- Session dương (1, 2,…): Gói phản hồi
- Session 0: Gói không cần phản hồi
Khi nhận gói tin, hệ thống sẽ phân phát dựa trên session ID. Ví dụ, khi thực hiện Remote Call từ Lua, coroutine sẽ được tạo kèm session tương ứng, treo tiến trình bằng hàm yield() cho đến khi nhận phản hồi có cùng session ID.
Ví dụ minh họa: Bộ nhớ SIMPLEDB
Tôi xây dựng một cơ sở dữ liệu SIMPLEDB đơn giản để minh họa. Người dùng có thể gửi lệnh “GET xxx” để truy vấn hoặc “SET xxx yyy” để cập nhật dữ liệu. Khi nhận yêu cầu, dịch vụ gate sẽ chuyển tiếp đến SIMPLEDB thông qua cơ chế RPC, sau đó trả kết quả về client. Mã nguồn Lua minh họa cơ chế này được thiết kế trực quan, phù hợp để nghiên cứu.
Tính mở rộng đa ngôn ngữ
Dù Lua là ngôn ngữ chính được tích hợp, Skynet hoàn toàn có thể mở rộng hỗ trợ các ngôn ngữ động khác (Python, Ruby…) thông qua module kết nối. Điều này đảm bảo tính linh hoạt cho hệ sinh thái phát triển.
Tổng kết
Thiết kế hiện tại không chỉ giải quyết bài toán truyền thông cơ bản mà còn cung cấp cơ chế phản hồi thông minh thông qua session ID. Sự kết hợp giữa kiến trúc cụm phân tán và giao thức RPC linh hoạt mở ra