我的目标是:使用redis做分布式缓存;使用lua API来访问redis缓存;使用nginx向客户端提供服务。基于这个目标,自然想到openresty。
第一次接触,理解的不多。感觉就是:1. 把nginx和lua结合起来,使得nginx开发更加方便;2. 提供一些模块,例如memcached、redis、mysql,postgres等等;FIX ME!
lua-resty-redis是openresty(1.9.15.1)的一个组件,简单来说,它提供一个lua语言版的redis API,使用socket(lua sock)和redis通信。使用比较直观:
--连接
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)
在localhost上开启6个redis实例,它们的端口是:7000-7005;其中3个为master,另外3个为slave。
具体配置与部署,见前一篇博客:点击打开链接 第4节。
# 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
# mkdir /var/objstore
# mkdir /var/objstore/lua
# mkdir /var/objstore/lualib
# vim /var/objstore/lua/hello.lua
ngx.say("hello world!");
# 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;
}
}
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;
# /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;
# curl http://127.0.0.1:8080/lua
hello world!
# 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
# 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实例(节点)。
在找到支持redis集群的lua API之前,我想尝试一下redis非集群模式,借助于一致性hash,大致也能够满足目标(使用redis做分布式缓存;使用lua API来访问redis缓存;使用nginx向客户端提供服务)。
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:
下文可见,我们使用第一种,即根据参数来进行一致性hash。
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(细节?)。
我还是启动6个redis实例,它们两两互为主备(各对直接是独立的):
master slave
7000 7003
7001 7004
7002 7004
具体配置与部署,见点击打开链接 第3节。
卸载前文的安装
# /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
# rm -fr /var/objstore/
# mkdir /var/objstore
# mkdir /var/objstore/lua
# mkdir /var/objstore/lualib
# 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;
}
}
# 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;
# /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.2的基础上,我们使用lua脚本来访问redis。
# 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。
# 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;
}
}
与之前相比,加入了测试脚本的入口而已。
# 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;
# /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。
没有支持集群的redis lua API的情况下,通过ngx_http_consistent_hash和redis2-nginx-module也能够实现数据在不同节点分布的功能。不过,master宕机,如何实现自动fail over呢?
集群的确提供了不少便利,假如自动fail over不易实现,尝试包装/修改lua-resty-redis来支持集群。
这两者有待于进一步研究。