zoukankan      html  css  js  c++  java
  • Lua脚本

    Lua是一种轻量级脚本语言,它是用C语言编写的,跟数据的存储过程有点类似。

    使用Lua脚本来执行Redis命令的好处:
    1、 一次发送多个命令,减少网络开销。
    2、 Redis会将整个脚本作为一个整体执行,不会被其他请求打断,保持原子性。
    3、 对于复杂的组合命令,我们可以放在文件中,可以实现命令复用。

    在Redis中调用Lua脚本

    使用eval方法,语法格式:
    redis> eval lua-script key-num [keyl key2 key3 ....] [value 1 value2 value3 ....]

    • eval代表执行Lua语言的命令。
    • lua-script代表Lua语言脚本内容。
    • key-num表示参数中有多少个key,需要注意的是Redis中key是从1开始的,如果没有key的参数,那么写0。
    • [keyl key2 key3…]是key作为参数传递给Lua语言,也可以不填,但是需要和key-num的个数对应起来。
    • [valuel value2 value3…」这些参数传递给Lua语言,它们是可填可不填的。

    示例,返回一个字符串,0个参数:

    redis> eval ”return 'Hello World"' 0
    

    实际上,Lua脚本在Redis里面真正的用途是用来执行Redis命令。

    在Lua脚本中调用Redis命令

    命令格式

    使用 redis.call(command, key [paraml, param2...])进行操作。语法格式:

    redis. call (command, key [param l,param2...])
    
    • command 是命令,包括 set、get> del 等。
    • key是被操作的键。
    • paraml,param2...代表给 key 的参数。

    简单的案例,让Lua脚本执行set snail 2673 (Redis客户端执行):

    eval return redis.call('set', 'snail','2573')  0
    

    这种方式是写死值的,当然也可以用传参的方式:

    eval return redis.call('set' .KEYS[1],ARGV[1])" 1 snail xiaoming
    

    如果KEY和ARGV有多个,继续往后面加就是了。
    在redis-cli中直接写Lua脚本不够方便,也不能实现编辑和复用,通常我们会把Lua 脚本放在文件里面,然后执行这个文件。

    Lua脚本文件

    创建Lua脚本文件:

    cd /usr/local/soft/redis-6.0.9/src
    vim snail.lua 
    

    Lua脚本内容,先赋值,再取值:

    redis. call('set', 'snail',lua1')
    return redis.call('get','snail')
    

    调用脚本文件:

    cd /usr/local/soft/redis-6.0.9/src
    redis-cli --eval snail.lua 0
    

    场景案例:对IP进行限流

    需求:每个用户在X秒内只能访问Y次。
    设计思路:
    1、首先是数据类型。用String的key记录IP,用value记录访问次数。几秒钟和几次要用参数动态传进去。
    拿到IP以后,对IP+1。如果是第一次访问,对key设置过期时间(参数1)。否则判断次数,超过限定的次数(参数2),返回0。如果没有超过次数则返回1。超过时间, key过期之后,可以再次访问。

    KEY[1]是IP, ARGV[1]是过期时间X, ARGV[2]是限制访问的次数Y。

    -- ip_limit.lua
    -- ip限流,对某个IP频率进行限制,6秒钟访问10次
    local num=redis.call('incr',KEYS[1])
    if tonumber(num)== 1 then
            redis.call('expire',KEYS[1],ARGV[1])
            return 1
          elseif tonumber(num)>tonumber(ARGV[2]) then
    return 0
    else
    return 1
    end
    

    6秒钟内限制访问10次,调用测试(连续调用10次):redis-cli --eval ipjimit.lua app:ip:limit: 192.168.8.111,6 10

    • app:ip:limit:192.168.8.111是key值,后面是参数值,中间要加上一个空格和 —个逗号,再加上一个空格。
      即:redis-cli -eval [lua 脚本][key...]空格,空格[args...]

    • 多个参数之间用空格分割。

    缓存Lua脚本

    为什么要缓存?

    在Lua脚本比较长的情况下,如果每次调用脚本都需要把整个脚本传给Redis服务 端,会产生比较大的网络开销。为了解决这个问题,Redis可以缓存Lua脚本并生成SHA1 摘要码,后面可以直接通过摘要码来执行Lua脚本。
    

    如何缓存?

    这里面涉及到两个命令,首先是在服务端缓存山a脚本生成一个摘要码,用script load命令。

    script load "return ‘Hello World’“
    

    第二个命令是通过摘要码执行缓存的脚本:

    evalsha ”470877a599ac74fbfda41caa908de682c5fc7d4b” 0
    

    自乘案例

    Redis有incrby这样的自增命令,但是没有自乘,比如乘以3,乘以5。

    set num 2
    

    我们可以写一个自乘的运算,让它乘以后面的参数:

    local curVal = redis.call(''get", KEYS[1]) 
    if curVal == false then
      curVal = 0
    else
      curVal = tonumber(curVal)
    end
    curVal = curVal * tonumber(ARGV[ 1 ]) 
    redis.call("set", KEYS[1], curVal) 
    return curVal
    

    把这个脚本变成单行,语句之间使用分号隔开:

    local curVal = redis.call("getL KEYS[1]); if curVal == false then curVal = 0 else curVal = tonumber(curVal) end; curVal = curVal * tonumber(ARGV[1]); redis.call("set", KEYS[1], curVal); return curVal
    

    script load命令(Redis客户端执行)

    script load local curVal = redis.call("get", KEYS[1]); if curVal == false then curVal = 0 else curVal = tonumber(curVal) end; curVal = curVal * tonumber(ARGV[1]); redis.call("set", KEYS[1], curVal); return curVar
    

    "be4f93d8a5379e5e5b768a74e77c8a4eb0434441"

    调用:

    evalsha be4f93d8a5379e5e5b768a74e77c8a4eb0434441 1 num 6
    

    使用Lua脚本需要注意一个问题---脚本超时。

    脚本超时

    Redis的指令执行本身是单线程的,这个线程还要执行客户端的Lua脚本,如果Lua 脚本执行超时或者陷入了死循环,是不是没有办法为客户端提供服务了呢?

    eval  'while(true) do end' 0
    

    还真是的。它会导致其他的命令都会进入等待状态。
    当然,这种小问题,antirez在设计的时候引入lua脚本的时候就考虑到了。
    首先,脚本执行有一个超时时间,默认为5秒钟。

    lua-time-limit 5000
    

    超过5秒钟,其他客户端的命令不会等待,而是直接会返回"BUSY"错误。

    但是这样也不行,不能一直拒绝其他客户端的命令执行吧。在提示里面我们也看到了, 有两个命令可以使用,第一个是script kill,中止脚本的执行。

    script kill
    

    但是需要注意:并不是所有的lua脚本执行都可以kill。如果当前执行的Lua脚本对 Redis的数据进行了修改(SET、DEL等),那么通过script kill命令是不能终止脚本运 行的。

    eval  "redis.call('set','snail','666') while true do end" 0
    

    这时候执行script kill会返回UNKILLABLE错误。为什么要这么设计?为什么包含修改的脚本不能中断?因为要保证脚本运行的原子性。如果脚本执行了一部分被终止, 那就违背了脚本原子性的目标。

    遇到这种情况,只能通过shutdown nosave命令,直接把Redis服务停掉。

    正常关机是 shutdown。 shutdown nosave 和 shutdown 的区别在于 shutdown nosave不会进行持久化操作,意味着发生在上一次快照后的数据库修改都会丢失。

    总结:如果我们有一些特殊的需求,可以用Lua来实现,但是要注意那些耗时的操作。

  • 相关阅读:
    查询Python支持的.whl格式
    内置模块之os
    内置标准库之time
    迭代器(iterator)
    STM32F103和SIM800L打造低成本短信转发系统(五):控制程序
    STM32F103和SIM800L打造低成本短信转发系统(四):MDK-ARM
    STM32F103和SIM800L打造低成本短信转发系统(三):STM32CubeMX
    STM32F103和SIM800L打造低成本短信转发系统(二):软件安装篇
    STM32F103和SIM800L打造低成本短信转发系统(一):硬件篇
    处理SIM800L模块的时间字符串之URL编码(百分号编码)
  • 原文地址:https://www.cnblogs.com/snail-gao/p/14313735.html
Copyright © 2011-2022 走看看