在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
OpenResty 官网:http://openresty.org/ OpenResty 是一个nginx和它的各种三方模块的一个打包而成的软件平台。最重要的一点是它将lua/luajit打包了进来,使得我们可以使用lua脚本来进行web的开发。有了lua,我们可以借助于nginx的异步非阻塞的功能,达到使用 lua 异步并发访问后端的 MySQL, PostgreSQL, Memcached, Redis等等服务。特别是特有的 ngx.location.capture_multi 功能让人印象深刻,其可以达到极大的减少浏览器的http连接数量,并且可以异步并发的访问后台 Java/PHP/Python 等等接口。OpenResty 架构的web可以轻松超越Node.js的性能,并且对后端语言没有限制,你可以使用Java/PHP/Python等等各种语言。OpenResty(nginx+lua)可以替代node.js的前端渲染的功能。 OpenResty (aka. ngx_openresty) is a full-fledged web application server by bundling the standard Nginx core, lots of 3rd-party Nginx modules, as well as most of their external dependencies. 1. 安装OpenResty 先安装依赖:yum install readline-devel pcre-devel openssl-devel gcc 解压: tar zxvf ngx_openresty-1.9.3.1.tar.gz 建立一个软连接:ln -s ngx_openresty-1.9.3.1 openresty 进入目录:cd openresty 编译: ./configure \ --with-cc-opt="-I/usr/local/include" \ --with-ld-opt="-L/usr/local/lib" \ --prefix=/opt/openresty 其中 --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. 3. ngx_lua 的指令 和 API 上面我们说到 nginx 嵌入 lua 脚本可以使用 content_by_lua 和 content_by_lua_file,它们其实是指令(Directives),类似的指令还有很多, 具体参见:https://www.nginx.com/resources/wiki/modules/lua/#directives 这些指令都是 nginx 访问 lua 脚本的入口。 ngx_lua API: 指令是 nginx 访问 lua 脚本的入口。那么lua脚本如何调用nginx中的函数呢?就是通过 ngx_lua 的API 。 具体介绍参见:https://www.nginx.com/resources/wiki/modules/lua/#nginx-api-for-lua The various The API is exposed to Lua in the form of two standard packages 其实nginx和Lua的交互开发主要就是指令和API,当然还有lua脚本的语法。指令是nginx访问lua的入口,API是lua调用nginx的函数,lua是脚本编程语言。 指令其实很简单,所以主要就是熟悉ngx_lua的 API 和Lua语法。 4. lua 访问 redis lua-resty-redis 模块:https://github.com/openresty/lua-resty-redis (有文档可以参考) 在nginx.conf中加入: 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]# cat cache_lock.lua local redis = require "resty.redis" local red = redis:new() local resty_lock = require "resty.lock" local ngx_cache = ngx.shared.ngx_cache function set_to_cache(key, value, exptime) if not exptime then exptime = 0 end 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 -- cache miss local lock = resty_lock:new("cache_lock") local elapsed, err = lock:lock(key) if not elapsed then return fail("failed to acquire the lock: ", err) end value = get_from_redis(key) if not value then local ok, err = lock:unlock() if not ok then return fail("failed to unlock: ", err) end ngx.say("no value found") return end local ok, err = ngx_cache:set(key, value, 1) if not ok then local ok, err = lock:unlock() if not ok then return fail("failed to unlock: ", err) end return faile("failed to update ngx_cache: ", err) end local ok, err = lock:unlock() if not ok then return faile("failed to unlock: ", err) end 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 |
请发表评论