我的博客: https://www.luozhiyun.com/archives/217

想要学好 OpenResty,你必须理解下面 8 个重点:

  • 同步非阻塞的编程模式;
  • 不同阶段的作用;
  • LuaJIT 和 Lua 的不同之处;
  • OpenResty API 和周边库;
  • 协程和 cosocket;
  • 单元测试框架和性能测试工具;
  • 火焰图和周边工具链;
  • 性能优化。

你不应该使用任何 Lua 世界的库来解决上述问题,而是应该使用 cosocket 的 lua-resty-* 库。Lua 世界的库很可能会带来阻塞,让原本高性能的服务,直接下降几个数量级。

OpenResty阶段

和nginx一样,都有阶段的概念,并且每个阶段都有自己不同的作用:

  • set_by_lua,用于设置变量;
  • rewrite_by_lua,用于转发、重定向等;
  • access_by_lua,用于准入、权限等;
  • content_by_lua,用于生成返回内容;
  • header_filter_by_lua,用于应答头过滤处理;
  • body_filter_by_lua,用于应答体过滤处理;
  • log_by_lua,用于日志记录。

OpenResty 的 API 是有阶段使用限制的。每一个 API 都有一个与之对应的使用阶段列表,如果你超范围使用就会报错。

具体的API可以查阅文档:https://github.com/openresty/lua-nginx-module

跨阶段的变量

有些情况下,我们需要的是跨越阶段的、可以读写的变量。

OpenResty 提供了 ngx.ctx,来解决这类问题。它是一个 Lua table,可以用来存储基于请求的 Lua 数据,且生存周期与当前请求相同。我们来看下官方文档中的这个示例:

location /test {
rewrite_by_lua_block {
ngx.ctx.foo = 76
}
access_by_lua_block {
ngx.ctx.foo = ngx.ctx.foo + 3
}
content_by_lua_block {
ngx.say(ngx.ctx.foo)
}
}

最终输出79

包管理

OPM

OPM(OpenResty Package Manager)是 OpenResty 自带的包管理器

opm search lua-resty-http

LUAROCKS

不同于 OPM 里只包含 OpenResty 相关的包,LuaRocks 里面还包含 Lua 世界的库。

luarocks search lua-resty-http

我们还可以去网站上看包的详细信息:https://luarocks.org/modules/pintsized/lua-resty-http,这里面包含了作者、License、GitHub 地址、下载次数、功能简介、历史版本、依赖等。

AWESOME-RESTY

awesome-resty 这个项目,就维护了几乎所有 OpenResty 可用的包,并且都分门别类地整理好了。

nginx

nginx命令行

  1. 格式:nginx -s reload
  2. 帮助: -? -h
  3. 使用指定的配置文件: -c
  4. 指定配置指令:-g
  5. 指定运行目录:-p
  6. 发送信号:-s (stop / quit / reload / reopen)
  7. 测试配置文件是否有语法错误:-t -T
  8. 打印nginx的版本信息、编译信息等:-v -V

nginx信号

因为nginx是多进程的程序:

所以信号分为Master进程信号和worker进程信号。

Master进程:

  • 监控worker进程: CHILD ,如果worker进程出现了故障而挂掉了,那么master可以通过这个信号将worker进程迅速拉起
  • 管理worker进程:
    • TERM,INT:表示立刻停止nginx进程
    • QUIT:表示优雅停止nginx进程
    • HUP:重载配置文件
    • USR1:表示重新打开日志文件
    • USR2、WINCH:专门针对热部署使用

worker进程:与master进程命令一一对应

  • TERM,INT:表示立刻停止nginx进程
  • QUIT:表示优雅停止nginx进程
  • USR1:表示重新打开日志文件
  • WINCH:专门针对热部署使用

Nginx命令行,相当于直接向master进程发送命令

  • reload:HUP
  • reopen:USR1
  • stop:TERM
  • quit:QUIT

openresty入门

  1. 创建工作目录
mkdir geektime
cd luoluo
mkdir logs/ conf/
  1. 在conf里面添加nginx.conf文件
events {
worker_connections 1024;
}
http {
server {
listen 8080;
location / {
content_by_lua '
ngx.say("hello, world")
';
}
}
}
  1. 启动openresty服务
openresty -p `pwd` -c conf/nginx.conf
指定运行目录:-p
使用指定的配置文件: -c

openresty后面跟随的命令和nginx是一样的

独立出Lua代码

  1. 我们先在luo的工作目录下,创建一个名为lua的目录
$ mkdir lua
$ cat lua/hello.lua
ngx.say("hello, world")
  1. 修改 nginx.conf 的配置
pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
server {
listen 8080;
location / {
content_by_lua_file lua/hello.lua;
}
}
}
}

这里把 content_by_lua_block 改为 content_by_lua_file

3. 重启OpenResty

$ sudo kill -HUP `cat logs/nginx.pid`

我这里使用了发送信号的方式 -HUP表示重载配置文件

NYI

NYI,全称为 Not Yet Implemented。LuaJIT 中 JIT 编译器的实现还不完善,有一些原语它还无法编译,因为这些原语实现起来比较困难,再加上 LuaJIT 的作者目前处于半退休状态。这些原语包括常见的 pairs() 函数、unpack() 函数、基于 Lua CFunction 实现的 Lua C 模块等。这样一来,当 JIT 编译器在当前代码路径上遇到它不支持的操作时,便会退回到解释器模式。这些不能编译的函数称为NYI。

NYI函数都在:http://wiki.luajit.org/NYI

在开发中,可以先去找OpenResty的API:https://github.com/openresty/lua-nginx-module

例如,NYI 列表中 string 库的几个函数:

其中,string.byte 对应的能否被编译的状态是 yes,表明可以被 JIT。

string.char 对应的编译状态是 2.1,表明从 LuaJIT 2.1 开始支持。我们知道,OpenResty 中的 LuaJIT 是基于 LuaJIT 2.1 的,所以你也可以放心使用。

string.dump 对应的编译状态是 never,即不会被 JIT,会退回到解释器模式。

string.find 对应的编译状态是 2.1 partial,意思是从 LuaJIT 2.1 开始部分支持,后面的备注中写的是 只支持搜索固定的字符串,不支持模式匹配。

如何检测函数

LuaJIT 自带的 jit.dump 和 jit.v 模块。它们都可以打印出 JIT 编译器工作的过程。前者会输出非常详细的信息,可以用来调试 LuaJIT 本身;后者的输出比较简单,每行对应一个 trace,通常用来检测是否可以被 JIT。

使用resty:

$resty -j v -e

其中,resty 的 -j 就是和 LuaJIT 相关的选项;后面的值为 dump 和 v,就对应着开启 jit.dump 和 jit.v 模式。

如下例子:

$resty -j v -e 'local t = {}
for i=1,100 do
t[i] = i
end for i=1, 1000 do
for j=1,1000 do
for k,v in pairs(t) do
--
end
end
end'

上面的pairs是NYI的语句,不能被JIT,所以结果里面就会显示:

 [TRACE   1 (command line -e):2 loop]
[TRACE --- (command line -e):7 -- NYI: bytecode 72 at (command line -e):8]

shdict get API

shared dict(共享字典)是基于 NGINX 共享内存区的 Lua 字典对象,它可以跨多个 worker 来存取数据,一般用来存放限流、限速、缓存等数据。

例子:

http {
lua_shared_dict dogs 10m;
server {
location /demo {
content_by_lua_block {
local dogs = ngx.shared.dogs
dogs:set("Jim", 8)
local v = dogs:get("Jim")
ngx.say(v)
}
}
}
}

简单说明一下,在 Lua 代码中使用 shared dict 之前,我们需要在 nginx.conf 中用 lua_shared_dict 指令增加一块内存空间,它的名字是 dogs,大小为 10M。

也可以使用resty CLI:

$ resty --shdict 'dogs 10m' -e 'local dogs = ngx.shared.dogs
dogs:set("Jim", 8)
local v = dogs:get("Jim")
ngx.say(v)'

共享内存使用阶段

context: set_by_lua*,
rewrite_by_lua*,
access_by_lua*,
content_by_lua*,
header_filter_by_lua*,
body_filter_by_lua*,
log_by_lua*,
ngx.timer.*,
balancer_by_lua*,
ssl_certificate_by_lua*,
ssl_session_fetch_by_lua*,
ssl_session_store_by_lua*

可以看出, init 和 init_worker 两个阶段不在其中,也就是说,共享内存的 get API 不能在这两个阶段使用。

get函数返回多个值

value, flags = ngx.shared.DICT:get(key)

正常情况下:

第一个参数value 返回的是字典中 key 对应的值;但当 key 不存在或者过期时,value 的值为 nil。

第二个参数 flags 就稍微复杂一些了,如果 set 接口设置了 flags,就返回,否则不返回。

一旦 API 调用出错,value 返回 nil,flags 返回具体的错误信息。

cosocket

cosocket 是把协程和网络套接字的英文拼在一起形成的,即 cosocket = coroutine + socket。

遇到网络 I/O 时,它会交出控制权(yield),把网络事件注册到 Nginx 监听列表中,并把权限交给 Nginx;当有 Nginx 事件达到触发条件时,便唤醒对应的协程继续处理(resume),最终实现了非阻塞网络 I/O。

API

  • 创建对象:ngx.socket.tcp。
  • 设置超时:tcpsock:settimeout 和 tcpsock:settimeouts。
  • 建立连接:tcpsock:connect。
  • 发送数据:tcpsock:send。
  • 接受数据:tcpsock:receive、tcpsock:receiveany 和 tcpsock:receiveuntil。
  • 连接池:tcpsock:setkeepalive。
  • 关闭连接:tcpsock:close。

上下文:

rewrite_by_lua*,
access_by_lua*,
content_by_lua*,
ngx.timer.*,
ssl_certificate_by_lua*,
ssl_session_fetch_by_lua*_

cosocket API 在 set_by_lua, log_by_lua, header_filter_by_lua* 和 body_filter_by_lua* 中是无法使用的。init_by_lua* 和 init_worker_by_lua* 中暂时也不能用。

与这些API相应的Nginx指令:

  • lua_socket_connect_timeout:连接超时,默认 60 秒。
  • lua_socket_send_timeout:发送超时,默认 60 秒。
  • lua_socket_send_lowat:发送阈值(low water),默认为 0。
  • lua_socket_read_timeout: 读取超时,默认 60 秒。
  • lua_socket_buffer_size:读取数据的缓存区大小,默认 4k/8k。
  • lua_socket_pool_size:连接池大小,默认 30。
  • lua_socket_keepalive_timeout:连接池 cosocket 对象的空闲时间,默认 60 秒。
  • lua_socket_log_errors:cosocket 发生错误时,是否记录日志,默认为 on。

例子

$ resty -e 'local sock = ngx.socket.tcp()
sock:settimeout(1000) -- one second timeout
local ok, err = sock:connect("www.baidu.com", 80)
if not ok then
ngx.say("failed to connect: ", err)
return
end local req_data = "GET / HTTP/1.1\r\nHost: www.baidu.com\r\n\r\n"
local bytes, err = sock:send(req_data)
if err then
ngx.say("failed to send: ", err)
return
end local data, err, partial = sock:receive()
if err then
ngx.say("failed to receive: ", err)
return
end sock:close()
ngx.say("response is: ", data)'
  • 首先,通过 ngx.socket.tcp() ,创建 TCP 的 cosocket 对象,名字是 sock。
  • 然后,使用 settimeout() ,把超时时间设置为 1 秒。注意这里的超时没有区分 connect、receive,是统一的设置。
  • 接着,使用 connect() 去连接指定网站的 80 端口,如果失败就直接退出。
  • 连接成功的话,就使用 send() 来发送构造好的数据,如果发送失败就退出。
  • 发送数据成功的话,就使用 receive() 来接收网站返回的数据。这里 receive() 的默认参数值是 l,也就是只返回第一行的数据;如果参数设置为了a,就是持续接收数据,直到连接关闭;
  • 最后,调用 close() ,主动关闭 socket 连接。

超时时间

在上面settimeout() ,作用是把连接、发送和读取超时时间统一设置为一个值。如果要想分开设置,就需要使用 settimeouts() 函数:

sock:settimeouts(1000, 2000, 3000)

接收数据

receive 接收指定大小:

local data, err, partial = sock:receiveany(10240)

这段代码就表示,最多只接收 10K 的数据。

关于 receive,还有另一个很常见的用户需求,那就是一直获取数据,直到遇到指定字符串才停止。

ocal reader = sock:receiveuntil("\r\n")

 while true do
local data, err, partial = reader(4)
if not data then
if err then
ngx.say("failed to read the data stream: ", err)
break
end ngx.say("read done")
break
end
ngx.say("read chunk: [", data, "]")
end

这段代码中的 receiveuntil 会返回 \r\n 之前的数据,并通过迭代器每次读取其中的 4 个字节。

连接池

没有连接池的话,每次请求进来都要新建一个连接,就会导致 cosocket 对象被频繁地创建和销毁,造成不必要的性能损耗。

在你使用完一个 cosocket 后,可以调用 setkeepalive() 放到连接池中:

local ok, err = sock:setkeepalive(2 * 1000, 100)
if not ok then
ngx.say("failed to set reusable: ", err)
end

这段代码设置了连接的空闲时间为 2 秒,连接池的大小为 100。在调用 connect() 函数时,就会优先从连接池中获取 cosocket 对象。

需注意:

  1. 不能把发生错误的连接放入连接池
  2. 第二,要搞清楚连接的数量。连接池是 worker 级别的,每个 worker 都有自己的连接池。所以,如果你有 10 个 worker,连接池大小设置为 30,那么对于后端的服务来讲,就等于有 300 个连接。

定时任务

OpenResty 的定时任务可以分为下面两种:

  • ngx.timer.at,用来执行一次性的定时任务;
  • ngx.time.every,用来执行固定周期的定时任务。但是在启动了一个 timer 之后,你就再也没有机会来取消这个定时任务了

如下:

