在編譯了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的項