Lua 环境安装
Linux环境安装
选择你需要的Lua版本:
curl -R -O http://www.lua.org/ftp/lua-5.3.4.tar.gztar zxf lua-5.3.4.tar.gzcd lua-5.3.4make linux testmake install
测试安装环境错误:
cd src && make linuxmake[1]: Entering directory `/home/webapps/lua/lua-5.3.4/src'make all SYSCFLAGS="-DLUA_USE_LINUX" SYSLIBS="-Wl,-E -ldl -lreadline"make[2]: Entering directory `/home/webapps/lua/lua-5.3.4/src'gcc -std=gnu99 -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_LINUX -c -o lua.o lua.clua.c:82:31: 错误:readline/readline.h:没有那个文件或目录lua.c:83:30: 错误:readline/history.h:没有那个文件或目录lua.c: 在函数‘pushline’中:lua.c:312: 警告:隐式声明函数‘readline’lua.c:312: 警告:赋值时将整数赋给指针,未作类型转换lua.c: 在函数‘addreturn’中:lua.c:339: 警告:隐式声明函数‘add_history’make[2]: *** [lua.o] 错误 1make[2]: Leaving directory `/home/webapps/lua/lua-5.3.4/src'make[1]: *** [linux] 错误 2make[1]: Leaving directory `/home/webapps/lua/lua-5.3.4/src'make: *** [linux] 错误 2
解决方案:
yum install libtermcap-devel ncurses-devel libevent-devel readline-devel
安装成功:
lua -vLua 5.3.4 Copyright (C) 1994-2017 Lua.org, PUC-Rio
编写测试代码:
vi lua_coding.lua --新建文件print("Hello World Keep Coding!!!") --执行代码lua lua_coding.lua --执行
Windows 环境安装
Window 系统上安装 Lua window下你可以使用一个叫"SciTE"的IDE环境来执行lua程序,下载地址为:
- 本站下载地址:
- Github 下载地址:
- Google Code下载地址 : 双击安装后即可在该环境下编写 Lua 程序并运行。
你也可以使用 Lua 官方推荐的方法使用 LuaDist:
国人开发IDEA Lua插件:
Lua语法
以下代码保存文件可以直接运行: 新建2个模块测试文件:TestMod.lua, TestLoad.lua
TestMod.lua 代码
local TestMod = {}local function getname() return "无忌"endfunction TestMod.Greeting() print("Hello, My name is "..getname())endreturn TestMod
TestLoad.lua 代码
print('load...')
测试代码:
print("Hello World Keep Coding!!!")--注释--[[多行注释--]]--[[变量NULL在Lua中是nillua中的变量如果没有特殊说明,全是全局变量,那怕是语句块或是函数里。变量前加local关键字的是局部变量--]]theGlobalVar = 50 --全局变量local theLocalVar = "local variable" --本地变量-- 5种方式定义的变量,字符串相等a1 = 'alo\n123"'a2 = "alo\n123\""a3 = '\97lo\10\04923"'a4 = [[alo123"]] -- 多行变量定义的方式a5 = [===[alo123"]===] -- [=[之间多少个==都没关系nullvar = nullprint('---- 变量 ----')print(a1 == a2)print(a2 == a3)print(a3 == a4)print(a4 == a5)print('nullvar == nil is',nullvar == nil)print('---- 变量 ----')print('---- 控制语句 ----')print('---- if-else分支 ----')io.write('请输入年龄:')local age = io.read("*number")io.read() -- 回车符io.write('请输入性别(男|女):')local sex = io.read()if age == 40 and sex =="男" then print("男人四十一枝花")elseif age > 60 and sex ~="女" then print("old man without country!")elseif age < 20 then io.write("too young, too naive!\n")else print("Your age is "..age)endprint('---- for循环 ----')sum = 0for i = 100, 1, -2 do sum = sum + iendprint("sum =",sum)print('---- while循环 ----')-- Lua没有++或是+=这样的操作sum = 0num = 1while num <= 100 do sum = sum + num num = num + 1endprint("sum =",sum)print('---- until循环 ----')sum = 2repeat sum = sum ^ 2 --幂操作 print('until循环 sum=',sum)until sum > 1000print('---- 控制语句 ----')print('\n---- 函数 与JavaScript 写法类似 ----')print('---- 递归 ----')function fib(n) if n < 2 then return 1 end return fib(n - 2) + fib(n - 1)endprint(fib(2))print('---- 闭包 ----')function newCounter() local i = 0 return function() -- anonymous function i = i + 1 return i endendc1 = newCounter()print(c1()) --> 1print(c1()) --> 2function myPower(x) return function(y) return y^x endendpower2 = myPower(2)power3 = myPower(3)print('4的2次方=',power2(4)) --4的2次方print('5的3次方=',power3(5)) --5的3次方--函数的返回值,可以一次返回多个值function returnMultiParam() return '无忌', 'nassir.wen@gmail.com', 40endprint('方法返回多个参数:',returnMultiParam())--局部函数 JavaScript类似function foo(x) return x^2 endfoo = function(x) return x^2 endprint('---- Table对象(支持Array 和 Map结构) ----')--所谓Table其实就是一个Key Value的数据结构,它很像Javascript中的Object,或是PHP中的数组,在别的语言里叫Dict或Maparr = {name='无忌',age=37,email='nassir.wen@gmail.com'}arr2 = {10,20,30} -- 等价于 {[1]=10,[2]=20,[3]=30}arr3 = {'无忌',function(x) return x + 1 end} -- 数组中可以定义不同类型,也可以定义方-- 遍历数组for k, v in pairs(arr) do print(k, v)endfor i = 1, #arr2 do -- 数组下标从0开始, #arr2代表arr2长度 print(arr2[i])endprint('调用数组中的方法:',arr3[2](1))print('---- MetaTable 和 MetaMethod ----')-- MetaTable和MetaMethod是Lua中的重要的语法,MetaTable主要是用来做一些类似于C++重载操作符式的功能fraction_a = {numerator=2, denominator=3} --分数 2/3fraction_b = {numerator=4, denominator=7} --分数 4/7-- 如果直接执行 fraction_a + fraction_b 会报错,我们需要通过MetaTable处理-- 使用MetaTablefraction_op={}function fraction_op.__add(f1, f2) ret = {} ret.numerator = f1.numerator * f2.denominator + f2.numerator * f1.denominator ret.denominator = f1.denominator * f2.denominator return retend-- 为之前定义的两个table设置MetaTable:(其中的setmetatble是库函数)setmetatable(fraction_a, fraction_op)setmetatable(fraction_b, fraction_op)fraction_s = fraction_a + fraction_bprint('MateTable 实现分数对象相加:', fraction_s.numerator, '/' , fraction_s.denominator)print('---- 面向对象(有点像Javascript的prototype)----')--[[ 面向对象的实现,主要是使用MetaMethod的__index重载,所谓__index,说得明确一点,如果我们有两个对象a和b,我们想让b作为a的prototype只需要setmetatable(a, {__index = b})--]]Person={}function Person:new(p) local obj = p if (obj == nil) then obj = {name="无忌", age=18, handsome=true} end self.__index = self return setmetatable(obj, self)endfunction Person:toString() return self.name .." : ".. self.age .." : ".. (self.handsome and "handsome" or "ugly")end--[[1)self 就是 Person,Person:new(p),相当于Person.new(self, p)2)new方法的self.__index = self 的意图是怕self被扩展后改写,所以,让其保持原样3)setmetatable这个函数返回的是第一个参数的值。--]]-- 测试me = Person:new()print(me:toString())kf = Person:new{name="King's fucking", age=70, handsome=false}print(kf:toString())-- 继承,同样使用setmetatableStudent = Person:new()function Student:new() newObj = {year = 2013} self.__index = self return setmetatable(newObj, self)endfunction Student:toString() return "Student : ".. self.year.." : " .. self.nameendstu = Student:new{name='无忌'}print('Student 继承:',stu:toString())print('---- 模块 ---- ')--[[加载文件几种方式区别:require("model_name") 载入相同文件只会执行一次dofile("model_name") 载入相同文件每次都会执行loadfile("model_name") 载入文件不执行,等你需要执行的时候再执行3种方式测试: 文件 TestLoad.luaprint('load...')--]]print('---- 测试require加载3次 ----')require('testload')require('testload')require('testload')print('---- 测试require加载3次 ----')print('---- 测试dofile加载3次 ----')dofile('testload.lua')dofile('TestLoad.lua')dofile('TestLoad.lua')print('---- 测试dofile加载3次 ----')print('---- 测试loadfile加载3次 ----')loadfile('testload')loadfile('testload')loadfile('testload')print('---- 测试loadfile加载3次 ----')-- 具体模块实现--[[ 模块名: TestMod.lualocal TestMod = {}local function getname() return "无忌"endfunction TestMod.Greeting() print("Hello, My name is "..getname())endreturn TestMod--]]-- 调用local test_mod = require("TestMod")test_mod.Greeting()
Lua + Redis集群 秒杀场景使用
实现逻辑:
- 秒杀商品先保存在Redis
- 确认下单资格,通过Lua脚本扣减库存,由于Redis是单线程模型,Lua可以保证多个命令原子性
初始化商品数据:
模拟数据格式:goodsId 商品ID,Total 商品总数,Booked 商品已预定数"goodsId" : { "Total": 3 "Booked": 0}
执行redis初始化:
redis 127.0.0.1:6379> HMSET goodsId Total 3 Booked 0 OK redis 127.0.0.1:6379> HMGET goodsId Total Booked 1) "3" 2) "3"
Lua实现扣减库存脚本: seckill.lua
local n = tonumber(ARGV[1])if not n or n == 0 then return 0 end local vals = redis.call('HMGET', KEYS[1], 'Total', 'Booked');local total = tonumber(vals[1])local blocked = tonumber(vals[2])if not total or not blocked then return 0 end if blocked + n <= total then redis.call('HINCRBY', KEYS[1], 'Booked', n) return n; end return 0
将扣减库存脚本加载到Redis:
./redis-cli SCRIPT LOAD "$(cat /usr/local/redis/bin/seckill.lua)"59dac41ffd27bef73ae87593da59b783b737a04b
执行扣减代码:
127.0.0.1:6379> EVALSHA 59dac41ffd27bef73ae87593da59b783b737a04b 1 goodsId 1(integer) 1127.0.0.1:6379> EVALSHA 59dac41ffd27bef73ae87593da59b783b737a04b 1 goodsId 1(integer) 1127.0.0.1:6379> EVALSHA 59dac41ffd27bef73ae87593da59b783b737a04b 1 goodsId 1(integer) 1127.0.0.1:6379> EVALSHA 59dac41ffd27bef73ae87593da59b783b737a04b 1 goodsId 1(integer) 0127.0.0.1:6379> EVALSHA 59dac41ffd27bef73ae87593da59b783b737a04b 1 goodsId 1(integer) 0127.0.0.1:6379> EVALSHA 59dac41ffd27bef73ae87593da59b783b737a04b 1 goodsId 1返回 1 扣减成功返回 0 扣减失败
执行函数说明
EVAL script numkeys key [key ...] arg [arg ...]
EVAL 执行脚本 key数量 key值 执行参数 EVALSHA sha1 numkeys key [key ...] arg [arg ...] EVALSHA 执行sha1效验值 key数量 key值 执行参数
每个被执行过的 Lua 脚本, 在 Lua 环境中都有一个和它相对应的函数, 函数的名字由 f_ 前缀加上 40 个字符长的 SHA1 校验和构成: 比如 f_5332031c6b470dc5a0dd9b4bf2030dea6d65de91 。
只要脚本所对应的函数曾经在 Lua 里面定义过, 那么即使用户不知道脚本的内容本身, 也可以直接通过脚本的 SHA1 校验和来调用脚本所对应的函数, 从而达到执行脚本的目的 —— 这就是 EVALSHA 命令的实现原理。
参考
Lua环境搭建:
Lua简明教程(入厕文章): Aliyun redis集群: Redis中Lua脚本使用: