您的位置:首页 > 运维架构 > Nginx

使用lua扩展nginx的功能

2016-12-01 15:57 363 查看
通常,nginx是作为一个负载均衡服务器或者web服务器使用,但是存在这样一种情况:当太多的无效请求通过nginx分发到上游的服务器的时候,虽然上游的服务器集群进行了水平扩展,但是当nginx仅仅做一个请求分发的动作的时候,太多的无效请求直接到达上游服务器,这样显然不太好。所以需要扩展一下nginx的功能,让他执行一些预定义的规则,拦截掉无效的请求。比如最简单的拦截IP黑名单,最简单的就是在nginx配置文件中写入黑名单IP,但是显然这样是很费力的,而且每次加入黑名单后需要nginx
-reload才能生效,这么做显然很糟糕。其实可以通过在nginx中嵌入lua脚本来方便的对nginx的功能进行扩展,并且这样也能比较方便的跟业务系统衔接起来,几乎实时生效,不用每次nginx -reload,这里的黑名单生效逻辑如下:

        业务系统产生IP黑名单 --> 放入redis --> lua从redis中将黑名单同步到nginx的共享内存 --> request到达nginx的时候,执行lua代码,检查访问者的IP是否存在于nginx的共享内存,如果访问者IP在黑名单,就直接在nginx处拦截,不让请求到达上游服务器

        建议直接使用openresty,它编译的时候已经包含了nginx和lua相关的模块

         以下是部分lua逻辑的代码:

          -- 更新黑名单处理

local function denyHandler(conn)
-- 首先检查黑名单是否有更新
local ok,err = conn:get(rc.UPDATE_DENY_CHECK_KEY)
if not ok then
ngx.log(ngx.ERR,err)
elseif ok==null or ok==0 then
-- 说明没有黑名单
return true
else
-- 比较黑名单更新tag 与本地缓存tag是否相同
-- 如果不同,则进行缓存的更新
local share = ngx.shared.rshare
local check = share:get(rc.UPDATE_DENY_CHECK_KEY)
if check == ok then
-- 没有更新,成功返回
return true
else
-- 更新缓存
local cache,err = conn:smembers(rc.UPDATE_DENY_CACHE_KEY)
if err then
ngx.log(ngx.ERR,err)
return false
else
local denies = ngx.shared.denies
-- 将旧有的全部置为过期
denies:flush_all()
if cache ~= null then
for __,deny in ipairs(cache) do
denies:set(deny,true)
end
end
-- 设置新的黑名单更新tag
share:set(rc.UPDATE_DENY_CHECK_KEY, ok)
return true
end
end
end

end

-- 更新系统黑名单

local updateDenyCache

updateDenyCache = function(premature)
if premature then
return
end
-- 系统黑名单放在共享内存中,进行互斥更新即可
local sync = sync.new()
sync.mutex(true,rc.DICT_SHARE_NAME,
rc.UPDATE_DENY_LAST_KEY,rc.UPDATE_DENY_DELAY,
denyHandler,updateDenyCache,"updateDenyCache")

end

-- 通用的执行递归操作的函数

function _M.mutex(need_redis,dict_name,upd_time_key,delay_time,handler,schedule,err_log,...)

    -- 锁定 保证执行的唯一性
local share = shared[dict_name]
local lock = locks:new(rc.DICT_LOCKS_NAME, rc.LOCK_OPT)
local red,conn
if need_redis then
red = redis:new()
-- 连接及失败处理
conn = red:getCoonect()
if not conn then
return
end
end

local elapsed, err = lock:lock(upd_time_key)
ngx.log(ngx.ERR,"upd_time_key:"..upd_time_key..",elaspsed:"..elapsed..",err:")
if elapsed ~= nil then
ngx.log(ngx.ERR,"lock acquired successfully !")
-- 获取现在距离上次执行的时间差
local sub = ngx.now() - shared:get(upd_time_key)
-- 增加调节系数,避免细微的时间差异的问题

        -- 这个时间的增加并不会导致对不同任务执行已否造成错判

        -- 因为这个时间与 delay time 差距是巨大的
sub = sub + rc.DELAY_ADJUST_TIME
-- 当时间大于等于 delay 时,说明本次还没执行 可以继续

        -- 执行成功后会更新执行时间,这样当下一个任务获得锁之后

        -- 瞬时内是不可能获得超过delay time的时间差值的

        -- 最坏的情况 这里执行多次也是可以接受的,虽然这不太可能出现
ngx.log(ngx.ERR,"sub:"..sub..",delay_time:"..delay_time)
if sub > delay_time then
ngx.log(ngx.ERR,"I am Entring")
if handler(conn,...) then
ngx.log(ngx.ERR,"Executed Success !")
share:set(upd_time_key,ngx.now())
end
end
-- 解锁
local ok,err = lock:unlock()
if not ok then
ngx.log(ngx.ERR,err)
end
else
ngx.log(ngx.ERR,err)
end

-- redis实例用完后,放回redis的连接池
if need_redis then
red:closeToPool()
end

-- 存在继续执行的计划任务,则再次设置
if schedule then
-- 再次设定timer 为避免不同任务的差异 需要根据共享值设定
-- 即使细小的差异 长久以后可能变成多个任务运行在不同的时间段
-- 导致根据时间差值判断谁先执行过 变得不准确
local lastUtime = share:get(upd_time_key)
-- 要执行的时间 = 计划时间 - (现在 - 上次执行任务的时间)
local delay = delay_time - (ngx.now() - lastUtime)
ngx.log(ngx.ERR,"delay_time:"..delay_time..",lastUtime:"..lastUtime..",ngx.now - lastUtime:"..(ngx.now() - lastUtime))

        ngx.log(ngx.ERR,"unknown value:")
-- 避免极端情况 同时不赋值为0 避免错误持续不断的执行下去
if delay < 0 then
delay = 1
end
local ok,err = ngx.timer.at(delay,schedule)
if not ok then
ngx.log(ngx.ERR, "failed to create the "..err_tag..": ", err)
return
end
end

end

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