注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

snoopyxdy的博客

https://github.com/DoubleSpout

 
 
 

日志

 
 

nginx_lua(3) - openapi项目实战  

2013-03-06 20:14:38|  分类: lua |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
nginx和lua的配合让我们眼前一亮,高性能的带逻辑和数据库访问的nginx诞生了,感觉它天生就是为了 http 接口或者代理而生的。现在有这样一些需求:
1、将公司所有的api放在后方,让ngx_lua顶在最前面,作为 api 服务的前置入口,安全性要好
2、要能抗住压力,毕竟公司所有的 api 接口都将从ngx_lua这条总线走,不能有太长的响应
3、要可以扩展,如果一台服务器扛不住压力,可以很方便的进行水平扩展
4、要稳定,不能隔三差五的挂一次,不能影响公司的 api 服务
5、要好维护,出现问题能及时解决

对于以上5点,我们使用ngx_lua完全满足,其中可维护和稳定性是我们不使用node.js的主要原因。我们主要使用openresty来作为我们openapi项目的架构。
下面使用一些代码片段来简单介绍一下ngx_lua在实际使用中的特性:

1、ngx配置:

worker_processes 1;
error_log logs/error.log debug;
pid /usr/local/openresty/nginx/logs/nginx.pid;
#events配置
events {
use epoll;
worker_connections 1024;
}
http {
#设置dns服务器
resolver 10.1.1.1;

#设置lua缓存off,product应该开启
lua_code_cache off;

# 设置lua模块的require路径
lua_package_path 'lib/?.lua;lua/class/?.lua;lua/db/?.lua;;';

# 设置lua的c模块的require路径
lua_package_cpath 'lib/?.so;;';

#执行lua初始化
init_by_lua_file 'lua/init/init.lua';

#加载server配置
include vhost/server_debug.conf;
}

vhost.conf

server {
#侦听80端口
listen       80;
#定义使用www.xx.com访问
server_name  test.api6998.com;
#反向代理的配置       
location /favicon.ico{
   echo 'favicon.ico';
}
location / {
   content_by_lua_file 'lua/access/access.lua';
        }
location /lua_test {
   content_by_lua_file 'test/test.lua';
        }
}

注意点:
1、dns服务器如果在内网的话需要制定,否则resty.http会无法发起请求
2、设定pid保存路径可以支持 nginx -s reload
3、lua_code_cache off; 是便于开发的,将lua缓存关闭,避免每次都重启ngx
4、require路径是指lua的require包,会依次去这些路径获取lua包,注意相对目录是当前ngx启动目录
5、特别注意,init_by_lua_file 是在nginx启动,让lua做一些初始化动作使用的,只可以使用一次,不能有多个init_by_lua_file。
6、这里我们设置了2个路径/作为api的通用路径,让lua去处理一些路由匹配的事情,/lua_test是用来执行整个openapi的测试代码的


2、启动nginx

nginx -p `pwd`/ -c conf/nginx_main_debug.conf;

在跟了参数-p之后,我们上面的ngx配置就可以使用相对路径了


3、lua连接和使用mysql
lua连接mysql也相对的简单,openresty封装了mysql的链接库供lua使用,使用C编写的,相当搞笑。

Mysql_CLass = {
  host = "192.168.28.4", 
  port = 3306,
  database = "openapi",
  user = "root",
  password = "123456",
  max_packet_size = 1024 * 1024
}

function Mysql_CLass:connect()

local db, err = mysql:new()
if not db then
    ngx.log(ngx.ERR, "mysql library error " .. err)   --  如果mysql模块出错,则记录错误日志
    return ngx.HTTP_INTERNAL_SERVER_ERROR, nil, ERR_MYSQL_LIB 
end

db:set_timeout(1000) -- 1 sec

local ok, err, errno, sqlstate = db:connect{
    host = self.host,
    port = self.port,
    database = self.database,
    user = self.user,
    password = self.password,
    max_packet_size = self.max_packet_size
}

if not ok then
    ngx.log(ngx.ERR, "mysql not connect: " .. err .. ": " .. errno .. ": ".. sqlstate .. ".")  --如果数据库连接错误则记录日志
    return ngx.HTTP_INTERNAL_SERVER_ERROR, nil, ERR_MYSQL_DB
end

return ngx.HTTP_OK, db, nil

end

如果一切顺利,我们就可以使用返回的 db 类来操作mysql数据库了,一个简单的联表查询:

local res2, err, errno, sqlstate =
db:query("SELECT name,apikey,ApiSecret from ApiUserRoleTag as a JOIN ApiUser as b ON a.ApiUserId = b.id where a.ServiceRoleId = " .. api_service_table.serviceroleid )
if not res2 then
ngx.log(ngx.ERR, "bad result: " .. err .. ": " .. errno .. ": ".. sqlstate .. ".") --出错记录错误日志
return ngx.HTTP_INTERNAL_SERVER_ERROR, nil, ERR_MYSQL_ERROR
end

直接在db:query()里写sql语句即可,当然我们也可以封装成ORM,不过既然ngx_lua被定义为接口,自然对数据库访问不会向做后台系统那么频繁,所以只需要简单的写一些sql语句就可以了

4、一些逻辑判断

local code, service_table, error_code = Mysql_CLass:query_api_service(db, ngx.var.uri)
if(code ~= ngx.HTTP_OK) then
return res:send({status=code, error_code = error_code})
end
local req = Filter:new(service_table)
local code, error_code = req:check_all()
if(code ~= ngx.HTTP_OK) then
return res:send({status=code, error_code = error_code})
end

比如上述代码就是根据mysql返回值和用户请求req的参数是否合法进行的不同的响应,代码不用细看,这样我们就可以让nginx拥有更加强大的逻辑判断,在openapi项目中,nginx更是获得了验证用户公钥和签名验证的能力,直接在nginx中判断比之前proxy到后端验证再响应高效很多。

5、根据需要进行http request
我们使用ngx_lua不仅要解决参数错误,签名有误和一些访问日志以及访问频率的限制,更需要将合法的请求转发到后端的应用服务器中,应用服务器可能在各个地方,拥有各种域名或ip,所以我们需要让lua具有发送http请求的能力,同时就算我们后端应用服务器更换ip或者down机了,也不用更改nginx配置和重启,只需要在mysql数据库或者redis数据库将相应的domain或ip变更即可,灵活度大大提高。
我们看一个利用resty.http模块简单的发送一个请求例子:

function Http_Class:send_request()
self.data_array = {}
local ok, code, headers, status, body = self.http_client:proxy_pass {
url = self.url, --例如 http://www.baidu.com
headers = self.header, --各种http请求头部,table格式
method = self.method, --POST,GET,PUT,DELETE
body = self.body, --KEY,VALUE
body_callback = function(data, ...)
table.insert(self.data_array, data)
end
}
if(ngx.header["Transfer-Encoding"]) then
ngx.header["Transfer-Encoding"] = nil
end
if(ngx.header["Connection"]) then
ngx.header["Connection"] = nil
end
self.data = table.concat(self.data_array, "")
return ok, code, headers, status, body
end

注意点:
1、为什么将 Transfer-Encoding 设置为 nil,这里我发现一个小坑,可能是我不会用,如果不设置他为nil,则在响应给客户端时会出现2个Transfer-Encoding和Connection
2、body_callback 这个回调函数肯能会被执行多次,这个回调表示当ngx接受到数据就会执行这个回调,所以如果不定义这个回调,resty.http库会自动调用ngx.print(data)直接响应给客户端了,为什么不用 str = str + data,虽然不清楚resty.http是否支持2进制的返回,这样str = str + data对2进制返回必然不友好。
即使返回 string 类型,使用table将其保存,并且最终利用 table.concat 将他们连接起来要比之前字符串直接拼接高效
3、另外真正的返回 http.status 是在 code 中

openresty项目地址:http://openresty.org/
  评论这张
 
阅读(9135)| 评论(7)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2016