OpenResty使用
Nginx 执行的阶段
Nginx 流程定义
nginx实际把请求处理流程划分为了11个阶段,这样划分的原因是将请求的执行逻辑细分,各阶段按照处理时机定义了清晰的执行语义,开发者可以很容易分辨自己需要开发的模块应该定义在什么阶段,其定义在http/ngx_http_core_module.h中有定义:
NGX_HTTP_POST_READ_PHASE | 接收完请求头之后的第一个阶段,它位于uri重写之前,实际上很少有模块会注册在该阶段,默认的情况下,该阶段被跳过 |
---|---|
NGX_HTTP_SERVER_REWRITE_PHASE | server级别的uri重写阶段,也就是该阶段执行处于server块内,location块外的重写指令,在读取请求头的过程中nginx会根据host及端口找到对应的虚拟主机配置 |
NGX_HTTP_FIND_CONFIG_PHASE | 寻找location配置阶段,该阶段使用重写之后的uri来查找对应的location,值得注意的是该阶段可能会被执行多次,因为也可能有location级别的重写指令 |
NGX_HTTP_REWRITE_PHASE | location级别的uri重写阶段,该阶段执行location基本的重写指令,也可能会被执行多次 |
NGX_HTTP_POST_REWRITE_PHASE | location级别重写的后一阶段,用来检查上阶段是否有uri重写,并根据结果跳转到合适的阶段 |
NGX_HTTP_PREACCESS_PHASE | 访问权限控制的前一阶段,该阶段在权限控制阶段之前,一般也用于访问控制,比如限制访问频率,链接数等 |
NGX_HTTP_ACCESS_PHASE | 访问权限控制阶段,比如基于ip黑白名单的权限控制,基于用户名密码的权限控制等 |
NGX_HTTP_POST_ACCESS_PHASE | 问权限控制的后一阶段,该阶段根据权限控制阶段的执行结果进行相应处理 |
NGX_HTTP_TRY_FILES_PHASE | try_files指令的处理阶段,如果没有配置try_files指令,则该阶段被跳过 |
NGX_HTTP_CONTENT_PHASE | 内容生成阶段,该阶段产生响应,并发送到客户端 |
NGX_HTTP_LOG_PHASE | 日志记录阶段,该阶段记录访问日志 |
Nginx 11阶段
1. 当请求进入Nginx后先READ REQUEST HEADERS 读取头部 然后再分配由哪个指令操作
Identity 寻找匹配哪个Location*
Apply Rate Limits 是否要对该请求限制
Preform Authertication 权限验证
Generate Content 生成给用户的响应内容
如果配置了反向代理 那么将要和上游服务器通信 Upstream Services
当返回给用户请求的时候要经过过滤模块 Response Filter
发送给用户的同时 记录一个Log日志
详细介绍
阶段 | 描述 |
---|---|
post-read | 接收到完整的http头部后处理的阶段,在uri重写之前。一般跳过 |
server-rewrite | location匹配前,修改uri的阶段,用于重定向,location块外的重写指令(多次执行) |
find-config | uri寻找匹配的location块配置项(多次执行) |
rewrite | 找到location块后再修改uri,location级别的uri重写阶段(多次执行) |
post-rewrite | 防死循环,跳转到对应阶段 |
preaccess | 权限预处理 |
access | 判断是否允许这个请求进入 |
post-access | 向用户发送拒绝服务的错误码,用来响应上一阶段的拒绝 |
try-files | 访问静态文件资源 |
content | 内容生成阶段,该阶段产生响应,并发送到客户端 |
log | 记录访问日志 |
OpenResty简介
OpenResty(又称:ngx_openresty) 是一个基于 NGINX 的可伸缩的 Web 平台,由中国人章亦春发起,提供了很多高质量的第三方模块。
OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台。我们知道开发 Nginx 的模块需要用 C 语言,同时还要熟悉它的源码,成本和门槛比较高。国人章亦春把 LuaJIT VM 嵌入到了 Nginx 中,使得可以直接通过 Lua 脚本在 Nginx 上进行编程,同时还提供了大量的类库(如:lua-resty-mysql lua-resty-redis 等),直接把一个 Nginx 这个 Web Server 扩展成了一个 Web 框架,借助于 Nginx 的高性能,能够快速地构造出一个足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。
Nginx 采用的是 master-worker 模型,一个 master 进程管理多个 worker 进程,worker 真正负责对客户端的请求处理,master 仅负责一些全局初始化,以及对 worker 进行管理。在 OpenResty 中,每个 worker 中有一个 Lua VM,当一个请求被分配到 worker 时,worker 中的 Lua VM 里创建一个 coroutine(协程) 来负责处理。协程之间的数据隔离,每个协程具有独立的全局变量 _G
。
处理请求流程
由于 Nginx 把一个请求分成了很多阶段,第三方模块就可以根据自己的行为,挂载到不同阶段处理达到目的。OpenResty 也应用了同样的特性。不同的阶段,有不同的处理行为,这是 OpenResty 的一大特色。OpenResty 处理一个请求的流程参考下图(从 Request start 开始):
指令 | 描述 |
---|---|
init_by_lua,init_by_lua_block | 运行在Nginx loading-config 阶段,注册Nginx Lua全局变量,和一些预加载模块。是Nginx master进程在加载Nginx配置时执行 |
init_worker_by_lua | 在Nginx starting-worker阶段,即每个nginx worker启动时会调用,通常用来hook worker进程,并创建worker进行的计时器,用来健康检查,或者设置熔断记时窗口等等。 |
access_by_lua | 在access tail阶段,用来对每次请求做访问控制,权限校验等等,能拿到很多相关变量。例如:请求体中的值,header中的值,可以将值添加到ngx.ctx, 在其他模块进行相应的控制 |
balancer_by_lua | 通过Lua设置不同的负载均衡策略, 具体可以参考lua-resty-balancer |
content_by_lua | 在content阶段,即content handler的角色,即对于每个api请求进行处理,注意不能与proxy_pass放在同一个location下 |
proxy_pass | 真正发送请求的一部分, 通常介于access_by_lua和log_by_lua之间 |
header_filter_by_lua | 在output-header-filter阶段,通常用来重新响应头部,设置cookie等,也可以用来作熔断触发标记 |
body_filter_by_lua | 对于响应体的content进行过滤处理 |
log_by_lua | 记录日志即,记录一下整个请求的耗时,状态码等 |
常用命令
主要帮助对http请求取参、取header头、输出等
命令 | 描述 |
---|---|
ngx.arg | 指令参数,如跟在content_by_lua_file后面的参数 |
ngx.var | request变量,ngx.var.VARIABLE引用某个变量,lua使用nginx内置的绑定变量. ngx.var.remote_addr 为获取远程的地址, |
ngx.ctx | 请求的lua上下文,每次请求的上下文,可以在ctx里记录,每次请求上下文的一些信息,例如:request_id , access_key 等等 |
ngx.header | 响应头,ngx.header.HEADER引用某个头 |
ngx.status | 响应码 |
ngx.log | 输出到error.log |
ngx.send_headers | 发送响应头 |
ngx.headers_sent | 响应头是否已发送 |
ngx.resp.get_headers | 获取响应头 |
ngx.is_subrequest | 当前请求是否是子请求 |
ngx.location.capture | 发布一个子请求 |
ngx.location.capture_multi | 发布多个子请求 |
ngx.print | 输出响应 |
ngx.say | 输出响应,自动添加‘\n‘ |
ngx.flush | 刷新响应 |
ngx.exit | 结束请求 |
Openresty安装
yum安装
你可以在你的 CentOS 系统中添加 openresty 仓库,这样就可以便于未来安装或更新我们的软件包(通过 yum update 命令)
运行下面的命令就可以添加我们的仓库:
1 | sudo yum install yum-utils |
然后就可以像下面这样安装软件包,比如 openresty:
1 | sudo yum install openresty |
如果你想安装命令行工具 resty,那么可以像下面这样安装 openresty-resty 包:
1 | sudo yum install openresty-resty |
源代码编译安装
安装所需依赖包
安装编译环境
1 | yum install -y make cmake gcc gcc-c++ autoconf automake libpng-devel libjpeg-devel zlib libxml2-devel ncurses-devel bison libtool-ltdl-devel libiconv libmcrypt mhash mcrypt pcre-devel openssl-devel freetype-devel libcurl-devel lua-devel readline-devel curl wget |
下载最新版源码
下载最新版源码
1 | wget https://openresty.org/download/openresty-1.17.8.2.tar.gz |
下载缓存插件
1 | wget http://labs.frickle.com/files/ngx_cache_purge-2.3.tar.gz |
编译OpenResty
选择需要的插件启用, –with-Components 激活组件,–without 则是禁止组件 ,–add-module是安装第三方模块
1 | ./configure --prefix=/usr/local/openresty --with‐luajit --without‐http_redis2_module --with-http_stub_status_module --with-http_v2_module --with-http_gzip_static_module --with-http_sub_module --add-module=/usr/local/openresty/modules/ngx_cache_purge-2.3 #配置缓存插件的源码路径 |
出现如下界面表示编译成功
安装OpenResty
1 | gmake && gmake install |
环境设置
1 | vi /etc/profile ##加入path路径 |
查看环境
1 | nginx -v |
查看安装的组件
1 | nginx -V |
运行测试
创建Nginx配置文件
我们在 conf 目录下创建一个 nginx.conf 文件 代码如下
1 | worker_processes 1; |
如果你熟悉 nginx 的配置,应该对以上代码就很熟悉。这里我们将 html 代码直接写在了配置文件中。
启动 OpenResty
1 | nginx -c /usr/local/openresty/nginx/conf/nginx.conf |
接下来我们可以使用 curl 来测试是否能够正常范围
1 | [root@localhost ~]# curl http://localhost:9000/ |
我们在配置文件写的 html 已正常输出。
OpenResty 命令详解
openresty -h | -?
含义:查看OpenResty的帮助,可以得知当前的版本号以及全部指令的使用方式。
1 | [root@localhost bin]# ./openresty -h |
openresty -v
含义:查看当前OpenResty的版本
1 | [root@localhost bin]# ./openresty -v |
openresty -V
含义:查看当前OpenResty的编译信息
1 | [root@localhost bin]# ./openresty -V |
openresty -t | -T
含义:检查当前nginx.conf文件的语法错误。运行这个命令只是去检查语法并不会去启动OpenResty。
1 | [root@localhost bin]# ./openresty -t |
openresty -s 信号
含义:使用”-s”选项,会主动想openrest的master进程发送信号,可以通过信号的类型来执行不同的操作。
1 | ./openresty -s stop |
Lua基础功能使用
hello world
创建 helloworld.lua
1 | ngx.exec("/item/get?id=1"); |
修改 nginx.conf
1 | location /helloworld { |
重启 nginx
1 | nginx -s reload |
验证
1 | { |
获取请求 uri 参数
获取url参数 ngx.var.arg_xx与ngx.req.get_uri_args[“xx”]两者都是为了获取请求uri中的参数
修改nginx.conf
1 | location /print_param { |
输出结果
1 | curl '127.0.0.1/print_param?a=1&b=2%26' -d 'c=3&d=4%26' |
从这个例子中,我们可以很明显看到两个函数 ngx.req.get_uri_args
、ngx.req.get_post_args
获取数据来源是有明显区别的,前者来自 uri 请求参数,而后者来自 post 请求内容。
传递请求 uri 参数
当我们可以获取到请求参数,自然是需要这些参数来完成业务控制目的。大家都知道,URI 内容传递过程中是需要调用 ngx.encode_args 进行规则转义。
修改nginx.conf
1 | location /test { |
输出结果:
1 | curl '127.0.0.1/test' |
与我们预期是一样的。
获取请求类型
修改nginx.conf
1 | location /lua_request{ |
lua_request.lua
vim /usr/example/lua/lua_request.lua ,添加一下代码:
1 | local arg = ngx.req.get_uri_args() |
在上述例子中有以下的api
- ngx.req.get_uri_args 获取在uri上的get类型参数,返回的是一个table类型的数据结构。
- ngx.req.read_body 读取body,这在解析body之前,一定要先读取body。
- ngx.req.get_post_args 获取form表单类型的参数,返回结果是一个table类型的数据。
使用curl模拟请求
curl ‘http://116.196.177.123/lua_request?a=323&b=ss’ -d ‘c=12w&d=2se3’
返回的结果:
1 | [GET ] key:b v:ss |
获取请求头
修改lua_request.lua
在原有的代码基础上,再添加一下代码
1 | local headers = ngx.req.get_headers() |
重新加载nginx -s reload
使用curl模拟请求
curl ‘http://116.196.177.123/lua_request?a=323&b=ss’ -d ‘c=12w&d=2se3’
1 | [GET ] key:b v:ss |
获取http的其他方法
在原有的代码基础上,再添加一下代码
1 | ngx.say("ngx.req.http_version : ", ngx.req.http_version(), "<br/>") |
重新加载nginx -s reload
使用curl模拟请求
curl ‘http://116.196.177.123/lua_request?a=323&b=ss’ -d ‘c=12w&d=2se3’
1 | //.... |
输出响应
修改nginx.conf
1 | location /lua_response{ |
lua_response.lua
lua_response.lua 添加一下代码
1 | ngx.header.a="1" |
上述代码中有以下api:
- ngx.header 向响应头输出内容
- ngx.say 输出响应体
- ngx.print输出响应体
- ngx.exit 指定http状态码退出
使用curl模拟请求
curl ‘http://116.196.177.123/lua_response’ ,获取的响应体如下:
1 | hello |
日志输出
修改nginx.conf
1 | location /lua_log{ |
lua_log.lua
lua_log.lua 加上以下代码
1 | local log="i'm log" |
重新加载配置文件nginx -s reload
测试
重新加载配置文件nginx -s reload
打开nginx 的logs目录下的error.log 文件:
tail -fn 1000 /usr/servers/nginx/logs/error.log
日志级别
可以看到在日志文件中已经输出了日志,这种日志主要用于记录和测试。
- ngx.STDERR – 标准输出
- ngx.EMERG – 紧急报错
- ngx.ALERT – 报警
- ngx.CRIT – 严重,系统故障,触发运维告警系统
- ngx.ERR – 错误,业务不可恢复性错误
- ngx.WARN – 告警,业务中可忽略错误
- ngx.NOTICE – 提醒,业务比较重要信息
- ngx.INFO– 信息,业务琐碎日志信息,包含不同情况判断等
- ngx.DEBUG – 调试
Lua多级缓存架构
Redis集群配置
安装配置
lua_resty_redis 它是一个基于cosocket API的为ngx_lua模块提供Lua redis客户端的驱动。
lua_resty_redis模块地址:https://github.com/cuiweixie/lua-resty-redis-cluster
将
lua-resty-redis-cluster/lib/redis_slot.c
拷贝到openresty/lualib
目录下将
lua-resty-redis-cluster/lib/resty/rediscluster.lua
拷贝到openresty/lualib/resty
编译redis_slot
需要将edis_slot.c编译成libredis_slot.so才能使用
cd 到
openresty/lualib
目录下 执行下面命令进行编译
1 | gcc redis_slot.c -fPIC -shared -o libredis_slot.so |
如果出现
redis_slot.c:1:17: 致命错误:lua.h:没有那个文件或目录
找不到lua.h等文件
yum install lua-devel
下载一个依赖
连接Redis集群封装
创建redis.lua的文件
1 | --操作Redis集群,封装成一个模块 |
Mysql配置
安装配置
因为安装OpenResty是已经自带了mysql所以无需在安装mysql模块
查看mysql模块
1 | [root@localhost resty]# /usr/local/openresty/lualib/resty |
已经存在mysql模块
连接Mysql封装
创建mysql.lua的文件
1 | --MySQL查询操作,封装成一个模块 |
创建多级缓存脚本
创建activity.lua
1 | --多级缓存流程操作 |
前置工作
安装mysql
参考【Docker安装MySQL】
安装Redis集群
参考【Redis安装部署】
配置docker-compose
编辑docker-compose.yml
1 | version: '2' |
执行
docker-compose up -d
启动mysql以及redis集群环境
执行集群搭建命令搭建redis集群,参考【Redis安装部署】
执行初始化sql
在刚刚创建的mysql中执行初始化sql
1 | /* |
OpenResty配置
配置lua脚本
将刚刚创建的
redis.lua
和mysql.lua
以及activity.lua
复制到/usr/local/openresty/nginx/lua
目录下
1 | [root@localhost local]# cd openresty/nginx/lua |
nginx配置文件
修改nginx.conf
vi conf/nginx.conf
1 |
|
注意:
lua_package_path "/usr/local/openresty/nginx/lua/?.lua;;";
后面是两个分号,只有一个分号将导致加载默认lua库失败。
启动nginx
指定配置文件启动
如果nginx没有启动执行如下命令启动
1 | nginx -c /usr/local/openresty/nginx/conf/nginx.conf |
重新加载配置文件
如果nginx已经启动只是更改了配置文件,执行如下命令重新加载新的配置
1 | nginx -s reload |
测试
访问测试
访问 http://192.168.64.143/act?id=1
能够访问成功说明已经搭建成功
检查Redis数据
检查redis也存在数据
Lua发送MQ消息
准备工作
安装RabbitMQ环境
因为lua 连接rabbitmq 需要使用STOMP插件的61613端口,普通RabbitMQ安装
docker 运行rabbitmq
1 | docker run -d -p 5672:5672 -p 15672:15672 -p 61613:61613 --name rabbitmq rabbitmq:management |
-p 15672:15672 将在容器15672接口的控制台map到本地的8080接口
-p 5672:5672 暴漏MQ默认服务端口
-p 61613:61613 暴露STOMP插件的默认服务端口
安装STOMP插件
获得容器的bash
1 | docker exec -ti rabbitmq /bin/bash |
手动安装
rabbitmq_stomp
插件
1 | rabbitmq-plugins enable rabbitmq_stomp |
登录验证
RabbitMQ控制台查看显示STOMP插件在61613端口启动
安装RabbitMQ模块
- 在release页面https://github.com/wingify/...下载项目源码,截止到目前最新版为
v0.1
- 将压缩包中目录
lua-resty-rabbitmqstomp-0.1\lib\resty
下的所有lua文件复制到OpenResty的lualib/resty
目录
安装JWT模块
在release页面https://github.com/SkyLothar/...下载项目源码,截止到目前最新版为
v0.1.11
将压缩包中目录
lua-resty-jwt-0.1.11/lib/resty/
下的所有lua文件复制到OpenResty的lualib/resty
目录
测试验证
修改
nginx.conf
验证是否生效
1 | worker_processes 1; |
访问测试
1 | curl localhost:8080 |
1 | { |
验证通过,jwt模块安装完毕
JWT-TOKEN再封装
创建
token.lua
的lua文件
1 | --依赖jwt库 |
RabbitMQ再封装
创建
mq.lua
的lua文件
1 | --设置JSON响应 |
监听RabbitMQ队列
创建RabbitMQ配置类
1 |
|
创建监听器
监听队列red.queue
1 |
|
OpenResty配置
配置lua脚本
将刚刚创建的
token.lua
和mq.lua
复制到/usr/local/openresty/nginx/lua
目录下
1 | [root@localhost local]# cd openresty/nginx/lua |
nginx配置文件
修改nginx.conf
vi conf/nginx.conf
1 | #user nobody; |
注意:
lua_package_path "/usr/local/openresty/nginx/lua/?.lua;;";
后面是两个分号,只有一个分号将导致加载默认lua库失败。
测试
生成token
浏览器访问
http://192.168.64.143/generate
生成TOKEN
1 | [root@localhost lua]# curl http://192.168.64.143/generate |
Postman 请求队列
运行项目测试
启动MQ监听服务,点击Postman 进行队列测试
1 | 2020-10-28 17:41:53.178 INFO 9004 --- [ntContainer#0-1] c.heima.rabbitmq.consumer.QueueConsumer : 接收到消息:{"user":"baiyp"} |