OpenResty编程tips
Lua 中定义和调用函数时点号和冒号的区别
定义table
中的函数时和调用table
中的函数时, 冒号:
和点号.
的区别是什么.
定义 function
|
|
调用 function
养成调用方法统一使用:
的习惯, 统一风格.
|
|
输出:
|
|
如何正确使用 Lua 中的 local
lua 的变量, 可以引用字符串、table、函数等各种 lua 对象, 默认是全局的.
定义全局变量的坏处:
- 在不同阶段的 lua 代码中存在命名冲突;
- 全局变量是同一个 worker 下的所有请求共享的, 所以会存在不同请求同时处理同一个变量造成 race condition(资源竞争), 导致意想不到的错误结果.
- 由上可知, 定义变量时尽量使用
local
来声明.
使用
local
声明变量后, 变量的有效范围是当前block
级别的,block
可以是条件语句块、循环语句块、函数块、文件中的代码块. 举个例子, 在条件语句块中local
声明的变量, 在条件语句块之外是无法访问的, 如果需要访问, 就需要在条件语句块之外local
声明该变量, 在条件语句块中直接使用该变量, 不可再次local
声明该变量.一个变量在相同
block
下只能被local
声明一次.使用 local 对 API 进行加速, 比如
|
|
Lua 中 return 的用法详解
用在函数块最后一行,
return
主要是用于从函数中返回结果, 不会终止代码文件继续执行;用在条件代码块时,
return
不论是否放在代码块的最后一行, 都会最后一个执行, 用于终止当前代码文件的运行, 代码文件后续的所有代码都不会执行了;用于循环语句时,
return
用于终止当前代码文件的运行, 后面的所有代码都不会执行了, 而break
是终止循环继续运行, 注意他们的区别;return
不能直接用于代码文件级别;do return end
一般用于调试代码的场景使用:- 用于函数中时, 可放置在函数中代码的中间, 这样函数剩余的代码部分不会被执行, 不会终止代码文件执行;
- 用于条件、循环语句中时, 可放置在代码块的中间, 会在此处终止代码文件执行;
- 直接用于代码文件级别, 会在
return
所在处终止代码文件执行.
注意:
以上说的终止代码文件执行, 终止的是return
所在的当前文件(模块)级别, 并不会影响其他执行阶段;
至于是否会终止当前执行阶段, 就要看当前执行阶段的代码块中是否有return
了.
如果return
只是出现在require
的模块中, 这个return
也不会影响到当前执行阶段.
使用 resty 写 Lua 脚本的技巧
脚本第一行
|
|
指定 require 库时需要的搜索路径
|
|
或者
|
|
注意,下面这样不可以,会报错:
|
|
中断脚本执行的方法, 用于调试
|
|
或者
|
|
书写版本和脚本名称, 养成好习惯
|
|
print()
print("")
在 resty 脚本中将不再是ngx.log(ngx.NOTICE, "")
而是等价于:
io.stdout:write("")
所以在resty
脚本中可以直接使用print
来打印输出到终端。
判断元素是否在 table 中
|
|
截取 table 中的元素
截取 table 中的前多少个元素,返回一个截取后的 table
|
|
读取操作系统中的文件, 返回一个 table
|
|
解析不到 POST 上来的 body 体的问题
客户端 post 数据总是提示错误, openresty lua
获取不到 body 体的问题.
|
|
POST 体中包含中文的解析
客户端向 openresty post 的 json 字符串, 如果包含中文, 那么 openresty 需要进行两次json.decode
才能转化为lua table
数据结构.
|
|
注意 typo 错误带来的排查困扰
使用 redis 时, error_log
日志始终报这个错误:
|
|
困扰了很多天, 最后发现是因为: red:set_timeout(2000)
这句拼写错误,
错误的写成了red:set_timemout(2000)
.
null 的比较
从 redis 读取的字符串如果通过ngx.say
打印出的是null
,
这个null
在 lua 里是用cjson.null
表示的, 所以这个时候我们如果做条件判断,
一定要和cjson.null
进行比对, 否则会和预期不一样.
其他情况和null
进行比较的时候要使用ngx.null
替代, 具体情况具体分析.
ngx.say 和 ngx.print 的使用阶段
ngx.say
和ngx.print
使用阶段为:
rewrite_by_lua*, access_by_lua*, content_by_lua*
,
如果出现在rewrite
, 就不会执行access
和content
阶段了,
但之后的header_filter
、body_filter
、log
三个阶段还是会执行的,
只是这三个阶段不能执行ngx.say
API, 其他代码还是会执行的.
同理出现在access
阶段, 就不会执行content
阶段了.
也就是说给客户端响应内容的只能是rewrite
、access
和content
的阶段.
ngx.var API 的几个用法
注意 ngx.var 几种不同用法的区别.
表示 http 头部时, 比如 content-type 头: ngx.var.http_content_type
表示 nginx 内置的变量时, 比如$remote_addr
: ngx.var.remote_addr
表示在 nginx 配置文件中自定义的变量时,
比如set $error_from "-"
: ngx.var.error_from
require 一个模块得到的大都是一个 table 数据类型
require
一个模块之后, 得到的大都是一个table
数据类型(因为一般模块都是通过table
来封装的), 比如:
|
|
输出: table
另外需要注意, 函数是不能被序列化的(所谓序列化就是从 table 类型转化为 json 字符串类型), 所以如果 table 中包含了函数, 通过cjson.encode
进行序列化是就会报错, 如下这样的 table:
|
|
这个 table _M
等价于:
|
|
使用 lua_cache_code 需要注意的
当lua_cache_code
指令值是off
的时候, *_by_lua_file
指定的 lua 文件代码修改后,
可以不需要 reload openresty 重新加载即可立即生效.
但*_by_lua
和*_by_lua_block
指令对应的 lua 代码内容就不可以,
因为这些代码写在了 nginx 配置文件里面.
当然这个指令一般只用于在代码测试阶段, 可以省去反复 reload openresty 的麻烦,
不过要特别注意, 如果测试通过共享变量存储数据或是lrucache
等,
这个指令一定要是on
.
ngx.timer.* 的执行与请求无关
ngx.timer.*
的执行是在独立的协程里完成的,
也就是说它的运行与当前的请求没有关系.
在事件循环中, Nginx 会找出到期的timer
(即需要开始执行里面的回调函数了),
并在一个独立的协程中执行对应的 Lua 回调函数.
请求级别的变量放到函数内
当代码文件作为模块使用时, 谨记要把请求级别的变量放到函数内,
否则同一worker
下的所有请求都会共享这个变量内容, 导致内容输出错误.
table 作为字典使用时如何排序
table
作为列表时, 是有序的, 作为字典使用时是无序的,
如果为了序列化后显示先后顺序, 可以这样做:
|
|
table 中键值对的值是 nil 时需要注意的
如果table
中的某些键值对的值是nil
,
通过cjson.encode(table)
是打印不出来该键值对的.
获取 table 的长度的方法
|
|
判断一个 table 是否为空
table.nkeys(tablename) ~= 0
http 库相关
tokers/lua-resty-requests
库同时请求后端数超过 40 个左右的时候,
部分请求会超时报错: lua tcp socket queued connect timed out
;
ledgetech/lua-resty-http
这个库比较完善, 没有上面的问题.
前一个库每查询一次后端就新建一个tcp
连接,
后一个库所有查询只用一个tcp
连接, 高下立见.
使用 resty.mysql 库连接 mysql8.0 以上报错
Client does not support authentication protocol requested by server; consider upgrading MySQL client: 1251 08004 sql
解决方法:
alter user 'root'@localhost IDENTIFIED WITH mysql_native_password by '123456';
将某个文件加载到内存时如何指定文件位置
|
|