Mikhail Salosin (nedan â MS): - Hej alla! Mitt namn Ă€r Michael. Jag jobbar som backend-utvecklare pĂ„ MC2 Software, och jag kommer att prata om att anvĂ€nda Go i backend av Look+-mobilapplikationen.
Ăr det nĂ„gon hĂ€r som gillar hockey?
DÄ Àr denna applikation för dig. Den Àr för Android och iOS och anvÀnds för att titta pÄ sÀndningar av olika sportevenemang online och inspelade. Applikationen innehÄller ocksÄ olika statistik, textsÀndningar, tabeller för konferenser, turneringar och annan information som Àr anvÀndbar för fansen.
OcksÄ i applikationen finns det nÄgot som videoögonblick, det vill sÀga du kan se de viktigaste ögonblicken i matcher (mÄl, slagsmÄl, skjutningar, etc.). Om du inte vill se hela sÀndningen kan du bara se de mest intressanta.
Vad anvÀnde du i utvecklingen?
Huvuddelen skrevs i Go. API:et som mobila klienter kommunicerade med skrevs i Go. En tjÀnst för att skicka pushnotiser till mobiltelefoner skrevs ocksÄ i Go. Vi var ocksÄ tvungna att skriva vÄr egen ORM, som vi kanske pratar om nÄgon gÄng. NÄvÀl, nÄgra smÄ tjÀnster skrevs i Go: Àndra storlek och ladda bilder för redaktörerna...
Vi anvÀnde PostgreSQL som databas. RedaktörsgrÀnssnittet skrevs i Ruby on Rails med ActiveAdmin-pÀrlan. Att importera statistik frÄn en statistikleverantör skrivs ocksÄ i Ruby.
För system-API-tester anvÀnde vi Python unittest. Memcached anvÀnds för att strypa API-betalningsanrop, "Chef" anvÀnds för att kontrollera konfigurationen, Zabbix anvÀnds för att samla in och övervaka intern systemstatistik. Graylog2 Àr för att samla in loggar, Slate Àr API-dokumentation för klienter.
Val av protokoll
Det första problemet vi stötte pÄ: vi behövde vÀlja ett protokoll för interaktion mellan backend- och mobilklienter, baserat pÄ följande punkter...
- Det viktigaste kravet: data om klienter mÄste uppdateras i realtid. Det vill sÀga att alla som just nu tittar pÄ sÀndningen ska fÄ uppdateringar nÀstan direkt.
- För att förenkla saker och ting antog vi att data som Àr synkroniserad med klienter inte raderas, utan döljs med hjÀlp av speciella flaggor.
- Alla typer av sÀllsynta förfrÄgningar (som statistik, lagsammansÀttningar, lagstatistik) erhÄlls av vanliga GET-förfrÄgningar.
- Dessutom mÄste systemet enkelt stödja 100 tusen anvÀndare samtidigt.
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Ä en websocket Àr ett överflödigt alternativ.
- Server-Sent Events (SSE) kom upp helt rÀtt! Det Àr ganska enkelt och tillfredsstÀller i princip allt vi behöver.
ServersÀnda hÀndelser
NÄgra ord om hur det hÀr fungerar...
Den körs ovanpÄ en http-anslutning. Klienten skickar en begÀran, servern svarar med Content-Type: text/event-stream och stÀnger inte anslutningen till klienten, utan fortsÀtter att skriva data till anslutningen:
Data kan skickas i ett format som överenskommits med kunderna. I vÄrt fall skickade vi det i denna form: namnet pÄ den Àndrade strukturen (person, spelare) skickades till hÀndelsefÀltet och JSON med nya, Àndrade fÀlt för spelaren skickades till datafÀltet.
LÄt oss nu prata om hur sjÀlva interaktionen fungerar.
- Det första klienten gör Àr att bestÀmma nÀr synkroniseringen med tjÀnsten senast utfördes: den tittar pÄ sin lokala databas och bestÀmmer datumet för den senaste Àndringen som registrerades av den.
- Den skickar en förfrÄgan med detta datum.
- Som svar skickar vi honom alla uppdateringar som har intrÀffat sedan det datumet.
- Efter det gör den en anslutning till livekanalen och stÀnger inte förrÀn den behöver dessa uppdateringar:
Vi skickar honom en lista med Àndringar: om nÄgon gör ett mÄl Àndrar vi matchresultatet, om han blir skadad skickas detta ocksÄ i realtid. SÄlunda fÄr kunder omedelbart uppdaterad data i matchhÀndelseflödet. Med jÀmna mellanrum, sÄ att klienten förstÄr att servern inte har dött, att ingenting hÀnt med den, skickar vi en tidsstÀmpel var 15:e sekund - sÄ att den vet att allt Àr i sin ordning och att det inte finns nÄgot behov av att Äteransluta.
Hur servas direktanslutningen?
- Först och frÀmst skapar vi en kanal dÀr buffrade uppdateringar kommer att tas emot.
- Efter det prenumererar vi pÄ den hÀr kanalen för att fÄ uppdateringar.
- Vi stÀller in rÀtt rubrik sÄ att klienten vet att allt Àr ok.
- Skicka det första pinget. Vi registrerar helt enkelt den aktuella anslutningens tidsstÀmpel.
- DÀrefter lÀser vi frÄn kanalen i en slinga tills uppdateringskanalen Àr stÀngd. Kanalen fÄr med jÀmna mellanrum antingen den aktuella tidsstÀmpeln eller Àndringar som vi redan skriver till öppna anslutningar.
Det första problemet vi stötte pÄ var följande: för varje anslutning som öppnades med klienten skapade vi en timer som tickade en gÄng var 15:e sekund - det visar sig att om vi hade 6 tusen anslutningar öppna med en maskin (med en API-server), 6 tusen timers skapades. Detta ledde till att maskinen inte höll den erforderliga lasten. Problemet var inte sÄ uppenbart för oss, men vi fick lite hjÀlp och fixade det.
Som ett resultat kommer nu vÄr ping frÄn samma kanal som uppdateringen kommer frÄn.
Följaktligen finns det bara en timer som tickar en gÄng var 15:e sekund.
Det finns flera hjÀlpfunktioner hÀr - att skicka rubriken, pinga och sjÀlva strukturen. Det vill sÀga namnet pÄ tabellen (person, match, sÀsong) och informationen om denna post överförs hÀr:
Mekanism för att skicka uppdateringar
Nu lite om var förÀndringarna kommer ifrÄn. Vi har flera personer, redaktörer, som tittar pÄ sÀndningen i realtid. De skapar alla hÀndelser: nÄgon blev utvisad, nÄgon blev skadad, nÄgon slags ersÀttare...
Med hjÀlp av ett CMS kommer data in i databasen. Efter detta meddelar databasen API-servrarna om detta med hjÀlp av Lyssna/Meddela-mekanismen. API-servrar skickar redan denna information till klienter. SÄledes har vi i princip bara ett fÄtal servrar anslutna till databasen och det finns ingen speciell belastning pÄ databasen, eftersom klienten inte interagerar direkt med databasen pÄ nÄgot sÀtt:
PostgreSQL: Lyssna/Meddela
Lyssna/Meddela-mekanismen i Postgres lÄter dig meddela hÀndelseprenumeranter att nÄgon hÀndelse har Àndrats - nÄgon post har skapats i databasen. För att göra detta skrev vi en enkel trigger och funktion:
NÀr vi infogar eller Àndrar en post anropar vi notify-funktionen pÄ data_updates-kanalen och skickar dit namnet pÄ tabellen och identifieraren för posten som Àndrades eller infogades.
För alla tabeller som mÄste synkroniseras med klienten definierar vi en trigger, som efter Àndring/uppdatering av en post anropar funktionen som anges pÄ bilden nedan.
Hur prenumererar API:et pÄ dessa Àndringar?
En Fanout-mekanism skapas - den skickar meddelanden till klienten. Den samlar in alla kundkanaler och skickar uppdateringar som den fÄtt genom dessa kanaler:
HÀr kontrollerar standard pq-biblioteket, som ansluter till databasen och sÀger att det vill lyssna pÄ kanalen (data_updates), att anslutningen Àr öppen och att allt Àr bra. Jag utelÀmnar felkontroll för att spara utrymme (det Àr farligt att inte kontrollera).
DĂ€refter stĂ€ller vi asynkront in Ticker, som skickar ett ping var 15:e sekund, och börjar lyssna pĂ„ kanalen vi prenumererar pĂ„. Om vi ââfĂ„r en ping publicerar vi denna ping. Om vi ââfĂ„r nĂ„gon form av bidrag publicerar vi detta till alla prenumeranter pĂ„ denna Fanout.
Hur fungerar Fan-out?
PÄ ryska översÀtts detta som "splitter". Vi har ett objekt som registrerar prenumeranter som vill fÄ lite uppdateringar. Och sÄ snart en uppdatering kommer till det hÀr objektet distribuerar den denna uppdatering till alla sina prenumeranter. Enkelt nog:
Hur det implementeras i Go:
Det finns en struktur, den synkroniseras med Mutexes. Den har ett fÀlt som sparar tillstÄndet för Fanouts anslutning till databasen, det vill sÀga den lyssnar för nÀrvarande och kommer att fÄ uppdateringar, samt en lista över alla tillgÀngliga kanaler - karta, vars nyckel Àr kanalen och strukturen i form av vÀrden (i huvudsak anvÀnds det inte pÄ nÄgot sÀtt).
TvÄ metoder - Ansluten och FrÄnkopplad - lÄter oss berÀtta för Fanout att vi har en anslutning till basen, den har dykt upp och att anslutningen till basen har brutits. I det andra fallet mÄste du koppla bort alla klienter och tala om för dem att de inte lÀngre kan lyssna pÄ nÄgonting och att de Äteransluter eftersom anslutningen till dem har stÀngts.
Det finns ocksÄ en prenumerationsmetod som lÀgger till kanalen till "lyssnarna":
Det finns en Unsubscribe-metod, som tar bort kanalen frÄn lyssnarna om klienten kopplar bort, samt en Publiceringsmetod, som lÄter dig skicka ett meddelande till alla prenumeranter.
FrĂ„ga: â Vad sĂ€nds genom denna kanal?
FRĂKEN: â Modellen som har Ă€ndrats eller ping sĂ€nds (i huvudsak bara ett tal, heltal).
FRĂKEN: â Du kan skicka vad som helst, skicka vilken struktur som helst, publicera det â det blir bara till JSON och det Ă€r allt.
FRĂKEN: â Vi fĂ„r ett meddelande frĂ„n Postgres â det innehĂ„ller tabellnamn och identifierare. Baserat pĂ„ tabellnamnet och identifieraren fĂ„r vi den post vi behöver, och sedan skickar vi denna struktur för publicering.
Infrastruktur
Hur ser det hÀr ut ur ett infrastrukturperspektiv? Vi har 7 hÄrdvaruservrar: en av dem Àr helt dedikerad till databasen, de andra sex kör virtuella maskiner. Det finns 6 kopior av API:t: varje virtuell maskin med API körs pÄ en separat hÄrdvaruserver - detta för tillförlitligheten.
Vi har tvĂ„ grĂ€nssnitt med Keepalved installerat för att förbĂ€ttra tillgĂ€ngligheten, sĂ„ att om nĂ„got hĂ€nder kan den ena grĂ€nsen ersĂ€tta den andra. Dessutom â tvĂ„ exemplar av CMS.
Det finns Àven en statistikimportör. Det finns en DB-slav frÄn vilken sÀkerhetskopior görs med jÀmna mellanrum. Det finns Pigeon Pusher, en applikation som skickar push-meddelanden till kunder, sÄvÀl som infrastruktursaker: Zabbix, Graylog2 och Chef.
Faktum Àr att denna infrastruktur Àr överflödig, eftersom 100 tusen kan serveras med fÀrre servrar. Men det fanns jÀrn - vi anvÀnde det (vi fick höra att det var möjligt - varför inte).
Fördelar med Go
Efter att vi arbetat med den hÀr applikationen framkom sÄ uppenbara fördelar med Go.
- Coolt http-bibliotek. Med den kan du skapa ganska mycket ur lÄdan.
- Dessutom kanaler som gjorde det möjligt för oss att mycket enkelt implementera en mekanism för att skicka meddelanden till kunder.
- Det underbara Race-detektorn tillÀt oss att eliminera flera kritiska buggar (staging-infrastruktur). Allt som fungerar pÄ iscensÀttning lanseras, sammanstÀllt med Race-nyckeln; och vi kan följaktligen titta pÄ iscensÀttningsinfrastrukturen för att se vilka potentiella problem vi har.
- Minimalism och enkelhet i sprÄket.
Vi söker utvecklare! Om nÄgon vill, snÀlla.
frÄgor
FrĂ„ga frĂ„n publiken (nedan â B): â Det verkar som om du missade en viktig punkt angĂ„ende Fan-out. Har jag rĂ€tt i att förstĂ„ att nĂ€r man skickar ett svar till en klient sĂ„ blockerar man om klienten inte vill lĂ€sa?
FRĂKEN: â Nej, vi blockerar inte. För det första har vi allt detta bakom nginx, det vill sĂ€ga det finns inga problem med lĂ„ngsamma klienter. För det andra har klienten en kanal med en buffert - faktiskt kan vi lĂ€gga upp till hundra uppdateringar dĂ€r... Om vi ââinte kan skriva till kanalen sĂ„ raderar den den. Om vi ââser att kanalen Ă€r blockerad stĂ€nger vi helt enkelt kanalen, och det Ă€r det - klienten kommer att Ă„teransluta om nĂ„got problem uppstĂ„r. DĂ€rför finns det i princip ingen blockering hĂ€r.
PĂ : â Kunde det inte vara möjligt att omedelbart skicka en post till Listen/Notify, och inte en identifieringstabell?
FRĂKEN: â Lyssna/Meddela har en grĂ€ns pĂ„ 8 tusen byte pĂ„ förladdningen den skickar. I princip skulle det vara möjligt att skicka om vi hade att göra med en liten mĂ€ngd data, men det verkar för mig att det hĂ€r sĂ€ttet [sĂ€ttet vi gör det] helt enkelt Ă€r mer tillförlitligt. BegrĂ€nsningarna finns i Postgres sjĂ€lv.
PĂ : â FĂ„r kunderna uppdateringar om matcher som de inte Ă€r intresserade av?
FRĂKEN: â Generellt sett, ja. Som regel pĂ„gĂ„r det 2-3 matcher parallellt, och Ă€ven dĂ„ ganska sĂ€llan. Om en klient tittar pĂ„ nĂ„got, sĂ„ brukar han titta pĂ„ matchen som pĂ„gĂ„r. Sedan har klienten en lokal databas i vilken alla dessa uppdateringar lĂ€ggs ihop, och Ă€ven utan internetanslutning kan klienten se alla tidigare matchningar som han har uppdateringar för. I huvudsak synkroniserar vi vĂ„r databas pĂ„ servern med kundens lokala databas sĂ„ att han kan arbeta offline.
PĂ : â Varför gjorde du din egen ORM?
Alexey (en av utvecklarna av Look+): â PĂ„ den tiden (det var ett Ă„r sedan) var det fĂ€rre ORM Ă€n nu, nĂ€r det Ă€r ganska mĂ„nga. Min favoritsak med de flesta ORMs dĂ€r ute Ă€r att de flesta av dem körs pĂ„ tomma grĂ€nssnitt. Det vill sĂ€ga, metoderna i dessa ORM:er Ă€r redo att ta pĂ„ sig vad som helst: en struktur, en strukturpekare, ett nummer, nĂ„got helt irrelevant...
VÄr ORM genererar strukturer baserade pÄ datamodellen. Jag sjÀlv. Och dÀrför Àr alla metoder konkreta, anvÀnder inte reflektion etc. De accepterar strukturer och förvÀntar sig att anvÀnda de strukturerna som kommer.
PĂ : â Hur mĂ„nga deltog?
FRĂKEN: â I det inledande skedet deltog tvĂ„ personer. Vi började nĂ„gonstans i juni, och i augusti var huvuddelen klar (första versionen). Det slĂ€pptes i september.
PĂ : â DĂ€r du beskriver SSE anvĂ€nder du inte timeout. Varför Ă€r det sĂ„?
FRĂKEN: â För att vara Ă€rlig Ă€r SSE fortfarande ett html5-protokoll: SSE-standarden Ă€r utformad för att kommunicera med webblĂ€sare, sĂ„ vitt jag förstĂ„r. Den har ytterligare funktioner sĂ„ att webblĂ€sare kan Ă„teransluta (och sĂ„ vidare), men vi behöver dem inte, eftersom vi hade klienter som kunde implementera vilken logik som helst för att ansluta och ta emot information. Vi gjorde inte SSE, utan snarare nĂ„got liknande SSE. Detta Ă€r inte sjĂ€lva protokollet.
Det fanns inget behov. SÄvitt jag förstÄr implementerade klienter anslutningsmekanismen nÀstan frÄn början. De brydde sig inte riktigt.
PĂ : â Vilka ytterligare verktyg anvĂ€nde du?
FRĂKEN: â Vi anvĂ€nde mest aktivt govet och golint för att göra stilen enhetlig, sĂ„vĂ€l som gofmt. Inget annat anvĂ€ndes.
PĂ : â Vad anvĂ€nde du för att felsöka?
FRĂKEN: â Felsökningen utfördes till stor del med hjĂ€lp av tester. Vi anvĂ€nde ingen debugger eller GOP.
PĂ : â Kan du returnera bilden dĂ€r Publiceringsfunktionen Ă€r implementerad? Förvirrar variabelnamn med en bokstav dig?
FRĂKEN: - Nej. De har en ganska "snĂ€v" synlighet. De anvĂ€nds inte nĂ„gon annanstans förutom hĂ€r (förutom den interna delen av denna klass), och den Ă€r vĂ€ldigt kompakt - den tar bara 7 rader.
PĂ : â PĂ„ nĂ„got sĂ€tt Ă€r det fortfarande inte intuitivt...
FRĂKEN: â Nej, nej, det hĂ€r Ă€r en riktig kod! Det handlar inte om stil. Det Ă€r bara en sĂ„dan utilitaristisk, vĂ€ldigt liten klass - bara 3 fĂ€lt i klassen...
FRĂKEN: â I stort sett förĂ€ndras inte all data som synkroniseras med kunder (sĂ€songsmatcher, spelare). Grovt sett, om vi gör en annan sport dĂ€r vi behöver Ă€ndra matchen, kommer vi helt enkelt att ta hĂ€nsyn till allt i den nya versionen av klienten, och de gamla versionerna av klienten kommer att förbjudas.
PĂ : â Finns det nĂ„gra paket för beroendehantering frĂ„n tredje part?
FRĂKEN: â Vi anvĂ€nde go dep.
PĂ : â Det stod nĂ„got om video i Ă€mnet för rapporten, men det stod ingenting om video i rapporten.
FRĂKEN: â Nej, jag har inget i Ă€mnet om videon. Det heter "Look+" - det Ă€r namnet pĂ„ applikationen.
PĂ : â Du sa att det streamas till kunder?
FRĂKEN: â Vi var inte involverade i strömmande video. Detta gjordes helt av Megafon. Ja, jag sa inte att applikationen var MegaFon.
FRĂKEN: â Go â för att skicka all data â om poĂ€ng, om matchhĂ€ndelser, statistik... Go Ă€r hela backend för applikationen. Klienten mĂ„ste nĂ„gonstans veta vilken lĂ€nk som ska anvĂ€ndas för spelaren sĂ„ att anvĂ€ndaren kan se matchen. Vi har lĂ€nkar till filmer och streams som har förberetts.
NĂ„gra annonser đ
Tack för att du stannar hos oss. Gillar du vĂ„ra artiklar? Vill du se mer intressant innehĂ„ll? Stöd oss ââgenom att lĂ€gga en bestĂ€llning eller rekommendera till vĂ€nner,
Dell R730xd 2 gÄnger billigare i Equinix Tier IV datacenter i Amsterdam? Bara hÀr
KĂ€lla: will.com