Mikhail Salosin. Golang Meetup. Bruke Go i bakenden av Look+-applikasjonen

Mikhail Salosin (heretter – MS): - Hei alle sammen! Jeg heter Michael. Jeg jobber som backend-utvikler hos MC2 Software, og jeg skal snakke om å bruke Go i backend av Look+-mobilapplikasjonen.

Mikhail Salosin. Golang Meetup. Bruke Go i bakenden av Look+-applikasjonen

Er det noen her som liker hockey?

Mikhail Salosin. Golang Meetup. Bruke Go i bakenden av Look+-applikasjonen

Da er denne applikasjonen for deg. Den er for Android og iOS og brukes til å se sendinger av ulike sportsbegivenheter på nettet og tatt opp. Applikasjonen inneholder også ulike statistikker, tekstsendinger, tabeller for konferanser, turneringer og annen nyttig informasjon for fansen.

Mikhail Salosin. Golang Meetup. Bruke Go i bakenden av Look+-applikasjonen

Også i applikasjonen er det noe som videoøyeblikk, det vil si at du kan se de viktigste øyeblikkene i kampene (mål, kamper, skuddvekslinger, etc.). Hvis du ikke vil se hele sendingen, kan du bare se de mest interessante.

Hva brukte du i utviklingen?

Hoveddelen ble skrevet i Go. API-en som mobilklienter kommuniserte med ble skrevet i Go. En tjeneste for å sende push-varsler til mobiltelefoner ble også skrevet i Go. Vi måtte også skrive vår egen ORM, som vi kanskje snakker om en dag. Vel, noen små tjenester ble skrevet i Go: endre størrelse og laste inn bilder for redaktørene...

Vi brukte PostgreSQL som database. Redaktørgrensesnittet ble skrevet i Ruby on Rails ved å bruke ActiveAdmin-perlen. Import av statistikk fra en statistikkleverandør skrives også i Ruby.

For system-API-tester brukte vi Python unittest. Memcached brukes til å strupe API-betalingsanrop, "Chef" brukes til å kontrollere konfigurasjon, Zabbix brukes til å samle inn og overvåke intern systemstatistikk. Graylog2 er for å samle logger, Slate er API-dokumentasjon for klienter.

Mikhail Salosin. Golang Meetup. Bruke Go i bakenden av Look+-applikasjonen

Protokollvalg

Det første problemet vi møtte: vi måtte velge en protokoll for interaksjon mellom backend- og mobilklienter, basert på følgende punkter...

  • Det viktigste kravet: data om klienter må oppdateres i sanntid. Det vil si at alle som for øyeblikket ser på sendingen skal få oppdateringer nesten umiddelbart.
  • For å forenkle ting, antok vi at data som er synkronisert med klienter ikke slettes, men skjules ved hjelp av spesielle flagg.
  • Alle slags sjeldne forespørsler (som statistikk, lagsammensetninger, lagstatistikker) innhentes av vanlige GET-forespørsler.
  • I tillegg måtte systemet enkelt støtte 100 tusen brukere samtidig.

Basert på dette hadde vi to protokollalternativer:

  1. Websockets. Men vi trengte ikke kanaler fra klienten til serveren. Vi trengte bare å sende oppdateringer fra serveren til klienten, så en websocket er et overflødig alternativ.
  2. Server-Sent Events (SSE) kom opp akkurat! Det er ganske enkelt og tilfredsstiller i grunnen alt vi trenger.

Server-sendte hendelser

Noen få ord om hvordan dette fungerer...

Den kjører på toppen av en http-tilkobling. Klienten sender en forespørsel, serveren svarer med Content-Type: text/event-stream og lukker ikke forbindelsen med klienten, men fortsetter å skrive data til forbindelsen:

Mikhail Salosin. Golang Meetup. Bruke Go i bakenden av Look+-applikasjonen

Data kan sendes i et format som er avtalt med kunder. I vårt tilfelle sendte vi det i dette skjemaet: navnet på den endrede strukturen (person, spiller) ble sendt til hendelsesfeltet, og JSON med nye, endrede felt for spilleren ble sendt til datafeltet.

La oss nå snakke om hvordan selve samhandlingen fungerer.

  • Det første klienten gjør er å bestemme siste gang synkronisering med tjenesten ble utført: den ser på sin lokale database og bestemmer datoen for den siste endringen registrert av den.
  • Den sender en forespørsel med denne datoen.
  • Som svar sender vi ham alle oppdateringene som har skjedd siden den datoen.
  • Etter det kobles den til livekanalen og lukkes ikke før den trenger disse oppdateringene:

Mikhail Salosin. Golang Meetup. Bruke Go i bakenden av Look+-applikasjonen

Vi sender ham en liste over endringer: hvis noen scorer et mål, endrer vi kampens poengsum, hvis han blir skadet, sendes dette også i sanntid. Dermed mottar klienter umiddelbart oppdaterte data i kamphendelsesfeeden. Med jevne mellomrom, slik at klienten forstår at serveren ikke har dødd, at ingenting har skjedd med den, sender vi et tidsstempel hvert 15. sekund - slik at den vet at alt er i orden og at det ikke er behov for å koble til på nytt.

Hvordan betjenes direkteforbindelsen?

  • Først av alt oppretter vi en kanal der bufrede oppdateringer vil bli mottatt.
  • Etter det abonnerer vi på denne kanalen for å motta oppdateringer.
  • Vi setter riktig overskrift slik at klienten vet at alt er ok.
  • Send det første pinget. Vi registrerer ganske enkelt gjeldende tilkoblingstidsstempel.
  • Etter det leser vi fra kanalen i en loop til oppdateringskanalen er stengt. Kanalen mottar med jevne mellomrom enten gjeldende tidsstempel eller endringer som vi allerede skriver for å åpne tilkoblinger.

Mikhail Salosin. Golang Meetup. Bruke Go i bakenden av Look+-applikasjonen

Det første problemet vi møtte var følgende: for hver tilkobling som ble åpnet med klienten, opprettet vi en timer som krysset av en gang hvert 15. sekund - det viser seg at hvis vi hadde 6 tusen tilkoblinger åpne med en maskin (med en API-server), 6 tusen tidtakere ble opprettet. Dette førte til at maskinen ikke holdt den nødvendige lasten. Problemet var ikke så åpenbart for oss, men vi fikk litt hjelp og fikset det.

Som et resultat kommer nå pinget vårt fra den samme kanalen som oppdateringen kommer fra.

Følgelig er det bare én timer som tikker en gang hvert 15. sekund.

Det er flere hjelpefunksjoner her - sending av header, ping og selve strukturen. Det vil si at navnet på tabellen (person, kamp, ​​sesong) og informasjonen om denne oppføringen overføres her:

Mikhail Salosin. Golang Meetup. Bruke Go i bakenden av Look+-applikasjonen

Mekanisme for å sende oppdateringer

Nå litt om hvor endringene kommer fra. Vi har flere personer, redaktører, som ser på sendingen i sanntid. De skaper alle hendelsene: noen ble utvist, noen ble skadet, en slags erstatning...

Ved hjelp av et CMS kommer data inn i databasen. Etter dette varsler databasen API-serverne om dette ved hjelp av Listen/Varsle-mekanismen. API-servere sender allerede denne informasjonen til klienter. Dermed har vi i hovedsak bare noen få servere koblet til databasen og det er ingen spesiell belastning på databasen, fordi klienten ikke samhandler direkte med databasen på noen måte:

Mikhail Salosin. Golang Meetup. Bruke Go i bakenden av Look+-applikasjonen

PostgreSQL: Lytt/varsle

Lytt/varsle-mekanismen i Postgres lar deg varsle hendelsesabonnenter om at en hendelse har endret seg - en eller annen post er opprettet i databasen. For å gjøre dette skrev vi en enkel trigger og funksjon:

Mikhail Salosin. Golang Meetup. Bruke Go i bakenden av Look+-applikasjonen

