Ghi Chú Phát Triển (3)
Tuần này phần lớn công việc tập trung vào việc viết mã nguồn.
Sau khi hoàn tất lộ trình phát triển, cả nhóm bắt đầu phân công triển khai. Để đảm bảo giai đoạn đầu tiên sớm có sản phẩm chạy được, chúng tôi chọn phương án tối giản nhất. Tổng lượng mã không quá lớn, nếu cố gắng phân chia thành nhiều phần nhỏ để nhiều người cùng làm, thì công việc thiết kế cơ chế phân chia, phân bổ nhiệm vụ và điều phối kết quả sẽ còn phức tạp hơn nhiều so với việc để một người tự làm hết toàn bộ.
Vì vậy, cuối cùng chỉ có hai người trực tiếp thực hiện. Người bạn từ Công ty Quái Vật phụ trách phần client, còn bạn Ốc Sên đảm nhận toàn bộ server. Mình coi như trở thành “nhân viên pha trà” chuyên thiết kế kiến trúc hệ thống. Dù vậy, khi rảnh rỗi vẫn có thể hỗ trợ công việc cụ thể. Việc rèn luyện khả năng tự tìm việc làm hiệu quả luôn là một thách thức không nhỏ.
Chúng tôi bắt đầu với một kiến trúc đơn giản, đồng thời thống nhất sử dụng một số công nghệ có sẵn: Redis, Google Protocol Buffers (GPB), và ZeroMQ.
Việc lựa chọn Redis và ZeroMQ là quyết định cá nhân mình cân nhắc rất kỹ lưỡng.
Lý do chọn Redis xuất phát từ bối cảnh các dự án trước đây mình từng tham gia đều không dùng cơ sở dữ liệu SQL. Điều này liên quan đặc thù ứng dụng MMO (Trò chơi trực tuyến nhiều người). Trong phát triển web thông thường, người dùng tạm thời, không có kết nối cố định, số lượng người dùng biến động lớn nên cần giải pháp mở rộng linh hoạt - lúc này SQL là lựa chọn hợp lý.
Tuy nhiên theo mô hình MVC, việc hiện thực hóa thành phần M (Model) không nhất thiết phải dùng SQL. Nếu xét từ góc độ phần mềm desktop, phần lớn các ứng dụng lại dùng cấu trúc dữ liệu trong bộ nhớ thay vì CSDL. Điều này không chỉ vì hiệu năng, mà chủ yếu do hành vi người dùng khác biệt.
Trong MMORPG, đặc biệt là các game của NetEase, việc không dùng SQL cho Model có hai nguyên nhân chính:
- Nền tảng phát triển ban đầu từ MUD không áp dụng SQL
- Số lượng người dùng cần xử lý cùng lúc giới hạn, dữ liệu chủ yếu tập trung vào thông tin người dùng riêng lẻ. Dù tổng lượng dữ liệu lớn, nhưng có thể dễ dàng đánh chỉ số theo ID người dùng. Mỗi người chơi được phục vụ liên tục, dữ liệu thay đổi thường xuyên, mô hình này gần với thiết kế phần mềm truyền thống hơn là kiến trúc web hiện đại.
Chúng ta hoàn toàn có thể chia tách logic Model mà không cần đến server CSDL vật lý, điều này giúp tối đa hiệu năng. Về bản chất, đây là mô hình đơn giản hóa của game đơn - chỉ cần xử lý nhiều luồng thao tác qua mạng và phản hồi hành động, thậm chí còn đơn giản hơn vì không cần xử lý giao diện đồ họa.
Miễn hệ thống ổn định, không xảy ra sự cố nguồn điện, việc lưu trữ dữ liệu có thể duy trì vô thời hạn. Dữ liệu bền vững thực chất chỉ nhằm phòng ngừa thảm họa. Giải pháp tối giản nhất là lưu trực tiếp lên file hệ điều hành thay vì dùng CSDL, vì cách này nhẹ nhàng và sạch sẽ hơn nhiều.
Hiểu rõ bối cảnh này sẽ dễ dàng thấy tại sao mình ưa thích Redis. Chúng tôi không thay đổi triết lý thiết kế vốn có, Redis giúp tách biệt dịch vụ dữ liệu một cách tự nhiên. Tất nhiên,随着MMORPG的发展环境变化,未来我们也会相应调整方案。
Về ZeroMQ, mình cần một giải pháp giao tiếp đa tiến trình ổn định và đơn giản. ZeroMQ đáp ứng tốt điều này, thậm chí còn vượt trội hơn cả thư viện Socket của hệ điều hành. Đặc biệt, với giao diện C của nó, việc tích hợp với nhiều ngôn ngữ lập trình khác trở nên dễ dàng.
Tuy nhiên, bạn Ốc Sên không đồng tình việc dùng ZeroMQ. Dù giữ quan điểm riêng, mình luôn tôn trọng ý kiến của thành viên trực tiếp phát triển. Anh ấy đề xuất dùng kết hợp Erlang + C Driver, lấy Erlang làm nền tảng trao đổi dữ liệu, các ngôn ngữ khác tích hợp thông qua Driver.
Mình thừa nhận phương án này khả thi, đặc biệt với kinh nghiệm Erlang hơn 5 năm của Ốc Sên. Tuy nhiên, mình không ưa chuộc các hệ thống cồng kềnh. Dù anh ấy lập luận rằng việc tự xây dựng giải pháp với C/C++ (Python/Lua/Golang…) + ZeroMQ sẽ dễ phát sinh lỗi, nhưng với 10 năm kinh nghiệm trong lĩnh vực game MMORPG, mình tin tưởng vào khả năng xây dựng hệ thống đơn giản nhưng bền vững. Chìa khóa chính là sự minh bạch, giúp hiểu rõ từng dòng mã nguồn.
Dù có tranh luận, mình luôn tuân thủ nguyên tắc: Cho phép người thực hiện chọn giải pháp phù hợp, miễn không có lỗi nghiêm trọng. Cuối cùng, chúng tôi quyết định không sử dụng ZeroMQ.
Về Google Protocol Buffers, mình không quá ưa thích nhưng đây là kết quả của sự thỏa hiệp đa chiều. Mình thích tự thiết kế giao thức riêng, chỉ mượn một phần chức năng từ GPB.
Mình công nhận GPB rất tốt trong việc định nghĩa mã hóa ở tầng底层 và ngôn ngữ mô tả giao thức cũng khá trực quan. Tuy nhiên, việc GPB tự mô tả chính nó theo mình là hơi phức tạp.
Ví dụ, nếu không dùng công cụ GPB có sẵn, rất khó để parse một giao thức GPB. Giống như việc compiler C đầu tiên phải được viết bằng C - tạo ra vòng luẩn quẩn “con gà và quả trứng”. Dù Lisp có thể giải quyết vấn đề này tốt hơn, nhưng như đa số lập trình viên, mình vẫn ưa dùng C hơn.
Một điểm khiến mình không hài lòng là GPB mặc định yêu cầu tạo mã nguồn riêng cho từng giao thức thay vì cung cấp thư viện C/C++ để binding ngôn ngữ khác. Điều này giống như việc thay vì dùng API regex thông thường, lại yêu cầu compile regex thành mã C rồi link vào project - dù hiệu năng cao nhưng bất tiện.
Tuy nhiên, đây chỉ là vấn đề sử dụng chứ không liên quan đến định nghĩa giao thức. Dù Google thiết lập cách dùng GPB từ đầu, mình vẫn quyết định phát triển một thư viện C thuần túy cho GPB không cần code generator, làm nền tảng cho các ngôn ngữ khác binding. Sau một tuần nỗ lực, phiên bản C đã gần hoàn tất (mình đã điều chỉnh thiết kế giao diện 2 lần). Sắp tới sẽ viết bài chi tiết về vấn đề này và open source dự án.
Chính nhờ nỗ lực này mà GPB chính thức được chọn làm giao thức truyền tin cho dự án.