openresty consistent hash

openresty大名如雷贯耳,有着活跃的社区和广泛的用户。它借助强大的nginx和lua语言,构建了轻巧但是性能强大的web技术栈。

这两天开始认真关注起这个项目来,在github下载源码并且运行了试试,觉得不错。本人以https://github.com/agentzh/lua-resty-balancer这个项目为例,介绍怎么使用openresty进行一致性hash的负载均衡

一切都在我的mbpr上进行。

流程图:

其中nginx1和nginx2指的是我在mac上按照不同的配置文件启动了两个nginx,其中监听8001端口nginx1的为服务入口,特别说明下这个nginx1是openresty提供的nginx,因为我们需要使用chash的库;而nginx2:8080则为普通的nginx,它和apache:80都是充当后端服务器的角色。

编译chash.so

openresty不包含lua-resty-balancer这个项目,所以需要自己编译然后生成so文件。

clone下载代码,然后修改Makefile,设置lua的目录。其中在macOS上编译,要修改CFLAGS增加-Du_char="unsigned char"

1
2
3
LUA_INCLUDE_DIR ?= /usr/local/Cellar/lua/5.2.4_4/include
LUA_LIB_DIR ?= /usr/local/Cellar/lua/5.2.4_4/lib
CFLAGS := -Wall -O3 -flto -g -DFP_RELAX=0 -DDEBUG -Du_char="unsigned char"

之后make会生成so文件。

启动入口nginx

安装openresty就不说了,网上有很多教程。启动8001端口的nginx1。配置文件如下,其中lua_package_path和lua_package_cpath为加载chash的库so和lua文件,这个视读者自身的目录自己修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
lua_package_path "/Users/libingtao/github/openresty/lua-resty-balancer/lib/?.lua;;";
lua_package_cpath "/Users/libingtao/github/openresty/lua-resty-balancer/.so;;";
## include mime.types;
server_tokens off;
init_by_lua_block {
local resty_chash = require "resty.chash"
local resty_roundrobin = require "resty.roundrobin"
local server_list = {
["127.0.0.1:80"] = 2,
["127.0.0.1:8080"] = 2,
}
-- XX: we can do the following steps to keep consistency with nginx chash
local str_null = string.char(0)
local servers, nodes = {}, {}
for serv, weight in pairs(server_list) do
-- XX: we can just use serv as id when we doesn't need keep consistency with nginx chash
local id = string.gsub(serv, ":", str_null)
servers[id] = serv
nodes[id] = weight
end
local chash_up = resty_chash:new(nodes)
package.loaded.my_chash_up = chash_up
package.loaded.my_servers = servers
local rr_up = resty_roundrobin:new(server_list)
package.loaded.my_rr_up = rr_up
}
upstream backend_chash {
server 0.0.0.1; # just a invalid name
balancer_by_lua_block {
local b = require "ngx.balancer"
local chash_up = package.loaded.my_chash_up
local servers = package.loaded.my_servers
-- we can balancer by any key here
ngx.log(ngx.ERR, "chash args is ", ngx.var.arg_key)
local id = chash_up:find(ngx.var.arg_key)
local server = servers[id]
ngx.log(ngx.ERR, "chash server result is: ", server)
assert(b.set_current_peer(server))
}
}
server {
server_name localhost;
listen 8001;
location / {
proxy_pass http://backend_chash;
}
}

其中,server监听8001端口,并且把请求转发给后端。

注意chash_up:find(ngx.var.arg_key)这一句,把ngx.var.arg_key作为hashkey,它是nginx解析的,如果url为?key=helloworldarg_keyhelloworld

启动后端

其中8080的nginx和80端口的apache不用太多说明。

nginx:8080

1
2
3
4
5
6
7
8
9
10
11
12
13
14
worker_processes 1;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
server {
listen 8080;
location / {
root html;
index index.html index.htm;
}
}
}

apache:80是默认的apache配置文件,没有任何修改。

测试

把nginx1、nginx2、apache启动后。

在浏览器输入localhost:8001,此时arg_key为nil:

在浏览器输入localhost:8001?key=hello,此时arg_key'hello':

上面表示我们的负载均衡已经完成了。

总结

整个流程不算复杂,周末下雨,在家中就搞了搞权当消遣。昨天在公司看了@agentzh在某一个nginx大会上分享OR技术栈,觉得春哥真的是很喜欢这一行,并且分享的时候大部分时间是带着微笑的,让人感觉技术人员的自信和平和。