Giải Pháp Đa Điểm Mới Trong Skynet
Gần đây tôi đang phát triển phiên bản 0.2 của skynet. Tính năng nổi bật nhất trong lần cập nhật này là phần thiết kế lại mô-đun phát đa điểm (multicast).
Trong quá trình phát triển skynet, mô-đun phát đa điểm đã tồn tại dưới nhiều hình thức khác nhau. Tuy nhiên trước khi phát hành phiên bản 0.1, nó đã bị loại bỏ khỏi mã nguồn chính. Lý do là bởi vì tôi muốn giữ cho các thành phần ở tầng lõi của hệ thống được gọn nhẹ, không tích hợp các chức năng có tính chuyên biệt cao như thế này.
Với việc hạ tầng của skynet ngày càng hoàn thiện, việc xây dựng một giải pháp phát đa điểm ở tầng cao trở nên khả thi hơn rất nhiều. Do đó, tôi quyết định đưa lại tính năng này trong phiên bản 0.2. Lưu ý rằng phiên bản 0.2 hiện đang được phát triển trên nhánh “dev” của kho lưu trữ github, và sẽ chỉ được hợp nhất vào nhánh “master” khi chính thức phát hành. Các API và cách triển khai hiện tại có thể thay đổi bất kỳ lúc nào.
Hiện tại, tôi dự kiến cung cấp một bộ API theo mô hình publish/subscribe. Khi một thông điệp được xuất bản qua hàm publish, tất cả các dịch vụ đã đăng ký qua hàm subscribe sẽ đều nhận được thông điệp đó.
Về mặt thiết kế, mỗi nút skynet sẽ có một dịch vụ chuyên trách quản lý việc phát đa điểm. Cấu trúc này hoàn toàn tách biệt khỏi các thành phần lõi. Các thông điệp đa điểm sẽ không được gửi trực tiếp mà phải thông qua dịch vụ đặc biệt này.
Trong quá trình thiết kế mô-đun phát đa điểm, chúng ta cần xử lý hai trường hợp chính:
- Truyền thông điệp nội bộ trong cùng một nút skynet: Khi các dịch vụ nằm cùng trong một tiến trình, chúng có thể chia sẻ vùng nhớ và sử dụng cơ chế bộ đếm tham chiếu để quản lý vòng đời của thông điệp.
- Truyền thông điệp qua mạng giữa các nút khác nhau: Khi thông điệp cần được gửi đến các nút khác, nội dung của nó phải được sao chép để phục vụ việc truyền tải qua mạng.
Quy trình truyền thông điệp nội bộ
Trong mỗi nút skynet tồn tại duy nhất một dịch vụ có tên multicastd, tự động được khởi động khi có yêu cầu phát đa điểm đầu tiên. Dịch vụ này được triển khai bằng Lua.
Để bắt đầu sử dụng dịch vụ phát đa điểm, trước tiên phải tạo một channel (kênh truyền). Mỗi channel được xác định duy nhất bởi một số nguyên 32-bit, trong đó 8-bit thấp nhất biểu thị số hiệu nút. Điều này đảm bảo rằng các channel được tạo ở các nút khác nhau sẽ không bị trùng lặp.
Dịch vụ multicastd sử dụng một bảng băm để lưu trữ danh sách các dịch vụ đăng ký nhận thông điệp trên từng channel cụ thể. Trong giai đoạn đầu, chúng ta chưa xem xét việc đăng ký giữa các nút với nhau, do đó bảng này chỉ chứa địa chỉ của các dịch vụ nội bộ.
Bộ thư viện cung cấp API cho mô-đun phát đa điểm được đặt tại lualib/multicast.lua, bao gồm các hàm chính sau:
new
: Tạo mới một channeldelete
: Xóa một channelpublish
: Xuất bản thông điệp lên channelsubscribe
: Đăng ký nhận thông điệp từ channelunsubscribe
: Hủy đăng ký
Tất cả các hàm này đều thực hiện việc chuyển tiếp yêu cầu đến dịch vụ multicastd. Điều này khiến quy trình xuất bản thông điệp phát đa điểm có đường đi dài hơn so với việc gửi thông điệp một-một thông thường.
Điều quan trọng cần lưu ý là order của thông điệp: ngay cả trên cùng một dịch vụ, một thông điệp phát đa điểm có thể đến trễ hơn một thông điệp một-một được gửi sau đó. Tuy nhiên, hàm publish
là một yêu cầu RPC có xác nhận, do đó nếu thực hiện hai hàm publish
và send
liên tiếp trong cùng một coroutine, thứ tự vẫn được đảm bảo. Đây chính là lý do tại sao hàm publish
được thiết kế để chặn (block) coroutine hiện tại cho đến khi hoàn tất.
Trước khi được gửi đến multicastd, thông điệp phát đa điểm sẽ được đóng gói thành một cấu trúc C:
|
|
Việc đóng gói ở dạng con trỏ này cho phép thực hiện đếm tham chiếu hiệu quả. Nội dung thông điệp cũng được trỏ gián tiếp thông qua một con trỏ khác, giúp việc đóng gói dữ liệu linh hoạt hơn.
Do thông điệp được truyền dưới dạng con trỏ giữa các dịch vụ nội bộ, nên nó hoàn toàn không thể bị lan ra ngoài tiến trình. Giới hạn này được đảm bảo bởi chính thư viện multicast (người dùng không thể tự ý gửi yêu cầu trực tiếp đến multicastd).
Khi nhận được yêu cầu publish, multicastd sẽ:
- Đếm tổng số dịch vụ đang đăng ký trên channel
- Thiết lập chính xác giá trị bộ đếm tham chiếu
- Chuyển tiếp thông điệp đến tất cả các dịch vụ đăng ký
Việc chuyển tiếp chỉ đơn giản là truyền con trỏ, thay vì toàn bộ nội dung, giúp tiết kiệm tài nguyên rất nhiều (cũng là lý do tồn tại của dịch vụ phát đa điểm).
Các thao tác đăng ký và hủy đăng ký channel cũng được thực hiện thông qua multicastd. Nhờ có cơ chế xử lý lỗi, hệ thống có thể bỏ qua các thao tác trùng lặp. Đặc biệt, việc hủy đăng ký được thiết kế không chặn coroutine (non-blocking), nhằm phục vụ quá trình thu gom rác (garbage collection) trong môi trường có ngữ cảnh không xác định.
Truyền thông điệp đa điểm qua mạng
Việc chỉ xử lý truyền nội bộ là tương đối đơn giản. Độ phức tạp thực sự nằm ở việc truyền thông điệp giữa các nút skynet khác nhau.
Để giải quyết vấn đề này, tôi đã thêm vào skynet một thành phần hạ tầng mới có tên datacenter (sẽ còn được sử dụng trong nhiều chức năng khác trong tương lai). Datacenter được triển khai dưới dạng một cơ sở dữ liệu dạng cây bằng Lua,