redis2: Модуль upstream NGINX для протокола Redis 2.0
Установка на Debian/Ubuntu
Эти документы относятся к APT пакету nginx-module-redis2, предоставляемому репозиторием GetPageSpeed Extras.
- Настройте APT репозиторий, как описано в настройке APT репозитория.
- Установите модуль:
sudo apt-get update
sudo apt-get install nginx-module-redis2
Показать дистрибутивы и архитектуры
| Дистрибутив | Версия | Компонент | Архитектуры |
|-------------|------------------|-------------|---------------|
| debian | bookworm | main | amd64, arm64 |
| debian | bookworm-mainline | main | amd64, arm64 |
| debian | trixie | main | amd64, arm64 |
| debian | trixie-mainline | main | amd64, arm64 |
| ubuntu | focal | main | amd64, arm64 |
| ubuntu | focal-mainline | main | amd64, arm64 |
| ubuntu | jammy | main | amd64, arm64 |
| ubuntu | jammy-mainline | main | amd64, arm64 |
| ubuntu | noble | main | amd64, arm64 |
| ubuntu | noble-mainline | main | amd64, arm64 |
ngx_redis2 - модуль Nginx upstream для протокола Redis 2.0
Статус
Этот модуль уже готов к использованию в производственных условиях.
Аннотация
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 вместо этого модуля, так как первая намного более гибкая и эффективная по памяти.
Если вы хотите использовать только команду redis get, вы можете попробовать HttpRedisModule. Он возвращает разобранную часть ответа Redis, так как для реализации требуется только get.
Еще один вариант - разобрать ответы Redis на стороне клиента самостоятельно.
Директивы
redis2_query
синтаксис: redis2_query cmd arg1 arg2 ...
по умолчанию: нет
контекст: location, location if
Укажите команду Redis, указав ее отдельные аргументы (включая само имя команды Redis) аналогично утилите redis-cli.
Разрешается несколько экземпляров этой директивы в одном местоположении, и эти запросы будут отправлены в пайплайне. Например,
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.
Разрешена только одна команда redis в аргументе QUERY.
redis2_pass
синтаксис: redis2_pass <upstream_name>
синтаксис: redis2_pass <host>:<port>
по умолчанию: нет
контекст: location, location if
фаза: содержимое
Укажите серверный бэкенд 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 (написанную на чистом C), чтобы разобрать такие сырые ответы в структуры данных 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, "не удалось запросить 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
Если вы хотите использовать функцию pub/sub Redis с этим модулем, тогда вы должны обратить внимание на следующие ограничения:
- Вы не можете использовать 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 на сбросе файла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.