init_worker_by_lua_block {
local function handler()
local sock = ngx.socket.tcp()
local ok, err = sock:connect(“www.baidu.com", 80)
end
local ok, err = ngx.timer.at(0, handler)
}

启动了一个延时为 0 的定时任务。它启动了回调函数 handler,并在这个函数中,用 cosocket 去访问一个网站

OpenResty学习指南(一)的更多相关文章

  1. OpenResty学习指南(二)

    我的个人博客:https://www.luozhiyun.com/ 数据结构table table并没有区分开数组.哈希.集合等概念,而是揉在了一起. local color = {first = & ...

  2. Civil 3D API二次开发学习指南

    Civil 3D构建于AutoCAD 和 Map 3D之上,在学习Civil 3D API二次开发之前,您至少需要了解AutoCAD API的二次开发,你可以参考AutoCAD .NET API二次开 ...

  3. 笔记——shell脚本学习指南

    <shell脚本学习指南>机械工业出版 ISBN 987-7-111-25504-8 第2章 2.4 初级陷阱 1.当今的系统,对#!这一行的长度限制从63到1024个字符都有,尽量不要超 ...

  4. 《Spring MVC学习指南》怎么样?答:书名具有很大的欺骗性

    2016年6月21日 最近,因为工作需要,我从网上买了一本<Spring MVC学习指南>,ISBN编号: 978-7-115-38639-7,定价:49.00元.此书是[美]Paul D ...

  5. [go语言学习指南]

    内部分享,根据自己的经验,收集汇总的go语言学习指南. 适合新手入门. 可以通过这里进行下载.

  6. Oracle学习指南

    Oracle学习指南 你走的那天,我决定不落泪,迎着风撑着眼帘用力不眨眼 创建数据库.创建用户.创建表空间.创建表.插入数据..... 1.用系统用户登录,任选系统用户 代码: >>sql ...

  7. 推荐10个很棒的AngularJS学习指南

    AngularJS 是非常棒的JS框架,能够创建功能强大,动态功能的Web app.AngularJS自2009发布以来,已经广泛应用于Web 开发中.但是对想要学习Angular JS 的人而言,只 ...

  8. 项目管理之道--纪我的新书《PMP项目管理认证学习指南(第4版)》出版并预祝大卖!

    新年伊始,我最新的项目管理书籍——<PMP项目管理认证学习指南(第4版)>也出版了,真是新年新气象啊!翻译英文书籍是一件任重道远的工作,除了要具备扎实的基本功,熟悉相关的领域外,还需要细致 ...

  9. Gulp学习指南之CSS合并、压缩与MD5命名及路径替换(转载)

    本文转载自: Gulp学习指南之CSS合并.压缩与MD5命名及路径替换

随机推荐

  1. 从头学pytorch(六):权重衰减

    深度学习中常常会存在过拟合现象,比如当训练数据过少时,训练得到的模型很可能在训练集上表现非常好,但是在测试集上表现不好. 应对过拟合,可以通过数据增强,增大训练集数量.我们这里先不介绍数据增强,先从模 ...

  2. WWDC2018 之 高性能 Auto Layout

    1. 关于 Auto Layout 的历史渊源 上世纪 90 年代,名叫 Cassowary的布局算法,通过将布局问题抽象成线性不等式,并分解成多个位置间的约束,解决了用户界面的布局问题. Apple ...

  3. CodeForces 796D

    不能一个一个bfs,要一起bfs #include<iostream> #include<vector> #include<cstdio> #include< ...

  4. CentOS 下 maven 安装

    获取maven安装包 wget http://mirrors.hust.edu.cn/apache/maven/maven-3/3.2.5/binaries/apache-maven-3.2.5-bi ...

  5. blackarch 安装指南

    建议直接  www.blackangle.cn/BlackArchGuide.htm v\:* {behavior:url(#default#VML);} o\:* {behavior:url(#de ...

  6. shell脚本配置maven

    #!/bin/bash # maven install mvnpath=/usr/local/maven # 不存在 if [ ! -d "$mvnpath" ]; then ec ...

  7. 从0开发3D引擎(二):准备预备知识

    大家好,本文介绍了开发3D引擎需要的预备知识,给出了相关的资源. 上一篇博文 从0开发3D引擎(一):开篇 了解Web 3D Web 3D的历史 目前Web 3D是基于WebGL这个Web端3D AP ...

  8. java序列化(二)

    上一篇我们简单的了解了java的序列化方法.可以想一下,如果有两个类,如果父类实现了序列化,子类没有实现序列化,子类在进行对象序列化读写时,父类和子类均被实现序列化.如果其中父类没有实现序列化,子类实 ...

  9. C# DataTable数据类型判断

    当我们从数据中获取到数据,一般会使用 DataTable 接收,然后会遍历每行数据.由于从数据库中读取的数据可能为空,比如我们的编译代码如下: foreach (DataRow datarow in ...

  10. JWT (一):认识 JSON Web Token

    JWT(一):认识 JSON WebToken JWT(二):使用 Java 实现 JWT 什么是 JWT? JSON Web Token(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑 ...