nginx_lua(3) - openapi项目有感

最近用nginx的开源项目openresty做了一个api网关的项目,代码就不方便开源了,其中踩了一些坑,做下笔记。
若有有缘人看到也希望少走弯路。

1、lua的os.time()返回时间戳,单位是秒而不是毫秒。对于这方面貌似要么使用第三方库才能解决,不过一般秒也够用了。
print(os.time())
1363263509

2、lua的模块是以文件为单位,所有的模块需要定义模块名和上下文,下面就是一个通用的模块供require的。一开始我没注意,在nginx配置文件中写入 lua_code_cache = off 时,就算不定义module也一样正常运行,可是当我切换到 lua_code_cache = on 时就出现了无法找到变量的错误了。
在包文件中定义:
module(“resclass”, package.seeall)
Res_Class = class(‘Res_Class’)

代码中引用:
local Res_Class = require “resclass”[“Res_Class”] –引用Res_Class类
注意,在 init_by_lua 中require的lua包会定义为全局变量,在之后content_by_lua等里放心使用

3、nginx事件主要发生3段,依次是 rewrite 阶段、access 阶段以及 content 阶段,这次我算是对这3个阶段有了彻底的理解,之前看文档还有点模模糊糊。这3个阶段就算你在代码中return,也还是会依次触发的。比如我在rewrite阶段执行了ngx.say()响应了一个东西,同时return,access和content阶段你注入的lua代码还是会依次执行的,除非你在中间显示的进行了跳转

4、HTTP method constants,比如:ngx.HTTP_GET 或 ngx.HTTP_POST,返回的不是字符串”GET”或”POST”,而是数字,而 ngx.req.get_method() 返回的却是字符串,这点文档上是有说明,需要注意

5、nginx lua的db库,比如我这次用到的mysql和redis库都带有连接池,需要善加利用,在close的时候请使用相对应的归还连接到连接池方法代替,这样我们的数据库连接就能够复用了,减少重复建立和销毁连接的损耗

6、lua是很脆弱的,比如将一个nil对象丢入string.lower()函数里就将导致抛出异常,所以一定要像写强类型语言那样检测变量的类型,而不是像node那样讲一个null随便丢入都不会报错。

7、lua的函数可以返回多个值,比如

return false,err

所以如下的代码很常见:
local ok, res_json = pcall(function() return –异常捕获,当用户提交不是json格式
cjson.decode(self._body_data)
end)
要习惯及善于使用函数多返回值的特性

8、nginx默认是不解析json格式的post提交的,好像有第三方库可以支持,但是我还是根据req头部手动写了一个支持json作为body的post提交支持,当然在使用 cjson.decode() 时可能抛出一个异常,所以在 cjson.decode() 或者 cjson.encode() 时要使用pcall来捕获可能发生的错误。

9、ngx.shared.DICT 是全局单进程共享的一个变量,可以往 ngx.shared.DICT 内存入key = value 的东西,当然value只能是字符串,不能存储lua的table等。所以我在项目中就是往 ngx.shared.DICT 存入了json字符串来了表示table缓存一些table格式的数据,这样就做到了一些table变量的进程共享内存。

10、善加利用 ngx.location.capture() 方法,他可以做到内部的跳转,而对客户端uri不变,还可以在跳转时动态修改参数以及一些其他东西。比如我们有:

1、获取用户头像接口A

2、获取用户信息接口B

3、获取用户好友接口C

4、获取用户最近帖子D

我们可以轻松的封装一个接口,获取用户的最新状态E,而对于E来说就是接口A,B,C,D的查询结果的总和,所以就可以利用这个方法轻松实现这类功能,而不用多写很多代码。

11、尽量减少 很多的字符串拼接,多利用table.concat,效率更高,lua中大字符串很常见,所以不用担心。

12、每次用户请求都是独立的一个上下文,所有的变量都会被自动GC,不用担心内存泄露,其实是你想泄露都没地方泄露。

13、resty.http这个库如果你想利用request的返回值,需要自定义body_callback函数,而且这个body_callback函数会执行多次,每次回将chunk作为参数返回,所以这里你就需要自己保存和拼接这些chunk了,不然默认body_callback 就是直接ngx.say(chunk),这不是我们想要的。

14、还是那个库,local ok, code, headers, status, body = self.http_client:proxy_pass 方法如果ok为false,则第二个参数code返回的不是number类型,所以我们得这样处理:

ngx.status = tonumber(code) or 502

15、nginx_lua的业务压测结果:

测试机:
cpu:E5620 @ 2.40GHz (4个)
men:8G

ab -n 500000 -c 1000 -T ‘application/json’ -p /tmp/open_api_test.json http://192.168.28.5/test/

qps:8715.41
cpu:60% (avg)
mem:22MB (avg)

CPU消耗还是蛮大的,最多的时候几乎跑到了80%,内存消耗非常小,这也和nginx单线程模型密不可分,业务主要包括redis库的多次访问,以及用户请求数据以及签名的md5是否合法等,这样的成绩单机性能已经完全可以应付我们公司的业务了。

就想到这么多,有机会再补充吧