Michał Salosin. Golang Meetup. Zastosowanie Go w backendzie aplikacji „Smotri+”

Michał Salosin (dalej – MS): – Cześć wszystkim! Nazywam się Michał. Pracuję jako backend developer w firmie MC2 Software i opowiem o zastosowaniu Go w backendzie mobilnej aplikacji „Smotri+”.

Michał Salosin. Golang Meetup. Zastosowanie Go w backendzie aplikacji „Smotri+”

Czy ktoś z obecnych lubi hokej?

Michał Salosin. Golang Meetup. Zastosowanie Go w backendzie aplikacji „Smotri+”

W takim razie ta aplikacja jest dla Was. Jest dostępna na Androida i iOS, służy do oglądania transmisji różnych wydarzeń sportowych na żywo oraz z archiwum. W aplikacji znajdziecie również różne statystyki, tekstowe transmisje, tabele konferencyjne, turniejowe i inne przydatne informacje dla kibiców.

Michał Salosin. Golang Meetup. Zastosowanie Go w backendzie aplikacji „Smotri+”

W aplikacji jest także funkcja video momentów, czyli można obejrzeć kluczowe momenty meczów (gole, bójki, rzuty karne itd.). Jeśli nie chcecie oglądać całej transmisji, możecie zobaczyć tylko to, co najciekawsze.

Czego użyto w procesie rozwoju?

Główna część została napisana w Go. API, z którym komunikowały się mobilne klienci, zostało stworzone na Go. Również serwis do wysyłania powiadomień push na urządzenia mobilne został napisany w Go. Musieliśmy również stworzyć swoje ORM, o którym może kiedyś opowiemy. Kilka mniejszych serwisów, takich jak resize i ładowanie obrazów dla redaktorów, zostało również napisanych w Go.

Jako bazę danych użyliśmy PostgreSQL. Interfejs dla redaktorów został stworzony za pomocą Ruby on Rails z użyciem gema ActiveAdmin. Import statystyk od dostawcy statystyk został również zrealizowany w Ruby.

Do testów systemowych API użyliśmy unittestów w Pythonie. Memcached wykorzystujemy do throttlingu żądań API płatności, Chef – do kontroli konfiguracji, Zabbix – do zbierania i monitorowania wewnętrznych danych statystycznych systemu. Graylog2 służy do zbierania logów, a Slate to dokumentacja API dla klientów.

Michał Salosin. Golang Meetup. Zastosowanie Go w backendzie aplikacji „Smotri+”

Wybór protokołu

Pierwszy problem, z którym się zmierzyliśmy: musieliśmy wybrać protokół interakcji backendu z mobilnymi klientami, biorąc pod uwagę następujące punkty…

  • Najważniejsze wymaganie: dane na urządzeniach klienckich muszą być aktualizowane w czasie rzeczywistym. To znaczy, że wszyscy, którzy obecnie oglądają transmisję, powinni otrzymywać aktualizacje praktycznie natychmiast.
  • Dla uproszczenia przyjęliśmy, że dane, które synchronizujemy z klientami, nie są usuwane, a ukrywane za pomocą specjalnych flag.
  • Różne rzadkie zapytania (takie jak statystyki, składy drużyn, statystyki drużyn) są realizowane jako zwykłe zapytania GET.
  • Dodatkowo system musiał być w stanie znieść 100 tysięcy użytkowników jednocześnie.

Wynikowo mieliśmy dwa warianty protokołu:

  1. Websockety. Ale nie potrzebowaliśmy kanałów od klienta do serwera. Potrzebowaliśmy tylko wysyłać aktualizacje z serwera do klienta, więc websocket byłby zbędnym rozwiązaniem.
  2. Server-Sent Events (SSE) idealnie się nadawały! Były wystarczająco proste i spełniały ogół naszych potrzeb.

Server-Sent Events

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

Działa to na bazie połączenia http. Klient wysyła zapytanie, serwer odpowiada nagłówkiem Content-Type: text/event-stream i nie zamyka połączenia z klientem, a kontynuuje przesyłanie danych do połączenia:

Michał Salosin. Golang Meetup. Zastosowanie Go w backendzie aplikacji „Smotri+”

Dane można wysyłać w formacie uzgodnionym z klientami. W naszym przypadku przesyłaliśmy w ten sposób: w polu event przekazywana była nazwa zmienionej struktury (osoba, gracz), a w polu data – JSON z nowymi, zmienionymi polami dla gracza.

Teraz o tym, jak działa samo interakcja.

  • Na początku klient ustala, kiedy ostatni raz synchronizował się z serwisem: sprawdza swoją lokalną bazę danych i ustala datę ostatniej zmiany, którą zapisał.
  • Wysyła zapytanie z tą datą.
  • W odpowiedzi wysyłamy mu wszystkie aktualizacje, które miały miejsce od tej daty.
  • Następnie nawiązuje połączenie z kanałem na żywo i nie zamyka go, dopóki potrzebuje tych aktualizacji:

Michał Salosin. Golang Meetup. Zastosowanie Go w backendzie aplikacji „Smotri+”

Wysyłamy mu listę zmian: jeśli ktoś strzelił gola – zmieniamy wynik meczu, jeśli ktoś doznał kontuzji – również przesyłamy to w czasie rzeczywistym. Tak więc klienci natychmiast otrzymują aktualne dane w strumieniu wydarzeń meczu. Okresowo, aby klient wiedział, że serwer nie padł, że nic się nie dzieje, co 15 sekund wysyłamy znacznik czasu – aby wiedział, że wszystko w porządku i nie musi się ponownie łączyć.

Jak jest obsługiwane połączenie na żywo?

  • Przede wszystkim tworzymy kanał, do którego będą wysyłane aktualizacje z buforem.
  • Następnie zapisujemy się do tego kanału na otrzymywanie aktualizacji.
  • Ustawiamy odpowiedni nagłówek, aby klient wiedział, że wszystko jest w porządku.
  • Wysyłamy pierwszego pingu. Po prostu zapisujemy aktualny znacznik czasu połączenia.
  • Następnie w pętli odczytujemy z kanału, aż kanał aktualizacji zostanie zamknięty. Do kanału okresowo przychodzi albo aktualny znacznik czasu, albo zmiany, które już zapisujemy w otwartych połączeniach.

Michał Salosin. Golang Meetup. Zastosowanie Go w backendzie aplikacji „Smotri+”

Pierwszy problem, z którym się zetknęliśmy, był następujący: dla każdego otwartego połączenia z klientem tworzyliśmy timer, który tykał co 15 sekund – to oznacza, że jeśli mieliśmy otwartych 6 tysięcy połączeń z jedną maszyną (z jednym serwerem API), tworzyło się 6 tysięcy timerów. Prowadziło to do tego, że maszyna nie utrzymywała wymaganej wydajności. Problem nie był dla nas zbyt oczywisty, ale trochę nam pomogli i go rozwiązaliśmy.

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

W związku z tym mamy tylko jeden timer, który tyka co 15 sekund.

Tutaj znajduje się kilka pomocniczych funkcji – wysyłanie nagłówka, pingu i samej struktury. To znaczy, tutaj przekazywana jest nazwa tabeli (osoba, mecz, sezon) oraz sama informacja o tym wpisie:

Michał Salosin. Golang Meetup. Zastosowanie Go w backendzie aplikacji „Smotri+”

Mechanizm wysyłania aktualizacji

Teraz trochę o tym, skąd pochodzą zmiany. Mamy kilku ludzi, redaktorów, którzy na bieżąco oglądają transmisję. Tworzą oni wszystkie zdarzenia: kogoś usunięto, ktoś doznał kontuzji, jakaś zmiana…

Dzięki CMS dane trafiają do bazy. Następnie baza z pomocą mechanizmu Listen/Notify powiadamia o tym serwery API. Serwery API rozsyłają tę informację do klientów. W ten sposób zasadniczo do bazy podłączonych jest tylko kilka serwerów i nie ma szczególnego obciążenia na bazie, ponieważ klient w żaden sposób nie współdziała bezpośrednio z bazą:

Michał Salosin. Golang Meetup. Zastosowanie Go w backendzie aplikacji „Smotri+”

PostgreSQL: Listen/Notify

Mechanizm Listen/Notify w PostgreSQL pozwala powiadamiać subskrybentów o zdarzeniach, że zmieniło się jakieś zdarzenie – że w bazie została stworzona jakaś rekord. W tym celu napisaliśmy prosty wyzwalacz i funkcję:

Michał Salosin. Golang Meetup. Zastosowanie Go w backendzie aplikacji „Smotri+”

Podczas wstawiania lub zmiany rekordu wywołujemy funkcję notify na kanale data_updates, przekazując nazwę tabeli oraz identyfikator rekordu, który został zmieniony lub wstawiony.

Na wszystkich tabelach, które mają być synchronizowane z klientem, definiujemy wyzwalacz, który po zmianie/aktualizacji rekordu wywołuje funkcję wskazaną na slajdzie poniżej.
Jak API subskrybuje te zmiany?

Tworzy się mechanizm Fanout – rozsyła on wiadomości do klientów. Zbiera wszystkie kanały klientów i przesyła aktualizacje, które otrzymał przez te kanały:

Michał Salosin. Golang Meetup. Zastosowanie Go w backendzie aplikacji „Smotri+”

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

Następnie asynchronicznie ustawiamy Ticker, który będzie wysyłał ping co 15 sekund i zaczynamy słuchać kanału, do którego się zapisaliśmy. Jeśli otrzymamy ping, publikujemy ten ping. Jeśli otrzymamy jakąś wiadomość, publikujemy tę wiadomość wszystkim subskrybentom tego Fanout’a.

Jak działa Fan-out?

W języku polskim tłumaczymy to jako „rozdzielacz”. Mamy jeden obiekt, który rejestruje subskrybentów chcących otrzymywać aktualizacje. I gdy tylko przychodzi aktualizacja do tego obiektu, rozsyła ją wszystkim subskrybentom, których ma. To wystarczająco proste:

Michał Salosin. Golang Meetup. Zastosowanie Go w backendzie aplikacji „Smotri+”

Jak to zaimplementowano w Go:

Michał Salosin. Golang Meetup. Zastosowanie Go w backendzie aplikacji „Smotri+”

Jest struktura, synchronizowana za pomocą Mutexów. Ma pole, które przechowuje stan połączenia Fanout z bazą, tzn. w danym momencie słucha i będzie otrzymywał aktualizacje, oraz lista wszystkich dostępnych kanałów – mapa, której kluczem jest kanał i struktura w postaci wartości (w zasadzie nie jest używana).

Dwie metody – Connected i Disconnected – pozwalają powiedzieć Fanout’owi, że mamy połączenie z bazą, że się nawiązało i że połączenie z bazą zostało zerwane. W drugim przypadku należy odłączyć wszystkich klientów i poinformować ich, że nie mogą już nic słuchać i że powinni się ponownie połączyć, ponieważ połączenie z nimi zostało zamknięte.

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

Michał Salosin. Golang Meetup. Zastosowanie Go w backendzie aplikacji „Smotri+”

Jest metoda Unsubscribe, która usuwa kanał z aktualnych słuchaczy, jeśli klient się odłączył, oraz metoda Publish, która pozwala rozesłać wiadomość do wszystkich subskrybentów.

Pytanie: – Co jest przesyłane w tym kanale?

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

MS: – Można przesyłać cokolwiek, dowolną strukturę – zostaje po prostu zamieniona na JSON i to wszystko.

MS: – Otrzymujemy powiadomienie z „Postgresa” – zawiera ono nazwę tabeli oraz identyfikator. Na podstawie nazwy tabeli oraz identyfikatora uzyskujemy potrzebny nam rekord, a tę strukturę wysyłamy do publikacji.

Infrastruktura

Jak to wygląda z perspektywy infrastruktury? Mamy 7 serwerów fizycznych: jeden z nich jest całkowicie dedykowany bazie, a na pozostałych sześciu działają wirtualki. Posiadamy 6 kopii API: każda wirtualka z API działa na oddzielnym serwerze fizycznym – to dla pewności.

Michał Salosin. Golang Meetup. Zastosowanie Go w backendzie aplikacji „Smotri+”

Mamy dwa frontend'y, na których zainstalowany jest Keepalived w celu zwiększenia dostępności, aby w razie czego jeden frontend mógł zastąpić drugi. Ponadto – dwie kopie CMS.

Dostępny jest również importer statystyk. Jest DB Slave, z którego okresowo wykonywane są kopie zapasowe. Jest Pigeon Pusher – aplikacja, która wysyła powiadomienia do klientów, oraz elementy infrastruktury: Zabbix, Graylog2 i Chef.

