Міхаіл Салосін. Golang Meetup. Выкарыстанне Go у бэкендзе прыкладання «Глядзі+»

Міхаіл Салосін (далей - МС): - Ўсім прывітанне! Мяне клічуць Міхась. Я працую бэкенд-распрацоўшчыкам у кампаніі MC2 Software, і я распавяду аб выкарыстанні Go ў бэкендзе мабільнага прыкладання «Глядзі +».

Міхаіл Салосін. Golang Meetup. Выкарыстанне Go у бэкендзе прыкладання «Глядзі+»

Хто-небудзь з прысутных любіць хакей?

Міхаіл Салосін. Golang Meetup. Выкарыстанне Go у бэкендзе прыкладання «Глядзі+»

Тады гэта дадатак для вас. Яно для «Андроіда» і iOS, служыць для прагляду трансляцый розных спартовых падзей у анлайне і ў запісе. Таксама ў дадатку ёсць розная статыстыка, тэкставыя трансляцыі, табліцы па канферэнцыях, па турнірах і іншая інфармацыя, карысная для балельшчыкаў.

Міхаіл Салосін. Golang Meetup. Выкарыстанне Go у бэкендзе прыкладання «Глядзі+»

Таксама ў дадатку ёсць такая штука, як відэамоманты, г. зн. можна паглядзець вострыя моманты матчаў (галы, бойкі, буліты і інш.). Калі вам не жадаецца глядзець усю трансляцыю, можна паглядзець толькі самае цікавае.

Што выкарыстоўвалі ў распрацоўцы?

Асноўная частка была напісана на Go. Той API, з якім размаўлялі мабільныя кліенты, быў напісаны на Go. Таксама на Go быў напісаны сэрвіс для адпраўкі push-паведамленняў на мабільныя. Яшчэ нам прыйшлося напісаць сваё ORM, пра які мы, магчыма, калі-небудзь раскажам. Ну і напісаны на Go сякія-такія дробныя сэрвісы: рэсайзінг і загрузка малюнкаў для боку рэдактараў…

У якасці базы дадзеных мы выкарыстоўвалі "Постгрэс" (PostgreSQL). Інтэрфейс для рэдактараў быў напісаны на Ruby on Rails з дапамогай гема (gem) ActiveAdmin. На "Рубі" напісаны і імпарт статыстыкі з пастаўшчыка статыстыкі.

Для сістэмных тэстаў API мы выкарыстоўвалі unittest "Пітона" (Python). Memcached выкарыстоўваецца для тротлінга зваротаў API-аплаты, "Шэф" (Chef) - для кантролю канфігурацыі, Zabbix - для збору і маніторынгу ўнутраных статыстычных дадзеных сістэмы. Graylog2 - для збору логаў, Slate - гэта дакументацыя API для кліентаў.

Міхаіл Салосін. Golang Meetup. Выкарыстанне Go у бэкендзе прыкладання «Глядзі+»

Выбар пратакола

Першая праблема, з якой мы сутыкнуліся: нам трэба было абраць пратакол узаемадзеяння бэкенда з мабільнымі кліентамі, зыходзячы з наступных пунктаў…

  • Самае галоўнае патрабаванне: дадзеныя на кліентах павінны абнаўляцца ў рэальным часе. Гэта значыць, усе, хто ў дадзены момант глядзіць трансляцыю, павінны атрымліваць абнаўлення практычна імгненна.
  • Для спрашчэння мы прынялі, што дадзеныя, якія сінхранізуюцца з кліентамі, не выдаляюцца, а хаваюцца з дапамогай спецыяльных сцягоў.
  • Усякія рэдкія запыты (накшталт статыстыкі, складаў каманд, статыстыкі каманд) атрымліваюцца звычайнымі GET-запытамі.
  • Плюс, сістэма павінна была спакойна вытрымаць 100 тысяч карыстачоў адначасова.

Зыходзячы з гэтага, мы мелі два варыянты пратаколу:

  1. Websocket'ы. Але нам не патрэбны былі каналы ад кліента да сервера. Нам трэба было толькі адпраўляць абнаўленні з сервера на кліент, таму вэб-сокет - залішні варыянт.
  2. Server-Sent Events (SSE) падышоў у самы раз! Ён дастаткова просты і задавальняе ў прынцыпе ўсяму, што нам трэба.

Падзеі, адпраўленыя серверам

Пару слоў аб тым, як працуе гэтая штука…

Яна працуе па-над http-злучэнні. Кліент адпраўляе запыт, на яго сервер адказвае Content-Type: text/event-stream і не закрывае злучэнне з кліентам, а працягвае пісаць у злучэнне дадзеныя:

Міхаіл Салосін. Golang Meetup. Выкарыстанне Go у бэкендзе прыкладання «Глядзі+»

Дадзеныя можна адпраўляць у фармаце, узгодненым з кліентамі. У нашым выпадку мы адпраўлялі ў такім выглядзе: у поле event адпраўлялася назоў якая змянілася структуры (чалавек, гулец), а ў поле data – JSON з новымі, змененымі палямі для гульца.

Цяпер аб тым, як працуе само ўзаемадзеянне.

  • Перш за ўсё кліент вызначае, калі апошні раз выраблялася сінхранізацыя з сэрвісам: ён глядзіць у сваю лакальную БД і вызначае дату апошняй змены, запісанай у яго.
  • Ён дасылае запыт з гэтай датай.
  • У адказ мы дасылаем яму ўсе абнаўленні, якія адбыліся з гэтай датай.
  • Пасля гэтага ён вырабляе злучэнне з live-каналам і не зачыняе датуль, пакуль яму патрэбныя гэтыя абнаўленні:

Міхаіл Салосін. Golang Meetup. Выкарыстанне Go у бэкендзе прыкладання «Глядзі+»

Мы шлем яму спіс змен: калі хтосьці забіў гол - змяняем лік матчу, атрымаў траўму - таксама адпраўляецца ў рэальным часе. Такім чынам, у стужцы падзей матча кліенты маментальна атрымліваюць актуальныя дадзеныя. Перыядычна, каб кліент разумеў, што сервер не памёр, што з ім нічога не здарылася, мы адпраўляем раз у 15 секунд timestamp - каб ён ведаў, што ўсё ў парадку і перападключаць не трэба.

Як абслугоўваецца live-злучэнне?

  • У першую чаргу мы ствараем канал, у які будуць прыходзіць абнаўленні з буферам.
  • Пасля гэтага падпісваем гэты канал на атрыманне абнаўленняў.
  • Усталёўваны правільны загаловак, для таго каб кліент ведаў, што ўсё ok.
  • Адпраўляем першы ping. Проста запісваем бягучы timestamp злучэння.
  • Пасля гэтага ў цыкле чытэльны з канала датуль, пакуль канал абнаўленняў не зачынены. У канал перыядычна прыходзіць альбо бягучы timestamp, альбо змены, якія мы ўжо запісваем у адчыненыя злучэнні.

Міхаіл Салосін. Golang Meetup. Выкарыстанне Go у бэкендзе прыкладання «Глядзі+»

Першая праблема, з якой мы сутыкнуліся, складалася ў наступным: на кожнае адчыненае з кліентам злучэнне мы стваралі таймер, які цікаў раз у 15 секунд – атрымліваецца, калі ў нас было адчынена 6 тысяч злучэнняў з адной машынай (з адным API-серверам), стваралася 6 тысяч таймераў. Гэта прыводзіла да таго, што машына не трымала патрэбнай нагрузкі. Праблема была не такой відавочнай для нас, але нам крыху дапамаглі, і мы яе ліквідавалі.

У выніку зараз у нас ping прыходзіць з таго ж канала, з якога прыходзіць update.

Адпаведна, маецца ўсяго адзін таймер, які цікае раз за 15 секунд.

Тут некалькі дапаможных функцый - адпраўка загалоўка, пінгу і самой структуры. Гэта значыць, тут перадаецца назва табліцы (person, match, season) і сама інфармацыя аб гэтым запісе:

Міхаіл Салосін. Golang Meetup. Выкарыстанне Go у бэкендзе прыкладання «Глядзі+»

Механізм адпраўкі абнаўленняў

Цяпер крыху пра тое, адкуль бяруцца змены. У нас ёсьць некалькі чалавек, рэдактараў, якія ў рэальным часе глядзяць трансьляцыю. Яны ствараюць усе падзеі: некага выдалілі, нехта атрымаў траўму, нейкая замена…

З дапамогай CMS дадзеныя пападаюць у базу. Пасля гэтага база з дапамогай механізму Listen / Notify паведамляе аб гэтым API-серверы. API-серверы ўжо рассылаюць гэтую інфармацыю кліентам. Такім чынам, у нас у сутнасці да базы падлучана ўсяго некалькі сервераў і ніякай адмысловай нагрузкі на базу няма, таму што кліент ніякай выявай напроста з базай не ўзаемадзейнічае:

Міхаіл Салосін. Golang Meetup. Выкарыстанне Go у бэкендзе прыкладання «Глядзі+»

PostgreSQL: Listen/Notify

Механізм Listen/Notify у «Постгрэсе» дазваляе апавяшчаць падпісчыкаў на падзеі аб тым, што змянілася нейкая падзея – быў створаны нейкі запіс у базе. Для гэтага мы напісалі просты трыгер і функцыю:

Міхаіл Салосін. Golang Meetup. Выкарыстанне Go у бэкендзе прыкладання «Глядзі+»

Пры insert'е ці змене запісу мы выклікаем функцыю notify на канале data_updates, перадаем туды назоў табліцы і ідэнтыфікатар запісу, якая была зменена або ўстаўлена.

На ўсе табліцы, якія павінны быць сінхранізаваныя з кліентам, мы вызначаем трыгер, які пасля змены / абнаўленні запісу выклікае функцыю, паказаную на слайдзе ўнізе.
Як API падпісваецца на гэтыя змены?

Ствараецца механізм Fanout - ён рассылае паведамленні кліентам. Ён збірае ўсе каналы кліентаў і рассылае абнаўленні, якія ён атрымаў па гэтых каналах:

Міхаіл Салосін. Golang Meetup. Выкарыстанне Go у бэкендзе прыкладання «Глядзі+»

Тут стандартная бібліятэка pq, якая падключаецца да базы і гаворыць аб тым, што хоча слухаць канал (data_updates), правярае, што злучэнне адкрыта і ўсё нармальна. Я апускаю праверку памылак, каб зэканоміць месца (не правяраць багата).

Далей мы асінхронна задаём Ticker, які будзе дасылаць ping раз у 15 секунд, і пачынаем слухаць канал, на які падпісаліся. Калі нам прыйшоў пінг, мы публікуем гэты пінг. Калі нам прыйшоў нейкі запіс, то мы публікуем гэты запіс усім падпісчыкам гэтага Fanout'a.

Як працуе Fan-out?

Па-руску гэта перакладаецца як «разгаліноўшчык». У нас ёсць адзін аб'ект, які рэгіструе падпісчыкаў, якія жадаюць атрымліваць нейкія абнаўленні. І як толькі апдэйт да гэтага аб'екта прыходзіць, ён раскладвае гэта абнаўленне ўсім падпісчыкам, якія ў яго ёсць. Досыць проста:

Міхаіл Салосін. Golang Meetup. Выкарыстанне Go у бэкендзе прыкладання «Глядзі+»

Як гэта рэалізавана на Go:

Міхаіл Салосін. Golang Meetup. Выкарыстанне Go у бэкендзе прыкладання «Глядзі+»

Ёсць структура, яна сінхранізуецца з дапамогай Mutex'аў. У яе ёсць поле, якое захоўвае стан падлучэння Fanout да базы, т. е. у дадзены момант ён слухае і будзе атрымліваць абнаўленні, а таксама спіс усіх наяўных каналаў - map, ключом, якога з'яўляецца канал і struct у выглядзе значэнняў (у сутнасці яно не выкарыстоўваецца ніяк).

Два метаду - Connected і Disconnected - дазваляюць сказаць Fanout'у, што ў нас ёсць злучэнне з базай, яно з'явілася і што злучэнне з базай абарвана. У другім выпадку трэба ўсіх кліентаў адключыць і паведаміць ім, што яны больш не могуць нічога слухаць і каб яны перападключыліся, паколькі злучэнне з імі зачынілася.

Таксама ёсць метад Subscribe, які дадае канал у "слухачы":

Міхаіл Салосін. Golang Meetup. Выкарыстанне Go у бэкендзе прыкладання «Глядзі+»

Ёсць метад Unsubscribe, які прыбірае канал са слухачоў, калі кліент адключыўся, а таксама метад Publish, які дазваляе разаслаць паведамленне ўсім падпісантам.

Пытанне: - Што па гэтым канале перадаецца?

СПАДАРЫНЯ: - Перадаецца мадэль, якая змянілася або пінг (па сутнасці проста лік, integer).

СПАДАРЫНЯ: - Можна што заўгодна, любую структуру перасылаць, апублікаваць - яна проста ператвараецца ў JSON і ўсё.

СПАДАРЫНЯ: – Мы атрымліваем натыфікацыю з «Постгрэсу» – у ёй утрымліваецца назва табліцы і ідэнтыфікатар. Па назве табліцы мы атрымліваем і ідэнтыфікатару мы атрымліваем патрэбны нам запіс, а ўжо гэтую структуру адпраўляем на публікацыю.

Інфраструктура

Як гэта выглядае з гледзішча інфраструктуры? У нас 7 жалезных сервераў: адзін з іх цалкам выдзелены пад базу, на астатніх шасці круцяцца віртуалкі. Ёсць 6 копій API: кожная віртуалка з API круціцца на асобным жалезным серверы - гэта для надзейнасці.

Міхаіл Салосін. Golang Meetup. Выкарыстанне Go у бэкендзе прыкладання «Глядзі+»

У нас ёсць два франтэнда, на якіх усталяваны Keepalived для паляпшэння даступнасці, каб у выпадку чаго адзін frontend мог замяніць іншы. Яшчэ - дзве копіі CMS.

Таксама ёсць імпарцёр статыстыкі. Ёсць DB Slave, з якога перыядычна робяцца бэкапы. Ёсць Pigeon Pusher - тое прыкладанне, якое рассылае пушы кліентам, а таксама інфраструктурная рэчы: Zabbix, Graylog2 і Chef.

Насамрэч гэта інфраструктура залішняя, таму што 100 тысяч можна абслугоўваць і з меншай колькасцю сервераў. Але жалеза было - мы яго выкарыстоўвалі (нам сказалі, што можна - чаму б і не).

Плюсы Go

Пасля таго як мы папрацавалі над гэтым дадаткам, выявіліся такія відавочныя плюсы Go.

  • Класная http-бібліятэка. З дапамогай яе можна дастаткова шмат стварыць ужо «са скрынкі».
  • Плюс, каналы, якія дазволілі нам вельмі лёгка рэалізаваць механізм адпраўкі апавяшчэнняў кліентам.
  • Выдатная штука Race detector дазволіла нам ухіліць некалькі крытычных багаў (staging-авая інфраструктура). Усё, што працуе на staging'е запушчана, скампілявана з ключом Race; і мы, адпаведна, можам на staging-най інфраструктуры паглядзець, якія ў нас ёсць патэнцыйныя праблемы.
  • Мінімалізм і прастата мовы.

Міхаіл Салосін. Golang Meetup. Выкарыстанне Go у бэкендзе прыкладання «Глядзі+»

Мы шукаем распрацоўшчыкаў! Калі хтосьці хоча - калі ласка.

пытанні

Пытанне з аўдыторыі (далей - У): - Мне здаецца, вы прапусцілі адзін важны момант адносна Fan-out. Я правільна разумею, што калі вы адпраўляеце кліенту адказ, вы блакуецеся, калі кліент не захоча чытаць?

СПАДАРЫНЯ: - Не, мы не блакуемся. Па-першае, у нас гэта ўсё знаходзіцца за nginx'ом, гэта значыць з павольнымі кліентамі ніякіх праблем няма. Па-другое, у кліента канал з буферам - па сутнасці мы можам пакласці туды да ста апдэйтаў… Калі мы не можам запісаць у канал, то ён яго выдаляе. Калі мы бачым, што канал заблакаваўся, то мы проста закрыем канал, і ўсё - кліент перападключыцца, калі ўзнікне нейкая праблема. Таму тут у прынцыпе блакіроўкі не ўзнікае.

У: - Ці нельга было адразу адпраўляць у Listen/Notify запіс, а не табліцу-ідэнтыфікатар?

СПАДАРЫНЯ: - У Listen / Notify ёсць абмежаванне ў 8 тысяч байт на preload, які ён адпраўляе. У прынцыпе можна было б адпраўляць, калі б мы мелі справу з малой колькасцю дадзеных, але мне здаецца, што так [як робім мы] проста надзейней. Абмежаванні - у самім "Постгрэсе".

У: - Ці атрымліваюць кліенты абнаўлення па матчах, якія іх не цікавяць?

СПАДАРЫНЯ: - Увогуле, так. Як правіла, там ідзе 2-3 матчы паралельна, і тое дастаткова рэдка. Калі кліент нешта глядзіць, то звычайна ён глядзіць той матч, які ідзе. Потым, на кліенце ёсць лакальная база, у якую ўсе гэтыя абнаўленні складаюцца, і нават без падлучэння да інтэрнэту кліент можа паглядзець усе мінулыя матч, па якіх у яго ёсць апдэйты. Па сутнасці мы сваю базу на серверы сінхранізуем з лакальнай базай кліента, каб ён мог працаваць і ў афлайне.

У: - Чаму вы зрабілі сваю ORM?

Аляксей (адзін з распрацоўшчыкаў «Глядзі+»): - На той момант (гэта было год таму) ORM было менш, чым цяпер, калі іх даволі шмат. З большасці існуючых ORM мне больш за ўсё не падабаецца тое, што большасць з іх працуе на пустых інтэрфейсах. Гэта значыць метады, якія ў гэтых ORM, гатовыя прыняць на сябе ўсё што заўгодна: структуру, паказальнік структуры, колькасць, што нешта наогул не адносіцца да справы…

Наш ORM генеруе структуры на аснове мадэлі даных. Сам. І таму ўсе метады канкрэтныя, не выкарыстоўваюць рэфлексію і т. д. Яны прымаюць структуры і чакаюць выкарыстоўваць тыя структуры, якія прыйдуць.

У: - Колькі чалавек удзельнічала?

СПАДАРЫНЯ: - На пачатковым этапе ўдзельнічалі два чалавекі. Недзе ў чэрвені мы пачалі, у жніўні асноўная частка была гатова (першая версія). У верасні быў рэліз.

У: - Там, дзе вы апісваеце SSE, вы не карыстаецеся timeout. Чаму так?

СПАДАРЫНЯ: - Калі казаць шчыра, то SSE - гэта ўсёткі html5-пратакол: стандарт SSE прызначаны для зносін з браўзэрамі, наколькі я разумею. У яго ёсць дадатковыя фічы, каб браўзэры маглі перападключаць (і іншае), але яны нам не патрэбныя, таму што ў нас былі кліенты, якія маглі рэалізаваць любую логіку падключэння і атрымання інфармацыі. Мы зрабілі хутчэй не SSE, а нешта падобнае да SSE. Гэта не сам пратакол.
Не было неабходнасці. Наколькі я разумею, кліенты рэалізоўвалі механізм падключэння практычна з нуля. Ім было ў прынцыпе ўсё роўна.

У: - Якія дадатковыя ўтыліты вы выкарыстоўвалі?

СПАДАРЫНЯ: - Найбольш актыўна мы выкарыстоўвалі govet і golint, каб стыль быў адзіным, а таксама gofmt. Больш нічога не выкарыстоўвалі.

У: - З дапамогай чаго выраблялі адладку?

СПАДАРЫНЯ: - Адладка па вялікім рахунку ішла з дапамогай тэстаў. Ніякага дэбагера, GOP мы не выкарыстоўвалі.

У: - Можаце вярнуць слайд, дзе рэалізавана функцыя Publish? Адналітарныя назвы зменных вас не бянтэжаць?

СПАДАРЫНЯ: - Не. У іх дастаткова «вузкая» вобласць бачнасці. Яны нідзе, акрамя як тут, больш не выкарыстоўваюцца (акрамя вантроб гэтага класа), і ён вельмі кампактны - займае ўсяго 7 радкоў.

У: – Неяк усё роўна не інтуітыўна…

СПАДАРЫНЯ: - Не-не, гэта сапраўдны код! Справа не ў стылі. Проста гэта такі ўтылітарны, зусім маленькі клас - усяго 3 палі ўнутры класа…

Міхаіл Салосін. Golang Meetup. Выкарыстанне Go у бэкендзе прыкладання «Глядзі+»

СПАДАРЫНЯ: - Па вялікім рахунку, усе тыя дадзеныя, якія сінхранізуюцца з кліентамі (сезонныя матчы, гульцы), не змяняюцца. Грубіянска кажучы, калі мы будзем рабіць яшчэ нейкі від спорту, у якім трэба будзе змяняць матч, мы проста ў новай версіі кліента ўсё ўлічым, а старыя версіі кліента будуць забанены.

У: - Ці ёсць нейкія іншыя пакеты для кіравання залежнасцямі?

СПАДАРЫНЯ: – Мы выкарыстоўвалі go dep.

У: – У тэме даклада нешта было пра відэа, а ў дакладзе пра відэа няма.

СПАДАРЫНЯ: - Не, у мяне ў тэме пра відэа нічога няма. Завецца «Глядзі+» - гэта так дадатак называецца.

У: – Вы казалі, што стрымаецца на кліенты?..

СПАДАРЫНЯ: - Стрымінгавым відэа мы не займаліся. Гэта цалкам рабіў "Мегафон". Так, я не сказаў, што дадатак мегафонаўскі.

СПАДАРЫНЯ: - Go - для рассылання ўсіх дадзеных - па рахунку, па падзеях матчу, статыстыцы ... Go - гэта цалкам бэкенд для прыкладання. Кліент аднекуль павінен даведацца, якую спасылку выкарыстоўваць для плэера, каб карыстач мог паглядзець матч. У нас ёсць спасылкі на відэа і на стрымы, якія падрыхтаваны.

Крыху рэкламы 🙂

Дзякуй, што застаяцеся з намі. Вам падабаюцца нашыя артыкулы? Жадаеце бачыць больш цікавых матэрыялаў? Падтрымайце нас, аформіўшы замову ці парэкамендаваўшы знаёмым, хмарныя VPS для распрацоўшчыкаў ад $4.99, унікальны аналаг entry-level сервераў, які быў прыдуманы намі для Вас: Уся праўда аб VPS (KVM) E5-2697 v3 (6 Cores) 10GB DDR4 480GB SSD 1Gbps ад $19 ці як правільна дзяліць сервер? (даступныя варыянты з RAID1 і RAID10, да 24 ядраў і да 40GB DDR4).

Dell R730xd у 2 разы танней у дата-цэнтры Equinix Tier IV у Амстэрдаме? Толькі ў нас 2 х Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 ТБ ад $199 у Нідэрландах! Dell R420 – 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB – ад $99! Чытайце аб тым Як пабудаваць інфраструктуру корп. класа c ужываннем сервераў Dell R730xd Е5-2650 v4 коштам 9000 еўра за капейкі?

Крыніца: habr.com

Дадаць каментар