Openresty简介及开发

来自技术开发小组内部wiki
跳转至: 导航搜索

openresty简介

OpenResty (也称为 ngx_openresty)是一个全功能的 Web 应用服务器,它打包了标准的 Nginx 核心,很多的常用的第三方模块,以及它们的大多数依赖项。 OpenResty 通过汇聚各种设计精良的 Nginx 模块, 从而将 Nginx 有效的变成一个强大的 Web 应用服务器, 这样, Web 开发人员可以使用 Lua 脚本语言调动 Nginx 支持的各种C以及Lua 模块, 快速构造出足以胜任 10K+ 并发连接响应的超高性能Web 应用系统.

OpenResty 的目标是让你的Web服务直接跑在 Nginx 服务内部, 充分利用 Nginx 的非阻塞 I/O 模型, 不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL,PostgreSQL,Memcaches 以及 Redis 等都进行一致的高性能响应.

openresty模块

由 nginx 和lua (LuaJIT)组合而成 其中 nginx 又包含下列模块

  1. ArrayVarNginxModule
  2. AuthRequestNginxModule
  3. CoolkitNginxModule
  4. DrizzleNginxModule
  5. EchoNginxModule
  6. EncryptedSessionNginxModule
  7. FormInputNginxModule
  8. HeadersMoreNginxModule
  9. IconvNginxModule
  10. StandardLuaInterpreter
  11. MemcNginxModule
  12. Nginx
  13. NginxDevelKit
  14. LuaCjsonLibrary
  15. LuaNginxModule
  16. LuaRdsParserLibrary
  17. LuaRedisParserLibrary
  18. LuaRestyCoreLibrary
  19. LuaRestyDNSLibrary
  20. LuaRestyLockLibrary
  21. LuaRestyLrucacheLibrary
  22. LuaRestyMemcachedLibrary
  23. LuaRestyMySQLLibrary
  24. LuaRestyRedisLibrary
  25. LuaRestyStringLibrary
  26. LuaRestyUploadLibrary
  27. LuaRestyUpstreamHealthcheckLibrary
  28. LuaRestyWebSocketLibrary
  29. LuaUpstreamNginxModule
  30. PostgresNginxModule
  31. RdsCsvNginxModule
  32. RdsJsonNginxModule
  33. RedisNginxModule
  34. Redis2NginxModule
  35. SetMiscNginxModule
  36. SrcacheNginxModule
  37. XssNginxModule

openResty 安装

wget http://openresty.org/download/ngx_openresty-1.7.10.2.tar.gz

解压之后进入解压目录进行编译参数配置

./configure --prefix=/data/soft/openresty --user=nginx --group=nginx --with-luajit

以上编译参数可根据自己的实际需求调整,

./configure --help 查看参数

make & make install 编译安装

lua 简介

Lua的目标是成为一个很容易嵌入其它语言中使用的语言。大多数程序员也认为它的确做到了这一点。 很多应用程序使用Lua作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。 Lua是一种轻量语言,它的官方版本只包括一个精简的核心和最基本的库。这使得Lua体积小、启动速度快。

lua 代码举例及组件

Lua是一种动态类型语言,因此语言中没有类型的定义,不需要声明变量类型,每个变量自己保存了类型。 有8种基本类型:nil、布尔值(boolean)、数字体(number)、字符串型(string)、用户自定义类型(userdata)、函数(function)、线程(thread)和表(table)
 print(type(nil)) -- 输出 nil
 print(type(99.7+12*9)) -- 输出 number
 print(type(true)) -- 输出 boolean
 print(type("Hello Wikipedia")) -- 输出 string
 print(type(print)) -- 输出 function
 print(type{1, 2, test = "test"}) -- 输出 table

hello 入门

print("Hello, world!") --经典的hell world;

函数体

function create_a_counter()
 local count = 0
 return function()
 count = count + 1
 return count
 end
end

结构体 if

score = 0
if score == 100 then
 print("Very good!Your score is 100")
elseif score >= 60 then
 print("Congratulations, you have passed it,your score greater or equal to 60")
else
 if score > 0 then 
 print("Your score is better than 0")
 else
 print("My God, your score turned out to be 0")
 end --与上一示例代码不同的是,此处要添加一个end
end


结构体 while

x = 1
sum = 0 
while x <= 5 do
 sum = sum + x
 x = x + 1
end
print(sum) -->output 15

结构体 for

for i=1,10,2 do
 print(i)
end

lua中支持 break return 与其他不同的是lua return 可以返回多个参数

 local ab=function (flag) return flat,nil end 
 local data,err = ab(1)
更多信息 [[1]] 常用的lua组件:
cjson -- lua table 和json 转换工具
resty.redis --redis 连接工具
resty.ssdb --ssdb 连接工具
cmsgpack --msgpack 压缩解压工具
resty.string --字符串加解密工具
resty.cookie --cookie管理工具
resty.lock --共享锁工具
...

ngx + lua 组合

nginx 执行顺序 nginx执行步骤 nginx在处理每一个用户请求时,都是按照若干个不同的阶段依次处理的,与配置文件上的顺序没有关系,详细内容可以阅读《深入理解nginx:模块开发与架构解析》这本书,这里只做简单介绍;

  1. post-read 读取请求内容阶段,nginx读取并解析完请求头之后就立即开始运行;
  2. server-rewrite server请求地址重写阶段;
  3. find-config 配置查找阶段,用来完成当前请求与location配重块之间的配对工作;
  4. rewrite location请求地址重写阶段,当ngx_rewrite指令用于location中,就是再这个阶段运行的;
  5. post-rewrite 请求地址重写提交阶段,当nginx完成rewrite阶段所要求的内部跳转动作,如果rewrite阶段有这个要求的话;
  6. preaccess 访问权限检查准备阶段,ngx_limit_req和ngx_limit_zone在这个阶段运行,ngx_limit_req可以控制请求的访问频率,ngx_limit_zone可以控制访问的并发度;
  7. access 权限检查阶段,ngx_access在这个阶段运行,配置指令多是执行访问控制相关的任务,如检查用户的访问权限,检查用户的来源IP是否合法;
  8. post-access 访问权限检查提交阶段;
  9. try-files 配置项try_files处理阶段;
  10. content 内容产生阶段,是所有请求处理阶段中最为重要的阶段,因为这个阶段的指令通常是用来生成HTTP响应内容的;
  11. log 日志模块处理阶段。

nginx +lua 执行顺序

  1. init_by_lua、init_by_lua_file --nginx 初始化 阶段
  2. init_worker_by_lua、init_worker_by_lua_file --nginx worker 初始化阶段
  3. set_by_lua、set_by_lua_file --设定nginx 变量
  4. rewrite_by_lua、rewrite_by_lua_file --检查rewrite 规则
  5. access_by_lua,access_by_lua_file --执行正式流程前的权限检查
  6. content_by_lua,content_by_lua_file --正文处理
  7. header_filter_by_lua,header_filter_by_lua_file -过滤头部 协议转换
  8. body_filter_by_lua,body_filter_by_lua_file --过滤正文 协议转换
  9. log_by_lua,log_by_lua_file 日志处理

ngx + lua 开发

  • nginx +lua 与 openResty 区别
 #设置纯lua的外部模块搜索路径(';;' is the default path):
 lua_package_path '/foo/bar/?.lua;/blah/?.lua;;';
 
 # 设置使用c编写的lua外部模块搜索路径(can also use ';;'):
 lua_package_cpath '/bar/baz/?.so;/blah/blah/?.so;;';
 lua_package_cpath 与 lua_package_path 在openResty下无需设定,openResty下默认库在lualibs下,其他基本没什么差异。
  • 线下与上线区别,及优化
lua_code_cache on<线上> | off <测试>

nginx.conf 配置示范

user www; worker_processes 1;

error_log logs/error.log;

events {

worker_connections 1024;

}


http {

include mime.types;
default_type text/plain;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
sendfile on;
keepalive_timeout 65;
#gzip on;
init_by_lua_file "/usr/local/openresty/nginx/conf/lua/init.lua";
server {
listen 60080;
server_name localhost 192.168.30.225;

#charset koi8-r;
#if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})") {
#set $year $1;
#set $month $2;
#set $day $3;
#}
#access_log logs/www.xxx.com_$year$month$day_access.log;
set_by_lua $date 'local date=os.date("%Y",os.time())..os.date("%m",os.time())..os.date("%d",os.time()) return date ';
access_log logs/host_test_$date.log main;
location / {
root html;
index index.html index.htm;
}
location /lua {
default_type 'text/plain';
content_by_lua_file "/usr/local/openresty/nginx/conf/lua/work.lua";
}
location /get {
default_type 'text/plain';
set_unescape_uri $key $arg_key;
redis2_query get $key;
redis2_pass 127.0.0.1:6379;
}
location /set {
set_unescape_uri $key $arg_key;
set_unescape_uri $val $arg_val;
redis2_query set $key $val;
redis2_pass 127.0.0.1:6379;
}
location /luasay {
default_type 'text/plain';
content_by_lua "ngx.say('Hello,world!');ngx.exit(200);";
}
location /luadate {
default_type 'text/plain';
content_by_lua 'local date =os.time();
ngx.say(os.date("%Y%m%d",date));
ngx.say("now use ngx.var");
ngx.say(ngx.var.date);

ngx.exit(200);';
}
location /luaheader {
content_by_lua " 
ngx.header.content_type = 'text/plain';
ngx.header['X-My-Header'] = 'blah blah';
ngx.header['server-id']=' web 1'; -- 服务器信息 方便调试
ngx.header['Set-Cookie'] = {'a=32; path=/', 'b=4; path=/'}
ngx.say('see html header');
ngx.exit(200);";
}
location /luareq {
content_by_lua "
ngx.header.content_type = 'text/plain';
ngx.say('request header');
ngx.print(ngx.req.raw_header());
ngx.say('request method');
ngx.say(ngx.req.get_method());
ngx.say('body');
ngx.say(ngx.req.get_body_data());
";

}
location /luaarg {
content_by_lua " local a = ngx.var.arg_a or 0; ngx.say('args '..a); ";
}

location /luaarg_req {

content_by_lua '
ngx.header.content_type = "text/plain";
local arg_a =tonumber(ngx.var.arg_a) or 0
if(ngx.var.arg_a==0) then 
ngx.say(" arg a not set ")
else 
res=ngx.location.capture("/luaarg",{args={a=arg_a}});
if res.status == 200 then
ngx.print(res.body)
end
end 
local args= ngx.decode_args(ngx.var.args);
ngx.say("input args is:") 
for arg, value in pairs(args ) do
ngx.say("arg "..arg.." is "..value)
end
';
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}


}

  • init.lua 内容
cjson = require "cjson"
redis = require "resty.redis"
--lock = require "resty.lock"

log = ngx.log
local ERR = ngx.ERR

--[[ nginx初始化: lua_shared_dict lock_cache 100k; ]]
local lock_cache = ngx.shared.lock_cache

redis_host = '127.0.0.1'
redis_port = 6379

--[[ 动态修改 lua init 变量 ]]
update_redis_info = function(host,port)
 if host ~= nil then
 redis_host = host
 end
 if port ~=nil and tonumber(port)>0 then
 redis_port = tonumber(port)
 end
end

-- [[ 写入文件 ]]
function file_put_contents(logfile,msg,mode)
 mode = mode or "w+"
 local fd = io.open(logfile,mode)
 if fd == nil then
 return nil
 end
 fd:write(msg)
 fd:flush()
 fd:close()
 return 'ok'
end

--[[ 获取件内容 ]]
function file_get_contents(filename)
 local file = io.open(filename, "r")
 if file then
 local content = file:read("*a")
 io.close(file)
 return content,nil
 end
 return nil
end

ngx.log(ERR,"init log")
if redis then
 ngx.log(ERR,"redis ok")
end
  • worker.lua
local inspect = require "inspect"
local function errorj(message,status)
 local _t=type(message)
 local _s=type(status)
 if t == 'userdata' then
 message = tostring(message)
 end
 if message == nil then
 message=""
 end
 if status == nil then
 status =0
 end
 if _s ~= 'number' then
 status = tonumber(status)
 status = _s
 end
 local json='{"status":"'..status..'","data":"","msg":"' .. message .. '"}'
 --local json= inspect(message)
 ngx.header.content_type = "text/plain";
 ngx.say(json)
 ngx.exit(200)
end

local red = redis:new()
local method = ngx.var.arg_m
local filename = "/usr/local/openresty/nginx/conf/lua/cache.txt"

if method == "read" or method =="write" then
 ngx.header['content-type']="text/plain"
 local data = ngx.var.arg_data or ""
 if method == "write" and data ~= "" then
 status=file_put_contents(filename , data)
 if status then
 ngx.print("write ok")
 else
 ngx.print("write error")
 end
 else
 data=file_get_contents(filename)
 if data then
 ngx.print("read ok \n")
 ngx.print(data)

 else
 ngx.print("read error")
 end
 end
end

if method =='set' or method =='get' or method =="del" then
 ngx.header['content-type']="text/json"

 local red = redis:new()
 red:set_timeout(1000)
 local ok, err = red:connect(redis_host, redis_port)
 if not ok then
 errorj("redis_server_err")
 else
 local key = ngx.var.arg_key or ""
 if method == "get" and key ~='' then
 local res, err = red:get(key)
 if not res then
 errorj("failed to get "..key..": ", err)
 else
 errorj(res,200)
 end
 end
 if method == "set" and key ~='' then
 local val = ngx.var.arg_val or ""
 local res, err = red:set(key,val)
 if not res then
 errorj("failed to set "..key..": ", err)
 else
 errorj(res,200)
 end
 end
 if method == "del" and key ~='' then
 local res, err = red:del(key)
 if not res then
 errorj("failed to set "..key..": ", err)
 else
 errorj(res,200)
 end
 end
 end
 if not key then
 errorj ("key is empty ",200)
 end
 red:set_keepalive(10000, 100)
 end




if method ~='set' and method ~='get' and method ~="del" and method ~= "read" and method ~="write" then
 errorj ("method not allowd ")
end
  • 通用api 接口设计
location ~ ^/api/([-_a-zA-Z0-9/]+).json {
 access_by_lua_file /path/to/lua/api/protocal_decode.lua; #协议解码
 content_by_lua_file /path/to/lua/api/$1.lua; #主要逻辑
 body_filter_by_lua_file /path/to/lua/api/protocal_encode.lua; #协议编码输出 
}

nginx 变量参考

$arg_name	请求中的name参数
$args	请求中的参数
$binary_remote_addr	远程地址的二进制表示
$body_bytes_sent	已发送的消息体字节数
$content_length	HTTP请求信息里的"Content-Length"
$content_type	请求信息里的"Content-Type"
$document_root	针对当前请求的根路径设置值
$document_uri	与$uri相同; 比如 /test2/test.php
$host	请求信息中的"Host",如果请求中没有Host行,则等于设置的服务器名
$hostname	机器名使用 gethostname系统调用的值
$http_cookie	cookie 信息
$http_referer	引用地址
$http_user_agent	客户端代理信息
$http_via	最后一个访问服务器的Ip地址。
$http_x_forwarded_for	相当于网络访问路径
$is_args	如果请求行带有参数,返回“?”,否则返回空字符串
$limit_rate	对连接速率的限制
$nginx_version	当前运行的nginx版本号
$pid	worker进程的PID
$query_string	与$args相同
$realpath_root	按root指令或alias指令算出的当前请求的绝对路径。其中的符号链接都会解析成真是文件路径
$remote_addr	客户端IP地址
$remote_port	客户端端口号
$remote_user	客户端用户名,认证用
$request	用户请求
$request_body	这个变量(0.7.58+)包含请求的主要信息。在使用proxy_pass或fastcgi_pass指令的location中比较有意义
$request_body_file	客户端请求主体信息的临时文件名
$request_completion	如果请求成功,设为"OK";如果请求未完成或者不是一系列请求中最后一部分则设为空
$request_filename	当前请求的文件路径名,比如/opt/nginx/www/test.php
$request_method	请求的方法,比如"GET"、"POST"等
$request_uri	请求的URI,带参数; 比如http://localhost:88/test1/	test2/test.php
$scheme	所用的协议,比如http或者是https
$server_addr	服务器地址,如果没有用listen指明服务器地址,使用这个变量将发起一次系统调用以取得地址(造成资源浪费)
$server_name	请求到达的服务器名
$server_port	请求到达的服务器端口号
$server_protocol	请求的协议版本,"HTTP/1.0"或"HTTP/1.1"
$uri	请求的URI,可能和最初的值有不同,比如经过重定向之类的