nginx 学习7-openresty

837 阅读24分钟

1.概述

openRestry

openresty.org/cn/linux-pa…

location /lua {
    default_type text/html;
    content_by_lua 'ngx.say("User-Agent:", ngx.req.get_headers()["User-Agent"])';
}

Openresty的主要组成

image.png

  1. Nginx
  2. Nginx官方模块
  3. Openresty工具
  4. Openresty第三方模块

Openresty第三方模块

  • lua语言模块
  • ngx_http_lua_module
  • ngx_stream_lua_module

运行机制

image.png

nginx框架

  • 事件驱动系统
  • HTTP框架
  • STREAM框架

nginx模块

  • ngx_http_lua_module
  • ngx_stream_lua_module
  • 其他第三方模块

ngx_http_lua_module

  • 纯Lua代码
  • 与Nginx交互的SDK
  • 配合 nginx.conf 和 lua指令使用

Openresty中的SDK

  • cosocket通讯: udp,tcp
  • 基于共享内存的字典shared.DICT
  • 定时器
  • 基于协程的并发编程
  • 获取客户端请求与响应的信息.
  • 修改客户端请求与响应,包括发送响应
  • 子请求

工具类

  • 正则表达式
  • 日志
  • 系统配置
  • 编解码
  • 时间

Openresty的使用要点

  • 不破坏Nginx的事件驱动体系,不使用任何会阻塞Nginx进程进行事件调度的方法。
  • 不调用会导致Nginx进程主动休眠的方法
  • 谨慎调用第三方库,若不是通过cosocket实现(例如Lua标准网络库或者第三方服务提供的SDK库),就无法融入Nginx的事件驱动体系中,那么对TCP消息的处理通常会导致Nginx进程进入Sleep状态
  • 不调用会长时间占用CPU的方法避免Lua代码块执行密集计算指令
  • 不破坏Nginx的低内存消耗优点,避免对每个请求分配过大内存。 对会导致分配内存的Lua SDK,谨慎分配内存。考虑用状态机多次分批处理
  • 理解Lua代码块如何嵌入Nginx中执行
  • 理解Lua SDK详细用法,及它们如何集成在Nginx中处理请求
  • 保持Lua代码的高效

2.OpenResty中的Nginx模块与 Lua模块

Openresty的Nginx模块: 核心模块

ndk_http_module # Openresty的基础模块,全名为Nginx Development Kit,顾名思议是一个开发工具包,为如ngx_http_lua_module等模块提供通用功能 ,通过-without-ngx_devel_kit_module禁用模块.
ngx_http_lua_module #Openresty提供HTTP服务lua编程能力的核心模块,在所有阶段、过滤、负载均衡等允许用lua语言处理请求通过--without-http lua module禁用模块
ngx_http_lua_upstream_module # 作为ngx http lua modulc模块的补充,为lua编程提供更多的关于upstream管理的API通过--without-http_lua upstream_module禁用模块
ngx_stream_lua_module # Openresty提供四层TCP/UDP服务lua编程能力的核心模块,允许用lua语言处理请求通过--without-stream lua module禁用模块

Openresty的Nginx模块: 反向代理模块

ngx_http_memc_module # content阶段的反向代理模块,上游为Memcached,相比Nginx官方的memcached模块 (仅提供GET命令),它提供了全部的命令 
ngx_http_redis2_module # content阶段的反向代理模块,上游为Redis服务。 
ngx_http_redis_module # content阶段的反向代理模块,上游为Redis服务。这两个redis模块皆为Nginx模块,使用nginx.conf语法,皆可被Lua语言版本的lua-resty-redis取代(它使用ngx_http_lua_module的tcp sdk)。 通过--without-http redis module禁用模块
ngx_http_postgres_module # content阶段的反向代理模块,上游为Postgres数据库 ,通过--with-http posteres module启用该模块
ngx_http_drizzle_module # content阶段的反向代理模块,上游为Mvsql或者Drizzle数据库 通过--with-http drizzle module启用该模块

Openresty的Nginx模块: 工具模块

ngx_http_headers_more_filter_module #rewvrite阶段处理请求、过滤模块,修改请求、响应的HTTP头部
ngx_http_rds_json_filter_module # 过滤模块,将RDS格式响应转换为JSON格式
ngx_http_xss_filter_module #使Nginx支持aiax跨站请求
ngx_http_rds_csv_filter_module  #过滤模块,将RDS格式响应转换为CSV格式 
ngx_http_srcache_filter_module # 在access阶段、响应过滤时生效,针对location在内存中缓存响应. 
ngx_coolkit_module #提供一些小功能集合,例如从Basic HTTP Authentication协议中取出密码作为变量值,等等 
ngx_http_iconv_module #可以将变量或者HITTP响应从一种编码(例如GBK) 转换为另一种编码 (如UTF8) 
ngx_http_echo_module #在content阶段、响应过滤时皆会生效,生成或者修改返回客户端的响应
ngx_http_set_misc_module #扩展了ngx http rewrite module模块的set指令
ngx_http_encrypted_session_module #使用AES256算法对变量的值进行加密和解密.
ngx_http_form_input_module #rewrite阶段读取请求包体,将POST和PUT请求中的FORM表单内容,解析成nginx变量供其他模块使用.
ngx_http_array_var_module #以固定分隔符的形式取出生成数组变量,并可将数组变量再通过固定分隔符邹将变量中数组类型的值,生成新的字符串变量

例子

server {
   listen 8341; 
   location /sharedict {
       content_by_lua_block {
       local dogs = ngx.shared.dogs
       dogs:set("Jim", 8,0.1,3)
       ngx.sleep(1)
       local jim,flags,stale = dogs:get stale("Jim")
       local bill,bflags = dogs:get('bill')
       success,err,forcible = dogs:add('bill',123)

       require "resty.core.shdict"
       local capacity_bytes = dogs:capacity()
       print(success,":",err,":",forcible,":",dogs:free_space(),"/",capacity bytes)
       ngx.say(jim,',',flags,',',stale,':',bill,",",bflags)
       
       }
   }
   location /array {
       array_split, $arg_names to=$names;
       array join '+' $names;
       #return 200"$namesin";
       content_by_lua_block {
           gx.say(ngx.var.names)
       }
   }
}
curl localhost:8341/array?names=l,2,3
# 输出 1+2+3

Openresty的官方Lua模块

lua-redis-parser # 将原始的redis响应解析为Lua语言的数据结构 
lua-rds-parser # 将原始的Drizzle、Mysql、PostGres数据库响应解析为Lua语言的数据结构 
lua-resty-dns # 基于cosocket实现DNS协议的通讯 
lua-resty-memcached # 基于ngxsockettcp实现的memcached客户端 
lua-resty-redis #基于ngx.socket.tcp实现的redis客户端 
lua-resty-mysql #基于ngx.socket.tcp实现的mysql客户端 
lua-resty-upload。 # 实现了读取请求中上传文件内容的功能 
lua-resty-upstream-healthcheck #通过向上游服务发送心跳检查上游服务的健康状态,以踢除有问题的节点 
lua-resty-string # 提供了hash函数 (如SHA1、MD5等)、字符串转换函数等工具 
lua-cjson #提供Lua语言与Json数据结构之间的编码、解码工具库
lua-resty-websocket #基于ngx_http_lua_module实现了websocket协议的客户端及服务器端 
lua-resty-limit-traffic #基于Lua语言实现了多种限速方案 
Lua库lua-resty-lock #提供基于共享内存的非阻塞互斥锁,基于ngx_http_lua_module中的ngxsleep方法实现" 
lua-resty-lrucache #基于内存实现的LRU (Least Recently Used) 缓存,故不能跨Nginx worker进程使用.通过 
lua-resty-core #ngx_http_lua_module模块提供的SDK皆基于老的CAPI实现,而本模块使用方式重新实现了ngx_http_lua_module模块中的SDK 

3.如何在Nginx中嵌入Lua代码

init_by_lua/init_by_lua_block/init_by_lua_file # 在Nginx解析配置文件 (Master进程)时在Lua VM层面立即调用的Lua代码
init_worker_by_lua/init_worker_by_lua_block/init_worker_by_lua_file #在Nginx worker进程启动时调用,实际实现了ngx module t中的init process方法

在11个HTTP阶段中嵌入Lua代码

  • set_by_lua/set_by_lua_block/set_by_lua_file
  • rewrite_by_lua/rewrite_by_lua_block/rewrite_by_lua_file
  • access_by_lua/access_by_lua_block/access_by_lua_file
  • content_by_lua/content_by_lua_block/content_by_lua_file
  • log_by_lua/log_by_lua_block/log_by_lua_file

控制rewrite/access是否延迟执行Lua代码

rewrite_by_lua_no_postpone  # 控制是否在rewrite阶段延迟至最后再执行Lua代码,默认关闭
access_by_lua_no_postpone  # 控制是否在access阶段延迟至最后再执行Lua代码,默认关闭

在过滤响应、负载均衡时嵌入Lua代码

  • header_filter_by_lua/header_filter_by_lua_block/header_filter_by_lua_file
  • body_filter_by_lua/body_filter_by_lua_block/body_filter_by_lua_file
  • balancer_by_lua_block/balancer_by_lua_file

在Openssl处理SSL协议时嵌入Lua代码

  • ssl_certificate_by_lua_block/ssl_certificate_by_lua_file
  • ssl_session_fetch_by_lua_block/ssl_session_fetch_by_lua_file
  • ssl_session_store_by_lua_block/ssl_session_store_by_lua_file

在Lua代码中获取当前阶段

ngx_get_phase

4.OpenResty中Lua与C代码交互的原理

提供一种Lua语言调用C语言函数的功能

  • ffi.cdef声明C语言中的函数或者结构体.
  • ffi.C调用声明说的方法
  • tonumber为Lua语言标准库函数
  • _M为Lua的数据结构:表
  • #s是Lua语法,表示取得字符串s长度
  • ngxatoi是Nginx中的工具函数,用于将
  • 字符串转换为数字

例子

lua-resty-string: string.lua
local ffi = require "ffi"
local C = ffi.C
local tonumber = tonumber
local _M = { _VERSION = '0.11' }
ffi.cdef[[
    intptr_t ngx_atoi(const unsigned char *line, size_t n);
]]
function _M.atoi(s)
    return tonumber(C.ngx_atoi(s, #s))
end
return _M

typedef intptr_t ngx_int_t;
#define u_char unsigned char
ngx_int_t ngx_atoi(u_char *line, size_t n) …}

系统级配置指令

lua_malloc_trim # 每N个请求使用C库的malloc trim方法,将缓存的空闲内存归还操作系统。该功能是在11个阶段中的log阶段判断是否需要执行的。值为0时表示关闭该功能
lua_code_cache # 值为on表示LuaVM由所有请求共享,值为of表示每个请求使用独立的Lua VM(这也导致)
lua_package_path # 设置调用Lua模块的路径地址
lua_package_cpath  # 设置Lua调用C模块的路径地址

取得配置参数SDK

ngx.config.subsystem   # 获取Nginx子模块,例如http分下会返回http,而stream升下会返回stream
ngx.config.debug  # 以布尔值返回当前Nginx编译执行configure时是否加入--with-debug
ngx.config.prefix  # 返回Nginx执行时的prefix路径,由命令行的-p或者configure--prefix指定的路径
ngx.config.nginx_version  # 以整型返回Neinx版本,例如1.13.6会返回1013006
ngx.config.nginx_configure  #  返回编译时执行configure命令的参数
ngx.config.ngx_lua_version # 以整型返回ngx_http_lua_module版本号,例如10013 (ngx http lua version宏)

5.获取、修改请求与响应的SDK

## 读取、修改变量的SDK
ngx.var.VAR_NAME
# 根据变量名VAR NAME读取Nginx中的变量值 (包括set定义的变量,以及Nginx框架生成的变量,例如http_HEADER或者arg _NAME)。也可修改变量的值,但必须是已经存在的变量。

## 客户端提前关闭连接 
lua_check_client_abort #是否检查客户端关闭连接,且发现该事件后回调ngx.on abort指定的Lua方法。默认关闭
ok, err = ngx.on_abort(callback)  #当下游客户端过早关闭连接时,调用callback函数

 ## 获取请求头部的SDK
ngx.req.get_method # 以字符串形式返回当前请求的方法名,例如“GET
ngx.req.http_version #返回请求中的HTTP版本号,例如1.1、20等
ngx.req.get_uri_args # 以Lua table的方式返回当前请求的全部URI参数,若URI参数只有名没有值,则该值为true。最多返回100个参数
ngx.arg[index] # 按index索引号读取到用户请求中的参数

## 获取请求包体的SDK
lua_need_request_body # Ngimx在HTTP11个阶段中都不确保读取到完整的请求包体,而该指令可以强制要求在以上阶段Lua代码执行前读取完整的请求包体。默认关闭
ngx.req.get_post_args # 以Lua table的方式返回POST请求中的表单数据,最多返回100个成员
ngx.req.read_body # 以同步的方式读取当前请求的包体。当请求没有包体、包体已经被读取或者包体被丢弃时,该方法会立即返回。读取到的包体,需要通过get body data或者get body fle获取到内容
ngx.req.get_body_data # 以Lua字符串形式返回内存中的包体。当请求包体未被读取、请求包体被存放在磁盘文件中、请求包体不存在时,返nil
ngx.req.get_body_file # 返回磁盘中存放请求包体的文件名,当包体未被读取或者未被存放至文件时,返回nil

HTTP请求方法名常量

  • 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)

修改请求信息的SDK

ngx.req.set_method(method_id) # 以HTTP方法变量的方式设置当前请求的方法
ngx.req.set_header(name , value) # 修改或者删除当前请求的请求头部。当value为nil时,表示删除该头部
ngx.req.clear_header(header_name)  #删除当前请求的某个请求头部,与ngx.reqset header中值为nil效果相同
ngx.req.set_uri_args(args) #设置当前请求的URI参数,可传入字符串(如ngx.req.set uri args(“a=3&b=hello%20world")) 

修改请求包体的SDK

ngx.req.discard_body  # 丢弃接收到的请求包体
ngx.req.set_body_data # 设置当前请求的请求包体内容。必须先读取完请求包体
ngx.req.set_body_file(filename, autoclean)  # 以磁盘文件来设置请求中的包体。必须先读取完请求包体。若autoclean值为true,则请求执行完后会删除这个磁盘文件,该值默认为false
ngx.req.init_body(buffer_size)  # 初始化一个空缓冲区,用于append body和finish body使用,创建用户请求包体。若buffer size没有传递则使用Nginx官方提供的client body buffer size值作为缓冲区大小。
ngx.req.append_body  # 向缓冲区中写入请求包体内容。若添加的包体大小超出缓冲区,则将包体写入临时文件中
ngx.req.finish_body # 使用init body和append body添加完包体后,必须调用该方法

修改URI并跳转的SDK

ngx.req.is_internal  # 以布尔值返回当前请求是否为内部跳转过来的请求
ngx.req.set_uri(uri, jump)  # 修改当前请求的URI。jump默认为false,若为truc且在rewrite by lua上下文中时则类似rewritc指令进行location跳转
ngx.exec # 更改当前请求的URI并执行location内部跳转

HTTP响应状态码常量

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)

读取、修改响应值

ngx.status

读取响应值,在响应头部发出以前可以修改响应值

修改发送响应的头部

ngx.header.HEADER成者ngx.header['HEADER']
# 增、删、改、查当前请求中的响应头部。注意lua transform underscores in response headers指令是默认开启中的。
# 其值为nil时(或者值为分) 表示删除该头部。当头部不存在时,则返回nil

lua_transform_underscores_in_response_headers 
# 当我们通过SDK中的ngx.header系列API去设置、获取头部时,是否自动将头部名称 (不包括值)中的’更换为’’,默认是开启的

发送响应的SDK

ngx.redirect(uri, status?)  # 向客户端返回重定向请求,status默认为302
ngx.send_headers # 显式的向客户端发送响应头部,返回1表示成功,返回nil表示失败
ngx.headers_sent # 返回布尔值标识响应头部是否已经发送

请求响应类指令

lua_use_default_type # 指定是否使用Nginx框架提供的default type中定义的Content-Type类型,作为Lua代码的响应类型。默认开启。
lua_http10_buffering  # 。控制对于Lua生成的、HTTP/1.0协议的响应是否先缓存再发送,当Lua代码中显示设定了Content-Length头部时会自动关闭该缓存功能

发送响应的SDK

