Nginx + Lua + Redis
2016-07-19 16:40
603 查看
1. 目标
我的目标是:使用redis做分布式缓存;使用lua API来访问redis缓存;使用nginx向客户端提供服务。基于这个目标,自然想到openresty。2. openresty
第一次接触,理解的不多。感觉就是:1. 把nginx和lua结合起来,使得nginx开发更加方便;2. 提供一些模块,例如memcached、redis、mysql,postgres等等;FIX ME!3. 失败的尝试:lua-resty-redis + redis集群模式
3.1 lua-resty-redis
lua-resty-redis是openresty(1.9.15.1)的一个组件,简单来说,它提供一个lua语言版的redis API,使用socket(lua sock)和redis通信。使用比较直观:<pre name="code" class="plain">--连接 local redis = require "resty.redis" local red = redis:new() red:set_timeout(1000) -- 1 sec local ok, err = red:connect("127.0.0.1", 6379) --set local res, err = red:set(key, value) --get local res, err = red:get(key)
3.2 redis集群部署
在localhost上开启6个redis实例,它们的端口是:7000-7005;其中3个为master,另外3个为slave。具体配置与部署,见前一篇博客:点击打开链接 第4节。
3.3 试验
3.3.1 安装openresty
# cd /tmp/ # wget https://openresty.org/download/openresty-1.9.15.1.tar.gz # tar zxvf openresty-1.9.15.1.tar.gz # cd openresty-1.9.15.1 # yum install pcre-devel.x86_64 # yum install openssl-devel.x86_64 # ./configure --prefix=/usr/local/openresty-1.9.15 --with-luajit # gmake # gmake install
3.3.2 创建一个工程(名为objstore)并实现hello world
3.3.2.1 创建目录
# mkdir /var/objstore # mkdir /var/objstore/lua # mkdir /var/objstore/lualib
3.3.2.2 lua脚本(hello world)
# vim /var/objstore/lua/hello.lua ngx.say("hello world!");
3.3.2.3 工程的nginx配置
# vim /var/objstore/objstore.conf server { listen 8080; server_name _; location /lua { default_type 'text/html'; lua_code_cache off; content_by_lua_file /var/objstore/lua/hello.lua; } }
3.3.2.4 把工程加入nginx
http { include mime.types; default_type application/octet-stream; + lua_package_path "/var/objstore/lualib/?.lua;;"; + lua_package_cpath "/var/objstore/lualib/?.so;;"; + include /var/objstore/objstore.conf;
3.3.2.5 启动nginx并测试
<pre name="code" class="plain"># /usr/local/openresty-1.9.15/nginx/sbin/nginx nginx: [alert] lua_code_cache is off; this will hurt performance in /var/objstore/objstore.conf:7
这个alert是因为objstore.conf中把lua_code_cache为off;若设置为off,nginx不缓存lua脚本,每次改变lua代码,不必reload nginx即可生效;这便于开发和测试。但禁用缓存对性能有影响,故正式环境下一定记得设置为on;
<pre name="code" class="plain"># curl http://127.0.0.1:8080/lua hello world!
3.3.3 把hello world改成redis访问
3.3.3.1 修改hello.lua
# cat lua/hello.lua local redis = require "resty.redis" local red = redis:new() red:set_timeout(1000) -- 1 sec local ok, err = red:connect("127.0.0.1", 7000) if not ok then ngx.say("failed to connect: ", err) return end for i = 1, 2 do local res, err = red:set("lua-key-"..i, "lua-value-"..i) if not res then ngx.say("failed to set lua-key-"..i..": ", err) return end end for i = 1, 2 do local res, err = red:get("lua-key-"..i) if err then ngx.say("failed to get lua-key-"..i..": ", err) return end if not res then ngx.say("lua-key-"..i.." not found.") return end ngx.say("lua-key-"..i..": ", res) end red:close()显然,上面脚本先连接redis,然后set/get以下两对键值,最后close:
lua-key-1:lua-value-1
lua-key-2:lua-value-2
3.3.3.2 测试
# curl http://127.0.0.1:8080/lua failed to set lua-key-2: MOVED 13617 127.0.0.1:7002悲剧,不支持cluster。上面脚本连接的是redis实例7000,从错误提示看,lua-key-2应该存储于实例7002上。支持集群的redis API会自动转发或者根据slot缓存直接访问正确的redis实例(节点)。
4. 一致性hash + redis2-nginx-module+ redis非集群模式
在找到支持redis集群的lua API之前,我想尝试一下redis非集群模式,借助于一致性hash,大致也能够满足目标(使用redis做分布式缓存;使用lua API来访问redis缓存;使用nginx向客户端提供服务)。4.1 一致性hash
ngx_http_consistent_hash是一个nginx upstream模块点击打开链接,它能是nginx在向多个upstream转发请求时,按一致性hash的方式进行。对于我们的目标,我们可以把redis部署为多对主备(多对主备之间是独立的,不是集群),然后使用一致性hash的方式访问它们:key1,key5,key13在redis-A(备份redis-A1)上;key2,key3,key8,key10在redis-B(备份redis-B1)上,以此类推。根据配置,ngx_http_consistent_hash可以使用不同的键来hash:
consistent_hash $arg;
consistent_hash $remote_addr;
consistent_hash $request_uri;
下文可见,我们使用第一种,即根据参数来进行一致性hash。
4.2 redis2-nginx-module
redis2-nginx-module是一个openresty(1.9.15.1)自带的模块。它能够把请求转发给upstream(redis2_pass)。注意它和lua-resty-redis不同,lua-resty-redis是一个lua语言版的redis API,使用socket(lua sock)和redis通信。而redis2-nginx-module是把请求转发给别的upstream(细节?)。4.3 redis非集群模式
我还是启动6个redis实例,它们两两互为主备(各对直接是独立的):master
slave
7000
7003
7001
7004
7002
7004
具体配置与部署,见点击打开链接 第3节。
4.4 试验
4.4.1 重新安装openresty(加入ngx_http_upstream_consistent_hash_module)
卸载前文的安装# /usr/local/openresty-1.9.15/nginx/sbin/nginx -s quit # rm -fr /usr/local/openresty-1.9.15/
重新安装
# cd /tmp/ # wget https://github.com/replay/ngx_http_consistent_hash/archive/master.zip # unzip master.zip # wget https://openresty.org/download/openresty-1.9.15.1.tar.gz # tar zxvf openresty-1.9.15.1.tar.gz # cd openresty-1.9.15.1 # ./configure --prefix=/usr/local/openresty-1.9.15 --with-luajit --add-module=/tmp/ngx_http_consistent_hash-master # gmake # gmake install
4.4.2 创建一个工程(名为objstore)
4.4.2.1 创建目录
# rm -fr /var/objstore/
# mkdir /var/objstore # mkdir /var/objstore/lua # mkdir /var/objstore/lualib
4.4.2.2 工程的nginx配置
# vim /var/objstore/objstore.conf upstream redis_nodes { consistent_hash $key; server 127.0.0.1:7000; server 127.0.0.1:7001; server 127.0.0.1:7002; } server { listen 8080; server_name _; location /redis/get { set_unescape_uri $key $arg_key; redis2_query get $key; redis2_pass redis_nodes; } location /redis/set { set_unescape_uri $key $arg_key; set_unescape_uri $val $arg_val; redis2_query set $key $val; redis2_pass redis_nodes; } }
如4.1所述,ngx_http_consistent_hash可以使用不同的键来hash;这里是用的参数(参数名为"key")。
4.4.2.3 把工程加入nginx
# vim /usr/local/openresty-1.9.15/nginx/conf/nginx.conf keepalive_timeout 65; #gzip on; + include /var/objstore/objstore.conf; server { listen 80; server_name localhost;
4.4.2.4 启动nginx并测试
# /usr/local/openresty-1.9.15/nginx/sbin/nginx # curl -s "http://127.0.0.1:8080/redis/set?key=a&val=A" +OK # curl -s "http://127.0.0.1:8080/redis/set?key=b&val=B" +OK # curl -s "http://127.0.0.1:8080/redis/set?key=c&val=C" +OK # curl -s "http://127.0.0.1:8080/redis/set?key=d&val=D" +OK # curl -s "http://127.0.0.1:8080/redis/set?key=e&val=E" +OK # curl -s "http://127.0.0.1:8080/redis/set?key=f&val=F" +OK # curl -s "http://127.0.0.1:8080/redis/set?key=g&val=G" +OK # # # curl -s "http://127.0.0.1:8080/redis/get?key=a" $1 A # curl -s "http://127.0.0.1:8080/redis/get?key=b" $1 B # curl -s "http://127.0.0.1:8080/redis/get?key=c" $1 C # curl -s "http://127.0.0.1:8080/redis/get?key=d" $1 D # curl -s "http://127.0.0.1:8080/redis/get?key=e" $1 E # curl -s "http://127.0.0.1:8080/redis/get?key=f" $1 F # curl -s "http://127.0.0.1:8080/redis/get?key=g" $1 G #一切按期望工作。我们再看看各个key在redis实例(节点)间的分布:
# redis-cli -p 7000 127.0.0.1:7000> mget a b c d e f g 1) (nil) 2) "B" 3) "C" 4) (nil) 5) (nil) 6) (nil) 7) (nil) 127.0.0.1:7000> exit # # redis-cli -p 7001 127.0.0.1:7001> mget a b c d e f g 1) (nil) 2) (nil) 3) (nil) 4) "D" 5) (nil) 6) (nil) 7) "G" 127.0.0.1:7001> exit # # redis-cli -p 7002 127.0.0.1:7002> mget a b c d e f g 1) "A" 2) (nil) 3) (nil) 4) (nil) 5) "E" 6) "F" 7) (nil) 127.0.0.1:7002> exit #可见,key分布在各个redis实例(节点)上,通过一致性hash,nginx能够正确的访问到每一个key。
4.4.3 使用lua访问
在4.4.2的基础上,我们使用lua脚本来访问redis。4.4.3.1 lua脚本
# vim /var/objstore/lua/redis.lua function setRedis(key, val) local res = ngx.location.capture('/redis/set', { args= { key= key, val= val } }) if res.status == 200 then return true else return false end end function getRedis(key) local capture = ngx.location.capture('/redis/get', { args= { key= key } }) local parser = require 'redis.parser' --require redis.parser local res, err = parser.parse_reply(capture.body) return res end local a = ngx.var.arg_a local b = ngx.var.arg_b if b then --b is non-nil, set if setRedis(a,b) then ngx.say("set "..a..":"..b.." OK") else ngx.say("set "..a..":"..b.." ERROR") end else --b is nil, get local res = getRedis(a) if res then ngx.say(a..":"..res) else ngx.say(a..":nil") end end
脚本的角色和4.4.2.4 中的curl角色一样,访问URI /redis/get和/redis/set,它们被redis2-nginx-module转发给一致性hash upstream。
4.4.3.2 修改工程配置
# vim /var/objstore/objstore.conf upstream redis_nodes { consistent_hash $key; server 127.0.0.1:7000; server 127.0.0.1:7001; server 127.0.0.1:7002; } server { listen 8080; server_name _; location /redis/get { set_unescape_uri $key $arg_key; redis2_query get $key; redis2_pass redis_nodes; } location /redis/set { set_unescape_uri $key $arg_key; set_unescape_uri $val $arg_val; redis2_query set $key $val; redis2_pass redis_nodes; } location /lua { default_type 'text/html'; lua_code_cache off; content_by_lua_file /var/objstore/lua/redis.lua; } }与之前相比,加入了测试脚本的入口而已。
4.4.3.3 把工程加入nginx
# vim /usr/local/openresty-1.9.15/nginx/conf/nginx.conf keepalive_timeout 65; #gzip on; + lua_package_path "/var/objstore/lualib/?.lua;;"; + lua_package_cpath "/var/objstore/lualib/?.so;;"; include /var/objstore/objstore.conf; server { listen 80; server_name localhost;
4.4.3.4 测试
# /usr/local/openresty-1.9.15/nginx/sbin/nginx -s reload # curl -s "http://127.0.0.1:8080/lua?a=key1&b=value1" set key1:value1 OK # curl -s "http://127.0.0.1:8080/lua?a=key2&b=value2" set key2:value2 OK # curl -s "http://127.0.0.1:8080/lua?a=key3&b=value3" set key3:value3 OK # curl -s "http://127.0.0.1:8080/lua?a=key4&b=value4" set key4:value4 OK # curl -s "http://127.0.0.1:8080/lua?a=key5&b=value5" set key5:value5 OK # curl -s "http://127.0.0.1:8080/lua?a=key6&b=value6" set key6:value6 OK # curl -s "http://127.0.0.1:8080/lua?a=key7&b=value7" set key7:value7 OK # curl -s "http://127.0.0.1:8080/lua?a=key8&b=value8" set key8:value8 OK # # # curl -s "http://127.0.0.1:8080/lua?a=key1" key1:value1 # curl -s "http://127.0.0.1:8080/lua?a=key2" key2:value2 # curl -s "http://127.0.0.1:8080/lua?a=key3" key3:value3 # curl -s "http://127.0.0.1:8080/lua?a=key4" key4:value4 # curl -s "http://127.0.0.1:8080/lua?a=key5" key5:value5 # curl -s "http://127.0.0.1:8080/lua?a=key6" key6:value6 # curl -s "http://127.0.0.1:8080/lua?a=key7" key7:value7 # curl -s "http://127.0.0.1:8080/lua?a=key8" key8:value8
一切正常。看看key在redis实例(节点)间的分布。
# redis-cli -p 7000 127.0.0.1:7000> mget key1 key2 key3 key4 key5 key6 key7 key8 1) "value1" 2) (nil) 3) "value3" 4) (nil) 5) (nil) 6) (nil) 7) (nil) 8) (nil) 127.0.0.1:7000> exit # # redis-cli -p 7001 127.0.0.1:7001> mget key1 key2 key3 key4 key5 key6 key7 key8 1) (nil) 2) "value2" 3) (nil) 4) "value4" 5) "value5" 6) "value6" 7) (nil) 8) (nil) 127.0.0.1:7001> exit # # redis-cli -p 7002 127.0.0.1:7002> mget key1 key2 key3 key4 key5 key6 key7 key8 1) (nil) 2) (nil) 3) (nil) 4) (nil) 5) (nil) 6) (nil) 7) "value7" 8) "value8" 127.0.0.1:7002> exit #key分布在各个redis实例(节点)上,通过一致性hash,nginx能够正确的访问到每一个key。
5. 下一步
没有支持集群的redis lua API的情况下,通过ngx_http_consistent_hash和redis2-nginx-module也能够实现数据在不同节点分布的功能。不过,master宕机,如何实现自动fail over呢?集群的确提供了不少便利,假如自动fail over不易实现,尝试包装/修改lua-resty-redis来支持集群。
这两者有待于进一步研究。
相关文章推荐
- Redis+TwemProxy(nutcracker)集群方案部署记录
- Linux下Redis主从复制以及SSDB主主复制环境部署记录
- 【redis】——简单入门之安装
- Redis主从复制下的工作原理梳理
- redis和Spring的整合
- [Redis] redis入门
- redis 五种类型介绍
- redis学习笔记(一)
- centos6.5下Redis3.2.1安装和配置
- Redis cluster简介
- redis部署集群分片
- Redis 学习笔记四
- [备忘]Redis运行出现Client sent AUTH, but no password is set
- python对redis的常用操作 上 (对列表、字符串、散列结构操作)
- Redis配置主从数据,实现主从库之间数据同步
- Redis 学习笔记三
- Redis cluster搭建
- session入redis
- Redis学习笔记二——redis安装
- Redis 学习笔记3:Jedis 连接虚拟机下的Redis 服务