openResty后端服务化网关
openResty介绍
openresty是基于 Nginx 与 Lua 的高性能 Web 平台,主要应用网关(API网关、负载均衡、CDN等)、web应用(mysql、redis、Memcached (一个分布式内存对象缓存系统))等。作者章亦春。
中文官网地址:https://openresty.org/cn/
一、简单应用
1.1 下载和安装
wins和linux下的下载和安装都很简单。官网下载和安装帮助文档:https://openresty.org/cn/download.html
文件夹介绍
conf:类似原nginx配置文件夹,司令部
logs:日志打印
lualib:第三方扩展模块存放的地方
lua:标配用来存放用户lua文件的位置
luajit:即时编译器,原理同jvm的JIT,只会加载热点代码、和解释器同时存在
1.2 wins下openResty网关开发
1.2.1 启动、关闭、检查进程这些就不说了,看一眼README就行了
1.2.2 集中炮火攻打司令部
forward:配置转发规则
lua:存放用户的lua文件
nginx.conf:配置文件的入口
1.2.3 nginx.conf
#user nobody;
#设置进程数,章亦春:openresty单线程多进程执行
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
keepalive_timeout 65;
#gzip on;
#默认lua能读取到请求的body
#lua_need_request_body true;
include forward/*.conf;
}
1.3 负载均衡
8080端口
server {
listen 8080;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
default_type text/html;
location /asky {
content_by_lua_block{
ngx.say("hello world")
ngx.print("hello world asky")
}
}
location /test {
proxy_pass http://upstream_back_9090;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
#负载均衡配置
upstream upstream_back_9090 {
ip_hash;
#注意修改你的项目的IP地址及端口
server 127.0.0.1:9090;
#server 127.0.0.2:9090;
}
9090端口
server {
listen 9090;
server_name localhost;
default_type text/html;
location /asky {
content_by_lua_block{
ngx.say("hello world")
ngx.print("hello world asky")
}
}
location /test {
#content_by_lua_file conf/lua/request.lua;
#content_by_lua_file conf/lua/header.lua;
content_by_lua_file conf/lua/body.lua;
}
}
1.4 自定义lua模块
-- 获取GET请求中query信息的key 和value
local args =ngx.req.get_uri_args()
-- if args ~= nil then
for k,v in pairs(args) do
ngx.say("[GET] key:",k,"v:",v)
ngx.say("<br>")
end
--end
ngx.req.read_body() -- 获取POST请求中的参数
-- 解析body参数之前需要先读取body 拿到的是数组
local postarg = ngx.req.get_post_args()
if postarg ~= nil then
for k,v in pairs(postarg) do
ngx.say("[POST] key:",k,"v:",v)
ngx.say("<br>")
end
end
-- 读取请求中的headers
local headers = ngx.req.get_headers()
for k,v in pairs(headers) do
ngx.say("[Header]key,",k,"value:",v)
ngx.say("<br>")
end
-- 获取请求体的data
ngx.req.read_body() -- 首先读取请求中的body体
-- 获取请求的body信息 获取的是字符串
local data = ngx.req.get_body_data()
ngx.say(data)
二、openresty特性
2.1 openresty三大特性
章亦春加入美国的 CDN 公司 Cloudflare后,因为 OpenResty 高性能和动态的优势很适合 CDN 的业务需求,很快, OpenResty 就成为 CDN 的技术标准。这也是现在很多产品服务流量入口,选择openresty的原因。
1、同步非阻塞
同步指的是代码执行顺序,同步的执行代码更符合多数coder的思维方式。当前行代码执行后,拿到执行结果,继续执行下段代码逻辑。
非阻塞指的是CPU资源,执行代码等待的间隙时间,CPU去执行其他请求的逻辑。这也保证了openresty的高性能。但非阻塞并不能缩减用户的request time,只是提高服务端的处理效率。openresty由worker处理单个用户请求,当前worker被阻塞时,该worker的其他请求是阻塞的。
2、动态
上文提到了lua脚本语言, 实际是由openResty 中 lua-nginx-module 模块中提供的 Lua API,由于lua语言天生动态的优势,在使用openresty时,只要不停车更新lua脚本文件内容即可执行新的lua逻辑。
3、测试文档
https://github.com/openresty/lua-nginx-module
2.2 Master-worker工作模式
Nginx启动后,会启动一个Master进程和一个或多个worker进程。其中Master不执行请求,只负责对worker进行分发调度,
2.3 Nginx的11个阶段
变量赋值与各阶段执行顺序,先看图:
nginx内执行过程不同于c/java这类语言,nginx依据执行指令在各个阶段做每个阶段该干的活。请求中更关注的是以下几个阶段:
set_by_lua*: 流程分支处理判断变量初始化
rewrite_by_lua*: 转发、重定向、缓存等功能(例如特定请求代理到外网)
access_by_lua*: IP 准入、接口权限等情况集中处理(例如配合 iptable 完成简单防火墙)
content_by_lua*: 内容生成
header_filter_by_lua*: 响应头部过滤处理(例如添加头部信息)
body_filter_by_lua*: 响应体过滤处理(例如完成应答内容统一成大写)
log_by_lua*: 会话完成后本地异步完成日志记录(日志可以记录在本地,还可以同步到其他机器)
三、openresty各阶段执行逻辑
3.1 Initialization Phase nginx初始化阶段
内置变量:
$ remote_addr来获取客户端的IP地址。
ngx_echo模块的 echo_before_body “before…”; nginx的切面编程法,
ngx_echo模块的 echo_after_body “after…”;nginx的切面编程法
3.2 Rewrite/Access Phase 请求访问、重写阶段
例:
set 指令是在rewrite阶段执行的。
rewrite_by_lua 虽然和set都在rewrite阶段,但是rewrite_by_lua要晚于set。
内置模块ngx_access用于控制访问等处在access阶段。实际上过滤ip功能时,ngx_access效率略高于access_by_lua。
set $value "hello world";
rewrite_by_lua "ngx.var.a = ngx.var.a + 1";
#allow deny 匹配到一条后忽略后续所有同类指令。除access_by_lua block或者file
allow 127.0.0.1;
deny all;
3.3 Content Phase 内容处理阶段
content是nginx处理请求中最重要的阶段。
该阶段包含一系列的指令集: echo, echo_exec, proxy_pass, echo_location, content_by_lua。
3.4 Log Phase 日志输出阶段
无论如何,每次请求的log_by_lua* 的内容打印都是在当前location最终结束前打印,想想也对,当前请求不结束nginx哪里知道本次请求的状态码到底是什么,一次请求在各个location里蹦来蹦去的。
四 nginx实践应用热点及章亦春博客总结
4.1 nginx用哪个变量进行location匹配?
大部分讲nginx的文章都在拼命说location的匹配规则,鲜有人提到这个问题。google/baidu到的有说uri也有说request_uri的,有争议就要拿实践作为标准来检验。(这个问题可能看起来很无趣,其实也挺有趣的)
划重点:
1、uri和request_uri都是nginx中ngx_http_core模块的内置变量,且是只读内置变量。
2、uri用于获取当前请求的(解码后)uri,不包括任何查询字符串参数。request_uri是用于获取URI原始的、未经解码的形式,包括任何查询字符串。
话不多说,开始整活。先说我的结论,nginx对location进行匹配时,使用uri进行正则匹配。看到有博客说进入location前会先对路径解码,个人感觉那种理解是错误的,下面实例可以说明这个问题。
#第一种匹配检查 注:%2f是/的编码
location /test%2fabc{
echo "hello world";
}
(1)我们用http://127.0.0.1:8080/test/abc进行访问,进入到nginx后内置变量的值
uri : /test/abc
request_uri : /test/abc
所以无论如何,这种访问也匹配不到,报错404找不到对应的location
(2)改用http://127.0.0.1:8080/test%2fabc访问,进入到nginx后内置变量的值
uri : /test/abc
request_uri :/test%2fabc
结果:
说明什么?匹配时不是用request_uri匹配的,否则不会The system cannot find the path specified。
(3)匹配规则改成
#第二种匹配检查
location /test/abc{
echo "hello world";
}
访问路径还是http://127.0.0.1:8080/test%2fabc
此时
uri : /test/abc
request_uri :/test%2fabc
结果:
4.2 openresty性能测试
先结论,后论述:openresty单机单进程动态访问某网络资源,QPS达到10000+,每次访问的耗时已基本达到我能接受的极限。
(并发量达到50,处理1000次请求,单次处理耗时已达到50ms)
1)ab apache bench
2)wrk