redis2: NGINX upstream модуль для протокола Redis 2.0
Установка
Вы можете установить этот модуль в любой дистрибутив, основанный на RHEL, включая, но не ограничиваясь:
- RedHat Enterprise Linux 7, 8, 9 и 10
- CentOS 7, 8, 9
- AlmaLinux 8, 9
- Rocky Linux 8, 9
- Amazon Linux 2 и Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install nginx-module-redis2
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install nginx-module-redis2
Включите модуль, добавив следующее в верхнюю часть файла /etc/nginx/nginx.conf:
load_module modules/ngx_http_redis2_module.so;
Этот документ описывает nginx-module-redis2 v0.15, выпущенный 19 апреля 2018 года.
location = /foo {
set $value 'first';
redis2_query set one $value;
redis2_pass 127.0.0.1:6379;
}
# GET /get?key=some_key
location = /get {
set_unescape_uri $key $arg_key; # это требует ngx_set_misc
redis2_query get $key;
redis2_pass foo.com:6379;
}
# GET /set?key=one&val=first%20value
location = /set {
set_unescape_uri $key $arg_key; # это требует ngx_set_misc
set_unescape_uri $val $arg_val; # это требует ngx_set_misc
redis2_query set $key $val;
redis2_pass foo.com:6379;
}
# множественные конвейерные запросы
location = /foo {
set $value 'first';
redis2_query set one $value;
redis2_query get one;
redis2_query set one two;
redis2_query get one;
redis2_pass 127.0.0.1:6379;
}
location = /bar {
# $ здесь не специальный...
redis2_literal_raw_query '*1\r\n$4\r\nping\r\n';
redis2_pass 127.0.0.1:6379;
}
location = /bar {
# переменные могут использоваться ниже, и $ специальный
redis2_raw_query 'get one\r\n';
redis2_pass 127.0.0.1:6379;
}
# GET /baz?get%20foo%0d%0a
location = /baz {
set_unescape_uri $query $query_string; # это требует модуля ngx_set_misc
redis2_raw_query $query;
redis2_pass 127.0.0.1:6379;
}
location = /init {
redis2_query del key1;
redis2_query lpush key1 C;
redis2_query lpush key1 B;
redis2_query lpush key1 A;
redis2_pass 127.0.0.1:6379;
}
location = /get {
redis2_query lrange key1 0 -1;
redis2_pass 127.0.0.1:6379;
}
Описание
Это модуль Nginx upstream, который позволяет nginx взаимодействовать с сервером Redis версии 2.x неблокирующим образом. Полный унифицированный протокол Redis 2.0 был реализован, включая поддержку конвейеризации Redis.
Этот модуль возвращает необработанный TCP-ответ от сервера Redis. Рекомендуется использовать мой lua-redis-parser (написанный на чистом C) для разбора этих ответов в структуру данных Lua при использовании вместе с lua-nginx-module.
При использовании вместе с lua-nginx-module рекомендуется использовать библиотеку lua-resty-redis вместо этого модуля, так как первая гораздо более гибкая и экономная по памяти.
Если вы хотите использовать только команду get Redis, вы можете попробовать HttpRedisModule. Он возвращает разобранную часть содержимого ответа Redis, так как только get необходим для реализации.
Еще один вариант - разобрать ответы Redis на стороне клиента самостоятельно.
Директивы
redis2_query
синтаксис: redis2_query cmd arg1 arg2 ...
по умолчанию: нет
контекст: location, location if
Укажите команду Redis, указав ее отдельные аргументы (включая само имя команды Redis) аналогично утилите redis-cli.
Разрешено несколько экземпляров этой директивы в одном location, и эти запросы будут конвейеризованы. Например,
location = /pipelined {
redis2_query set hello world;
redis2_query get hello;
redis2_pass 127.0.0.1:$TEST_NGINX_REDIS_PORT;
}
Тогда GET /pipelined даст два последовательных необработанных ответа Redis
+OK
$5
world
где символы новой строки на самом деле являются CR LF (\r\n).
redis2_raw_query
синтаксис: redis2_raw_query QUERY
по умолчанию: нет
контекст: location, location if
Укажите необработанные запросы Redis, и переменные nginx распознаются в аргументе QUERY.
В аргументе QUERY разрешена только одна команда Redis, иначе вы получите ошибку. Если вы хотите указать несколько конвейерных команд в одном запросе, используйте директиву redis2_raw_queries.
redis2_raw_queries
синтаксис: redis2_raw_queries N QUERIES
по умолчанию: нет
контекст: location, location if
Укажите N команд в аргументе QUERIES. Оба аргумента N и QUERIES могут принимать переменные Nginx.
Вот несколько примеров
location = /pipelined {
redis2_raw_queries 3 "flushall\r\nget key1\r\nget key2\r\n";
redis2_pass 127.0.0.1:6379;
}
# GET /pipelined2?n=2&cmds=flushall%0D%0Aget%20key%0D%0A
location = /pipelined2 {
set_unescape_uri $n $arg_n;
set_unescape_uri $cmds $arg_cmds;
redis2_raw_queries $n $cmds;
redis2_pass 127.0.0.1:6379;
}
redis2_literal_raw_query
синтаксис: redis2_literal_raw_query QUERY
по умолчанию: нет
контекст: location, location if
Укажите необработанный запрос Redis, но переменные Nginx в нем не будут распознаны. Другими словами, вы можете свободно использовать символ доллара ($) в аргументе QUERY.
В аргументе QUERY разрешена только одна команда Redis.
redis2_pass
синтаксис: redis2_pass <upstream_name>
синтаксис: redis2_pass <host>:<port>
по умолчанию: нет
контекст: location, location if
фаза: content
Укажите серверный бэкенд Redis.
redis2_connect_timeout
синтаксис: redis2_connect_timeout <time>
по умолчанию: 60s
контекст: http, server, location
Тайм-аут для подключения к серверу Redis, по умолчанию в секундах.
Рекомендуется всегда явно указывать единицу времени, чтобы избежать путаницы. Поддерживаемые единицы времени: s(секунды), ms(миллисекунды), y(годы), M(месяцы), w(недели), d(дни), h(часы) и m(минуты).
Это время должно быть меньше 597 часов.
redis2_send_timeout
синтаксис: redis2_send_timeout <time>
по умолчанию: 60s
контекст: http, server, location
Тайм-аут для отправки TCP-запросов на сервер Redis, по умолчанию в секундах.
Рекомендуется всегда явно указывать единицу времени, чтобы избежать путаницы. Поддерживаемые единицы времени: s(секунды), ms(миллисекунды), y(годы), M(месяцы), w(недели), d(дни), h(часы) и m(минуты).
redis2_read_timeout
синтаксис: redis2_read_timeout <time>
по умолчанию: 60s
контекст: http, server, location
Тайм-аут для чтения TCP-ответов от сервера Redis, по умолчанию в секундах.
Рекомендуется всегда явно указывать единицу времени, чтобы избежать путаницы. Поддерживаемые единицы времени: s(секунды), ms(миллисекунды), y(годы), M(месяцы), w(недели), d(дни), h(часы) и m(минуты).
redis2_buffer_size
синтаксис: redis2_buffer_size <size>
по умолчанию: 4k/8k
контекст: http, server, location
Этот размер буфера используется для чтения ответов Redis, но не обязательно должен быть таким большим, как самый большой возможный ответ Redis.
Этот размер по умолчанию соответствует размеру страницы, может быть 4k или 8k.
redis2_next_upstream
синтаксис: redis2_next_upstream [ error | timeout | invalid_response | off ]
по умолчанию: error timeout
контекст: http, server, location
Укажите, какие условия сбоя должны привести к перенаправлению запроса на другой upstream сервер. Применяется только тогда, когда значение в redis2_pass является upstream с двумя или более серверами.
Вот искусственный пример:
upstream redis_cluster {
server 127.0.0.1:6379;
server 127.0.0.1:6380;
}
server {
location = /redis {
redis2_next_upstream error timeout invalid_response;
redis2_query get foo;
redis2_pass redis_cluster;
}
}
Пул соединений
Вы можете использовать отличный HttpUpstreamKeepaliveModule с этим модулем для обеспечения пула TCP-соединений для Redis.
Пример конфигурации выглядит следующим образом
http {
upstream backend {
server 127.0.0.1:6379;
# пул с максимум 1024 соединениями
# и не различает серверы:
keepalive 1024;
}
server {
...
location = /redis {
set_unescape_uri $query $arg_query;
redis2_query $query;
redis2_pass backend;
}
}
}
Выбор баз данных Redis
Redis предоставляет команду select для переключения баз данных Redis. Эта команда не отличается от других обычных команд, таких как get или set. Поэтому вы можете использовать их в директивах redis2_query, например,
redis2_query select 8;
redis2_query get foo;
Совместимость с Lua
Этот модуль может служить неблокирующим клиентом redis2 для lua-nginx-module (но в настоящее время рекомендуется использовать библиотеку lua-resty-redis, которая гораздо проще в использовании и более эффективна в большинстве случаев). Вот пример использования подзапроса GET:
location = /redis {
internal;
# set_unescape_uri предоставляется ngx_set_misc
set_unescape_uri $query $arg_query;
redis2_raw_query $query;
redis2_pass 127.0.0.1:6379;
}
location = /main {
content_by_lua '
local res = ngx.location.capture("/redis",
{ args = { query = "ping\\r\\n" } }
)
ngx.print("[" .. res.body .. "]")
';
}
Тогда доступ к /main даст
[+PONG\r\n]
где \r\n это CRLF. То есть, этот модуль возвращает необработанные TCP-ответы от удаленного сервера Redis. Для разработчиков приложений на Lua они могут захотеть использовать библиотеку lua-redis-parser для разбора таких необработанных ответов в структуры данных Lua.
При перемещении встроенного кода Lua в внешний файл .lua важно использовать последовательность экранирования \r\n напрямую. Мы использовали \\r\\n выше только потому, что код Lua сам нуждается в кавычках, когда помещается в строковый литерал Nginx.
Вы также можете использовать подзапросы POST/PUT для передачи необработанного запроса Redis через тело запроса, что не требует экранирования и декодирования URI, тем самым экономя некоторые циклы ЦП. Вот такой пример:
location = /redis {
internal;
# $echo_request_body предоставляется модулем ngx_echo
redis2_raw_query $echo_request_body;
redis2_pass 127.0.0.1:6379;
}
location = /main {
content_by_lua '
local res = ngx.location.capture("/redis",
{ method = ngx.HTTP_PUT,
body = "ping\\r\\n" }
)
ngx.print("[" .. res.body .. "]")
';
}
Это даст точно такой же вывод, как и предыдущий (GET) пример.
Также можно использовать Lua для выбора конкретного бэкенда Redis на основе некоторых сложных правил хеширования. Например,
upstream redis-a {
server foo.bar.com:6379;
}
upstream redis-b {
server bar.baz.com:6379;
}
upstream redis-c {
server blah.blah.org:6379;
}
server {
...
location = /redis {
set_unescape_uri $query $arg_query;
redis2_query $query;
redis2_pass $arg_backend;
}
location = /foo {
content_by_lua "
-- выбрать сервер случайным образом
local servers = {'redis-a', 'redis-b', 'redis-c'}
local i = ngx.time() % #servers + 1;
local srv = servers[i]
local res = ngx.location.capture('/redis',
{ args = {
query = '...',
backend = srv
}
}
)
ngx.say(res.body)
";
}
}
Конвейерные запросы Redis с помощью Lua
Вот полный пример, демонстрирующий, как использовать Lua для выполнения нескольких конвейерных запросов Redis через этот модуль Nginx.
Прежде всего, мы включаем следующее в наш файл nginx.conf:
location = /redis2 {
internal;
redis2_raw_queries $args $echo_request_body;
redis2_pass 127.0.0.1:6379;
}
location = /test {
content_by_lua_file conf/test.lua;
}
В основном мы используем аргументы запроса URI для передачи количества запросов Redis и тело запроса для передачи строки конвейерного запроса Redis.
Затем мы создаем файл conf/test.lua (путь которого относителен к корню сервера Nginx), чтобы включить следующий код Lua:
-- conf/test.lua
local parser = require "redis.parser"
local reqs = {
{"set", "foo", "hello world"},
{"get", "foo"}
}
local raw_reqs = {}
for i, req in ipairs(reqs) do
table.insert(raw_reqs, parser.build_query(req))
end
local res = ngx.location.capture("/redis2?" .. #reqs,
{ body = table.concat(raw_reqs, "") })
if res.status ~= 200 or not res.body then
ngx.log(ngx.ERR, "failed to query redis")
ngx.exit(500)
end
local replies = parser.parse_replies(res.body, #reqs)
for i, reply in ipairs(replies) do
ngx.say(reply[1])
end
Здесь мы предполагаем, что ваш сервер Redis слушает на порту по умолчанию (6379) на localhost. Мы также используем библиотеку lua-redis-parser для построения необработанных запросов Redis и также используем ее для разбора ответов.
Доступ к расположению /test через HTTP-клиенты, такие как curl, дает следующий вывод
OK
hello world
Более реалистичная настройка - использовать правильное определение upstream для нашего бэкенда Redis и включить пул TCP-соединений через директиву keepalive в нем.
Поддержка публикации/подписки Redis
Этот модуль имеет ограниченную поддержку функции публикации/подписки Redis. Полная поддержка невозможна из-за безсостояния REST и модели HTTP.
Рассмотрим следующий пример:
location = /redis {
redis2_raw_queries 2 "subscribe /foo/bar\r\n";
redis2_pass 127.0.0.1:6379;
}
Затем опубликуйте сообщение для ключа /foo/bar в командной строке redis-cli. И тогда вы получите два многоблочных ответа от расположения /redis.
Вы, конечно, можете разобрать ответы с помощью библиотеки lua-redis-parser, если вы используете Lua для доступа к этому расположению модуля.
Ограничения для публикации/подписки Redis
Если вы хотите использовать функцию Redis pub/sub с этим модулем, то вы должны учитывать следующие ограничения:
- Вы не можете использовать HttpUpstreamKeepaliveModule с этим upstream Redis. Работают только короткие соединения Redis.
- Могут возникнуть некоторые гонки, которые создают безвредные предупреждения
Redis server returned extra bytesв вашем error.log Nginx. Такие предупреждения могут быть редкими, но будьте готовы к этому. - Вам следует настроить различные настройки тайм-аутов, предоставляемые этим модулем, такие как redis2_connect_timeout и redis2_read_timeout.
Если вы не можете смириться с этими ограничениями, то вам настоятельно рекомендуется перейти на библиотеку lua-resty-redis для lua-nginx-module.
Настройка производительности
- Когда вы используете этот модуль, пожалуйста, убедитесь, что вы используете пул TCP-соединений (предоставляемый HttpUpstreamKeepaliveModule) и конвейеризацию Redis, где это возможно. Эти функции значительно улучшат производительность.
- Использование нескольких экземпляров серверов Redis на ваших многопроцессорных машинах также сильно помогает из-за последовательной обработки одного экземпляра сервера Redis.
- Когда вы проводите тестирование производительности с помощью чего-то вроде
abилиhttp_load, пожалуйста, убедитесь, что уровень логирования ошибок достаточно высок (например,warn), чтобы предотвратить слишком большое количество циклов Nginx workers на очистку файлаerror.log, который всегда небуферизованный и блокирующий, а значит, очень затратный.
ССЫЛКИ
- Главная страница сервера Redis.
- Протокол передачи Redis: http://redis.io/topics/protocol
- парсер ответов redis и конструктор запросов для Lua: lua-redis-parser.
- lua-nginx-module
- Пакет ngx_openresty.
- Библиотека lua-resty-redis, основанная на API cosocket lua-nginx-module.
GitHub
Вы можете найти дополнительные советы по конфигурации и документацию для этого модуля в репозитории GitHub для nginx-module-redis2.