Mikhail Salosin (hĂ€refter â MS): â Hej alla! Jag heter Mikhail. Jag arbetar som backend-utvecklare pĂ„ företaget MC2 Software, och jag kommer att berĂ€tta om anvĂ€ndningen av Go i backend för mobilapplikationen "Smotri+".

Ăr det nĂ„gon hĂ€r som Ă€lskar hockey?

DÄ Àr den hÀr appen för dig. Den finns för Android och iOS och anvÀnds för att titta pÄ sÀndningar av olika sportevenemang online och i efterhand. I appen finns Àven olika statistik, textbaserade sÀndningar, tabeller för konferenser, turneringar och annan information som Àr anvÀndbar för fans.

Appen har ocksÄ en funktion som kallas videomoments, vilket innebÀr att man kan se höjdpunkter frÄn matcherna (mÄl, slagsmÄl, straffar osv.). Om du inte vill se hela sÀndningen kan du bara titta pÄ det mest intressanta.
Vad anvÀnde vi i utvecklingen?
Största delen var skriven i Go. Den API som mobilklienterna kommunicerade med var skriven i Go. Ăven tjĂ€nsten för att skicka push-notifikationer till mobiler var skriven i Go. Dessutom var vi tvungna att skriva vĂ„r egen ORM, som vi kanske berĂ€ttar mer om nĂ„gon gĂ„ng. Och vissa mindre tjĂ€nster har skrivits i Go: resizing och uppladdning av bilder för redaktörernas sidaâŠ
För databasen anvÀnde vi PostgreSQL. GrÀnssnittet för redaktörerna var skrivet i Ruby on Rails med hjÀlp av gemet (gem) ActiveAdmin. Statistikimporten frÄn statistikleverantören var ocksÄ skriven i Ruby.
För systemtester av API:n anvÀnde vi Python's unittest. Memcached anvÀnds för att throttla API-betalningar, Chef för konfigurationshantering, Zabbix för insamling och övervakning av interna statistiska data i systemet. Graylog2 anvÀnds för att samla loggar, och Slate Àr API-dokumentationen för kunder.

Val av protokoll
Det första problemet vi stötte pĂ„: vi behövde vĂ€lja ett protokoll för interaktionen mellan backend och mobilklienterna, baserat pĂ„ följande punkterâŠ
- Det viktigaste kravet: data pÄ klienterna mÄste uppdateras i realtid. Det vill sÀga alla som just nu tittar pÄ sÀndningen ska fÄ uppdateringar nÀstan omedelbart.
- För enkelhetens skull antog vi att de data som synkroniseras med klienterna inte tas bort utan döljs med hjÀlp av speciella flaggor.
- SÀllsynta förfrÄgningar (som statistik, laguppstÀllningar, lagstatistik) görs genom vanliga GET-förfrÄgningar.
- Dessutom skulle systemet kunna hantera 100 000 anvÀndare samtidigt utan problem.
Baserat pÄ detta hade vi tvÄ protokollalternativ:
- Websockets. Men vi behövde inga kanaler frÄn klienten till servern. Vi behövde bara skicka uppdateringar frÄn servern till klienten, sÄ websockets var överflödiga.
- Server-Sent Events (SSE) passade perfekt! Det Àr tillrÀckligt enkelt och uppfyller i princip allt vi behöver.
Server-Sent Events
NÄgra ord om hur denna sak fungerar...
Den fungerar över http-anslutningen. Klienten skickar en förfrÄgan, servern svarar med Content-Type: text/event-stream och stÀnger inte anslutningen till klienten, utan fortsÀtter att skriva data i anslutningen:

Data kan skickas i det format som överenskommits med klienterna. I vĂ„rt fall skickade vi i följande form: i fĂ€ltet event angavs namnet pĂ„ den Ă€ndrade strukturen (person, spelare), och i data-fĂ€ltet â JSON med de nya, Ă€ndrade fĂ€lten för spelaren.
Nu om hur sjÀlva interaktionen fungerar.
- Först och frÀmst faststÀller klienten nÀr den senast synkroniserade med tjÀnsten: den tittar i sin lokala databas och bestÀmmer datumet för den senaste Àndringen som registrerades hos den.
- Den skickar en förfrÄgan med detta datum.
- Som svar skickar vi all uppdatering som har skett efter detta datum.
- DÀrefter upprÀttar den en anslutning till live-kanalen och stÀnger den inte förrÀn den inte lÀngre behöver dessa uppdateringar:

Vi skickar en lista över förĂ€ndringar: om nĂ„gon gör mĂ„l förĂ€ndrar vi matchresultatet, om nĂ„gon skadar sig â det skickas ocksĂ„ i realtid. PĂ„ sĂ„ sĂ€tt fĂ„r klienterna omedelbart aktuella data i matchens hĂ€ndelseflöde. Periodiskt, för att klienten ska förstĂ„ att servern inte har kraschat och att ingenting har hĂ€nt med den, skickar vi varje 15:e sekund en tidsstĂ€mpel â sĂ„ att den vet att allt Ă€r okej och att det inte behövs nĂ„gon Ă„teranslutning.
Hur underhÄlls live-anslutningen?
- Först skapar vi en kanal som kommer att ta emot uppdateringar med en buffert.
- Efter det registrerar vi denna kanal för att ta emot uppdateringar.
- Vi sÀtter rÀtt rubrik sÄ att klienten vet att allt Àr ok.
- Vi skickar den första ping. Vi skriver helt enkelt ned den aktuella tidsstÀmpeln för anslutningen.
- Efter detta lÀser vi i en loop frÄn kanalen sÄ lÀnge uppdateringskanalen inte Àr stÀngd. Det kommer periodiskt antingen en aktuell tidsstÀmpel eller förÀndringar som vi redan skriver till öppna anslutningar.

Det första problemet vi stötte pĂ„ var följande: för varje öppen anslutning med en klient skapade vi en timer som tickade var 15:e sekund â vilket innebĂ€r att om vi hade 6000 anslutningar till en maskin (med en API-server) skapades 6000 timers. Detta ledde till att maskinen inte höll den nödvĂ€ndiga belastningen. Problemet var inte sĂ„ uppenbart för oss, men vi fick lite hjĂ€lp och löste det.
I slutÀndan kommer nu pinget frÄn samma kanal som uppdateringen kommer ifrÄn.
DĂ€rmed finns det bara en timer som tickar var 15:e sekund.
HĂ€r finns nĂ„gra hjĂ€lpfunktioner â att skicka header, ping och sjĂ€lva strukturen. Det vill sĂ€ga, hĂ€r skickas namnet pĂ„ tabellen (person, match, sĂ€song) och sjĂ€lva informationen om denna post:

Mekanismen för att skicka uppdateringar
Nu lite om var förĂ€ndringarna kommer ifrĂ„n. Vi har flera personer, redaktörer, som i realtid ser pĂ„ sĂ€ndningen. De skapar alla hĂ€ndelser: nĂ„gon har blivit utvisad, nĂ„gon har skadat sig, nĂ„got byteâŠ
Genom CMS gÄr datan in i databasen. Efter det meddelar databasen via mekanismen Listen/Notify API-servrarna om detta. API-servrarna skickar sedan denna information till klienterna. PÄ sÄ sÀtt Àr det i praktiken bara nÄgra servrar som Àr anslutna till databasen och det finns ingen sÀrskild belastning pÄ databasen eftersom klienten pÄ inget sÀtt interagerar direkt med databasen:

PostgreSQL: Listen/Notify
Mekanismen Listen/Notify i "Postgres" gör det möjligt att informera prenumeranter om hĂ€ndelser om att nĂ„gon hĂ€ndelse har förĂ€ndrats â det har skapats en post i databasen. För detta skrev vi en enkel trigger och funktion:

Vid insert eller Àndring av en post anropar vi funktionen notify pÄ kanalen data_updates, dit vi skickar namnet pÄ tabellen och identifieraren för den post som har Àndrats eller lagts till.
För alla tabeller som ska synkroniseras med klienten definierar vi en trigger som efter förÀndring/uppdatering av posten anropar funktionen som anges pÄ bilden nedan.
Hur prenumererar API:t pÄ dessa förÀndringar?
En fanout-mekanism skapas â den distribuerar meddelanden till kunder. Den samlar alla kundkanaler och skickar de uppdateringar som den fĂ„tt genom dessa kanaler:

HÀr Àr standardbiblioteket pq, som ansluter till databasen och sÀger att den vill lyssna pÄ kanalen (data_updates), kontrollerar att anslutningen Àr öppen och att allt fungerar. Jag hoppar över felkontrollen för att spara utrymme (att inte kontrollera Àr riskabelt).
Sedan stÀller vi in en Ticker asynkront, som skickar ping var 15:e sekund, och börjar lyssna pÄ kanalen som vi prenumererat pÄ. Om vi fÄr en ping, publicerar vi den pinget. Om vi fÄr nÄgon post, publicerar vi denna post till alla prenumeranter av denna Fanout.
Hur fungerar Fan-out?
PÄ svenska översÀtts det som "förgrenare". Vi har ett objekt som registrerar prenumeranter som vill fÄ nÄgra uppdateringar. SÄ snart en uppdatering till detta objekt kommer, distribuerar det denna uppdatering till alla sina prenumeranter. Ganska enkelt:

Hur detta implementeras i Go:

Det finns en struktur som synkroniseras med hjĂ€lp av Mutexar. Den har ett fĂ€lt som sparar anslutningens tillstĂ„nd mellan Fanout och databasen, dvs. just nu lyssnar den och kommer att ta emot uppdateringar, samt en lista över alla befintliga kanaler â en map, dĂ€r nyckeln Ă€r kanalen och struct i form av vĂ€rden (egentligen anvĂ€nds den inte pĂ„ nĂ„got sĂ€tt).
TvĂ„ metoder â Connected och Disconnected â gör att Fanout vet att vi har en anslutning till databasen, den har skapats och att anslutningen till databasen har brutits. I det andra fallet behöver alla klienter kopplas bort och meddelas att de inte lĂ€ngre kan lyssna och att de bör Ă„teransluta, eftersom anslutningen till dem har stĂ€ngts.
Det finns ocksÄ en metod Subscribe som lÀgger till en kanal i "lyssnare":

Det finns en metod Unsubscribe som tar bort kanalen frÄn de lyssnande, om klienten har kopplat bort sig, samt en metod Publish som gör att meddelanden kan skickas till alla prenumeranter.
FrĂ„ga: â Vad överförs via denna kanal?
MS: â En modell som har Ă€ndrats eller en ping (egentligen bara ett nummer, heltal).
MS: â Man kan skicka vad som helst, vilken struktur som helst, publicera â den omvandlas helt enkelt till JSON och det Ă€r allt.
MS: â Vi fĂ„r en notifiering frĂ„n "Postgres" â den innehĂ„ller namnet pĂ„ tabellen och identifieraren. Genom tabellens namn fĂ„r vi den registrering vi behöver via identifieraren, och denna struktur skickar vi för publicering.
Infrastruktur
Hur ser det ut ur ett infrastruktursynpunkt? Vi har 7 hĂ„rdvaruserverar: en av dem Ă€r helt dedikerad för databasen, pĂ„ de andra sex körs virtuella maskiner. Det finns 6 kopior av API:et: varje virtuell maskin med API körs pĂ„ en separat hĂ„rdvaruserver â detta för pĂ„litlighet.

Vi har tvÄ frontend, pÄ vilka Keepalived Àr installerat för att förbÀttra tillgÀngligheten, sÄ att i hÀndelse av nÄgot kan en frontend ersÀtta den andra. Dessutom finns det tvÄ kopior av CMS.
Det finns ocksĂ„ en statistikimportör. Det finns en DB Slave, frĂ„n vilken sĂ€kerhetskopior görs periodiskt. Det finns Pigeon Pusher â den applikationen som skickar push-notiser till kunderna, samt infrastrukturella saker: Zabbix, Graylog2 och Chef.
Denna infrastruktur Ă€r faktiskt överflödig, eftersom man kan hantera 100 000 med fĂ€rre servrar. Men vi hade hĂ„rdvaran â vi anvĂ€nde den (vi fick höra att det var okej â varför inte).
Fördelar med Go
Efter att vi har arbetat med denna applikation har dessa uppenbara fördelar med Go framtrÀtt.
- En fantastisk http-bibliotek. Med hjÀlp av det kan man skapa ganska mycket direkt "ur lÄdan".
- Dessutom kanaler som gjorde det mycket enkelt för oss att implementera mekanismen för att skicka notifieringar till kunder.
- Det underbara verktyget Race detector gjorde att vi kunde undanröja flera kritiska buggar (staging-infrastrukturen). Allt som fungerar pÄ staging körs och kompileras med Race-nyckeln; och vi kan dÀrmed se pÄ staging-infrastrukturen vilka potentiella problem vi har.
- Minimalism och enkelhet i sprÄket.

Vi söker utvecklare! Om nĂ„gon Ă€r intresserad â vĂ€nligen hör av er.
FrÄgor
FrĂ„ga frĂ„n publiken (vidare â F): â Jag verkar ha missat en viktig punkt angĂ„ende Fan-out. FörstĂ„r jag rĂ€tt att nĂ€r ni skickar svar till kunden, blockeras ni om kunden inte vill lĂ€sa?
MS: â Nej, vi blockerar oss inte. För det första Ă€r allt detta bakom nginx, vilket innebĂ€r att vi inte har nĂ„gra problem med lĂ„ngsamma kunder. För det andra har kunden en buffertkanal â i princip kan vi lĂ€gga upp till hundra uppdateringar dĂ€r... Om vi inte kan skriva till kanalen, sĂ„ tar den bort den. Om vi ser att kanalen har blockerat, stĂ€nger vi helt enkelt kanalen, och allt â kunden kommer att Ă„teransluta om det uppstĂ„r nĂ„got problem. DĂ€rför uppstĂ„r det i princip inga blockeringar hĂ€r.
L: â Kunde vi inte skicka en Listen/Notify-post direkt, istĂ€llet för identifikatorn för tabellen?
MS: â Listen/Notify har en begrĂ€nsning pĂ„ 8 000 byte pĂ„ preload som den skickar. I princip skulle vi kunna skicka det om vi hade att göra med en liten mĂ€ngd data, men jag tycker att det Ă€r helt enkelt mer pĂ„litligt att göra som vi gör. BegrĂ€nsningarna ligger i sjĂ€lva "PostgreSQL".
L: â FĂ„r kunderna uppdateringar om matcher som de inte Ă€r intresserade av?
MS: â I allmĂ€nhet, ja. Som regel sker det 2-3 matcher parallellt, och det Ă€r ganska sĂ€llsynt. Om kunden tittar pĂ„ nĂ„got, Ă€r det vanligtvis den matchen som pĂ„gĂ„r just nu. Dessutom har kunden en lokal databas dĂ€r alla dessa uppdateringar lagras, och Ă€ven utan internetanslutning kan kunden se alla passerade matcher som han har uppdateringar för. I princip synkroniserar vi vĂ„r databas pĂ„ servern med kundens lokala databas sĂ„ att han kan arbeta offline.
L: â Varför gjorde ni er egen ORM?
Alexey (en av utvecklarna av "Smotri+"): â Vid den tiden (det var för ett Ă„r sedan) fanns det fĂ€rre ORM:er Ă€n det finns nu, nĂ€r det finns ganska mĂ„nga. Av de flesta existerande ORM:er gillar jag mest att de flesta av dem arbetar med tomma grĂ€nssnitt. Det betyder att metoderna i dessa ORM:er Ă€r redo att ta emot vad som helst: en struktur, en strukturpekare, ett nummer, nĂ„got helt orelaterat...
VÄr ORM genererar strukturer baserat pÄ datamodellen. SjÀlv. SÄ alla metoder Àr specifika och anvÀnder inte reflektion osv. De tar emot strukturer och förvÀntar sig att anvÀnda de strukturer som tas emot.
L: â Hur mĂ„nga personer deltog?
MS: â I det inledande skedet deltog tvĂ„ personer. NĂ„gon gĂ„ng i juni började vi, i augusti var den största delen klar (den första versionen). I september var det release.
L: â DĂ€r du beskriver SSE, anvĂ€nder du ingen timeout. Varför sĂ„?
MS: â Om vi talar klart och tydligt, sĂ„ Ă€r SSE Ă€ndĂ„ ett html5-protokoll: standarden för SSE Ă€r avsedd för kommunikation med webblĂ€sare, sĂ„vitt jag förstĂ„r. Den har ytterligare funktioner för att webblĂ€sare ska kunna Ă„teransluta (och sĂ„ vidare), men vi behöver dem inte, eftersom vi hade kunder som kunde implementera vilken anslutningslogik som helst och hĂ€mta information. Vi gjorde snarare nĂ„got som liknar SSE Ă€n sjĂ€lva protokollet.
Det fanns ingen anledning. SÄvitt jag förstÄr implementerade kunderna anslutningsmekanismen praktiskt taget frÄn början. Det spelade i princip ingen roll för dem.
L: â Vilka ytterligare verktyg anvĂ€nde ni?
MS: â Vi anvĂ€nde mest aktivt govet och golint för att stilen skulle vara enhetlig, samt gofmt. Inga andra verktyg anvĂ€ndes.
L: â Hur genomförde ni felsökning?
MS: â Felsökningen skedde i stort sett med hjĂ€lp av tester. Vi anvĂ€nde ingen debugger och inget GOP.
L: â Kan du Ă„tervĂ€nda till den bild dĂ€r funktionen Publish Ă€r implementerad? Stör dig inte pĂ„ variabler med enstaviga namn?
MS: â Nej. De har en ganska "snĂ€v" synlighetsomrĂ„de. De anvĂ€nds ingen annanstans Ă€n hĂ€r (förutom inuti denna klass), och den Ă€r mycket kompakt â bara 7 rader lĂ„ng.
L: â Det kĂ€nns Ă€ndĂ„ inte intuitivt...
MS: â Nej, nej, det hĂ€r Ă€r verklig kod! Det handlar inte om stil. Det Ă€r helt enkelt en sĂ„n utilitaristisk, mycket liten klass â bara 3 fĂ€lt inuti klassen...

