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