ngx.print # 通过写入响应包体向下游客户端发送响应。该方法是异步的,它会立刻返回而不等待所有响应发送完毕在其后使用
ngx.flush(true) # 可以同步等待发送成功
ngx.say #  与ngx.print相似,但在包体最后会加入换行符
ngx.flush(wait?) # 将响应发向客户端,wait参数默认为false,此时ush方法是异步的,即它返回时所有响应未必都已经传递给内核的发送缓冲区;当值为true时,flush方法是同步,它返回时要么响应发送完毕,要么超时
ngx.exit(status)  # 当status>=200时,exit会终止当前请求的Lua代码,通过向Nginx返回响应码来发送响应当status--0时,exit会终止当前阶段(参见HTTP11个阶段)的执行,继续后续阶段的执行
ngx.cof  # 显式的指定响应内容已经结束。对于HTTP/1.1 (RFC2616)中定义的chunked编码来说,eof会指定Nginx发出“last chunk”来指明响应结束

6.工具类型的SDK

Nginx函数返回值常量

  • ngx.OK(0)
  • ngx.ERROR(-1)
  • ngx.AGAIN(-2)
  • ngx.DONE(-4)
  • ngx.DECLINED (-5)

取worker进程信息SDK

ngx.worker.exiting  # 以布尔值返回当前Nginx worker进程是否正在退出
ngx.worker.pid  # 返回worker进程ID,由于不依赖变量,所以比ngx.var.pid应用范围更广
ngx.worker.count  # 返回nginx.conf中配置的Nginx worker进程的数量
ngx.worker.id # 返回所属worker进程的序号 (从0到N-1)

日志级别常量

  • ngx.STDERR
  • ngx.EMERG
  • ngx.ALERT
  • ngx.CRIT
  • ngx.ERR
  • ngx.WARN0
  • ngx.NOTICE0
  • ngx.INFO
  • ngx.DEBUG
## 写入日志的SDK
print(...) # 使用notice级别将日志写入crror.log文件中
ngx.log(log level, ...)  # 以指定的日志级别log level来写入日志至error.log文件中

# 获取error.log日志内容的SDK
lua_capture_error_log # 基于Nginx提供的ngx cycle t->ngx_log_intercept_pt机制,使用API中的get_logs (由lua-resty-core模块提供)直接在内存中获取到Nginx所有输出至error.log中的日志内容。该指令定义了缓存大小

get_logs
# local errlog = require "ngx.errlog“
# local res = errlog.get_logs(10)

# 建立上下文信息的SDK
ngx.ctx
# 针对每个HTTP请求在内存中建立上下文字典,可通过名称设置、读取值。子请求内部跳转后皆不能使用ctx上下文

# 编解码
ngx.escape_uri  # 将字符串进行URL编码
ngx.unescape_uri # 将以URL编码的字符串解码
ngx.encode_args  # 将Lua table以URL格式中参数的形式编码,如foo =3,“br”=“hello world”编码为foo=3&b%20r=hello%20world
ngx.decode_args # 将URL格式的参数解码为Lua table
ngx.encode_base64 # 将字符串编码为base64格式
ngx.decode_base64 # 将base64格式的字符串解码为原字符串
ngx.quote_sql_str # 将SQL语句字符串转换为Mysql格式

# 哈希编码SDK
ngx.crc32_short(str) # 计算字符串的CRC32编码,该方法适用于较短的字符串 (小于30~60字节)
ngx.crc32_long # 计算字符串的CRC32编码,该方法适用于较长的字符串 (大于30~60字节)
ngx.hmac_sha1(secret_key, str) # 返回字符串的SHA1摘要值 (依赖Openssl库)
ngx.md5(str) # 返回字符串的MD5摘要值
ngx.md5_bin # 返回字符串的二进制格式的MD5摘要值
ngx.sha1_bin # 返回字符串的二进制格式的SHA1摘要值

# 正则表达式SDK
ngx.re.match # 进行正则表达式匹配,返回匹配中的子字符串
ngx.re.find # 进行正则表达式匹配,返回匹配中的子字符串中的第1个、最后1个字符的索引。未创建新字符串
ngx.re.gmatch # 进行正则表达式匹配,但返回的是迭代器,需要通过迭代器取得所有匹配的子字符串
ngx.re.sub # 进行正则表达式匹配,并可将匹配中的子字符串替换为新的字符串
ngx.re.gsub # 进行正则表达式匹配,并以全局方式将匹配中的子字符串替换为新的字符串
lua_regex_match_limit  # 限制ngx.re正则表达式类API匹配中的最大组数,默认值0表示使用PCRE库编译时设定的
最大值
lua_regex_cache_max_entries  # 对于以下API: ngxrematch,ngx.regmatch,ngxresub,ngxre.gsub所生成的正则表达式缓存至内存中,该指令定义了缓存的最大条目数。当达到最大数目后,新的正则表达式不会被缓存。默认值1024

