您的位置:首页 > 数据库 > Redis

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来支持集群。

这两者有待于进一步研究。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: