1 Idea中创建Lua项目
lua官网:https://www.lua.org/
1.1 添加插件,重启idea
1.2 创建项目
file-New Project
1.3 创建lua文件
1.4 配置lua运行程序
1)下载
https://github.com/rjpcomputing/luaforwindows/releases
2)安装
直接按照就好
3)配置Debug
program选择lua安装路径
1.5 测试
代码
local function main()
print("hello world")
end
main()
执行
2 Lua语法简介
2.1 保留关键字
and break do else elseif end false for function if in local nil not or repeat return then true until while
2.2 注释
-- 两个减号是行注释
--[[
这是块注释
这是块注释.
--]]
2.3 变量
1)数字类型
Lua的数字只有double型,64bits
你可以以如下的方式表示数字
num = 1024
num = 3.0
num = 3.1416
num = 314.16e-2
num = 0.31416E1
num = 0xff
num = 0x56
2)字符串
可以用单引号,也可以用双引号
也可以使用转义字符‘\n’ (换行), ‘\r’ (回车), ‘\t’ (横向制表), ‘\v’ (纵向制表), ‘\’ (反斜杠), ‘\”‘ (双引号), 以及 ‘\” (单引号)等等
下面的四种方式定义了完全相同的字符串(其中的两个中括号可以用于定义有换行的字符串)
a = 'alo\n123"'
a = "alo\n123\""
a = '\97lo\10\04923"'
a = [[alo
123"]]
3)空值
C语言中的NULL在Lua中是nil,比如你访问一个没有声明过的变量,就是nil
4)布尔类型
只有nil和false是 false
数字0,‘’空字符串(’\0’)都是true
5)作用域
lua中的变量如果没有特殊说明,全是全局变量,那怕是语句块或是函数里。
变量前加local关键字的是局部变量
2.4 控制语句
1)while循环
local i = 0
local max = 10
while i <= max do
print(i)
i = i +1
end
2)if-else
local function main()
local age = 140
local sex = 'Male'
if age == 40 and sex =="Male" then
print(" 男人四十一枝花 ")
elseif age > 60 and sex ~="Female" then
print("old man without country!")
elseif age < 20 then
io.write("too young, too naive!\n")
else
print("Your age is "..age)
end
end
-- 调用
main()
3)for循环
sum = 0
for i = 100, 1, -2 do
sum = sum + i
end
2.5 函数
function myPower(x,y)
return y+x
end
power2 = myPower(2,3)
print(power2)
function newCounter()
local i = 0
return function() -- anonymous function
i = i + 1
return i
end
end
c1 = newCounter()
print(c1()) --> 1
print(c1()) --> 2
print(c1())
2.6 返回值
name, age,bGay = "yiming", 37, false, "yimingl@hotmail.com"
print(name,age,bGay)
function isMyGirl(name)
return name == 'xiao6' , name
end
local bol,name = isMyGirl('xiao6')
print(name,bol)
2.7 Table
key,value的键值对 类似 map
lucy = {name='xiao6',age=18,height=165.5}
xiao6.age=35
print(xiao6.name,xiao6.age,xiao6.height)
print(xiao6)
2.8 数组
arr = {"string", 100, "xiao6",function() print("memeda") return 1 end}
print(arr[4]())
遍历
for k, v in pairs(arr) do
print(k, v)
end
2.9 面向对象
成员函数
person = {name='xiao6',age = 18}
function person.eat(food)
print(person.name .." eating "..food)
end
person.eat("xxoo")
3 Lua整合redis
从redis2.6.0版本开始,通过内置的lua编译器和解析器,可以使用eval命令执行lua脚本
3.1 在redis客户端中执行简单lua脚本
登录到客户端后执行
eval "return 1+1" 0
#命令 脚本 参数个数
EVAL "local msg='hello world' return msg..KEYS[1]" 1 AAA BBB
#...表示拼接字符串
表是基于1的,也就是说索引以数值1开始。所以在表中的第一个元素就是mytable[1],第二个就是mytable[2]等等。
表中不能有nil值。如果一个操作表中有[1, nil, 3, 4],那么结果将会是[1]——表将会在第一个nil截断
3.2 执行独立脚本test.lua
1)创建一个脚本
local list=redis.call("get","qq");
return list;
2)执行脚本
redis-cli -p 6379 -a 573875306 --eval /usr/local/programs/redis-5.0.10/mylua/test.lua 0
3)带参数
test.lua
local num = redis.call('get',KEYS[1]);
if not num then
return num;
else
local res = num * KEYS[2] * ARGV[1];
redis.call('set',KEYS[1],res);
return res;
end;
执行
这里要说明一下linux中执行lua脚本参数传值和redis命令执行lua脚本传值的差异问题。
如果传入多个参数,那么在redis命令中,需要指定key的个数,所有的key和argv参数之间都使用空格分隔即可,lua脚本执行时,会根据传入的key个数自动区分开key参数和argv参数;
但是在linux命令中,key参数和argv参数要用逗号分隔,key和key之间、argv与argv之间用空格分隔,如果key和argv之间不使用逗号,则会抛出异常,并且逗号前后需有空格,否则会被认为是传的一个参数,同样会抛出异常
redis-cli -p 6379 -a 573875306 --eval /usr/local/programs/redis-5.0.10/mylua/test.lua launumber 20 , 30
3.3 Lua 与 Redis 交互
3.3.1 Lua 脚本获取 EVAL & EVALSHA 命令的参数
通过 Lua 脚本的全局变量 KEYS 和 ARGV,能够访问 EVAL 和 EVALSHA 命令的 key [key ...] 参数和 arg [arg ...] 参数。
作为 Lua Table,能够将 KEYS 和 ARGV 作为一维数组使用,其下标从 1 开始。
3.3.2 Lua 脚本内部执行 Redis 命令
Lua 脚本内部允许通过内置函数执行 Redis 命令:
redis.call()
redis.pcall()
两者非常相似,区别在于:
若 Redis 命令执行错误,redis.call() 将错误抛出(即 EVAL & EVALSHA 执行出错);
redis.pcall() 将错误内容返回。
local msg='count:' local count = redis.call("get","count") if not count then redis.call("set","count",1) end redis.call("incr","count") return msg..count+1
3.3.3 redis WATCH/MULTI/EXEC 与Lua
redis 原生支持 监听、事务、批处理,那么还需要lua吗?
- 两者不存在竞争关系,而是增强关系,lua可以完成redis自身没有的功能
- 在lua中可以使用上一步的结果,也就是可以开发后面操作依赖前面操作的执行结果的应用,MULT中的命令都是独立操作
- redis可以编写模块增强功能,但是c语言写模块,太难了,lua简单的多
- 计算向移动数据
- 原子操作
lua脚本尽量短小并且尽量保证同一事物写在一段脚本内,因为redis是单线程的,过长的执行会造成阻塞,影响服务器性能。
3.3.4 Redis Lua 脚本管理
1)script load 此命令用于将Lua脚本加载到Redis内存中
2)script exists scripts exists sha1 [sha1 …] 此命令用于判断sha1是否已经加载到Redis内存中
3)script flush 此命令用于清除Redis内存已经加载的所有Lua脚本,在执行script flush后,sha1不复存在
4)script kill 此命令用于杀掉正在执行的Lua脚本
3.3.5 死锁
下面代码会进入死循环,导致redis无法接受其他命令。
eval "while true do end" 0
127.0.0.1:6379> keys *
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
但是可以接受 SCRIPT KILL or SHUTDOWN NOSAVE. 两个命令
SHUTDOWN NOSAVE 不会进行持久化的操作
SCRIPT KILL 可以杀死正在执行的进程
3.4 生产环境下部署
加载到redis
redis-cli script load "$(cat test.lua)"
得到sha1值
执行
redis-cli evalsha "7a2054836e94e19da22c13f160bd987fbc9ef146" 0
4 openresty安装使用(lua整合nginx)
我们都知道Nginx有很多的特性和好处,但是在Nginx上开发成了一个难题,Nginx模块需要用C开发,而且必须符合一系列复杂的规则,最重要的用C开发模块必须要熟悉Nginx的源代码,使得开发者对其望而生畏。为了开发人员方便,所以接下来我们要介绍一种整合了Nginx和lua的框架,那就是OpenResty,它帮我们实现了可以用lua的规范开发,实现各种业务,并且帮我们弄清楚各个模块的编译顺序。关于OpenResty,我想大家应该不再陌生,随着系统架构的不断升级、优化,OpenResty在被广泛的应用
4.1 下载
http://openresty.org/cn/download.html
4.2 安装
官方文档安装介绍:http://openresty.org/cn/installation.html
1)下载好的文件
2)上传到服务器解压
tar -xzvf xxx
3)进入目录/usr/local/openresty-1.21.4.1
4)环境准备
yum install pcre-devel openssl-devel gcc curl -y
5)安装perl
6)执行
./configure
7) 安装
程序会被安装到`/usr/local/openresty`目录
make
make install
8) 启动
./nginx -c /usr/local/openresty/nginx//conf/nginx.conf
9)访问
4.3 相关命令
4.3.1 服务命令
1)启动
Service openresty start
2)停止
Service openresty stop
3)检查配置文件是否正确
Nginx -t
4)重新加载配置文件
Service openresty reload
5)查看已安装模块和版本号
Nginx -V
5 nginx使用lua扩展
5.1 简单使用
1)进入目录
这里进入的是openresty下的nginx目录,这就是一个全新的可以使用lua扩展的nginx
2)修改配置文件nginx.conf
/usr/local/openresty/nginx/conf
首先修改下端口号,免得冲突:server下的 listen属性
listen 8099;
在server下添加一个local
location /lua {
default_type text/html;
content_by_lua_file /usr/local/openresty/nginx/lua/hello.lua; #也可以用相对路径,相对目录在nginx下
}
3)进入上面配置的目录,创建hello.lua
添加内容
ngx.say("<p>Hello, World!</p>")
4)重新启动
./nginx -s stop
./nginx -c /usr/local/openresty/nginx//conf/nginx.conf
5)访问
http://192.168.28.110:8099/lua
如果出错,可以在/usr/local/openresty/nginx/logs下查看日志查找原因
5.2 配置热部署
没有配置热部署,修改hello.lua,都需要重新启动
修改配置文件:/usr/local/openresty/nginx/conf/nginx.conf
在http下加上配置:lua_code_cache off; #热部署,每次执行都编译
重新启动,再修改hello.lua直接刷新页面就可以
5.3 其它示例
1)获取Nginx请求头信息
local headers = ngx.req.get_headers()
ngx.say("Host : ", headers["Host"], "<br/>")
ngx.say("user-agent : ", headers["user-agent"], "<br/>")
ngx.say("user-agent : ", headers.user_agent, "<br/>")
for k,v in pairs(headers) do
if type(v) == "table" then
ngx.say(k, " : ", table.concat(v, ","), "<br/>")
else
ngx.say(k, " : ", v, "<br/>")
end
end
2)获取post请求参数
ngx.req.read_body()
ngx.say("post args begin", "<br/>")
local post_args = ngx.req.get_post_args()
for k, v in pairs(post_args) do
if type(v) == "table" then
ngx.say(k, " : ", table.concat(v, ", "), "<br/>")
else
ngx.say(k, ": ", v, "<br/>")
end
end
3)获取http协议版本
ngx.say("ngx.req.http_version : ", ngx.req.http_version(), "<br/>")
4)获取请求方法
ngx.say("ngx.req.get_method : ", ngx.req.get_method(), "<br/>")
5)获取原始的请求头内容
ngx.say("ngx.req.raw_header : ", ngx.req.raw_header(), "<br/>")
6)获取body内容体
ngx.say("ngx.req.get_body_data() : ", ngx.req.get_body_data(), "<br/>")
6 Nginx全局内存缓存
多进程共享,且能保障原子性。可以通过lua去访问它。
重载Nginx配置时,缓存数据会丢失。
1)修改配置文件:/usr/local/openresty/nginx/conf/nginx.conf
在http下加上一行配置
lua_shared_dict shared_data 1m;
重新启动
2)修改hello.lua
local shared_data = ngx.shared.shared_data
local i = shared_data:get("i")
if not i then
i = 1
shared_data:set("i", i)
ngx.say("lazy set i ", i, "<br/>")
end
i = shared_data:incr("i", 1)
ngx.say("i=", i, "<br/>")
3)访问
7 lua-resty-lrucache缓存
gihub地址:https://github.com/openresty/lua-resty-lrucache
Lua 实现的一个简单的 LRU 缓存,适合在 Lua 空间里直接缓存较为复杂的 Lua 数据结构
它相比 ngx_lua 共享内存字典可以省去较昂贵的序列化操作,相比 memcached 这样的外部服务又能省去较昂贵的 socket 操作
1)支持更丰富的数据类型,可以把table存放在value中,这对数据结构复杂的业务非常有用。
2)可以预先分配key的数量,不用设置固定的内存空间,在内存的使用上更为灵活。
3)每个worker进程独立缓存,所以当worker进程同时读取同一个key 时不存在锁竞争。
但它与lua_shared_dict相比也有一些缺点
1)因为数据不在worker之间共享,所以无法保证在更新数据时,数据在同一时间的不同worker进程上完全一致。
2)虽然可以支持复杂的数据结构,但可使用的指令却很少,如不支持消息队列功能。
3)重载Nginx配置时,缓存数据会丢失。如果使用lua_shared_dict,则不会如此
1)修改配置文件:/usr/local/openresty/nginx/conf/nginx.conf
注释掉热部署,每次都编译对缓存会造成影响,使用不正确
#lua_code_cache off; #热部署,每次执行都编译
之前配置的location 进行修改
location /lua {
default_type text/html;
content_by_lua_block {
require("my/cache").go()
}
}
2)编码
在/usr/local/openresty/lualib创建文件夹my(因为上面配置的相对路径,它会在这些目录下去找)
在my下创建文件cache.lua
写入内容
local _M = {}
lrucache = require "resty.lrucache"
c, err = lrucache.new(200) -- allow up to 200 items in the cache
ngx.say("count=init")
if not c then
error("failed to create the cache: " .. (err or "unknown"))
end
function _M.go()
count = c:get("count")
c:set("count",100)
ngx.say("count=", count, " --<br/>")
if not count then
c:set("count",1)
ngx.say("lazy set count ", c:get("count"), "<br/>")
else
c:set("count",count+1)
ngx.say("count=", count, "<br/>")
end
end
return _M
3)重启
4)访问
8 openresty连接redis(lua-resty-redis)