7.同步且非阻塞的底层SDK cosocket

lua_socket_connect_timeout # 设置SDK中的cosocket的连接超时时间,默认60秒,可以携带所有时间类单位
lua_socket_send_timeout # 设置SDK中的cosocket的两次写操作间的超时时间,默认60秒,可以携带所有时间类单位
lua_socket_read_timeout # 设置SDK中的cosocket的两次读操作间的超时时间,默认60秒,可以携带所有时间类单位
lua_socket_buffer_size # 设置cosocket中的读缓冲区大小,默认为4K/8K
lua_socket_pool_size # 设置每个sosocket连接池中的最大连接数 (每个worker进程),超出后使用LRU算法关闭、淘汰连接
lua_socket_keepalive_timeout # 设置每个cosocket连接的最大空闲时间 (类似HTTP的Keepalive),默认60秒
lua_socket_log_errors # 控制cosocket出错时是否记录错误日志至Nginx的error.log文件中

TCP cosocket

ngx.socket.tcp # 创建TCP协议的cosocket对象。当没有显示关闭cosocket或者把它放入连接池,则当前阶段结束时会自动关闭对象
connect # 连接上游服务(IP端口或者unix地址)
sslhandshake # 在已经建立好的TCP连接上进行SSL握手
send # 将消息发送到对端。在send方法返回时,它已经将用户态的字节流拷贝到内核socket缓冲区中

receive(size)或者receive(patlern?) 
# 自cosocket中接收size个字节的消息,未接收足时则不会返回,直到满足超时条件(由settimeoul、setimeouts方法或者lua socket read timeout指令定义)
# pattern为*a表示一直接收消息,直到连接关闭
# pattemn为*1表示接收一整行消息,直到收到LF为止。没有参数时等同*1 
receiveany(max) # 自cosocket中接收到任意数据即返回,最多接收max字节(若内核读缓冲区数据超过max,仍只返回数据
receiveuntil(pattern, options?) # 返回用于读取消息的Lua迭代器方法,直到patten匹配到接收到的字节流
close # 关闭TCP连接 
settimeout(microseconds) # 设置超时时间,该时间同时应用在connect、send、receive方法上,参数单位为毫秒
settimeouts(connect timeout. send timeout read timeout)分别设置connect、send、receive方法的超时时间,单位毫秒.
setkeepalive(timeout?, size?) # 关闭cosocket对象,并将当前TCP连接放入keepalive连接池,最长空闲时间为timeout (单位毫秒),设为0表示永不讨期,若忽将该参数则使用lua socket keepalive timeout指令的值。
# size指定了连接池中对当前上游服务的最大连接数(仅第一次创建该上游服务的连接池时有效),忽略该参数时使用lua socket pool size指令的值达到size上限后(每worker进程内),按LRU关闭较老的空闲连接
# 若内核读缓冲区仍有数据,则该方法会返回错误信息“connection in dubious state"
getreusedtimes # 返回当前TCP连接复用了多少次

8.基于协程的并发编程SDK

协程SDK

co = ngx.thread.spawn(func, argl, arg2, ...)
# 生成轻量级线程并由nginx及ngx_http_lua_module模块立刻调度执行
# 入参func为线程中执行的函数,arg为func需要的参数,返回对象为Lua thread
ok, res1, res2, ... =  ngxthread.wait(thread1, thread2, ...) 
# 等待1个或者多个轻量级线程执行完毕后返回,ok表示所有线程是否正常结束,接下来的res返回值按照次序是每个func的返回值
ok, err = ngxthread.kill(thread) # 杀死正在运行的轻量级线程,仅父线程可以执行
coroutine  # 创建Lua协程,它不会自动执行
resume # 执行已创建还未执行的协程,或者恢复执行被yield挂起的协程
yield # 挂起当前执行的协程。
wrap # 创建Lua协程,可通过调用返回函数实现在协程中执行传入的方法(类似resume)
running # 返回正在执行的协程对象
status # 返回协程的状态,包括running、suspended、normal、dead

同步非阻塞的sleep方法及lua-resty-lock锁

ngx.sleep(seconds) # 对当前Lua代码块睡眠一段时间,seconds可以精确到0.001 (即毫秒)。它是通过Nginx中的定时器实现,不会阻塞Nginx运行

# lua-resty-lock模块的方法
obj, err = lock:new(dict name, opts) 
# 自共享内存字典中新建锁对象.
# opts选项包括
# - exptime: 设置锁的过期时间,到期后自动释放锁。默认30秒,精确到毫秒
# - timeout: lock方法的最大等待时间,默认5秒,精确到毫秒
# - step 锁是由ngx.sleep方法实现的,step定义了第一次sleep的时间。默认0.001秒。
# - ratio:定义每轮sleep时间的增长率,默认为2
# - max_step:定义了最大sleep时间,默认为0.5秒
elapsed err = obi:lock(key) # 获取关于key的锁,返回等待的时间,如果未获取到锁则返回nil
ok. err = obi:unlock() # 释放锁
ok, err = obi:expire(timeout) # 重置new方法传入的timeout选项

9.定时器及时间相关的SDK

hdl, err = ngx.timer.at(delay, callback, user_arg1, user_arg2, ...)
# 新增定时器,定时器触发后执行callback函数,user arg是函数的参数。
# delay是延迟执行时间,单位为秒,精确到毫秒。0表示立刻在后台的轻量级线程中执行callback
# callback的第一个参数是布尔值的premature,它表示定时器是否过早被触发,例Nginx worker进程在关闭时并不会等待定时器触发,而是直接调用callback,此时premature值为true,接下来才是user arg等参数
# callback由于没有当前请求,故不能使用子请求类API(如ngx.location.capture),也不能获取当前请求的参数,如ngx.req.
ngx.timer.every # 类似at,相当于每daley秒就调用一次callback
ngx.timer.running_count # 当前正在执行的定时器数量
ngx.timer.pending_count  # 等待执行的定时器数量

## 定时器相关指令
lua_max_pending_timers  # 指定API: ngxtimer.at中等待执行的定时器的最大数目,达到后新增的定时器直接返回nil,默认值1024
lua_max_running_timers  # 指定API: ngxtimer.at中正在执行回调方法的定时器最大数目,达到后新增的定时器的回调方法不会被执行,在error.log中出现N lua max running timers are not enough字样的日志,默认256

## 获取请求的处理时间SDK
ngx.req.start_time  # 返回开始处理当前请求经过的时间

## 时间类SDK
ngx.today # 从Nginx中取当前时间的日期(格式为yyyy-mm-dd)
ngx.time # 取得自epoch时间 (1970年1月1日0点)到现在的秒数
ngx.now # 取得自epoch时间 (1970年1月1日0点)到现在的秒数,精度到0.001 (毫秒)
nex.update # time·更新Nginx的缓存时间
nex.localtime # 返回本地时区下的当前时间(格式为yyyy-mm-dd hh:mm:ss)
ngx.utctime # 返回UTC下的当前时间(格式为yyyy-mm-dd hh:mm:ss)
ngx.cookie time(sec) # 将epoch时间秒数转换为cookie中可以识别的时间格式,如ngxcookie time1547466623) 返回Mon.14-Jan-19 
ngx.http time(sec) # 将epoch时间秒数转换为HTTP头部中的标准时间格式,例如nx.http time(1547466623) 返回Mon14Jan 2019GMT:2019-01-14
ngx.parse http time(str) # 将HTTP头部标准时间格式的字符串转换为epoch时间秒数

10.share.DICT基于共享内存的字典

基于共享内存的字典shared dict

lua_shared_dict

  • 指令 :基于Nginx的共享内存(使用Slab管理器)实现的跨worker进程字典容器,支持LRU淘汰功能。由于reload不会清除共享内存中的内容,故reload后shared dict值仍存在
  • SDK : 共享内存的所有方法都是原子的、线程安全的

11.shared dict的SDK

value, flagr = ngxshared.DICT:get(key)
# 返回字典中未过期key的值 
value. flags, stale = nex.shared.DlCT:eet stale(key)
# 返回字典中key的值 (无论是否过期)  
success, err, forcible = ngx.shared.DICT:set(key, value, exptime?, flags?)
# 向字典中设置键值对,当内存不足时会按过期时间淘汰老的元素。当连续淘汰30个(当前版本)元素后仍然无法分配出足够内存,则返回失败 
ok, err = ngx.sharedDICT:safe set(kev, value, exptime?, flags?)
success, err, forcible = ngx.shared.DICT:add(key, value, exptime?, flags?)
# 类似set,但仅向字典中添加新的键值对,若key已经存在则返回false,”exists”,false
ok, err = ngx.shared.DICT:safe add(key, value, exptime?, flags?)
# 类似add,但不会在内存不足时根据LRU强行淘汰未过期的键。内存不足时,返回nil和”o memory”
success, err, forcible = ngx.shared.DICT:replace(key, value, exptime?, flags?)
# 类似set,但仅能覆盖字典中已经存在的键值。若kev不存在,则返回false,”not found”.false
ngx.shared.DICT:delete(key)
# 从字典中删除key及对应的值,等价于set(key,nil)
newval, err, forcible? = ngxshared.DICT:incr(key, value, init?, init ttl?)
# 如果字典中key对应的值存在,则将其值加上value,并返回相加后的值newval。若kev在字典中对应的值不是数字,则返回nil’not a number”false
length, err = ngx.shared.DICT:rpush(key, value) # 类似lpush,但却是从队列的尾部插入元素
val, err = ngxshared.DICT:lpop(key) # 从key对应的队列头部取出1个元素
val, err = ngx.shared.DICT:rpop(key) # 从key对应的队列尾部取出1个元素
len. err = ngx.shared.DICT:llen(key)  # 查询key对应的队列的元素个数。若key对应的值不是队列,则返回nil,”value not a list”
ttl, err = ngx.shared.DICT:ttl(key) # 返回key对应的元素距离过期时间的剩余秒数,如果是永不过期类型则返回tt1为0
success, err = ngx.shared.DICT:expire(key, exptime) # 设置key对应元素的过期时间,如果key对应元素不存在则返回nil,not found”。exptime为0则表示永不过期
ngx.shared.DICT:flush all() # 标记字典中所有元素皆为过期.
flushed = ngx.shared.DICT:flush expired(max count?) # 将字典中最多max count(0表示不做限制)个过期元素释放内存
keys = ngx.shared.DICT:get keys(max count?) # 将字典中最多max count个键取出,max count默认值1024,设置为0时表示取出所有的键
capacity_bytes = ngx.shared.DICT:capacity() # 以字节数返回共享内存大小。须require"resty.core.shdict'
free_page_bytes = ngx.shared.DICT:free space # 以字节数返回共享内存slab管理器中空闲页的大小。须require"resty.core.shdict"

12.子请求的使用方法

Nginx中的主请求与子请求

子请求的生命周期 : 依赖父请求

主请求与父子请求间的关系

  • 主请求即客户端发来的请求
  • 主请求可派生多个子请求
  • 子请求也可派生多个子请求
  • 子请求完成时会激活父请求

子请求的响应如何处理

  • 作为父请求响应 : postpone filter过滤模块
  • 放置内存中等待模块处理

location.capture子请求

ngx.location.capture(uri, option)
# 生成Nginx子请求,且接下来的Lua代码会以同步的方式等待子请求返回后再执行。因此,该方法执行前应读取完整的请求包体,防止读取请求包体超时发生。子请求继承当前请求所有头部。返回值依赖子请求的响应,且响应都会放置在内存中,故子请求返回的响应不应过大,对于大文件响应可以使用

ngx.location.capture_multi(ffuri, option,furi, option?....?)
# 支持并行发起多个子请求,返回值代表的子请求响应与参数中的uri顺序相同仅所有子请求皆获得响应后,当前方法所属的代码段才会继续执行

ngx.is_subrequest   # 以布尔值返回当前请求是否为子请求

13.基于OpenResty的WAF防火墙

WAF(Web Application Firewall)防火墙 github.com/loveshell/n…

lua_package_path "/usr/local/nginx/conf/waf/?.lua";
lua_shared_dict limit 10m;
init_by_lua_file /usr/local/nginx/conf/waf/init.lua;
access_by_lua_file /usr/local/nginx/conf/waf/waf.lua;

参考

time.geekbang.org/course/intr…