Tối Ưu Hóa Quá Trình Khởi Động Dịch Vụ Skynet
Sau 6 tháng phát triển, tựa game di động của chúng tôi sắp chính thức ra mắt. Đối tác phân phối yêu cầu hệ thống phải xử lý được 200.000 người dùng trực tuyến cùng lúc và 1 triệu người dùng hoạt động vào ngày đầu tiên. Đây là một thách thức không nhỏ, và hiện tại chúng tôi đang tiến hành kiểm tra hiệu năng kỹ lưỡng trên hệ thống server.
Hệ thống server của chúng tôi xây dựng trên kiến trúc Skynet, trước đây chưa từng xử lý ứng dụng quy mô lớn như thế này. Trong quá trình kiểm tra áp lực, nhiều vấn đề từng được dự đoán lý thuyết đã xuất hiện, đồng thời phát hiện thêm một số hiện tượng bất ngờ chưa từng lường trước.
Nút thắt hiệu năng đầu tiên: Đợt đăng nhập ồ ạt
Điểm nghẽn đầu tiên xảy ra khi hàng chục nghìn robot đồng loạt đăng nhập hệ thống - điều mà chúng tôi đã dự đoán từ trước và từng chia sẻ chi tiết trong một bài blog trước đây. Để giải quyết tình trạng nghẽn mạng này, tôi đề xuất chuỗi giải pháp sau:
-
Tách biệt dịch vụ xác thực khỏi agent:
Không nên khởi động một agent mới cho mỗi kết nối đến để xử lý xác thực. Thay vào đó, nên tập trung xử lý xác thực tại watchdog, từ đó phân phối yêu cầu một cách hiệu quả. Điều này yêu cầu chúng ta không sử dụng watchdog mặc định của Skynet (chỉ mang tính minh họa) mà phải tự xây dựng. Trong hai dự án trước đây, nhóm chúng tôi đều phát triển watchdog tùy chỉnh riêng. -
Cách ly logic xác thực thành dịch vụ không trạng thái:
Các logic nghiệp vụ phức tạp (như truy vấn CSDL) nên được triển khai trong một dịch vụ độc lập, có khả năng mở rộng ngang. Watchdog sẽ phân bổ tải đều lên các phiên bản dịch vụ này. Nếu cần, có thể tích hợp cơ chế xếp hàng (xem tài liệu tham khảo 1 & 2) do watchdog điều phối. -
Thiết kế watchdog phân tán:
Với dự án hiện tại là game server đơn (toàn bộ người dùng trong một hệ thống), chúng tôi triển khai một watchdog trên mỗi máy vật lý. Các watchdog này đồng bộ thông tin qua trao đổi nội bộ tại server trung tâm. Thiết kế này giúp tránh tạo điểm nghẽn đơn vì watchdog chỉ quản lý trạng thái trực tuyến, không thực hiện xử lý tính toán nặng. -
Quy trình chuyển tiếp kết nối:
Sau khi xác thực thành công, watchdog sẽ khởi động agent tương ứng và thông báo cho gate (cổng kết nối) chuyển hướng kết nối người dùng sang agent. Từ đây, mỗi agent sẽ phục vụ độc quyền một người dùng.
Tối ưu hiệu năng khởi động agent
Do agent được viết bằng Lua nên quá trình khởi động luôn tiêu tốn nhiều tài nguyên. Việc tải script Lua chậm hơn hàng bậc độ so với nạp thư viện C động. Trong kiểm tra thực tế, agent còn cần yêu cầu địa chỉ các dịch vụ khác từ service manager trung tâm qua cơ chế message của Skynet. Chúng tôi deliberately không dùng dịch vụ tên miền của Skynet vì service manager (viết bằng Lua) dễ tùy biến hơn, tuy nhiên điều này lại tạo điểm nghẽn tiềm ẩn.
Trước khi vấn đề nghẽn xuất hiện, nhóm đã chuẩn bị phương án dự phòng:
- Tự động dự phòng 1.000-5.000 agent:
Khởi động sẵn hàng nghìn agent trong giai đoạn hệ thống khởi động, chỉ mở cổng kết nối sau khi hoàn tất. Sử dụng timer định kỳ kiểm tra và bổ sung lượng agent dự phòng khi cần. Giải pháp này chỉ cần ~100 dòng code Lua, đơn giản nhưng hiệu quả cao, giúp vượt qua thời điểm cao điểm mở server.
Lưu ý quan trọng: Không cần xây dựng hệ thống agent pool có khả năng tái chế. Việc không thu hồi agent sau khi ngắt kết nối giúp tận dụng lợi thế của sandbox Lua (dọn dẹp toàn bộ trạng thái liên quan khi người dùng thoát), đồng thời tránh làm phức tạp hóa hệ thống.
Cải thiện tốc độ tải script Lua
Khi thử nghiệm với 1.000 agent dự phòng, nhóm phát hiện thời gian khởi động kéo dài tới 40 giây (trung bình 40ms/agent). Thông qua phân tích boot time, 95% thời gian tiêu tốn cho việc tải script Lua. Mỗi agent đều phải đọc file và parse source code riêng, dù cùng script.
Giải pháp: Xây dựng cơ chế cache code Lua không thay đổi source code gốc:
- Inject loader tùy chỉnh vào package.searchers của Lua 5.2
- Cache kết quả đọc file và parse opcode
Kết quả: Thời gian khởi động giảm từ 40s xuống còn 20s (tăng tốc 200%).
Hiện tượng nghẽn đa luồng khó hiểu
Một hiện tượng bất thường gây bối rối:
- Khởi động tuần tự 1.000 agent nhanh hơn song song 2 lần
Dù logic xử lý độc lập hoàn toàn không cần lock, nhưng khi khởi động đồng loạt, CPU tải cao hơn hẳn nhưng thời gian thực tế kéo dài hơn.
Phân tích:
- Quá trình khởi động chia 2 giai đoạn: tạo service trên Skynet và tải code vào Lua state
- Khi tuần tự: Một luồng tạo service, luồng khác tải code theo pipeline
- Khi song song: Nhiều luồng đồng thời xử lý pipeline, nhưng hiệu suất giảm mạnh
Nguyên nhân nghi ngờ: Hiệu ứng nghẽn bộ nhớ đệm L1 của CPU khi nhiều luồng đồng thời truy cập dữ liệu. Nhóm dự định nghiên cứu sâu hơn về cơ chế cache và điều phối luồng trong môi trường đa nhân.
Kết quả kiểm tra áp lực
Với cấu hình 6 server 64GB RAM, 12 core CPU (6 core x 2), hệ thống đạt hiệu năng ấn tượng:
- Dễ dàng xử lý 100.000 người dùng trực tuyến
- Trải nghiệm game mượt mà, độ trễ thấp
- Ngưỡng giới hạn mỗi server khoảng 30.000 người dùng (giới hạn bởi RAM), vượt xa mục tiêu ban đầu 10.000 người/dàn máy
Dù kết quả khả quan, chúng tôi vẫn thận trọng chờ thử nghiệm thực tế khi chính thức ra mắt để xác nhận hiệu năng tối ưu.