2019独角兽企业重金招聘Python工程师标准>>>
Lua 环境安装
Linux环境安装
选择你需要的Lua版本:http://www.lua.org/ftp/
curl -R -O http://www.lua.org/ftp/lua-5.3.4.tar.gz
tar zxf lua-5.3.4.tar.gz
cd lua-5.3.4
make linux test
make install
测试安装环境错误:
cd src && make linux
make[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.c
lua.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] 错误 1
make[2]: Leaving directory `/home/webapps/lua/lua-5.3.4/src'
make[1]: *** [linux] 错误 2
make[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 -v
Lua 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程序,下载地址为:
- 本站下载地址:LuaForWindows_v5.1.4-46.exe
- Github 下载地址:https://github.com/rjpcomputing/luaforwindows/releases
- Google Code下载地址 : https://code.google.com/p/luaforwindows/downloads/list 双击安装后即可在该环境下编写 Lua 程序并运行。
你也可以使用 Lua 官方推荐的方法使用 LuaDist:http://luadist.org/
国人开发IDEA Lua插件:https://github.com/tangzx/IntelliJ-EmmyLua
Lua语法
以下代码保存文件可以直接运行: 新建2个模块测试文件:TestMod.lua, TestLoad.lua
TestMod.lua 代码
local TestMod = {}
local function getname()
return "无忌"
end
function TestMod.Greeting()
print("Hello, My name is "..getname())
end
return TestMod
TestLoad.lua 代码
print('load...')
测试代码:
print("Hello World Keep Coding!!!")
--注释
--[[
多行注释
--]]
--[[
变量
NULL在Lua中是nil
lua中的变量如果没有特殊说明,全是全局变量,那怕是语句块或是函数里。变量前加local关键字的是局部变量
--]]
theGlobalVar = 50 --全局变量
local theLocalVar = "local variable" --本地变量
-- 5种方式定义的变量,字符串相等
a1 = 'alo\n123"'
a2 = "alo\n123\""
a3 = '\97lo\10\04923"'
a4 = [[alo
123"]] -- 多行变量定义的方式
a5 = [===[
alo
123"]===] -- [=[之间多少个==都没关系
nullvar = null
print('---- 变量 ----')
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)
end
print('---- for循环 ----')
sum = 0
for i = 100, 1, -2 do
sum = sum + i
end
print("sum =",sum)
print('---- while循环 ----')
-- Lua没有++或是+=这样的操作
sum = 0
num = 1
while num <= 100 do
sum = sum + num
num = num + 1
end
print("sum =",sum)
print('---- until循环 ----')
sum = 2
repeat
sum = sum ^ 2 --幂操作
print('until循环 sum=',sum)
until sum > 1000
print('---- 控制语句 ----')
print('\n---- 函数 与JavaScript 写法类似 ----')
print('---- 递归 ----')
function fib(n)
if n < 2 then return 1 end
return fib(n - 2) + fib(n - 1)
end
print(fib(2))
print('---- 闭包 ----')
function newCounter()
local i = 0
return function() -- anonymous function
i = i + 1
return i
end
end
c1 = newCounter()
print(c1()) --> 1
print(c1()) --> 2
function myPower(x)
return function(y) return y^x end
end
power2 = 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', 40
end
print('方法返回多个参数:',returnMultiParam())
--局部函数 JavaScript类似
function foo(x) return x^2 end
foo = function(x) return x^2 end
print('---- Table对象(支持Array 和 Map结构) ----')
--所谓Table其实就是一个Key Value的数据结构,它很像Javascript中的Object,或是PHP中的数组,在别的语言里叫Dict或Map
arr = {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)
end
for i = 1, #arr2 do -- 数组下标从0开始, #arr2代表arr2长度
print(arr2[i])
end
print('调用数组中的方法:',arr3[2](1))
print('---- MetaTable 和 MetaMethod ----')
-- MetaTable和MetaMethod是Lua中的重要的语法,MetaTable主要是用来做一些类似于C++重载操作符式的功能
fraction_a = {numerator=2, denominator=3} --分数 2/3
fraction_b = {numerator=4, denominator=7} --分数 4/7
-- 如果直接执行 fraction_a + fraction_b 会报错,我们需要通过MetaTable处理
-- 使用MetaTable
fraction_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 ret
end
-- 为之前定义的两个table设置MetaTable:(其中的setmetatble是库函数)
setmetatable(fraction_a, fraction_op)
setmetatable(fraction_b, fraction_op)
fraction_s = fraction_a + fraction_b
print('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)
end
function 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())
-- 继承,同样使用setmetatable
Student = Person:new()
function Student:new()
newObj = {year = 2013}
self.__index = self
return setmetatable(newObj, self)
end
function Student:toString()
return "Student : ".. self.year.." : " .. self.name
end
stu = Student:new{name='无忌'}
print('Student 继承:',stu:toString())
print('---- 模块 ---- ')
--[[
加载文件几种方式区别:
require("model_name") 载入相同文件只会执行一次
dofile("model_name") 载入相同文件每次都会执行
loadfile("model_name") 载入文件不执行,等你需要执行的时候再执行
3种方式测试: 文件 TestLoad.lua
print('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.lua
local TestMod = {}
local function getname()
return "无忌"
end
function TestMod.Greeting()
print("Hello, My name is "..getname())
end
return 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) 1
127.0.0.1:6379> EVALSHA 59dac41ffd27bef73ae87593da59b783b737a04b 1 goodsId 1
(integer) 1
127.0.0.1:6379> EVALSHA 59dac41ffd27bef73ae87593da59b783b737a04b 1 goodsId 1
(integer) 1
127.0.0.1:6379> EVALSHA 59dac41ffd27bef73ae87593da59b783b737a04b 1 goodsId 1
(integer) 0
127.0.0.1:6379> EVALSHA 59dac41ffd27bef73ae87593da59b783b737a04b 1 goodsId 1
(integer) 0
127.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环境搭建:http://www.runoob.com/lua/lua-environment.html
Lua简明教程(入厕文章):https://coolshell.cn/articles/10739.html
Aliyun redis集群: https://help.aliyun.com/document_detail/63920.html?spm=a2c4g.11186623.6.611.A4j6rw
Redis中Lua脚本使用: http://redisbook.readthedocs.io/en/latest/feature/scripting.html