Вам знакома такая архитектура? Хоровод демонов, пляшущих между web-server, cache и storage.



Какие минусы такой архитектуры можно отметить? Решая задачи в рамках такой архитектуры, мы сталкиваемся с кучей вопросов: какой язык(и?) взять, какой I/O framework выбрать, как синхронизировать cache и storage? Куча инфраструктурных вопросов. А зачем решать инфраструктурные вопросы, когда надо решить задачу? Безусловно, можно сказать, что нам нравятся некие технологии X и Y, и перевести эти минусы в рамки идеологических. Но нельзя отрицать тот факт, что данные располагаются на неком расстоянии от кода (картинка выше), что добавляет latency, что может уменьшить RPS.

Цель данной статьи — рассказать об альтернативе, которая построена на базе Nginx как web-server, bаlancer и Tarantool как App Server, Cache, Storage.

Улучшаем cache и storage




У Tarantool есть несколько интересных свойств. Tarantool — это не только эффективная inmemory DB, но и полноценный Application Server, приложения пишутся на Lua (luajit), C, C++, т.е. можно написать логику любой сложности, ограничение одно: фантазия. Если данных больше, чем доступно памяти, часть данных можно хранить на диске, используя движок Sophia. Если Sophia не подходит, можно взять что-то другое и скидывать «холодные» данные, т.е. данные, которые не нужны прямо сейчас, из Tarantool в другой Storage, а «горячую» часть хранить в Tarantool, т.е. в памяти. Какие преимущества это дает нам?

  • Нет посредников. Как минимум горячая часть данных находится на одном уровне с кодом.
  • Горячие данные в памяти.
  • Код достаточно простой и легко обновляется, если мы говорим о Lua.
  • Транзакции, репликация, шардинг и множество других возможностей Tarantool.


Улучшаем web-server




Конечным потребителем данных является пользователь. Обычно, пользователь получает данные от Application Server через Nginx как балансер/прокси. Вариант написания демона, который умеет общаться и с Tarantool, и с HTTP не подходит, так как приведет нас к первому рисунку, и мы опять вернёмся к тому, с чего начали. Поэтому попробуем взглянуть на ситуацию с другой стороны, и задать другой вопрос: «Как избавиться от посредников между данными и пользователем?». Ответом на этот вопрос и стала реализация Tarantool Nginx Upstream Module.

Nginx Upstream


Nginx Upstream — это персистентное (см. Upstream Keepalive) соединение через pipe/socket к backend, далее будем называть это «проксированием». Nginx предоставляет много разнообразного функционала для написания правил Upstream, для проксирования HTTP в Tarantool особое значение приобретают следующие возможности:

  1. возможность указывать несколько backend, на которые Nginx будет балансировать нагрузку;
  2. возможность указывать backup, т.е. указывать, куда ходить, если Upstream не работает.

Эти возможности позволяют:

  1. распределять нагрузку на N Tarantool, например, вкупе с шардингом можно построить кластер с равномерной загрузкой по нодам;
  2. можно сделать отказоустойчивую систему при помощи репликации;
  3. используя п. а) и п. b) получим отказоустойчивый кластер.

Пример конфига для Nginx, частично иллюстрирующий возможности настроек:

   # Настройки проксирования в Tarantool
   upstream tnt
   {
      server  127.0.0.1:10001;      # первый сервер живет на localhost
      server  node.com:10001;     # второй где-то еще
      server  unix:/tmp/tnt;           # третий через unix socket

      server  node.backup.com  backup; # а тут backup
    }

   # HTTP-сервер
    server
    {
        listen 8081 default;
        location = /tnt/pass {
          # Говорим Nginx что надо использовать Tarantool Upstream Module
          # и указываем имя Upstream
          tnt_pass tnt; 
        }
    }

Более детально о конфигурировании Nginx Upstream можно прочитать тут: http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream.

Nginx Tarantool Upstream Module (v0.1.4 Stable)




Основной функционал:

  • модуль активируется в Nginx.conf директивой — tnt_pass UPSTREAM_NAME;
  • быстрое потоковое преобразование HTTP + JSON <-> Tarantool Protocol, минимальные блокировки (на время парсинга) Nginx worker;
  • неблокирующее I/O Nginx в оба направления;
  • как приятный бонус: все фичи Nginx, Nginx Upstream;
  • модуль позволяет вызывать хранимые процедуры Tarantool через JSON-based Protocol;
  • данные доставляются через HTTP(S) POST, что удобно для Modern WebApps и не только.

Входные данные


[ { "method": STR, "params":[arg0 ... argN], "id": UINT }, ...N ]

«method»
Имя хранимой процедуры. Имя должно совпадать с именем процедуры в Tarantool. Например, чтобы вызвать lua-функцию do_something(a, b), надо: “method”: “do_something”

«params»
Аргументы хранимой процедуры. Например, чтобы передать аргументы в lua-функцию do_something(a, b), надо: “params”: [ “1”, 2 ]

«id»
Числовой идентификатор, устанавливается клиентом.

Выходные данные


[ { "result": JSON_RESULT_OBJECT, "id":UINT, "error": { "message": STR, "code": INT } }, ...N ]


