OpenResty连接mysql

一、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_uriset_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访问后,可以发现数据库中已正常建表和插入了数据。

openresty-mysql-lua.png

而且在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_uriset_quote_sql_str 进行处理。避免非法参数传给数据库。而且现网实现API时,建议主要以select 读为主,而且尽量调用都在内部实现(能过访问控制,禁止外网访问)。

三、小结

Openresty本身是和tengine项目差不多同时代的产品,而且作者本人也是当年参与tengine项目的主要人员。Openresty目前在很多大厂都有应用,这自不必细说。关于更多功能,在《OpenResty最佳实践》一书和官方的readme文档都比较详尽。而且对于官方未实现的一些功能,也很方便的通过lua代码实现,lua代码也花了一点时间看了下,其操作起来并不难。具体应用还是看自身的应用场景,如果公司并没有API这类需求,也不需要实现很复杂的URL处理逻辑,nginx和tengine就已经OK了。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注