在编译了lua-nginx-module的nginx上,可以方便地使用shared dict特性,在不reload配置文件的情况下实现配置同步。
由于shared dict使用一块共享内存,因此所有worker均可读写,也就不存在一致性的问题。
使用shared dict
ngx.shared.DICT的作用域是”init_by_lua, init_worker_by_lua, set_by_lua, rewrite_by_lua, access_by_lua, content_by_lua, header_filter_by_lua, body_filter_by_lua, log_by_lua, ngx.timer.**”,加上最近的balancer_by_lua赌五毛也是可以使用的。
目前可用的API有
貌似春哥还准备给shared dict加上更多redis like的API
同步配置
作为例子使用一个的location来更新shared dict中的值,假设配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
http{ lua_shared_dict cfg 1m; server_name yay.lol; server { location = /test { content_by_lua ' ngx.redirect(ngx.share.cfg:get("test")) '; } location = /update_cfg { access_by_lua ' local args = ngx.req.get_uri_args(1) ngx.share.cfg:set("test", args["loc"]) ngx.exit(204) '; } } } |
可以使用curl http://yay.lol/update_cfg?loc=http%3A%2F%2Fyooooo.us来更新配置,更新后的配置将体现在/test的返回中,即重定向到http://yooooo.us
由于Lua简单的数据结构(ngx.shared本身就是一个table),还可以跳过shared dict的API:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
http{ server_name yay.lol; server { location = /test { content_by_lua ' ngx.redirect(ngx.share["cfg_test"]) '; } location = /update_cfg { access_by_lua ' local args = ngx.req.get_uri_args(1) ngx.share["cfg_test"] = args["loc"] ngx.exit(204) '; } } } |
优点是API更简洁,不用预先定义键和大小(lua_shared_dict项),还可以省去shared dict的API call(其实就是绑定到C的function)的开销;缺点是功能只有存取,没有ttl之类的功能了,而且reload之后会丢失。
现实场景
在现实场景中,我们肯定不能向每台服务器都发一个http请求来更新配合,不仅看起来非常地low,而且最重要的是相当于给自己的服务器留下了一个后门。当然你可以说做一些http验证啊、随机的server_name啊、allow/deny啊等等,但这其实增加了管理的开销。
mysql和redis等已经提供了replication的功能,并且有完善的认证机制,可以直接拿来用: )
这里以mysql举例。首先需要一台主服务器,配置的修改直接在这台机器上进行,对这台机器,可以使用上面提到的安全措施进行保护,甚至直接ssh进去也不是很费事。将这台主服务器设置为mysql的master,建立同步用的库和表(假设为config.main_tbl),然后将需要同步配置的机器设置为它的slave并同步该表。
在主服务器上,简单起见这里省略了所有错误处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
http{ server_name yay.lol; location = /update_cfg_master { access_by_lua ' local mysql = require "resty.mysql" local db, err = mysql:new() local ok, err, errno, sqlstate = db:connect{ host = "127.0.0.1", port = 3306, database = "config", user = "ngx_usr", password = "ngx_pwd", } local args = ngx.req.get_uri_args(1) db:query("UPDATE `main_tbl` SET `cfg_val` = ".args["loc"]." WHERE `cfg_key` = test") ngx.exit(204) '; } } } |
在从服务器上:
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 |
http{ lua_shared_dict cfg 1m; server_name lb1.yay.lol; server { location = /test { content_by_lua ' ngx.redirect(ngx.share.cfg:get("test")) '; } location = /update_cfg_slave { allow 127.0.0.1; allow ::1; deny all; access_by_lua ' local mysql = require "resty.mysql" local db, err = mysql:new() local ok, err, errno, sqlstate = db:connect{ host = "127.0.0.1", port = 3306, database = "config", user = "ngx_usr", password = "ngx_pwd", } local args = ngx.req.get_uri_args(1) res, err, errno, sqlstate = db:query("SELECT `cfg_val` FROM `main_tbl` WHERE `cfg_key` = \"main\"")) ngx.shared.cfg:set("test", res[1]["cfg_val"]) '; } } } |
然后,在crontab中增加定时curl localhost/update_cfg_slave的项