Michaił Salosin. Spotkanie Golanga. Korzystanie z Go w zapleczu aplikacji Look+

Michaił Salosin (dalej – MS): - Cześć wszystkim! Mam na imię Michał. Pracuję jako backend developer w MC2 Software i opowiem o wykorzystaniu Go w backendzie aplikacji mobilnej Look+.

Michaił Salosin. Spotkanie Golanga. Korzystanie z Go w zapleczu aplikacji Look+

Czy ktoś tutaj lubi hokej?

Michaił Salosin. Spotkanie Golanga. Korzystanie z Go w zapleczu aplikacji Look+

Zatem ta aplikacja jest dla Ciebie. Jest przeznaczony na Androida i iOS i służy do oglądania transmisji różnych wydarzeń sportowych online i nagrywanych. Aplikacja zawiera także różne statystyki, transmisje tekstowe, tabele konferencji, turniejów i inne informacje przydatne fanom.

Michaił Salosin. Spotkanie Golanga. Korzystanie z Go w zapleczu aplikacji Look+

Również w aplikacji istnieje coś takiego jak momenty wideo, czyli możesz obejrzeć najważniejsze momenty meczów (gole, walki, rzuty karne itp.). Jeśli nie chcesz oglądać całej transmisji, możesz obejrzeć tylko najciekawsze.

Czego użyłeś w rozwoju?

Główna część została napisana w Go. API, z którym komunikują się klienci mobilni, zostało napisane w Go. W Go napisano także usługę wysyłania powiadomień push na telefony komórkowe. Musieliśmy także napisać własny ORM, o którym być może pewnego dnia porozmawiamy. Cóż, w Go napisano kilka małych usług: zmiana rozmiaru i ładowanie obrazów dla redaktorów...

Jako bazę danych użyliśmy PostgreSQL. Interfejs edytora został napisany w Ruby on Rails przy użyciu gem'a ActiveAdmin. Importowanie statystyk od dostawcy statystyk jest również napisane w języku Ruby.

Do testów API systemu użyliśmy testu jednostkowego Pythona. Memcached służy do ograniczania połączeń płatniczych API, „Chef” służy do kontrolowania konfiguracji, Zabbix służy do gromadzenia i monitorowania wewnętrznych statystyk systemu. Graylog2 służy do zbierania logów, Slate to dokumentacja API dla klientów.

Michaił Salosin. Spotkanie Golanga. Korzystanie z Go w zapleczu aplikacji Look+

Wybór protokołu

Pierwszy problem, jaki napotkaliśmy: musieliśmy wybrać protokół interakcji pomiędzy backendem a klientami mobilnymi, w oparciu o następujące punkty...

  • Najważniejszy wymóg: dane o klientach muszą być aktualizowane w czasie rzeczywistym. Oznacza to, że każdy, kto aktualnie ogląda transmisję, powinien niemal natychmiast otrzymać aktualizacje.
  • Dla uproszczenia założyliśmy, że dane synchronizowane z klientami nie są usuwane, lecz ukrywane za pomocą specjalnych flag.
  • Wszelkiego rodzaju rzadkie żądania (takie jak statystyki, skład zespołu, statystyki zespołu) są uzyskiwane za pomocą zwykłych żądań GET.
  • Dodatkowo system musiał bez problemu obsłużyć jednocześnie 100 tys. użytkowników.

Na tej podstawie mieliśmy dwie opcje protokołu:

  1. Gniazda internetowe. Ale nie potrzebowaliśmy kanałów od klienta do serwera. Musieliśmy jedynie wysłać aktualizacje z serwera do klienta, więc websocket jest opcją zbędną.
  2. Zdarzenia wysłane przez serwer (SSE) wypadły idealnie! Jest to dość proste i w zasadzie zaspokaja wszystko, czego potrzebujemy.

Zdarzenia wysłane przez serwer

Kilka słów o tym jak to działa...

Działa na podstawie połączenia http. Klient wysyła żądanie, serwer odpowiada Content-Type: tekst/event-stream i nie zamyka połączenia z klientem, ale kontynuuje zapisywanie danych do połączenia:

Michaił Salosin. Spotkanie Golanga. Korzystanie z Go w zapleczu aplikacji Look+

Dane mogą zostać przesłane w formacie uzgodnionym z Klientem. W naszym przypadku wysłaliśmy to w takiej formie: do pola zdarzenia przesłana została nazwa zmienionej struktury (osoba, gracz), a do pola danych JSON z nowymi, zmienionymi polami dla gracza.

Porozmawiajmy teraz o tym, jak działa sama interakcja.

  • Pierwszą rzeczą, którą robi klient, jest ustalenie czasu ostatniej synchronizacji z usługą: przegląda swoją lokalną bazę danych i ustala datę ostatniej zarejestrowanej przez nią zmiany.
  • Wysyła żądanie z tą datą.
  • W odpowiedzi przesyłamy mu wszystkie aktualizacje, które nastąpiły od tej daty.
  • Następnie nawiązuje połączenie z kanałem na żywo i nie zamyka się, dopóki nie będzie potrzebować tych aktualizacji:

Michaił Salosin. Spotkanie Golanga. Korzystanie z Go w zapleczu aplikacji Look+

Wysyłamy mu listę zmian: jeśli ktoś strzeli gola, zmieniamy wynik meczu, jeśli dozna kontuzji, jest to również przesyłane w czasie rzeczywistym. Dzięki temu klienci natychmiast otrzymują aktualne dane w kanale wydarzeń meczowych. Okresowo, aby klient zrozumiał, że serwer nie umarł, że nic mu się nie stało, co 15 sekund wysyłamy znacznik czasu - aby wiedział, że wszystko jest w porządku i nie ma potrzeby ponownego łączenia się.

W jaki sposób obsługiwane jest połączenie na żywo?

  • W pierwszej kolejności tworzymy kanał, do którego będą odbierane buforowane aktualizacje.
  • Następnie subskrybujemy ten kanał, aby otrzymywać aktualizacje.
  • Ustawiamy prawidłowy nagłówek, aby klient wiedział, że wszystko jest w porządku.
  • Wyślij pierwszy ping. Po prostu rejestrujemy aktualny znacznik czasu połączenia.
  • Następnie czytamy z kanału w pętli, aż do zamknięcia kanału aktualizacji. Kanał okresowo otrzymuje albo bieżący znacznik czasu, albo zmiany, które już zapisaliśmy w otwartych połączeniach.

Michaił Salosin. Spotkanie Golanga. Korzystanie z Go w zapleczu aplikacji Look+

Pierwszy problem jaki napotkaliśmy był następujący: dla każdego połączenia otwartego z klientem tworzyliśmy timer, który tykał co 15 sekund - okazuje się, że gdybyśmy mieli otwartych 6 tysięcy połączeń na jednej maszynie (z jednym serwerem API), 6 utworzono tysiąc timerów. Doprowadziło to do tego, że maszyna nie utrzymywała wymaganego obciążenia. Problem nie był dla nas aż tak oczywisty, ale otrzymaliśmy małą pomoc i go rozwiązaliśmy.

W rezultacie teraz nasz ping pochodzi z tego samego kanału, z którego pochodzi aktualizacja.

W związku z tym istnieje tylko jeden licznik czasu, który odmierza czas co 15 sekund.

Jest tu kilka funkcji pomocniczych - wysłanie nagłówka, ping i sama struktura. Oznacza to, że nazwa tabeli (osoba, mecz, sezon) i informacje o tym wpisie są przesyłane tutaj:

Michaił Salosin. Spotkanie Golanga. Korzystanie z Go w zapleczu aplikacji Look+

Mechanizm wysyłania aktualizacji

Teraz trochę o tym, skąd biorą się zmiany. Mamy kilka osób, redaktorów, którzy oglądają transmisję w czasie rzeczywistym. To oni tworzą wszystkie zdarzenia: ktoś został wyrzucony z boiska, ktoś został kontuzjowany, ktoś miał zastąpić...

Za pomocą CMS-a dane trafiają do bazy danych. Następnie baza danych powiadamia o tym serwery API za pomocą mechanizmu Listen/Notify. Serwery API już wysyłają te informacje do klientów. Zatem zasadniczo mamy tylko kilka serwerów podłączonych do bazy danych i nie ma specjalnego obciążenia bazy danych, ponieważ klient w żaden sposób nie wchodzi w bezpośrednią interakcję z bazą danych:

Michaił Salosin. Spotkanie Golanga. Korzystanie z Go w zapleczu aplikacji Look+

PostgreSQL: Słuchaj/Powiadom

Mechanizm Listen/Notify w Postgresie umożliwia powiadamianie subskrybentów zdarzeń o zmianie jakiegoś zdarzenia - w bazie danych powstał jakiś rekord. Aby to zrobić, napisaliśmy prosty wyzwalacz i funkcję:

Michaił Salosin. Spotkanie Golanga. Korzystanie z Go w zapleczu aplikacji Look+

