chaoz的杂货铺

生命有息、学无止境、折腾不止

0%

OpenResty从入门到实战笔记

安装

官方教程

ubuntu 实践:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# import our GPG key:
wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add -

# for installing the add-apt-repository command
# (you can remove this package and its dependencies later):
sudo apt-get -y install software-properties-common

# add the our official APT repository:
sudo add-apt-repository -y "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main"

# to update the APT index:
sudo apt-get update

sudo apt-get install openresty

官网直通车

中文官网主页

OpenResty 官方 Yum 资源库提供下面的 RPM 包

NginxLuaModule 源码分析-某不知名大佬有道云笔记

开始

第一个实例

1
2
3
resty -e 'print("hello, world")'

hello, world

准备目录布局

1
2
3
4
5
6
先实践一个简单的
mkdir ~/work
cd ~/work
mkdir logs/ conf/

我们还为日志文件创建了 logs / 目录,为配置文件创建了 conf / 。

logs/error.log

1
vim 造一个空文件

conf/nginx.conf

下面是不同的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
官方:

worker_processes 1;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
server {
listen 8080;
location / {
default_type text/html;
content_by_lua_block {
ngx.say("<p>hello, world</p>")
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
极客时间:

events {
worker_connections 1024;
}

http {
server {
listen 8080;
location / {
content_by_lua '
ngx.say("hello, world")
';
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
极客时间:

pid logs/nginx.pid;
events {
worker_connections 1024;
}

http {
server {
listen 8080;
location / {
content_by_lua_file lua/hello.lua;
}
}
}
}

启动 Nignx 服务器

/usr/local/openresty (this is the default)

确定路径正确

环境变量设置:

1
2
PATH=/usr/local/openresty/nginx/sbin:$PATH
export PATH

启动:

1
2
3
4
5
nginx -p `pwd`/ -c conf/nginx.conf

curl http://localhost:8080/

<p>hello, world</p>

### 结构介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/usr/local/openresty/lualib$ ls

drwxr-xr-x 5 root root 4096 6月 17 22:04 ./
drwxr-xr-x 10 root root 4096 6月 17 22:04 ../
-rw-r--r-- 1 root root 39464 5月 17 07:33 cjson.so
-rw-r--r-- 1 root root 5848 5月 17 07:33 librestysignal.so
drwxr-xr-x 3 root root 4096 6月 17 22:04 ngx/
drwxr-xr-x 2 root root 4096 6月 17 22:04 redis/
drwxr-xr-x 8 root root 4096 6月 17 22:04 resty/
-rw-r--r-- 1 root root 1374 5月 17 07:33 tablepool.lua

ngx: 存放的是 lua-resty-core 这个官方项目中的 Lua 代码,里面都是基于 FFI 重新实现的 OpenResty API

resty: 存放的则是各种 lua-resty-* 项目包含的 Lua 代码

PS:
早期的 OpenResty 同时带有 Lua 和 LuaJIT,你可以通过编译选项,来决定使用 Lua 还是 LuaJIT。不过到了现在,Lua 逐渐被淘汰,就只支持更高性能的 LuaJIT 了。

OpenResty 项目概览

OpenResty 包含了 68 个公开的项目

NGINX C 模块

OpenResty 中一共包含了 20 多个 C 模块,我们在本节最开始使用的 openresty -V 中,也可以看到这些 C 模块:

–add-module=后面跟着的,就是 OpenResty 的 C 模块。其中,最核心的就是 lua-nginx-module 和 stream-lua-nginx-module,前者用来处理七层流量,后者用来处理四层流量。

这些 C 模块中,有些是需要特别注意的,虽然默认编译进入了 OpenResty,但并不推荐使用。

lua 是未来,其他项目大多处于疏于维护状态。

lua-resty- 周边库

OpenResty 官方仓库中包含 18 个 lua-resty-* 库,涵盖 Redis、MySQL、memcached、websocket、dns、流量控制、字符串处理、进程内缓存等常用库。

了官方自带的之外,还有更多的第三方库。

自己维护的 LuaJIT 分支

LuaJIT 分支

OpenResty 除了维护自己的 OpenSSL patch 外,还维护了自己的 LuaJIT 分支。

相对于 Lua,LuaJIT 增加了不少独有的函数,这些函数非常重要

测试框架

Perl 语言来开发的 test-nginx:OpenResty 官方的所有 C 模块和 lua-resty 库的测试案例,都是由 test-nginx 驱动的。

可以模拟慢速的网络的 mockeagain :让程序每次只读写一个字节。

调试工具链

动态追踪技术漫谈

使用 systemtap 最大的优势,便是实现活体分析,同时对目标程序完全无侵入。

openresty-systemtap-toolkit

stapxx

打包相关

openresty-packaging

home-brew

工程化工具

Perl 开发的 OpenResty 和 NGINX 的工具集 openresty-devel-utils

lj-releng 是一个简单有效的 LuaJIT 代码检测工具,类似 luacheck,可以找出全局变量等潜在的问题。

reindex 从名字来看是重建索引的意思,它其实是格式化 test-nginx 测试案例的工具,可以重新排列测试案例的编号,以及去除多余的空白符。reindex 可以说是 OpenResty 开发者每天都会用到的工具之一。

opsboy 也是一个深藏不露的项目,主要用于自动化部署。OpenResty 每次发布版本前,都会在 AWS EC2 集群上做完整的回归测试,详细的文档你可以参考官方文档,而这个回归测试正是由 opsboy 来部署和驱动的。opsboy 是一个用 Perl 实现的 DSL(领域特定语言)。

OpenResty 中用到的 NGINX 知识

NGINX 配置

NGINX 通过配置文件来控制自身行为,它的配置可以看作是一个简单的 DSL。NGINX 在进程启动的时候读取配置,并加载到内存中。如果修改了配置文件,需要你重启或者重载 NGINX,再次读取后才能生效。

NGINX 支持的功能,OpenResty 并不一定支持,需要看 OpenResty 的版本号。

MASTER-WORKER 模式

执行阶段

执行阶段也是 NGINX 重要的特性,与 OpenResty 的具体实现密切相关。NGINX 有 11 个执行阶段.

二进制热升级

热升级通过向旧的 Master 进程发送 USR2 和 WINCH 信号量来完成。对于这两步,前者的作用,是启动新的 Master 进程;后者的作用,是逐步关闭 Worker 进程。

执行完这两步后,新的 Master 和新的 Worker 就已经启动了。不过此时,旧的 Master 并没有退出。不退出的原因也很简单,如果你需要回退,依旧可以给旧的 Master 发送 HUP 信号量。当然,如果你已经确定不需要回退,就可以给旧 Master 发送 KILL 信号量来退出。

NGINX 官方文档

官方文档

OpenResty 的作者 NGINX 教程

管理第三方包工具

OPM

OpenResty 自带的包管理器

1
2
3
4
opm search ‘包名’

返回:
GitHub ID / repo name

OPM 官网

LUAROCKS

LuaRocks 官网

1
luarocks search "包名"

我们可以到 LuaRocks 的网站上,去查看这个包的详细信息,这里面包含了作者、License、GitHub 地址、下载次数、功能简介、历史版本、依赖等。和 OPM 不同的是,LuaRocks 并没有直接使用 GitHub 的用户信息,而是需要开发者单独在 LuaRocks 上进行注册。

开源的 API 网关项目 Kong

Kong 项目地址

AWESOME-RESTY

OPM 和 LuaRocks 都不支持私有包。

AWESOME-RESTY 项目地址

库 以及一些 项目

lua-resty-requests

它是人类更友好的 HTTP 访问库,接口风格与 Python 中大名鼎鼎的 Requests 一致。

python-requests

OpenResty 的官方网站项目: openresty.org

基于 systemtap 的扩展

GDB 的工具集

简单的 API - ngx.base64_decode

lua-resty-core

在 Lua 中,你可以用 Lua C API 来调用 C 函数,而在 LuaJIT 中还可以使用 FFI。对 OpenResty 而言:

在核心的 lua-nginx-module 中,调用 C 函数的 API,都是使用 Lua C API 来完成的;
而在 lua-resty-core 中,则是把 lua-nginx-module 已有的部分 API,使用 FFI 的模式重新实现了一遍。

Lua CFunction

能够被 Lua 调用的 C 函数来说,它的接口必须遵循 Lua 要求的形式,也就是 typedef int (lua_CFunction)(lua_State L)。

它包含的参数是 lua_State 类型的指针 L ;它的返回值类型是一个整型,表示返回值的数量,而非返回值自身。

for example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static int
ngx_http_lua_ngx_decode_base64(lua_State *L)
{
ngx_str_t p, src;

src.data = (u_char *) luaL_checklstring(L, 1, &src.len);

p.len = ngx_base64_decoded_length(src.len);

p.data = lua_newuserdata(L, p.len);

if (ngx_decode_base64(&p, &src) == NGX_OK) {
lua_pushlstring(L, (char *) p.data, p.len);

} else {
lua_pushnil(L);
}

return 1;
}

LuaJIT FFI

FFI 的交互部分是用 Lua 实现的,这部分代码可以被 JIT 跟踪到,并进行优化;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

ngx.decode_base64 = function (s)
local slen = #s
local dlen = base64_decoded_length(slen)

local dst = get_string_buf(dlen)
local pdlen = get_size_ptr()
local ok = C.ngx_http_lua_ffi_decode_base64(s, slen, dst, pdlen)
if ok == 0 then
return nil
end
return ffi_string(dst, pdlen[0])
end

ngx_http_lua_ffi_ ,是用 FFI 来处理 NGINX http 请求的 Lua 函数;
ngx_http_lua_ngx_ ,是用 Cfunction 来处理 NGINX http 请求的 Lua 函数;
其他 ngx_ 和 lua_ 开头的函数,则分别属于 NGINX 和 Lua 的内置函数。

OpenResty® C 代码风格指南:对于有意学习 OpenResty 的 C 代码并提交 PR 的开发者来说,这是必备的一篇文档。

LuaJIT 官方的教程

LuaJIT 文档

LuaJIT FFI GC

LuaJIT 只负责由自己分配的资源;而 ffi.C 是 C 库的命名空间,所以,使用 ffi.C 分配的空间不由 LuaJIT 负责,需要你自己手动释放。

如果要在 OpenResty 中申请大块的内存,我更推荐你用 ffi.C.malloc 而不是 ffi.new。原因也很明显:

  • ffi.new 返回的是一个 cdata,这部分内存由 LuaJIT 管理;
  • LuaJIT GC 的管理内存是有上限的,OpenResty 中的 LuaJIT 并未开启 GC64 选项,所以单个 worker 内存的上限只有 2G。一旦超过 LuaJIT 的内存管理上限,就会导致报错。

在使用 FFI 的时候,我们还需要特别注意内存泄漏的问题。周边测试和调试工具.

lua-resty-core

现在新的 API,都通过 FFI 的方式,在 lua-resty-core 仓库中实现。

LuaJIT分支和标准Lua

LuaJIT 在 OpenResty 整体架构中的位置:

标准 Lua 和 LuaJIT 的关系

标准 Lua 和 LuaJIT 是两回事儿,LuaJIT 只是兼容了 Lua 5.1 的语法。

LuaJIT 的性能优势

标准 Lua 出于性能考虑,也内置了虚拟机,所以 Lua 代码并不是直接被解释执行的,而是先由 Lua 编译器编译为字节码(Byte Code),然后再由 Lua 虚拟机执行。

LuaJIT 的运行时环境,除了一个汇编实现的 Lua 解释器外,还有一个可以直接生成机器代码的 JIT 编译器。开始的时候,LuaJIT 和标准 Lua 一样,Lua 代码被编译为字节码,字节码被 LuaJIT 的解释器解释执行。

所谓 LuaJIT 的性能优化,本质上就是让尽可能多的 Lua 代码可以被 JIT 编译器生成机器码,而不是回退到 Lua 解释器的解释执行模式。

喜欢这篇文章?打赏一下作者吧!

欢迎关注我的其它发布渠道