Nginx+lua=手术刀

目录

之前听说过@agentzh出品的openresty集成了很多有用的第三方module,特别是集成了lua相关的一系列东西,非常好用,这次终于有机会来尝试了。使用之后总体感觉就是一个字,“爽”,有一种甩开java的畅快感。

首先为了省事,没有安装nginx官方版本再自己一个个的去安装module,安装的是openresty最新的稳定版。我们使用的到的模块,基本包括了ngx_luangx_redis2、cjson、rds这些模块。另外为了从nginx直接访问数据库,使用了drizzle模块。

操作的流程概括起来讲,就是一方面通过ngx_lua模块抽取http post数据,进行一些简单的分析判断,将数据插入redis;另外一方面,当需要返回数据给客户端的时候,会调用redis2模块从多个redis DB中获取数据并拼接,最后转为json格式的字符串。

下面展示几个lua的code点:

  1. 使用content_by_lua_file调用lua脚本文件来实现逻辑,比如

     location ~ ^/lua1 {
        default_type 'text/plain';
        set $latesttime  $arg_latesttime;
        set $lasttime  $arg_lasttime;
        set $limit $arg_limit;
        set $offset $arg_offset;
        content_by_lua_file conf/timeline.lua;
     }
    

而timeline.lua文件里面就可以放具体的业务逻辑了

  1. 使用content_by_lua,直接把脚本写在nginx配置文件中,比如

     location ~ ^/luatest$ {
       default_type 'text/plain';
       set $latesttime  $arg_latesttime;
       set $lasttime  $arg_lasttime;
       set $limit $arg_limit;
       set $offset $arg_offset;
       content_by_lua '
           local parser = require("redis.parser")
           local minscore
           if #ngx.var.lasttime == 0 then
               minscore = "-inf"
           else
               minscore = ngx.var.lasttime
           end
           local maxscore
           if #ngx.var.latesttime == 0 then
               maxscore = "+inf"
           else
               maxscore = ngx.var.latesttime
           end
           local subQuery = "ZRANGEBYSCORE luatest_zset " .. minscore .. " " .. maxscore .. " withscores limit " .. ngx.var.offset .. " " .. ngx.var.limit .. "\\r\\n"
           local res = ngx.location.capture("/redis",{args={query=subQuery}});
           if res.status == 200 then
             reply = parser.parse_reply(res.body)
             local data = {}
             data["data"] = reply
             local cjson = require("cjson")
             ngx.say(cjson.encode(data))
             return
           else
             ngx.exit(500)
           end
       ';
     }
     location  /redis {
         internal;
         set_unescape_uri $query $arg_query;
         redis2_raw_query $query;
         redis2_pass redis-pool;
     }
    

这一大段的code中,主要有下面几个点:

  1. 使用set_misc中的set_unescape_uri来对传入location中的参数进行unescape,nginx会对传入的url参数进行escape,不管它是来自于客户端直接请求,还是在内部通过capture形式调用
  2. internal保证不能通过外部访问,确保安全性
  3. redis这个location专门接受内部调用,用来直接发起对redis的请求,redis-pool是配置在upstream中的对redis的连接池,使用redis2_raw_query可以保证这种灵活性。但是需要注意的是,使用redis2_raw_query的时候需要在你字符串的最后加入换行“\r\n”,否则语句不会被执行
  4. 在content_by_lua中,获取request的参数,做一些简单的判断,拼接成redis query串,然后使用ngx.location.capture发起subrequest,并使用lua对返回的数据进行处理,转换为json格式的字符串,使用ngx.say()返回给client。注意此处ngx.say()会在输出末尾自动加上换行,如果想返回一个纯字符串,可以使用ngx.print()

第一天code完了之后快下班的时候,才注意到原来开了eclipse一整天一分钟都没用过,而原来用java可能需要开发2、3天还得小心测试的code一天已经搞定并测试过了。并且nginx+lua的组合在淘宝已经经过了实战的检验,被证明是可以承受住相当的流量的。那么这样一个开发、调试迅速,系统消耗还低的组合,你是不是值得拥有?