- Lichess là một nền tảng cờ vua mã nguồn mở miễn phí với hàng triệu người chơi trên toàn thế giới
- Sử dụng tab Network của Chrome DevTools để theo dõi giao tiếp giữa client và server
Kết nối WebSocket
- Hành vi mạng đáng chú ý đầu tiên là một kết nối WebSocket tới URL tương tự như sau:
wss://socket2.lichess.org/play/H5uHz0egyvIA/v6?sri=bt6QzcyOiZg5&v=0
- Giao thức
wssbiểu thị một kết nối WebSocket được mã hóa dùng TLS - WebSocket cho phép giao tiếp song công toàn phần, giúp cập nhật thời gian thực giữa client và server mà không cần các yêu cầu HTTP lặp đi lặp lại
Lượt của người chơi cục bộ
- Khi thực hiện một hành động, các gói dữ liệu sẽ được trao đổi:
// gửi lúc 22:51:35.280
{
"t": "move",
"d": {
"u": "d2d4",
"l": 32,
"a": 1
}
}
- Tin nhắn nhận được từ server:
// nhận lúc 22:51:35.312
{
"t": "ack",
"d": 1
}
- Điều này cho biết server đã nhận được hành động của chúng ta
// nhận lúc 22:51:35.312
{
"t": "move",
"v": 1,
"d": {
"uci": "d2d4",
"san": "d4",
"fen": "rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR",
"ply": 1,
"clock": {
"white": 300,
"black": 300
}
}
}
- Tin nhắn này cung cấp thông tin chi tiết về nước đi chúng ta vừa thực hiện và trạng thái ván cờ đã được cập nhật
Lượt của đối thủ
- Khi đối thủ thực hiện hành động, ta nhận được một gói tương tự từ server:
// nhận lúc 22:51:43.489
{
"t": "move",
"v": 2,
"d": {
"uci": "d7d5",
"san": "d5",
"fen": "rnbqkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBQKBNR",
"ply": 2,
"dests": {
"c2": "c3c4",
"g2": "g3g4"
// các nước đi bổ sung có thể có
},
"clock": {
"white": 300,
"black": 300
}
}
}
- Tham số
destsliệt kê tất cả các nước đi khả dụng ở vị trí hiện tại
Kiến trúc của Lichess
- Hệ thống chơi thời gian thực của Lichess chủ yếu gồm hai dịch vụ chính (cả hai đều được viết bằng Scala):
lila: dịch vụ lõi quản lý logic ván cờ, trạng thái, tương tác người dùng và các chức năng cốt lõi kháclila-ws: dịch vụ chuyên xử lý WebSocket, đóng vai trò cầu nối giữa client vàlila
Tổng quan kiến trúc
lila <-> redis <-> lila-ws <-> websocket <-> client
lilagiao tiếp vớilila-wsthông qua Redis, vàlila-wsquản lý các kết nối WebSocket với client
Giao tiếp bằng Redis Pub/Sub
- Các sự kiện nước đi được publish lên các kênh Redis Pub/Sub, nơi
lilađăng ký để xử lý các nước đi - Redis Pub/Sub cung cấp cơ chế chuyển phát at-most-once. Tin nhắn có thể bị mất nhưng đổi lại giảm mức sử dụng bộ nhớ
Tính bền vững dữ liệu cuối cùng với MongoDB
lilalưu trạng thái ván cờ vào MongoDB, nhưng không lưu ngay lập tức từng nước đi đơn lẻ- Thay vào đó, nó đệm các nước đi và lưu theo chu kỳ để giảm tải cho DB
- Khi có sự kiện quan trọng xảy ra, trạng thái ván cờ sẽ được flush
Tham gia một ván đang diễn ra
- Khi người chơi kết nối, họ cung cấp tham số
vđể cho hệ thống biết phiên bản mới nhất của ván cờ mà họ đang nắm được lila-wssử dụngConcurrentHashMapđể theo dõi và quản lý mọi sự kiện của các ván cờ đang diễn ra
Kết luận
Quy trình thực hiện một nước đi trên Lichess có thể được tóm tắt như sau:
- Client thiết lập kết nối WebSocket với
lila-ws - Khi người chơi thực hiện một nước đi, client gửi sự kiện nước đi tới
lila-ws lila-wsphản hồiackđể xác nhận đã nhận được nước đi- Sự kiện nước đi được publish lên kênh Redis Pub/Sub và được
lilaxử lý lilanhận nước đi, cập nhật trạng thái ván cờ và cuối cùng lưu vào MongoDB. Trạng thái ván cờ đã cập nhật sau đó được gửi trở lại client thông qualila-ws- Client nhận trạng thái ván cờ đã cập nhật, phản ánh nước đi mới và các thay đổi trong ván cờ
Ý kiến của GN⁺
- Bài viết này xem xét chi tiết kiến trúc backend và quy trình giúp lichess.org, một nền tảng cờ vua mã nguồn mở phổ biến, vận hành gameplay thời gian thực
- Nó giới thiệu những yếu tố kỹ thuật quan trọng cần cân nhắc khi xây dựng ứng dụng web thời gian thực, chẳng hạn giao tiếp thời gian thực bằng WebSocket, truyền tin có thể mở rộng bằng Redis Pub/Sub và lưu trữ dữ liệu cuối cùng bằng MongoDB
- Kiến trúc của Lichess rất phù hợp cho game nhiều người chơi thời gian thực, nhưng các mẫu và công nghệ tương tự cũng có thể áp dụng cho những loại ứng dụng web thời gian thực khác như chat, công cụ cộng tác và feed mạng xã hội
- Các tính năng thời gian thực có thể cải thiện trải nghiệm và tương tác người dùng, nhưng cũng đặt ra những thách thức kỹ thuật riêng về khả năng mở rộng, độ ổn định và tính nhất quán dữ liệu. Bài viết này đưa ra các chiến lược để giải quyết những thách thức đó
- Các dự án mã nguồn mở dùng stack công nghệ tương tự bao gồm Socket.IO (framework ứng dụng thời gian thực dựa trên Node.js) và RethinkDB (cơ sở dữ liệu NoSQL tối ưu cho ứng dụng web thời gian thực)
- Phân tích trong bài viết này không dựa trên việc xem xét trực tiếp mã nguồn của Lichess, nên có thể có khác biệt so với cách triển khai thực tế. Tuy nhiên, các khái niệm cốt lõi và mẫu kiến trúc được mô tả vẫn có giá trị
- Khi thiết kế hệ thống thời gian thực, cần cân nhắc cẩn thận liệu cơ chế chuyển phát at-most-once (có thể mất tin nhắn) hay at-least-once (có thể trùng lặp tin nhắn) sẽ phù hợp hơn. Điều này phụ thuộc vào yêu cầu và các đánh đổi của ứng dụng
1 bình luận
Ý kiến trên Hacker News
Có phàn nàn về cấu trúc tính thời gian của Chess.com. Có vẻ như máy chủ theo dõi thời gian nên bỏ qua thời gian truyền và độ trễ. Điều này đặc biệt bất tiện khi chơi cờ giới hạn thời gian trên ứng dụng di động
Lichess đã chọn cách tiếp cận kiểu StackOverflow và sử dụng máy chủ mạnh
Việc tính toán nước đi ở phía máy chủ giúp đảm bảo tính nhất quán và tối ưu hiệu năng cho các client có năng lực xử lý hoặc năng lượng hạn chế
Phần giải thích về cách xử lý mất thông điệp trong kênh pub/sub của Redis còn thiếu
Tham số
lcó thể biểu thị độ trễ mà máy chủ quan sát đượcViệc máy chủ liệt kê và gửi toàn bộ các nước đi hợp lệ tiếp theo là điều gây ngạc nhiên
Có câu hỏi về cách bảo vệ máy chủ WebSocket
Có người thắc mắc vì sao giao thức lại cần ack
FEN chỉ mã hóa trạng thái bàn cờ, không bao gồm trạng thái ván đấu