«result»
Данные, которые вернула хранимая процедура. Например, lua-функция do_something(a, b) возвращает return {1, 2} то “result”: [[1, 2]]

«id»
Числовой идентификатор, установленный клиентом.

«error»
Если произошла ошибка, в этом поле будут данные о причинах.

Более детальней о протоколе тут: https://github.com/tarantool/nginx_upstream_module/blob/master/README.md

Hello World


Запускаем Nginx


Nginx мы соберем из исходников:

$ git clone https://github.com/tarantool/nginx_upstream_module.git
$ cd nginx_upstream_module
$ git submodule update --init --recursive
$ git clone https://github.com/nginx/nginx.git
$ cd nginx && git checkout release-1.9.7 && cd -
$ make build-all-debug 

Цель build-all-debug — это debug-версия. Делаем так, чтобы меньше конфигурировать Nginx. Для тех, кто хочет законфигурировать все с нуля, есть цель build-all.

Файл test-root/conf/nginx.conf

http
{
    # Добавляет один Tarantool как backend
    upstream echo
    {
      server 127.0.0.1:10001;
    }
    server
    {
        listen 8081 default; # Nginx повесим на *:8081

        server_name tnt_test;

        location = /echo # на *:8081/echo вешаем ‘echo’ Tarantool Upstream
        {
          tnt_pass echo;
        }
    }
}


$ ./nginx/obj/nginx # запускаем nginx

Запускаем Tarantool


Tarantool можно поставить из пакетов, либо собрать.

Файл hello-world.lua

-- Это и есть наша хранимая процедура, она предельно простая и не использует Tarantool как DB.
-- Все что она делает - это просто возвращает свой 1-й аргумент.
function echo(a)
     return  {{a}}
end

box.cfg {
    listen = 10001; -- указываем куда вешаем Tarantool
}

Если вы поставили Tarantool из пакетов, запустить его можно так:

$ tarantool hello-world.lua # первым аргументом передаем имя lua-скрипта.

Вызываем хранимую процедуру


Вызвать хранимую процедуру echo можно любым HTTP-коннектором, все что нужно сделать — HTTP POST по 127.0.0.1/echo и в теле передать следующий JSON (см. Входные данные):

{
  "method":"echo", // имя метода, должно совпадать с именем метода в Tarantool
   "params":[
     {"Hello world": "!"} // 1-й аргумент - объект
   ],
   "id":1 // ID сообщения
}

Я вызову эту процедуру wget’ом

$ wget 127.0.0.1:8081/echo --post-data '{"method":"echo","params":[{"Hello world": "!"}],"id":1}'
$ cat echo
{"id":1,"result":[[{"hello world":"!"}]]}


Еще несколько примеров:
https://github.com/tarantool/nginx_upstream_module/blob/master/examples/echo.html
https://github.com/tarantool/nginx_upstream_module/blob/master/test/client.py
?

Подведем итоги


Плюсы использования Nginx Tarantool Upstream Module:

  • нет посредников, код и данные, как правило, на одном уровне;
  • относительно простое конфигурирование;
  • балансировка нагрузки на N Tarantool;
  • высокая скорость работы, низкая latency;
  • JSON-based протокол вместо бинарного, не надо искать Tarantool Driver, JSON есть везде;
  • Tarantool Sharding/Replication и Nginx = кластерное решение, но это тема отдельной статьи;
  • решение используется в продакшене.

Минусы:

  • Overhead JSON вместо более компактного и быстрого MsgPack;
  • решение не коробочное, нужно конфигурировать, нужно думать, как деплоить.

Планы:

  • поддержка OpenRеsty и nginScript;
  • поддержка WebSocket и HTTP 2.0.


Результаты бенчмарка, а они очень даже интересные, будут в другой статье. Tarantool, как и Upstream Module, всегда открыт для новых пользователей, если у вас есть желание это все попробовать, использовать или выразить новую идею — обращайтесь на github, google group.

Ссылки


Сайт Tarantool — http://tarantool.org
Git Tarantool — https://github.com/tarantool/tarantool
Git Tarantool Nginx Upstream Module — github.com/tarantool/nginx_upstream_module
Google group — https://groups.google.com/forum/#!forum/tarantool

P.S. В следующей статье я покажу, какие задачи можно решить, используя Tarantool.

