Làm Thế Nào Để Cài Đặt Dịch Vụ Thông Báo Khi Nhận Được Email Trong Skynet
Trong Skynet, làm thế nào để thực hiện dịch vụ thông báo khi có thư mới đến
Trong Skynet, các nghiệp vụ độc lập đều tồn tại dưới dạng dịch vụ độc lập. Hôm qua, mình và đồng nghiệp đã thảo luận cách xây dựng một dịch vụ thông báo thư mới. Hiện tại kiến trúc đang được thiết kế như sau: tồn tại một dịch vụ trung tâm thư riêng biệt, có khả năng xử lý ba giao thức chính:
- Gửi một lá thư vào một hộp thư (mailbox).
- Tra cứu số lượng thư trong hộp thư.
- Nhận một lá thư cụ thể từ hộp thư.
Thông tin về số lượng thư đã được đọc không được lưu trữ tại trung tâm thư, mà được ghi nhận trong dữ liệu người chơi. Giao diện người dùng cần hiển thị số lượng thư chưa đọc, ví dụ như biểu tượng dấu đỏ có số trên iOS. Theo quy ước thiết kế của Skynet, mỗi người chơi sẽ có một agent đại diện trên máy chủ, vì vậy không cần xét đến việc trao đổi dữ liệu với client mà chỉ cần tập trung vào tương tác giữa agent và trung tâm thư.
Hiện tại khi người chơi đăng nhập, hệ thống sẽ truy vấn trung tâm thư một lần để so sánh số lượng thư, sau đó đẩy thông tin về client. Khi người chơi thực hiện các hành động đặc biệt (nhập vai, ra副本等), hoặc ở lại một cảnh quá lâu, client sẽ định kỳ gửi yêu cầu truy vấn.
Tuy nhiên, nếu muốn thông báo thư mới ngay lập tức khi đến, hệ thống có thêm một dịch vụ trung tâm người dùng. Khi có thư mới, dịch vụ thư sẽ đẩy thông báo đến trung tâm người dùng; nếu người chơi offline, thông báo sẽ bị loại bỏ; nếu online, trung tâm sẽ gửi thông báo về client.
Mình nhận thấy phương án này có một số điểm bất cập nên đề xuất cải tiến như sau:
Vấn đề tồn tại:
- Dịch vụ trung tâm thư không nên chủ động gửi thông báo, điều này yêu cầu nó phải hiểu biết thêm về các hệ thống bên ngoài.
- Việc truy vấn định kỳ gây lãng phí tài nguyên, dễ dẫn đến quá tải hệ thống. Khi các hệ thống độc lập quyết định tần suất truy vấn, nếu tần suất cao hơn khả năng xử lý của dịch vụ thư (có thể liên quan đến truy vấn CSDL bên ngoài), hiện tượng “tuyết lở” quá tải sẽ xảy ra.
Giải pháp cải tiến:
Khi người chơi đăng nhập, agent sẽ gửi yêu cầu truy vấn kèm theo số lượng thư đã đọc đến trung tâm thư. Nếu không có thư mới, trung tâm thư sẽ không phản hồi yêu cầu này. Agent chỉ gửi yêu cầu mới khi nhận được phản hồi yêu cầu trước đó. Có thể dùng skynet.fork
tạo một coroutine vòng lặp vô hạn để cập nhật số lượng thư. Khi người chơi yêu cầu đọc số lượng thư, chỉ cần truy vấn biến cục bộ, không cần gọi đến trung tâm thư.
Ưu điểm:
- Mỗi agent chỉ tồn tại một yêu cầu truy vấn duy nhất tại cùng một thời điểm.
- Ngăn chặn việc gửi nhiều thông báo khi có nhiều thư mới liên tiếp đến.
- Giảm đáng kể nguy cơ quá tải hệ thống.
Thách thức triển khai:
Trung tâm thư cần tạm giữ phản hồi khi không thể trả lời ngay, và chỉ phản hồi khi có thư mới đến. May mắn thay, Skynet hỗ trợ cơ chế này dễ dàng thông qua API skynet.response
. Khác với skynet.ret
phản hồi ngay lập tức, skynet.response
trả về một closure. Khi cần phản hồi, chỉ cần gọi closure này với tham số:
closure(true, ...)
cho phản hồi bình thường.closure(false, ...)
cho phản hồi lỗi.
Giải pháp xử lý yêu cầu tồn đọng: Nếu agent thường xuyên online/offline, có thể xảy ra tình trạng yêu cầu tồn đọng tại trung tâm thư. Mỗi yêu cầu chỉ tốn vài byte bộ nhớ, nhưng nếu lo ngại, có thể thiết lập timer định kỳ (ví dụ 3 giờ/lần) để:
- Dọn dẹp các yêu cầu tồn đọng bằng cách phản hồi “không có thư mới”.
- Sử dụng tham số “TEST” trong
response
để kiểm tra xem đối tượng cần phản hồi còn tồn tại hay không, tránh gửi thông báo thừa.
Mô hình Skynet ưu việt:
- Tạo coroutine và trì hoãn phản hồi là các thao tác nhẹ nhàng nhờ cơ chế sandbox Lua hiệu quả.
- Khi A gọi B:
- Nếu B thoát trước khi phản hồi, A nhận lỗi và lan truyền đúng đến nơi gọi.
- Nếu A thoát trước khi nhận phản hồi, B nhận lỗi nhưng không ảnh hưởng tiến trình, chỉ thu hồi tài nguyên.
Ứng dụng rộng rãi:
- Theo dõi trạng thái online/offline của bạn bè.
- Giám sát tin nhắn mới trong kênh chat.
- Kiểm tra trạng thái vật phẩm đấu giá (bán được/chưa bán).
Mô hình module hóa tối ưu:
Khi xây dựng module độc lập, chỉ cần định nghĩa giao diện yêu cầu mà không cần quy định loại thông báo đẩy ra ngoài. Ví dụ trong nhánh dev của Skynet (sẽ có trong phiên bản 0.6.0), có tính năng sharedata
cho phép các dịch vụ trên cùng một node chia sẻ cấu trúc dữ liệu. Khi có phiên bản dữ liệu mới, bên cung cấp sẽ đánh dấu bản cũ là “bẩn”, và các bên nhận sẽ cập nhật tự động nhờ cơ chế chia sẻ bộ nhớ.
Trong trường hợp này, mô hình “tạm giữ phản hồi” cũng được áp dụng. Vì sharedata
được đóng gói dưới dạng thư viện, bên đọc dữ liệu không cần xử lý riêng biệt khi có thông báo cập nhật. (Trong Erlang, mỗi dịch vụ có mailbox để lọc thông báo, nhưng Skynet không có cơ chế tương tự do giới hạn 256 loại message type, quản lý phức tạp.)
Giải pháp cho dữ liệu thay đổi liên tục:
- Không cần gửi yêu cầu cập nhật ngay khi phát hiện dữ liệu “bẩn” (tránh vấn đề async phức tạp).
- Chỉ quan tâm đến phiên bản dữ liệu cuối cùng.
Tham khảo mã nguồn:
- Hàm
monitor
tronglualib/sharedata.lua
- Hàm
update
trongservice/sharedatad.lua