Уменьшаем количество слоёв архитектуры с 5 до 2 +26



Работая над несколькими open-source проектами, в один прекрасный день я решил упростить себе жизнь и разработал Upstream-модуль для nginx, который помог мне убрать громоздкие слои многослойной архитектуры. Это был забавный опыт, которым я хочу поделиться в этой статье. Мой код лежит в открытом доступе тут: github.com/tarantool/nginx_upstream_module. Его можно поднять с нуля или скачать Docker-образ по этой ссылке: hub.docker.com/r/tarantool/tarantool-nginx.

На повестке дня:

  • Введение и теория.
  • Как использовать эти технологии.
  • Оценка производительности.
  • Полезные ссылки.

Введение и теория




Вот так выглядит стандартная архитектура микросервисов. Запросы пользователей поступают через nginx на сервер приложений. На сервере есть бизнес-логика, с которой взаимодействуют пользователи.

Сервер приложений не хранит состояния объектов, поэтому их нужно хранить где-то еще. Для этого можно использовать базы данных. И не забывайте про кэш, который уменьшит задержку и обеспечит более быструю доставку контента.

Разобьем это на слои:

1-й слой — nginx.
2-й слой — сервер приложений.
3-й слой — кэш.
4-й слой — прокси базы данных. Этот прокси необходим для обеспечения отказоустойчивости и поддержания постоянного подключения к базе данных.
5-й слой — сервер базы данных.

Раздумывая об этих слоях, я придумал, как исключить некоторые из них. Зачем? Много причин. Мне нравятся простые понятные вещи; я не люблю поддерживать большое количество разных систем в prodution; и последнее, но не по значимости — чем меньше слоев, тем меньше точек отказа. В результате я сделал модуль Tarantool Upstream под nginx, который помог сократить количество слоёв до двух.



Как Tarantool помогает уменьшить количество слоёв? Первый слой — nginx, второй, третий и пятый слои заменяет Tarantool. Четвертый слой — прокси базы данных — теперь в nginx. Фишка в том, что Tarantool — это база данных, кэш и сервер приложений, три в одном. Мой upstream-модуль связывает nginx и Tarantool друг с другом и позволяет им слаженно работать без других трёх слоёв.



Вот так выглядит новый микросервис. Пользователь отправляет запрос к 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. Нагрузка масштабируется с помощью шардинга.

Эти графики показывают количество операций чтения в секунду. Верхний график показывает задержки (в миллисекундах).



А эти графики показывают количество операций записи в секунду. Верхний график показывает задержки (в миллисекундах)



Впечатляюще!

В следующей статье я подробно расскажу про REST и JSON RPC.

Англоязычная версия статьи: hackernoon.com/shrink-the-number-of-tiers-in-a-multitier-architecture-from-5-to-2-c59b7bf46c86




К сожалению, не доступен сервер mySQL