OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняўМы зноў публікуем расшыфроўку даклада з канферэнцыі Высокая нагрузка++ 2016, якая праходзіла ў падмаскоўным Сколкава 7-8 лістапада мінулага года. Уладзімір Пратасаў расказвае, як пашырыць функцыянальнасць NGINX з дапамогай OpenResty і Lua.

Усім прывітанне, мяне клічуць Уладзімір Протасаў, я працую ў Parallels. Раскажу крыху пра сябе. Тры чвэрці свайго жыцця я займаюся тым, што пішу код. Стаў праграмістам да мозгу касцей у прамым сэнсе: я часам у сне бачу код. Чвэрць жыцця - прамысловая распрацоўка, напісанне кода, які ідзе прама ў прадакшн. Код, якім некаторыя з вас карыстаюцца, але не здагадваюцца пра гэта.

Каб вы разумелі, наколькі ўсё было дрэнна. Калі я быў маленькім джуніёрам, я прыйшоў, і мне выдалі такія двухтэрабайтныя базы. Гэта зараз тут ва ўсіх highload. Я хадзіў на канферэнцыі, пытаўся: «Хлопцы, раскажыце, у вас big data, усё крута? Колькі ў вас там базы? Мне адказвалі: "У нас 100 гігабайт!" Я казаў: "Крута, 100 гігабайт!" А пра сябе думаў, як бы акуратна захаваць покерфейс. Думаеш, так, хлопцы стромкія, а потым вяртаешся і калупаешся з гэтымі шматтэрабайтнымі базамі. І гэта – будучы джуніёрам. Уяўляеце сабе, які гэта ўдар?

Я ведаю больш за 20 моў праграмавання. Гэта тое, у чым мне прыйшлося разабрацца ў працэсе працы. Табе выдаюць код на Erlang, на C, на З++, на Lua, на Python, на Ruby, на нечым яшчэ, і табе трэба гэта ўсё пілаваць. Увогуле прыйшлося. Дакладную колькасць палічыць так і не ўдалося, але недзе на 20 чысло згубілася.

Паколькі ўсе прысутныя ведаюць, што такое Parallels, і чым мы займаемся, казаць пра тое, якія мы крутыя і што які робіцца, не буду. Раскажу толькі, што ў нас 13 офісаў па свеце, больш за 300 супрацоўнікаў, распрацоўка ў Маскве, Таліне і на Мальце. Пры жаданні можна ўзяць і пераехаць на Мальту, калі ўзімку холадна і трэба пагрэць спінку.

Канкрэтна наш аддзел піша на Python 2. Мы займаемся бізнэсам і нам некалі ўкараняць модныя тэхналогіі, таму мы пакутуем. У нас Django, таму што ў ёй усё ёсць, а лішняе мы ўзялі і выкінулі. Таксама MySQL, Redis і NGINX. Яшчэ ў нас - шмат іншых крутых штук. У нас ёсць MongoDB, у нас трусы бегаюць, у нас чаго толькі няма - але гэта не маё, і я гэтым не займаюся.

OpenResty

Пра сябе я расказаў. Давайце разбяромся, пра што я буду сёння казаць:

  • Што такое OpenResty і з чым яго ядуць?
  • Навошта вынаходзіць яшчэ адзін ровар, калі ў нас ёсць Python, NodeJS, PHP, Go і іншыя крутыя штукі, якімі ўсё задаволены?
  • І трошачкі прыкладаў з жыцця. Мне прыйшлося моцна зрэзаць даклад, таму што ён у мяне атрымліваўся на 3,5 гадзіны, таму прыкладаў будзе мала.

OpenResty - гэта NGINX. Дзякуючы яму мы маем паўнавартасны вэб-сервер, які напісаны добра, ён працуе хутка. Я думаю, большасць з нас выкарыстоўваюць NGINX у прадакшні. Усе вы ведаеце, што ён хуткі і круты. У ім зрабілі круты сінхронны ўвод/вывад, таму нам не трэба нічога веласіпедаваць падобна таму, як у Python навеласіпедылі gevent. Gevent - круты, здаровы, але калі вы напішыце сішны код, і там нешта пойдзе не так, то з gevent вы сыдзеце з розуму гэта дэбажыць. У мяне быў досвед: спатрэбіліся цэлых два дні, каб разабрацца, што ж там пайшло не так. Калі б нехта б да гэтага не пакапаўся некалькі тыдняў, не знайшоў праблему, не напісаў у Інтэрнэце, і Google не знайшоў бы гэтага, то мы б наогул звар'яцелі.

У NGINX ужо зроблены кэшаванне і статычны кантэнт. Вам не трэба парыцца, як гэта зрабіць па-чалавечы, каб у вас дзе-небудзь не затармазіла, каб вы недзе дэскрыптары не страцілі. Nginx вельмі зручна дэплоіць, вам не трэба задумвацца, што ўзяць – WSGI, PHP-FPM, Gunicorn, Unicorn. Nginx паставілі, адмінам падалі, яны ведаюць, як з гэтым працаваць. Nginx структуравана апрацоўвае запыты. Я пра гэта крыху пазней раскажу. Сцісла ў яго ёсць фаза, калі ён толькі прыняў запыт, калі ён апрацаваў і калі аддаў кантэнт карыстачу.

Nginx круты, але ёсць адна праблема: ён нядосыць гнуткі нават пры ўсіх тых стромкіх фішках, што рабяты ўпіхнулі ў канфіг, пры тым, што можна наладзіць. Гэтай моцы не хапае. Таму хлопцы з Taobao калісьці даўно, здаецца, гадоў восем таму, убудавалі туды Lua. Што ён дае?

  • Памер. Ён маленькі. LuaJIT дае дзесьці 100-200 кілабайт оверхеда па памяці і мінімальны оверхед па прадукцыйнасці.
  • Хуткасць. Інтэрпрэтатар LuaJIT у шматлікіх сітуацыях блізкі да C, у некаторых сітуацыях ён прайгравае Java, у некаторых - абганяе яе. Нейкі час ён лічыўся state of art, найкруцейшым JIT-кампілятарам. Цяпер ёсць больш стромкія, але яны вельмі цяжкія, да прыкладу, той жа V8. Некаторыя JS-ныя інтэрпрэтатары і джавоўскі HotSpot у нейкіх кропках хутчэй, але ў нейкіх месцах усё яшчэ прайграюць.
  • Прастата ў засваенні. Калі ў вас, дапусцім, кодавая база на Perl, і вы не Booking, вы не знойдзеце пярловых праграмістаў. Бо іх няма, іх усіх забралі, а вучыць іх доўга і складана. Калі вы хочаце праграмістаў на чымсьці іншым, магчыма, іх таксама іх давядзецца перавучваць, або знаходзіць. У выпадку Lua усё проста. Lua вучыцца любым джуніёрам за тры дні. Мне спатрэбілася недзе гадзіны дзве, каб разабрацца. Праз дзве гадзіны я ўжо пісаў код у прадакшн. Недзе праз тыдзень ён проста ў прадакшн і з'ехаў.

У выніку гэта выглядае вось так:

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

Тут шмат усяго. У OpenResty сабралі кучу модуляў, як луашных, так і энджынсаўскіх. І ў вас усё гатовае - задэплоіў і працуе.

прыклады

Хопіць лірыкі, пераходзім да кода. Вось маленькі Hello World:

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

Што тут ёсць? гэта энджынсаўскі location. Мы не парымся, не пішам свой роўтынг, не бярэм нейкі гатовы - у нас ужо ёсць у NGINX, мы жывем добра і ляніва.

content_by_lua_block – гэта блок, які кажа, што мы аддаём кантэнт пры дапамозе Lua-скрыпта. Бярэм энджынсаўскую зменную remote_addr і падсоўваем яе ў string.format. Гэта тое самае, што і sprintf, толькі на Lua, толькі правільны. І аддаём кліенту.

У выніку гэта будзе выглядаць вось так:

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

Але вернемся ў рэальны свет. У прадакшн ніхто не дэплоітаў Hello World. У нас прыкладанне звычайна ходзіць у базу ці яшчэ кудысьці і большую частку часу чакае адказу.

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

Проста сядзіць і чакае. Гэта ня вельмі добра. Калі прыходзяць 100.000 карыстальнікаў, нам вельмі цяжка. Таму давайце ў якасці прыкладу накідае просценькі дадатак. Будзем шукаць карцінкі, напрыклад, коцікаў. Толькі мы не будзем проста так шукаць, мы будзем пашыраць ключавыя словы і, калі карыстач пашукаў «кацяняты», мы яму знойдзем коцікаў, пушыстыкаў і іншае. Для пачатку нам трэба атрымаць дадзеныя запыту на бэкендзе. Выглядае гэта так:

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

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

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

Падключаем бібліятэчку resty.mysql, якая ў нас ужо ёсць у камплекце. Нам нічога не трэба ставіць, усё гатовае. Указваем, як падключыцца, і робім SQL-запыт:

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

Тут крышачку страшна, але ўсё працуе. Тут 10 - гэта ліміт. Мы выцягваем 10 запісаў, мы лянівыя, не жадаем больш паказваць. У SQL пра ліміт я забыўся.

Далей мы знаходзім карцінкі па ўсіх запытах. Мы збіраем пачак запытаў і запаўняем Lua-таблічку, якая называецца reqs, і робім ngx.location.capture_multi.

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

Усе гэтыя запыты ідуць у паралель, і нам вяртаюцца адказы. Час працы роўна часу адказу самага павольнага. Калі ў нас усё адстрэльваюцца за 50 мілісекунд, і мы даслалі сотню запытаў, то адказ у нас прыйдзе за 50 мілісекунд.

Паколькі мы лянівыя і не жадаем пісаць апрацоўку HTTP і кэшавання, мы прымусім NGINX рабіць усё за нас. Як вы бачылі, там быў запыт на url/fetch, вось ён:

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

Мы робім просты proxy_pass, паказваем, куды закэшаваць, як гэта зрабіць, і ў нас усё працуе.

Але гэтага недастаткова, нам яшчэ трэба аддаць дадзеныя карыстачу. Самая простая ідэя – гэта ўсё серылізаваць у JSON, лёгка, у два радкі. Аддаём Content-Type, аддаём JSON.

Але ёсць адна складанасць: карыстач не жадае чытаць JSON. Трэба прыцягваць франтэндэраў. Часам нам не жадаецца гэтага спачатку рабіць. Ды і сеошнікі скажуць, што калі мы карцінкі шукаем, то ім без розніцы. А калі мы ім нейкі кантэнт выдаем, то яны скажуць, што ў нас пошукавікі нічога не індэксуюць.

Што з гэтым рабіць? Само сабой, мы будзем аддаваць карыстачу HTML. Генераваць ручкамі - не камільфа, таму мы хочам выкарыстоўваць шаблоны. Для гэтага ёсць бібліятэка lua-resty-template.

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

Вы, мусіць, убачылі тры страшныя літары OPM. OpenResty ідзе са сваім пакетным мэнэджарам, праз які можна паставіць яшчэ кучу розных модуляў, у прыватнасці, lua-resty-template. Гэта просты рухавічок шаблонаў, блізкі да Django templates. Там можна напісаць код і зрабіць падстаноўку зменных.

У выніку ўсё будзе выглядаць прыкладна вось так:

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

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

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

  • Першая фаза - доступ, калі карыстач толькі прыйшоў, і мы на яго паглядзелі па загалоўках, па IP-адрасу, па іншым дадзеным. Можна адразу адсекчы яго, калі ён нам не спадабаўся. Гэта можна выкарыстоўваць для аўтарызацыі, альбо, калі нам прыходзіць вельмі шмат запытаў, мы можам іх лёгка секчы на ​​гэтай фазе.
  • перазапіс. Перапісваем нейкія дадзеныя запыту.
  • змест. Аддаём кантэнт карыстачу.
  • headers filter. Падмяняем загалоўкі адказу. Калі мы выкарыстоўвалі proxy_pass, мы можам перапісаць нейкія загалоўкі, перш чым аддаць карыстачу.
  • body filter. Можам падмяніць цела.
  • часопіс - лагіраванне. Можна пісаць лагі ў elasticsearch без дадатковага пласта.

Наша аўтарызацыя будзе выглядаць прыкладна так:

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

Мы дадамо гэта ў той location, які мы апісалі да гэтага, і засунем туды такі код:

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

Мы глядзім, ці ёсць у нас cookie token. Калі не, то кідаем на аўтарызацыю. Карыстальнікі хітрыя і могуць здагадацца, што трэба паставіць cookie token. Таму мы яшчэ пакладзем яе ў Redis:

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

Код працы з Redis вельмі просты і нічым не адрозніваецца ад іншых моў. Пры гэтым увесь увод/вывад што там, што тут, ён не блакуючы. Калі пішаце сінхронны код, тое працуе асінхронна. Прыкладна як з gevent, толькі зроблена добра.

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

Давайце зробім саму аўтарызацыю:

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

Кажам, што нам трэба чытаць цела запыту. Атрымліваем POST-аргументы, правяраем, што лагін і пароль правільныя. Калі няправільныя, то кідаем на аўтарызацыю. А калі правільныя, то запісваем token у Redis:

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

Не забываем паставіць cookie, гэта таксама робіцца ў два радкі:

OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

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

  • Мінімалістычны бэкенд. Часам нам патрабуецца ў бэкенд выдаць зусім крыху дадзеных: дзесьці трэба дату падставіць, дзесьці нейкі спіс вывесці, сказаць, колькі зараз карыстальнікаў на сайце, прыкруціць лічыльнік або статыстыку. Нешта такое невялікае. Мінімальныя нейкія кавалачкі можна вельмі лёгка зрабіць. На гэтым атрымаецца хутка, лёгка і здорава.
  • Прэпрацэсінг дадзеных. Часам нам жадаецца ўбудаваць у нашу старонку рэкламу, прычым гэтую рэкламу мы бярэм API-запытамі. Такое вельмі лёгка зрабіць менавіта тут. Мы не загружаем наш бэкэнд, які і так сядзіць цяжка працуе. Можна ўзяць і сабраць тут. Мы можам зляпіць нейкія JS ці, наадварот, разляпіць, нешта препроцессить перш, чым аддаць карыстачу.
  • Фасад для мікрасэрвісу. Гэта таксама вельмі добры кейс, я яго рэалізоўваў. Да гэтага я працаваў у кампаніі Tenzor, якая займаецца электроннай справаздачнасцю, забяспечвае справаздачнасць прыкладна паловы юрыдычных асоб у краіне. Мы зрабілі сэрвіс, там пры дапамозе гэтага ж механізму зроблены многія рэчы: маршрутызацыя, аўтарызацыя і іншае.
    OpenResty можна выкарыстоўваць як клей для вашых мікрасэрвісаў, які забяспечыць адзіны доступ да ўсяго і адзіны інтэрфейс. Паколькі мікрасэрвісы могуць быць напісаны так, што вось тут у вас Node.js, тут у вас PHP, тут Python, тут стаіць нейкая штука на Erlang, мы разумеем, што не жадаем адзін і той жа код усюды перапісваць. Таму OpenResty можна ўторкнуць на фронт.

  • Статыстыка і аналітыка. Звычайна NGINX стаіць на ўваходзе, і ўсе запыты ідуць праз яго. Менавіта тут вельмі зручна сабраць. Можна нешта адразу палічыць і куды-небудзь закінуць, напрыклад, той жа Elasticsearch, Logstash ці проста запісаць у лог і потым куды-небудзь адправіць.
  • Шматкарыстальніцкія сістэмы. Напрыклад, анлайн-гульні таксама вельмі добра рабіць. Сёння ў Кейптаўне Аляксандр Гладыш будзе распавядаць, як хутка прататыпіраваць шматкарыстальніцкую гульню пры дапамозе OpenResty.
  • Фільтраванне запытаў (WAF). Цяпер модна рабіць усякія web application firewall, ёсць шмат сэрвісаў, якія іх падаюць. З дапамогай OpenResty можна зрабіць сабе web application firewall, які проста і лёгка будзе фільтраваць запыты па вашых патрабаваннях. Калі ў вас Python, то вы разумееце, што PHP вам сапраўды не інджэкцяць, калі вы, вядома, з кансолі яго не спауніце нідзе. Вы ведаеце, што ў вас ёсць MySQL і Python. Мусіць, тут могуць паспрабаваць зрабіць які-небудзь directory traversal і што-небудзь заінджэкціць у базу. Таму можна адфільтраваць стрымлівыя запыты хутка і танна адразу на фронце.
  • Супольнасць. Паколькі OpenResty пабудаваны на базе NGINX, то ў яго ёсць бонус - гэта NGINX-кам'юніці. Яно вельмі вялікае, і прыстойная частка пытанняў, якая ў вас узнікне спачатку, ужо вырашана NGINX-супольнасцю.

    Lua-распрацоўшчыкі. Учора я меў зносіны з рабятамі, якія дашлі на навучальны дзень HighLoad++ і пачуў, што на Lua напісаны толькі Tarantool. Гэта не так, на Lua шмат чаго напісана. Прыклады: OpenResty, XMPP-сервер Prosody, гульнявы ​​рухавічок Love2D, Lua скрыптуецца ў Warcraft і ў іншых месцах. Lua-распрацоўшчыкаў вельмі шмат, у іх вялікае і спагаднае кам'юніці. Усе мае пытанні па Lua вырашаліся на працягу некалькіх гадзін. Калі пішаш у спіс рассылання, літаральна праз некалькі хвілін ужо куча адказаў, распісваюць што і як, што да чаго. Гэта вельмі здорава. Нажаль, не ўсюды такое добрае душэўнае кам'юніці.
    Па OpenResty ёсць GitHub, там можна завесці issue, калі нешта зламалася. Ёсць спіс рассылкі на Google Groups, дзе можна абмеркаваць агульныя пытанні, ёсць рассыланне на кітайскай - ці мала, можа, ангельскай вы не валодаеце, а веды кітайскай ёсць.

Вынікі

  • Спадзяюся змог данесці, што OpenResty - гэта вельмі зручны фрэймворк, заменчаны пад вэб.
  • У яго нізкі парог уваходжання, паколькі код падобны да таго, на чым мы пішам, мова даволі простая і мінімалістычная.
  • Ён дае асінхронны I/O без коллбеков, у нас не будзе локшыны, як мы можам часам напісаць у NodeJS.
  • У яго лёгкі дэплой, паколькі нам патрэбен толькі NGINX c патрэбным модулем і наш код, і ўсё адразу працуе.
  • Вялікая і спагадная супольнасць.

Я не расказаў у дэталях як робіцца маршрутызацыя, там атрымлівалася вельмі доўгае апавяданне.

Дзякуй за ўвагу!


Уладзімір Протасаў - OpenResty: ператвараем NGINX у паўнавартасны сервер прыкладанняў

Крыніца: habr.com

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