一、Openresty数据库请求理念

Openresty这个nginx变种产品之前早有耳闻,直到最近花了点时间读了下《OpenResty最佳实践》(OpenResty-Best-Practices)一书。其实Openresty引入lua实现的一个比较强的一个功能就是和后端mysql、redis 等数据库的连接更直接。减少了中间其他动态语言再去处理的过程,可以通过nginx直接返回或修改数据,也便于实现API。

1常规请求步骤:
2nginx ---> php / tomcat ---> mysql
3openresty请求步骤:
4openresty ---> mysql

上面只是简单描述了下单向请求,同样数据返回也一样。而动态语言在通过页面请求时,其处理的过程相对于nginx这种非阻塞模式是很慢的。

懒得画图了,这里盗用JD架构里的一张图。可以看到nginx+lua取标签类数据时,直接从redis去取,在redis缓存中不存在的,才会回源到tomcat再去取,tomcat从后端数据库查询到相关数据库再更新到redis里去。便于下次查询命中。

二、Openresty连接mysql

Openresty和mysql连接有两种方法:一种是使用其早期加入的Drizzle 模块(c语言实现),另一种是使用lua mysql模块。

1、Drizzle 模块

先看下Drizzle模块,官方给出的操作示例如下:

 1http {
 2    upstream backend {
 3        drizzle_server 127.0.0.1:3306 protocol=mysql dbname=ngx_test user=ngx_test password=ngx_test;
 4        drizzle_keepalive max=10 overflow=ignore mode=single;
 5    }
 6    server {
 7        listen 8080;
 8        location @cats-by-name {
 9            set_unescape_uri $name $arg_name;
10            set_quote_sql_str $name;
11            drizzle_query 'select * from cats where name=$name';
12            drizzle_pass backend;
13            rds_json on;
14        }
15        location @cats-by-id {
16            set_quote_sql_str $id $arg_id;
17            drizzle_query 'select * from cats where id=$id';
18            drizzle_pass backend;
19            rds_json on;
20        }
21        location = /cats {
22            access_by_lua '
23                if ngx.var.arg_name then
24                    return ngx.exec("@cats-by-name")
25                end
26                if ngx.var.arg_id then
27                    return ngx.exec("@cats-by-id")
28                end
29            ';
30            rds_json_ret 400 "expecting \"name\" or \"id\" query arguments";
31        }
32    }
33}

可以看出其调用非常简单,不过其默认在安装时并未集成该模块“ 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代码,如下:

 1[root@localhost ~]# cat /usr/local/openresty/nginx/lua/test_mysql.lua
 2local function close_db(db)
 3    if not db then
 4        return
 5    end
 6    db:close()
 7end
 8local mysql = require("resty.mysql")
 9local db, err = mysql:new()
10if not db then
11    ngx.say("new mysql error : ", err)
12    return
13end
14db:set_timeout(1000)
15local props = {
16    host = "127.0.0.1",
17    port = 3306,
18    database = "test",
19    user = "root",
20    password = "asdf"
21}
22local res, err, errno, sqlstate = db:connect(props)
23if not res then
24   ngx.say("connect to mysql error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
25   return close_db(db)
26end
27local drop_table_sql = "drop table if exists test"
28res, err, errno, sqlstate = db:query(drop_table_sql)
29if not res then
30   ngx.say("drop table error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
31   return close_db(db)
32end
33local create_table_sql = "create table test(id int primary key auto_increment, ch varchar(100))"
34res, err, errno, sqlstate = db:query(create_table_sql)
35if not res then
36   ngx.say("create table error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
37   return close_db(db)
38end
39local insert_sql = "insert into test (ch) values('hello')"
40res, err, errno, sqlstate = db:query(insert_sql)
41if not res then
42   ngx.say("insert error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
43   return close_db(db)
44end
45res, err, errno, sqlstate = db:query(insert_sql)
46ngx.say("insert rows : ", res.affected_rows, " , id : ", res.insert_id, "")
47local update_sql = "update test set ch = 'hello2' where id =" .. res.insert_id
48res, err, errno, sqlstate = db:query(update_sql)
49if not res then
50   ngx.say("update error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
51   return close_db(db)
52end
53ngx.say("update rows : ", res.affected_rows, "")
54local select_sql = "select id, ch from test"
55res, err, errno, sqlstate = db:query(select_sql)
56if not res then
57   ngx.say("select error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
58   return close_db(db)
59end
60for i, row in ipairs(res) do
61   for name, value in pairs(row) do
62     ngx.say("select row ", i, " : ", name, " = ", value, "")
63   end
64end
65ngx.say("")
66local ch_param = ngx.req.get_uri_args()["ch"] or ''
67local query_sql = "select id, ch from test where ch = " .. ngx.quote_sql_str(ch_param)
68res, err, errno, sqlstate = db:query(query_sql)
69if not res then
70   ngx.say("select error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
71   return close_db(db)
72end
73for i, row in ipairs(res) do
74   for name, value in pairs(row) do
75     ngx.say("select row ", i, " : ", name, " = ", value, "")
76   end
77end
78--[[  为了便于看到效果,我把删除这段代码先注释了
79local delete_sql = "delete from test"
80res, err, errno, sqlstate = db:query(delete_sql)
81if not res then
82   ngx.say("delete error : ", err, " , errno : ", errno, " , sqlstate : ", sqlstate)
83   return close_db(db)
84end
85ngx.say("delete rows : ", res.affected_rows, "")
86--]]
87close_db(db)  

在nginx.conf中增加如下增配置并重载生效:

1server {
2listen 8081;
3location /lua_mysql {
4   default_type 'text/html';
5   lua_code_cache on;
6   content_by_lua_file /usr/local/openresty/nginx/lua/test_mysql.lua;
7 }
8}

通过URL访问后,可以发现数据库中已正常建表和插入了数据。

openresty-mysql-lua.png
openresty-mysql-lua.png

而且在web前台也会返回如下信息:

1insert rows : 1 , id : 2
2update rows : 1
3select row 1 : ch = hello
4select row 1 : id = 1
5select row 2 : ch = hello2
6select 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了。