如何lua_code_cache on且代码更新的情况下不用重启nginx

1,560 views
Skip to first unread message

陈立强

unread,
Apr 2, 2015, 2:27:16 AM4/2/15
to open...@googlegroups.com
1. 希望用nginx+lua实现类似网站统计的功能(页面PV,页面点赞这类的统计功能),原来的接口全是NGINX+PHP+MEMCACHE实现,现在希望能够使接口更快响应请求,采用了NGINX+LUA+REDIS做接口响应,后台功能仍然是采用PHP,以及用PHP去分析整理LUA接口采集到的数据。
2. 前期接口实现的功能会很简单,只有实现PV和页面点赞的功能,后期可能加在接口收集到更多的信息,但是为了运维方便,不希望每次项目有代码更新,都要运维去每台部署代码的服务器重启一遍(服务器有其他的应用)。
基于这种情况,请问有没有办法让LUA代码类似PHP这样部署,每次只要代码更新,不用失望nginx就可以实现接口的更新。
当然,上面的不重启考虑到生产环境的性能问题,是打缓存打开的,即:lua_code_cache on

我目前项目是打算只有一个入口:
 location ~ ^/api {
     content_by_lua_file /home/httpd/html/hits/trunk/api.lua;
 }
类似上面这样的配置,然后通过URL中的参数,执行不同的代码功能。
这里,我希望通过参数来判断要执行的功能,并获取指定lua代码目录下是否有相应的文件,如果有存在就执行,不存在就执行。
但是这样也存在一个io的操作。而且代码缓存后,上次判断不存在的文件,下次执行时是否会一直认为不存在?
另外在lua中这样执行一个指定文件中的代码有哪些好的方法吗?(类似于PHP中include 功能)

以上就是我的情况和个人的想法,因为刚接触lua不久,所以真心来请教各位有经验的,请指教一下。

DeJiang Zhu

unread,
Apr 2, 2015, 9:34:59 AM4/2/15
to open...@googlegroups.com
在 2015年4月2日 下午2:27,陈立强 <che...@gmail.com>写道:

这里,我希望通过参数来判断要执行的功能,并获取指定lua代码目录下是否有相应的文件,如果有存在就执行,不存在就执行。
但是这样也存在一个io的操作。而且代码缓存后,上次判断不存在的文件,下次执行时是否会一直认为不存在?

你这个想法不靠谱,如果更新文件怎么处理呢

话说 kill -HUP [1] 热更新不是挺好的么,还少了在代码里加多余的逻辑

 
另外在lua中这样执行一个指定文件中的代码有哪些好的方法吗?(类似于PHP中include 功能)


用 require [2]


--
--
邮件来自列表“openresty”,专用于技术讨论!
订阅: 请发空白邮件到 openresty...@googlegroups.com
发言: 请发邮件到 open...@googlegroups.com
退订: 请发邮件至 openresty+...@googlegroups.com
归档: http://groups.google.com/group/openresty
官网: http://openresty.org/
仓库: https://github.com/agentzh/ngx_openresty
教程: http://openresty.org/download/agentzh-nginx-tutorials-zhcn.html

nette...@gmail.com

unread,
Apr 2, 2015, 11:25:55 AM4/2/15
to open...@googlegroups.com
我想到个办法,就是要自己写点东西:

再加一个入口脚本,比方叫entry.lua,在这里面去loadfile你的api.lua。每次在loadfile之前检查一下api.lua文件的更新时间,如果没有更新,就不要loadfile了,用之前loadfile得到的chunk。如果有更新,那么重新loadfile一次。

检查文件更新时间可能要用C扩展了。

在 2015年4月2日星期四 UTC+8下午2:27:16,陈立强写道:

Heero Zhang

unread,
Apr 4, 2015, 8:14:18 AM4/4/15
to open...@googlegroups.com
就“要运维去每台部署代码的服务器重启一遍”这个原因的话,我觉得你们需要弄的是自动化更新,包括自动化重启nginx。
关于“服务器有其他的应用”,重启nginx并不会中断你的服务,之前的进程会处理完后才退出的,所以不存在影响其他的服务吧?

lfow...@gmail.com

unread,
Apr 4, 2015, 12:53:13 PM4/4/15
to open...@googlegroups.com
试下看看可否通过修改 package.loaded[modname] = nil 实现模块缓存的删除,如果可以的话,配置一个 location /codechange {} 来触发这个动作


在 2015年4月2日星期四 UTC+8下午2:27:16,陈立强写道:
1. 希望用nginx+lua实现类似网站统计的功能(页面PV,页面点赞这类的统计功能),原来的接口全是NGINX+PHP+MEMCACHE实现,现在希望能够使接口更快响应请求,采用了NGINX+LUA+REDIS做接口响应,后台功能仍然是采用PHP,以及用PHP去分析整理LUA接口采集到的数据。

阿杜.shzz

unread,
Apr 5, 2015, 11:50:13 PM4/5/15
to open...@googlegroups.com
对官方最新版(ngx_openresty-1.7.10.1)做了下测试 —— 配置 location /codechange {} ,在运行时动态修改 package.loaded[modname] = nil ,操作成功,可以触发对应模块的重新加载,你可以用这种方法来做代码热更新。


在 2015年4月2日星期四 UTC+8下午2:27:16,陈立强写道:
1. 希望用nginx+lua实现类似网站统计的功能(页面PV,页面点赞这类的统计功能),原来的接口全是NGINX+PHP+MEMCACHE实现,现在希望能够使接口更快响应请求,采用了NGINX+LUA+REDIS做接口响应,后台功能仍然是采用PHP,以及用PHP去分析整理LUA接口采集到的数据。

Yichun Zhang (agentzh)

unread,
Apr 6, 2015, 12:13:04 AM4/6/15
to openresty
Hello!

2015-04-05 20:50 GMT-07:00 阿杜.shzz:
> 对官方最新版(ngx_openresty-1.7.10.1)做了下测试 —— 配置 location /codechange {} ,在运行时动态修改
> package.loaded[modname] = nil ,操作成功,可以触发对应模块的重新加载,你可以用这种方法来做代码热更新。
>

loadstring + package.loaded 是标准玩法,但要仔细一些限制,比如被加载的 Lua 代码里不能通过 ffi.cdef
等接口定义新的 FFI 符号或通过 ffi.load 动态加载 .so 文件,否则在卸载 Lua
代码时会有内存错误。这个问题先前在这个邮件列表里面讨论过,比如

https://groups.google.com/d/msg/openresty/5ZLuSCQYh9w/wQTLRnRtFnkJ

在 CloudFlare 的 Lua CDN 系统中,我们就是使用的 loadstring + package.loaded 这个
trick,同时被加载的 Lua 代码是通过 KyotoTycoon 动态分发到 CloudFlare 的全球网络。

Regards,
-agentzh

阿杜.shzz

unread,
Apr 6, 2015, 1:23:34 AM4/6/15
to open...@googlegroups.com
噢,我忽略了 nginx 多 worker 的运行方式。
用 ngx.shared.DICT 来协调各 worker 主动触发代码热更新这个方法很棒!


在 2015年4月6日星期一 UTC+8下午12:13:04,agentzh写道:

陈立强

unread,
May 15, 2015, 5:31:58 AM5/15/15
to open...@googlegroups.com
谢谢春哥的解答,我用package.loaded解决了问题,不过loadstring我没用,这个感觉是动态加载代码进行执行,会不会影响到效率问题?


在 2015年4月6日星期一 UTC+8下午12:13:04,agentzh写道:
Hello!

陈立强

unread,
May 15, 2015, 5:36:21 AM5/15/15
to open...@googlegroups.com
谢谢,这个办法确实好用,不过我不是单独配置一个location,我是通过判断在url中是否有某个参数来判断,当我需要动态修改,则手动发起一个含有这个url参数的链接,代码判断有这个变量时则设置 package.loaded[modname] = nil。不知道这样操作是否合适?

在 2015年4月6日星期一 UTC+8上午11:50:13,阿杜.shzz写道:

Yichun Zhang (agentzh)

unread,
May 29, 2015, 4:18:30 AM5/29/15
to openresty
Hello!

2015-05-15 17:31 GMT+08:00 陈立强:
> 谢谢春哥的解答,我用package.loaded解决了问题,不过loadstring我没用,这个感觉是动态加载代码进行执行,会不会影响到效率问题?
>

你只要不是频发调用 loadstring() 就没问题。使用 package.loaded 缓存 loadstring()
的结果,可以避免频发调用 loadstring(). 由于这里讨论的就是 Lua 代码的动态加载和卸载,你不用 loadstring()
还能用什么?loadfile() 开销更大,因为要读文件系统。

> 谢谢,这个办法确实好用,不过我不是单独配置一个location,我是通过判断在url中是否有某个参数来判
> 断,当我需要动态修改,则手动发起一个含有这个url参数的链接,代码判断有这个变量时则设置
> package.loaded[modname] = nil。不知道这样操作是否合适?

这个就随便你怎么玩了。不过你的这种玩法要小心恶意用户故意频繁触发你服务器上面的 Lua 代码缓存的刷新,造成拒绝服务攻击。

Regards,
-agentzh

P.S. 抱歉回复晚了,最近还在休假中。。。

wwaz wwaz

unread,
Dec 20, 2016, 4:32:41 AM12/20/16
to openresty
你好,用require代替loadstring可以么?
目前的需求:已经被require的模块,后续还会修改,然后在不reload的情况下生效,即本次讨论的热加载。
目前的方案:在shmdict中设置标志符号,在请求来到时发现置位后将package.loaded[module]=nil ,然后将标志符号反置位不让其反复require,这样就会执行一次require重新加载热的代码,

有一个问题就是这个请求让一个worker热加载了后将dict中的标志符号反置位后,其它worker咋办呢,它们不能够热加载了

在 2015年4月6日星期一 UTC+8下午1:23:34,阿杜.shzz写道:

YuanSheng Wang

unread,
Dec 26, 2016, 6:02:47 AM12/26/16
to open...@googlegroups.com
2016-12-20 17:32 GMT+08:00 wwaz wwaz <3824...@qq.com>:
你好,用require代替loadstring可以么?
目前的需求:已经被require的模块,后续还会修改,然后在不reload的情况下生效,即本次讨论的热加载。
目前的方案:在shmdict中设置标志符号,在请求来到时发现置位后将package.loaded[module]=nil ,然后将标志符号反置位不让其反复require,这样就会执行一次require重新加载热的代码,

有一个问题就是这个请求让一个worker热加载了后将dict中的标志符号反置位后,其它worker咋办呢,它们不能够热加载了

可以用 lua module 缓存和 share dict 标识位做比较。如果不相同,触发代码热更新。
 

在 2015年4月6日星期一 UTC+8下午1:23:34,阿杜.shzz写道:
噢,我忽略了 nginx 多 worker 的运行方式。
用 ngx.shared.DICT 来协调各 worker 主动触发代码热更新这个方法很棒!


在 2015年4月6日星期一 UTC+8下午12:13:04,agentzh写道:
Hello!

2015-04-05 20:50 GMT-07:00 阿杜.shzz:
> 对官方最新版(ngx_openresty-1.7.10.1)做了下测试 —— 配置 location /codechange {} ,在运行时动态修改
> package.loaded[modname] = nil ,操作成功,可以触发对应模块的重新加载,你可以用这种方法来做代码热更新。
>

loadstring + package.loaded 是标准玩法,但要仔细一些限制,比如被加载的 Lua 代码里不能通过 ffi.cdef
等接口定义新的 FFI 符号或通过 ffi.load 动态加载 .so 文件,否则在卸载 Lua
代码时会有内存错误。这个问题先前在这个邮件列表里面讨论过,比如

https://groups.google.com/d/msg/openresty/5ZLuSCQYh9w/wQTLRnRtFnkJ

在 CloudFlare 的 Lua CDN 系统中,我们就是使用的 loadstring + package.loaded 这个
trick,同时被加载的 Lua 代码是通过 KyotoTycoon 动态分发到 CloudFlare 的全球网络。

Regards,
-agentzh

--
--
邮件来自列表“openresty”,专用于技术讨论!
订阅: 请发空白邮件到 openresty+subscribe@googlegroups.com
发言: 请发邮件到 open...@googlegroups.com
退订: 请发邮件至 openresty+unsubscribe@googlegroups.com



--

YuanSheng Wang
---------------------------------------
OpenResty lover ^_^
Message has been deleted

hzgoin

unread,
Apr 19, 2018, 3:20:22 AM4/19/18
to openresty
我写的一个热加载的功能
1.每次上线前修改version.lua中的_VERSION,随其他代码一起提交上线
2.调用check_version.lua(curl 'http://domain.com/dloading')将新的构建版本写进share dict
3.每次请求都会检查share dict中的build_version和缓存的version.lua中的_VERSION,如果不一致,重新加载指定的目录或模块
不知写的有没有坑。。。

version.lua
local _M = {
   
_VERSION = '108',--构建版本号
    shd_key = 'bu_version',
}
return _M


check_version.lua
local version_path = 'dloading.version'
local main_path = 'dloading.main'
--上一版本
local cached_version = require(version_path)
package.loaded[version_path] = nil
--最新版本
local version = require(version_path)
--如果版本变更上线,重新加载指定的模块
local dloading = ngx.shared.dloading
dloading:set(version.shd_key, version._VERSION)
if version._VERSION and cached_version._VERSION and version._VERSION ~= cached_version._VERSION then
    --代码上线后,更新代码缓存
    package.loaded[main_path] = nil
    local dyload = require(main_path)
   
dyload:reload_change()
   
ngx.say('up to '.. version._VERSION)
else
    ngx.say('no update '.. version._VERSION)
end


main.lua
--用于代码动态加载
local cached_version = require"dloading.version"
local ipairs = ipairs
local str_find = string.find

local _M = {}

_M.check_version = function(self)
   
local dloading = ngx.shared.dloading
    local build_version = dloading:get(cached_version.shd_key)
   
if build_version and cached_version._VERSION and build_version ~= cached_version._VERSION then
        self:reload_change()
   
end
end
--更新被加载的模块
_M.reload_change = function(self)
   
local cached_mod = package.loaded
    local tmp_var
    for k,v in pairs(cached_mod) do
        if self:match_reload_dir(k) then
            cached_mod[k] = nil
            tmp_var = require(k)
       
end

    end
end

--只有预定义的目录支持热加载,业务代码目录
_M.match_reload_dir = function(self, mod_name)
   
local reload_dir_or_file = {
       
'lib.',
       
'conf.',
       
'api.',
       
'model.',
       
'index',
   
}
   
local pos_start, pos_end
    for _,v in ipairs(reload_dir_or_file) do
        pos_start, pos_end = str_find(mod_name, v)
       
if pos_start and pos_start == 1 then
            return true
        end
    end
    return false
end

return _M


index.lua(入口脚本)
--动态加载
local dloading = require"dloading.main"
dloading:check_version()

nginx location
location = /dloading {
             content_by_lua_file 'path/to/dloading/check_version.lua';
         }




在 2015年4月2日星期四 UTC+8下午2:27:16,陈立强写道:
1. 希望用nginx+lua实现类似网站统计的功能(页面PV,页面点赞这类的统计功能),原来的接口全是NGINX+PHP+MEMCACHE实现,现在希望能够使接口更快响应请求,采用了NGINX+LUA+REDIS做接口响应,后台功能仍然是采用PHP,以及用PHP去分析整理LUA接口采集到的数据。
Reply all
Reply to author
Forward
0 new messages