redis2-nginx-module LuaRedisParser

本文介绍了如何在Nginx中利用HttpMemcModule和HttpRedis2Module高效访问Memcached和Redis,以及如何在Lua中进行非阻塞访问和集成。重点讨论了配置细节、访问方法、性能提升策略,如连接池和管道机制的应用,旨在提高数据存储系统的性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. Memcached

        在Nginx中访问Memcached需要模块的支持,这里选用HttpMemcModule,这个模块可以与后端的Memcached进行非阻塞的通信。我们知道官方提供了Memcached,这个模块只支持get操作,而Memc支持大部分Memcached的命令。

        Memc模块采用入口变量作为参数进行传递,所有以$memc_为前缀的变量都是Memc的入口变量。memc_pass指向后端的Memcached Server。

  配置:

[plain]  view plain copy print ?
  1. #使用HttpMemcModule  
  2. location = /memc {  
  3.     set $memc_cmd $arg_cmd;  
  4.     set $memc_key  $arg_key;  
  5.     set $memc_value $arg_val;  
  6.     set $memc_exptime $arg_exptime;  
  7.           
  8.     memc_pass '127.0.0.1:11211';  
  9. }  

        输出:

[plain]  view plain copy print ?
  1. $ curl  'http://localhost/memc?cmd=set&key=foo&val=Hello'  
  2. $ STORED  
  3. $ curl  'http://localhost/memc?cmd=get&key=foo'  
  4. $ Hello  
        这就实现了memcached的访问,下面看一下如何在lua中访问memcached。

  配置:

[plain]  view plain copy print ?
  1. #在Lua中访问Memcached  
  2. location = /memc {  
  3.     internal;   #只能内部访问  
  4.     set $memc_cmd get;  
  5.     set $memc_key  $arg_key;  
  6.     memc_pass '127.0.0.1:11211';  
  7. }  
  8. location = /lua_memc {  
  9.     content_by_lua '  
  10.         local res = ngx.location.capture("/memc", {  
  11.             args = { key = ngx.var.arg_key }  
  12.         })  
  13.         if res.status == 200 then  
  14.             ngx.say(res.body)  
  15.         end  
  16.     ';  
  17. }  
        输出:

[plain]  view plain copy print ?
  1. $ curl  'http://localhost/lua_memc?key=foo'  
  2. $ Hello  
        通过lua访问memcached,主要是通过子请求采用一种类似函数调用的方式实现。首先,定义了一个memc location用于通过后端memcached通信,就相当于memcached storage。由于整个Memc模块时非阻塞的,ngx.location.capture也是非阻塞的,所以整个操作非阻塞。
2. Redis

        访问redis需要HttpRedis2Module的支持,它也可以同redis进行非阻塞通行。不过,redis2的响应是redis的原生响应,所以在lua中使用时,需要解析这个响应。可以采用LuaRedisModule,这个模块可以构建redis的原生请求,并解析redis的原生响应。

        配置:

[plain]  view plain copy print ?
  1. #在Lua中访问Redis  
  2. location = /redis {  
  3.     internal;   #只能内部访问  
  4.     redis2_query get $arg_key;  
  5.     redis2_pass '127.0.0.1:6379';  
  6. }   
  7. location = /lua_redis { #需要LuaRedisParser  
  8.     content_by_lua '  
  9.         local parser = require("redis.parser")  
  10.         local res = ngx.location.capture("/redis", {  
  11.             args = { key = ngx.var.arg_key }  
  12.         })  
  13.         if res.status == 200 then  
  14.             reply = parser.parse_reply(res.body)  
  15.             ngx.say(reply)  
  16.         end  
  17.     ';  
  18. }  
        输出:

[plain]  view plain copy print ?
  1. $ curl  'http://localhost/lua_redis?key=foo'  
  2. $ Hello  
        和访问memcached类似,需要提供一个redis storage专门用于查询redis,然后通过子请求去调用redis。

3. Redis Pipeline
        在实际访问redis时,有可能需要同时查询多个key的情况。我们可以采用ngx.location.capture_multi通过发送多个子请求给redis storage,然后在解析响应内容。但是,这会有个限制,Nginx内核规定一次可以发起的子请求的个数不能超过50个,所以在key个数多于50时,这种方案不再适用。

        幸好redis提供pipeline机制,可以在一次连接中执行多个命令,这样可以减少多次执行命令的往返时延。客户端在通过pipeline发送多个命令后,redis顺序接收这些命令并执行,然后按照顺序把命令的结果输出出去。在lua中使用pipeline需要用到redis2模块的redis2_raw_queries进行redis的原生请求查询。

        配置:

[plain]  view plain copy print ?
  1. #在Lua中访问Redis  
  2. location = /redis {  
  3.     internal;   #只能内部访问  
  4.   
  5.     redis2_raw_queries $args $echo_request_body;  
  6.     redis2_pass '127.0.0.1:6379';  
  7. }   
  8.       
  9. location = /pipeline {  
  10.     content_by_lua 'conf/pipeline.lua';  
  11. }   
        pipeline.lua

[plain]  view plain copy print ?
  1. -- conf/pipeline.lua file  
  2. local parser = require(‘redis.parser’)  
  3. local reqs = {   
  4.     {‘get’, ‘one’}, {‘get’, ‘two’}   
  5. }  
  6. -- 构造原生的redis查询,get one\r\nget two\r\n  
  7. local raw_reqs = {}  
  8. for i, req in ipairs(reqs)  do  
  9.       table.insert(raw_reqs, parser.build_query(req))  
  10. end  
  11. local res = ngx.location.capture(‘/redis?’..#reqs, { body = table.concat(raw_reqs, ‘’) })  
  12.       
  13. if res.status and res.body then  
  14.        -- 解析redis的原生响应  
  15.        local replies = parser.parse_replies(res.body, #reqs)  
  16.        for i, reply in ipairs(replies)  do   
  17.           ngx.say(reply[1])  
  18.        end  
  19. end  
        输出:

[plain]  view plain copy print ?
  1. $ curl  'http://localhost/pipeline'  
  2. $ first  
  3.   second  
4. Connection Pool

        前面访问redis和memcached的例子中,在每次处理一个请求时,都会和后端的server建立连接,然后在请求处理完之后这个连接就会被释放。这个过程中,会有3次握手、timewait等一些开销,这对于高并发的应用是不可容忍的。这里引入connection pool来消除这个开销。

        连接池需要HttpUpstreamKeepaliveModule模块的支持。

        配置:

[plain]  view plain copy print ?
  1. http {  
  2.     # 需要HttpUpstreamKeepaliveModule  
  3.     upstream redis_pool {  
  4.         server 127.0.0.1:6379;  
  5.         # 可以容纳1024个连接的连接池  
  6.         keepalive 1024 single;  
  7.     }  
  8.       
  9.     server {  
  10.         location = /redis {  
  11.             …  
  12.             redis2_pass redis_pool;  
  13.         }  
  14.     }  
  15. }  
        这个模块提供keepalive指令,它的context是upstream。我们知道upstream在使用Nginx做反向代理时使用,实际upstream是指“上游”,这个“上游”可以是redis、memcached或是mysql等一些server。upstream可以定义一个虚拟server集群,并且这些后端的server可以享受负载均衡。keepalive 1024就是定义连接池的大小,当连接数超过这个大小后,后续的连接自动退化为短连接。连接池的使用很简单,直接替换掉原来的ip和端口号即可。

        有人曾经测过,在没有使用连接池的情况下,访问memcached(使用之前的Memc模块),rps为20000。在使用连接池之后,rps一路飙到140000。在实际情况下,这么大的提升可能达不到,但是基本上100-200%的提高还是可以的。

5. 小结
        这里对memcached、redis的访问做个小结。
        1. Nginx提供了强大的编程模型,location相当于函数,子请求相当于函数调用,并且location还可以向自己发送子请求,这样构成一个递归的模型,所以采用这种模型实现复杂的业务逻辑。
        2. Nginx的IO操作必须是非阻塞的,如果Nginx在那阻着,则会大大降低Nginx的性能。所以在Lua中必须通过ngx.location.capture发出子请求将这些IO操作委托给Nginx的事件模型。
        3. 在需要使用tcp连接时,尽量使用连接池。这样可以消除大量的建立、释放连接的开销。
cd /mnt/openresty-1.27.1.1/build/LuaJIT-2.1-20240815 && make TARGET_STRIP=@: CCDEBUG=-g XCFLAGS='-DLUAJIT_ENABLE_LUA52COMPAT' CC=cc PREFIX=/data/apps/or/luajit make[1]: 进入目录“/mnt/openresty-1.27.1.1/build/LuaJIT-2.1-20240815” ==== Building LuaJIT 2.1 ==== make -C src make[2]: 进入目录“/mnt/openresty-1.27.1.1/build/LuaJIT-2.1-20240815/src” make[2]: 对“default”无需做任何事。 make[2]: 离开目录“/mnt/openresty-1.27.1.1/build/LuaJIT-2.1-20240815/src” ==== Successfully built LuaJIT 2.1 ==== make[1]: 离开目录“/mnt/openresty-1.27.1.1/build/LuaJIT-2.1-20240815” cd /mnt/openresty-1.27.1.1/build/lua-cjson-2.1.0.14 && make DESTDIR= LUA_INCLUDE_DIR=/mnt/openresty-1.27.1.1/build/luajit-root/data/apps/or/luajit/include/luajit-2.1 LUA_CMODULE_DIR=/data/apps/or/lualib LUA_MODULE_DIR=/data/apps/or/lualib CJSON_CFLAGS="-g -fpic" CC=cc make[1]: 进入目录“/mnt/openresty-1.27.1.1/build/lua-cjson-2.1.0.14” make[1]: 对“all”无需做任何事。 make[1]: 离开目录“/mnt/openresty-1.27.1.1/build/lua-cjson-2.1.0.14” cd /mnt/openresty-1.27.1.1/build/lua-resty-signal-0.04 && make DESTDIR= LUA_INCLUDE_DIR=/mnt/openresty-1.27.1.1/build/luajit-root/data/apps/or/luajit/include/luajit-2.1 LUA_LIB_DIR=/data/apps/or/lualib CC=cc make[1]: 进入目录“/mnt/openresty-1.27.1.1/build/lua-resty-signal-0.04” make[1]: 对“all”无需做任何事。 make[1]: 离开目录“/mnt/openresty-1.27.1.1/build/lua-resty-signal-0.04” cd /mnt/openresty-1.27.1.1/build/lua-redis-parser-0.13 && make DESTDIR= LUA_INCLUDE_DIR=/mnt/openresty-1.27.1.1/build/luajit-root/data/apps/or/luajit/include/luajit-2.1 LUA_LIB_DIR=/data/apps/or/lualib CC=cc make[1]: 进入目录“/mnt/openresty-1.27.1.1/build/lua-redis-parser-0.13” make[1]: 对“all”无需做任何事。 make[1]: 离开目录“/mnt/openresty-1.27.1.1/build/lua-redis-parser-0.13” cd /mnt/openresty-1.27.1.1/build/lua-rds-parser-0.06 && make DESTDIR= LUA_INCLUDE_DIR=/mnt/openresty-1.27.1.1/build/luajit-root/data/apps/or/luajit/include/luajit-2.1 LUA_LIB_DIR=/data/apps/or/lualib CC=cc make[1]: 进入目录“/mnt/openresty-1.27.1.1/build/lua-rds-parser-0.06” make[1]: 对“all”无需做任何事。 make[1]: 离开目录“/mnt/openresty-1.27.1.1/build/lua-rds-parser-0.06” cd /mnt/openresty-1.27.1.1/build/nginx-1.27.1 && make make[1]: 进入目录“/mnt/openresty-1.27.1.1/build/nginx-1.27.1” make[1]: *** 没有指明目标并且找不到 makefile。 停止。 make[1]: 离开目录“/mnt/openresty-1.27.1.1/build/nginx-1.27.1” make: *** [all] 错误 2
04-09
<think>我们正在处理用户关于OpenResty升级时遇到的编译错误,错误信息是redis2-nginx-module模块中函数`ngx_http_redis2_handler`的多重定义。 根据错误提示,这通常是因为同一个符号在多个地方被定义,导致链接器发现多个定义。 可能的原因: 1. 模块被重复添加:可能在配置时多次添加了同一个模块(redis2-nginx-module)的不同版本或相同路径。 2. 全局变量/函数定义在头文件中:如果`ngx_http_redis2_handler`在头文件中被定义(而不是声明),并且该头文件被多个源文件包含,则会导致多重定义。 3. 编译选项变化:新版本的编译器(如GCC 10+)默认使用`-fno-common`,将全局变量视为强符号,不允许重复定义。 解决方案: 1. **检查模块依赖**:确保在配置OpenResty时没有重复添加同一个模块。例如,检查configure命令中是否多次出现`--add-module=/path/to/redis2-nginx-module`。 2. **修改模块源码**:如果问题出在模块内部,我们需要修改模块的源码,确保函数只被定义一次。通常,在头文件中使用`extern`声明函数,在源文件中定义函数。 具体到`ngx_http_redis2_handler`,我们可以在模块的源文件中查找这个函数,并检查它是否在头文件中被定义(即是否有函数体在头文件中)。如果是,则需要将头文件中的定义改为声明,并将定义移到源文件中。 3. **使用编译选项**:临时解决方案是在编译时添加`-fcommon`选项,允许重复定义(但不推荐,因为这是绕过问题,而不是解决问题)。 4. **更新模块**:检查redis2-nginx-module是否有新版本修复了该问题,如果有,则更新该模块。 根据经验,redis2-nginx-module在较新的版本中已经修复了类似问题。因此,建议更新该模块到最新版本。 具体步骤: 步骤1:更新redis2-nginx-module 如果你使用的是git clone的模块,进入模块目录,拉取最新代码: ```bash cd /path/to/redis2-nginx-module git pull origin master ``` 或者,如果你使用的是OpenResty的bundle,可以尝试更新整个OpenResty源码。 步骤2:检查配置 确保配置命令中没有重复添加模块。 步骤3:如果问题仍然存在,手动修改源码 在redis2-nginx-module的源码中,找到定义`ngx_http_redis2_handler`的地方。通常,它应该在源文件(如`ngx_http_redis2_module.c`)中定义一次,并且在头文件中只有声明(使用`extern`)。 检查头文件(如`ngx_http_redis2_module.h`)中是否有如下定义: ```c ngx_int_t ngx_http_redis2_handler(ngx_http_request_t *r); ``` 如果有函数体(即实现)在头文件中,则将其移到源文件中。 如果头文件中只有声明,那么可能是链接时发现多个目标文件中有该函数的定义。这时,检查是否同一个模块被编译了两次(比如,配置时添加了两次)。 步骤4:重新编译 清理之前的编译结果,重新编译: ```bash make clean ./configure ... # 你的配置参数 make ``` 步骤5:如果以上都不行,尝试使用`-fcommon`选项(临时) 在配置时添加: ```bash ./configure --with-cc-opt="-fcommon" ... # 其他配置 ``` 但是,请注意,这只是临时解决方案,长期来看应该修复代码。 由于用户提到是升级OpenResty,所以建议先更新所有模块到最新版本,因为新版本可能已经修复了这个问题。 参考用户提供的引用[1],建议使用OpenResty官方发布的版本,其中包含了兼容的模块组合。 因此,我们给出以下建议: 1. 更新OpenResty到最新版本(如果可能),因为最新版本可能已经修复了该问题。 2. 如果无法更新整个OpenResty,则更新redis2-nginx-module模块到最新版本。 如果用户是自己编译的,并且添加了第三方模块,那么需要确保这些模块与当前OpenResty版本兼容。 下面是一个具体的修复示例(假设需要修改源码): 在redis2-nginx-module的源码中,我们通常会在`ngx_http_redis2_module.c`文件中找到函数`ngx_http_redis2_handler`的定义。同时,在`ngx_http_redis2_module.h`中,我们应该只看到它的声明,即: ```c extern ngx_int_t ngx_http_redis2_handler(ngx_http_request_t *r); ``` 如果头文件中不小心包含了定义(即函数体),那么需要将其删除,并将函数体保留在.c文件中。 但是,根据历史经验,redis2-nginx-module的代码结构是符合规范的。因此,更可能的原因是在配置时重复添加了该模块,或者模块的版本太旧。 因此,我们优先建议更新模块。 如果用户无法更新模块,我们可以尝试以下步骤: 1. 定位到编译错误中提到的文件,比如`ngx_http_redis2_module.c`,查看该函数定义的位置(行号)。 2. 检查该函数是否被定义为static(静态函数)?如果不是,那么它会被暴露给链接器。如果同一个函数在另一个模块中也有定义(即使同一个模块被编译两次),就会导致冲突。 但是,在redis2-nginx-module中,这个函数是作为模块的处理函数,必须是非静态的,因为Nginx框架需要调用它。 所以,我们回到重复模块的问题:检查configure命令,确保没有重复添加。 示例配置命令: ```bash ./configure --prefix=/usr/local/openresty \ --with-http_ssl_module \ --add-module=/path/to/redis2-nginx-module \ # 只出现一次 ... ``` 如果用户确实只添加了一次,那么可能是该模块的多个版本被包含(比如,通过不同的路径添加了两次)。请确保路径唯一。 最后,如果确认配置没有问题,并且模块已经是最新,那么可以尝试在配置时加上`-fcommon`选项,但这只是权宜之计。 综上所述,我们推荐以下步骤: 1. 更新redis2-nginx-module到最新版本(从官方仓库拉取最新代码)。 2. 检查配置命令,确保没有重复添加模块。 3. 如果仍然报错,尝试在配置时加入`--with-cc-opt="-fcommon"`。 4. 如果还是不行,考虑升级整个OpenResty到最新版本。 注意:在升级OpenResty时,确保所有第三方模块都兼容新版本的OpenResty。 由于用户是在升级过程中遇到问题,可能是新版本的OpenResty使用了新版本的编译器(如GCC 10+),而旧模块没有及时更新。因此,更新模块是最直接的解决方案。 下面给出一个完整的操作示例: ```bash # 假设OpenResty源码目录在/usr/local/src/openresty-1.21.4.1 # 假设redis2-nginx-module在/usr/local/src/redis2-nginx-module # 步骤1:更新redis2-nginx-module cd /usr/local/src/redis2-nginx-module git pull # 步骤2:重新配置OpenResty cd /usr/local/src/openresty-1.21.4.1 ./configure --prefix=/usr/local/openresty \ --with-http_ssl_module \ --add-module=/usr/local/src/redis2-nginx-module \ ... # 其他模块 # 如果仍然报错,尝试添加-fcommon ./configure --prefix=/usr/local/openresty \ --with-cc-opt="-fcommon" \ --with-http_ssl_module \ --add-module=/usr/local/src/redis2-nginx-module \ ... # 步骤3:编译 make -j4 # 如果没有错误,则安装 make install ``` 如果更新模块后问题解决,则不需要添加`-fcommon`。 希望这些步骤能帮助用户解决问题。 最后,我们生成相关问题:</think>### OpenResty编译错误:`multiple definition of 'ngx_http_redis2_handler'`解决方案 #### 问题分析 该错误发生在编译redis2-nginx-module模块时,表明`ngx_http_redis2_handler`函数被重复定义。主要原因是: 1. **符号冲突**:函数在多个位置被定义,导致链接器无法确定使用哪个定义[^1] 2. **模块版本不兼容**:redis2-nginx-module模块与当前OpenResty版本存在兼容性问题 3. **编译选项变化**:新版本GCC(≥10)默认使用`-fno-common`,对符号重复定义更严格 #### 解决方案 ##### ✅ 方案1:更新redis2-nginx-module(推荐) ```bash # 进入模块目录 cd bundle/redis2-nginx-module # 拉取最新代码 git pull origin master # 或完全重置模块 rm -rf bundle/redis2-nginx-module git clone https://github.com/openresty/redis2-nginx-module.git bundle/redis2-nginx-module ``` ##### ✅ 方案2:修改源码(兼容旧版本) 在源文件中修复符号定义: 1. 打开文件 `ngx_http_redis2_handler.c` 2. 修改函数定义(添加`static`限定符): ```c // 修改前 ngx_int_t ngx_http_redis2_handler(ngx_http_request_t *r) { // 函数体 } // 修改后 static ngx_int_t ngx_http_redis2_handler(ngx_http_request_t *r) { // 函数体 } ``` ##### ✅ 方案3:添加编译兼容选项 ```bash # 重新配置时添加 -fcommon ./configure \ --prefix=/usr/local/openresty \ --add-module=../redis2-nginx-module \ --with-cc-opt="-O2 -fcommon" # 关键参数 # 重新编译 make clean && make -j$(nproc) sudo make install ``` ##### ✅ 方案4:检查模块冲突 确认是否重复添加模块: ```bash ./configure --help | grep redis2 # 检查模块列表 ``` 移除可能冲突的第三方模块(如多个Redis客户端模块)。 #### 完整修复流程 ```bash # 1. 更新OpenResty基础版本 wget https://openresty.org/download/openresty-1.21.4.1.tar.gz tar zxvf openresty-*.tar.gz # 2. 更新redis2模块 cd openresty-1.21.4.1/bundle git clone --depth=1 https://github.com/openresty/redis2-nginx-module.git # 3. 配置编译选项 ./configure \ --prefix=/usr/local/openresty \ --with-http_ssl_module \ --add-module=./redis2-nginx-module \ --with-cc-opt="-fcommon" # 4. 编译安装 make -j$(nproc) sudo make install ``` #### 验证安装 ```bash # 检查符号定义唯一性 nm /usr/local/openresty/nginx/sbin/nginx | grep ngx_http_redis2_handler # 预期输出(T表示唯一文本段定义): 000000000054a910 T ngx_http_redis2_handler ``` > **重要提示**:OpenResty官方强烈建议使用其捆绑的模块组合以保证兼容性[^1]。若需使用自定义模块,请确保模块与OpenResty版本匹配。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值