一、Openresty数据库请求理念
Openresty这个nginx变种产品之前早有耳闻,直到最近花了点时间读了下《OpenResty最佳实践》(OpenResty-Best-Practices)一书。其实Openresty引入lua实现的一个比较强的一个功能就是和后端mysql、redis 等数据库的连接更直接。减少了中间其他动态语言再去处理的过程,可以通过nginx直接返回或修改数据,也便于实现API。
常规请求步骤:
nginx ---> php / tomcat ---> mysql
openresty请求步骤:
openresty ---> mysql
上面只是简单描述了下单向请求,同样数据返回也一样。而动态语言在通过页面请求时,其处理的过程相对于nginx这种非阻塞模式是很慢的。
懒得画图了,这里盗用JD架构里的一张图。可以看到nginx+lua取标签类数据时,直接从redis去取,在redis缓存中不存在的,才会回源到tomcat再去取,tomcat从后端数据库查询到相关数据库再更新到redis里去。便于下次查询命中。
二、Openresty连接mysql
Openresty和mysql连接有两种方法:一种是使用其早期加入的Drizzle 模块(c语言实现),另一种是使用lua mysql模块。
1、Drizzle 模块
先看下Drizzle模块,官方给出的操作示例如下:
http {
upstream backend {
drizzle_server 127.0.0.1:3306 protocol=mysql dbname=ngx_test user=ngx_test password=ngx_test;
drizzle_keepalive max=10 overflow=ignore mode=single;
}
server {
listen 8080;
location @cats-by-name {
set_unescape_uri $name $arg_name;
set_quote_sql_str $name;
drizzle_query 'select * from cats where name=$name';
drizzle_pass backend;
rds_json on;
}
location @cats-by-id {
set_quote_sql_str $id $arg_id;
drizzle_query 'select * from cats where id=$id';
drizzle_pass backend;
rds_json on;
}
location = /cats {
access_by_lua '
if ngx.var.arg_name then
return ngx.exec("@cats-by-name")
end
if ngx.var.arg_id then
return ngx.exec("@cats-by-id")
end
';
rds_json_ret 400 "expecting \"name\" or \"id\" query arguments";
}
}
}
可以看出其调用非常简单,不过其默认在安装时并未集成该模块“ This ngx_drizzle module is not enabled by default. You should specify the –with-http_drizzle_module option while configuring OpenResty ”,其安装步骤有点麻烦,具体可以参看官方手册。为防止SQL注入等,上面有调用两个方法:set_unescape_uri、set_quote_sql_str ,这两个方法不是nginx本身的方法,是openresty里实现的方法。该模块更新并不频繁,感觉也并不是openresty官方主推的模块。更多也可以参看openresty在github上的关于该模块的部分: 。
2、lua实现的方法
先看下一个CRUD代码,如下:
[root@localhost ~]# cat /usr/local/openresty/nginx/lua/test_mysql.lua
local function close_db(db)
if not db then
return
end
db:close()
end
local mysql = require("resty.mysql")
local db, err = mysql:new()
if not db then
ngx.say("new mysql error : ", err)
return
end
db:set_timeout(1000)
local props = {
host = "127.0.0.1",
port = 3306,
database = "test",
user = "root",
password = "asdf"
}
local res, err, errno, sqlstate = db:connect(props)
if not res then
ngx.say("connect to mysql error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
local drop_table_sql = "drop table if exists test"
res, err, errno, sqlstate = db:query(drop_table_sql)
if not res then
ngx.say("drop table error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
local create_table_sql = "create table test(id int primary key auto_increment, ch varchar(100))"
res, err, errno, sqlstate = db:query(create_table_sql)
if not res then
ngx.say("create table error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
local insert_sql = "insert into test (ch) values('hello')"
res, err, errno, sqlstate = db:query(insert_sql)
if not res then
ngx.say("insert error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
res, err, errno, sqlstate = db:query(insert_sql)
ngx.say("insert rows : ", res.affected_rows, " , id : ", res.insert_id, "")
local update_sql = "update test set ch = 'hello2' where id =" .. res.insert_id
res, err, errno, sqlstate = db:query(update_sql)
if not res then
ngx.say("update error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
ngx.say("update rows : ", res.affected_rows, "")
local select_sql = "select id, ch from test"
res, err, errno, sqlstate = db:query(select_sql)
if not res then
ngx.say("select error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
for i, row in ipairs(res) do
for name, value in pairs(row) do
ngx.say("select row ", i, " : ", name, " = ", value, "")
end
end
ngx.say("")
local ch_param = ngx.req.get_uri_args()["ch"] or ''
local query_sql = "select id, ch from test where ch = " .. ngx.quote_sql_str(ch_param)
res, err, errno, sqlstate = db:query(query_sql)
if not res then
ngx.say("select error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
for i, row in ipairs(res) do
for name, value in pairs(row) do
ngx.say("select row ", i, " : ", name, " = ", value, "")
end
end
--[[ 为了便于看到效果,我把删除这段代码先注释了
local delete_sql = "delete from test"
res, err, errno, sqlstate = db:query(delete_sql)
if not res then
ngx.say("delete error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
return close_db(db)
end
ngx.say("delete rows : ", res.affected_rows, "")
--]]
close_db(db)
在nginx.conf中增加如下增配置并重载生效:
server {
listen 8081;
location /lua_mysql {
default_type 'text/html';
lua_code_cache on;
content_by_lua_file /usr/local/openresty/nginx/lua/test_mysql.lua;
}
}
通过URL访问后,可以发现数据库中已正常建表和插入了数据。
而且在web前台也会返回如下信息:
insert rows : 1 , id : 2
update rows : 1
select row 1 : ch = hello
select row 1 : id = 1
select row 2 : ch = hello2
select row 2 : id = 2
不过这里并未涉及到传参,如果需要传参,可以使用ngx.req.get_uri_args、ngx.req.get_post_args方法。方法的使用,可以参看:https://github.com/openresty/lua-nginx-module#nginx-api-for-lua ,注意,无论是get还是post,一定要使用set_unescape_uri、set_quote_sql_str 进行处理。避免非法参数传给数据库。而且现网实现API时,建议主要以select 读为主,而且尽量调用都在内部实现(能过访问控制,禁止外网访问)。
三、小结
Openresty本身是和tengine项目差不多同时代的产品,而且作者本人也是当年参与tengine项目的主要人员。Openresty目前在很多大厂都有应用,这自不必细说。关于更多功能,在《OpenResty最佳实践》一书和官方的readme文档都比较详尽。而且对于官方未实现的一些功能,也很方便的通过lua代码实现,lua代码也花了一点时间看了下,其操作起来并不难。具体应用还是看自身的应用场景,如果公司并没有API这类需求,也不需要实现很复杂的URL处理逻辑,nginx和tengine就已经OK了。