Vanilla (lua web framework)中文文档 [2018.09.19]
0、前言
香草/Vanilla是一个基于Openresty实现的高性能Web应用开发框架.
邮件列表
- vanilla-en vanilla-en@googlegroups.com
- vanilla-devel vanilla-devel@googlegroups.com
- vanilla中文邮件列表 vanilla@googlegroups.com
推荐始终使用最新版的Vanilla
当前Vanilla最新版本0.1.0.rc6,支持命令:
- vanilla-0.1.0.rc6(你没看错,自0.1.0.rc5起,vanilla的命令行和框架代码都带着版本号,方便多版本共存,也方便框架升级)
- v-console-0.1.0.rc6
特性
- 提供很多优良组件诸如:bootstrap、 router、 controllers、 models、 views。
- 强劲的插件体系。
- 多 Application 部署。
- 多版本框架共存,支持便捷的框架升级。
- 一键 nginx 配置、 应用部署。
- 便捷的服务批量管理。
- 你只需关注自身业务逻辑。
0.1、安装
1 | $ ./setup-framework -v $VANILLA_PROJ_ROOT -o $OPENRESTY_ROOT #运行 ./setup-framework -h 查看更多参数细节 |
0.2、快速开始
部署你的第一个Vanilla Application
1 | $ ./setup-vanilal-demoapp [-a $VANILLA_APP_ROOT -u $VANILLA_APP_USER -g $VANILLA_APP_GROUP -e $VANILLA_RUNNING_ENV] #运行 ./setup-vanilal-demoapp -h 查看更多参数细节 |
启动你的 Vanilla 服务
1 | $ ./$VANILLA_APP_ROOT/va-appname-service start |
社区组织
QQ群&&微信公众号
- Openresty/Vanilla 开发 1 群:205773855
- Openresty/Vanilla 开发 2 群:419191655
- Openresty 技术交流 1 群:34782325
- Openresty 技术交流 2 群:481213820
- Openresty 技术交流 3 群:124613000
- Vanilla开发微信公众号:Vanilla-OpenResty(Vanilla相关资讯、文档推送)
1、快速上手
1.1、Hello World
1.1.1、Vanilla 的安装
安装准备
- 安装好 OpenResty
- Vanilla Github 地址:https://github.com/idevz/vanilla
安装
1 | # 1.git clone 最新 Vanilla 版本(或者下载相应的 Vanilla release 版本) |
经过这 3 步如果没有报错,则安装 vanilla 成功
创建 vanilla 项目
1 | #1. 创建 vanilla 的运行用户 |
服务启动后,开发环境默认启动在 9110 端口,http://localhost:9110 即可访问
vanilla 常用命令
启动项目:
./va-cms-service start
或者./va-orcms-service start dev
重启项目
./va-cms-service restart
或者./va-orcms-service restart dev
停止项目:
./va-cms-service stop
或者./va-orcms-service stop dev
创建配置文件
./va-cms-service initconf dev -f
1.2、如何调试
1.2.1、Vanilla 的 调试
除了查看 nginx 错误日志辅助开发外,为了方便 Vanilla 项目的开发和调试,Vanilla 提供了诸如 print_r
之类的对象输出方法,以及详细友好的页面报错输出,你不需要到服务器日志去查看,就能所见即所得的开发调试代码.
sprint_r,print_r,lprint_r,err_log
- sprint_r
将 LUA 对象等格式化为易读的字符串返回
- print_r
类似 ngx.say
的效果,将对象、变量等以易读的格式进行输出,适用于 Vanilla 开发的 Web 服务
- lprint_r
print_r
的 CLI 版本,适用于 v-console 命令行环境
1 | ╰─○ v-console-0.1.0.rc6 |
- err_log
err_log 方法是对 ngx.ERR
的封装,将 msg
记录到 nginx 错误日志
1.3、如何新增一个Controller
1.3.1、Vanilla 的 controller
vanilla 的 controller 是业务处理的关键,vanilla 通过对 URI 的路由,找到本次请求对应的 controller 和 action。
- 最简单的 Controller
自动生成的 demo 中默认生成了 IndexController 和 index action(function IndexController:index()
),默认使用简单路由协议(vanilla.v.routes.simple
)对 URI 进行路由
1 | local IndexController = {} |
以上代码解释
关于上面的 controller 实例代码,我们只需关注下面几点
- IndexController:index (index Controller 中的 index Action),通过
self:getView()
方法获取视图实例 - 可以通过先调用
view:assign(p)
将所需要的参数传入视图,再调用view:display()
进行模板渲染,或者可以直接调用view:render('index/index.html', p)
方法,指定需要渲染的模板,并同时传入相应的参数 - 模板参数都是与 LUA 数组的形式进行传递
- 每个 action 的返回值都必须是字符串,所以可以知道
view:display()
和view:render()
方法都是返回字符串 - IndexController:action_b (index Controller 中的 action_b Action,这里注意,action 的方法名必须小写),使用默认的简单路由协议,访问 URI 为
curl http://localhost:9110/index/action_b
注:目前 vanilla 所默认使用的模板引擎是 appo 老师开发的 resty-template,模板详细的使用文档请移步 appo 老师处参阅。
1.3.2、新添加一个 Controller
给 Vanilla 添加一个新的 Controller 非常简单,只需要在项目的 controllers 目录,实现一个 LUA 包,包导入的函数即为各个 action, 文件名与 controller 同名。例如添加一个名为 idevz 的 controller, 且实现一个名为 dohello 的 action()。
1 | local IdevzController = {} |
1.4、如何使用Models/Dao
1.4.1、Vanilla 的 DAO
vanilla 的 DAO 预设为项目对数据源的封装,一切对数据源的操作都可以封装成 DAO,方便维护、管理、缓存等。 Vanilla 的 DAO 在项目的 models/dao 路径下,一般使用 LoadModel
方法进行加载
最简单的 DAO
由自动生成的 demo 中默认生成了 TableDao,可以看出 TableDao 只是一个普通的 LUA 包。
1 | local TableDao = {} |
以上代码解释
DAO 可以是任何对数据层访问封装的 LUA 包,实现方式非常自由。
1.5、如何使用Models/Service
1.5.1、Vanilla 的 Service
vanilla 的 Service 预设为项目对某些通用业务逻辑封装为独立的 Service,方便维护、管理、缓存等。 Vanilla 的 Service 在项目的 models/service 路径下,一般使用 LoadModel
方法进行加载
最简单的 Service
由自动生成的 demo 中默认生成了 UserService,可以看出 UserService 也只是一个普通的 LUA 包。不过 Service 一般调用更底层的 DAO ,并对之做必要封装,并将相关的 Service 暴露给 Controller 使用
1 | local table_dao = LoadApplication('models.dao.table'):new() |
以上代码解释
Service 可以是任何对数据层访问封装的 LUA 包
2、APIs
2.1、配置
香草/Vanilla的配置由以下三个部分组成.
- App配置
- Nginx配置
- WAF配置
2.1.1、App配置
应用基础配置(config/application.lua)
1 | Appconf.sysconf = { --系统预加载配置文件 |
应用基础配置的引用
1 | -- 如上的配置,可以在代码中通过 Registry['APP_CONF'] 表来进行获取,比如获取 APP_NAME |
错误处理配置(config/errors.lua)
根据errors.lua文件中实例,配置用户级别错误码.
1 | local Errors = { |
Restful 路由协议配置(config/restful.lua)
根据 URI 需要来自定义路由协议的配置
1 | local restful = { |
系统相关配置(sys/*)
比如DB、MC等资源配置,系统相关的分机房配置等(在某些大公司,这部分配置又运维人员统一管理和下发),文件格式目前使用相对更运维友好的 ini 文件,开发中可以方便的在 Registry[‘sys_conf’] 中获取相关数据,如 Registry['sys_conf']['cache']['lrucache']
获取 lrucache 相关配置
系统缓存相关配置 (sys/cache)
1 | [shared_dict] |
- 目前这部分配置一般由 vanilla.v.libs.cache 来使用
- 目前支持的配置项如 poolsize(连接池大小)、timeout(数据获取超时等)
系统缓存相关配置 (sys/v_resource)
1 | [mc] |
- 对所使用的数据资源做配置
Registry['sys_conf']['v_resource)']['db.user.write']['host']
获取写库的 HOST 信息
2.1.2、Nginx配置
自动生成的 Nginx 配置文件
初始化项目的时候会在项目目录下(nginx_conf/)自动生成这个项目所对应的两套(分别对应开发和线上环境)配置文件,生成的两套配置文件中,每套都包含 nginx.conf 和 vhost 两个配置文件
- 生产环境
va-nginx.conf 文件
va-nginx.conf 配置文件内容包含 nginx 配置主干(main、events、http 等重点配置段),包括用户、组的配置,工作进程等等通用配置,关键的还有 lua_package_path
、lua_package_cpath
的配置,还有框架初始化文件(vanilla/framework/init.lua)的加载
vhost/app_name.conf 文件
vhost/app_name.conf 文件是当前应用的相关配置,包括 APP_NAME、VANILLA_VERSION、$template_root、$va_cache_status 等全局变量的初始化,$document_root,Server_name 等的设置,还有关键的应用入口(content_by_lua_file),lua_shared_dict 等的设置,不过这些设置都是自动生成的,开发人员没有特殊需求的话,并不需要关注这些
- 开发环境
va-nginx-development.conf 文件
va-nginx-development.conf 文件的内容跟开发环境类似,唯一的区别在于加载框架初始化文件(vanilla/framework/init.lua)的方式为 init_by_lua_file
dev_vhost/app_name.conf 文件
默认的dev_vhost/app_name.conf 文件的配置同生产环境的配置基本一样,关键不同在于 lua_code_cache
的设置
注:所以初始化项目后,首先需要执行 sudo ./va-app_name-service initconf dev
命令,就是为了将自动生成的配置文件部署到 OpenResty 默认的配置文件路径下,如果需要更新 va-nginx(-development).conf 则还需要在命令后面加上 -f
参数进行强行部署,每次如果需要修改配置,也只需修改这部分配置,然后执行 initconf
即可
nginx.lua( vanilla-0.1.0.rc5 后废弃此配置 )
分为ngx_conf.common和ngx_conf.env两个部分,common是对Openresty指令集的配置如INIT_BY_LUA,可以是包或者文件(BY_LUA_FILE),env是环境的部分,包括了开发环境,测试环境和生产环境端口和缓存配置等控制.
1 | ngx_conf.common = { |
2.1.3、WAF配置
waf.lua
包括WAF规则的配置,及各种规则参数的配置,相关使用方法详见 waf
2.2、Bootstrap
2.2.1、使用 Bootstrap 来做服务初始化
Vanilla 使用 Bootstrap 来做应用初始化的工作,用户可以在此对应用做一些配置(比如所使用的路由协议,使用何种视图引擎),对配置做一些初始化加载,初始化 WAF,初始化 Plugins 等操作,Vanilla 运行在 OpenResty content_by_lua\这个 Phrase,使用 Bootstrap 可以很好的实现对请求的细粒度控制*
Bootstrap 即类 application.bootstrap
Bootstrap 的实现其实是一个名为 application.bootstrap
的 Vanilla 类,实现了构造器初始化的属性只有一个(当前请求所使用的 dispatcher),我们只需要关注根据需求实现各种 init 方法即可,最后只要在 boot_list
方法返回的列表中的 init 方法都会被顺序执行
1 | function Bootstrap:boot_list() |
上面的定义代表只有 initRoute
方法会被执行,而上面两个方法的实现我们并不需要关心和更改,只需要定义各种 init 方法,并更新 boot_list
返回的表元素即可,比如下面初始化路由协议的 initRoute
1 | function Bootstrap:initRoute() |
注:可以通过 self.dispatcher 获取当前请求相关的详细信息,并进行相关控制
2.3、Controllers
2.3.1、Vanilla 的 controller
vanilla 的 controller 是业务处理的关键,基本的用法请参考上文 (如何新增一个Controller) 。
关于 Controller
Vanilla 的 Controller 可以是任何普通的 LUA 包,只不过导入的方法被用作处理请求的 Action。如下示例:
1 | local IndexController = {} |
更面向对象的 Controller
Vanilla 支持使用 Class
方法来声明一个 Controller,实例如下:
1 | local IndexController = Class('controllers.index') |
这种情况下,可以定义 Controller 的构造器来对其进行初始化。示例如下:
1 | local IndexController = Class('controllers.index') |
甚至还可以声明一个 Controller 基类,处理某些通用的逻辑,相关的详细用法参见 Vanilla面向对象 相关章节。
关于 Action 的返回值
Vanilla 底层会将 Action 执行的结果,完全使用 ngx.print
进行输出,所以 Action 的返回值必须不能为空。而由于 Vanilla 的 Response 中,提供了给响应添加头尾的 Response:appendBody
和 Response:prependBody
方法,最终的结果会将这些部分合起来一起返回,所以 Action 的返回值要求如下:
- Action 返回值必须非空
- Action 返回值可以为一维索引数组(不可以是多维 Hash 数组)或者字符串
2.4、模板引擎
2.4.1、Vanilla 的视图引擎
为去除模板运行时模板解析带来的不必要开销,从 vanilla-0.1.0.rc7 起 Vanilla 开始支持 OpenResty 官方的 Lemplate 模板引擎,下面将简要介绍 Vanilla 中 Lemplate 的用法,以及 Vanilla View 接口介绍
Vanilla 的视图渲染
Vanilla 在 vanilla.v.dispatcher
中导入了默认的模板引擎(local View = LoadV 'vanilla.v.views.rtpl'
),但是可以在 Bootstrap 中实现 initView
来修改所使用的视图引擎。
而 Vanilla 的模板渲染,只需要在相应的 Action 中获取当前视图实例,注入数据,展示即可。下面就以 Lemplate 模板引擎为例,展示相关用法
首先在 Bootstrap 中实现 initView
方法,修改项目所使用的视图引擎
1 | -- application/bootstrap.lua |
运行 ./va-{app_name}-service ltpl
命令调用 Lemplate
编译你的 TT2 模板
下面是 TT2 模板示意
1 |
|
在 Vanilla Action 中调用编译好的模板
1 | function IndexController:index() |
注:
local view = self:getView()
获取当前视图实例view:assign({userlists=users, title = 'Vanilla-Lemplate'})
将数据注入视图
注:以上为 Lemplate 所使用的 TT2 模板实例,关于 Lemplate 的详细使用,可参考其详细 文档
2.5、插件
2.5.1、Vanilla 的插件体系
为了减少运行占用的系统资源,使开发更简便,Vanilla 默认只运行在 content_by_lua 这个 phrase,但是为了支持业务开发有层次化的请求控制,Vanilla 实现了便捷的插件机制,提供了六个钩子,给请求的细粒度控制提供了可能。下面我们来看看如何使用
Vanilla Plugin 的简单使用
在 Vanilla 项目中使用插件是非常简单的,只需要在 application/plugins/ 路径下实现 Vanilla 的插件 LUA 包即可,插件包可以按需实现 6 个钩子的方法。默认生成的 demo 项目中自动生成了一个 admin plugin,见 application/plugins/admin.lua。六个钩子方法按需实现,空方法可去掉,如下所示:
1 | local AdminPlugin = LoadV('vanilla.v.plugin'):new() |
Vanilla Plugin 的调用
Vanilla Plugins 的调用非常简单,只需要在 application/bootstrap.lua 中实现 initPlugin
方法,并调用 dispatcher
的插件注册方法将插件注入项目( self.dispatcher:registerPlugin(admin_plugin)
),即能在对应的时机执行相关钩子对应的方法
1 | local Bootstrap = Class('application.bootstrap') |
Vanilla 支持的插件钩子
以下列出 Vanilla 支持的 6 中插件钩子
1 | function Plugin:routerStartup(request, response) -- 开始路由 |
2.6、路由
2.6.1、Vanilla 的路由体系
Vanilla 实现的路由体系有一个路由器(vanilla.v.router
)和若干路由协议构成(Vanilla 默认实现了 vanilla.v.routes.simple
和 vanilla.v.routes.restful
两种路由协议,默认使用 simple
路由来路由请求),请求处理的开始阶段,Vanilla 通过调用路由器协议栈中的各种路由协议,计算出处理当前请求的 controller
和 action
,这就是 Vanilla 路由体系的职责所在。如果默认的两种路由协议不能满足你的 URI 路由需求,你可以参考我的一篇《如何给Vanilla(OpenResty)添加一个路由协议》的博文
Vanilla 路由器
Vanilla 的路由器 vanilla.v.router
是请求路由的基础,路由器提供了对路由协议的添加 addRoute(route, only_one)
,删除 removeRoute(route_name)
,获取路由列表 getRoutes()
等方法,用户可以调用这些方法来管理路由协议栈并使用路由器,不过用户不需要关心路由器的实现,而只需要关注路由协议的实现。
给路由器添加一条路由协议
路由器只有唯一一个 vanilla.v.router
,但路由协议可以有多个,通过 addRoute(route, only_one)
方法的调用可以向路由协议栈添加一条路由协议,第二个参数为可选参数,当设置为 true
时,代表将清空路由协议栈,只使用当前添加的这条路由协议,因为 Vanilla 路由请求的方式是路由器根据路由协议栈中的路由协议挨条解析,直到找到匹配的 controller
和 action
为止,太多的路由协议栈可能影响路由性能。
删除一条路由协议
Vanilla 的每条路由协议都有 route_name
属性,删除时只需要调用 removeRoute(route_name)
获取当前所使用的路由协议
Vanilla 支持获取当前请求所使用的路由协议,只需调用 getCurrentRoute()
方法,调用 getCurrentRouteName()
方法可以获取当前路由协议名
路由协议
路由协议非常的简单,因为路由协议的关键功能在于为当前请求找到对应的 controller
和 action
,核心在于根据当前请求实例 request
,通过实现 match
方法,来获取结果,下面是根据 vanilla.v.routes.simple
路由协议提炼出来的路由协议简单骨架:
1 | local Simple = {} |
注:我们需要关注以下两点
route_name
这是路由协议栈索引的关键,协议栈中的路由协议依靠 route_name 进行管理request
是当前请求的实例,包含了当前请求携带的 URI,http_header 等数据,是请求路由的依据
2.7、异常处理
2.7.1、Vanilla 的错误处理
Vanilla 的错误处理分为框架系统错误和应用错误两种类型,系统错误由框架控制,一般导致致命错误,直接抛出 500 内部错误,切不再往下执行,而应用错误则可以通过定义 errorController
来自定义处理
Vanilla 应用错误
Vanilla 提供了方便的错误处理方式,避免当代码运行报错后,页面只显示一个 500 错误的白页,没有详细报错信息,影响开发效率,Vanilla 的应用错误处理非常简单,在业务开发中,我们所关注的各个组件比如 DAO、Service、Controller、Action、Library 等都可能报错,Controller Action 作为 Vanilla 项目处理请求的执行体,一切业务组件的错误都可以通过一个统一的处理口径 errorController
来方便的处理。在业务组件开发过程中的错误,或者用户自定义的错误,都可以在 errorController
中得到捕获和处理,默认初始化的 demo 项目中,application/controllers/ 路径下,默认定义了一个 error.lua 文件,这就是前面所说的 errorController
,下面我们具体来看看这段代码:
1 | local ErrorController = {} |
代码释意:(你只需要关注以下几点,即可随意,按需定义适合你的 errorController)
- 这是一个普通的 LUA 包,一个普通的 Vanilla Controller,唯一需要注意的一点就是需要实现一个
error
方法,注意方法名小写 - 可以通过对运行环境的判断来,对不同的运行环境进行不同的错误处理,比如开发环境可能需要直接将错误打印到页面,而生产环境可能需要出错误页面等
Vanilla 框架系统错误(致命错误)
当有些 Vanilla 项目所必须的配置或者关键步骤执行异常而影响项目往下运行的情况下,会抛出致命错误,并结束当前请求,目前有以下几种情况
- 关键配置缺少
项目未配置项目名 name
,或者未指定项目根路径 root
,Vanilla 有很多地方依赖项目名,比如缓存的 KEY 设置,项目的各类包加载依赖于项目根路径做全局加载,多 APP 支持也依赖与此。如果 config/application.lua 中缺少这两个配置,则会如下错误:
1 | Sys Err: Please set app name and app root in config/application.lua like: |
- bootstrap 报错
Bootstrap 中的各种 init 方法并不是必须的,但是如果这部分方法定义后,执行错误,将影响整体项目的正常运行,所以 application/bootstrap.lua 中的运行报错也会报出系统致命错误,举例如下:
1 | function Bootstrap:initRoute() |
注:以上代码,最后一行操作试图将字符串与 bool 值 false 连接,会报出致命错误,如下:
1 | <pre /> |
- dispatch 执行报错
dispatch 属于框架的请求分发操作,请求分发执行出错直接导致致命错误,不过这个错误由框架自己处理,用户不需要关注
2.8、内建类
2.8.1、Vanilla 的內建变量和方法
为方便业务开发,Vanilla 提供了一些比较实用的內建方法和变量,这里我们说明如下,随着框架的更新,本页面会及时更新,欢迎随时关注。
Vanilla 的內建变量
Vanilla 的內建变量很多来自于 nginx.conf,其他则来自于 ngx.var
,Vanilla 将这些变量都缓存在了 Registry 中
- Registry 变量
Registry 是 Vanilla 中为了全局数据共享,及高效数据访问而封装的一个全局表,这里缓存了刻画当前请求比较全的数据,具体列表说明如下:
1 | -- 以下数据以 "curl http://domain.org/?arg1=aa1&arg2=aa2" 访问为例进行说明 |
注:上面很多全局变量是从 ngx.var.
获取来的结果缓存的,这样避免每次都请求 ngx.var
而减少这部分性能开销,并且其中有些信息比如 APP_NAME
,APP_ROOT
等服务一经启动就不会更改,而像 `REQ_相关的数据则是每次请求都不一样,好在一次请求可能对这部分数据会多次调用,所以将其缓存在
Registry` 表中*
Vanilla 的內建函数
Vanilla 有很多内建的函数,这些函数有些来自于 Vanilla 框架本身功能性的一些 LUA 包中,比如 vanilla.v.controller
、vanilla.v.request
、vanilla.v.response
等,另一些比如通用的方法,比如 print_r
、 page_cache
和 vanilla_init
,再有比如 Vanilla 定义的各种包加载函数,列表如下:
1 | LoadLibrary -- 加载项目 library 路径下的 LUA 包 |
注:以上有些加载器功能重复,目的在于减短所传递参数的长度,比如加载 Index Controller, 使用 LoadController 方法是,只需要写 LoadController('index'), 而如果使用方法 LoadApp 则应该写成
LoadApp(‘application.controllers.index’)`
- 方法
page_cache
该方法调用 Vanilla 封装的页面缓存逻辑,详细内容参见 (进阶/页缓存)
- 单步调试方法
print_r
、sprint_r
等
调试系列方法主要为了开发时能清晰方便的查看变量状态,记录开发日志等功能,详细内容参见 (快速上手/如何调试)
- 方法
init_vanilla
方法 init_vanilla
主要完成框架基础功能的初始化,比如 Registry 的初始化,各种 Loader 的定义,页面缓存的实现等,本方法默认在应用请求处理入口的第一句语句执行
3、Libs
3.1、Cookie
3.1.1、Vanilla 中使用 Cookie
Vanilla 中封装了 vanilla.v.libs.cookie
包,源至 lua-resty-cookie
,提供了简单的 get
、set
、getAll
等方法来控制 Cookie,下面具体使用举例如下:
vanilla.v.libs.cookie 包使用
一例胜千言:
1 | local IndexController = {} |
注:
vanilla.v.libs.cookie
支持以下 cookie 选项
1 | path |
4、进阶
4.1、页缓存
4.1.1、Vanilla 的 Page Cache
vanilla 的 Page Cache 实现了类似 Nginx 的 FastCGICache 或者 ProxyCache 的访问结果整体缓存,以 URI 的一定规则作为缓存的 KEY,属于内存型 Cache,存储位置可配置,默认存储在 OpenResty 共享字典(Share Dict)中,默认生成的项目中 Page Cache 为关闭状态
Page Cache 相关配置
Page Cache 相关的所有配置见项目的 config/application.lua 中, Appconf.page_cache
相关配置段,如下所示:
1 | Appconf.page_cache = {} |
配置释意
- cache_on 缓存开关,true 为开启 Page Cache,false 则为关闭
- cache_handle 设置 Page Cache 的存储介质,目前支持 Memcache、Redis、resty.lrucache、OpenResty Share Dict,默认为 OpenResty Share Dict
- no_cache_cookie 设置不缓存的 cookie KEY,Vanilla Page Cache 使用这个设置所指的 cookie KEY 来对某些特殊页面不缓存,默认当页面中有 KEY 为
va-no-cache
这个 COOKIE 的时候,当前页面不缓存 - no_cache_uris 设置不缓存的 URI 列表,默认配置例如 http://app.com/uris 命中
uris
则,当前页面不缓存 - build_cache_key_without_args 设置在缓存 KEY 中去除某些参数,比如某些 API 的版本号,或者随机数等,默认配置中的
rd
设置代表,当 URI 中有 rd 参数时,则生成的 Page Cache KEY 中清除这个参数
注:缓存的清理,只需要在请求的 URL 中,添加参数 vapurge
4.2、面向对象
4.2.1、面向对象的 Vanilla
Lua 提供了部分面向对象的语法糖,这仅仅能在开发中提供一个功能不完备的独立 Class 的使用,有 self
可以来引用 LUA 表的某些属性和方法,但是更多的面向对象特性,比如继承,比如类的构造等,LUA 支持的并不是非常好,日常的业务开发中,我们确实有些通用的逻辑可能需要复用,或者数据需要共享,需要有父子关系等等。所以我们在 Vanilla 中,简单封装了部分面向对象的特性,这里我们简单介绍其使用方法。
一个简单的 Vanilla 类
下面我们看一个例子:
类定义
1 | local LibA = Class("LibA") |
代码释意:
Class("LibA")
声明一个 Vanilla 类,类名为LibA
LibA:__construct( data )
提供了一个类 LibA 的构造器,并对相应的属性进行初始化
类使用
1 | local LibA = LoadLibrary('aa') |
执行结果 sex-->man
代码释意:(类使用的时候需要注意,类的使用分为类文件的加载 Load( 如这里的 LoadLibrary )
和实例化 LibA()
) 两个步骤
local LibA = LoadLibrary('aa')
载入类名为LibA
的类local liba_instance = LibA({name='idevz',sex='man'})
传入表{name='idevz',sex='man'}
对类进行相关的实例化liba_instance.sex
是对实例属性的引用
注:载入和实例化也可以一步达成 local liba_instance = LoadLibrary('aa')({name='idevz',sex='man'})
类继承
下面我么定义一个类 LibB,并使之集成于 LibA
类定义
1 | local LibB = Class("LibB", LoadLibrary('LibA')) |
代码释意:
Class("LibB", LoadLibrary('LibA'))
声明一个 Vanilla 类,类名为LibB
继承自类LibA
self.parent:__construct(data)
构造器中调用父类的构造器
类使用
1 | local LibB = LoadLibrary('LibB') |
执行结果 xxx
代码释意:
libb_instance:idevzDo
调用父类的idevzDo
方法
4.3、Vanilla 包开发
4.3.1、Vanilla 的包开发
可以使用任意 LUA 包的开发方式来开发 Vanilla 包(Controllers,Library,Dao,Services等),也可以使用 Vanilla 所提供的 (面向对象) 方式进行开发
对 Controller 使用继承和构造器
下面我们看一个例子:
1 | local IndexController = Class('controllers.index', |
5、OpenResty
5.1、OR文档精炼
OR文档精炼
感谢春哥给我们带来这么好的平台,在这里希望能通读 OR 文档,把自己的理解记录下来,并与时俱进的更新
5.1.1、描述 / Description
lua-nginx-module
模块通过标准的 Lua 5.1 解释器,或者 LuaJIT 2.0/2.1 在 Nginx 运行环境中嵌入 Lua,并利用 Nginx 的子请求,允许在 Nginx 的时间模块中集成强大的 Lua 线程(Lua 协程)。
与 Apache 的 mod_lua
和 Lighttpd 的 mod_magnet
不同的是,只要使用 lua-nginx-module
模块为 Lua 提供的 Nginx API 来处理上游服务的请求,诸如 MySQL、PostgreSQL、Memcached、Redis 或者上游的 HTTP Web 服务,网络传输都是 100% 非阻塞的。
至少下面列举的这些 Lua 包,和 Nginx 模块可以与 ngx_lua
模块完美结合使用:
5.1.2、ngx.timer
ngx.timer.at
语法:
ok, err = ngx.timer.at(delay, callback, user_arg1, user_arg2, ...)
上下文:
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*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*
使用一个自定义函数以及可选的自定义参数创建一个 Nginx 计时器
第一个参数 delay
以秒为单位指定计时器的延迟时间,支持分秒设置,比如 0.001 在这里表示 1 毫秒延迟。delay
同样可以设置为 0 ,此时如果当前句柄正被唤醒则计时器将立即获得执行。(in which case the timer will immediately expire when the current handler yields execution.//TODO yields)
第二个参数 callback
可以是任何 Lua 函数,后期延迟时间到了,该函数将被以一个后台 “轻线程” 的形式被调用。这个自定义的回调函数将被 Nginx 核心使用 premature
参数、user_arg1、user_arg2 等参数自动调用,参数 premature
是一个 boolean 值,表示当前定时器是否过期以后,而 user_arg1、user_arg2 等参数就是调用 ngx.timer.at
时所传递的余下参数列表。
当 Nginx 工作进程尝试关闭,比如在 Nginx 由于收到 HUP 信号而触发了 Nginx 配置重载的时候,或者 Nginx 服务正在关闭的时候,将会出现无效的计时器(//TODO Premature timer)。当 Nginx 工作进程尝试关闭,将无法通过调用 ngx.timer.at
来创建一个新的非零延迟的计时器,并且此时 ngx.timer.at
将返回 nil
和 “process exiting” 错误。
这个 API 从 v0.9.3 版本开始,即使 Nginx 工作进程开始关闭的时候,仍然允许创建零延迟计时器。
当一个计时器到期时,计时器中用户定义回调的 Lua 代码将在一个与创建这个计时器的源请求完全隔离的 “轻线程” 中运行,所以,源请求生命周期内的对象,比如 cosockets
并不能与回调函数共享。
下面来看一个简单的例子:
1 | location / { |
还可以创建一个无限执行的计时器,例如,一个每 5 秒触发执行一次的计时器,在它的回调方法中递归的调用 ngx.timer.at
,这里给出这样的一个例子。
1 | local delay = 5 |
因为定时器的回调函数都是运行在后端,而且他们的运行时间不会叠加到客户端请求的相应时间中,它们可能会因为 Lua 语法错误,或者过多的客户端请求而很容易在服务端造成累积,或者耗尽系统资源。为了防止出现像 Nginx 服务器宕机这种极端结果,在一个 Nginx 工作进程中提供了对 “等待中的计时器” 和 “运行中的计时器” 这两种计时器的数量限制。这里 “等待中的计时器” 是指还没有过期的计时器,而 “运行中的计时器” 是指那些用户回调方法当前正在运行的计时器。
一个 Nginx 进程中所允许的 “等待中的计时器” 允许的最大数量由 lua_max_pending_timers
指令控制。而允许的 “运行中的计时器” 允许的最大数量由 lua_max_running_timers
指令控制。
目前的实现,每个 “运行中的计时器” 都会从 nginx.conf 配置中 worker_connections
指令配置的全局连接列表中占用一个 (虚) 连接记录,所以必须确保 worker_connections
指令设置了一个足够大的值能同时包含真正的连接数和计时器回调函数运行所需要的虚连接数(这个连接数是有 lua_max_running_timers
指令设限的)。
许多 Nginx 的 Lua API 能在计时器回调函数的上下文中使用,比如操作流和数据包的 cosockets API(ngx.socket.tcp
和 ngx.socket.udp
),共享内存字典(ngx.shared.DICT
),用户协程函数(coroutine.*
),用户“轻线程”(ngx.thread.*
),ngx.exit
,ngx.now/ngx.time
,ngx.md5/ngx.sha1_bin
等都是可用的,但是相关子请求的 API (诸如ngx.location.capture
),ngx.req.* API
,下游输出 API (诸如 ngx.say
,ngx.print
和 ngx.flush
)都是明确在此上下文中不支持的。
你可以给计时器的回调函数传递大部分的标准 Lua 值类型(nils、布尔、数字、字符串、表、闭包、文件句柄等),要么显示的使用用户参数或者隐式的使用回调函数闭包的上游值。然而有一些例外诸如:你不能传递任何由 coroutine.create
和 ngx.thread.spawn
返回的线程对象,或者任何由 ngx.socket.tcp
、ngx.socket.udp
和 ngx.req.socket
返回的 cosocket 对象,因为这些对象的生命周期是与创建他们的请求上下文绑定的,而计时器的回调函数(设计时)是与创建他们的请求上下文分离的,并且运行在它自己的(虚)请求上下文中。如果你试图跨越创建这些线程和 cosocket 的请求上下文边界来共享这些线程和 cosocket 对象,将会报错,对线程将报错 no co ctx found
,对 cosocket 将报错 bad request
,然而在计时器回调函数内部来创建这些对象则是没问题的。
这个 API 在 v0.8.0 版本第一次释出。
ngx.timer.running_count
语法:
count = ngx.timer.running_count()
上下文:
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*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*
返回当前正在运行的计时器数量。
这个指令在 v0.9.20 版本第一次释出。
ngx.timer.pending_count
语法:
count = ngx.timer.pending_count()
上下文:
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*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*
返回当前正在等待的计时器数量。
这个指令在 v0.9.20 版本第一次释出。
5.1.3、ngx.config
ngx.config.subsystem
语法:
subsystem = ngx.config.subsystem
上下文:
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.*, init_by_lua*, init_worker_by_lua*
这个字符串字段表示了当前基于哪个 Nginx 子系统,对当前模块(ngx_stream_lua_module),这个字段始终返回 “http”,而对 ngx_stream_lua_module 模块,这个字段将返回 “stream”
这个字段在 v0.10.1 版本第一次释出。
ngx.config.debug
语法:
debug = ngx.config.debug
上下文:
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.*, init_by_lua*, init_worker_by_lua*
*这个布尔字段表示了当前 Nginx 是否打开 debug 编译选项,如编译时配置为 ./configure option --with-debug
。
这个字段在 v0.8.7 版本第一次释出。
5.1.4、coroutine
coroutine.create
语法:
co = coroutine.create(f)
上下文:
rewrite_by_lua*, access_by_lua*, content_by_lua*, init_by_lua*, ngx.timer.*, header_filter_by_lua*, body_filter_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*
使用 Lua 函数创建一个用户态 Lua 协程, 并返回一个协程对象。与标准 Lua 中的协程创建的 API coroutine.create
类似,但是工作在 ngx_lua 模块创建的 Lua 协程上下文中,这个 API 第一次是被使用在 0.9.2 版本的 `init_by_lua` 上下文中。*
这个 API 在 v0.6.0 版本第一次释出。
5.1.5、ngx.thread
ngx.config.subsystem
语法:
co = ngx.thread.spawn(func, arg1, arg2, ...)
上下文:
rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*
使用 Lua 函数 func
以及可选的参数 arg1
,arg2
等生成一个新的用户“轻线程”,返回一个 Lua 线程(或者 Lua 协程)对象代表这个 “轻线程”
“轻线程”仅仅是一种特殊的由 ngx_lua
模块来调度的 Lua 协程。
在 ngx.thread.spawn
返回之前, func
函数将会被使用响应的可选参数进行调用,直到此函数调用返回、或者因为错误而终止或是因为通过使用 Nginx 的 I/O 操作 API 导致请求挂起(如 tcpsock:receive
操作)。
在 ngx.thread.spawn
返回后,新被创建的“轻线程”将在各种 I/O 事件中保持通常的异步运行。
所有在 rewrite_by_lua
、access_by_lua
和 content_by_lua
运行的 Lua 代码块都在一个由 ngx_lua 自动创建的样板“轻线程”中,这些样板“轻线程”通常又叫“入口线程”。
默认情况下,相应的 Nginx 处理程序(例如 rewrite_by_lua
处理程序)不会终止直到“入口线程”和所有的用户“轻线程”都终止,一个“轻线程(要么是“入口线程”要么是“用户轻线程”因为调用 ngx.exit
,ngx.exec
,ngx.redirect
或者 ngx.req.set_uri(uri, true)
)或者“入口线程”因为报错而终止。
当一个用户“轻线程”因为报错而终止,他将不会像“入口线程”一样终止其他线程的运行。
因为 Nginx 子请求模块的限制,一般不允许中止一个正在运行中的 Nginx 子请求。所以同样禁止中止一个运行中的正在等待一个或多个 Nginx 子请求的“轻线程”。你应该调用 ngx.thread.wait
来在结束前等待这些“轻线程”结束。这里有个值得注意的例外是你可以通过使用而且只能使用 ngx.ERROR(-1),408,444或者499
状态调用 ngx.exit
来中止等待的子请求。
“轻线程”不是使用预先抢占的方式来调度的,换句话说,没有自动执行的时间片,一个“轻线程”将保持在 CPU 运行,直到一个(非阻塞)I/O 操作在一个单线程运行不能被完成。
The “light threads” are not scheduled in a pre-emptive way. In other words, no time-slicing is performed automatically. A “light thread” will keep running exclusively on the CPU until
a (nonblocking) I/O operation cannot be completed in a single run,
it calls coroutine.yield to actively give up execution, or
it is aborted by a Lua error or an invocation of ngx.exit, ngx.exec, ngx.redirect, or ngx.req.set_uri(uri, true).
For the first two cases, the “light thread” will usually be resumed later by the ngx_lua scheduler unless a “stop-the-world” event happens.User “light threads” can create “light threads” themselves. And normal user coroutines created by coroutine.create can also create “light threads”. The coroutine (be it a normal Lua coroutine or a “light thread”) that directly spawns the “light thread” is called the “parent coroutine” for the “light thread” newly spawned.
The “parent coroutine” can call ngx.thread.wait to wait on the termination of its child “light thread”.
You can call coroutine.status() and coroutine.yield() on the “light thread” coroutines.
The status of the “light thread” coroutine can be “zombie” if
the current “light thread” already terminates (either successfully or with an error),
its parent coroutine is still alive, and
its parent coroutine is not waiting on it with ngx.thread.wait.
The following example demonstrates the use of coroutine.yield() in the “light thread” coroutines to do manual time-slicing:
6、Vanilla Change log
6.1、0.1.0-rc3
6.1.1、创建应用
1 | vanilla new app_name |
6.1.2、代码目录结构
1 | /Users/zj-git/app_name/ tree ./ |
6.1.3、业务代码示例 IndexController
1 | local IndexController = {} |
6.1.4、模板示例 views/index/index.html
1 |
|
6.2、0.1.0-rc4
0_1_rc4 的安装使用与 0_1_rc3 没有本质区别,相关内容见
6.3、0.1.0-rc5
6.3.1、安装
- 通过 ./setup-framework 脚本安装 Vanilla
./setup-framework
脚本是对 make install
后面安装方式一个自动化封装,仅需要指定 OpenResty 的安装路径,即可简单安装 Vanilla
1 | ./setup-framework -h |
./setup-framework
脚本参数选项说明:
-v : 指定 Vanilla 项目的根目录,此选项默认为
/data/vanilla
,默认则将 Vanilla 安装到/data/vanilla/framework/0_1_0_rc5/vanilla/
目录下。-o : 指定 OpenResty 的安装目录, 此选项默认为
/usr/local/openresty
, 如果你的 OpenResty 安装路径与此不同,则需要指定为正真的 OpenResty 安装目录。
安装目录结构如下:
1 | tree /data/vanilla -L 2 |
- 通过
make install
安装 Vanilla
Vanilla 支持的选项都提供了默认值,如果你的环境与默认值不一样,请configure时指定成你自己的。
特别注意选项 --openresty-path
,默认为 /usr/local/openresty
,请确保设置正确。
可以在源码目录下执行 configure --help
来查看安装选项的使用方法。
下面是一个简单的安装示例:
1 | ./configure --prefix=/usr/local/vanilla --openresty-path=/usr/local/openresty |
6.3.2、Vanilla 使用
Vanilla命令
Vanilla-V0.1.0-rc5 目前依旧提供两个命令, 但是 rc5 以后提供的命令与安装的框架代码一样,都自动携带版本号,
vanilla-0.1.0.rc5
,和v-console-0.1.0.rc5
这样做的好处在于方便多版本共存以及 Vanilla 的无痛升级*
vanilla-0.1.0.rc5
用来初始化应用骨架,而vanilla-0.1.0.rc5
之后,服务的停启不再通过vanilla-0.1.0.rc5
命令管理,而是通过项目路径下面的va-appname-service
脚本进行管理,使用细节见后面说明。v-console-0.1.0.rc5
是一个交互式命令行,主要提供一种方便学习Lua入门的工具,可以使用一些 vanilla 开发环境下的包,比如table输出的 lprint_r 方法等。
- 创建应用
1 | vanilla-0.1.0.rc5 new app_full_path |
上面创建应用的过程也可以通过脚本 ./setup-vanilal-demoapp
简单自动完成:
1 | ./setup-vanilal-demoapp -h |
./setup-vanilal-demoapp
脚本参数选项说明:
- -a : 指定初始化应用的全路径(绝对路径),默认为 /data/vanilla/vademo
- -u : 指定运行服务的用户名,默认为 idevz
- -g : 指定运行服务的用户组,默认为 sina
- -e : 指定运行服务的环境,默认为’’ 指生产环境
安装目录结构如下:
1 | tree /data/vanilla/ -L 1 |
- 应用配置初始化与服务管理
通过脚本 /data/vanilla/vademo/va-vademo-service 管理 vademo 服务
1 | /data/vanilla/vademo/va-vademo-service -h |
注意: 如果没有使用 ./setup-vanilal-demoapp 脚本,而是手动 new 的 app, 则在启动服务之前需要先运行 /data/vanilla/vademo/va-vademo-service initconf [dev] 对相应环境的 nginx 配置文件进行初始化。
6.3.3、工程化的vanilla-0.1.0.rc5
vanilla-0.1.0.rc5是vanilla-0.1.0.rc4在新浪移动全线推广过程中针对一些工程化部署问题改进版本,可以肯定的一点vanilla-0.1.0.rc4在功能、扩展性、使用方面表现良好,非常适合移动业务的场景,更不止于API,但是在集群部署、多App部署、Vanilla框架升级等方面vanilla-0.1.0.rc4存在短板,而vanilla-0.1.0.rc5极大程度的弥补了这些短板。
- 推荐始终使用最新版的Vanilla
当前Vanilla最新版本0.1.0.rc5,支持命令:
- vanilla-0.1.0.rc5(你没看错,自0.1.0.rc5起,vanilla的命令行和框架代码都带着版本号,方便多版本共存,也方便框架升级)
- v-console-0.1.0.rc5
6.3.4、如何从vanilla-0.1.0.rc4到vanilla-0.1.0.rc5
升级步骤
- 安装最新的 vanilla-0.1.0.rc5
1 | $ wget https://github.com/idevz/vanilla/archive/v0.1.0-rc5.0.tar.gz |
- 基于新的 Vanilla(vanilla-0.1.0.rc5) 初始化新版的 vatest 项目:
1 | $ sudo vanilla-0.1.0.rc5 new $VANILLA_APP_ROOT #$VANILLA_APP_ROOT 为新版 vatest 项目全路径 |
将新项目中的以下目录及文件直接覆盖至老版 vatest 的相关位置:
- pub/index.lua 文件($VANILLA_APP_ROOT/pub/index.lua)直接覆盖
- va-vasina-service 脚本($VANILLA_APP_ROOT/va-vasina-service)直接复制,因为老版没有这个文件
- 注意确认脚本中 $OPENRESTY_NGINX_ROOT 的路径
- 注意确认脚本中 $VA_APP_PATH 的路径是否为项目的根路径
- nginx_conf 目录($VANILLA_APP_ROOT/nginx_conf)直接复制,因为老版没有这个目录
- application/bootstrap.lua 文件($VANILLA_APP_ROOT/application/bootstrap.lua)将老项目 bootstrap 中的相关 init* 方法拷贝到新的 bootstrap.lua 中,并用新的 bootstrap.lua 覆盖老版的 bootstrap.lua 文件
- 修改
$VANILLA_APP_ROOT/nginx_conf
中的 nginx 配置文件, 确保其中各种目录正确- 确保
$VANILLA_APP_ROOT/nginx_conf/va-nginx{-development}.conf
文件中lua_package_{c}path
中, 全路径的 Vanilla 框架目录正确 - 确保
$VANILLA_APP_ROOT/nginx_conf/{dev_}vhost/vasina.conf
文件中 Server 段的root
配置为项目根目录,require
方法中local VANILLA_ROOT
为框架根路径
- 确保
- 使用
$VANILLA_APP_ROOT/va-vasina-service
脚本管理服务:
- 给
$VANILLA_APP_ROOT/va-vasina-service
脚本添加可执行权限chmod +x $VANILLA_APP_ROOT/va-vasina-service
- 初始化 nginx 配置
$VANILLA_APP_ROOT/va-vasina-service initconf [dev]
- 启动服务
$VANILLA_APP_ROOT/va-vasina-service start [dev]
注: 添加 dev
参数,则管理 development 环境的服务, 默认为 production 生产环境服务
6.4、0.1.0-rc6
6.5、0.1.0-rc7
- Add lemplate 模板引擎
- Add dynamic dns for lib.http
7、Nginx执行阶段
我的理解Openresty = Nginx + ngx_http_lua_module + lua_resty_* ;它是一个原生Nginx合上一个HTTP_LUA模块,在加上一系列Lua_resty模块组成的一个Ngx_Lua高性能服务生态。
7.1、Openresty处理HTTP请求的执行阶段
Openrestry处理HTTP请求的执行阶段来自于Nginx,Nginx的HTTP框架依据常见的处理流程将处理阶段划分为11个阶段,其中每个处理阶段可以由任意多个HTTP模块流水式地处理请求,Openresty通过ngx_http_lua_module将Lua特性嵌入Nginx,ngx_http_lua_module属于一个Nginx的HTTP模块,为高性能服务开发封装了7个相应HTTP请求处理阶段如下:
- set_by_lua
- content_by_lua
- rewrite_by_lua
- access_by_lua
- header_filter_by_lua
- body_filter_by_lua
- log_by_lua
有同学会发现,Openresty所提供的LUA解析指令还有以下两个:
- init_by_lua
- init_worker_by_lua
init_by_lua
作用在配置加载阶段,init_worker_by_lua
作用在worker进程初始化阶段,并非HTTP请求处理阶段。
另外,需要重点提一下的是,Nginx输出过滤器是流式处理模型,一个数据块body filter就被调用一次。可以用以下简单的例子测试验证:
nginx.conf
1 | http { |
logs/development-error.log截取
1 | set_by_lua:1: =======>set |
可以看出body_filter的执行次数等于ngx.say数量加一
8、GDB 调试 OpenResty
8.1、使用 GDB 调试 Nginx
1 | sudo gdb -q -tui # -q 安静模式启动 GDB -tui 显示代码界面 |
进入 GDB 运行 attach
Nginx 子进程报错如下:
issing separate debuginfos, use: debuginfo-install libgcc-4.8.5-4.el7.x86_64 zlib-1.2.7-17.el7.x86_64
直接安装即可
8.2、断点
ngx_process_events_and_timers
子进程即 worker 进程在运行后会停留在epoll_wait
处等待相应的事件发生,而这个函数调用被封装在 ngx_process_events_and_timers
中
1 | 1 breakpoint keep y 0x000000000046c8f0 in ngx_process_events_and_timers at src/event/ngx_event.c:195 |
8.3、Lua 请求处理
8.3.1、堆栈
1 | 0 __libc_writev (fd=3, vector=0x7ffe08245dd0, count=14) at ../sysdeps/unix/sysv/linux/writev.c:68 |
8.3.2、断点
1 | 1 breakpoint keep y 0x000000000048c420 in ngx_http_process_request_headers at src/http/ngx_http_request.c:1185 |
8.4、使用 GDB 调试 OpenResty
8.4.1、加断点
比如 ngx_http_lua_ffi_worker_pid
1 | local ffi = require 'ffi' |
运行调用这个包的代码,则可以在 GDB 中加断点 ngx_http_lua_ffi_worker_pid
即可直接追踪 OpenResty 的大致运行过程。通过 c
继续下一步
breakpoints
1 | ngx_http_process_request_headers |
9、基于 OpenResty 安装 Luarocks
基于 OpenResty 来安装 其实意在基于 OpenResty 自带的 Luajit 来安装 Luarocks, Luarocks 安装时需要指定 lua 目录和 lua 的 include 目录,而 OpenResty 自身带有的 Luajit 就包含所需的 Lua 解释器和头文件。
只不过 Luarocks 安装需要的是 Lua 而不是 Luajit,这就是关键的一步
1 | # 我本机的 OpenResty 安装路径为 /usr/local/openresty-1.11.2.1/ |
经过以上几步的准备工作,就可以进入正常的安装步骤,演示如下:
1 | ./configure --with-lua-bin=/usr/local/openresty-1.11.2.1/luajit/bin --with-lua-include=/usr/local/openresty-1.11.2.1/luajit/include --prefix=/usr/local/luarocks-2.4.1 |
安装完毕后,只需要将 Luarocks 安装路径的 bin 目录(/usr/local/luarocks-2.4.1/bin)加到 PATH 环境变量下,即可使用 luarocks
命令来管理标准 Lua 包,使用 Luarocks 安装的包将保存在 Luarocks 安装的 share 目录,而可执行文件(比如 Vanilla 的可执行命令 vanilla 和 v-console)都在 luarocks 命令同级目录。
10、Vanilla集成的一些优秀第三方包
这里记录下vanilla中默认集成的一些优秀第三方包.
- Waf
- 模板引擎
- 工具包
10.1、Waf
10.2、模板引擎
vanilla-rc7 后默认使用此种模板引擎
10.3、工具包
11、QCon 2015 Broken Performance Tools
分享一个Brendan Gregg大神(QCon 2015分享)