Tak naprawdę ta infrastruktura jest nadmiarowa, ponieważ 100 tysięcy można obsługiwać i przy mniejszej liczbie serwerów. Ale sprzęt był – wykorzystaliśmy go (powiedziano nam, że możemy – czemu nie?).

Zalety Go

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

  • Świetna biblioteka http. Dzięki niej można stworzyć naprawdę wiele już „z pudełka”.
  • Dodatkowo, kanały, które pozwoliły nam bardzo łatwo zaimplementować mechanizm wysyłania powiadomień do klientów.
  • Cudowna rzecz – detektor wyścigów, który pozwolił nam usunąć kilka krytycznych błędów (infrastruktura stagingowa). Wszystko, co działa na stagingu – jest uruchomione, skompilowane z kluczem Race; i możemy odpowiednio sprawdzić na infrastrukturze stagingowej, jakie mamy potencjalne problemy.
  • Minimalizm i prostota języka.

Michał Salosin. Golang Meetup. Zastosowanie Go w backendzie aplikacji „Smotri+”

Szukamy programistów! Jeśli ktoś jest zainteresowany – proszę bardzo.

Pytania

Pytanie z publiczności (dalej – P): – Wydaje mi się, że pominęliście jeden ważny aspekt dotyczący Fan-out. Czy dobrze rozumiem, że kiedy wysyłacie klientowi odpowiedź, blokujecie się, jeśli klient nie chce jej przeczytać?

MS: – Nie, nie blokujemy się. Po pierwsze, wszystko jest za nginx'em, więc nie ma problemów z wolnymi klientami. Po drugie, klient ma kanał z buforem – w zasadzie możemy umieścić tam do stu aktualizacji… Jeśli nie możemy zapisać w kanale, to go usuwamy. Jeśli widzimy, że kanał został zablokowany, po prostu zamykamy kanał i wszystko – klient się ponownie podłączy, jeśli pojawi się jakiś problem. Dlatego tu w zasadzie blokady nie występują.

P: – Czy nie można było od razu wysyłać do Listen/Notify zapis, a nie identyfikator tabeli?

MS: – Listen/Notify ma ograniczenie do 8 tysięcy bajtów na preload, który wysyła. W zasadzie można by to wysyłać, gdybyśmy mieli do czynienia z małą ilością danych, ale moim zdaniem to, co robimy, jest po prostu bardziej niezawodne. Ograniczenia są w samym „Postgresie”.

P: – Czy klienci otrzymują aktualizacje dotyczące meczów, którymi się nie interesują?

MS: – Generalnie tak. Najczęściej równocześnie odbywa się 2-3 mecze, i to raczej rzadko. Jeśli klient ogląda coś, to zazwyczaj jest to ten mecz, który aktualnie trwa. Potem, na kliencie jest lokalna baza, do której te wszystkie aktualizacje są zrzucane, i nawet bez połączenia z internetem klient może zobaczyć wszystkie minione mecze, na które ma aktualizacje. W zasadzie synchronizujemy naszą bazę na serwerze z lokalną bazą klienta, aby mógł działać również offline.

P: – Dlaczego stworzyliście własną ORM?

Oleg (jeden z deweloperów „Smotri+”): – W tamtym czasie (to było rok temu) ORM było mniej niż obecnie, kiedy jest ich całkiem sporo. Z większości istniejących ORM najbardziej nie podoba mi się to, że większość z nich działa na pustych interfejsach. To znaczy, że metody, które są w tych ORM, są gotowe przyjąć wszystko: strukturę, wskaźnik struktury, liczbę, coś zupełnie nie związanego z tematem…

Nasza ORM generuje struktury na podstawie modelu danych. Sama. I dlatego wszystkie metody są konkretne, nie używają refleksji itp. Przyjmują struktury i oczekują użycia tych struktur, które przyjdą.

P: – Ile osób w tym uczestniczyło?

MS: – Na początkowym etapie brały udział dwie osoby. Gdzieś w czerwcu zaczęliśmy, w sierpniu była gotowa główna część (pierwsza wersja). We wrześniu miała miejsce premiera.

P: – Tam, gdzie opisujesz SSE, nie używasz timeoutu. Dlaczego tak?

