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开发, 后端开发, 安全合规, 搜索引擎查询, 网络代理