Wstawiając lub zmieniając rekord, wywołujemy funkcję notify na kanale data_updates, przekazując tam nazwę tabeli oraz identyfikator rekordu, który został zmieniony lub wstawiony.

Dla wszystkich tabel, które muszą być zsynchronizowane z klientem definiujemy wyzwalacz, który po zmianie/aktualizacji rekordu wywołuje funkcję wskazaną na slajdzie poniżej.
W jaki sposób interfejs API subskrybuje te zmiany?

Tworzy się mechanizm Fanout, który wysyła wiadomości do klienta. Gromadzi wszystkie kanały klientów i wysyła aktualizacje otrzymane za pośrednictwem tych kanałów:

Michaił Salosin. Spotkanie Golanga. Korzystanie z Go w zapleczu aplikacji Look+

Tutaj standardowa biblioteka pq, która łączy się z bazą danych i mówi, że chce słuchać kanału (data_updates), sprawdza, czy połączenie jest otwarte i wszystko jest w porządku. Pomijam sprawdzanie błędów, aby zaoszczędzić miejsce (nie sprawdzanie jest niebezpieczne).

Następnie asynchronicznie ustawiamy Tickera, który będzie wysyłał ping co 15 sekund i zaczniemy słuchać kanału, który subskrybowaliśmy. Jeśli otrzymamy sygnał ping, publikujemy go. Jeśli otrzymamy jakiś wpis, publikujemy go wszystkim subskrybentom tego Fanoutu.

Jak działa Fan-Out?

W języku rosyjskim oznacza to „rozdzielacz”. Mamy jeden obiekt, który rejestruje subskrybentów, którzy chcą otrzymywać aktualizacje. Gdy tylko aktualizacja dotrze do tego obiektu, dystrybuuje ją do wszystkich swoich subskrybentów. Wystarczająco proste:

Michaił Salosin. Spotkanie Golanga. Korzystanie z Go w zapleczu aplikacji Look+

Jak jest to zaimplementowane w Go:

Michaił Salosin. Spotkanie Golanga. Korzystanie z Go w zapleczu aplikacji Look+

Istnieje struktura, która jest synchronizowana za pomocą Muteksów. Posiada pole zapisujące stan połączenia Fanouta z bazą danych, czyli aktualnie nasłuchuje i będzie otrzymywał aktualizacje, a także listę wszystkich dostępnych kanałów – mapę, której kluczem jest kanał oraz struktura w postaci wartości (w zasadzie nie jest on w żaden sposób używany).

Dwie metody - Connected i Disconnected - pozwalają nam powiedzieć Fanoutowi, że mamy połączenie z bazą, pojawiło się i że połączenie z bazą zostało zerwane. W drugim przypadku musisz rozłączyć wszystkich klientów i powiedzieć im, że nie mogą już niczego słuchać i że łączą się ponownie, ponieważ połączenie z nimi zostało zamknięte.

Istnieje również metoda Subskrybuj, która dodaje kanał do „słuchaczy”:

Michaił Salosin. Spotkanie Golanga. Korzystanie z Go w zapleczu aplikacji Look+

Istnieje metoda Unsubscribe, która usuwa kanał ze słuchaczy w przypadku rozłączenia się klienta, a także metoda Publish, która pozwala wysłać wiadomość do wszystkich subskrybentów.

Pytanie: – Co jest przesyłane tym kanałem?

SM: – Przesyłany jest model, który się zmienił lub ping (w zasadzie tylko liczba, liczba całkowita).

SM: – Można wysłać wszystko, wysłać dowolną strukturę, opublikować – po prostu zamienia się w JSON i tyle.

SM: – Otrzymujemy powiadomienie od Postgres – zawiera ono nazwę tabeli oraz identyfikator. Na podstawie nazwy tabeli i identyfikatora uzyskujemy potrzebny nam rekord, a następnie wysyłamy tę strukturę do publikacji.

Infrastruktura

Jak to wygląda z punktu widzenia infrastruktury? Mamy 7 serwerów sprzętowych: jeden z nich jest w całości dedykowany dla bazy danych, na pozostałych sześciu uruchamiane są maszyny wirtualne. Istnieje 6 kopii API: każda maszyna wirtualna z API działa na oddzielnym serwerze sprzętowym – ma to na celu zapewnienie niezawodności.

Michaił Salosin. Spotkanie Golanga. Korzystanie z Go w zapleczu aplikacji Look+

Mamy dwa frontendy z zainstalowanym Keepalive, aby poprawić dostępność, tak aby w przypadku, gdy coś się stanie, jeden frontend mógł zastąpić drugi. Oraz dwie kopie CMS-a.

Istnieje również importer statystyk. Istnieje DB Slave, z którego okresowo tworzone są kopie zapasowe. Istnieje Pigeon Pusher, aplikacja wysyłająca powiadomienia push do klientów, a także elementy infrastruktury: Zabbix, Graylog2 i Chef.

Tak naprawdę ta infrastruktura jest redundantna, bo 100 tys. można obsłużyć przy mniejszej liczbie serwerów. Ale było żelazo - używaliśmy (powiedziano nam, że można - czemu nie).

Plusy Go

Po pracy nad tą aplikacją ujawniły się oczywiste zalety Go.

  • Fajna biblioteka http. Dzięki niemu możesz stworzyć całkiem sporo od razu po wyjęciu z pudełka.
  • Do tego kanały, które pozwoliły nam w bardzo prosty sposób zaimplementować mechanizm wysyłania powiadomień do klientów.
  • Wspaniała rzecz Detektor wyścigów pozwolił nam wyeliminować kilka krytycznych błędów (infrastruktura postojowa). Uruchamiane jest wszystko, co działa na etapie inscenizacji, skompilowane za pomocą klucza Race; w związku z tym możemy przyjrzeć się infrastrukturze tymczasowej, aby zobaczyć, jakie mamy potencjalne problemy.
  • Minimalizm i prostota języka.

Michaił Salosin. Spotkanie Golanga. Korzystanie z Go w zapleczu aplikacji Look+

Poszukujemy programistów! Jeśli ktoś chce to proszę.

pytania

Pytanie od publiczności (dalej – B): – Wydaje mi się, że pominąłeś jeden ważny punkt dotyczący Fan-outu. Czy dobrze rozumiem, że wysyłając odpowiedź do klienta, blokujesz ją, jeśli klient nie chce czytać?

SM: - Nie, nie blokujemy. Po pierwsze, mamy to wszystko za nginxem, czyli nie ma problemów z wolnymi klientami. Po drugie, klient posiada kanał z buforem - tak naprawdę możemy tam umieścić aż sto aktualizacji... Jeśli nie uda nam się zapisać na kanał, to go usunie. Jeśli zobaczymy, że kanał jest zablokowany, to po prostu go zamkniemy i tyle – klient ponownie połączy się, jeśli pojawi się jakiś problem. Dlatego w zasadzie nie ma tu mowy o blokowaniu.

W: – Czy nie dałoby się od razu wysłać rekordu do Listen/Notify, a nie tabeli identyfikatorów?

SM: – Funkcja Listen/Notify ma limit 8 tysięcy bajtów wysyłanego wstępnego ładowania. W zasadzie dałoby się wysłać, gdybyśmy mieli do czynienia z małą ilością danych, ale wydaje mi się, że w ten sposób [tak jak to robimy] jest po prostu bardziej niezawodny. Ograniczenia znajdują się w samym Postgresie.

W: – Czy klienci otrzymują aktualizacje dotyczące meczów, którymi nie są zainteresowani?

SM: - Generalnie tak. Z reguły równolegle toczą się 2-3 mecze, a nawet wtedy dość rzadko. Jeśli klient coś ogląda, to zazwyczaj ogląda rozgrywany mecz. Klient ma wówczas lokalną bazę danych, do której dodawane są wszystkie te aktualizacje i nawet bez połączenia z Internetem może przeglądać wszystkie przeszłe mecze, dla których posiada aktualizacje. Zasadniczo synchronizujemy naszą bazę danych na serwerze z lokalną bazą danych klienta, aby mógł on pracować offline.

W: – Dlaczego stworzyłeś własny ORM?

Alexey (jeden z twórców Look+): – Wtedy (było to rok temu) ORM-ów było mniej niż obecnie, kiedy jest ich całkiem sporo. Moją ulubioną cechą większości ORM-ów jest to, że większość z nich działa na pustych interfejsach. Oznacza to, że metody w tych ORM-ach są gotowe przyjąć wszystko: strukturę, wskaźnik struktury, liczbę, coś zupełnie nieistotnego...

Nasz ORM generuje struktury w oparciu o model danych. Ja. Dlatego wszystkie metody są konkretne, nie wykorzystują refleksji itp. Akceptują struktury i oczekują, że wykorzystają te struktury, które się pojawią.

W: – Ile osób wzięło udział?

SM: – Na początkowym etapie wzięły w nim udział dwie osoby. Zaczęliśmy gdzieś w czerwcu, a w sierpniu główna część była już gotowa (pierwsza wersja). We wrześniu była premiera.

W: – Tam, gdzie opisujesz SSE, nie używasz limitu czasu. Dlaczego?

SM: – Szczerze mówiąc, SSE jest nadal protokołem HTML5: standard SSE jest przeznaczony do komunikacji z przeglądarkami, o ile rozumiem. Ma dodatkowe funkcje, dzięki którym przeglądarki mogą się ponownie łączyć (i tak dalej), ale nie potrzebujemy ich, ponieważ mieliśmy klientów, którzy mogli wdrożyć dowolną logikę łączenia i odbierania informacji. Nie stworzyliśmy SSE, ale raczej coś podobnego do SSE. To nie jest sam protokół.
Nie było takiej potrzeby. O ile rozumiem, klienci wdrożyli mechanizm połączenia niemal od zera. Tak naprawdę nie obchodziło ich to.

W: – Z jakich dodatkowych narzędzi korzystałeś?

SM: – Najaktywniej używaliśmy govet i golint, aby ujednolicić styl, a także gofmt. Nic innego nie było używane.

W: – Czego użyłeś do debugowania?

SM: – Debugowanie odbywało się głównie za pomocą testów. Nie korzystaliśmy z żadnego debuggera ani GOP.

W: – Czy możesz zwrócić slajd, w którym zaimplementowano funkcję Publikuj? Czy jednoliterowe nazwy zmiennych są dla Ciebie mylące?

SM: - NIE. Mają dość „wąski” zakres widoczności. Nie są one używane nigdzie indziej poza tutaj (z wyjątkiem elementów wewnętrznych tej klasy) i są bardzo kompaktowe - zajmują tylko 7 linii.

W: – Jakoś to nadal nie jest intuicyjne…

SM: - Nie, nie, to jest prawdziwy kod! Tu nie chodzi o styl. To po prostu taka użyteczna, bardzo mała klasa - tylko 3 pola w klasie...

Michaił Salosin. Spotkanie Golanga. Korzystanie z Go w zapleczu aplikacji Look+

SM: – Ogólnie rzecz biorąc, wszystkie dane synchronizowane z klientami (mecze sezonowe, gracze) nie ulegają zmianie. Z grubsza rzecz biorąc, jeśli stworzymy kolejny sport, w którym będziemy musieli zmienić mecz, po prostu weźmiemy wszystko pod uwagę w nowej wersji klienta, a stare wersje klienta zostaną zbanowane.

W: – Czy istnieją pakiety do zarządzania zależnościami innych firm?

SM: – Użyliśmy go Dep.

W: – W temacie raportu było coś o wideo, ale w raporcie nie było nic o wideo.

SM: – Nie, nie mam nic w temacie dotyczącym filmu. Nazywa się „Look+” – tak nazywa się aplikacja.

W: – Mówiłeś, że jest przesyłany strumieniowo do klientów?..

SM: – Nie zajmowaliśmy się streamingiem wideo. Zostało to w całości wykonane przez Megafon. Tak, nie powiedziałem, że aplikacją jest MegaFon.

SM: – Go – do przesyłania wszystkich danych – o wynikach, wydarzeniach meczowych, statystykach… Go to cały backend aplikacji. Klient musi skądś wiedzieć, jakiego linku użyć dla gracza, aby użytkownik mógł obejrzeć mecz. Mamy linki do przygotowanych filmów i streamów.

Kilka reklam 🙂

Dziękujemy za pobyt z nami. Podobają Ci się nasze artykuły? Chcesz zobaczyć więcej ciekawych treści? Wesprzyj nas składając zamówienie lub polecając znajomym, VPS w chmurze dla programistów od 4.99 USD, unikalny odpowiednik serwerów klasy podstawowej, który został przez nas wymyślony dla Ciebie: Cała prawda o VPS (KVM) E5-2697 v3 (6 rdzeni) 10GB DDR4 480GB SSD 1Gbps od 19$ czyli jak udostępnić serwer? (dostępne z RAID1 i RAID10, do 24 rdzeni i do 40 GB DDR4).

Dell R730xd 2 razy taniej w centrum danych Equinix Tier IV w Amsterdamie? Tylko tutaj 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6 GHz 14C 64 GB DDR4 4x960 GB SSD 1 Gb/s 100 Telewizor od 199 USD w Holandii! Dell R420 — 2x E5-2430 2.2 GHz 6C 128 GB DDR3 2x960 GB SSD 1 Gb/s 100 TB — od 99 USD! Czytać o Jak zbudować firmę infrastrukturalną klasy z wykorzystaniem serwerów Dell R730xd E5-2650 v4 o wartości 9000 euro za grosz?

Źródło: www.habr.com

Dodaj komentarz