MS: – Jeśli mówimy otwarcie, to SSE to jednak protokół html5: standard SSE jest przeznaczony do komunikacji z przeglądarkami, o ile rozumiem. Posiada dodatkowe funkcje, które pozwalają przeglądarkom na ponowne połączenie (i inne), ale nie są nam potrzebne, ponieważ mieliśmy klientów, którzy mogli zrealizować dowolną logikę połączenia i uzyskiwania informacji. Zrobiliśmy raczej coś, co przypomina SSE, niż sama SSE. To nie jest sam protokół.
Nie było takiej potrzeby. O ile rozumiem, klienci realizowali mechanizm połączenia praktycznie od zera. W zasadzie to nie miało dla nich znaczenia.

P: – Jakie dodatkowe narzędzia wykorzystywaliście?

MS: – Najaktywniej używaliśmy govet i golint, aby utrzymać jednolity styl, a także gofmt. Nic więcej nie używaliśmy.

P: – Czym się posługiwaliście do debugowania?

MS: – Debugowanie odbywało się głównie za pomocą testów. Żadnego debuggera, GOP nie używaliśmy.

P: – Czy możecie pokazać slajd, na którym zaimplementowano funkcję Publish? Dwuznakowe nazwy zmiennych nie dziwią was?

MS: – Nie. Mają wystarczająco "wąski" zasięg. W nigdzie, poza tym miejscem, nie są używane (poza wnętrzem tej klasy), a klasa jest bardzo kompaktowa – zajmuje tylko 7 linii.

P: – Jakkolwiek to nie jest intuicyjne…

MS: – Nie, nie, to prawdziwy kod! To nie jest kwestia stylu. Po prostu to taki utylitarny, całkowicie mały klas – tylko 3 pola wewnątrz klasy…

Michał Salosin. Golang Meetup. Zastosowanie Go w backendzie aplikacji „Smotri+”

MS: – Zasadniczo, wszystkie dane, które są synchronizowane z klientami (sezonowe mecze, zawodnicy), nie zmieniają się. Mówiąc w prost, jeśli będziemy tworzyć jeszcze jakiś sport, w którym trzeba będzie zmienić mecz, uwzględnimy to w nowej wersji klienta, a stare wersje klienta będą zablokowane.

P: – Czy są jakieś zewnętrzne pakiety do zarządzania zależnościami?

MS: – Używaliśmy go dep.

P: – W temacie prezentacji było coś o wideo, a w prezentacji nie ma o wideo.

MS: – Nie, w temacie o wideo nie mam nic. Nazywa się „Szukaj+” – tak się nazywa aplikacja.

P: – Mówiliście, że strumień jest przekazywany do klientów?..

MS: – Nie zajmowaliśmy się strumieniowaniem wideo. To w pełni robił „MegaFon”. Tak, nie powiedziałem, że aplikacja jest megafonowska.

MS: – Go – do wysyłania wszystkich danych – o koncie, o wydarzeniach meczu, statystyce… Go – to w pełni backend aplikacji. Klient musi skądś wiedzieć, jaki link użyć do odtwarzacza, aby użytkownik mógł obejrzeć mecz. Mamy linki do wideo i strumieni, które są przygotowane.

Odtwarzaj wideo

Trochę reklamy 🙂

Dziękujemy, że jesteś z nami. Podobają Ci się nasze artykuły? Chcesz zobaczyć więcej interesujących materiałów? Wspieraj nas składając zamówienie lub polecając nas znajomym, chmurowe VPS dla programistów od 4,99 $, unikatowy odpowiednik serwerów entry-level, który został stworzony przez nas dla Ciebie: Cała prawda o VPS (KVM) E5-2697 v3 (6 rdzeni) 10GB DDR4 480GB SSD 1Gbps od 19 $ lub jak prawidłowo podzielić serwer? (dostępne opcje z RAID1 i RAID10, do 24 rdzeni i do 40GB DDR4).

Dell R730xd dwa razy tańszy w centrum danych Equinix Tier IV w Amsterdamie? Tylko u nas 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100TB od 199 dolarów w Holandii! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — od 99 dolarów! Czytaj o tym Jak zbudować infrastrukturę klasy korporacyjnej z zastosowaniem serwerów Dell R730xd E5-2650 v4 kosztujących 9000 euro za grosze?

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster