Web-Оповіщення навантажених проектах

В сучасному WE-конструюванні дуже часто виникають задачі, коли необхідно оповіщати користувача про яку-небудь подію: прийшло нове повідомлення, змінився курс на біржі або статус замовлення, сконвертировался відео-контент або подскачила температура хворої бабусі.

Є кілька варіантів вирішення такого класу задач. Найбільш оптимальне і поширене рішення – це підписка на події. Як це реалізується в навантажених проектах?

Припустимо, що ми розробляємо сервіс брокеркой контори, в якій обслуживются тисячі клієнтів. Для того, щоб дізнатися про стан курсу акцій на біржі або дізнатися кількість вільних місць в готелях, нам необхідно звернутися до одного або кількох зовнішньому сервісів. Так як, зовнішній сервіс відповідає з затримкою, а у нас тисячі клієнтів, то у випадку, якщо ми будемо робити запити на пряму з ВЕБ додатків і чекати відповіді від сервісу, то в результаті все подвиснет.

З цього, нам доводиться робити, так званий відкладений запит. Наше WEB додаток відразу повертає користувачеві сформовану HTML сторінку без результату, на якій показується заставка, що зпрос виконується, а результат приходить трохи пізніше, по мірі його виконання. Як це происхотит?

Перед початком формування HTML-сторінки, наше WEB додаток кладе в чергу дані. Демон, чи викликається по крону завдання, просмотривает чергу і забирає з неї дані. Далі, на підставі цих даних, вона формує запит і відправляє його зовнішнього сервісу (малюнок 1).

image

Начебто, все в цій схемі добре – працює без затримок. Але нам потрібен зворотний зв'язок.Кінцевому користувачеві необхідна та інформація, яку він запитував. І ось, цю інформацію ми отримали в нашому крон скрипті. Тепер її необхідно переправити користувачеві.

Тут нам допоможе патерн Видавець-Читач. Багатьом, хто використовує JavaScript відома ця схема:

Передплатник (Subscriber) підписується на певний канал, а при звершенні деякого події, Видавець (Producer) в цей канал відправляє повідомлення. В якості такого механізму повідомлень можна використовувати багато різних рішень: Redis, RabbitMQ, Tarantool, MsMQ, ZMQ, Kafka (брокера повідомлень). Так як у нас ряд сервісів вже був зав'язаний на Redis, ми вирішили не вводити нові сутності.

Як би ви це використали? Тут знайдеться кілька варіантів, але фахівці відразу в три горла заявлять «Для зв'язку WEB сторінки і сервера треба використовувати websockets». Не буду сперечатися, так, на сьогодні – це найбільш передова технологія миттєвого спілкування WEB-клієнта і сервера. Розглянемо внутрішню сторону.

Ні для кого не відкрию секрету, що вже кілька років як nginx вміє проксировать websockets. Якщо у нас в якості бекенду використовується php-fpm, то на кожен запущений WEB-клієнт, у нас повинен бути запущений PHP процес. Тут виникає проблема 10К, коли на 10К запросовбудет висіти 10К процесів. Банально не вистачить пам'яті. Як один з варіантів, можна використовувати node.js. Це, як раз його клас задач, де використовуються довгограючі не блокуються з'єднання.

А можна обійтися без нього? Адже, не хотілося б вводити нову сутність, тим більше на неї покладаємо дуже просту задачу. Чим складніше архітектура, тим більше точок відмови і менше ймовірність безвідмовної роботи. У нас вже був позитивний досвід впровадження модуля nginx-lua (Більш детальніше про nginx-lua можна почитати тут і тут). А може він виконати ці функції? Загалом, в результаті вийшла ось така картина (малюнок 2):

image

Виявляється, це не так складно. Додатково до lua-nginx-module підключаємо lua-resty-redis і lua-resty-websocket. Для цього, на відміну від lua-nginx-module ні чого збирати не треба, а лише всі вихідні коди модулів, які знаходяться в каталозі lib переписати в папку /usr/share/nginx/lua/lib і підключити директивою в контексті http (конфігураційний файл nginx.f):

http {
lua_package_path "/usr/share/nginx/lua/lib/?.lua;;"; 
...
}

Далі, в конфігураційному файлі nginx.conf (або підключається конфіги для нашого віртуального хоста) визначаємо location /ws:

location /ws { 
content_by_lua_file /path/to/file/websocket_server.lua; 
}

Сам файл websocket_server.lua не такий вже і складний, викладати тут частинами — сенсу не бачу. Його повну версію можна знайти на github.

Для перевірки є тестовий консольний клієнт, який можна доопрацювати і запустити в кілька тисяч примірників з різних таймоутом і перевірити на практиці. Дана версія клієнта діалогова, з консолі вводиться найменування каналу, і якщо в канал направляється публікація, то вона миттєво надходить на клієнт. Клієнт має таймаут 5 хв.

Сподіваюся, ця фіча кому-небудь знадобиться.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.