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.sayAPI, 其他代码还是会执行的.
同理出现在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';
将某个文件加载到内存时如何指定文件位置
| |