A look at how Tencent Games built service architecture based on CQRS and event sourcing patterns with Pulsar and ScyllaDB. Là một phần của Tencent Interactive Entertainment Group Global (IEG Global), Proxima Beta cam kết hỗ trợ các nhóm và studio của chúng tôi mang lại các trò chơi độc đáo và thú vị cho hàng triệu người chơi trên toàn thế giới. Đội ngũ của chúng tôi tại Level Infinite chịu trách nhiệm quản lý một loạt các rủi ro đối với doanh nghiệp của chúng tôi – ví dụ, các hoạt động gian lận và nội dung có hại. Từ quan điểm kỹ thuật, điều này đòi hỏi chúng tôi phải xây dựng một hệ thống phân tích thời gian thực hiệu quả để theo dõi nhất quán tất cả các loại hoạt động trong lĩnh vực kinh doanh của chúng tôi. Trong blog này, chúng tôi chia sẻ kinh nghiệm của chúng tôi trong việc xây dựng hệ thống phân tích sự kiện theo thời gian thực này.Trước tiên, chúng tôi sẽ khám phá lý do tại sao chúng tôi xây dựng kiến trúc dịch vụ dựa trên phân chia trách nhiệm lệnh và truy vấn ( ) và event sourcing patterns với và ScyllaDB. Tiếp theo, chúng tôi sẽ xem xét cách chúng tôi sử dụng ScyllaDB để giải quyết vấn đề gửi sự kiện đến nhiều phiên chơi game. Cuối cùng, chúng tôi sẽ bao gồm cách chúng tôi sử dụng không gian phím ScyllaDB và sao chép dữ liệu để đơn giản hóa việc quản lý dữ liệu toàn cầu của chúng tôi. CQRs Tải Apache Pulsar Một cái nhìn vào trường hợp sử dụng: Giải quyết rủi ro trong các trò chơi của Tencent Hãy bắt đầu với một ví dụ thế giới thực về những gì chúng ta đang làm việc và những thách thức mà chúng ta phải đối mặt. Đây là ảnh chụp màn hình từ Tower of Fantasy, một trò chơi nhập vai hành động 3D. Người chơi có thể sử dụng cuộc đối thoại này để gửi báo cáo chống lại người chơi khác vì nhiều lý do khác nhau.Nếu bạn sử dụng một hệ thống CRUD điển hình cho nó, làm thế nào bạn sẽ giữ những hồ sơ đó để theo dõi?Và những vấn đề tiềm ẩn là gì? Thách thức đầu tiên sẽ là xác định nhóm nào sẽ sở hữu cơ sở dữ liệu để lưu trữ biểu mẫu này. Có những lý do khác nhau để tạo báo cáo (bao gồm một tùy chọn được gọi là "Người khác"), vì vậy một trường hợp có thể được xử lý bởi các nhóm chức năng khác nhau. tuy nhiên, không có một nhóm chức năng duy nhất trong tổ chức của chúng tôi có thể sở hữu đầy đủ biểu mẫu. Đó là lý do tại sao nó là một lựa chọn tự nhiên cho chúng tôi để nắm bắt trường hợp này như là một sự kiện, chẳng hạn như "báo cáo một trường hợp."Tất cả thông tin được nắm bắt trong sự kiện này như là.Tất cả các nhóm chức năng chỉ cần đăng ký sự kiện này và làm bộ lọc của riêng họ.Nếu họ nghĩ rằng trường hợp rơi vào lĩnh vực của họ, họ chỉ có thể nắm bắt nó và kích hoạt các hành động khác. CQRS và Event Sourcing Kiến trúc dịch vụ đằng sau ví dụ này dựa trên các mô hình nguồn cấp CQRS và sự kiện. Nếu các điều khoản này là mới đối với bạn, đừng lo lắng! Vào cuối tổng quan này, bạn nên có một sự hiểu biết vững chắc về các khái niệm này. Và nếu bạn muốn chi tiết hơn tại thời điểm đó, hãy xem chúng tôi . Blog dành riêng cho chủ đề này Khái niệm đầu tiên để hiểu ở đây là nguồn cấp sự kiện. Ý tưởng cốt lõi đằng sau nguồn cấp sự kiện là mọi thay đổi về trạng thái của hệ thống được ghi lại trong một đối tượng sự kiện và các đối tượng sự kiện này được lưu trữ theo thứ tự mà chúng được áp dụng cho trạng thái hệ thống. Nói cách khác, thay vì chỉ lưu trữ trạng thái hiện tại, chúng tôi sử dụng một cửa hàng chỉ phụ lục để ghi lại toàn bộ loạt các hành động được thực hiện trên trạng thái đó. khái niệm này đơn giản nhưng mạnh mẽ vì các sự kiện đại diện cho mỗi hành động được ghi lại để bất kỳ mô hình có thể mô tả hệ thống có thể được xây dựng từ các sự kiện. Khái niệm tiếp theo là CQRS, viết tắt của Command Query Responsibility Segregation. CQRS được phát minh bởi Greg Young hơn một thập kỷ trước và bắt nguồn từ Nguyên tắc tách lệnh và truy vấn. Ý tưởng cơ bản là tạo ra các mô hình dữ liệu riêng biệt cho đọc và viết, thay vì sử dụng cùng một mô hình cho cả hai mục đích. Bằng cách làm theo mô hình CQRS, mỗi API nên là một lệnh thực hiện một hành động, hoặc một truy vấn trả lại dữ liệu cho người gọi - nhưng không phải cả hai. Sự tách biệt này mang lại nhiều lợi ích. Ví dụ, chúng tôi có thể mở rộng khả năng viết và đọc độc lập để tối ưu hóa hiệu quả chi phí. Từ quan điểm làm việc theo nhóm, các nhóm khác nhau có thể tạo ra các dạng xem khác nhau của cùng một dữ liệu với ít xung đột hơn. Dòng công việc cấp cao của trang viết có thể được tóm tắt như sau: các sự kiện xảy ra trong nhiều phiên trò chơi được cung cấp cho một số lượng hạn chế của bộ xử lý sự kiện. Việc thực hiện cũng đơn giản, thường liên quan đến một bus tin nhắn như Pulsar, Kafka, hoặc một hệ thống hàng đợi đơn giản hơn hoạt động như một cửa hàng sự kiện. Sự kiện từ khách hàng vẫn tồn tại trong cửa hàng sự kiện theo chủ đề và bộ xử lý sự kiện tiêu thụ sự kiện bằng cách đăng ký chủ đề. Nếu bạn quan tâm đến lý do tại sao chúng tôi chọn Apache Pulsar so với các hệ thống khác, bạn có thể tìm thấy thêm thông tin trong . Blog đã đề cập trước đó Mặc dù các hệ thống giống như hàng đợi thường hiệu quả trong việc xử lý lưu lượng truy cập chảy theo một hướng (ví dụ như fan-in), chúng có thể không hiệu quả trong việc xử lý lưu lượng truy cập chảy theo hướng ngược lại (ví dụ như fan-out). Trong kịch bản của chúng tôi, số lượng phiên chơi game sẽ lớn và một hệ thống hàng đợi điển hình không phù hợp tốt vì chúng tôi không thể đủ khả năng tạo hàng đợi dành riêng cho mỗi phiên chơi game. Chúng tôi cần tìm cách thực tế để phân phối các phát hiện và số liệu cho các phiên chơi game riêng lẻ thông qua Query API. Đó là lý do tại sao chúng tôi sử dụng ScyllaDB để xây dựng một cửa hàng sự kiện giống như hàng đợi, được tối ưu hóa cho fan-out sự kiện. Trước khi tiếp tục, đây là tóm tắt về kiến trúc dịch vụ của chúng tôi. Bắt đầu từ phía viết, máy chủ trò chơi liên tục gửi các sự kiện đến hệ thống của chúng tôi thông qua các điểm cuối lệnh và mỗi sự kiện đại diện cho một loại hoạt động nhất định xảy ra trong một phiên trò chơi. bộ xử lý sự kiện tạo ra các phát hiện hoặc số liệu đối với các luồng sự kiện của mỗi phiên trò chơi và hoạt động như một cây cầu giữa hai bên. Distributed Queue-Like Event Store for Time Series Sự kiện Bây giờ chúng ta hãy xem cách chúng ta sử dụng ScyllaDB để giải quyết vấn đề gửi các sự kiện đến nhiều phiên chơi game. Nhân tiện, nếu bạn Google “Cassandra” và “queue”, bạn có thể gặp một bài viết từ hơn một thập kỷ trước tuyên bố rằng sử dụng Cassandra như một hàng đợi là một phản mẫu. Mặc dù điều này có thể đúng vào thời điểm đó, tôi sẽ tranh luận rằng nó chỉ đúng một phần ngày nay. Để hỗ trợ việc phân phối các sự kiện cho mỗi phiên chơi game, chúng tôi sử dụng session id làm phím phân vùng để mỗi phiên chơi game có phân vùng riêng của nó và các sự kiện thuộc một phiên chơi game cụ thể có thể được đặt bởi session id hiệu quả. Mỗi sự kiện cũng có một ID sự kiện duy nhất, đó là UUID thời gian, là khóa nhóm. Bởi vì các bản ghi trong cùng một phân vùng được sắp xếp bởi khóa nhóm, ID sự kiện có thể được sử dụng như ID vị trí trong hàng đợi. Cuối cùng, khách hàng ScyllaDB có thể hiệu quả truy xuất các sự kiện mới đến bằng cách theo dõi ID sự kiện của sự kiện gần đây nhất đã nhận được. Có một cảnh báo cần lưu ý khi sử dụng phương pháp này: vấn đề tính nhất quán. Tìm các sự kiện mới bằng cách theo dõi ID sự kiện gần đây nhất dựa trên giả định rằng không có sự kiện nào có ID nhỏ hơn sẽ xảy ra trong tương lai. Tuy nhiên, giả định này có thể không phải lúc nào cũng đúng. Ví dụ, nếu hai nút tạo ra hai ID sự kiện cùng một lúc, một sự kiện có ID nhỏ hơn có thể được chèn sau một sự kiện có ID lớn hơn. Vấn đề này, mà tôi gọi là "phantom read", tương tự như hiện tượng trong thế giới SQL, nơi lặp lại cùng một truy vấn có thể mang lại kết quả khác nhau do những thay đổi không cam kết được thực hiện bởi một giao dịch khác. tuy nhiên, nguyên nhân gốc rễ của vấn đề trong trường hợp của chúng tôi là khác. Có nhiều cách để giải quyết vấn đề này. Một giải pháp là duy trì trạng thái toàn bộ cụm, mà tôi gọi là "pseudo bây giờ", dựa trên giá trị nhỏ nhất của các dấu thời gian di chuyển trong số tất cả các bộ xử lý sự kiện. Một cân nhắc quan trọng khác là cho phép TimeWindowCompactionStrategy, loại bỏ tác động tiêu cực đến hiệu suất do đá mộ gây ra. Sự tích lũy của đá mộ là một vấn đề lớn ngăn chặn việc sử dụng Cassandra như một hàng đợi trước khi TimeWindowCompactionStrategy trở nên có sẵn. Bây giờ chúng ta hãy chuyển sang thảo luận về các lợi ích khác ngoài việc sử dụng ScyllaDB như một hàng đợi phân phối. Đơn giản hóa các thách thức phân phối dữ liệu toàn cầu phức tạp Vì chúng tôi đang xây dựng một hệ thống đa chi nhánh để phục vụ khách hàng trên toàn thế giới, điều quan trọng là phải đảm bảo rằng các cấu hình của khách hàng là nhất quán trên các cụm trong các khu vực khác nhau. Chúng tôi đã giải quyết vấn đề này bằng cách chỉ đơn giản là cho phép sao chép dữ liệu trên một không gian phím trên tất cả các trung tâm dữ liệu. Điều này có nghĩa là bất kỳ thay đổi nào được thực hiện trong một trung tâm dữ liệu cuối cùng sẽ lây lan cho những người khác. Cảm ơn ScyllaDB, cũng như DynamoDB và Cassandra, cho việc nâng cấp nặng nề khiến vấn đề đầy thử thách này có vẻ tầm thường. Bạn có thể nghĩ rằng việc sử dụng bất kỳ RDBMS điển hình nào có thể đạt được kết quả tương tự vì hầu hết các cơ sở dữ liệu cũng hỗ trợ sao chép dữ liệu. Điều này đúng nếu chỉ có một trường hợp của bảng điều khiển chạy trong một khu vực nhất định. Trong một kiến trúc primary/replica điển hình, chỉ nút chính hỗ trợ đọc/viết trong khi nút replica chỉ đọc. Tuy nhiên, khi bạn cần chạy nhiều trường hợp của bảng điều khiển trên các khu vực khác nhau – ví dụ, mỗi người thuê có bảng điều khiển chạy trong khu vực nhà của họ, hoặc thậm chí mỗi khu vực có bảng điều khiển chạy cho các nhóm địa phương – việc thực hiện điều này trở nên khó khăn hơn nhiều bằng cách sử dụng kiến trúc primary/replica điển hình. Nếu bạn đã sử dụng AWS DynamoDB, bạn có thể quen thuộc với một tính năng gọi là Global Table, cho phép các ứng dụng đọc và viết cục bộ và truy cập dữ liệu toàn cầu. Cho phép sao chép trên không gian phím với ScyllaDB cung cấp một tính năng tương tự, nhưng không có khóa nhà cung cấp. Keyspace như container dữ liệu Tiếp theo, chúng ta hãy xem cách chúng ta sử dụng không gian phím như các container dữ liệu để cải thiện tính minh bạch của việc phân phối dữ liệu toàn cầu. Hãy xem biểu đồ dưới đây. nó cho thấy một giải pháp cho một vấn đề phân phối dữ liệu điển hình áp đặt bởi luật bảo vệ dữ liệu. Ví dụ, giả sử khu vực A cho phép một số loại dữ liệu được xử lý bên ngoài biên giới của nó miễn là một bản sao ban đầu được giữ trong khu vực của nó. * Đánh giá * Đánh giá Một giải pháp tiềm năng là thực hiện các thử nghiệm từ đầu đến cuối (E2E) để đảm bảo rằng các ứng dụng gửi dữ liệu chính xác đến đúng khu vực như mong đợi. Cách tiếp cận này đòi hỏi các nhà phát triển ứng dụng phải chịu trách nhiệm đầy đủ về việc thực hiện phân phối dữ liệu một cách chính xác. tuy nhiên, khi số lượng ứng dụng tăng lên, nó trở nên không thực tế cho mỗi ứng dụng để xử lý vấn đề này riêng lẻ và các thử nghiệm E2E cũng trở nên ngày càng tốn kém về cả thời gian và tiền bạc. Bằng cách cho phép sao chép dữ liệu trên không gian phím, chúng ta có thể chia trách nhiệm phân phối dữ liệu một cách chính xác thành hai nhiệm vụ: 1) xác định các loại dữ liệu và tuyên bố điểm đến của chúng, và 2) sao chép hoặc di chuyển dữ liệu đến các vị trí dự kiến. Bằng cách tách hai nhiệm vụ này, chúng ta có thể trừu tượng các cấu hình và quy định phức tạp từ các ứng dụng. Điều này là bởi vì quá trình chuyển dữ liệu đến một khu vực khác thường là phần phức tạp nhất để xử lý, chẳng hạn như vượt qua ranh giới mạng, mã hóa lưu lượng truy cập đúng cách và xử lý sự gián đoạn. Sau khi tách hai nhiệm vụ này, các ứng dụng chỉ được yêu cầu thực hiện đúng bước đầu tiên, điều này dễ dàng hơn nhiều để xác minh thông qua thử nghiệm ở giai đoạn đầu của chu kỳ phát triển. Ngoài ra, tính chính xác của cấu hình phân phối dữ liệu trở nên dễ dàng hơn nhiều để xác minh và kiểm tra. Mẹo cho những người khác đi theo một con đường tương tự Để kết luận, chúng tôi sẽ để lại cho bạn những bài học quan trọng mà chúng tôi đã học được, và chúng tôi khuyên bạn nên áp dụng nếu bạn kết thúc theo một con đường tương tự như của chúng tôi: Khi sử dụng ScyllaDB để xử lý dữ liệu chuỗi thời gian, chẳng hạn như sử dụng nó như một hàng đợi phân phối sự kiện, hãy nhớ sử dụng Chiến lược nén cửa sổ thời gian. Hãy xem xét việc sử dụng không gian phím làm thùng chứa dữ liệu để tách trách nhiệm phân phối dữ liệu.Điều này có thể làm cho các vấn đề phân phối dữ liệu phức tạp dễ quản lý hơn nhiều. Xem Tech Talks On-Demand Bài viết này dựa trên một cuộc trò chuyện công nghệ được trình bày tại Hội nghị thượng đỉnh ScyllaDB 2023. Bạn xem cuộc trò chuyện này - cũng như các cuộc trò chuyện của các kỹ sư từ Discord, Epic Games, Disney, Strava, ShareChat và nhiều hơn nữa - theo yêu cầu. Xem các cuộc đàm phán công nghệ theo yêu cầu