Lua 学习

Posted bluesatc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lua 学习相关的知识,希望对你有一定的参考价值。

Lua 学习

前言

五一闲着太无聊了,想起之前学习Redis的时候,经常看到Lua

“Lua?Lua头?Lua猫?23333”(之前还以为发音是”rua",后面才知道是”LOO-ah“(“噜啊”))

萌生起学习Lua备用的想法。顺便看看如何在Redis中运用。走起!

这里主要学习的资源为:极客学院 Lua 教程。更新时间是2018年,三年前了,学习基础够用了。

目标

  1. 了解 Lua
  2. 学习 Lua基础
  3. Redis中的 Lua介绍
  4. 完成一个 Redis执行 Lua脚本的实例

背景知识

Lua 介绍

Lua is a powerful, efficient, lightweight, embeddable scripting language. It supports procedural programming, object-oriented programming, functional programming, data-driven programming, and data description.

Lua combines simple procedural syntax with powerful data description constructs based on associative arrays and extensible semantics. Lua is dynamically typed, runs by interpreting bytecode with a register-based virtual machine, and has automatic memory management with incremental garbage collection, making it ideal for configuration, scripting, and rapid prototyping.

让我个英语渣渣给大家翻译一下,如有不对,请批评指正。

Lua 是一个强力高效、轻量级、嵌入式的脚本语言。它支持过程化编程、面向对象式编程、函数式编程、数据驱动编程和数据描述。

Lua 基于关联数组和可扩展语义结合了简单过程法和强大的数据描述结构。Lua是动态类型,通过基于寄存器的虚拟机解释字节码运行,基于增量式垃圾回收自动内存关联,使它适合配置、脚本和快速原型。

Lua 的特点

  1. 健壮性
  2. 运行快
  3. 轻便
  4. 嵌入式
  5. 强有力,但是简单
  6. 小(29000行C代码)
  7. 免费(使用MIT协议)

以下是网络上摘抄的RedisLua脚本之间的关系。(Redis Lua脚本完全入门)(https://segmentfault.com/a/1190000037518418))

Redis提供了丰富的命令来供我们使用以实现一些计算。Redis的单个命令都是原子性的,有时候我们希望能够组合多个Redis命令,并让这个组合也能够原子性的执行,甚至可以重复使用,在软件热更新中也有一席之地。Redis开发者意识到这种场景还是很普遍的,就在2.6版本中引入了一个特性来解决这个问题,这就是Redis执行Lua脚本。

开源协议

作为程序员常常会接触到各种开源项目,或者听说某些大型项目更换开源协议了,或者干脆闭源。

MongoDB更换开源协议:https://database.51cto.com/art/201904/594977.htm

Elasticsearch更换开源协议:https://www.elastic.co/cn/blog/licensing-change

刚听到都会有点懵,啥是开源协议?开源协议就是一种法律文件,限制了该使用和修改该开源软件的行为,如修改、商业化等。我认为开源是能促进程序世界的发展的,但是开源提供者也必须能捍卫自己的权利和应得的利益。我们常见的开源协议有 MIT License(当你在Github上开源自己的代码,没有特殊设置,就会默认这个协议)、Apache License 2.0 、BSD、SSPL。

如何选择开源协议

Lua 基础

-- 局部变量定义方法: [作用域] 变量名
-- 作用域有 local 以及默认不写的话为 global
-- 定义局部变量 a
local a = 10
a = 10


-- 大部分特点:没有花括号,以end标识结束

-- while循环 while do end
while (true)
do
    print("123")
    break
end

-- until 循环 repeat until
repeat
    print("234")
until (true)

-- for 循环 for do end
-- 变量初始值,目标值(包含),步长
for a = 122
do
    print("345")
end


-- 决策
-- if  then end
if currentNumber<iteratorMaxCount
    then
    currentNumber = currentNumber+1
    return currentNumber, currentNumber*currentNumber
end

-- if then elseif then else 
a = 3
if( a < 1)
    then
    print(1)
elseif(a < 2)
    then
    print(2)
else
    print(3)
end


-- 函数声明
-- functon 关键字, end结束
function square(iteratorMaxCount,currentNumber)
    if currentNumber<iteratorMaxCount
        then
        currentNumber = currentNumber+1
        return currentNumber, currentNumber*currentNumber
    end
end

-- 数组声明
array = {"Lua""Tutorial"}
-- 初始化数组
array = {}
for i=1,3 do
    array[i] = {}
    for j=1,3 do
        array[i][j] = i*j
    end
end
-- 访问数组元素
for i=1,3 do
    for j=1,3 do
        print(array[i][j])
    end
end


-- 迭代器
function square(iteratorMaxCount,currentNumber)
    if currentNumber<iteratorMaxCount
        then
        currentNumber = currentNumber+1
        return currentNumber, currentNumber*currentNumber
    end
end
for i,n in square,3,0
do
    print(i,n)
end

-- 表
--简单的表初始化
mytable = {}

--简单的表赋值
mytable[1]= "Lua"

--移除引用
-- lua 的垃圾回收机制负责回收内存空间
mytable = nil


-- 协程
-- 创建协程,启动
local co1 = coroutine.create(function()
        while(true)
        do
            print(1)
            a = 1
            while(a < 99999999999)
            do
                a = a + 1
            end
        end
    end)
coroutine.resume(co1)

Redis中的Lua介绍

Redis Lua 介绍(https://redis.io/commands/eval)。要点如下。

常用命令

EVAL

这是一个进入redis-cli 后执行的命令。用于执行脚本。

EVAL script numKeys key [key ...] arg [arg ...]

eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second

script: 指的是要运行的脚本

numKeys: 指的是要传入多少个 Redis Key 值(因为Redis可以根据这个值,进行优化)

key: 传入的key值,数量要满足前面声明的keyNums。在脚本中用KEYS[1]KEYS[2]进行引用,计数开始的值为1,而不是0

arg: 传入的参数,在脚本中使用ARGV[1]ARGV[2]进行引用,计数也是从1开始。

EVALSHA

这是一个进入redis-cli 后执行的命令。用于执行脚本。和EVAL命令不同的是,script 改成使用sha1。

目的是减少script传输损耗。当然这个命令的使用前提是服务端必须缓存有这个sha1对应的脚本。

EVALSHA sha1 numkeys key [key ...] arg [arg ...]

脚本中调用redis命令

redis.call('Redis命令', ...Redis命令参数,以逗号分隔)
eval "return redis.call('set','foo','bar')" 0

redis.pcall('Redis命令', ...Redis命令参数,以逗号分隔)
eval "return redis.pcall('set','foo','bar')" 0

redis.call()会抛出异常,另外一种方式是 redis.pcall(),将异常封装在返回里。

注意点

  1. Lua中的浮点数转为Redis数据类型,会变成string;同时,如果想设置redis浮点数类型,需要Lua返回string类型
  2. Lua的布尔值 false 会转成 Redis 的 Nil,Redis Nil回复会转成Lua的false
  3. 脚本具有原子性
  4. 不允许使用全局变量
  5. 脚本不应该占有太多运行时间,因为运行脚本的时候,其他命令会被拒绝。在脚本执行时间超时后,脚本被标记为超时,只接受 SCRIPT KILLSHUTDOWN NOSAVE 两个命令
  6. 脚本应该被设计为一个纯函数,即相同输入有相同输出,把需要随机的变量当作参数传入脚本。

Redis 执行 Lua 脚本实例

制作一个限流脚本,上传到服务器的 /data/目录下,命名为interceptor.lua

参考自 https://segmentfault.com/a/1190000018070172

local busyName = tostring(KEYS[1])
local token = tostring(KEYS[2])
local expireMilSeconds = tonumber(ARGV[1])
local limitTimes = tonumber(ARGV[2])
local timestamp = tonumber(ARGV[3])
local lastTimestamp

-- 计算唯一键值
local identify = busyName .. ":" .. token
-- 已经请求的次数
local times = redis.call("LLEN", identify)
-- 如果请求次数小于限制次数,入队,设置过期时间
if times < limitTimes then
  redis.call("RPUSH", identify, timestamp)
  redis.call("EXPIRE", identify, 100)
  redis.log(redis.LOG_WARNING,"access")
  return 1
end

-- 取出队列头
lastTimestamp = redis.call("LRANGE", identify, 00)
lastTimestamp = tonumber(lastTimestamp[1])
redis.log(redis.LOG_WARNING,"lastTimeStamp:"..lastTimestamp)
redis.call("EXPIRE", identify, 100)

-- 如果队头+过期时间大于当前时间戳,则应当被限流
if lastTimestamp + expireMilSeconds > timestamp then
  redis.log(redis.LOG_WARNING,"limit")
  return 0
end
-- 将这次请求也记录下来
redis.call("LPOP", identify)
redis.call("RPUSH", identify, timestamp)
redis.log(redis.LOG_WARNING,"access")
return 1

接着依次运行下列命令,查看返回结果

redis-cli --eval /data/interceptor.lua limit_group token1 , 1000 3 1548664100

redis-cli --eval /data/interceptor.lua limit_group token1 , 1000 3 1548664200

redis-cli --eval /data/interceptor.lua limit_group token1 , 1000 3 1548664300

redis-cli --eval /data/interceptor.lua limit_group token1 , 1000 3 1548664400

redis-cli --eval /data/interceptor.lua limit_group token1 , 1000 3 1548664500

redis-cli --eval /data/interceptor.lua limit_group token1 , 1000 3 1548665200

限流执行结果

后记

  1. 我真的太菜了,发现自己对 Redis掌握不熟,找个时间认真学习下
  2. 想学下用 Lua做些骚操作,再开个新坑,慢慢填


以上是关于Lua 学习的主要内容,如果未能解决你的问题,请参考以下文章

Lua学习六----------Lua流程控制

Lua学习总结三

Lua学习总结三

redis Lua学习与坑

Lua脚本语言简单学习

Lua学习笔记 —— 风格