Какие минусы такой архитектуры можно отметить? Решая задачи в рамках такой архитектуры, мы сталкиваемся с кучей вопросов: какой язык(и?) взять, какой 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 особое значение приобретают следующие возможности:
- возможность указывать несколько backend, на которые Nginx будет балансировать нагрузку;
- возможность указывать backup, т.е. указывать, куда ходить, если Upstream не работает.
Эти возможности позволяют:
- распределять нагрузку на N Tarantool, например, вкупе с шардингом можно построить кластер с равномерной загрузкой по нодам;
- можно сделать отказоустойчивую систему при помощи репликации;
- используя п. а) и п. 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)
ilnarb
02.12.2015 15:51+1Nginx модуль в интерфейсе для клиента получается привязанным к модели JSON RPC сервера.
Это означает, что даже для простого получения данных необходимо формировать вызов метода.
В данный момент популярно/молодёжно/втренде REST API.
Было бы неплохо реализовать такую модель работы: имя метода в пути URI или зашит в конфиге nginx, а параметры берутся из URI.
Еще неплохо было бы где-то встроить поддержку oauth2.zloidemon
02.12.2015 16:37Можно сколько угодно говорить про REST, в особенности после прочтения этого. JSON RPC хороший и простой стандарт, где не надо тратить много время на ненужную работу.
dedokOne
02.12.2015 16:46+1Одно не исключает другое, как отдельные фичи. Плюс такие запросы есть от членов комьюнити. Да и реализовать такое не сложно так же это даст кучу новых возможностей в nginx.conf. Так что эта фича будет точно.
ksdaemon
02.12.2015 19:13+1Кстати, про Tarantool, почему LUA, новый вид апп-серверов на базе nginx и tarantool'а и прочее мы общались с Костей Осиповым у меня в SDCast'е #20: sdcast.ksdaemon.ru/2015/03/sdcast-20
rtsisyk
02.12.2015 21:36+1Я уже подготовил статью по данной теме, как вычитаю и откорректирую — размещу на хабре.
stanlee
03.12.2015 11:58зачем LUA, когда ввели js
это было бы куда интереснееdedokOne
03.12.2015 12:52rtsisyk ответил ранее.
Добавлю от себя, сейчас есть возможность сделать биндинги в другие языки, достаточно просто, главное требование: язык, в который биндим, не должно разносить от С-шных корутин.
Кое кто даже _планирует_ попробовать js, а там как пойдет.
И не надо боятся lua он простой но, конечно, со своими приколами.
Shoonoise
05.12.2015 19:19Что все к LUA прицепились? Нормальный встраиваемый язык. Только с библиотеками всё плохо, конечно. Не понятно как писать на нём что-то большое (то есть понятно как — писать всё самим, но не понятно зачем).
Вначале статьи есть подводка почему использование связки nginx + tarantool вместо традиционных способов лучше.
А плюсы в конце статьи как то вообще про другое. Близость кода и данных какое-то сомнительное достоинство.
А LUA, как я понимаю, в один поток выполняется на сервере?dedokOne
05.12.2015 23:13+1В Tarantool, для lua свой отдельный, единственный поток.
Про 'Близость кода к данным' это неоспоримое достоинство, например, в случаях:
1) данных много, гонять их по сети дорого, долго;
2) важна latency.
daron666
Привет! С дебютом. А почему всё-таки LUA? Ну т.е. чем вы руководствовались когда его выбирали?
rtsisyk
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 и заново попробовать уже на нём запустить какой-нибудь еще язык. Вопрос только какой?
daron666
Haskell конечно же!
rtsisyk
Если его не разнесёт от переключения Сишных стеков в корутинах, то можно попробовать уже сейчас.
Shared libraries (.so) мы уже умеем загружать, даже по протоколу через CALL можем вызывать по имени функции.
Достаточно лишь биндинги наваять.
ilnarb
Судя по githut.info
Активность языка Lua заметно выше чем у Haskell
rtsisyk
Так то там и vimL есть с активностью больше Go и Perl, но от этого конфиги вима автоматом не стали удобным языком программирования.
samokhvalov
Активных репозиториев у Haskell больше, нет? по той диаграмме.
Пушей меньше, и сильно меньше тикетов — но живых проектов-то чуть больше. Меньше багов, коммиты посодержательнее :)