Når vi setter inn eller endrer en post, kaller vi varslingsfunksjonen på data_updates-kanalen, og sender der navnet på tabellen og identifikatoren til posten som ble endret eller satt inn.

For alle tabeller som må synkroniseres med klienten, definerer vi en trigger, som, etter å ha endret/oppdatert en post, kaller opp funksjonen som er angitt på lysbildet nedenfor.
Hvordan abonnerer API på disse endringene?

En Fanout-mekanisme opprettes - den sender meldinger til klienten. Den samler inn alle kundekanaler og sender oppdateringer den har mottatt gjennom disse kanalene:

Mikhail Salosin. Golang Meetup. Bruke Go i bakenden av Look+-applikasjonen

Her sjekker standard pq-biblioteket, som kobler seg til databasen og sier at den vil høre på kanalen (data_updates), at tilkoblingen er åpen og alt er i orden. Jeg utelater feilkontroll for å spare plass (det er farlig å ikke sjekke).

Deretter setter vi asynkront Ticker, som sender et ping hvert 15. sekund, og begynner å lytte til kanalen vi abonnerer på. Hvis vi mottar et ping, publiserer vi dette pinget. Hvis vi mottar en form for oppføring, publiserer vi denne oppføringen til alle abonnenter på denne Fanout.

Hvordan fungerer Fan-out?

På russisk oversettes dette som "splitter". Vi har ett objekt som registrerer abonnenter som ønsker å motta noen oppdateringer. Og så snart en oppdatering kommer til dette objektet, distribuerer det denne oppdateringen til alle sine abonnenter. Enkelt nok:

Mikhail Salosin. Golang Meetup. Bruke Go i bakenden av Look+-applikasjonen

Slik implementeres det i Go:

Mikhail Salosin. Golang Meetup. Bruke Go i bakenden av Look+-applikasjonen

Det er en struktur, den er synkronisert ved hjelp av Mutexes. Den har et felt som lagrer tilstanden til Fanouts tilkobling til databasen, det vil si at den lytter og vil motta oppdateringer, samt en liste over alle tilgjengelige kanaler - kart, hvis nøkkel er kanalen og strukturen i form av verdier (i hovedsak brukes den ikke på noen måte).

To metoder - Tilkoblet og Frakoblet - lar oss fortelle Fanout at vi har en forbindelse til basen, den har dukket opp og at forbindelsen til basen er brutt. I det andre tilfellet må du koble fra alle klienter og fortelle dem at de ikke lenger kan lytte til noe, og at de kobler til på nytt fordi tilkoblingen til dem er stengt.

Det er også en abonnentmetode som legger til kanalen til "lytterne":

Mikhail Salosin. Golang Meetup. Bruke Go i bakenden av Look+-applikasjonen

Det er en Unsubscribe-metode, som fjerner kanalen fra lyttere hvis klienten kobler fra, samt en Publish-metode, som lar deg sende en melding til alle abonnenter.

Spørsmål: – Hva overføres gjennom denne kanalen?

MS: – Modellen som har endret seg eller ping blir overført (i hovedsak bare et tall, heltall).

MS: – Du kan sende hva som helst, sende hvilken som helst struktur, publisere det – det blir bare til JSON og det er det.

MS: – Vi mottar en melding fra Postgres – den inneholder tabellnavn og identifikator. Basert på tabellnavn og identifikator får vi posten vi trenger, og så sender vi denne strukturen for publisering.

infrastruktur

Hvordan ser dette ut fra et infrastrukturperspektiv? Vi har 7 maskinvareservere: en av dem er fullstendig dedikert til databasen, de andre seks kjører virtuelle maskiner. Det er 6 kopier av API: hver virtuell maskin med API kjører på en separat maskinvareserver - dette er for påliteligheten.

Mikhail Salosin. Golang Meetup. Bruke Go i bakenden av Look+-applikasjonen

Vi har to frontends med Keepalved installert for å forbedre tilgjengeligheten, slik at hvis noe skjer, kan den ene frontenden erstatte den andre. Dessuten – to eksemplarer av CMS.

Det er også en statistikkimportør. Det er en DB-slave som det blir tatt backup fra med jevne mellomrom. Det er Pigeon Pusher, en applikasjon som sender push-varsler til klienter, samt infrastruktur-ting: Zabbix, Graylog2 og Chef.

Faktisk er denne infrastrukturen overflødig, fordi 100 tusen kan betjenes med færre servere. Men det var jern - vi brukte det (vi ble fortalt at det var mulig - hvorfor ikke).

Fordeler med Go

Etter at vi jobbet med denne applikasjonen, dukket det opp slike åpenbare fordeler med Go.

  • Kult http-bibliotek. Med den kan du lage ganske mye ut av esken.
  • Pluss kanaler som gjorde at vi veldig enkelt kunne implementere en mekanisme for å sende varsler til klienter.
  • Den fantastiske tingen Race-detektor tillot oss å eliminere flere kritiske feil (iscenesettelsesinfrastruktur). Alt som fungerer på iscenesettelse lanseres, kompilert med Race-nøkkelen; og vi kan følgelig se på iscenesettelsesinfrastrukturen for å se hvilke potensielle problemer vi har.
  • Minimalisme og enkelhet i språket.

Mikhail Salosin. Golang Meetup. Bruke Go i bakenden av Look+-applikasjonen

Vi søker utviklere! Hvis noen vil, vær så snill.

spørsmål

Spørsmål fra salen (heretter – B): – Det ser ut til at du gikk glipp av ett viktig poeng angående Fan-out. Har jeg rett i å forstå at når du sender et svar til en klient, blokkerer du hvis klienten ikke vil lese?

MS: – Nei, vi blokkerer ikke. For det første har vi alt dette bak nginx, det vil si at det ikke er noen problemer med trege klienter. For det andre har klienten en kanal med buffer - faktisk kan vi legge inn opptil hundre oppdateringer der... Hvis vi ikke kan skrive til kanalen, så sletter den den. Hvis vi ser at kanalen er blokkert, lukker vi ganske enkelt kanalen, og det er det - klienten vil koble til på nytt hvis det oppstår problemer. Derfor er det i prinsippet ingen blokkering her.

I: – Kunne det ikke vært mulig å umiddelbart sende en post til Listen/Notify, og ikke en identifikasjonstabell?

MS: – Lytt/varsle har en grense på 8 tusen byte på forhåndsinnlastingen den sender. I prinsippet ville det vært mulig å sende hvis vi hadde å gjøre med en liten mengde data, men det virker for meg som om denne måten [måten vi gjør det på] rett og slett er mer pålitelig. Begrensningene ligger i Postgres selv.

I: – Får kundene oppdateringer om kamper de ikke er interessert i?

MS: – Generelt sett, ja. Som regel går det 2-3 kamper parallelt, og selv da ganske sjelden. Hvis en klient ser på noe, ser han vanligvis kampen som pågår. Deretter har klienten en lokal database der alle disse oppdateringene er lagt sammen, og selv uten Internett-tilkobling kan klienten se alle tidligere kamper som han har oppdateringer for. I hovedsak synkroniserer vi databasen vår på serveren med klientens lokale database slik at han kan jobbe offline.

I: – Hvorfor laget du din egen ORM?

Alexey (en av utviklerne av Look+): – På den tiden (det var et år siden) var det færre ORM enn nå, da det er ganske mange av dem. Min favoritt ting om de fleste ORMs der ute er at de fleste av dem kjører på tomme grensesnitt. Det vil si at metodene i disse ORM-ene er klare til å ta på seg hva som helst: en struktur, en strukturpeker, et tall, noe helt irrelevant...

Vår ORM genererer strukturer basert på datamodellen. Meg selv. Og derfor er alle metoder konkrete, bruker ikke refleksjon osv. De aksepterer strukturer og forventer å bruke de strukturene som kommer.

I: – Hvor mange deltok?

MS: – I den innledende fasen deltok to personer. Vi startet et sted i juni, og i august var hoveddelen klar (første versjon). Det var en utgivelse i september.

I: – Der du beskriver SSE, bruker du ikke timeout. Hvorfor det?

MS: – For å være ærlig er SSE fortsatt en html5-protokoll: SSE-standarden er designet for å kommunisere med nettlesere, så vidt jeg forstår. Den har tilleggsfunksjoner slik at nettlesere kan koble til på nytt (og så videre), men vi trenger dem ikke, fordi vi hadde klienter som kunne implementere enhver logikk for å koble til og motta informasjon. Vi laget ikke SSE, men snarere noe som ligner på SSE. Dette er ikke selve protokollen.
Det var ikke behov. Så vidt jeg forstår, implementerte klienter tilkoblingsmekanismen nesten fra bunnen av. De brydde seg egentlig ikke.

I: – Hvilke tilleggsverktøy brukte du?

MS: – Vi brukte mest aktivt govet og golint for å gjøre stilen enhetlig, så vel som gofmt. Ingenting annet ble brukt.

I: – Hva brukte du til å feilsøke?

MS: – Feilsøking ble i stor grad utført ved hjelp av tester. Vi brukte ingen debugger eller GOP.

I: – Kan du returnere lysbildet der publiseringsfunksjonen er implementert? Forvirrer variabelnavn med én bokstav deg?

MS: - Nei. De har et ganske "smalt" synlighetsområde. De brukes ikke andre steder bortsett fra her (bortsett fra de interne delene i denne klassen), og den er veldig kompakt - den tar bare 7 linjer.

I: – På en eller annen måte er det fortsatt ikke intuitivt...

MS: – Nei, nei, dette er en skikkelig kode! Det handler ikke om stil. Det er bare en så utilitaristisk, veldig liten klasse - bare 3 felt inne i klassen ...

Mikhail Salosin. Golang Meetup. Bruke Go i bakenden av Look+-applikasjonen

MS: – Stort sett endres ikke all data som er synkronisert med klienter (sesongkamper, spillere). Grovt sett, hvis vi lager en annen sport der vi må endre kampen, vil vi ganske enkelt ta hensyn til alt i den nye versjonen av klienten, og de gamle versjonene av klienten vil bli utestengt.

I: – Er det noen tredjeparts avhengighetsadministrasjonspakker?

MS: – Vi brukte go dep.

I: – Det var noe om video i temaet for rapporten, men det var ingenting i rapporten om video.

MS: – Nei, jeg har ikke noe i emnet om videoen. Den heter "Look+" - det er navnet på applikasjonen.

I: – Du sa at det streames til klienter?

MS: – Vi var ikke involvert i streaming av video. Dette ble i sin helhet gjort av Megafon. Ja, jeg sa ikke at applikasjonen var MegaFon.

MS: – Go – for å sende alle data – om poengsum, på kamphendelser, statistikk... Go er hele backend for applikasjonen. Klienten må vite fra et sted hvilken lenke som skal brukes for spilleren slik at brukeren kan se kampen. Vi har lenker til videoer og strømmer som er utarbeidet.

Noen annonser 🙂

Takk for at du bor hos oss. Liker du artiklene våre? Vil du se mer interessant innhold? Støtt oss ved å legge inn en bestilling eller anbefale til venner, cloud VPS for utviklere fra $4.99, en unik analog av entry-level servere, som ble oppfunnet av oss for deg: Hele sannheten om VPS (KVM) E5-2697 v3 (6 kjerner) 10GB DDR4 480GB SSD 1Gbps fra $19 eller hvordan dele en server? (tilgjengelig med RAID1 og RAID10, opptil 24 kjerner og opptil 40 GB DDR4).

Dell R730xd 2x billigere i Equinix Tier IV datasenter i Amsterdam? Bare her 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV fra $199 i Nederland! Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - fra $99! Lese om Hvordan bygge infrastruktur corp. klasse med bruk av Dell R730xd E5-2650 v4-servere verdt 9000 euro for en krone?

Kilde: www.habr.com

Legg til en kommentar