![](https://habrastorage.org/webt/kw/j7/fd/kwj7fdn4wlfdatsf5joghqb3we8.jpeg)
Работая над несколькими open-source проектами, в один прекрасный день я решил упростить себе жизнь и разработал Upstream-модуль для nginx, который помог мне убрать громоздкие слои многослойной архитектуры. Это был забавный опыт, которым я хочу поделиться в этой статье. Мой код лежит в открытом доступе тут: github.com/tarantool/nginx_upstream_module. Его можно поднять с нуля или скачать Docker-образ по этой ссылке: hub.docker.com/r/tarantool/tarantool-nginx.
На повестке дня:
- Введение и теория.
- Как использовать эти технологии.
- Оценка производительности.
- Полезные ссылки.
Введение и теория
![](https://habrastorage.org/webt/10/rj/x5/10rjx5ayayibzqllg9-u3hdw-ei.png)
Вот так выглядит стандартная архитектура микросервисов. Запросы пользователей поступают через nginx на сервер приложений. На сервере есть бизнес-логика, с которой взаимодействуют пользователи.
Сервер приложений не хранит состояния объектов, поэтому их нужно хранить где-то еще. Для этого можно использовать базы данных. И не забывайте про кэш, который уменьшит задержку и обеспечит более быструю доставку контента.
Разобьем это на слои:
1-й слой — nginx.
2-й слой — сервер приложений.
3-й слой — кэш.
4-й слой — прокси базы данных. Этот прокси необходим для обеспечения отказоустойчивости и поддержания постоянного подключения к базе данных.
5-й слой — сервер базы данных.
Раздумывая об этих слоях, я придумал, как исключить некоторые из них. Зачем? Много причин. Мне нравятся простые понятные вещи; я не люблю поддерживать большое количество разных систем в prodution; и последнее, но не по значимости — чем меньше слоев, тем меньше точек отказа. В результате я сделал модуль Tarantool Upstream под nginx, который помог сократить количество слоёв до двух.
![](https://habrastorage.org/webt/_j/3g/4a/_j3g4akjywetwlgaayru26ot0vi.png)
Как Tarantool помогает уменьшить количество слоёв? Первый слой — nginx, второй, третий и пятый слои заменяет Tarantool. Четвертый слой — прокси базы данных — теперь в nginx. Фишка в том, что Tarantool — это база данных, кэш и сервер приложений, три в одном. Мой upstream-модуль связывает nginx и Tarantool друг с другом и позволяет им слаженно работать без других трёх слоёв.
![](https://habrastorage.org/webt/aj/fn/tx/ajfntx13aw0r1hsbopzn9soky50.png)
Вот так выглядит новый микросервис. Пользователь отправляет запрос к REST или JSON RPC в nginx с модулем Tarantool Upstream. Модуль может быть подключен напрямую к Tarantool, или нагрузка может быть сбалансирована на несколько серверов Tarantool. Между nginx и Tarantool мы используем эффективный протокол, в основе которого лежит MSGPack. Дополнительную информацию вы найдете в этой статье.
Еще можете перейти по этим ссылкам, чтобы загрузить Tarantool и модуль nginx. Но я бы посоветовал установить их через пакетный менеджер вашего дистрибутива или воспользоваться Docker-образом (
docker pull tarantool/tarantool-nginx
). Docker-образы: hub.docker.com/r/tarantool/tarantool
Tarantool NginX upstream module
Бинарные пакеты: Tarantool — Download
Исходный код: Tarantool
tarantool/nginx_upstream_module
Как использовать эти технологии
Вот пример файла nginx.conf. Как видите, это обычный upstream nginx. Тут у нас есть
tnt_pass
, прямо указывающий nginx, по какому пути расположить upstream tarantool.nginx-tnt.conf
http {
# upstream
upstream tnt {
server 127.0.0.1:3301;
keepalive 1000;
}
server {
listen 8081;
# gateway
location /api/do {
tnt_pass_http_request parse_args;
tnt_pass tnt;
}
}
}
Вот ссылки на документацию:
nginx.org/en/docs/http/ngx_http_upstream_module.html
github.com/tarantool/nginx_upstream_module/blob/master/README.md
Сконфигурировали связку nginx и Tarantool, что потом? Теперь нужно прописать функцию обработчика для нашего сервиса и разместить ее в файле. Я положил её в файл “app.lua”.
Вот ссылка на документацию Tarantool: tarantool.io/ru/doc/1.9/book/box/data_model/#index
-- Bootstrap Tarantool
box.cfg { listen='*:3301' }
-- Grants
box.once('grants', function()
box.schema.user.grant('guest', 'read,write,execute', 'universe')
end)
-- Global variable
hello_str = 'Hello'
-- function
function api(http_request)
local str = hello_str
if http_request.method == 'GET' then
str = 'Goodbye'
end
return 'first', 2, { str .. 'world!' }, http_request.args
end
Теперь рассмотрим код Lua.
Наш
Box.cfg {}
говорит Tarantool начать слушать порт 3301, но он может принимать и другие параметры.Box.once
говорит Tarantool вызывать какую-то функцию один раз.function api ()
— это функция, которую я скоро буду вызывать. Она принимает HTTP-запрос в качестве первого аргумента и возвращает массив значений.Я сохранил этот код в файл и назвал его “app.lua”. Выполнить его можно, просто запустив Tarantool-приложение.
$> tarantool app.lua
Вызовем нашу функцию с помощью GET-запроса. Я для этого использую “wget”. По умолчанию, “wget” сохраняет ответ в файл. И для чтения данных из файла я использую “cat”.
$ wget '0.0.0.0:8081/api/do?arg_1=1&arg_2=2'
$ cat do*
{ “id”:0, # — unique identifier of the request
“result”: [ # — is what our Tarantool function returns
[“first”], [2], [{
“request”:{“arg_2”:”2",”arg_1":”1"}
“1”:”Goodbye world!”
}]
]}
Оценка производительности
Оценка проводилась на данных из production. Входные данные — это большой JSON-объект. Средний размер такого объекта 2 Кб. Один сервер, 4-ядерный CPU, 90 Гб RAM, OS Ubuntu 14.04.1 LTS.
Для этого теста мы используем только один nginx worker. Этот worker — балансировщик с простым алгоритмом ROUND-ROBIN. Он балансирует нагрузку между двумя узлами Tarantool. Нагрузка масштабируется с помощью шардинга.
Эти графики показывают количество операций чтения в секунду. Верхний график показывает задержки (в миллисекундах).
![](https://habrastorage.org/webt/ji/ob/ef/jiobefhg74ac5e6u298d755qpak.png)
А эти графики показывают количество операций записи в секунду. Верхний график показывает задержки (в миллисекундах)
![](https://habrastorage.org/webt/m0/k1/_i/m0k1_i2pcdkqjsrbwckwpv6g4_e.png)
Впечатляюще!
В следующей статье я подробно расскажу про REST и JSON RPC.
Англоязычная версия статьи: hackernoon.com/shrink-the-number-of-tiers-in-a-multitier-architecture-from-5-to-2-c59b7bf46c86
Комментарии (7)
ebt
24.07.2018 01:59Соединение Nginx напрямую с БД выглядит интересно. И, кстати, уже есть похожий модуль для Postgres.
dedokOne Автор
24.07.2018 11:18Согласен полностью. Люблю такие модули позволяют многие вещи делать проще, а иногда сложней :)
ReklatsMasters
24.07.2018 08:06+1Мне кажется, что это не лучший подход. Он заставляет размазывать логику по приложению, а также подталкивает к программированию на конфигах. Для больших и сложных вещей у вас обязательно вернётся сервер приложений. А значит логика уже будет в 2х местах.
dedokOne Автор
24.07.2018 11:16Привет,
А давайте посчитаем, на примере систем построеных на PostgreSQL.
1-й вариант:
[DNS Balancer, (?)] + Nginx (конфиги + логика) + Application (конфиги + логика) + (Тут иногда HAProxy) + pgbouncer (конфиги) + (тут иногда HAProxy и еще один pgbouncer) + pg-кластер (конфиги + иногда хранимые процедуры — логика).
2-й вариант:
[DNS Balancer, (?)] + Nginx (конфиги) + Tarantool-кластер (конфиги + логика).
Оба подхода имеют место в современной архитектуре, какой вариант лучше — вопрос дискуссионный и, кмк, не является предметом данной дискуссии. Однако, очевидно — из сравнения выше -, что конфигов и логики во «2-м варианте» меньше.dedokOne Автор
24.07.2018 11:22PS
вышеописанные варианты не включаю в себя ряд компонентов и технологий, для обеспечения непрерывной работы высоконагруженной системы 24/7. Варианты описывают необходимый минимум, так сказать.
x67
>это большой JSON-объект
>2 Кб
Интересное у вас представление о больших объектах
dedokOne Автор
Спасибо за комент! Полностью согласен, однако с другой стороны назвать его маленький или средним тоже нельзя. Небольшая инсинуация всегда нужна :)