Ponownie publikujemy transkrypcję wystąpienia z konferencji 2016, która odbyła się w podmoskiewskim Skolokowie w dniach 7-8 listopada ubiegłego roku. opowiada, jak rozszerzyć funkcjonalność NGINX za pomocą OpenResty i Lua.
Cześć wszystkim, nazywam się Wladimir Protasow, pracuję w Parallels. Opowiem nieco o sobie. Przez trzy czwarte swojego życia zajmuję się pisaniem kodu. Zostałem programistą do szpiku kości w dosłownym tego słowa znaczeniu: czasami widzę kod we śnie. Jedną czwartą życia poświęciłem na przemysłowy rozwój, pisanie kodu, który trafia bezpośrednio do produkcji. Kod, z którego niektórzy z was korzystają, ale nie zdają sobie z tego sprawy.
Abyście zrozumieli, jak źle było. Gdy byłem małym juniorem, przyszedłem i wydano mi takie dwuterabajtowe bazy. Teraz wszyscy mają highload. Chodziłem na konferencje, pytałem: „Chłopaki, opowiedzcie, macie big data, wszystko super? Ile macie baz?” Odpowiadali: „Mamy 100 gigabajtów!” Mówiłem: „Świetnie, 100 gigabajtów!” A w myślach myślałem, jak by tu zachować poker face. Myślisz, tak, chłopaki są fajni, a potem wracasz i grzebiesz się w tych wieloteraabajtowych bazach. I to będąc juniorem. Wyobrażacie sobie, jaki to cios?
Znam ponad 20 języków programowania. To jest coś, co musiałem zgłębić w trakcie pracy. Dajesz mi kod w Erlang, C, C++, Lua, Pythonie, Ruby, na czymś jeszcze, i musisz to wszystko ogarniać. W sumie musiałem. Nie udało mi się dokładnie policzyć, ale gdzieś w okolicach 20 liczba się zgubiła.
Ponieważ wszyscy obecni wiedzą, czym zajmuje się Parallels, nie będę mówił, jacy jesteśmy wspaniali i co robimy. Powiem tylko, że mamy 13 biur na całym świecie, ponad 300 pracowników, rozwój w Moskwie, Tallinie i na Malcie. W razie chęci można po prostu przenieść się na Maltę, jeśli zimą jest zimno i trzeba ogrzać plecy.
Nasz dział programuje w Pythonie 2. Zajmujemy się biznesem i nie mamy czasu na wdrażanie modnych technologii, więc cierpimy. Mamy Django, bo ma wszystko, a to, co zbędne, wyrzuciliśmy. Mamy też MySQL, Redis i NGINX. Mamy jeszcze wiele innych fajnych rzeczy. Mamy MongoDB, biegające króliki, mamy niemal wszystko, ale to nie jest moje i się tym nie zajmuję.
OpenResty
Opowiedziałem o sobie. Zajmijmy się tym, o czym dzisiaj będę mówił:
- Czym jest OpenResty i jak to działa?
- Po co wymyślać jeszcze jeden rower, skoro mamy Pythona, NodeJS, PHP, Go i inne świetne technologie, z których wszyscy są zadowoleni?
- I trochę przykładów z życia. Musiałem znacznie skrócić prezentację, bo wynosiła 3,5 godziny, więc przykładów będzie mało.
OpenResty to NGINX. Dzięki niemu mamy w pełni funkcjonalny serwer WWW, który został dobrze napisany i działa szybko. Myślę, że większość z nas wykorzystuje NGINX w produkcji. Wszyscy wiecie, że jest szybki i genialny. Zrobiono w nim świetny synchroniczny input/output, więc nie musimy tworzyć niczego od podstaw, jak w Pythonie zrobiono z gevent. Gevent jest świetny, ale jeśli napiszesz kod w C i coś pójdzie nie tak, to zwariujesz, próbując to debugować. Miałem takie doświadczenie: zajęło mi dwa dni, aby zrozumieć, co poszło nie tak. Gdyby nikt nie grzebał w tym przez kilka tygodni, nie znalazł problemu, nie napisał o tym w Internecie i Google tego nie odnalazł, to byśmy całkiem zwariowali.
W NGINX już zrealizowane są cachowanie i statyczny content. Nie musisz się martwić, jak to zrobić porządnie, żeby gdzieś nie spowolniło i nie zgubić deskryptorów. Nginx jest bardzo wygodny do wdrożenia, nie musisz się zastanawiać, co brać – WSGI, PHP-FPM, Gunicorn, Unicorn. Nginx jest zainstalowany, oddany administratorom, którzy wiedzą, jak z tym pracować. Nginx strukturalnie obsługuje żądania. O tym opowiem nieco później. Krótko mówiąc, ma fazę, kiedy tylko odbiera żądanie, kiedy je przetwarza i kiedy przekazuje content użytkownikowi.
Nginx jest wspaniały, ale jest jeden problem: nie jest wystarczająco elastyczny, pomimo wszystkich tych wspaniałych funkcji, które chłopaki wpakowali w konfigurację, jako że można dostosować. Ta moc nie wystarcza. Dlatego chłopaki z Taobao kiedyś, chyba osiem lat temu, wbudowali Lua. Co to daje?
- Wymiary. Jest mały. LuaJIT daje około 100-200 kilobajtów dodatkowego obciążenia pamięci i minimalne obciążenie wydajności.
- Szybkość. Interpreter LuaJIT w wielu sytuacjach zbliża się do C, w niektórych przypadkach przegrywa z Java, a w innych ją wyprzedza. Jakieś czasy temu był uważany za najlepszy, genialny kompilator JIT. Teraz są bardziej zaawansowane, ale są bardzo ciężkie, na przykład V8. Niektóre interpretery JS i HotSpot Java w pewnych miejscach są szybsze, ale w innych miejscach nadal przegrywają.
- Łatwość w nauce. Jeśli macie, powiedzmy, bazę kodu w Perl, a nie jesteście Booking, nie znajdziecie programistów Perl. Bo ich nie ma, wszyscy zostali zabrani, a nauka ich zajmuje dużo czasu i jest trudna. Jeśli chcecie programistów w czymś innym, to może również trzeba będzie ich przekwalifikować lub znaleźć. W przypadku Lua jest to proste. Lua uczy się w trzy dni przez każdego juniora. Zajęło mi około dwóch godzin, żeby to ogarnąć. Po dwóch godzinach już pisałem kod do produkcji. Po około tygodniu trafił on bezpośrednio do produkcji.
W rezultacie wygląda to tak:

Jest tu sporo rzeczy. W OpenResty zebrano mnóstwo modułów, zarówno luowych, jak i z Nginx. I już wszystko gotowe – wdrażasz i działa.
Przykłady
Dość liryki, przechodzimy do kodu. Oto małe Hello World:

Co się tutaj znajduje? to lokalizacja z Nginx. Nie martwimy się, nie piszemy własnego routingu, nie bierzemy jakiegoś gotowego – mamy już w NGINX, żyjemy dobrze i leniwie.
content_by_lua_block – to blok, który mówi, że wydajemy zawartość za pomocą skryptu Lua. Bierzemy zmienną z Nginx remote_addr i wplątujemy ją w string.format. To to samo, co i sprintf, tylko w Lua, tylko właściwe. I wydajemy klientowi.
W rezultacie będzie to wyglądać tak:

Ale wróćmy do rzeczywistego świata. Nikt nie wdraża Hello World do produkcji. Nasza aplikacja zazwyczaj łączy się z bazą danych lub gdzie indziej i przez większość czasu czeka na odpowiedź.

Po prostu siedzi i czeka. To niezbyt dobrze. Kiedy przychodzi 100.000 użytkowników, mamy naprawdę ciężko. Dlatego stwórzmy prostą aplikację. Będziemy szukać obrazków, na przykład, kotów. Tylko nie będziemy po prostu szukać, będziemy rozszerzać słowa kluczowe i jeśli użytkownik szukał „kociąt”, znajdziemy mu koty, puchate i inne. Na początku musimy uzyskać dane zapytania na backendzie. Wygląda to tak:

Dwie linijki pozwalają na pobranie parametrów GET, żadnych trudności. Następnie, powiedzmy, z bazy danych z tabelą po słowie kluczowym i rozszerzeniu uzyskujemy tę informację zwykłym zapytaniem SQL. Wszystko prosto. Wygląda to tak:

Podłączamy bibliotekę resty.mysql, która jest już w zestawie. Nie musimy nic instalować, wszystko gotowe. Określamy, jak się połączyć, i wykonujemy zapytanie SQL:

Tu jest trochę strasznie, ale wszystko działa. Tutaj 10 - to jest limit. Wydobywamy 10 rekordów, jesteśmy leniwi, nie chcemy pokazywać więcej. Zapomniałem o limicie w SQL.
Następnie znajdujemy obrazy dla wszystkich zapytań. Zbieramy stos zapytań i wypełniamy tabelę Lua, która nazywa się reqs, i robimy ngx.location.capture_multi.

Wszystkie te zapytania są wysyłane równolegle, a my otrzymujemy odpowiedzi. Czas wykonania równy jest czasowi odpowiedzi najwolniejszego. Jeśli wszystkie odpowiedzi przyjmują 50 milisekund, a wysłaliśmy sto zapytań, to odpowiedź przyjdzie po 50 milisekundach.
Ponieważ jesteśmy leniwi i nie chcemy pisać obsługi HTTP i pamięci podręcznej, zmusimy NGINX, aby zrobił wszystko za nas. Jak widzieliście, był tam wniosek o url/fetch, oto on:

Robimy prosty proxy_pass, wskazując, gdzie to zapisać, jak to zrobić, a wszystko działa.
Ale to za mało, musimy jeszcze zwrócić dane użytkownikowi. Najprostszym pomysłem jest serializacja wszystkiego do JSON, łatwo, w dwóch linijkach. Ustawiamy Content-Type, zwracamy JSON.
Ale jest jedna trudność: użytkownik nie chce czytać JSON. Musimy przyciągnąć frontendowców. Czasami na początku nie chcemy tego robić. SEO-mani też powiedzą, że jeśli szukamy obrazków, to nie ma dla nich znaczenia. A jeśli wydajemy jakiś kontent, to powiedzą, że nasze wyszukiwarki nic nie indeksują.
Co z tym zrobić? Oczywiście, będziemy zwracać użytkownikowi HTML. Generowanie ręcznie - to nie jest eleganckie, więc chcemy używać szablonów. W tym celu istnieje biblioteka lua-resty-template.

Pewnie widzieliście trzy straszne litery OPM. OpenResty idzie z własnym menedżerem pakietów, przez który można zainstalować jeszcze wiele różnych modułów, w tym lua-resty-template. To prosty silnik szablonów, podobny do szablonów Django. Można tam napisać kod i wstawić zmienne.
W rezultacie wszystko będzie wyglądać mniej więcej tak:

Zabraliśmy dane i zrenderowaliśmy szablon znowu w dwóch linijkach. Użytkownik jest szczęśliwy, otrzymał kotki. Ponieważ rozszerzyliśmy zapytanie, otrzymał również morskiego lwa. Kto wie, może dokładnie to chciał, ale nie mógł poprawnie sformułować swojego zapytania.
Wszystko świetnie, ale jesteśmy w fazie dewelopmentu i nie chcemy na razie pokazywać użytkownikom. Zróbmy autoryzację. Aby to zrobić, zobaczmy, jak NGINX przetwarza zapytania w terminach OpenResty:
- Pierwsza faza - access, gdy użytkownik właśnie przybył, a my oceniliśmy go na podstawie nagłówków, adresu IP i innych danych. Możemy go natychmiast odciąć, jeśli nam się nie spodoba. Można to wykorzystać do autoryzacji, lub jeśli otrzymujemy bardzo dużo żądań, możemy je łatwo odrzucić na tym etapie.
- rewrite. Przepisywanie niektórych danych żądania.
- zawartość. Dostarczamy treść użytkownikowi.
- filtr nagłówków. Zmieniamy nagłówki odpowiedzi. Jeśli użyliśmy
proxy_pass, możemy przepisać niektóre nagłówki, zanim przekazujemy je użytkownikowi. - filtr ciała. Możemy zmienić treść.
- log — logowanie. Można zapisywać logi w elasticsearch bez dodatkowej warstwy.
Nasza autoryzacja będzie wyglądać mniej więcej tak:

Dodamy to do tego lokacja, który opisaliśmy wcześniej, i wstawimy tam taki kod:

Sprawdzamy, czy mamy token cookie. Jeśli nie, to wysyłamy na autoryzację. Użytkownicy są sprytni i mogą się domyślić, że trzeba ustawić token cookie. Dlatego dodatkowo umieścimy go w Redis:

Kod obsługi Redis jest bardzo prosty i niczym się nie różni od innych języków. Cały wejście/wyjście tam i tutaj jest nieblokujące. Jeśli piszesz kod synchronizujący, działa asynchronicznie. Podobnie jak z gevent, tylko zrobione dobrze.

Zróbmy samą autoryzację:

Mówimy, że musimy odczytać treść żądania. Odbieramy argumenty POST, sprawdzamy, czy login i hasło są poprawne. Jeśli są niepoprawne, to wysyłamy na autoryzację. A jeśli są poprawne, zapisujemy token w Redis:

Nie zapomnijmy ustawić cookie, to również odbywa się w dwóch linijkach:

Przykład prosty, umowny. Oczywiście nie zamierzamy tworzyć serwisu, który pokazuje ludziom kotki. Chociaż kto nas zna. Dlatego przejdźmy przez to, co można zrobić w produkcji.
- Minimalistyczny backend. Czasami potrzebujemy w backendzie wydobyć kilka informacji: gdzieś trzeba wstawić datę, gdzieś pokazać jakiś wykaz, podać, ile jest obecnie użytkowników na stronie, dołączyć licznik lub statystyki. Coś takiego małego. Minimalne kawałki można łatwo stworzyć. W ten sposób będzie szybko, łatwo i świetnie.
- Przeprocesowanie danych. Czasami chcemy wpleść reklamy na naszą stronę, przy czym te reklamy pobieramy za pomocą zapytań API. Można to łatwo zrobić właśnie tutaj. Nie obciążamy naszego backendu, który i tak ciężko pracuje. Można to zbudować tutaj. Możemy stworzyć jakieś JS lub, przeciwnie, coś rozwalić, przetworzyć przed oddaniem użytkownikowi.
- Interfejs dla mikrousług. To także bardzo dobry przypadek, który wdrożyłem. Przedtem pracowałem w firmie Tenzor, która zajmuje się e-raportowaniem, zapewniającej raporty dla około połowy podmiotów prawnych w kraju. Stworzyliśmy usługę, w której dzięki temu samemu mechanizmowi zrealizowane są różne rzeczy: routowanie, autoryzacja i inne.
OpenResty można używać jako kleju dla twoich mikrousług, który zapewni jednoczesny dostęp do wszystkiego i jednolity interfejs. Ponieważ mikrousługi mogą być napisane w różnych językach, takich jak Node.js, PHP, Python czy Erlang, rozumiemy, że nie chcemy pisać tego samego kodu wszędzie. Dlatego OpenResty można umieścić na froncie. - Statystyki i analiza. Zwykle NGINX jest umieszczony na początku, a wszystkie zapytania przechodzą przez niego. Właśnie w tym miejscu bardzo удобно jest zbierać dane. Można coś od razu zliczyć i gdzieś przesłać, na przykład do Elasticsearch, Logstash lub po prostu zapisać w logach i później wysłać gdzie indziej.
- Systemy wieloosobowe. Na przykład, bardzo dobrze jest robić również gry online. Dziś w Kapsztadzie Aleksander Gladyś opowie, jak szybko prototypować grę wieloosobową z pomocą OpenResty.
- Filtrowanie zapytań (WAF). Obecnie modne jest tworzenie różnych zapór aplikacji webowych, jest wiele usług, które je oferują. Za pomocą OpenResty można stworzyć własną zaporę aplikacji webowej, która szybko i łatwo filtruje zapytania zgodnie z twoimi wymaganiami. Jeśli masz Python, to rozumiesz, że PHP na pewno nie zostanie zaindeksowane, o ile nie uruchomisz go z konsoli. Wiesz, że używasz MySQL i Pythona. Ponieważ mogą próbować wykonać inne podejście do katalogu i zaindeksować coś w bazie, można więc szybko i tanio odfiltrować podejrzane zapytania na froncie.
- Społeczność. Ponieważ OpenResty jest zbudowany na bazie NGINX, ma dodatkową zaletę – to społeczność NGINXJest to bardzo obszerne, a znacząca część pytań, które początkowo się pojawią, została już rozwiązana przez społeczność NGINX.
Programiści LuaWczoraj rozmawiałem z chłopakami, którzy przyszli na dzień nauki HighLoad++ i usłyszałem, że tylko Tarantool jest napisany w Lua. To nieprawda, w Lua napisano wiele rzeczy. Przykłady: OpenResty, serwer XMPP Prosody, silnik gier Love2D, Lua skryptuje w Warcraft i w innych miejscach. Jest wielu programistów Lua, mają dużą i responsywną społeczność. Wszystkie moje pytania dotyczące Lua były rozwiązywane w ciągu kilku godzin. Gdy piszesz na liście mailingowej, dosłownie po kilku minutach już jest masa odpowiedzi, wyjaśniają, co i jak, co do czego. To naprawdę wspaniałe. Niestety, nie wszędzie jest taka życzliwa społeczność.
Na temat OpenResty jest GitHub, gdzie można zgłosić problem, jeśli coś się zepsuło. Jest lista dyskusyjna na Google Groups, gdzie można omówić ogólne pytania, jest też lista w języku chińskim — wiadomo, może nie znasz angielskiego, ale potrafisz posługiwać się chińskim.
Podsumowanie
- Mam nadzieję, że udało mi się przekazać, że OpenResty to bardzo wygodny framework zaprojektowany pod kątem webu.
- Ma niski próg wejścia, ponieważ kod przypomina to, w czym piszemy, język jest dość prosty i minimalistyczny.
- Zapewnia asynchroniczne I/O bez callbacków, nie będzie u nas splątania, jak czasami możemy napisać w NodeJS.
- Jest łatwy w wdrożeniu, ponieważ potrzebujemy tylko NGINX z odpowiednim modułem i naszego kodu, i wszystko od razu działa.
- Duża i responsywna społeczność.
Nie opowiedziałem szczegółowo o tym, jak działa routing, bo to byłby bardzo długi wykład.
Dziękuję za uwagę!

Źródło: habr.com
