Giới thiệu
Năm ngoái, tôi đã đặt ra thử thách cho bản thân là phát triển một trò chơi. Bài viết này sẽ phác thảo tầm nhìn của tôi về kiến trúc của một trò chơi giao dịch thời gian thực, nơi mà thời điểm là điều cực kỳ quan trọng.
Wolf Street — trò chơi giao dịch giấy. Phiên bản MVP là một lựa chọn đơn giản trong 30 giây với kế hoạch lớn để nâng cấp lên giao dịch giấy “thực sự” sau khi ra mắt.
Đối với những ai chưa quen với các sản phẩm phái sinh tài chính này, có thể tưởng tượng một lựa chọn như một ván cược. Người giao dịch, hay còn gọi là “người chơi,” đang cố gắng dự đoán sự biến động giá trong một khoảng thời gian nhất định.
Tại đây, tôi muốn thuyết phục bạn rằng bạn không nên chơi các tùy chọn nhị phân bằng tiền thật. Hầu hết các nền tảng trực tuyến sẽ chỉ lừa đảo bạn.
Ý tưởng của chúng tôi hoàn toàn khác — chúng tôi muốn tạo ra một không gian giao dịch an toàn và trung thực mà không phải mạo hiểm bất kỳ khoản tiền thật nào. Đó là cách mà nó sẽ hoạt động: các nhà tài trợ tạo ra các giải đấu giao dịch mang thương hiệu, người chơi tham gia vào chúng bằng các token ảo (không thể mua bằng tiền thật nhưng có thể kiếm được bằng cách chơi trò chơi), leo lên bảng xếp hạng và giành được những giải thưởng thực sự.
Dưới đây là cách mà trò chơi trông như thế nào.
Tuy nhiên, việc triển khai ý tưởng này không hề đơn giản — để đảm bảo tính công bằng của trò chơi, chúng tôi phải làm việc với giá BTC theo thời gian thực, và độ trễ giữa các hành động của người chơi và việc thực hiện chúng phải tối thiểu, vì chúng tôi đang xử lý một tùy chọn nhị phân.
Hãy đặt ra những câu hỏi đúng và xây dựng kiến trúc cùng nhau!
Thiết kế Kiến trúc
Dữ liệu thị trường sẽ được truyền tải từ polygon.io. Tất cả các giao dịch nên được xử lý bởi Game Engine, vì vậy ở dạng đơn giản nhất, kiến trúc trông như thế này:
Nó có thể hoạt động cho một POC, nhưng có rất nhiều vấn đề nếu chúng ta nói về một trò chơi đạt tiêu chuẩn sản xuất.
Trước hết, Game Engine chịu trách nhiệm cho mọi thứ: xử lý các luồng dữ liệu thị trường, phục vụ dữ liệu này cho khách hàng, xác thực các hành động của người dùng và xử lý giao dịch. Điều gì có thể sai? Mọi thứ. Quá nhiều kết nối khách hàng, lỗi từ Polygon, lỗi trò chơi, và nhiều thứ khác. Điều tồi tệ nhất là bất kỳ lỗi nào cũng có thể gây ra sự cố cho mọi thứ khác. Ngoài ra, việc thêm các tính năng mới vào trò chơi sẽ biến mã nguồn thành một cơn ác mộng. Hãy chia nhỏ Game Engine thành nhiều dịch vụ, mỗi dịch vụ phục vụ một mục đích riêng. Thêm vào đó, hãy thêm một cơ sở dữ liệu, vì dịch vụ hiện tại không lưu trữ bất kỳ tiến trình trò chơi nào.
Tại đây, dữ liệu giá xuất phát từ Polygon. Market Feed xử lý dữ liệu này, xây dựng các nến, và gửi chúng đến Trade Engine và các Client qua WebSocket. Dịch vụ Core được sử dụng cho tất cả các hoạt động không liên quan đến giao dịch, chẳng hạn như quản lý tài khoản. Chúng tôi lưu trữ dữ liệu người dùng và tất cả các giao dịch trong một cơ sở dữ liệu Postgres.
Khi một người chơi thực hiện giao dịch, một yêu cầu được gửi đến Trade Engine. Trade Engine có giá thực tế của tài sản vì Market Feed truyền tải nó và có thể xử lý giao dịch một cách chính xác.
Tuy nhiên, chúng ta vẫn chưa có một hệ thống hoàn chỉnh. Hãy xem xét quy trình hiện tại.
Xin lưu ý rằng tôi đã bỏ qua một số yêu cầu không liên quan trực tiếp đến việc xử lý giao dịch, chẳng hạn như những yêu cầu được thực hiện trong giai đoạn thiết lập dịch vụ hoặc những yêu cầu liên quan đến việc truy xuất dữ liệu tài khoản người dùng.
Bạn thấy vấn đề gì không? Làm thế nào để khách hàng được thông báo rằng giao dịch đã đóng và số dư đã được cập nhật? Long polling (các yêu cầu HTTP định kỳ) là một giải pháp tiềm năng ở đây, nhưng liệu đó có phải là giải pháp tốt nhất không? Chúng tôi đã có các cập nhật trực tiếp về giá do Market Feed cung cấp, và bây giờ chúng tôi muốn có các cập nhật tương tự về số dư và giao dịch của người dùng.
Ngoài ra, điều quan trọng là phải xem xét tải cao. Điều gì sẽ xảy ra nếu hàng ngàn hoặc hàng chục ngàn khách hàng kết nối với Market Feed? Hiệu suất của nó có bị suy giảm không? Market Feed có một nhiệm vụ cực kỳ quan trọng: cung cấp dữ liệu giá trực tiếp cho hệ thống. Không chỉ cho khách hàng, mà còn cho trái tim của trò chơi — Trade Engine. Quản lý các kết nối WebSocket của Client chắc chắn nằm ngoài phạm vi của microservice này, và bất kỳ vấn đề nào trong việc xử lý chúng có thể gây ra lỗi liên quan đến việc xử lý dữ liệu giá.
Một điều khác cần xem xét là điều gì sẽ xảy ra nếu chúng tôi cập nhật và triển khai lại dịch vụ Market Feed trong khi trò chơi đang hoạt động? Điều này sẽ dẫn đến việc mất dữ liệu giá lịch sử. Bạn có nhớ ảnh chụp màn hình từ trò chơi không? Chúng tôi có 2 phút lịch sử giá trên biểu đồ. Khởi động lại Market Feed sẽ gây ra lỗi trên UI trong 2 phút trong khi dữ liệu mới đang được hình thành để làm đầy lịch sử.
Hãy giải quyết mọi thứ bằng cách thêm một Redis để lưu trữ lịch sử giá của Market Feed và thêm một microservice mới để xử lý các kết nối WebSocket và phục vụ tất cả các cập nhật thời gian thực cho Client. Ngoài ra, tôi sẽ thêm một microservice API để đơn giản tạo một điểm cuối API thống nhất cho tất cả các yêu cầu HTTP REST. Chúng tôi cũng có thể sử dụng nó để proxy các kết nối WS, nhưng trong dự án thực tế, chúng tôi đã quyết định tách biệt WS khỏi dịch vụ HTTP.
Lưu ý rằng chúng tôi đã chia các dịch vụ của mình thành hai nhóm: nội bộ (màu xanh) và bên ngoài, bao gồm Polygon và mọi thứ mà người dùng cuối có thể truy cập.
Hãy cùng phân tích những gì đang xảy ra ở đây.
Nội bộ
- Market Feed xử lý luồng giá từ Polygon, lưu trữ dữ liệu vào Redis, và phục vụ nó qua WebSocket cho Trade Engine và WS Notifier. Đó là cách mà các dịch vụ này luôn có giá mới nhất.
- Trade Engine xử lý yêu cầu giao dịch từ API. Nhận dữ liệu giá trực tiếp qua WebSocket từ Market Feed. Lưu trữ dữ liệu giao dịch vào Postgres.
- Core xử lý các yêu cầu từ API liên quan đến tài khoản người dùng. Lưu trữ dữ liệu người dùng vào Postgres.
- Postgres Là nơi lưu trữ dữ liệu người dùng và giao dịch. Nó sử dụng cơ chế Postgres NOTIFY để cung cấp các cập nhật dữ liệu cho WS Notifier.
- Redis Lưu trữ lịch sử giá từ Market Feed. Truy cập đọc trực tiếp từ WS Notifier.
Bên ngoài
- API Một điểm truy cập thống nhất cho tất cả các yêu cầu API REST HTTP.
- WS Notifier Chịu trách nhiệm cung cấp các cập nhật trực tiếp về giá và dữ liệu người dùng (số dư) cho Client. Nó cung cấp lịch sử giá từ Redis cho lần đăng ký đầu tiên của Client và proxy các cập nhật giá trực tiếp từ Market Feed. Ngoài ra, dịch vụ này được đăng ký nhận các cập nhật dữ liệu giao dịch và người dùng từ Postgres và chuyển tiếp chúng đến các Client phù hợp.
Bây giờ, hãy nhìn vào sơ đồ tương tác cập nhật.
Đối mặt với Độ trễ
Có vẻ như mọi thứ đều hoàn hảo bây giờ, nhưng khi cố gắng chơi trò chơi của chính mình, tôi đã gặp một vấn đề đã làm hỏng toàn bộ trải nghiệm! Trong giao dịch tùy chọn, thời điểm là rất quan trọng. Đặc biệt khi chúng ta đang nói về các tùy chọn trong 30 giây — giá rất dễ biến động trong các khung thời gian thấp.
Vì vậy, khi chơi trò chơi ở Đông Nam Á với máy chủ trò chơi của tôi ở Frankfurt, EU, tôi đã gặp độ trễ một hoặc thậm chí hai giây giữa việc nhấn nút để mở giao dịch và việc thực hiện thực tế của nó. Điều này thật sự gây khó chịu khi mất giao dịch chỉ vì điều này, và nó đã xảy ra thường xuyên. Bạn nhấn “LÊN” với hy vọng giá sẽ tăng, quan sát vòng quay trong khoảng 2 giây trong khi biểu đồ giá tăng vọt, và sau đó nó cho bạn biết rằng bạn đã mở giao dịch của mình với một mức giá hoàn toàn khác, cao hơn nhiều so với bạn mong muốn. Một chút giảm giá và bạn thua. Nếu bạn đã mở giao dịch với giá ban đầu, bạn sẽ thắng. Tuy nhiên, những hạn chế kỹ thuật của trò chơi đơn giản đã không cho phép điều đó.
Làm cho nó Phân phối Địa lý
Hãy xây dựng một hệ thống cho phép người chơi mở giao dịch gần như ngay lập tức. Chúng ta cần khắc phục yêu cầu được đánh dấu màu cam trong sơ đồ tương tác trước đó. Để đạt được điều này, chúng ta cần đưa máy chủ trò chơi gần hơn với người chơi. Trước tiên, chúng ta cần quyết định dịch vụ nào nên được phân phối theo địa lý. Rõ ràng, Trade Engine. Chúng tôi quyết định giữ Core ở một bản sao duy nhất tại EU, vì nó chủ yếu được sử dụng trong quá trình tải trò chơi và không đóng góp đáng kể vào độ trễ.
Nếu chúng tôi di chuyển Trade Engine, chúng tôi cũng cần di chuyển API. Không có lý do gì khi đưa Trade Engine lại gần người dùng cuối nếu nó chỉ có thể truy cập thông qua một API không nằm ở cùng một vị trí. Nếu không, điều đó sẽ gây ra độ trễ nhiều hơn với các yêu cầu ping-pong khắp nơi trên thế giới.
Bây giờ chúng tôi có Trade Engine và API nằm gần hơn với người dùng cuối. Tất cả những gì còn lại là geo-phân phối WS Notifier. Mặc dù nó chỉ đơn giản chuyển dữ liệu từ UE, nhưng chúng tôi chọn điều này vì một số lý do. WS Notifier phân phối theo địa lý đảm bảo rằng khách hàng nhận được giá gần nhất với Trade Engine. Nó cũng cho phép mở rộng theo chiều ngang tốt hơn dựa trên tải khu vực.
Trade Engine và WS Notifier đều nhận dữ liệu giá từ Market Feed, nằm ở EU. Bây giờ cả hai đều có độ trễ cơ bản giống nhau từ EU đến khu vực của họ. Khi một khách hàng kết nối với WS Notifier, sự khác biệt dữ liệu giá từ Trade Engine khu vực bây giờ chỉ bị gây ra bởi độ trễ từ khách hàng đến WS Notifier khu vực, không phải độ trễ từ khách hàng đến WS Notifier EU.
Bây giờ hãy nhìn lại sơ đồ trước đó. Có một bước “lưu dữ liệu giao dịch” trong Trade Engine trước khi trả dữ liệu giao dịch về cho Client. Bước này yêu cầu Trade Engine phải thực hiện một yêu cầu đến Postgres ở EU, điều này là một hành động rất tốn kém về độ trễ.
Chúng tôi cần thực hiện thêm một tối ưu hóa nữa — hãy trở nên lạc quan! Và ý tôi là chúng tôi có thể coi giao dịch đã mở ngay cả trước khi tất cả các bước kiểm tra được vượt qua và trước khi dữ liệu được lưu trữ trong cơ sở dữ liệu. Điều đó sẽ cho phép chúng tôi trả về thời gian và giá của giao dịch ngay lập tức, với một chi phí liên quan đến độ trễ yêu cầu giữa Client và Trade Engine. Bạn có thể lập luận rằng điều này không an toàn vì có thể có vấn đề trong quá trình kiểm tra và lưu dữ liệu vào cơ sở dữ liệu, nhưng thực tế, điều này chỉ có thể xảy ra nếu ai đó cố gắng gian lận trong trò chơi, và chúng tôi không nên ưu tiên một giao diện mượt mà cho những cá nhân này. Trong trường hợp đó, giao dịch sẽ được đánh dấu là đang hoạt động trên client, nhưng nó sẽ không vượt qua kiểm tra và sẽ không được thực hiện ở phía backend. Đối với những ai chơi một cách trung thực, frontend xử lý tất cả các xác thực, chẳng hạn như chỉ có một giao dịch đang hoạt động tại một thời điểm, và tất cả chúng sẽ hợp lệ.
Còn việc đóng giao dịch thì sao? Như bạn có thể nhớ, trong hệ thống hiện tại của chúng tôi, Trade Engine đóng giao dịch, cập nhật dữ liệu trong Postgres, thông báo cho WS Notifier, và sau đó gửi cập nhật đến Client thích hợp. Vì Postgres có thể nằm xa Client, việc này có thể mất một thời gian, nhưng thực sự không phải là vấn đề. Vấn đề nằm ở việc mở giao dịch đúng lúc, nhưng sau đó, nó sẽ được đóng lại bởi Trade Engine trong 30 giây, và client có thể chờ thêm 1–2 giây (hoặc hơn) để nhận kết quả.
Chúng tôi chạy bộ đếm ngược 30 giây trên Client điều chỉnh theo thời gian mở giao dịch từ Trade Engine. Sau khi hết thời gian, chúng tôi đơn giản hiển thị một vòng quay, cho thấy rằng giao dịch đã được đóng nhưng chúng tôi đang chờ kết quả. Về lý thuyết, điều này có thể được tối ưu hóa — Trade Engine có thể gửi thông báo cho WS Notifier ngay khi giao dịch được đóng, trước khi cập nhật trạng thái trong Postgres. Tuy nhiên, trong trò chơi thực tế, điều này không phải là một vấn đề đáng kể.
Hãy cập nhật sơ đồ kiến trúc và sơ đồ tương tác.
Tôi đã thêm màu sắc vào các mũi tên để minh họa độ trễ giao tiếp của khách hàng. Các mũi tên màu xanh đại diện cho độ trễ thấp trong một khu vực, trong khi các mũi tên màu đỏ cho thấy độ trễ cao hơn do các yêu cầu vượt qua các ranh giới khu vực.
Chúng tôi đã giải quyết vấn đề, cho phép giao dịch mở gần như ngay lập tức!
Trong cấu hình này, trò chơi đã được phát hành và hoạt động rất tốt ngay cả dưới tải cao trong các giải đấu.
Hãy thử nghiệm trò chơi này qua Telegram: t.me/WolfStreetGameBot.
Cảm ơn bạn đã đọc.
Tôi hy vọng bài viết này đã cung cấp những hiểu biết quý giá để giúp bạn tiếp cận kiến trúc dự án một cách tự tin và rõ ràng hơn. Để tìm hiểu thêm về các bài viết sâu hơn về công nghệ và kinh doanh, hãy theo dõi tôi trên Medium và X.