Resty 入门
简介
是一个基于 Nginx 与 Lua 的高性能 Web 平台,汇聚各种设计精良的 Nginx 模块,从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。
这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。
核心特性
- 非阻塞IO模型
- 集成Lua的开发环境,通过Lua扩展,使Nginx变成了一个web平台(框架)
- 集成众多的Nginx模块
集成组件
目标
- web服务
- API网关
- 负载均衡
Nginx 简介
为Web服务器而生, 为了解决C10K问题,其核心设计思想是基于事件的处理机制。
网上的架构图:
主要包括了Master和Worker。
Master:
Nginx的主进程,用来管理worker,充当与用户交互的接口。
不处理网络事件
Worker:
Nginx的工作进程
辅助进程(helper process):
缓存辅助进程
模块化
模块化的设计,使得Nginx很容易进行扩展。
常用模块
1、ngx_http_core_module #包括一些核心的http参数配置,对应Nginx的配置为HTTP区块部分
2、ngx_http_access_module #访问控制模块,用来控制网站用户对Nginx的访问
3、ngx_http_gzip_module #压缩模块,对Nginx返回的数据压缩,属于性能优化模块
4、ngx_http_fastcgi_module #FastCGI模块,和 动态应用相关的模块,例如PHP
5、ngx_http_proxy_module #Proxy代理模块
6、ngx_http_upstream_module #负载均衡模块,可以实现网站的负载均衡功能及节点的健康检查
7、ngx_http_rewrite_module #URL地址重写模块
8、ngx_http_limit_conn_module #限制用户并发连接数及请求数模块
9、ngx_http_limit_req_module #根据定义的key限制Nginx请求过程的速率
10、ngx_http_log_module #访问日志模块,以指定的格式记录Nginx客户访问日志等信息
11、ngx_http_auth_basic_module #Web认证模块,设置Web用户通过账号、密码访问Nginx
12、ngx_http_ssl_module #ssl模块,用于加密的http连接,如https
13、ngx_http_stub_status_module #记录Nginx基本访问状态信息等模块
处理阶段
每个请求对应Nginx相应11个处理阶段。
- post-read:读取请求内容
- server-rewrite: Server请求地址重写
- find-config:配置查找,不支持模块注册程序, 由Nginx核心完成当前请求与location配置块之间的配对工作
- rewrite:Location地址重写
- post-rewrite:请求地址重写提交阶段,
- preaccess:访问权限检查准备阶段,ngx_limit_req 控制请求的访问额度,ngx_limit_zone限制访问并发
- access:访问权限检查
ngx_access, ngx_auth_request, ngx_lua, access_by_lua运行在该阶段 - post-access:访问权限检查提交, 配合access阶段实现标准,提供satify的功能。 satisfy all , satisfy any。
- try-files:配置项 try_files 的功能
- content:产生内容并输出HTTP相应, 是交互最多的阶段。
- log: 日志处理阶段,处理日志的输出
【待补充】
OpenRestry
应用场景:
- 高性能Web服务器
- 通过Lua扩展,构建web网关和应用防火墙(WAF)
部署安装
安装文档参考官网
内置模块
我本机下载的包中包括的模块
Hello World
作为一个直观的认识,以下是hello world的配置
worker_processes 1; #nginx worker 数量
error_log logs/error.log; #指定错误日志文件路径
events {
worker_connections 1024;
}
http {
server {
#监听端口,若你的8000端口已经被占用,则需要修改
listen 8000;
location / {
default_type text/html;
content_by_lua_block {
ngx.say("HelloWorld")
}
}
}
}
content_by_lua_block , 执行lua的程序代码
content_by_lua_file /path/to/content.lua; 执行代码文件
Lua API
Nginx Lua API
通过该模块,用户可以编写Lua脚本与Nginx交互,处理客户端的请求。
注意: 代码中的I/O请求, 一定要使用Nginx Lua的API进行处理,因为所有API的处理方式为非阻塞调用, Lua官方标准的一些io库中,为阻塞的方式。
以下列出一些基本的API,给大家有个直观的认识
基本API
ngx.arg[index]:
上下文:set_by_lua , body_filter_by_lua
描述: 获取参数
ngx.null 表示nil值
ngx.var.VAR : 变量定义
常量:
- ngx.OK(0)
- ngx.ERROR(-1)
- ngx.AGAIN(-2)
- ngx.DONE(-4)
-
ngx.DECLINED(-5)
- ngx.HTTP_GET
- ngx.HTTP_HEAD
- ngx.HTTP_PUT
- ngx.HTTP_POST
- ngx.HTTP_DELETE
- ngx.HTTP_OPTIONS (added in the v0.5.0rc24 release)
- ngx.HTTP_MKCOL (added in the v0.8.2 release)
- ngx.HTTP_COPY (added in the v0.8.2 release)
- ngx.HTTP_MOVE (added in the v0.8.2 release)
- ngx.HTTP_PROPFIND (added in the v0.8.2 release)
- ngx.HTTP_PROPPATCH (added in the v0.8.2 release)
- ngx.HTTP_LOCK (added in the v0.8.2 release)
- ngx.HTTP_UNLOCK (added in the v0.8.2 release)
- ngx.HTTP_PATCH (added in the v0.8.2 release)
- ngx.HTTP_TRACE (added in the v0.8.2 release)
- value = ngx.HTTP_CONTINUE (100) (first added in the v0.9.20 release)
- value = ngx.HTTP_SWITCHING_PROTOCOLS (101) (first added in the v0.9.20 release)
- value = ngx.HTTP_OK (200)
- value = ngx.HTTP_CREATED (201)
- value = ngx.HTTP_ACCEPTED (202) (first added in the v0.9.20 release)
- value = ngx.HTTP_NO_CONTENT (204) (first added in the v0.9.20 release)
- value = ngx.HTTP_PARTIAL_CONTENT (206) (first added in the v0.9.20 release)
- value = ngx.HTTP_SPECIAL_RESPONSE (300)
- value = ngx.HTTP_MOVED_PERMANENTLY (301)
- value = ngx.HTTP_MOVED_TEMPORARILY (302)
- value = ngx.HTTP_SEE_OTHER (303)
- value = ngx.HTTP_NOT_MODIFIED (304)
- value = ngx.HTTP_TEMPORARY_REDIRECT (307) (first added in the v0.9.20 release)
- value = ngx.HTTP_BAD_REQUEST (400)
- value = ngx.HTTP_UNAUTHORIZED (401)
- value = ngx.HTTP_PAYMENT_REQUIRED (402) (first added in the v0.9.20 release)
- value = ngx.HTTP_FORBIDDEN (403)
- value = ngx.HTTP_NOT_FOUND (404)
- value = ngx.HTTP_NOT_ALLOWED (405)
- value = ngx.HTTP_NOT_ACCEPTABLE (406) (first added in the v0.9.20 release)
- value = ngx.HTTP_REQUEST_TIMEOUT (408) (first added in the v0.9.20 release)
- value = ngx.HTTP_CONFLICT (409) (first added in the v0.9.20 release)
- value = ngx.HTTP_GONE (410)
- value = ngx.HTTP_UPGRADE_REQUIRED (426) (first added in the v0.9.20 release)
- value = ngx.HTTP_TOO_MANY_REQUESTS (429) (first added in the v0.9.20 release)
- value = ngx.HTTP_CLOSE (444) (first added in the v0.9.20 release)
- value = ngx.HTTP_ILLEGAL (451) (first added in the v0.9.20 release)
- value = ngx.HTTP_INTERNAL_SERVER_ERROR (500)
- value = ngx.HTTP_METHOD_NOT_IMPLEMENTED (501)
- value = ngx.HTTP_BAD_GATEWAY (502) (first added in the v0.9.20 release)
- value = ngx.HTTP_SERVICE_UNAVAILABLE (503)
- value = ngx.HTTP_GATEWAY_TIMEOUT (504) (first added in the v0.3.1rc38 release)
- value = ngx.HTTP_VERSION_NOT_SUPPORTED (505) (first added in the v0.9.20 release)
- value = ngx.HTTP_INSUFFICIENT_STORAGE (507) (first added in the v0.9.20 release)
- ngx.STDERR
- ngx.EMERG
- ngx.ALERT
- ngx.CRIT
- ngx.ERR
- ngx.WARN
- ngx.NOTICE
- ngx.INFO
- ngx.DEBUG
ngx.ctx: 表示每个请求的上下文数据
lacation
funciton | 含义 | 备注 |
---|---|---|
ngx.req.get_uri_args | 获取参数 | |
ngx.req.read_body | 读取body | |
ngx.req.get_post_args | 获取post参数 | |
ngx.location.capture | ||
ngx.location.capture_multi | 子请求 | local res1, res2 = ngx.location.capture_multi({ {"/sum", {args={a=3, b=8}}} |
ngx.exec | 内部跳转 | |
ngx.redirect | 外部重定向 | |
ngx.encode_args | 转义 | |
ngx.req.get_body_data | 获取body | lua_need_request_body on; |
ngx.req.read_body | 非全局获取body | |
ngx.req.get_body_file | 已存入临时文件时,通过该方法获取 |
内置变量
API Server
OpenResty的主要目的之一,是用来作为API的一个server,通过lua脚本对基础功能进行扩充,使之具备限流,熔断,降级的功能
简单的API 框架
实现一个最最简单的数学计算:加、减、乘、除,给大家演示如何搭建简单的 API Server。
nginx conf 的例子
worker_processes 1; #nginx worker 数量
error_log logs/error.log; #指定错误日志文件路径
events {
worker_connections 1024;
}
http {
server {
listen 80;
# 在代码路径中使用nginx变量
# 注意: nginx var 的变量一定要谨慎,否则将会带来非常大的风险
location ~ ^/api/([-_a-zA-Z0-9/]+) {
access_by_lua_file lua/access_check.lua;
content_by_lua_file lua/$1.lua;
}
}
}
lua 脚本例子
--========== {$prefix}/lua/comm/param.lua
local _M = {}
-- 对输入参数逐个进行校验,只要有一个不是数字类型,则返回 false
function _M.is_number(...)
local arg = {...}
local num
for _,v in ipairs(arg) do
num = tonumber(v)
if nil == num then
return false
end
end
return true
end
return _M
--========== {$prefix}/lua/access_check.lua
local param= require("comm.param")
local args = ngx.req.get_uri_args()
if not args.a or not args.b or not param.is_number(args.a, args.b) then
ngx.exit(ngx.HTTP_BAD_REQUEST)
return
end
子查询
子查询是常用的功能,可以发起非阻塞的内部请求访问目标Location。
注意:
子查询与HTTP 301/302 完全不同。
子查询与内部的重定向(ngx.exec)完全不同。
子查询只是模拟HTTP接口的形式,没有额外的HTTP/TCP流量,也没有IPC 调用,所有工作都是在C语言的内部完成。
--发起一个请求
res = ngx.location.capture('/foo/bar?a=3&b=4')
--发起一个POST请求
res = ngx.location.capture(
'/foo/bar',
{ method = ngx.HTTP_POST, body = 'hello, world' }
)
--arg可以附带参数
ngx.location.capture('/foo?a=1',
{ args = { b = 3, c = ':' } }
)
--等同于
ngx.location.capture('/foo?a=1&b=3&c=%3a')
--或者
ngx.location.capture('/foo?a=1',
{ args = 'b=3&c=%3a' } }
)
HTTP 调用
引用 resty.http 库资源,它来自 github
参考 resty-http 官方 wiki 说明,我们可以知道 request_uri 函数完成了连接池、HTTP 请求等一系列动作。
题外话,为什么这么简单的方法我们还要求助外部开源组件呢?其实我也觉得这个功能太基础了,真的应该集成到 OpenResty 官方包里面,只不过目前官方默认包里还没有。
如果你的内部请求比较少,使用 ngx.location.capture+proxy_pass 的方式还没什么问题。但如果你的请求数量比较多,或者需要频繁的修改上游地址,那么 resty.http就更适合你。
示例:
location /test {
content_by_lua_block {
ngx.req.read_body()
local args, err = ngx.req.get_uri_args()
-- http库
local http = require "resty.http"
local httpc = http.new()
local res, err = httpc:request_uri(
"http://127.0.0.1:81/spe_md5",
{
method = "POST",
body = args.data,
}
)
if 200 ~= res.status then
ngx.exit(res.status)
end
if args.key == res.body then
ngx.say("valid request")
else
ngx.say("invalid request")
end
}
连接池
从我们应用最多的 HTTP 连接、数据库连接、消息推送、日志存储等,所有点到点之间,都需要花样繁多的各色连接。为了传输数据,我们需要完成创建连接、收发数据、拆除连接。对并发量不高的场景,我们为每个请求都完整走这三步(短连接),开发工作基本只考虑业务即可,基本上也不会有什么问题。一旦挪到高并发应用场景,那么可能我们就要郁闷了。
思考:为什么要使用连接池?
如果没有连接池,你将会碰到下面几个常见问题:
性能普遍上不去
CPU 大量资源被系统消耗
网络一旦抖动,会有大量 TIME_WAIT 产生,不得不定期重启服务或定期重启机器
服务器工作不稳定,QPS 忽高忽低
Resty在使用连接池时,如果不注意,很容易产生数据奇怪的错误。
通过使用短连接可以规避该问题, 但当高并发的场景,每次都为一个请求建立连接,是对企业内部防火墙巨大的考验。
在 OpenResty 中,所有具备 set_keepalive 的类、库函数,说明他都是支持连接池的。
在连接memcache时,如果没有设置keepalive,使用的就是短连接。
resty可以通过设置keepalive, 将已经建立的正确连接放入连接池;
此处需要注意的是,需要把已建立的连接放入池子,千万不要把失败的连接也放进去。
示例
local redis = require "resty.redis"
local red = redis:new()
local ok, err = red:connect("192.168.1.123", 6379)
if not ok then
ngx.log(ngx.ERR, err)
return
end
red:set_keepalive(10000, 100)
cache
srcache
【待补充】
缓存失效
缓存失效的瞬间, 请求会打向数据库,dop-pile effect;
使用lua-resty-lock来解决缓存失效问题
HTTPS
【待补充】
参考
灰度发布
【待补充】
参考
限流
【待补充】
参考
Kong
一个基于OpenResty的网关,有开源版本,有企业版本。