Комментарии (21)


  1. daron666
    02.12.2015 14:16
    +2

    Привет! С дебютом. А почему всё-таки LUA? Ну т.е. чем вы руководствовались когда его выбирали?


    1. rtsisyk
      02.12.2015 14:53
      +2

      1. В те времена, когда начиналась разработка Tarantool (а это даже раньше 2009 года), альтернатив не было видно даже на горизонте.
      2. Lua очень простой и компактный язык, не требующий изучения двадцати томов большой советской энциклопедии для начала работы. По большему секрету скажу, что ни в Mail.Ru, ни у других пользователей Tarantool нет специально обученных Lua-программистов для работы с Tarantool. С написанием хранимок на Lua одинаково легко справляются как JS, так и PHP/Perl/Ruby/Go/whatever разработчики.
      3. Мы пробовали встраивать V8 где-то с годик назад. По началу в синтетических тестах все казалось очень быстрым. Когда же реализовали простейший биндинг к space:select() — v8 стал сливать Lua по производительности примерно в раза 3-4. Каждое создание объекта в v8 делалось настолько долго, что ни о какой высокопроизводительной базе данных с хранимками уже особо не могли идти и речи. Мораль сей басни такова, что если в виртуальной машине быстрая числодробилка, это никак гарантирует быстрой производительности в целом на реальных задачах.

      В Tarantool 1.6.7 мы также открыли C API, дав возможность написания хранимок на C/C++ и других языках. Сейчас в качестве разминки пальцев можно взять данное API и заново попробовать уже на нём запустить какой-нибудь еще язык. Вопрос только какой?


      1. daron666
        02.12.2015 14:55

        Haskell конечно же!


        1. rtsisyk
          02.12.2015 15:00

          Если его не разнесёт от переключения Сишных стеков в корутинах, то можно попробовать уже сейчас.
          Shared libraries (.so) мы уже умеем загружать, даже по протоколу через CALL можем вызывать по имени функции.
          Достаточно лишь биндинги наваять.


        1. ilnarb
          02.12.2015 16:03
          +1

          Судя по githut.info
          Активность языка Lua заметно выше чем у Haskell


          1. rtsisyk
            02.12.2015 16:14
            +4

            Так то там и vimL есть с активностью больше Go и Perl, но от этого конфиги вима автоматом не стали удобным языком программирования.


          1. samokhvalov
            06.12.2015 04:22

            Активных репозиториев у Haskell больше, нет? по той диаграмме.
            Пушей меньше, и сильно меньше тикетов — но живых проектов-то чуть больше. Меньше багов, коммиты посодержательнее :)


  1. ilnarb
    02.12.2015 15:51
    +1

    Nginx модуль в интерфейсе для клиента получается привязанным к модели JSON RPC сервера.
    Это означает, что даже для простого получения данных необходимо формировать вызов метода.
    В данный момент популярно/молодёжно/втренде REST API.
    Было бы неплохо реализовать такую модель работы: имя метода в пути URI или зашит в конфиге nginx, а параметры берутся из URI.
    Еще неплохо было бы где-то встроить поддержку oauth2.


    1. dedokOne
      02.12.2015 15:59

      Ценное замечание. REST хотят многие, сделаем в след. версии.


    1. zloidemon
      02.12.2015 16:37

      Можно сколько угодно говорить про REST, в особенности после прочтения этого. JSON RPC хороший и простой стандарт, где не надо тратить много время на ненужную работу.


      1. dedokOne
        02.12.2015 16:46
        +1

        Одно не исключает другое, как отдельные фичи. Плюс такие запросы есть от членов комьюнити. Да и реализовать такое не сложно так же это даст кучу новых возможностей в nginx.conf. Так что эта фича будет точно.


      1. rtsisyk
        02.12.2015 17:00

        Дак модуль и реализует практически JSON RPC.


        1. dedokOne
          02.12.2015 18:14

          Ага, но мы про REST, как я понял zloidemon его не любит.


  1. ksdaemon
    02.12.2015 19:13
    +1

    Кстати, про Tarantool, почему LUA, новый вид апп-серверов на базе nginx и tarantool'а и прочее мы общались с Костей Осиповым у меня в SDCast'е #20: sdcast.ksdaemon.ru/2015/03/sdcast-20


    1. rtsisyk
      02.12.2015 21:36
      +1

      Я уже подготовил статью по данной теме, как вычитаю и откорректирую — размещу на хабре.


  1. stanlee
    03.12.2015 11:58

    зачем LUA, когда ввели js
    это было бы куда интереснее


    1. dedokOne
      03.12.2015 12:52

      rtsisyk ответил ранее.
      Добавлю от себя, сейчас есть возможность сделать биндинги в другие языки, достаточно просто, главное требование: язык, в который биндим, не должно разносить от С-шных корутин.
      Кое кто даже _планирует_ попробовать js, а там как пойдет.
      И не надо боятся lua он простой но, конечно, со своими приколами.


    1. zloidemon
      03.12.2015 13:31
      +2

      Чем же интересней? Может тогда сразу perl/python/php?


      1. rtsisyk
        05.12.2015 12:35

        Сразу Swift!


  1. Shoonoise
    05.12.2015 19:19

    Что все к LUA прицепились? Нормальный встраиваемый язык. Только с библиотеками всё плохо, конечно. Не понятно как писать на нём что-то большое (то есть понятно как — писать всё самим, но не понятно зачем).

    Вначале статьи есть подводка почему использование связки nginx + tarantool вместо традиционных способов лучше.
    А плюсы в конце статьи как то вообще про другое. Близость кода и данных какое-то сомнительное достоинство.

    А LUA, как я понимаю, в один поток выполняется на сервере?


    1. dedokOne
      05.12.2015 23:13
      +1

      В Tarantool, для lua свой отдельный, единственный поток.
      Про 'Близость кода к данным' это неоспоримое достоинство, например, в случаях:
      1) данных много, гонять их по сети дорого, долго;
      2) важна latency.