Mihail Salosin (dalje – MS): – Pozdrav svima! Zovem se Mihail. Radim kao backend programer u firmi MC2 Software i pričat ću o korištenju Go-a u backendu mobilne aplikacije "Smatri+".

Da li neko od prisutnih voli hokej?

U tom slučaju, ova aplikacija je za vas. Dostupna je za Android i iOS i služi za praćenje prenosa različitih sportskih događaja uživo i u snimku. Takođe, aplikacija sadrži razne statistike, tekstualne prenose, tabele konferencija, turnira i druge informacije korisne za navijače.

Takođe, aplikacija nudi mogućnost gledanja video trenutaka, tj. možete videti ključne trenutke mečeva (golove, tuče, penale itd.). Ako ne želite da gledate ceo prenos, možete pogledati samo najzanimljivije delove.
Šta smo koristili u razvoju?
Veći deo je napisan na Go-u. Taj API, s kojim su komunicirali mobilni klijenti, je takođe razvijen na Go-u. Takođe, na Go-u je razvijena usluga za slanje push obaveštenja na mobilne uređaje. Morali smo napisati i naše ORM, o kojem ćemo možda jednog dana pričati. Na Go-u su napisane i neke manje usluge: resize i upload slika za strane urednika...
Kao bazu podataka koristili smo PostgreSQL. Interfejs za urednike je razvijen u Ruby on Rails uz pomoć gem-a (gem) ActiveAdmin. U Ruby-u je napisana i funkcionalnost za uvoz statistike od dobavljača podataka.
Za sistemske testove API-a koristili smo unittest Python-a. Memcached se koristi za throttling zahteva za API plaćanja, Chef se koristi za kontrolu konfiguracije, Zabbix za sakupljanje i praćenje unutrašnjih statističkih podataka sistema. Graylog2 se koristi za sakupljanje logova, Slate je dokumentacija API-a za klijente.

Izbor protokola
Prvi problem s kojim smo se susreli: morali smo izabrati protokol za interakciju backend-a sa mobilnim klijentima, imajući u vidu sledeće tačke...
- Najvažniji zahtev: podaci na klijentima moraju se ažurirati u realnom vremenu. To znači da svi koji u tom trenutku gledaju prenos moraju primati ažuriranja gotovo odmah.
- Radi pojednostavljenja, pretpostavili smo da se podaci koji se sinhronizuju s klijentima ne brišu, već skrivaju pomoću posebnih oznaka.
- Različne redke poizvedbe (kot so statistika, sestava ekip, statistika ekip) se izvajajo s pomočjo običajnih GET poizvedb.
- Poleg tega je sistem moral brez težav zdržati 100 tisoč uporabnikov hkrati.
Na podlagi tega smo imeli dve možnosti protokola:
- Websocketi. A kanali od odjemalca do strežnika niso bili potrebni. Potrebovali smo le pošiljanje posodobitev s strežnika na odjemalca, zato je bil websocket odvečna izbira.
- Dogodki, posredovani s strežnika (SSE), so bili idealni! Enostaven je in v bistvu izpolnjuje vse naše potrebe.
Dogodki, posredovani s strežnika
Besedica o tem, kako ta zadeva deluje…
Deluje preko http povezave. Odjemalec pošlje poizvedbo, strežnik odgovori z Content-Type: text/event-stream in ne zaključi povezave z odjemalcem, temveč nadaljuje z zapisovanjem podatkov v povezavo:

Podatke je mogoče pošiljati v formatu, dogovorjenem z odjemalci. V našem primeru smo pošiljali na tak način: v polju event se je poslala oznaka spremenjene strukture (oseba, igralec), v polju data pa JSON z novimi, spremenjenimi polji za igralca.
Sedaj o tem, kako poteka samo interakcijo.
- Najprej odjemalec ugotovi, kdaj je bila zadnjič izvedena sinhronizacija s storitvijo: pogleda v svojo lokalno bazo podatkov in ugotovi datum zadnje spremembe, zapisane pri njem.
- Pošlje poizvedbo s tem datumom.
- V odgovor mu pošljemo vse posodobitve, ki so se zgodile od tega datuma.
- Potem vzpostavi povezavo z live-kanalom in je ne zapre, dokler potrebuje te posodobitve:

Pošiljamo mu seznam sprememb: če nekdo doseže gol – se spremeni rezultat tekme, če se poškoduje – se to prav tako pošlje v realnem času. Tako odjemalci v dogodkovni vrstici tekme takoj prejmejo aktualne podatke. Občasno, da odjemalec razume, da strežnik ni umrl, da se nič ne dogaja, mu vsakih 15 sekund pošljemo timestamp – da ve, da je vse v redu in da se ni potrebno ponovno povezati.
Kako se upravlja live-povezava?
- Najprej ustvarimo kanal, v katerega bodo prihajale posodobitve z bufferjem.
- Potem se naročimo na ta kanal za prejemanje posodobitev.
- Nastavimo pravi glavi, da odjemalec ve, da je vse v redu.
- Pošljemo prvi ping. Preprosto zabeležimo trenutni timestamp povezave.
- Nato beremo iz kanala v zanki, dokler kanal za posodobitve ni zaprt. Kanal občasno prejme bodisi trenutni časovni žig bodisi spremembe, ki jih že zapisujemo v odprte povezave.

Prva težava, s katero smo se soočili, je bila naslednja: za vsako odprto povezavo s stranko smo ustvarili časovnik, ki je
Kot rezultat zdaj ping prihaja iz istega kanala, iz katerega prihaja posodobitev.
Tako imamo samo en časovnik, ki tiktaka vsaka 15 sekund.
Tukaj je nekaj pomožnih funkcij – pošiljanje glave, pinga in same strukture. To pomeni, da se tukaj posreduje ime tabele (oseba, tekma, sezona) in sami podatki o tej vnosu:

Mehanizem pošiljanja posodobitev
Zdaj pa malo o tem, kje prihajajo spremembe. Imamo nekaj ljudi, urednikov, ki v realnem času spremljajo prenos. Ustvarjajo vse dogodke: nekoga so odstranili, nekdo je poškodovan, kakšna menjava…
S pomočjo CMS podatki pridejo v bazo. Po tem baza z mehanizmom Listen/Notify obvešča API-strežnike. API-strežniki nato pošiljajo te informacije strankam. Tako imamo v bistvu le nekaj strežnikov, povezanih z bazo, in nobene posebne obremenitve na bazo, ker stranka na noben način ne komunicira neposredno z bazo:

PostgreSQL: Listen/Notify
Mehanizem Listen/Notify v 'Postgresu' omogoča obveščanje naročnikov o dogodkih, da so se spremenili določeni dogodki – bila je ustvarjena določena vnos v bazi. Za to smo napisali preprost sprožilec in funkcijo:

Ob vstavljanju ali spreminjanju vnosa kličemo funkcijo notify na kanalu data_updates in vanjo posredujemo ime tabele in identifikator vnosa, ki je bil spremenjen ali vstavljen.
Na vse tabele, ki jih je treba uskladiti s stranko, določimo sprožilec, ki po spremembi / posodobitvi vnosa kliče funkcijo, navedeno na sprednji strani.
Kako se API prijavi na te spremembe?
Создается механизм Fanout – он рассылает сообщения клиентам. Он собирает все каналы клиентов и рассылает обновления, которые он получил по этим каналам:

Здесь стандартная библиотека pq, которая подключается к базе и говорит о том, что хочет слушать канал (data_updates), проверяет, что соединение открыто и всё нормально. Я опускаю проверку ошибок, чтобы сэкономить место (не проверять чревато).
Далее мы асинхронно задаем Ticker, который будет отправлять ping раз в 15 секунд, и начинаем слушать канал, на который подписались. Если нам пришел пинг, мы публикуем этот пинг. Если нам пришла какая-то запись, то мы публикуем эту запись всем подписчикам этого Fanout’a.
Как работает Fan-out?
По-русски это можно перевести как «разветвитель». У нас есть один объект, который регистрирует подписчиков, желающих получать какие-то обновления. И как только апдейт к этому объекту приходит, он рассылает это обновление всем имеющимся у него подписчикам. Достаточно просто:

Как это реализовано на Go:

Есть структура, она синхронизируется с помощью Mutex’ов. У неё есть поле, которое сохраняет состояние подключения Fanout к базе, т. е. в данный момент он слушает и будет получать обновления, а также список всех имеющихся каналов – map, ключом которого является канал и struct в виде значений (по сути оно не используется никак).
Два метода – Connected и Disconnected – позволяют сказать Fanout’у, что у нас есть соединение с базой, оно появилось и что соединение с базой оборвано. Во втором случае нужно всех клиентов отключить и сообщить им, что они больше не могут ничего слушать и чтобы они переподключились, поскольку соединение с ними закрылось.
Также есть метод Subscribe, который добавляет канал в «слушатели»:

Есть метод Unsubscribe, который убирает канал из слушающих, если клиент отключился, а также метод Publish, позволяющий разослать сообщение всем подписчикам.
Pitanje: – Что по этому каналу передаётся?
МС: – Передаётся модель, которая изменилась или пинг (по сути просто число, integer).
МС: – Можно что угодно, любую структуру пересылать, опубликовать – она просто превращается в JSON и всё.
МС: – Prejemamo obvestilo iz „Postgresa“ – vsebuje ime tabele in identifikator. Na podlagi imena tabele in identifikatorja pridobimo želeno zapis, to strukturo pa nato pošljemo na objavo.
Infrastruktura
Kako to izgleda z vidika infrastrukture? Imamo 7 fizičnih strežnikov: eden je popolnoma namenjen bazi, ostalih šest pa poganjajo virtualne naprave. Imamo 6 kopij API: vsaka virtualka z API deluje na ločenem fizičnem strežniku – to je za zanesljivost.

Imamo dva frontend-a, na katerih je nameščen Keepalived za izboljšanje dostopnosti, da bi v primeru težav lahko en frontend nadomestil drugega. Poleg tega imamo še dve kopiji CMS.
Prav tako imamo uvoznik statistik. Obstaja DB Slave, s katerega se redno izvajajo varnostne kopije. Obstaja Pigeon Pusher – aplikacija, ki pošilja obvestila strankam, ter infrastrukturne stvari: Zabbix, Graylog2 in Chef.
V resnici je ta infrastruktura odvečna, saj lahko 100 tisoč obdelujemo tudi z manjšim številom strežnikov. A železo je bilo – uporabili smo ga (rekli so nam, da se da – zakaj pa ne).
Prednosti Go
Po tem, ko smo delali na tej aplikaciji, so se pokazale očitne prednosti Go.
- Odlična http-knjižnica. Z njeno pomočjo lahko hitro ustvarimo precej stvari že „iz škatle“.
- Poleg tega so kanali omogočili enostavno implementacijo mehanizma za pošiljanje obvestil strankam.
- Čudovita zadeva Race detector nam je omogočila, da smo odpravili več kritičnih napak (staging infrastruktura). Vse, kar deluje na staging-u – je zagnano, kompilirano s ključem Race; in lahko na staging infrastrukturi vidimo, katere potencialne težave imamo.
- Minimalizem in preprostost jezika.

Iščemo razvijalce! Kdor koli želi – prosimo.
Vprašanja
Vprašanje iz občinstva (naprej – V): – Zdi se mi, da ste spustili en pomemben trenutek glede Fan-out. Pravilno razumem, da se pri pošiljanju odgovora stranki blokirate, če stranka noče brati?
МС: – Ne, ne blokiramo se. Prvo, sve to imamo iza nginx-a, tako da nemamo problema sa sporim klijentima. Drugo, klijent ima kanal s međuspremnikom – zapravo možemo staviti do stotinu ažuriranja... Ako ne možemo zapisati u kanal, on ga briše. Ako vidimo da je kanal blokiran, jednostavno ćemo zatvoriti kanal, i to je to – klijent će se ponovno povezati ako se pojavi neki problem. Zato ovdje praktički ne dolazi do blokada.
V: – Zar nije bilo bolje odmah poslati zapis na Listen/Notify umjesto identifikacijske tablice?
МС: – Listen/Notify ima ograničenje od 8000 bajtova na preload koji šalje. U načelu bi se moglo slati, da se radi o maloj količini podataka, ali mislim da je ovako [kako radimo mi] jednostavno pouzdanije. Ograničenja su u samom "Postgresu".
V: – Da li klijenti dobivaju ažuriranja o utakmicama koje ih ne zanimaju?
МС: – Uglavnom, da. Obično se odvijaju 2-3 utakmice paralelno, i to prilično rijetko. Ako klijent gleda nešto, obično gleda onu utakmicu koja se odvija. Zatim, na klijentu postoji lokalna baza u koju se spremaju sva ta ažuriranja, i čak i bez internetske konekcije klijent može pogledati sve prošle utakmice za koje ima ažuriranja. U suštini, svoju bazu na serveru sinhroniziramo s lokalnom bazom klijenta, kako bi mogao raditi i offline.
V: – Zašto ste napravili svoju ORM?
Aleksiej (jedan od developera „Smotri+“): – U to vrijeme (prije godinu dana) bilo je manje ORM-a nego danas, kada ih je prilično mnogo. Od većine postojećih ORM-a, najviše mi se ne sviđa to što većina njih radi na praznim sučeljima. To jest, metode u tim ORM-ima su spremne primiti apsolutno sve: strukturu, pokazivač na strukturu, broj, bilo što što se ne odnosi na stvar...
Naš ORM generira strukture na osnovu modela podataka. Sam. I zato su sve metode konkretne, ne koriste refleksiju itd. Primaju strukture i očekuju da koriste te strukture koje dolaze.
V: – Koliko je ljudi sudjelovalo?
МС: – U početnoj fazi sudjelovale su dvije osobe. Negdje u lipnju smo počeli, u kolovozu je većina bila spremna (prva verzija). U rujnu je bio izdan.
V: – Tamo gdje opisujete SSE, ne koristite timeout. Zašto tako?
МС: – Če govorimo odkrito, je SSE vendarle html5-protokol: standard SSE je namenjen komunikaciji z brskalniki, kolikor razumem. Ima dodatne funkcije, da se brskalniki lahko ponovno povežejo (in še več), vendar jih ne potrebujemo, ker smo imeli stranke, ki so lahko realizirale kakršno koli logiko povezovanja in pridobivanja informacij. Naredili smo bolj SSE ali pa nekaj, kar je podobno SSE. To ni pravi protokol.
Ni bilo potrebe. Kolikor razumem, so stranke mehanizem povezovanja realizirale praktično iz nič. Bilo jim je v bistvu vseeno.
V: – Katere dodatne pripomočke ste uporabili?
МС: – Najbolj aktivno smo uporabljali govet in golint, da je bil slog enoten, ter gofmt. Nič več nismo uporabljali.
V: – S čim ste izvajali odpravljanje napak?
МС: – Odpravljanje napak je potekalo predvsem s testi. Nismo uporabljali nobenega debagerja, GOP.
V: – Lahko pokažete diapozitiv, kjer je implementirana funkcija Publish? Vas ne motijo enobrkna imena spremenljivk?
МС: – Ne. Imajo dovolj 'ozko' območje vidnosti. Ne uporabljajo se nikjer drugje, razen tukaj (poleg notranjosti te klase), in je zelo kompaktna - zavzema le 7 vrstic.
V: – Kljub temu se mi zdi, da ni intuitivno…
МС: – Ne-ne, to je pravi koda! Ni vprašanje sloga. Preprosto je to tako utilitarna, zelo majhna klasa – ima samo 3 polja znotraj klase…

МС: – Na splošno vsi podatki, ki se sinhronizirajo s strankami (sezonske tekme, igralci), se ne spreminjajo. Na grobo, če bomo delali še kakšen drug šport, v katerem bo treba spremeniti tekmo, bomo to upoštevali v novi različici odjemalca, stare različice odjemalca pa bodo blokirane.
V: – Ali obstajajo kakšni zunanji paketi za upravljanje odvisnosti?
МС: – Uporabili smo go dep.
V: – V temi poročila je bilo nekaj o videu, v poročilu o videu pa ni.
МС: – Ne, v mojem dokumentu o videu ni nič. Imenuje se 'Sveti+', tako se imenuje aplikacija.
V: – Govorili ste, da se pretaka na stranke?..
МС: – S pretakanjem videa se nismo ukvarjali. To je popolnoma počel 'Megafon'. Ja, nisem rekel, da je aplikacija megafonska.
МС: – Go – za pošiljanje vseh podatkov – o računu, dogodkih tekme, statistiki… Go je v celoti backend za aplikacijo. Stranka mora nekje izvedeti, katero povezavo uporabiti za predvajalnik, da lahko uporabnik gleda tekmo. Imamo povezave do videov in streamov, ki so pripravljeni.

Malo reklame 🙂
Hvala što ostajete s nama. Da li vam se sviđaju naši članci? Želite li videti više zanimljivih materijala? Podržite nas tako što ćete naručiti ili preporučiti prijateljima, , jedinstveni ekvivalent entry-level servera koji smo stvorili za vas: (options available with RAID1 and RAID10, up to 24 cores and up to 40GB DDR4).
Dell R730xd je dvostruko jeftiniji u Equinix Tier IV data centru u Amsterdamu? Only with us in the Netherlands! Dell R420 — 2x E5-2430 2.2GHz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — from $99! Read about how
Izvor: habr.com
