openresty/redis2-nginx-module
GitHub: openresty/redis2-nginx-module
该模块使 Nginx 能够以非阻塞方式与 Redis 2.x 服务器通信,支持完整的 Redis 协议及管道操作。
Stars: 906 | Forks: 144
# 名称
ngx_redis2 - 用于 Redis 2.0 协议的 Nginx upstream 模块
*本模块并未随 Nginx 源码一起分发。* 请参阅[安装说明](#installation)。
# 目录
* [名称](#name)
* [状态](#status)
* [版本](#version)
* [概要](#synopsis)
* [描述](#description)
* [指令](#directives)
* [redis2_query](#redis2_query)
* [redis2_raw_query](#redis2_raw_query)
* [redis2_raw_queries](#redis2_raw_queries)
* [redis2_literal_raw_query](#redis2_literal_raw_query)
* [redis2_pass](#redis2_pass)
* [redis2_connect_timeout](#redis2_connect_timeout)
* [redis2_send_timeout](#redis2_send_timeout)
* [redis2_read_timeout](#redis2_read_timeout)
* [redis2_buffer_size](#redis2_buffer_size)
* [redis2_next_upstream](#redis2_next_upstream)
* [连接池](#connection-pool)
* [选择 Redis 数据库](#selecting-redis-databases)
* [Lua 互操作性](#lua-interoperability)
* [通过 Lua 实现 Redis 管道请求](#pipelined-redis-requests-by-lua)
* [Redis 发布/订阅支持](#redis-publishsubscribe-support)
* [Redis 发布/订阅的限制](#limitations-for-redis-publishsubscribe)
* [性能调优](#performance-tuning)
* [安装](#installation)
* [兼容性](#compatibility)
* [社区](#community)
* [英文邮件列表](#english-mailing-list)
* [中文邮件列表](#chinese-mailing-list)
* [Bug 和补丁](#bugs-and-patches)
* [源码仓库](#source-repository)
* [待办事项](#todo)
* [作者](#author)
* [参与贡献](#getting-involved)
* [版权与许可](#copyright--license)
* [参见](#see-also)
# 状态
本模块已具备生产环境可用性。
# 版本
本文档描述了 2018 年 4 月 19 日发布的 ngx_redis2 [v0.15](https://github.com/openresty/redis2-nginx-module/tags)。
# 概要
```
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; # this requires 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; # this requires ngx_set_misc
set_unescape_uri $val $arg_val; # this requires ngx_set_misc
redis2_query set $key $val;
redis2_pass foo.com:6379;
}
# multiple pipelined queries
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 {
# $ is not special here...
redis2_literal_raw_query '*1\r\n$4\r\nping\r\n';
redis2_pass 127.0.0.1:6379;
}
location = /bar {
# variables can be used below and $ is special
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; # this requires the ngx_set_misc module
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;
}
```
[返回目录](#table-of-contents)
# 描述
这是一个 Nginx upstream 模块,使 nginx 能够以非阻塞方式与 [Redis](http://redis.io/) 2.x 服务器进行通信。它已经实现了完整的 Redis 2.0 统一协议,包括对 Redis pipelining 的支持。
该模块返回来自 Redis 服务器的原始 TCP 响应。建议在与 [lua-nginx-module](http://github.com/openresty/lua-nginx-module) 结合使用时,使用我的 [lua-redis-parser](http://github.com/openresty/lua-redis-parser)(纯 C 编写)将这些响应解析为 Lua 数据结构。
不过,当与 [lua-nginx-module](http://github.com/openresty/lua-nginx-module) 结合使用时,建议使用 [lua-resty-redis](http://github.com/openresty/lua-resty-redis) 库而不是本模块,因为前者更灵活且内存效率更高。
如果您只想使用 `get` redis 命令,可以试试 [HttpRedisModule](http://wiki.nginx.org/HttpRedisModule)。因为它只需要实现 `get` 命令,所以它会返回 Redis 响应中已解析的内容部分。
另一种选择是由您自己在客户端解析 redis 响应。
[返回目录](#table-of-contents)
# 指令
[返回目录](#table-of-contents)
## redis2 查询
**语法:** *redis2_query cmd arg1 arg2 ...*
**默认值:** *no*
**上下文:** *location, location if*
通过指定其各个单独的参数(包括 Redis 命令名称本身)来指定一个 Redis 命令,使用方式类似于 `redis-cli` 工具。
在一个 location 中允许使用此指令的多个实例,并且这些查询将被流水线化(pipelined)。例如,
```
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`)。
[返回目录](#table-of-contents)
## redis2 原始查询
**语法:** *redis2_raw_query QUERY*
**默认值:** *no*
**上下文:** *location, location if*
指定原始 Redis 查询,并且在 `QUERY` 参数中识别 nginx 变量。
在 `QUERY` 参数中只允许有*一个* Redis 命令,否则您将收到一个错误。如果您想在单个查询中指定多个流水线命令,请改用 [redis2_raw_queries](#redis2_raw_queries) 指令。
[返回目录](#table-of-contents)
## redis2 批量原始查询
**语法:** *redis2_raw_queries N QUERIES*
**默认值:** *no*
**上下文:** *location, location if*
在 `QUERIES` 参数中指定 `N` 条命令。`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;
}
```
请注意,在上面的第二个示例中,[set_unescape_uri](http://github.com/openresty/set-misc-nginx-module#set_unescape_uri) 指令是由 [set-misc-nginx-module](http://github.com/openresty/set-misc-nginx-module) 提供的。
[返回目录](#table-of-contents)
## redis2 字面原始查询
**语法:** *redis2_literal_raw_query QUERY*
**默认值:** *no*
**上下文:** *location, location if*
指定一个原始 Redis 查询,但其中的 Nginx 变量*不会*被识别。换句话说,您可以在您的 `QUERY` 参数中自由使用美元符号 (`$`)。
在 `QUERY` 参数中只允许有一个 Redis 命令。
[返回目录](#table-of-contents)
## redis2 密码
**语法:** *redis2_pass <upstream_name>*
**语法:** *redis2_pass <host>:<port>*
**默认值:** *no*
**上下文:** *location, location if*
**阶段:** *content*
指定 Redis 服务器后端。
[返回目录](#table-of-contents)
## redis2 连接超时
**语法:** *redis2_connect_timeout <time>*
**默认值:** *60s*
**上下文:** *http, server, location*
连接到 Redis 服务器的超时时间,默认以秒为单位。
始终显式指定时间单位是明智的,以避免混淆。支持的时间单位有 `s`(秒)、`ms`(毫秒)、`y`(年)、`M`(月)、`w`(周)、`d`(天)、`h`(小时) 和 `m`(分钟)。
此时间必须小于 597 小时。
[返回目录](#table-of-contents)
## redis2 发送超时
**语法:** *redis2_send_timeout <time>*
**默认值:** *60s*
**上下文:** *http, server, location*
向 Redis 服务器发送 TCP 请求的超时时间,默认以秒为单位。
始终显式指定时间单位是明智的,以避免混淆。支持的时间单位有 `s`(秒)、`ms`(毫秒)、`y`(年)、`M`(月)、`w`(周)、`d`(天)、`h`(小时) 和 `m`(分钟)。
[返回目录](#table-of-contents)
## redis2 读取超时
**语法:** *redis2_read_timeout <time>*
**默认值:** *60s*
**上下文:** *http, server, location*
从 Redis 服务器读取 TCP 响应的超时时间,默认以秒为单位。
始终显式指定时间单位是明智的,以避免混淆。支持的时间单位有 `s`(秒)、`ms`(毫秒)、`y`(年)、`M`(月)、`w`(周)、`d`(天)、`h`(小时) 和 `m`(分钟)。
[返回目录](#table-of-contents)
## redis2 缓冲区大小
**语法:** *redis2_buffer_size <size>*
**默认值:** *4k/8k*
**上下文:** *http, server, location*
此缓冲区大小用于读取 Redis 回复,但它不需要像可能的最大 Redis 回复那么大。
默认大小为页面大小,可能是 4k 或 8k。
[返回目录](#table-of-contents)
## redis2 next_upstream
**语法:** *redis2_next_upstream [ error | timeout | invalid_response | off ]*
**默认值:** *error timeout*
**上下文:** *http, server, location*
指定哪些故障条件应导致请求被转发到另一台上游(upstream)服务器。仅当 [redis2_pass](#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;
}
}
```
[返回目录](#table-of-contents)
# 连接池
您可以将出色的 [HttpUpstreamKeepaliveModule](http://wiki.nginx.org/HttpUpstreamKeepaliveModule) 与本模块一起使用,为 Redis 提供 TCP 连接池。
一个示例配置片段如下所示
```
http {
upstream backend {
server 127.0.0.1:6379;
# a pool with at most 1024 connections
# and do not distinguish the servers:
keepalive 1024;
}
server {
...
location = /redis {
set_unescape_uri $query $arg_query;
redis2_query $query;
redis2_pass backend;
}
}
}
```
[返回目录](#table-of-contents)
# 选择 Redis 数据库
Redis 提供了 [select](http://redis.io/commands/SELECT) 命令来切换 Redis 数据库。此命令与 [get](http://redis.io/commands/GET) 或 [set](http://redis.io/commands/SET) 等其他普通命令没有什么不同。因此,您可以在 [redis2_query](#redis2_query) 指令中使用它们,例如,
```
redis2_query select 8;
redis2_query get foo;
```
[返回目录](#table-of-contents)
# Lua 互操作性
该模块可以作为 [lua-nginx-module](http://github.com/openresty/lua-nginx-module) 的非阻塞 redis2 客户端(但如今建议改用 [lua-resty-redis](http://github.com/openresty/lua-resty-redis) 库,它更易于使用,并且在大多数情况下效率更高)。
以下是使用 GET 子请求的示例:
```
location = /redis {
internal;
# set_unescape_uri is provided by 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`。也就是说,此模块返回来自远程 redis 服务器的*原始* TCP 响应。对于基于 Lua 的应用程序开发者,他们可能希望利用 [lua-redis-parser](http://github.com/openresty/lua-redis-parser) 库(纯 C 编写)将此类原始响应解析为 Lua 数据结构。
当将内联 Lua 代码移入外部 `.lua` 文件时,直接使用转义序列 `\r\n` 非常重要。我们上面之所以使用 `\\r\\n`,仅仅是因为当 Lua 代码被放入 Nginx 字符串字面量时,其本身需要进行引号转义。
您还可以使用 POST/PUT 子请求通过请求体(request body)传输原始 Redis 请求,这不需要 URI 转义和反转义,从而节省一些 CPU 周期。下面是一个这样的例子:
```
location = /redis {
internal;
# $echo_request_body is provided by the ngx_echo module
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 "
-- pick up a server randomly
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)
";
}
}
```
[返回目录](#table-of-contents)
## 通过 Lua 实现 Redis 管道请求
这是一个完整的示例,演示了如何使用 Lua 通过此 Nginx 模块发出多个流水线形式的 Redis 请求。
首先,我们在我们的 `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 服务器正在监听 localhost 的默认端口(6379)。我们还利用了 [lua-redis-parser](http://github.com/openresty/lua-redis-parser) 库为我们构造原始 Redis 查询,并同样使用它来解析回复。
通过诸如 `curl` 之类的 HTTP 客户端访问 `/test` location 会产生以下输出
```
OK
hello world
```
更现实的设置是为我们的 Redis 后端使用一个正式的 upstream 定义,并通过其中的 [keepalive](http://wiki.nginx.org/HttpUpstreamKeepaliveModule#keepalive) 指令启用 TCP 连接池。
[返回目录](#table-of-contents)
# Redis 发布/订阅支持
本模块对 Redis 发布/订阅功能提供有限的支持。由于 REST 和 HTTP 模型的无状态特性,它无法得到完全支持。
考虑以下示例:
```
location = /redis {
redis2_raw_queries 2 "subscribe /foo/bar\r\n";
redis2_pass 127.0.0.1:6379;
}
```
然后在 `redis-cli` 命令行中为键 `/foo/bar` 发布一条消息。接着,您将从 `/redis` location 接收到两条多块(multi-bulk)回复。
如果您使用 Lua 来访问此模块的 location,您当然可以使用 [lua-redis-parser](http://github.com/openresty/lua-redis-parser) 库来解析回复。
[返回目录](#table-of-contents)
## Redis 发布/订阅的限制
如果您想将 [Redis pub/sub](http://redis.io/topics/pubsub)与此模块结合使用,那么您必须注意以下限制:
* 您不能将 [HttpUpstreamKeepaliveModule](http://wiki.nginx.org/HttpUpstreamKeepaliveModule) 用于此 Redis upstream。只有短暂的 Redis 连接才能正常工作。
* 可能会出现一些竞争条件,从而在您的 nginx error.log 中产生无害的 `Redis server returned extra bytes` 警告。这种警告可能很少见,但请对此做好准备。
* 您应该调整本模块提供的各种超时设置,如 [redis2_connect_timeout](#redis2_connect_timeout) 和 [redis2_read_timeout](#redis2_read_timeout)。
如果您无法忍受这些限制,那么强烈建议您改用基于 [lua-nginx-module](http://github.com/openresty/lua-nginx-module) 的 [lua-resty-redis](https://github.com/openresty/lua-resty-redis) 库。
[返回目录](#table-of-contents)
# 性能调优
* 在使用本模块时,请确保您正在使用 TCP 连接池(由 [HttpUpstreamKeepaliveModule](http://wiki.nginx.org/HttpUpstreamKeepaliveModule) 提供),并在可能的情况下使用 Redis pipelining。这些功能将显著提升性能。
* 由于单个 Redis 服务器实例具有顺序处理的特性,因此在您的多核机器上使用多个 Redis 服务器实例也会有很大帮助。
* 当您使用 `ab` 或 `http_load` 之类的工具进行性能基准测试时,请确保您的错误日志级别足够高(如 `warn`),以防止 Nginx worker 进程消耗过多的周期去刷新 `error.log` 文件,该操作始终是非缓冲且阻塞的,因此开销非常大。
[返回目录](#table-of-contents)
# 安装
建议您通过 [ngx_openresty bundle](http://openresty.org) 安装本模块(以及 Nginx 核心和许多其他好东西)。请查看[安装说明](http://openresty.org/#Installation)来设置 [ngx_openresty](http://openresty.org)。
或者,您也可以通过如下重新编译标准的 Nginx 核心来手动安装此模块:
* 从 [nginx.org](http://nginx.org) 获取 nginx 源代码,例如 1.11.2 版本(参见 nginx 兼容性),
* 然后从 ngx_redis2 的[文件列表](http://github.com/openresty/redis2-nginx-module/tags)下载此模块的最新版本发布压缩包。
* 最后使用此模块编译源代码:
```
wget 'http://nginx.org/download/nginx-1.11.2.tar.gz'
tar -xzvf nginx-1.11.2.tar.gz
cd nginx-1.11.2/
# Here we assume you would install you nginx under /opt/nginx/.
./configure --prefix=/opt/nginx \
--add-module=/path/to/redis2-nginx-module
make -j2
make install
```
从 NGINX 1.9.11 开始,您也可以将此模块编译为动态模块,只需在上述 `./configure` 命令行中使用 `--add-dynamic-module=PATH` 选项代替 `--add-module=PATH`。然后您可以在您的 `nginx.conf` 中通过 [load_module](http://nginx.org/en/docs/ngx_core_module.html#load_module) 指令显式加载该模块,例如,
```
load_module /path/to/modules/ngx_http_redis2_module.so;
```
[返回目录](#table-of-contents)
# 兼容性
Redis 2.0, 2.2, 2.4 及以上版本应该能与此模块正常配合使用,没有任何问题。[Alchemy Database](http://code.google.com/p/alchemydatabase/)(早期被称为 redisql)也是如此。
以下版本的 Nginx 应该能与此模块正常配合使用:
* **1.17.x** (最后测试:1.17.4)
* **1.16.x**
* **1.15.x** (最后测试:1.15.8)
* **1.14.x**
* **1.13.x** (最后测试:1.13.6)
* **1.12.x**
* 1.11.x (最后测试:1.11.2)
* 1.10.x
* 1.9.x (最后测试:1.9.15)
* 1.8.x
* 1.7.x (最后测试:1.7.10)
* 1.6.x
* 1.5.x (最后测试:1.5.12)
* 1.4.x (最后测试:1.4.3)
* 1.3.x (最后测试:1.3.7)
* 1.2.x (最后测试:1.2.7)
* 1.1.x (最后测试:1.1.5)
* 1.0.x (最后测试:1.0.10)
* 0.9.x (最后测试:0.9.4)
* 0.8.x >= 0.8.31 (最后测试:0.8.54)
更早版本的 Nginx 将*无法*工作。
如果您发现 0.8.31 以上的任何特定版本的 Nginx 无法与此模块一起工作,请考虑报告 bug。
[返回目录](#table-of-contents)
# 社区
[返回目录](#table-of-contents)
## 英文邮件列表
[openresty-en](https://groups.google.com/group/openresty-en) 邮件列表是为英语使用者提供的。
[返回目录](#table-of-contents)
## 中文邮件列表
[openresty](https://groups.google.com/group/openresty) 邮件列表是为中文使用者提供的。
[返回目录](#table-of-contents)
# Bug 和补丁
请通过以下方式提交 bug 报告、愿望清单或补丁
1. 在 [GitHub Issue Tracker](http://github.com/openresty/redis2-nginx-module/issues) 上创建工单,
2. 或者发布到 [OpenResty 社区](#community)。
[返回目录](#table-of-contents)
# 源码仓库
可在 GitHub 上的 [openresty/redis2-nginx-module](http://github.com/openresty/redis2-nginx-module) 获取。
[返回目录](#table-of-contents)
# 待办事项
* 添加 `redis2_as_json` 指令以允许直接输出 JSON。
[返回目录](#table-of-contents)
# 作者
Yichun "agentzh" Zhang (章亦春) , OpenResty Inc.
[返回目录](#table-of-contents)
# 参与贡献
非常欢迎您向作者提交补丁,或仅仅是为 GitHub 上的源代码仓库申请提交权限。
[返回目录](#table-of-contents)
# 版权与许可
本模块基于 BSD 许可协议授权。
版权所有 (C) 2010-2018,由 Yichun "agentzh" Zhang (章亦春) , OpenResty Inc. 所有。
保留所有权利。
在满足以下条件的情况下,允许以源代码和二进制形式重新分发和使用,无论是否进行修改:
* 源代码的再分发必须保留上述版权声明、此条件列表以及下述免责声明。
* 二进制形式的再分发必须在随分发提供的文档和/或其他材料中复制上述版权声明、此条件列表以及下述免责声明。
本软件由版权所有者和贡献者“按原样”提供,并且不提供任何明示或暗示的担保,包括但不限于对适销性和特定用途适用性的暗示担保。在任何情况下,无论出于何种责任理论,无论是在合同、严格责任还是侵权(包括疏忽或其他)行为中,对于因使用本软件而以任何方式产生的任何直接、间接、偶然、特殊、惩戒性或后果性损害(包括但不限于替代商品或服务的采购;使用、数据或利润的损失;或业务中断),版权所有者或贡献者均不承担任何责任,即使已被告知发生此类损害的可能性。
[返回目录](#table-of-contents)
# 参见
* [Redis](http://redis.io/) 服务器主页。
* Redis 线协议(wire protocol):
* 用于 Lua 的 redis 响应解析器和请求构造器:[lua-redis-parser](http://github.com/openresty/lua-redis-parser)。
* [lua-nginx-module](http://github.com/openresty/lua-nginx-module)
* [ngx_openresty bundle](http://openresty.org)。
* 基于 [lua-nginx-module](http://github.com/openresty/lua-nginx-module) cosocket API 的 [lua-resty-redis](https://github.com/openresty/lua-resty-redis) 库。
[返回目录](#table-of-contents)
标签:Nginx模块, Redis, rizin, Syscall, Web开发, 后端开发, 安全合规, 搜索引擎查询, 网络代理