MS: â I stort sett Ă€ndras alla de data som synkroniseras med kunderna (sĂ€songsmatcher, spelare) inte. Grovt sagt, om vi ska göra nĂ„gon annan sport dĂ€r vi behöver Ă€ndra matchen, sĂ„ tar vi bara hĂ€nsyn till det i den nya versionen av klienten, och de gamla versionerna av klienten kommer att blockeras.
L: â Finns det nĂ„gra externa paket för att hantera beroenden?
MS: â Vi anvĂ€nde go dep.
L: â I föredragets Ă€mne fanns det nĂ„got om video, men det finns inget om video i föredraget.
MS: â Nej, jag har inget om video i mitt Ă€mne. Det heter "Svet +" â sĂ„ heter appen.
L: â Du sa att det strömmas till kunderna?..
MS: â Vi sysslade inte med strömmande video. Det gjorde helt och hĂ„llet "MegaFon". Ja, jag nĂ€mnde inte att appen Ă€r frĂ„n MegaFon.
MS: â Go â för att skicka all data â om kontot, om matchhĂ€ndelser, statistik⊠Go â det Ă€r helt backend för applikationen. Klienten mĂ„ste nĂ„gonstans fĂ„ veta vilken lĂ€nk som ska anvĂ€ndas för spelaren sĂ„ att anvĂ€ndaren kan se matchen. Vi har lĂ€nkar till videor och strömmar som Ă€r förberedda.

Lite reklam đ
Tack för att ni stannar kvar hos oss. Gillar ni vÄra artiklar? Vill ni se fler intressanta material? Stöd oss genom att göra en bestÀllning eller rekommendera oss till era vÀnner, , en unik motsvarighet till entry-level servrar som vi har skapat för er: (alternativ med RAID1 och RAID10 finns tillgÀngliga, upp till 24 kÀrnor och upp till 40GB DDR4).
Dell R730xd Ă€r tvĂ„ gĂ„nger billigare i datacentret Equinix Tier IV i Amsterdam? Bara hos oss i NederlĂ€nderna! Dell R420 â 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB â frĂ„n $99! LĂ€s om
KĂ€lla: habr.com
