понедельник, 14 ноября 2011 г.

Nginx+Redis: делаем асихронное web-приложение для больших нагрузок

Как работает обычное web-приложение?
Примерно так:
Проходит время, нагрузка растёт и узким местом становится база данных. Разработчики, стараясь снять с неё нагрузку, переходят к асинхронной схеме. Тут база данных не используется в каждом запросе. В основном используется только быстрое noSQL-хранилище или специализированный сервер очередей, для передачи заданий пулу обработчиков. Основную работу эти обработчики выполнят уже позже, когда довольный клиент получит быстрый ответ и пойдёт заниматься своими делами:


Но нагрузка может расти и дальше. Оставим в стороне "горизонтальное" масштабирование при котором мы строим кластера и плодим инстансы приложений - речь сейчас не об этом. Что в последней схеме становится узким местом? База данных уже не в счёт: спрятанная за слоем очередей и обработчиков, кэшей и буферов, она может чувствовать себя спокойно. Обработчики великолепно масштабируются, ведь они "висят" на безразмерной шине очереди. Nginx - один из самых высопроизводительных серверов, за него не беспокоимся. noSQL-хранилища тоже как правило замечтально держат нагрузку и масштабируются.
Что остаётся? К сожалению "крайним" остаётся наше web-приложение. Это оно разбирает запрос, авторизует его, создаёт обьекты, манипулирует с ними, сериализует в базу или в очередь. А потом ещё вычитать данные, сериализовать для ответа... Приложение может содержать неэффективный код, плохо масштабироваться... Кстати, а зачем нам оно вообще нужно? Давайте уберём его из схемы:


 Как же так? Очень просто. Задача получить данные из запроса и уложить в очередь вообще-то тривиальная. И обратная задача тоже. Зачем программировать тривиальные вещи? Всё уж сделано за нас :)
Nginx при помощи HttpRedis2Module может уложить в noSQL-хранилище Redis любые параметры из запроса в виде такого набора ключ-значение, который нам нужен. И вычитать нужный нам набор ключей для возврата клиенту. Вы не используете параметры запросов? У вас обмен с клиентской частью в формате JSON? Нет проблем! Используя set-misc-nginx-module мы можем прямо в конфиге nginx-а описать правила получения данных из запроса: UrlDecode, JSONDecode, Base64Decode и т.п.

Теперь посмотрим, как настроить такое "сверхтонкое" web-приложение:

Первым делом установим Redis:

$ wget http://redis.googlecode.com/files/redis-2.4.2.tar.gz
$ tar xzf redis-2.4.2.tar.gz
$ cd redis-2.4.2
$ make


Из директории src можем запустить redis сервер (redis-server) и клиент (redis-cli).
О конфигурировании redis можно подробнее прочитать в документации. Нам пока нужно знать только порт на котором слушает redis. По умолчанию это 6379.

Теперь нужно собрать Nginx. Именно собрать из исходников, ведь нам нужно включить в него сторонние модули.


    $ wget 'http://nginx.org/download/nginx-1.1.7.tar.gz'
    $ tar -xzvf nginx-1.1.7.tar.gz
    $ cd nginx-1.1.7/
    $ ./configure --prefix=/opt/nginx \
                --add-module=/path/to/redis2-nginx-module \
                --add-module=/path/to/ngx-devel-kit-module \
                --add-module=/path/to/set-misc-nginx-module 
    $ make -j2
    $ make install

Тут при конфигуривании nginx мы включаем 3 модуля: первый для работы с Redis (redis2-nginx-module), третий для парсинга и преобразования данных запроса (set-misc-nginx-module), второй - ngx_devel_kit нужен для работы третьего.
Как видно из первого параметра команды configure, nginx установится в /opt/nginx. В /opt/nginx/conf/nginx.conf в блок конфигурации http-сервера по умолчанию добавим несколько директив, так чтобы он выглядел следующим образом:


  1.   server {
  2.     listen    80;
  3.     server_name localhost;
  4.     location / {
  5.       root  html;
  6.       index index.html index.htm;
  7.     }
  8.     location /get {
  9.       set_unescape_uri $key $arg_key;
  10.       redis2_query get $key;
  11.       redis2_pass 127.0.0.1:6379;
  12.     }
  13.     location /set {
  14.       set_unescape_uri $key $arg_key;
  15.       set_unescape_uri $val $arg_val;
  16.       redis2_query set $key $val;
  17.       redis2_pass 127.0.0.1:6379;
  18.     }
  19.     location /addhello {
  20.         redis2_query set hello world;
  21.         redis2_query get hello;
  22.         redis2_pass 127.0.0.1:6379;
  23.     }
  24.   }
 Тут в строках 8-12 определяется ресурс /get который может в ответ на запрос вида http://localhost/get?key=keyname вернуть из Redis значение ключа "keyname". 
В строках 13-18 описана обратная операция: запись в Redis значения определённого GET-параметром "val" с ключом, имя которого определено GET-параметром "key".
Можем реализовать и цепочку запросов: например в ресурсе /addhello (строки 19-23) мы укладываем в Redis значение и вычитываем его для ответа. 
Сохраняем изменённый конфиг, шлём запросы c параметрами на http://localhost и выбираем из Redis-а данные запросов.
Вообще, Nginx весьма гибкий инструмент. Расширяя его модулями и конфигурируя, мы можем реализовать функционал достаточно сложного web-приложения без программирования. Программировать же нам остаётся только слой "обработчиков", которые общаются с клиентами (и друг с другом, если нужно) через Redis. Именно там мы реализуем бизнес-логику не беспокоясь больше о нагрузке.

6 комментариев:

  1. Интересно, но на сколько гибким могут быть сторонние модули?
    Мне недавно пришла идея реализовать бизнес логику как модуль для nginx в качестве базы всё тот же redis, посмотрим на сколько получится производительное решение.

    ОтветитьУдалить
    Ответы
    1. Глупая идея, если тяжелая бизнесс логика - может упасть nginx. Не забывай, что nginx асинхронный однопоточный процесс (их можно запустить несколько). По этому, если один из HTTP запросов зависнит внутри nginx, то будут висеть и другие процессы.

      Суть nginx в том, чтоб быстро принять HTTP запрос и перебросить его на backend, потом обработать следующий запрос, еще, и еще..., пока baсkend думает над ответом, о потом отдать ответ.

      Принципиально это возможно, если разбить проект на части (микросервисы) и часть легкой Логики реальизовать через lua модуль.

      Удалить
    2. Видимо, вы невнимательно читали пост, раз не поняли основоной мысли. Цель связки nginx + redis как раз в том, чтобы исключить ситуацию, когда nginx-у приходится "ждать" пока исполняется бизнес-логика. Его задача - уложить или достать значения из redis. Бизнес-логика начинается потом, когда ответ клиенту уже отправлен.
      Это требует модификации логики фронтенда, он должен уметь реагировать на сигнал бекэнда "зайти позже", но получаемые взамен преимущества того стоят.

      Удалить
  2. Появилась неплохая альтернатива - webdis

    ОтветитьУдалить
  3. как передать пароль в redis? redis запароленый

    ОтветитьУдалить
  4. Добрый день. Мы занимаемся разработкой программы FastoRedis. FastoRedis - это кроссплатформенный GUI-менеджер для баз данных Redis, Memcached, SSDB. Возможно, наша программа будет полезна для ваших разработчиков. Если Вы используете FastoRedis или планируете использовать, мы будем очень благодарны если в ответном письме Вы напишите нам Ваше мнение, замечания, предложения.
    Так же будем благодарны за помощь в разрешении спорного вопроса. А именно ->
    В первых версиях FastoRedis была возможность работать в консоле lua и python
    http://fastoredis.com/whats-new_060.html
    http://fastoredis.com/whats-new_045.html
    Счас это функционал убрали - есть мнение что это лишнее и совсем не нужно для работы с Redis, Memcached, SSDB.
    Если не трудно, сообщите пожалуйста мнение Ваших разработчиков по этому вопросу(возможно был бы полезен еще какой нибудь функционал).
    В дальнейшем планируем развивать функционал программы основываясь на замечаниях и предложениях пользователей.

    С уважением, разработчики FastoRedis.
    Сайт - http://fastoredis.com
    E-mail - atopilski@fastoredis.com

    ОтветитьУдалить