OpenResty
OpenResty
简介与安装
1. 简介
OpenResty是一个基于Nginx与Lua的高性能web平台,其内部继承了大量精良的Lua库,第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发,扩展性极高的动态的web应用,可以方便的基于nginx进行二次开发。
Nginx是一个轻量级的,高性能的HTTP和反向代理服务器,而OpenResty是基于Nginx的集成软件平台,增强了Nginx的功能,特别是通过嵌入Lua脚本来支持灵活的web应用开发
让你的web服务直接跑在Nginx服务内部,充分利用Nginx的
非阻塞I/O模型
,不仅仅对HTTP客户端请求,甚至于对远程后端诸如MySQL,PostgreSQL以及Redis等都进行一些列的高性能响应。
OpenResty的主要作用和功能包括:
- web服务器:OpenResty 作为一个高性能的 Web 服务器,可以处理大量的并发请求,提供高效的静态文件服务和动态内容生成。
- 反向代理:OpenResty可以作为反向代理服务器,用于将请求转发到后端的应用服务器,并且可以进行负载均衡,缓存,SSL终止等操作。
- 动态内容处理:OpenResty提供了强大的lua编程能力,可以用Lua编写脚本来处理请求,实现动态内容生成,访问控制,日志记录等功能。
- 扩展性:OpenResty 的模块化架构使得它可以轻松地集成各种第三方模块,扩展了 NGINX 的功能,例如集成了各种缓存模块、安全模块、访问控制模块等。
- 高性能代理:OpenResty 可以作为高性能代理服务器,用于构建 CDN、缓存代理等场景,提供高性能的数据传输和缓存服务。
Nginx和OpenResty的对比
- 架构与扩展性
- nginx:主要通过预编译的模块来扩展功能,虽然其模块化架构设计良好,但对于定制化需求要重新编译服务器
- OpenResty:通过嵌入Lua脚本可以动态扩展功能,无需重新编译服务器,开发者可以灵活滴调整和添加功能
- 性能与资源使用
- nginx:在处理静态内容和反向代理方面表现出色,资源消耗低,适合高并发的静态内容服务
- OpenResty: 在高并发处理能力上继承了Nginx的优势,同事LuaJT提供了高效的脚本执行能力,适合动态内容处理和复杂逻辑的实现
- 开发灵活性
- nginx配置文件采用纯文本格式,功能定义相对简单,适合标准化和固定需求的场景
- OpenResty:通过Lua脚本可以实现复杂的业务逻辑,适合需要灵活处理和快速迭代的开发场景
2. Linux安装
-
安装OpenResty的依赖库
yum install -y pcre-devel openssl-devel gcc --skip-broken
-
安装OpenResty仓库
yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
-
如果提示说命令不存在,则运行
yum install -y yum-utils
-
然后重复上面的命令
-
-
安装OpenResty
yum install -y openresty
-
安装opm工具
yum install -y openresty-opm
-
目录结构
默认情况下,OpenResty安装的目录是:
/usr/local/openresty
OpenResty就是在Nginx基础上继承了一些lua模块。-
配置Nginx环境变量
-
打开配置文件
vi /etc/profile
-
在最下面加入两行
export NGINX_HOME=/usr/local/openresty/nginx
export PATH=${NGINX_HOME}/sbin:$PATH
-
NGINX_HOME:后面是OpenResty安装目录下的Nginx的目录
-
然后让配置生效
source /etc/profile
-
-
OpenResty启动
# 启动nginx
nginx
# 重新加载配置
nginx -s reload
# 停止
nginx -s stop
-
nginx的默认配置文件注释太多,影响后续我们的编辑,这里将nginx.conf中的注释部分删除,保留有效部分。
修改
/usr/local/openresty/nginx/conf/nginx.conf
文件,内容如下:#user nobody; worker_processes 1; error_log logs/error.log; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 8081; server_name localhost; location / { root html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } } -
在Linux的控制台输入命令以启动nginx:
nginx
3. Windows安装
-
解压
-
双击nginx.exe 或者执行 start nginx启动nginx
-
验证是否成功
tasklist /fi "imagename eq nginx.exe"
其中一个是 master 进程,另一个是 worker 进程
另外当 nginx 成功启动后,master 进程的 pid 存放在
logs\nginx.pid
文件中。 -
HelloWorld
为了工作目录与安装目录互不干扰,并顺便学下简单的配置文件编写,就另外创建一个OpenResty的工作目录来练习,并且另外写一个配置文件。
-
创建OpenResty-test目录,并在该目录下创建logs和conf子目录分别用于存放日志和配置文件
$ mkdir ~/openresty-test ~/openresty-test/logs/ ~/openresty-test/conf/ $ $ tree ~/openresty-test /Users/yuansheng/openresty-test ├── conf └── logs 2 directories, 0 files -
创建配置文件
在 conf 目录下创建一个文本文件作为配置文件,命名为 nginx.conf,文件内容如下:
worker_processes 1; #nginx worker 数量 error_log logs/error.log; #指定错误日志文件路径 events { worker_connections 1024; } http { server { #监听端口,若你的6699端口已经被占用,则需要修改 listen 6699; location / { default_type text/html; content_by_lua_block { ngx.say("HelloWorld") } } } } 提示:如果你安装的是 openresty 1.9.3.1 及以下版本,请使用
content_by_lua
命令代替示例中的content_by_lua_block
。可使用nginx -V
命令查看版本号。--- 启动nginx ➜ ~ nginx -p ~/openresty-test --- 查看nginx进程 ➜ ~ ps -ef | grep nginx 501 88620 1 0 10:58AM ?? 0:00.00 nginx: master process nginx -p /Users/yuansheng/openresty-test 501 88622 88620 0 10:58AM ?? 0:00.00 nginx: worker process --- 访问nginx ➜ ~ curl http://localhost:6699 -i HTTP/1.1 200 OK Server: openresty/1.9.7.3 Date: Sun, 20 Mar 2016 03:01:35 GMT Content-Type: text/html Transfer-Encoding: chunked Connection: keep-alive HelloWorld
与其他Location配合
利用不同的Location的功能组合,可以完成内部调用,流水线方式跳转,外部重定向等几大不同方式,
内部调用
例如对数据库,内部公共函数的统一接口,可以把他们放到统一的Location中,通常情况下,为了保护这些内部接口,都会把这些接口设置为internal。这么做的好处就是可以让这个内部接口相互独立,不受外界干扰。
示例代码
location = /sum { # 只允许内部调用 internal; # 这里做了一个求和运算只是一个例子,可以在这里完成一些数据库、 # 缓存服务器的操作,达到基础模块和业务逻辑分离目的 content_by_lua_block { local args = ngx.req.get_uri_args() ngx.say(tonumber(args.a) + tonumber(args.b)) } } location = /app/test { content_by_lua_block { local res = ngx.location.capture( "/sum", {args={a=3, b=8}} ) ngx.say("status:", res.status, " response:", res.body) } }
稍微扩充一下, 并行请求效果,示例如下:
location = /sum { internal; content_by_lua_block { ngx.sleep(0.1) local args = ngx.req.get_uri_args() ngx.print(tonumber(args.a) + tonumber(args.b)) } } location = /subduction { internal; content_by_lua_block { ngx.sleep(0.1) local args = ngx.req.get_uri_args() ngx.print(tonumber(args.a) - tonumber(args.b)) } } location = /app/test_parallels { content_by_lua_block { local start_time = ngx.now() local res1, res2 = ngx.location.capture_multi( { {"/sum", {args={a=3, b=8}}}, {"/subduction", {args={a=3, b=8}}} }) ngx.say("status:", res1.status, " response:", res1.body) ngx.say("status:", res2.status, " response:", res2.body) ngx.say("time used:", ngx.now() - start_time) } } -- ngx.location.capture_multi 方法允许在同一个请求中并发地发起多个子请求 location = /app/test_queue { content_by_lua_block { local start_time = ngx.now() local res1 = ngx.location.capture_multi( { {"/sum", {args={a=3, b=8}}} }) local res2 = ngx.location.capture_multi( { {"/subduction", {args={a=3, b=8}}} }) ngx.say("status:", res1.status, " response:", res1.body) ngx.say("status:", res2.status, " response:", res2.body) ngx.say("time used:", ngx.now() - start_time) } }
➜ ~ curl 127.0.0.1/app/test_parallels status:200 response:11 status:200 response:-5 time used:0.10099983215332 ➜ ~ curl 127.0.0.1/app/test_queue status:200 response:11 status:200 response:-5 time used:0.20199990272522
利用 nginx.location.capture_multi 函数,直接完成了两个子请求并执行。当两个请求没有相互依赖,这种方法可以极大提高查询效率。
该方法,可以被广泛应用于广告系统(1:N模型,一个请求,后端从N家供应商中获取条件最优的广告)、高并发前段页面展示(并行无依赖界面,降级开关等)。
流水线方式跳转
各种不同的API,下载请求混杂在一起,要求厂商对下载的动态调整有各种不同的定制策略,而这些策略在一天的不同时间段,规则可能还不一样。这个时候还可以效仿工厂的流水线模式,逐层过滤,处理。
示例代码
location ~ ^/static/([-_a-zA-Z0-9/]+).jpg { set $image_name $1; content_by_lua_block { ngx.exec("/download_internal/images/" .. ngx.var.image_name .. ".jpg"); }; } location /download_internal { internal; # 这里还可以有其他统一的 download 下载设置,例如限速等 alias ../download; }
注意,ngx.exec 方法与 ngx.redirect 是完全不同的,前者是个纯粹的内部跳转并且没有引入任何额外 HTTP 信号。 这里的两个 location 更像是流水线上工人之间的协作关系。第一环节的工人对完成自己处理部分后,直接交给第二环节处理人(实际上可以有更多环节),它们之间的数据流是定向的。
外部重定向
百度的首页已经不再是 HTTP 协议,它已经全面修改到了 HTTPS 协议上。但是对于大家的输入习惯,估计还是在地址栏里面输入 baidu.com
,回车后发现它会自动跳转到 https://www.baidu.com
,这时候就需要的外部重定向了。
location = /foo { content_by_lua_block { ngx.say([[I am foo]]) } } location = / { rewrite_by_lua_block { return ngx.redirect('/foo'); } }
测试结果如下
-- -i 表示输出响应头信息 ➜ ~ curl 127.0.0.1 -i HTTP/1.1 302 Moved Temporarily Server: openresty/1.9.3.2rc3 Date: Sun, 22 Nov 2015 11:04:03 GMT Content-Type: text/html Content-Length: 169 Connection: keep-alive Location: /foo <html> <head><title>302 Found</title></head> <body bgcolor="white"> <center><h1>302 Found</h1></center> <hr><center>openresty/1.9.3.2rc3</center> </body> </html> ➜ ~ curl 127.0.0.1/foo -i HTTP/1.1 200 OK Server: openresty/1.9.3.2rc3 Date: Sun, 22 Nov 2015 10:43:51 GMT Content-Type: text/html Transfer-Encoding: chunked Connection: keep-alive I am foo
外部重定向是可以跨域名的。例如从 A 网站跳转到 B 网站是绝对允许的。在 CDN 场景的大量下载应用中,一般分为调度、存储两个重要环节。调度就是通过根据请求方 IP 、下载文件等信息寻找最近、最快节点,应答跳转给请求方完成下载。
获取uri参数
获取请求uri参数
获取一个uri有两个方法:ngx.req.get_uri_args
、ngx.req.get_post_args
,二者主要的区别是参数来源有区别。前者来自uri参数,后者来自post请求内容。
示例
server { listen 80; server_name localhost; location /print_param { content_by_lua_block { local arg = ngx.req.get_uri_args() for k,v in pairs(arg) do ngx.say("[GET ] key:", k, " v:", v) end ngx.req.read_body() -- 解析 body 参数之前一定要先读取 body local arg = ngx.req.get_post_args() for k,v in pairs(arg) do ngx.say("[POST] key:", k, " v:", v) end } } }
输出结果:
➜ ~ curl '127.0.0.1/print_param?a=1&b=2%26' -d 'c=3&d=4%26' [GET ] key:b v:2& [GET ] key:a v:1 [POST] key:d v:4& [POST] key:c v:3
传递uri参数
ngx.location.capture 函数用于发起一个内部子请求,并且等待子请求的响应
location /test { content_by_lua_block { local res = ngx.location.capture( '/print_param', { method = ngx.HTTP_POST, args = ngx.encode_args({a = 1, b = '2&'}), body = ngx.encode_args({c = 3, d = '4&'}) } ) ngx.say(res.body) } }
输出响应结果
➜ ~ curl '127.0.0.1/test' [GET] key:b v:2& [GET] key:a v:1 [POST] key:d v:4& [POST] key:c v:3
如果这里不调用ngx.encode_args
,可能就会比较丑了,看下面例子:
local res = ngx.location.capture('/print_param', { method = ngx.HTTP_POST, args = 'a=1&b=2%26', -- 注意这里的 %26 ,代表的是 & 字符 body = 'c=3&d=4%26' } ) ngx.say(res.body)
PS:对于 ngx.location.capture 这里有个小技巧,args 参数可以接受字符串或Lua 表的,这样我们的代码就更加简洁直观。
local res = ngx.location.capture('/print_param', { method = ngx.HTTP_POST, args = {a = 1, b = '2&'}, body = 'c=3&d=4%26' } ) ngx.say(res.body)
获取请求body
在nginx的典型应用场景中,几乎都是只读取HTTP请求头即可,例如负载均衡,正反向代理等场景。但是对于API server 或者 web Application,对body可以说比较敏感了。
最简单的“hello *******”
我们先来构造最简单的一个请求,POST 一个名字给服务端,服务端应答一个 “Hello ********”。
http { server { listen 80; location /test { content_by_lua_block { local data = ngx.req.get_body_data() ngx.say("hello ", data) } } } }
测试结果:
➜ ~ curl 127.0.0.1/test -d jack hello nil
从结果中可以看出data部分获取为nil,原因是还需要添加指令 lua_need_request_body
http { server { listen 80; # 默认读取 body 设置全局行为 lua_need_request_body on; location /test { content_by_lua_block { local data = ngx.req.get_body_data() ngx.say("hello ", data) } } } }
再次测试,符合我们预期:
➜ ~ curl 127.0.0.1/test -d jack hello jack
如果读取body并非全局行为,也可以显示的调用ngx.req.read_body() 接口,参看下面示例:
http { server { listen 80; location /test { content_by_lua_block { ngx.req.read_body() local data = ngx.req.get_body_data() ngx.say("hello ", data) } } } }
输出响应体
HTTP响应报文分为三个部分:
- 响应行
- 响应头
- 响应体
对于HTTP响应体的输出,在OpenResty中调用 ngx.say
或 ngx.print
即可。
区别:
ngx.say
会对输出响应体多输出一个 \n
ngx.say 与 ngx.print 均为异步输出,也就是说当调用 ngx.say
后并不会立刻输出响应体。
server { listen 80; location /test { content_by_lua_block { ngx.say("hello") ngx.sleep(3) ngx.say("the world") } } location /test2 { content_by_lua_block { ngx.say("hello") ngx.flush() -- 显式的向客户端刷新响应输出 ngx.sleep(3) ngx.say("the world") } } }
测试接口可以观察到, /test
响应内容实在触发请求 3s 后一起接收到响应体,而 /test2
则是先收到一个 hello
停顿 3s 后又接收到后面的 the world
。
再看下面的例子:
server { listen 80; lua_code_cache off; location /test { content_by_lua_block { ngx.say(string.rep("hello", 1000)) ngx.sleep(3) ngx.say("the world") } } }
执行测试,可以发现首先收到了所有的 "hello" ,停顿大约 3 秒后,接着又收到了 "the world" 。
通过两个例子对比,可以知道,因为是异步输出,两个响应体的输出时机是 不一样 的。
处理响应体过大的输出
当响应体过大(例如超过2G),是不能直接调用API完成响应体输出的。响应体过大,分两种情况:
- 输出内容本身体积很大,例如超过2G的文件下载
- 输出内容本身是由各种碎片拼凑的,碎片数量庞大,例如应答数据是某地区所有人的姓名
第①个情况,要利用 HTTP 1.1 特性 CHUNKED 编码来完成
可以利用CHUNKED
格式,把一个大的响应体拆分成多个小的响应体,分批,有节制的响应给请求方
location /test { content_by_lua_block { -- ngx.var.limit_rate = 1024*1024 local file, err = io.open(ngx.config.prefix() .. "data.db","r") if not file then ngx.log(ngx.ERR, "open file error:", err) ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE) end local data while true do data = file:read(1024) if nil == data then break end ngx.print(data) ngx.flush(true) end file:close() } }
日志输出
标准日志输出
OpenResty的标准日志输入原句为 ngx.log(log_level, ...)
,几乎可以在任何ngx_lua阶段进行日志的输出
worker_processes 1; error_log logs/error.log error; # 日志级别 #pid logs/nginx.pid; events { worker_connections 1024; } http { server { listen 80; location / { content_by_lua_block { local num = 55 local str = "string" local obj ngx.log(ngx.ERR, "num:", num) ngx.log(ngx.INFO, " string:", str) print([[i am print]]) ngx.log(ngx.ERR, " object:", obj) } } } }
访问网页,生成日志(logs/error.log文件)结果如下:
2016/01/22 16:43:34 [error] 61610#0: *10 [lua] content_by_lua(nginx.conf:26):5: num:55, client: 127.0.0.1, server: , request: "GET /hello HTTP/1.1", host: "127.0.0.1" 2016/01/22 16:43:34 [error] 61610#0: *10 [lua] content_by_lua(nginx.conf:26):7: object:nil, client: 127.0.0.1, server: , request: "GET /hello HTTP/1.1", host: "127.0.0.1"
Nginx的日志级别:
ngx.STDERR -- 标准输出 ngx.EMERG -- 紧急报错 ngx.ALERT -- 报警 ngx.CRIT -- 严重,系统故障,触发运维告警系统 ngx.ERR -- 错误,业务不可恢复性错误 ngx.WARN -- 告警,业务中可忽略错误 ngx.NOTICE -- 提醒,业务比较重要信息 ngx.INFO -- 信息,业务琐碎日志信息,包含不同情况判断等 ngx.DEBUG -- 调试
网络日志输出
如果你的日志需要归集,并且对时效性要求比较高那么这里要推荐的库可能就让你很喜欢了。 lua-resty-logger-socket ,可以说很好的解决了上面提及的几个特性。
lua-resty-logger-socket 的目标是替代 Nginx 标准的 ngx_http_log_module 以非阻塞 IO 方式推送 access log 到远程服务器上。对远程服务器的要求是支持 syslog-ng 的日志服务。
lua_package_path "/path/to/lua-resty-logger-socket/lib/?.lua;;"; server { location / { log_by_lua_block { local logger = require "resty.logger.socket" if not logger.initted() then local ok, err = logger.init{ host = 'xxx', port = 1234, flush_limit = 1234, drop_limit = 5678, } if not ok then ngx.log(ngx.ERR, "failed to initialize the logger: ", err) return end end -- construct the custom access log message in -- the Lua variable "msg" local bytes, err = logger.log(msg) if err then ngx.log(ngx.ERR, "failed to log message: ", err) return end } } }
例举几个好处:
- 基于cosocket非阻塞IO实现
- 日志累计到一定量,集体提交,增加网络传输利用率
- 短时间的网络抖动,自动容错
- 日志累计到一定量,如果没有传输完毕,直接丢弃
- 日志传输过程完全不落地,没有任何磁盘 IO 消耗
独立Lua源代码文件
上面的lua代码直接写在Nginx配置文件里面,当代码多了后不好维护,可以将Lua代码保存成独立文件
在OpenResty
目录下创建lua
目录,然后在lua
目录创建 hello.lua
文件:
ngx.say("<p>hello, world from lua src</p >")
调整nginx.conf
配置:
worker_processes 1; error_log logs/error.log; events { worker_connections 1024; } http { server { listen 9080; location ~ /lua/(.+) { default_type text/html; content_by_lua_file lua/$1.lua; } } }
其中, location ~ /lua/(.+)
用于动态匹配 url,content_by_lua_file lua/$1.lua;
用于指明 Lua 文件位置。
重启 Nginx ,即,kill
master 进程后执行:
/usr/local/openresty/nginx/sbin/nginx -p `pwd`/ -c conf/nginx.conf
打开浏览器,访问 http://10.88.115.137:9080/lua/hello
,可以看到页面输出 hello, world from lua src
。
获取请求参数
接下来实现一个稍复杂一点的例子,提取客户端请求 name
参数,并打印出来。
创建 lua/req.lua
文件,提供 get_args
函数,用于提取请求参数:
local _M = {} -- 获取 http get/post 请求参数 function _M.get_args() -- 获取 http 请求方式 get/post local request_method = ngx.var.request_method -- 这里是一个 table,包含所有 get 请求参数 local args = ngx.req.get_uri_args() -- 如果是 post 参数获取 if "POST" == request_method then -- 先读取请求体 ngx.req.read_body() -- 这里也是一个 table,包含所有 post 请求参数 local post_args = ngx.req.get_post_args() if post_args then for k, v in pairs(post_args) do args[k] = v end end end return args end return _M
创建 lua/test.lua
,通过调用 get_args
函数,提取 name
参数值:
-- 引入 req 模块 local req = require "req" -- 获取请求参数列表 local args = req.get_args() -- 获取 key 为 name 的值 local name = args['name'] -- 如果不存在指定默认值 if name == nil or name == "" then name = "leo" end -- 输出结果 ngx.say("<p>hello " .. name .. "!</p >")
修改 nginx.conf
:
worker_processes 1; error_log logs/error.log; events { worker_connections 1024; } http { lua_package_path "/home/lihao/code/openresty/lua/?.lua;;"; server { listen 9080; location ~ /lua/(.+) { default_type text/html; content_by_lua_file lua/$1.lua; } } }
其中,lua_package_path "/home/lihao/code/openresty/lua/?.lua;;";
用于指定引入模块的路径。
重启 Nginx,打开浏览器,输入 http://10.88.115.137:9080/lua/test?name=Leo
,可以看到输出 hello Leo!
。
本文作者:小郑[努力版]
本文链接:https://www.cnblogs.com/2678066103hs/p/18297322
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步