Czy to odpowiednia architektura dla naszej gry mobilnej MMORPG?

14

Obecnie próbuję zaprojektować architekturę nowej gry mobilnej MMORPG dla mojej firmy. Ta gra jest podobna do Mafia Wars, iMobsters lub RISK. Podstawowym pomysłem jest przygotowanie armii do walki z przeciwnikami (użytkownicy online).

Chociaż wcześniej pracowałem nad wieloma aplikacjami mobilnymi, ale jest to dla mnie coś nowego. Po wielu zmaganiach opracowałem architekturę ilustrowaną za pomocą schematu wysokiego poziomu:

Schemat przepływu wysokiego poziomu

Zdecydowaliśmy się na model klient-serwer. Na serwerze będzie scentralizowana baza danych. Każdy klient będzie miał własną lokalną bazę danych, która pozostanie zsynchronizowana z serwerem. Ta baza danych działa jako pamięć podręczna do przechowywania rzeczy, które nie zmieniają się często, np. Mapy, produkty, zapasy itp.

Po wdrożeniu tego modelu nie jestem pewien, jak rozwiązać następujące problemy:

  • Jaki byłby najlepszy sposób synchronizacji baz danych serwera i klienta?
  • Czy zdarzenie powinno zostać zapisane w lokalnej bazie danych przed aktualizacją do serwera? Co jeśli aplikacja z jakiegoś powodu zostanie zakończona przed zapisaniem zmian w scentralizowanej bazie danych?
  • Czy proste żądania HTTP będą służyć synchronizacji?
  • Jak sprawdzić, którzy użytkownicy są obecnie zalogowani? (Jednym ze sposobów może być wysyłanie przez klienta żądania do serwera co x minut w celu powiadomienia, że ​​jest ono aktywne. W przeciwnym razie należy uznać klienta za nieaktywny).
  • Czy wystarczające są weryfikacje po stronie klienta? Jeśli nie, jak cofnąć akcję, jeśli serwer czegoś nie zweryfikuje?

Nie jestem pewien, czy jest to skuteczne rozwiązanie i jak się skaluje. Byłbym bardzo wdzięczny, gdyby ludzie, którzy już pracowali nad takimi aplikacjami, mogli podzielić się swoimi doświadczeniami, które mogą pomóc mi wymyślić coś lepszego. Z góry dziękuję.

Dodatkowe informacje:

Po stronie klienta zaimplementowano silnik gry C ++ o nazwie marmolada. Jest to wieloplatformowy silnik gry, co oznacza, że ​​możesz uruchomić aplikację na wszystkich głównych systemach operacyjnych dla urządzeń mobilnych. Z pewnością możemy osiągnąć gwintowanie, co zostało również zilustrowane na moim schemacie blokowym. Planuję używać MySQL dla serwera i SQLite dla klienta.

To nie jest gra turowa, więc nie ma wiele interakcji z innymi graczami. Serwer dostarczy listę graczy online i możesz z nimi walczyć klikając przycisk bitwy, a po animacji zostanie ogłoszony wynik.

Do synchronizacji baz danych mam na myśli dwa rozwiązania:

  1. Przechowuj znacznik czasu dla każdego rekordu. Śledź również datę ostatniej aktualizacji lokalnego DB. Podczas synchronizacji wybierz tylko te wiersze, które mają większy znacznik czasu i wyślij do lokalnej bazy danych. Zachowaj flagę isDeleted dla usuniętych wierszy, aby każde usunięcie zachowywało się jak aktualizacja. Ale mam poważne wątpliwości co do wydajności, ponieważ przy każdym żądaniu synchronizacji musielibyśmy przeskanować całą bazę danych i poszukać zaktualizowanych wierszy.
  2. Inną techniką może być prowadzenie dziennika każdego wstawiania lub aktualizacji, która ma miejsce w stosunku do użytkownika. Gdy aplikacja kliencka prosi o synchronizację, przejdź do tej tabeli i dowiedz się, które wiersze której tabeli zostały zaktualizowane lub wstawione. Po pomyślnym przesłaniu tych wierszy do klienta usuń ten dziennik. Ale potem myślę o tym, co się stanie, jeśli użytkownik użyje innego urządzenia. Zgodnie z tabelą dzienników wszystkie aktualizacje zostały przesłane dla tego użytkownika, ale tak naprawdę zostało to zrobione na innym urządzeniu. Być może będziemy musieli również śledzić urządzenie. Wdrożenie tej techniki jest bardziej czasochłonne, ale nie jestem pewien, czy zostanie ona wykonana jako pierwsza.
umair
źródło
2
Chwyciłbym ocenę MsSQL i bawił się nią - nie jestem pewien, co MySQL zapewnia w zakresie replikacji, ale MsSQL 2008 R2 daje WIĘKSZOŚĆ technik replikacji (5-6) do wyboru. To tylko myśl, nie nienawidząca MySQL ani nic takiego, ale Microsoft jest naprawdę dobry w klastrowaniu.
Jonathan Dickinson
1
@Jonathan Dickinson: Istnieje klaster MySQL: P
Coyote
1
Spójrz także na PostgreSQL - wersja 9 ma technologię replikacji, PostgreSQL ma doskonałą reputację do radzenia sobie z dużymi obciążeniami, wielu programistów wydaje się być optymistami, a co najważniejsze, jest to oprogramowanie open source i całkowicie bezpłatne dla wszystkich zastosowań (w tym komercyjne).
Randolf Richardson
chodzi o to, że nie możemy używać tej samej bazy danych po stronie klienta i serwera. Myślę, że najbardziej odpowiednim DB dla klienta byłby SQLite, aby nie zużywał dużo zasobów, ponieważ aplikacja działałaby na urządzeniach mobilnych. Podczas gdy osoba pracująca po stronie serwera zna tylko MySQL, dlatego zdecydowaliśmy się na to. Słyszałem również, że większość gier MMO korzysta z bazy danych MySQL.
umair
1
@umair: Po stronie klienta może być SQLite (na przykład na iPhonie jest już dostępny). Możesz jednak po prostu zapisać tylko potrzebne dane w pliku (co SQLite i tak robi) i nie zawracać sobie głowy bazą danych po stronie klienta, podobnie jak w przypadku aplikacji Javascript.
Coyote

Odpowiedzi:

15

Jeśli nie jest to gra „w czasie rzeczywistym”, w tym sensie, że gracze nie muszą widzieć natychmiastowego wyniku działań innego gracza na scenie gry, powinieneś być w porządku z żądaniami HTTP. Ale pamiętaj o kosztach HTTP.

To powiedziawszy, użycie HTTP nie uratuje cię od ostrożnego projektowania protokołu komunikacyjnego. Ale jeśli jesteś odpowiedzialny zarówno za serwer, jak i po stronie klienta, masz szczęście, ponieważ możesz dostosować protokół, gdy go potrzebujesz.

Aby zsynchronizować między główną bazą danych a bazą danych klienta, możesz użyć dowolnego protokołu transportu, do którego masz dostęp, HTTP lub innych. Ważną częścią jest logika synchronizacji. Aby uzyskać proste podejście, po prostu połącz z serwera wszystkie najnowsze zmiany potrzebne klientowi od ostatniej sygnatury czasowej w bazie danych klienta. Zastosuj go do bazy danych klienta i przejdź do tego. Jeśli masz więcej zmian po stronie klienta, prześlij go, jeśli nadal jest istotny, w przeciwnym razie odrzuć.

Niektóre gry nawet nie używają lokalnej bazy danych, po prostu śledzą status, zbierając odpowiednie informacje z serwera, gdy jest to potrzebne.

Jeśli utrata lokalnych zdarzeń jest nie do zaakceptowania, to tak, powinieneś mieć lokalną pamięć i zapisywać w niej tak często, jak to możliwe. Możesz spróbować to zrobić przed wysłaniem każdej sieci.

Aby sprawdzić, czy aktywni użytkownicy używali pingowania HTTP co 20 sekund w udanej grze ... Ta wartość wzrosła natychmiast, ponieważ serwery zostały przeciążone :( Zespół serwerów nie pomyślał o sukcesie. Chciałbym więc dodać wiadomość lub jakiś specjalny nagłówek w protokole komunikacyjnym, który pozwoli Ci ponownie skonfigurować klientów (w celu równoważenia obciążenia ping i innych wartości związanych z komunikacją).

Walidacje po stronie klienta są wystarczające, jeśli nie przeszkadza ci oszustów, hakerów i inne automatyczne skrypty atakujące twoją grę. Serwer może po prostu odrzucić twoje działanie i wysłać komunikat o błędzie z pewnymi szczegółami. Ten komunikat o błędzie jest obsługiwany przez wycofanie zmian lokalnych lub przez niestosowanie ich do pamięci lokalnej, jeśli można poczekać, aż serwer zareaguje na faktyczne zapisanie zmian.

Użyliśmy wzorca poleceń w naszej grze, aby umożliwić proste i wydajne wycofywanie nieudanych lub nieprawidłowych działań użytkownika. Komendy były odtwarzane lokalnie wysyłane do peerów lub tłumaczone na komunikat serwera, a następnie stosowane na peerach lub sprawdzane na serwerze, w przypadku problemu polecenia nie były odtwarzane, a scena gry powróciła do stanu początkowego z powiadomieniem.

Korzystanie z HTTP nie jest złym pomysłem, ponieważ później pozwoli na bardzo łatwą integrację z klientami Flash lub HTML5, jest elastyczny, możesz używać dowolnego rodzaju języka skryptowego serwera, a dzięki podstawowym technikom równoważenia obciążenia możesz później dodać więcej serwerów bez większego wysiłku skalować backend.

Masz dużo do zrobienia, ale to fajna praca ... Ciesz się!

Kojot
źródło
3
+1 za „HTML prawdopodobnie jest wystarczająco dobry”, ponieważ prawdopodobnie jest :).
Jonathan Dickinson
1
@Coyote: dziękuję za poświęcenie czasu i udzielenie tak szczegółowej odpowiedzi. Naprawdę podoba Ci się pomysł HTTP na zapewnienie wsparcia innym klientom.
umair
1
Niech moc będzie z tobą :)
Coyote
5

Jaki byłby najlepszy sposób synchronizacji baz danych serwera i klienta?

Najłatwiej jest zaimplementować bazę danych jako pojedynczy plik, który można przesłać. Jeśli próbujesz porównać różnice między bazami danych, to świat pełen bólu, a ja mówię z doświadczenia.

Pamiętaj, że twój serwer nie może ufać decyzjom podejmowanym przez klienta na podstawie tej lokalnej bazy danych, ponieważ klient może to zmienić. Musi istnieć wyłącznie w celu przedstawienia szczegółów.

Czy zdarzenie powinno zostać zapisane w lokalnej bazie danych przed aktualizacją do serwera?

Nie. Co jeśli serwer zdecyduje, że zdarzenie nigdy się nie wydarzyło? Klient i tak nie powinien podejmować decyzji o zdarzeniach, ponieważ nie można mu ufać.

Zauważyłem, że mówisz także o lokalnej bazie danych na dwa sposoby: jeden dla „rzeczy, które nie zmieniają się często” i dwa dla zdarzeń. Z powyższych powodów nie powinny one znajdować się w tej samej bazie danych - nie chcesz próbować scalać ani różnicować poszczególnych wierszy danych w bazach danych. Na przykład integralność referencyjna staje się problemem, gdy klient ma odniesienie do elementu, który zdecydujesz się usunąć z serwera. Lub jeśli klient zmienia wiersz, a serwer zmienia wiersz, która zmiana ma pierwszeństwo i dlaczego?

Czy proste żądania HTTP będą służyć synchronizacji?

Tak, pod warunkiem, że są dość rzadkie lub małe. HTTP nie jest efektywny pod względem przepustowości, więc miej to na uwadze.

Jak sprawdzić, którzy użytkownicy są obecnie zalogowani? (Jednym ze sposobów może być wysyłanie przez klienta żądania do serwera co x minut w celu powiadomienia, że ​​jest ono aktywne. W przeciwnym razie należy uznać klienta za nieaktywny).

Jeśli używasz przejściowego protokołu, takiego jak HTTP, to rozsądny pomysł. Gdy serwer otrzyma wiadomość od klienta, możesz zaktualizować czas „ostatniego obejrzenia” dla tego klienta.

Czy wystarczające są weryfikacje po stronie klienta? Jeśli nie, jak cofnąć akcję, jeśli serwer czegoś nie zweryfikuje?

Nie, wcale nie. Klient jest w rękach wroga. Jak cofnąć akcję, zależy całkowicie od tego, co uważasz za akcję i jakie mogą mieć efekty. Najłatwiejszą drogą jest w ogóle nie zaimplementowanie akcji, dopóki serwer nie zareaguje, aby na to zezwolić. Nieco trudniejszą drogą jest upewnienie się, że każda akcja ma możliwość cofnięcia akcji i buforowania wszystkich niepotwierdzonych akcji na kliencie. Gdy serwer je potwierdzi, usuń je z pamięci podręcznej. Jeśli akcja zostanie odrzucona, wycofaj każdą akcję w odwrotnej kolejności, aż do odrzuconej.

Kylotan
źródło
1
Udało wam się. Masz rację, synchronizując bazy danych +1 Klient nigdy nie powinien samodzielnie zmieniać bazy danych ... Wystarczy wywołać funkcje działające na serwerze i zaktualizować bazę danych za pomocą logiki gry, a następnie pobrać wyniki.
Coyote
@Kylotan: dzięki za wskazanie przepływów w moim modelu
umair,
@Kylotan: „Najłatwiej jest zaimplementować bazę danych jako pojedynczy plik, który można przesłać. Jeśli próbujesz porównać różnice między bazami danych, to świat pełen bólu, a ja mówię z doświadczenia”. oznacza to, że wszystkie dane są pobierane przy każdym uruchomieniu aplikacji. Są rzeczy, które nie zmieniają się bardzo często, więc ich pobieranie za każdym razem może być nie do przyjęcia. Zwiększy to również znacznie czas uruchamiania aplikacji. jakieś pomysły?
umair,
Jeśli dane są małe, problem zniknie. Byłbym zaskoczony, gdyby Twoja scentralizowana baza danych statycznych była bardzo duża. Ale w każdym razie, jeśli podzielisz dane statyczne na mniejsze części, wystarczy wysłać tylko te, które zmieniły się od ostatniego razu. Jasne, możesz to zrobić dla każdego wiersza, jeśli chcesz zachować czas modyfikacji w każdym rzędzie. Ogólnie wolę mieć dane statyczne poza relacyjną bazą danych, aby umożliwić łatwe ich nadpisanie.
Kylotan