./configure \ --with-cc-opt="-I/usr/local/include" \ --with-ld-opt="-L/usr/local/lib" \ --prefix=/opt/openresty ... ... Configuration summary + using system PCRE library + using system OpenSSL library + md5: using OpenSSL library + sha1: using OpenSSL library + using system zlib library nginx path prefix: "/opt/openresty/nginx" nginx binary file: "/opt/openresty/nginx/sbin/nginx" nginx configuration prefix: "/opt/openresty/nginx/conf" nginx configuration file: "/opt/openresty/nginx/conf/nginx.conf" nginx pid file: "/opt/openresty/nginx/logs/nginx.pid" nginx error log file: "/opt/openresty/nginx/logs/error.log" nginx http access log file: "/opt/openresty/nginx/logs/access.log" nginx http client request body temporary files: "client_body_temp" nginx http proxy temporary files: "proxy_temp" nginx http fastcgi temporary files: "fastcgi_temp" nginx http uwsgi temporary files: "uwsgi_temp" nginx http scgi temporary files: "scgi_temp"
其中 --prefix=/opt/openresty 指定了安裝目錄,不指定的話默認會安裝到 /usr/local/openresty 目錄下。 編譯安裝: make && make install
[root@localhost src]# cd /opt/openresty/ [root@localhost openresty]# ls bin luajit lualib nginx
可以看到 /opt/openresty 目錄下四個文件夾,其中包括了 luajit,nginx。 啟動openresty: /opt/openresty/nginx/sbin/nginx -c /opt/openresty/nginx/conf/nginx.conf -p /opt/openresty/nginx/
[root@localhost src]# ps -elf|grep nginx 1 S root 2076 1 0 80 0 - 34999 - 21:24 ? 00:00:00 nginx: master process /opt/openresty/nginx/sbin/nginx -c /opt/openresty/nginx/conf/nginx.conf -p /opt/openresty/nginx/ 5 S nobody 2077 2076 0 80 0 - 35045 - 21:24 ? 00:00:00 nginx: worker process 0 S root 2079 1678 0 80 0 - 1088 - 21:24 pts/1 00:00:00 grep nginx
驗證可以訪問: curl 127.0.0.1 2. content_by_lua 和 content_by_lua_file nginx 如何嵌入 lua 腳本。方法就是在nginx的配置文件nginx.conf 中使用 content_by_lua 或者 cotent_by_lua_file 指令: 1) content_by_lua 一般在很簡單的lua腳本時使用:
location /lua { set $test "hello, world."; content_by_lua ' ngx.header.content_type = "text/plain"; ngx.say(ngx.var.test); '; }
訪問 http://localhost/lua 可以看到輸出到頁面的 hello, world. 2)cotent_by_lua_file 適應於復雜的 lua 腳本,專門放入一個文件中:
location /lua2 { #lua_code_cache off; content_by_lua_file lua/hello.lua; }
路徑相對於 /opt/openresty/nginx
[root@localhost lua]# pwd /opt/openresty/nginx/lua [root@localhost lua]# cat hello.lua ngx.say('hello ngx_lua!!!!');
本例子中 hello.lua 只包含一句: ngx.say('hello ngx_lua!!!!'); 訪問 /lua2 : [root@localhost lua]# curl localhost/lua hello ngx_lua!!!! 可以看到訪問成功。 在 nginx.conf 文件的 server {.. ...} 中加入 lua_code_cache off; 可以方便調試lua腳本,修改lua腳本之後,不需要 reload nginx. openresty 中的 nginx 嵌入 luajit 的原理: 每一個nginx的進程中都嵌入了一個 luajit的虛擬機,來執行lua腳本。nginx將lua腳本的執行交給了luajit vm.
location /redis_test{ content_by_lua_file lua/redis_test.lua; }
redis_test.lua 內容:
[root@localhost lua]# cat redis_test.lua local redis = require "resty.redis" local red = redis:new() red:set_timeout(1000) local ok, err = red:connect("127.0.0.1", 6379) if not ok then ngx.say("failed to connect: ", err) return end ngx.say("set result: ", ok) local res, err = red:get("dog") if not res then ngx.say("failed to get doy: ", err) return end if res == ngx.null then ngx.say("dog not found.") return end ngx.say("dog: ", res) [root@localhost lua]# 訪問: [root@localhost lua]# curl localhost/redis_test set result: 1 dog: an animal [root@localhost lua]#
我們看到訪問成功。 5. lua 訪問mysql openresty的mysql模塊:lua-resty-mysql :https://github.com/openresty/lua-resty-mysql(有文檔可以參考) 在nginx.conf加入如下配置:
location /mysql_test { content_by_lua_file lua/mysql_test.lua; }
mysql_test.lua腳本內容:
[root@localhost lua]# pwd /opt/openresty/nginx/lua [root@localhost lua]# cat mysql_test.lua local mysql = require "resty.mysql" local db, err = mysql:new() if not db then ngx.say("failed to instantiate mysql: ", err) return end db:set_timeout(1000) local ok, err, errno, sqlstate = db:connect{ host = "127.0.0.1", port = 3306, database = "ngx_lua", user = "root", password="digdeep", max_packet_size = 1024 * 1024 } if not ok then ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) return end ngx.say("connected to mysql.") local res, err, errno, sqlstate = db:query("drop table if exists cats") if not res then ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") return end res, err, errno, sqlstate = db:query("create table cats " .. "(id int not null primary key auto_increment, " .. "name varchar(30))") if not res then ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") return end ngx.say("table cats created.") res, err, errno, sqlstate = db:query("insert into cats(name) " .. "values (\'Bob\'),(\'\'),(null)") if not res then ngx.say("bad request: ", err, ": ", errno, ": ", sqlstate, ".") return end ngx.say(res.affected_rows, " rows inserted into table cats ", "(last insert id: ", res.insert_id, ")") res, err, errno, sqlstate = db:query("select * from cats order by id asc", 10) if not res then ngx.say("bad result ", err, ": ", errno, ": ", sqlstate, ".") return end local cjson = require "cjson" ngx.say("result: ", cjson.encode(res)) local ok, err = db:set_keepalive(1000, 100) if not ok then ngx.say("failed to set keepalive: ", err) return end
測試:
[root@localhost lua]# curl localhost/mysql_test connected to mysql. table cats created. 3 rows inserted into table cats (last insert id: 1) result: [{"name":"Bob","id":1},{"name":"","id":2},{"name":null,"id":3}]
測試通過。 5. lua 的 capture 和 capture_multi(子查詢) capture_multi 是 openresty 一個十分強大的功能。它能極大的減少前端浏覽器發送的http請求的數量,突破了浏覽器對於同一個服務器並發請求數量的限制,因為他可以將前端的多個http請求減少為只要一個http請求到nginx,然後nginx使用capture_multi特性,對後端發起多個異步並發請求,然後統一將結果返回給前端。下面看一個例子: 首先在nginx.conf中加入下面的 location 配置,並且配置好 nginx 訪問 php 的配置:
location /capture { content_by_lua_file lua/capture.lua; #access_by_lua_file lua/capture.lua; } location ~ \.php$ { root html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }
capture.lua 的代碼如下:
[root@localhost lua]# pwd /opt/openresty/nginx/lua [root@localhost lua]# cat capture.lua local res1,res2,res3,res4 = ngx.location.capture_multi{ {"/mysql_test", {args="t=1&id=1"}}, {"/redis_test", {args="t=2&id=2"}}, {"/lua", {args="t=3&id=3"}}, {"/index.php", {args="t=3&id=3"}}, } ngx.header.content_type="text/plain" ngx.say(res1.body) ngx.say(res2.body) ngx.say(res3.body) ngx.say(res4.truncated) ngx.say(res4.status) ngx.say(res4.header["Set-Cookie"]) --ngx.say(res4.body)
index.php 代碼:
[root@localhost html]# pwd /opt/openresty/nginx/html [root@localhost html]# cat index.php <?php echo phpinfo(); ?>
訪問:
[root@localhost html]# curl localhost/capture connected to mysql. table cats created. 3 rows inserted into table cats (last insert id: 1) result: [{"name":"Bob","id":1},{"name":"","id":2},{"name":null,"id":3}] set result: 1 dog: an animal hello ngx_lua!!!! false 200 nil
可以看到訪問成功了。/mysql_test,/redis_test, /lua, /index.php 四個請求的結果都輸出了。 注意: ngx.location.capture_multi{... ...} 中的多個異步並發請求可以是 nginx.conf 中配置的 location(比如 /mysql_test, /redis_test, /lua),也可以不是 location配置的路徑,比如 index.php 就不是。index.php 就是一個簡單的後台php 腳本。當然也可以是一個 java 實現的後台接口。 6. openresty的緩存 lua_shared_dict 定義一個緩存: 在nginx的配置文件 nginx.conf 的 http 端下面加入指令: lua_shared_dict ngx_cache 128m; 就定義了一個 名稱為 ngx_cache 大小為128m的內存用於緩存,注意該緩存是所有nginx work process所共享的。 在lua腳本中訪問緩存:
local ngx_cache = ngx.shared.ngx_cache local value = ngx_cache:get(key) local succ, err, forcible = ngx_cache:set(key, value, exptime)
下面測試一下,首先在 nginx.conf的server端中加入:
location /cache { content_by_lua_file lua/cache.lua; }
然後編寫 cache.lua 腳本:
[root@localhost lua]# cat cache.lua local redis = require "resty.redis" local red = redis:new() function set_to_cache(key, value, exptime) if not exptime then exptime = 0 end local ngx_cache = ngx.shared.ngx_cache local succ, err, forcible = ngx_cache:set(key, value, exptime) return succ end function get_from_cache(key) local ngx_cache = ngx.shared.ngx_cache; local value = ngx_cache:get(key) if not value then value = get_from_redis(key) set_to_cache(key, value) return value end ngx.say("get from cache.") return value end function get_from_redis(key) red:set_timeout(1000) local ok, err = red:connect("127.0.0.1", 6379) if not ok then ngx.say("failed to connect: ", err) return end local res, err = red:get(key) if not res then ngx.say("failed to get doy: ", err) return ngx.null end ngx.say("get from redis.") return res end function set_to_redis(key, value) red:set_timeout(1000) local ok, err = red:connect("127.0.0.1", 6379) if not ok then ngx.say("failed to connect: ", err) return end local ok, err = red:set(key, value) if not ok then ngx.say("failed to set to redis: ", err) return end return ok end set_to_redis('dog', "Bob") local rs = get_from_cache('dog') ngx.say(rs)
測試:
[root@localhost ~]# curl localhost/cache get from redis. Bob [root@localhost ~]# curl localhost/cache get from cache. Bob [root@localhost ~]# curl localhost/cache get from cache. Bob
第一次從 redis中獲取,以後每次都從cache中獲取。 可以使用 ab 測試一下rps(Requests per second): ab -n 1000 -c 100 -k http://127.0.0.1/cache 7. 解決緩存失效風暴 lua-resty-lock 緩存失效風暴是指緩存因為時間過期而失效時,會導致所有的請求都去訪問 後台的redis或者mysql,而導致CPU性能即刻增長的現象。所以關鍵是當緩存失效時,用lock保證只有一個線程去訪問後台的redis或者mysql,然後更新緩存。需要使用到 lua-resty-lock 模塊的加鎖、解鎖功能。 lua-resty-lock 文檔:https://github.com/openresty/lua-resty-lock 首先在nginx.conf 的 http 端下面加入指令: lua_shared_dict ngx_cache 128m; # cache lua_shared_dict cache_lock 100k; # lock for cache 然後在nginx.conf的server端中加入:
location /cache_lock { content_by_lua_file lua/cache_lock.lua; }
cache_lock.lua代碼: 測試:
[root@localhost lua]# curl localhost/cache_lock get from cache. Bob [root@localhost lua]# curl localhost/cache_lock get from cache. Bob
7. openresty 執行階段 nginx的執行階段分成了很多個階段,所以第三方模塊就可以在某個適當的階段加入一些處理。openresty進行了簡化成了7個階段: