Tarantool как сервер приложений
Привет, %хабраюзер%. Команда Тарантула продолжает делиться инсайтами и экспертизой для эффективной работы с данными в высоконагруженных проектах. Сегодня мы попытаемся разобраться, почему же Tarantool — это «два в одном»: не только база данных, но и сервер приложений. Наверное, некоторые слышали о Тарантуле как о сверхбыстром персистентном in-memory хранилище с поддержкой репликации и хранимок на Lua. Представьте, что мы берём кусочки Redis, добавляем замороженный Node.js, сверху заправляем Go, после чего варим, медленно перемешивая, в течение пяти минут после закипания. Казалось бы, при чём здесь Application Server?
Многие, наверное, будут удивлены, но такие отличные продукты, как nginx, Go, Node.js, Redis, MongoDB, Tarantool и др., имеют много общего в архитектурном плане. Для создания любого высокопроизводительного сетевого сервера так или иначе требуется набор библиотек, обеспечивающий неблокирующий ввод-вывод, асинхронную обработку событий, работу с памятью, обработку ошибок, логирование, демонизацию и т. п. и т. д. Данный движок (runtime) обычно представляет собой достаточно сложную штуку, требующую глубокого понимания основ работы различных систем и навыков низкоуровневого программирования.
Мы в команде Tarantool прошли очень длинный путь, создав свой runtime с асинхронной обработкой событий, неблокирующими I/O, зелёными тредами в user-space (файберами), кооперативной многозадачностью, семейством специализированных аллокаторов памяти и пр., и пр. Получилось что-то архитектурно больше всего похожее на Go (у глупцов мысли совпадают), но только в виде библиотек для чистого C. Благодаря данной основе «под капотом» удалось создать СУБД, которая сегодня способна обрабатывать до 6M запросов в секунду на одном ядре рядового ноутбука и имеет лучший memory footprint на рынке. В какой-то момент мы решили дать неограниченную свободу разработчикам приложений, разрешив в хранимых процедурах делать не только запросы к базе, но и использовать весь наш инструментарий по полной программе. Получить и распарсить JSON«чик по HTTP с облачного сервиса прямо из базы данных — легко. Запустить свой REST-сервис прямо в СУБД — пожалуйста. Сходить за данными на десяток серверов, параллельно обрабатывая запросы, — без проблем! Все возможности и инструменты в руки разработчиков! Developers, developers, developers!
Наc очень часто спрашивают, есть ли в Tarantool очереди, expiration данных, pub/sub, multiget или ещё что-нибудь из сотен команд Redis«а. Нет, в Tarantool ничего этого нет, не было и не будет (сорян, котаны). Мы отказались от подобного пути и предлагаем немного другой подход. Как известно, дай человеку рыбу, и он будет сыт один день, научи его ловить рыбу, и он будет сыт всегда. Tarantool предоставляет инструменты («удочку»), с помощью которых можно решать различные задачи, в том числе и выходящие из разряда шаблонов и паттернов. Автоматическое удаление старых данных — фоновый файбер на Lua. Multiget из нескольких таблиц по определённому условию — две строчки Lua. Отдача данных в формате JSON — загрузить модуль веб-сервера прямо в базу данных. Think out of the box!
Посмотрим, что же за инструменты предоставляет Tarantool:
- fiber — кооперативная многозадачность и каналы (как в Go);
- socket, fio — асинхронный неблокирующий сокетный и файловый ввод-вывод;
- json/msgpack/yaml — упаковщики-распаковщики данных;
- mysql/pg/whatever — клиенты к MySQL, PostgreSQL из самого Тарантула;
- net.box — клиент к Тарантулу из самого Тарантула;
- http — примитивный HTTP сервер и клиент;
- tap — тестирование для приложений и модулей;
- console — инспектирование состояния сервера, горячая загрузка кода, изменение настроек на лету;
- log — логирование событий, logrotate;
- init-скрипты, RPM/DEB-пакеты, средства развертывания и др.
В роли вишенки на торте выступает box — супербыстрая multi-engine база данных с поддержкой транзакций и multi-master репликации, работающая прямо в том же адресном пространстве, что и приложения.
«Неужели опять предлагается переносить всю бизнес-логику в базу данных, вместо отдельного application server (Node.js, PHP, Python, whatever) и СУБД (Redis, MongoDB и т. п.)?» — резонно возразите вы. Нет, мы не покушаемся на основы мироздания. Давайте лишь посмотрим чуть более прагматично. Во времена, когда космиче приложения можно писать уже прямо в браузере, серверная часть остаётся по большей мере для хранения и обработки данных, тогда как браузер может запрашивать и обновлять всю необходимую информацию динамически через AJAX. Что же в таком раскладе делает ваш application server (PHP/Node/Go/Python)? Простаивает в ожидании ответа от базы, чтобы потом сразу отдать всё это в виде JSON в nginx? А ведь надо открыть транзакцию, после этого по сети выкачать данные из базы, поменять в них какие-то поля в application server, послать обновления обратно в базу данных, закрыть транзакцию и вернуть результат в nginx. Сколько лишних сетевых round trip и переключений userspace <-> kernel <-> userspace мы при этом потратим? А ещё ведь база данных должна на каждую открытую транзакцию поддерживать для вас целостный read-view данных, например ценой использования локов или других, не менее тяжеловесных механизмов. И всё это ради того, чтобы выбрать пять записей из одной таблички, обновить две записи в другой и вернуть результат в наш REST-сервис?
Для подобного рода микросервисов Тарантул предлагает удоч написать хранимку прямо рядом с самими данными, внутри СУБД. Модуль tarantool-http и nginx_tarantool_upstream легко организуют REST-сервис из Тарантула, упрощая архитектуру сервиса и убирая лишнее звено в виде выделенного application server. При этом никто не предлагает таким образом переписывать всё приложение, ведь можно выделить в микросервис лишь самые нагруженные части проекта, где запаса производительности традиционных решений уже не хватает. Для остального можно использовать тот же Tarantool как СУБД общего назначения через коннекторы из различных языков программирования.
«Мы не умеем писать на Lua! Да и где же взять таких программистов?» — спросите вы. Don«t panic! Lua (португ. луна) — простой как три копейки язык, не требующий для работы изучать собрание сочинений авторов в десяти томах. Посмотрели на примеры вместо просмотра рекламы в метро — и уже можно начинать грабить коро кодить. В Mail.Ru Group процедурки на Lua одинаково успешно пишут как программисты на C/C++, так и Python-, Perl-, Ruby- и JS-девелоперы. Но почему же Lua? Tarantool даёт разработчику настоящий Тьюринг-полный язык программирования высокого уровня, позволяющий решать любые задачи. Скажем своё твёрдое железное нет программированию на PL/SQL, XML, YAML и ini-файлах (прости господи). Кроме того, Lua крайне простой и очень быстро работает. С Lua нет споров о том, какие же 10% функциональности языка разрешить использовать в проекте.
Кстати, Tarantool также имеет сишный API, что позволяет достигать невиданной ранее производительности и, в теории, возможности использовать любые другие языки. Мы расскажем об этом подробнее в следующих сериях, не отключайтесь.
Устанавливаем Tarantool со страницы tarantool.org/download.html. В репозиториях на сайте есть бинарные пакеты под основные Linux-дистрибутивы, а также порты для FreeBSD и brew для OS X. После установки вводим в консольке команду tarantool
, которая по умолчанию запускает интерактивную консоль (как Python, Node, irb и др.):
roman@book:~$ tarantool
tarantool: version 1.6.8-123-gbe2ce21
type 'help' for interactive help
tarantool>
В интерпретаторе можно вводить произвольный Lua-код, а результат выполнения будет выведен в читабельном формате (YAML) в консольку:
tarantool> 2 + 2
---
- 4
...
tarantool> { name = "Roman", gender = "male" }
---
- name: Roman
gender: male
...
tarantool> print('Hello')
Hello
---
...
Всё то же самое можно написать в виде скриптика в отдельном файле:
#!/usr/bin/env tarantool
print(‘Hello world!’)
Запускаем скрипт аналогично Bash, Python или Ruby:
roman@desktop:~$ edit ololo.lua
roman@desktop:~$ chmod a+x ololo.lua
roman@desktop:~$ ./ololo.lua
Hello world!
Tarantool полностью совместим с Lua 5.1 и LuaJIT на уровне скриптов и может использоваться как drop-in replacement. Все модули от Lua работают в Tarantool.
Функция box.cfg{}
конфигурирует и запускает встроенную базу данных (box), после чего можно создавать таблицы (space) и выполнять запросы:
tarantool> box.cfg {}
[cut]
tarantool> space = box.schema.space.create('test')
[cut]
tarantool> box.space.test:create_index('primary', { type = 'tree', parts = { 1, 'num' }})
[cut]
tarantool> box.space.test:insert({48, 'some data', { key = 'value', key2 = 'value2' }})
---
- [48, 'some data', {'key': 'value', 'key2': 'value2'}]
...
tarantool> box.space.test:select()
---
- - [48, 'some data', {'key': 'value', 'key2': 'value2'}]
...
Если же теперь остановить интерактивную консоль (через CTRL+D или os.exit(0)
), то в каталоге можно увидеть новые файлы *.snap, *.xlog. Данные файлы используются для обеспечения persistence нашей базы данных в оперативной памяти. Повторный же запуск tarantool
восстановит все данные:
tarantool> box.cfg{}
---
...
tarantool> box.space.test:select()
---
- - [48, 'some data', {'key': 'value', 'key2': 'value2'}]
...
А теперь попробуем что-нибудь более сложное (пример с главной страницы):
#!/usr/bin/env tarantool
box.cfg{}
-- Создаёт таблички при первом запуске
box.once('schema', function()
box.schema.create_space('hosts')
box.space.hosts:create_index('primary', { type = 'hash',
parts = {1, 'str'} })
end)
-- Обработчик GET-запросов к /
local function handler(self)
-- Получаем IP-адрес клиента
local ipaddr = self.peer.host
-- Вставляем новую запись для адреса или инкрементируем существующую
box.space.hosts:upsert({ ipaddr, 1 }, {{'+', 2, 1}})
-- Возвращаем все записи в виде JSON клиенту
return self:render{ json = box.space.hosts:select() }
end
local httpd = require('http.server')
local server = httpd.new('127.0.0.1', 8080)
server:route({ path = '/' }, handler)
server:start()
Для запуска необходим модуль tarantool-http, который можно поставить из пакетов или из GitHub. Скрипт при первом запуске создаст таблицу hosts
, после чего будет запущен HTTP-сервер, который на `/` будет инкрементировать counter для каждого IP-адреса и возвращать клиенту все адреса в формате JSON. Так же легко можно поставить перед сервисом nginx. Кстати, try.tarantool.org написан на самом Tarantool. Мы сами кушаем свои кактусы и стараемся делать жизнь разработчиков лучше.
Как же лучше разместить наше простенькое приложение на рабочем сервере? Ведь одно дело в консольках поиграться, и совсем другое — запустить всё это в бой. Всё просто. Переносим скриптец в /etc/tarantool/instances.enabled/myapp.lua
и запускаем уже через готовые утилиты для init (tarantoolctl start myapp
или даже service tarantool restart
). Работает! Просто?
Приложений можно сделать сколько угодно много, init-система сама будет запускать нужное количество демонов Tarantool и следить за ними. Мы рекомендуем запускать чуть меньше тарантулов, чем вы имеете физических ядер. Данный подход позволит обеспечить наилучшие показатели производительности на один сервер и сэкономить миллионы долларов. В списке процессов легко можно найти демон с именем вашего приложения. Log-файлы по умолчанию пишутся в /var/log/tarantool/myapp.log
, данные хранятся в /var/lib/tarantool/myapp/
, а pid-файл пишется в /run/tarantool
. Иными словами, всё именно так, как задумано в вашем любимом дистрибутиве. Для сборки RPM- и DEB-пакетов можно воспользоваться нашим шаблоном.
Из полезного стоит отдельно отметить команду tarantoolctl enter myapp
, которая позволяет подключаться консолькой к работающему демону для интроспекции состояния и изменения кода на лету. Также через box.cfg({listen = 3313 })
можно открыть сетевой порт для подключения коннекторами из других языков программирования и фреймворков (мы же вам обещали, что не будем ломать всё мироустройство!).
В следующих сериях мы расскажем более подробно, как обеспечить модульную архитектуру вашего приложения, организовать тестирование и непрерывную интеграцию кода. Также будет раскрыто секретное know-how по приготовлению Тарантула для обработки до 6М запросов в секунду на одном физическом ядре (seriously).
Ждём вопросов и комментариев.
З. Ы. 28 января мы проводим второй Tarantool Meetup в суперсовременном офисе Mail.Ru Group на м. Аэропорт. На встрече будут новые инсайты как от нашей команды, так и от внешних пользователей Тарантула. Вход бесплатный после регистрации, все ништяки включены. С вас хорошее настроение и желание попробовать Tarantool в своих проектах.