company_banner

Строим сервисы на базе Nginx и Tarantool

    Вам знакома такая архитектура? Хоровод демонов, пляшущих между 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.
    Mail.Ru Group 752,07
    Строим Интернет
    Поделиться публикацией
    Комментарии 22
    • +2
      Привет! С дебютом. А почему всё-таки LUA? Ну т.е. чем вы руководствовались когда его выбирали?
      • +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 и заново попробовать уже на нём запустить какой-нибудь еще язык. Вопрос только какой?
        • 0
          Haskell конечно же!
          • 0
            Если его не разнесёт от переключения Сишных стеков в корутинах, то можно попробовать уже сейчас.
            Shared libraries (.so) мы уже умеем загружать, даже по протоколу через CALL можем вызывать по имени функции.
            Достаточно лишь биндинги наваять.
            • +1
              Судя по githut.info
              Активность языка Lua заметно выше чем у Haskell
              • +4
                Так то там и vimL есть с активностью больше Go и Perl, но от этого конфиги вима автоматом не стали удобным языком программирования.
                • 0
                  Активных репозиториев у Haskell больше, нет? по той диаграмме.
                  Пушей меньше, и сильно меньше тикетов — но живых проектов-то чуть больше. Меньше багов, коммиты посодержательнее :)
          • +1
            Nginx модуль в интерфейсе для клиента получается привязанным к модели JSON RPC сервера.
            Это означает, что даже для простого получения данных необходимо формировать вызов метода.
            В данный момент популярно/молодёжно/втренде REST API.
            Было бы неплохо реализовать такую модель работы: имя метода в пути URI или зашит в конфиге nginx, а параметры берутся из URI.
            Еще неплохо было бы где-то встроить поддержку oauth2.
            • 0
              Ценное замечание. REST хотят многие, сделаем в след. версии.
              • 0
                Можно сколько угодно говорить про REST, в особенности после прочтения этого. JSON RPC хороший и простой стандарт, где не надо тратить много время на ненужную работу.
                • +1
                  Одно не исключает другое, как отдельные фичи. Плюс такие запросы есть от членов комьюнити. Да и реализовать такое не сложно так же это даст кучу новых возможностей в nginx.conf. Так что эта фича будет точно.
                  • 0
                    Дак модуль и реализует практически JSON RPC.
                    • 0
                      Ага, но мы про REST, как я понял zloidemon его не любит.
                • +1
                  Кстати, про Tarantool, почему LUA, новый вид апп-серверов на базе nginx и tarantool'а и прочее мы общались с Костей Осиповым у меня в SDCast'е #20: sdcast.ksdaemon.ru/2015/03/sdcast-20
                  • +1
                    Я уже подготовил статью по данной теме, как вычитаю и откорректирую — размещу на хабре.
                  • 0
                    зачем LUA, когда ввели js
                    это было бы куда интереснее
                    • 0
                      rtsisyk ответил ранее.
                      Добавлю от себя, сейчас есть возможность сделать биндинги в другие языки, достаточно просто, главное требование: язык, в который биндим, не должно разносить от С-шных корутин.
                      Кое кто даже _планирует_ попробовать js, а там как пойдет.
                      И не надо боятся lua он простой но, конечно, со своими приколами.
                      • 0
                        Может он и прост, но не массовый, а это может быть большим препоном для выхода в массы.
                      • +2
                        Чем же интересней? Может тогда сразу perl/python/php?
                    • 0
                      Что все к LUA прицепились? Нормальный встраиваемый язык. Только с библиотеками всё плохо, конечно. Не понятно как писать на нём что-то большое (то есть понятно как — писать всё самим, но не понятно зачем).

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

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

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое