Mikhail Salosin. Golang Meetup. Brug af Go i backend af Look+-applikationen

Mikhail Salosin (i det følgende – MS): - Hej alle! Mit navn er Michael. Jeg arbejder som backend-udvikler hos MC2 Software, og jeg vil tale om at bruge Go i backend af Look+ mobilapplikationen.

Mikhail Salosin. Golang Meetup. Brug af Go i backend af Look+-applikationen

Er der nogen her, der kan lide hockey?

Mikhail Salosin. Golang Meetup. Brug af Go i backend af Look+-applikationen

Så er denne applikation noget for dig. Den er til Android og iOS og bruges til at se udsendelser af forskellige sportsbegivenheder online og optages. Applikationen indeholder også forskellige statistikker, tekstudsendelser, tabeller til konferencer, turneringer og anden nyttig information for fans.

Mikhail Salosin. Golang Meetup. Brug af Go i backend af Look+-applikationen

Også i applikationen er der sådan noget som videoøjeblikke, dvs. du kan se de vigtigste øjeblikke i kampene (mål, kampe, skudvekslinger osv.). Hvis du ikke vil se hele udsendelsen, kan du kun se de mest interessante.

Hvad brugte du i udviklingen?

Hoveddelen blev skrevet i Go. API'et, som mobile klienter kommunikerede med, blev skrevet i Go. En tjeneste til at sende push-beskeder til mobiltelefoner blev også skrevet i Go. Vi skulle også skrive vores egen ORM, som vi måske skulle tale om en dag. Nå, nogle små tjenester blev skrevet i Go: ændre størrelse og indlæse billeder til redaktørerne...

Vi brugte PostgreSQL som database. Editor-grænsefladen blev skrevet i Ruby on Rails ved hjælp af ActiveAdmin-perlen. Import af statistik fra en statistikudbyder er også skrevet i Ruby.

Til system-API-tests brugte vi Python unittest. Memcached bruges til at drosle API-betalingsopkald, "Chef" bruges til at kontrollere konfigurationen, Zabbix bruges til at indsamle og overvåge intern systemstatistik. Graylog2 er til indsamling af logfiler, Slate er API-dokumentation til klienter.

Mikhail Salosin. Golang Meetup. Brug af Go i backend af Look+-applikationen

Protokolvalg

Det første problem, vi stødte på: vi var nødt til at vælge en protokol til interaktion mellem backend og mobile klienter, baseret på følgende punkter...

  • Det vigtigste krav: data om klienter skal opdateres i realtid. Det vil sige, at alle, der lige nu ser udsendelsen, bør modtage opdateringer næsten øjeblikkeligt.
  • For at forenkle tingene antog vi, at data, der er synkroniseret med klienter, ikke slettes, men skjules ved hjælp af specielle flag.
  • Alle mulige sjældne anmodninger (såsom statistik, holdsammensætninger, holdstatistik) opnås ved almindelige GET-anmodninger.
  • Plus, systemet skulle nemt understøtte 100 tusinde brugere på samme tid.

Baseret på dette havde vi to protokolmuligheder:

  1. Websockets. Men vi havde ikke brug for kanaler fra klienten til serveren. Vi behøvede kun at sende opdateringer fra serveren til klienten, så en websocket er en overflødig mulighed.
  2. Server-Sent Events (SSE) kom helt rigtigt op! Det er ret simpelt og opfylder stort set alt, hvad vi har brug for.

Server-Sendte hændelser

Et par ord om, hvordan denne ting fungerer...

Det kører oven på en http-forbindelse. Klienten sender en anmodning, serveren svarer med Content-Type: text/event-stream og lukker ikke forbindelsen med klienten, men fortsætter med at skrive data til forbindelsen:

Mikhail Salosin. Golang Meetup. Brug af Go i backend af Look+-applikationen

Data kan sendes i et format, som er aftalt med kunderne. I vores tilfælde sendte vi det i denne form: navnet på den ændrede struktur (person, spiller) blev sendt til begivenhedsfeltet, og JSON med nye, ændrede felter for spilleren blev sendt til datafeltet.

Lad os nu tale om, hvordan selve interaktionen fungerer.

  • Den første ting, klienten gør, er at bestemme, hvornår synkronisering med tjenesten sidst blev udført: den ser på sin lokale database og bestemmer datoen for den sidste ændring, som den registrerede.
  • Den sender en anmodning med denne dato.
  • Som svar sender vi ham alle de opdateringer, der er sket siden den dato.
  • Derefter opretter den forbindelse til livekanalen og lukker ikke, før den har brug for disse opdateringer:

Mikhail Salosin. Golang Meetup. Brug af Go i backend af Look+-applikationen

Vi sender ham en liste over ændringer: hvis nogen scorer et mål, ændrer vi kampens scoring, hvis han bliver skadet, sendes dette også i realtid. Således modtager klienter øjeblikkeligt opdaterede data i kamphændelsesfeedet. Med jævne mellemrum, så klienten forstår, at serveren ikke er død, at der ikke er sket noget med den, sender vi et tidsstempel hvert 15. sekund - så den ved, at alt er i orden, og der ikke er behov for at oprette forbindelse igen.

Hvordan betjenes liveforbindelsen?

  • Først og fremmest opretter vi en kanal, hvor bufferede opdateringer vil blive modtaget.
  • Derefter abonnerer vi på denne kanal for at modtage opdateringer.
  • Vi indstiller den korrekte header, så kunden ved, at alt er ok.
  • Send det første ping. Vi registrerer blot det aktuelle forbindelsestidsstempel.
  • Derefter læser vi fra kanalen i en loop, indtil opdateringskanalen er lukket. Kanalen modtager periodisk enten det aktuelle tidsstempel eller ændringer, som vi allerede skriver til åbne forbindelser.

Mikhail Salosin. Golang Meetup. Brug af Go i backend af Look+-applikationen

Det første problem, vi stødte på, var følgende: for hver forbindelse, der blev åbnet med klienten, oprettede vi en timer, der tikkede en gang hvert 15. sekund - det viser sig, at hvis vi havde 6 tusinde forbindelser åbne med en maskine (med en API-server), 6 tusind timere blev oprettet. Dette førte til, at maskinen ikke holdt den nødvendige belastning. Problemet var ikke så indlysende for os, men vi fik lidt hjælp og fiksede det.

Som et resultat kommer vores ping nu fra den samme kanal, som opdateringen kommer fra.

Derfor er der kun én timer, der tikker en gang hvert 15. sekund.

Der er flere hjælpefunktioner her - afsendelse af header, ping og selve strukturen. Det vil sige, at navnet på tabellen (person, kamp, ​​sæson) og oplysningerne om denne post sendes her:

Mikhail Salosin. Golang Meetup. Brug af Go i backend af Look+-applikationen

Mekanisme til at sende opdateringer

Nu lidt om, hvor ændringerne kommer fra. Vi har flere folk, redaktører, som ser udsendelsen i realtid. De skaber alle begivenhederne: nogen blev sendt ud, nogen blev såret, en slags erstatning...

Ved hjælp af et CMS kommer data ind i databasen. Herefter giver databasen API-serverne besked om dette ved hjælp af Listen/Notify-mekanismen. API-servere sender allerede disse oplysninger til klienter. Således har vi stort set kun få servere forbundet til databasen, og der er ingen særlig belastning på databasen, fordi klienten ikke interagerer direkte med databasen på nogen måde:

Mikhail Salosin. Golang Meetup. Brug af Go i backend af Look+-applikationen

PostgreSQL: Lyt/Giv besked

Listen/Notify-mekanismen i Postgres giver dig mulighed for at underrette begivenhedsabonnenter om, at en begivenhed er ændret - en eller anden post er blevet oprettet i databasen. For at gøre dette skrev vi en simpel trigger og funktion:

Mikhail Salosin. Golang Meetup. Brug af Go i backend af Look+-applikationen

Når vi indsætter eller ændrer en post, kalder vi notify-funktionen på data_updates-kanalen og videregiver navnet på tabellen og identifikatoren for den post, der blev ændret eller indsat.

For alle tabeller, der skal synkroniseres med klienten, definerer vi en trigger, som efter ændring/opdatering af en post kalder den funktion, der er angivet på sliden nedenfor.
Hvordan abonnerer API'et på disse ændringer?

Der oprettes en Fanout-mekanisme - den sender beskeder til klienten. Den indsamler alle kundekanaler og sender opdateringer, den har modtaget via disse kanaler:

Mikhail Salosin. Golang Meetup. Brug af Go i backend af Look+-applikationen

Her tjekker standard pq-biblioteket, som forbinder til databasen og siger, at det vil lytte til kanalen (data_updates), at forbindelsen er åben og alt er i orden. Jeg udelader fejlkontrol for at spare plads (ikke at kontrollere er farligt).

Dernæst indstiller vi asynkront Ticker, som sender et ping hvert 15. sekund, og begynder at lytte til den kanal, vi abonnerer på. Hvis vi modtager et ping, udgiver vi dette ping. Hvis vi modtager en form for bidrag, så offentliggør vi denne post til alle abonnenter af denne Fanout.

Hvordan fungerer Fan-out?

På russisk oversættes dette som "splitter". Vi har ét objekt, der registrerer abonnenter, der ønsker at modtage nogle opdateringer. Og så snart der kommer en opdatering til dette objekt, distribuerer det denne opdatering til alle dets abonnenter. Simpelt nok:

Mikhail Salosin. Golang Meetup. Brug af Go i backend af Look+-applikationen

Sådan implementeres det i Go:

Mikhail Salosin. Golang Meetup. Brug af Go i backend af Look+-applikationen

Der er en struktur, den er synkroniseret ved hjælp af Mutexes. Den har et felt, der gemmer tilstanden for Fanouts forbindelse til databasen, dvs. den lytter i øjeblikket og vil modtage opdateringer, samt en liste over alle tilgængelige kanaler - kort, hvis nøgle er kanalen og strukturen i form af værdier (det bruges i bund og grund ikke på nogen måde).

To metoder - Connected og Disconnected - giver os mulighed for at fortælle Fanout, at vi har en forbindelse til basen, den har vist sig, og at forbindelsen til basen er brudt. I det andet tilfælde skal du afbryde forbindelsen til alle klienter og fortælle dem, at de ikke længere kan lytte til noget, og at de genopretter forbindelsen, fordi forbindelsen til dem er lukket.

Der er også en Subscribe-metode, der føjer kanalen til "lytterne":

Mikhail Salosin. Golang Meetup. Brug af Go i backend af Look+-applikationen

Der er en Unsubscribe-metode, som fjerner kanalen fra lyttere, hvis klienten afbryder forbindelsen, samt en Publish-metode, som giver dig mulighed for at sende en besked til alle abonnenter.

Spørgsmål: – Hvad transmitteres gennem denne kanal?

FRK: – Den model, der er ændret eller ping, overføres (i det væsentlige kun et tal, heltal).

FRK: – Du kan sende hvad som helst, sende enhver struktur, udgive det – det bliver bare til JSON, og det er det.

FRK: – Vi modtager en notifikation fra Postgres – den indeholder tabelnavn og identifikator. Baseret på tabelnavn og identifikator får vi den post, vi skal bruge, og så sender vi denne struktur til offentliggørelse.

Infrastruktur

Hvordan ser det ud fra et infrastrukturperspektiv? Vi har 7 hardwareservere: en af ​​dem er fuldstændig dedikeret til databasen, de andre seks kører virtuelle maskiner. Der er 6 kopier af API'en: hver virtuel maskine med API'en kører på en separat hardwareserver - dette er for pålideligheden.

Mikhail Salosin. Golang Meetup. Brug af Go i backend af Look+-applikationen

Vi har to frontends med Keepalived installeret for at forbedre tilgængeligheden, så hvis der sker noget, kan den ene frontend erstatte den anden. Desuden – to eksemplarer af CMS.

Der er også en statistikimportør. Der er en DB-slave, hvorfra der jævnligt tages backup. Der er Pigeon Pusher, en applikation, der sender push-meddelelser til klienter, såvel som infrastruktur-ting: Zabbix, Graylog2 og Chef.

Faktisk er denne infrastruktur overflødig, fordi 100 tusind kan betjenes med færre servere. Men der var jern - vi brugte det (vi fik at vide, at det var muligt - hvorfor ikke).

Fordele ved Go

Efter at vi arbejdede på denne applikation, dukkede så åbenlyse fordele ved Go op.

  • Fedt http-bibliotek. Med den kan du skabe ret meget ud af boksen.
  • Plus kanaler, der gjorde det muligt for os meget nemt at implementere en mekanisme til at sende meddelelser til kunder.
  • Den vidunderlige ting Race-detektor tillod os at eliminere flere kritiske fejl (iscenesættelsesinfrastruktur). Alt, hvad der virker på iscenesættelse, lanceres, kompileret med Race-nøglen; og vi kan derfor se på iscenesættelsesinfrastrukturen for at se, hvilke potentielle problemer vi har.
  • Minimalisme og enkelthed i sproget.

Mikhail Salosin. Golang Meetup. Brug af Go i backend af Look+-applikationen

Vi søger udviklere! Hvis nogen vil, tak.

R'RѕRїSЂRѕSЃS <

Spørgsmål fra salen (herefter – B): – Det forekommer mig, at du gik glip af en vigtig pointe vedrørende Fan-out. Har jeg ret i at forstå, at når man sender et svar til en klient, blokerer man, hvis klienten ikke vil læse?

FRK: - Nej, vi blokerer ikke. For det første har vi alt dette bag nginx, det vil sige, der er ingen problemer med langsomme klienter. For det andet har klienten en kanal med en buffer - faktisk kan vi lægge op til hundrede opdateringer der... Hvis vi ikke kan skrive til kanalen, så sletter den den. Hvis vi ser, at kanalen er blokeret, lukker vi simpelthen kanalen, og det er det - klienten vil genoprette forbindelsen, hvis der opstår et problem. Derfor er der i princippet ingen blokering her.

I: – Kunne det ikke være muligt med det samme at sende en post til Listen/Notify, og ikke en identifikationstabel?

FRK: – Listen/Notify har en grænse på 8 tusinde bytes på den forudindlæsning, den sender. I princippet ville det være muligt at sende, hvis vi havde at gøre med en lille mængde data, men det forekommer mig, at denne måde [måden vi gør det på] simpelthen er mere pålidelig. Begrænsningerne er i Postgres selv.

I: – Modtager kunder opdateringer om kampe, som de ikke er interesserede i?

FRK: - Generelt, ja. Som regel er der 2-3 kampe i gang sideløbende, og endda ret sjældent. Hvis en klient ser noget, så ser han normalt den kamp, ​​der foregår. Derefter har klienten en lokal database, hvori alle disse opdateringer er tilføjet, og selv uden internetforbindelse kan klienten se alle tidligere kampe, som han har opdateringer til. Grundlæggende synkroniserer vi vores database på serveren med klientens lokale database, så han kan arbejde offline.

I: – Hvorfor lavede du din egen ORM?

Alexey (en af ​​udviklerne af Look+): – Dengang (det var et år siden) var der færre ORM'er end nu, hvor der er ret mange af dem. Min yndlingsting ved de fleste ORM'er derude er, at de fleste af dem kører på tomme grænseflader. Det vil sige, at metoderne i disse ORM'er er klar til at påtage sig hvad som helst: en struktur, en strukturpointer, et tal, noget helt irrelevant...

Vores ORM genererer strukturer baseret på datamodellen. Mig selv. Og derfor er alle metoder konkrete, bruger ikke refleksion osv. De accepterer strukturer og forventer at bruge de strukturer der kommer.

I: – Hvor mange deltog?

FRK: – I den indledende fase deltog to personer. Vi startede et sted i juni, og i august var hoveddelen klar (den første version). Der var en udgivelse i september.

I: – Hvor du beskriver SSE, bruger du ikke timeout. Hvorfor det?

FRK: – For at være ærlig er SSE stadig en html5-protokol: SSE-standarden er designet til at kommunikere med browsere, så vidt jeg forstår. Den har yderligere funktioner, så browsere kan oprette forbindelse igen (og så videre), men vi har ikke brug for dem, fordi vi havde klienter, der kunne implementere enhver logik til at forbinde og modtage information. Vi lavede ikke SSE, men snarere noget der ligner SSE. Dette er ikke selve protokollen.
Der var ikke behov. Så vidt jeg forstår, implementerede klienter forbindelsesmekanismen næsten fra bunden. De var ligeglade.

I: – Hvilke yderligere hjælpeprogrammer brugte du?

FRK: – Vi brugte mest aktivt govet og golint til at gøre stilen forenet, såvel som gofmt. Intet andet blev brugt.

I: – Hvad brugte du til at fejlfinde?

FRK: – Debugging blev stort set udført ved hjælp af tests. Vi brugte ikke nogen debugger eller GOP.

I: – Kan du returnere det slide, hvor publiceringsfunktionen er implementeret? Forvirrer variabelnavne med et bogstav dig?

FRK: - Nej. De har et ret "snævert" synlighedsområde. De bruges ikke andre steder undtagen her (bortset fra det interne i denne klasse), og det er meget kompakt - det tager kun 7 linjer.

I: - På en eller anden måde er det stadig ikke intuitivt...

FRK: - Nej, nej, det er en rigtig kode! Det handler ikke om stil. Det er bare sådan en utilitaristisk, meget lille klasse - kun 3 felter inde i klassen...

Mikhail Salosin. Golang Meetup. Brug af Go i backend af Look+-applikationen

FRK: – I det store og hele ændres alle data, der er synkroniseret med klienter (sæsonkampe, spillere), ikke. Groft sagt, hvis vi laver en anden sport, hvor vi skal ændre kampen, vil vi simpelthen tage højde for alt i den nye version af klienten, og de gamle versioner af klienten vil blive bandlyst.

I: – Er der nogen tredjeparts afhængighedsstyringspakker?

FRK: – Vi brugte go dep.

I: - Der var noget om video i rapportens emne, men der var intet i rapporten om video.

FRK: - Nej, jeg har ikke noget i emnet om videoen. Det hedder "Look+" - det er navnet på applikationen.

I: – Du sagde, at det streames til kunder?

FRK: - Vi var ikke involveret i streaming af video. Dette blev udelukkende gjort af Megafon. Ja, jeg sagde ikke, at applikationen var MegaFon.

FRK: – Go – til at sende alle data – om scoringen, på kampbegivenheder, statistik... Go er hele backend for applikationen. Klienten skal vide fra et sted, hvilket link der skal bruges til spilleren, så brugeren kan se kampen. Vi har links til videoer og streams, der er udarbejdet.

Nogle annoncer 🙂

Tak fordi du blev hos os. Kan du lide vores artikler? Vil du se mere interessant indhold? Støt os ved at afgive en ordre eller anbefale til venner, cloud VPS for udviklere fra $4.99, en unik analog af entry-level servere, som blev opfundet af os til dig: Hele sandheden om VPS (KVM) E5-2697 v3 (6 Cores) 10GB DDR4 480GB SSD 1Gbps fra $19 eller hvordan deler man en server? (tilgængelig med RAID1 og RAID10, op til 24 kerner og op til 40 GB DDR4).

Dell R730xd 2 gange billigere i Equinix Tier IV datacenter i Amsterdam? Kun her 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV fra $199 i Holland! Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - fra $99! Læse om Hvordan man bygger infrastruktur corp. klasse med brug af Dell R730xd E5-2650 v4-servere til en værdi af 9000 euro for en krone?

Kilde: www.habr.com

Tilføj en kommentar