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来实现,但是要注意那些耗时的操作。

  • 相关阅读:
    web服务器-Apache
    nginx优化
    nginx下载限速
    nginx-URL重写
    HDU 5358 First One 求和(序列求和,优化)
    HDU 5360 Hiking 登山 (优先队列,排序)
    HDU 5353 Average 糖果分配(模拟,图)
    UVALive 4128 Steam Roller 蒸汽式压路机(最短路,变形) WA中。。。。。
    HDU 5348 MZL's endless loop 给边定向(欧拉回路,最大流)
    HDU 5344 MZL's xor (水题)
  • 原文地址:https://www.cnblogs.com/snail-gao/p/14313735.html
Copyright © 2011-2022 走看看