redis基础第三篇:script、function

redis执行lua脚本涉及到的命令:

1、eval

语法:eval "脚本" numkeys [key [key ...]] [arg [arg ...]]

numkeys是key的个数,redis服务端通过这个参数知悉脚本后面哪些参数是key,哪些参数是arg。在脚本中,通过KEYS[1]、KEYS[2]获取key,通过ARGV[1]、ARGV[2]获取arg。KEYS和ARGV都是大写的,小写不行。如

eval "return 1" 0,会返回1

eval "return KEYS[1]" 1 a,会返回a

eval "return {KEYS[1],ARGV[1]}" 1 a b,会返回a和b

如果在脚本中要用到redis的命令,如set、get,则需要用redis.call(),如

eval "redis.call('set','name','张三');return redis.call('get', 'name')" 0,会返回张三

设置一个string类型的key的值,当key不存在时,设置值,并返回nil。否则返回旧值,且当新值大于旧值时设置值:

eval "if redis.call('exists',KEYS[1]) ==0 then redis.call('set',KEYS[1],ARGV[1]) return nil else local a = redis.call('get',KEYS[1]) if ARGV[1] > a then redis.call('set',KEYS[1],ARGV[1]) return a else return a end end" 1 qq 10

2、script load

语法:script load "脚本"

会返回脚本的sha1值,用于script exists和evalsha。

如script load "return 1",会返回e0e1f9fabfc9d4800c877a703b823ac0578ff8db

如果脚本很长,我们可以把脚本放到xxx.lua文件中,然后通过redis-cli script load "$(cat xxx.lua)加载。需要注意的是,用vim编辑文件会自动在最后加一个换行符,所以如果发现脚本在放到文件前后,sha1不一样的话,要考虑是否是这个换行符导致的。在保存前,切换到非编辑模式,执行set bin noeol,即可去掉最后的换行符。可以通过cat xxx.lua | sha1sum查看xxx.lua的sha1值。

但是,load的脚本不会持久化,当redis服务器重启或者主从切换时,load的lua脚本会消失。所以当redis服务器重启或者主从切换时,可能会出问题。

3、script exists

语法:script exists sha1

用于判断脚本是否存在,脚本存在,则返回1,否则返回0。如script exists e0e1f9fabfc9d4800c877a703b823ac0578ff8db。

4、evalsha

语法:evalsha sha1 numkeys [key [key ...]] [arg [arg ...]]

sha1是script load的返回值。如evalsha e0e1f9fabfc9d4800c877a703b823ac0578ff8db 0,会返回1。

 

鉴于script不能持久化的缺陷,从7.0起,redis引入了function,功能更加强大。 

function

1、function load

在7.0之后,redis引入了function,function可以持久化,不怕重启丢失。

语法:function load [replace] function-code

function load "#!lua name=mylib\nredis.register_function('f1', function(keys,args) return keys[1] end)\nredis.register_function('f2', function(keys,args) return args[1] end)" 

function load "#!lua name=mylib\nredis.register_function('getsetbigger', function(keys,args) if redis.call('exists',keys[1]) ==0 then redis.call('set',keys[1],args[1]) return nil else local a = redis.call('get',keys[1]) if args[1] > a then redis.call('set',keys[1],args[1]) return a else return a end end end)"

function-code必须以#!$engine_name name=$library_name开头,engine_name目前只能是lua,以后可能有其他选项。上面代码中的mylib即为library_name,即库名。调用redis.register_function()定义function,见https://redis.io/docs/latest/develop/programmability/lua-api/#redis.register_function,callback必须以end结尾。一个库中可以定义多个function,多次调用redis.register_function即可,多个redis.register_function以\n分隔。多个库中的function_name不能相同。同redis的command一样,function_name不区分大小写。如果想重新定义某个库(重新定义某函数,或者新增函数),则需要加replace关键字。上面的\n是换行符。

如果function-code过长,也可以放到文本中,然后通过redis-cli function load "$(cat xxx.lua)"加载。

脚本没问题的话,会返回库名。

2、fcall

语法:fcall function_name numkeys [key [key ...]] [arg [arg ...]]

调用某个函数,如fcall getsetbigger 1 qq 10

3、function delete

语法:function delete library_name

删除某个库。

4、function flush

删除所有库。

5、function list

语法:function list [LIBRARYNAME library-name-pattern] [withcode]

返回库及内部函数信息,如果加withcode,则还会返回库定义代码。LIBRARYNAME是关键字,必须大写。

6、function stats

返回正在运行的脚本的个数、engine名称、库的数量、函数的数量。

实际使用的一些自定义函数: 

#!lua name=mylib
redis.register_function('hgetallPrefix',function(keys,args)
  local m = redis.call('hgetall',keys[1])
  --redis.log(redis.LOG_WARNING, "m type:" .. type(m))
  local m2 = {}
  for i = 1, #m-1, 2 do
    --redis.log(redis.LOG_WARNING, "i:" .. i .. ",m[i]:" .. m[i] .. ",m[i+1]:" .. m[i+1])
    if string.find(m[i], "^" .. args[1]) then
      table.insert(m2,m[i])
      table.insert(m2,m[i+1])
    end
  end
  return m2
end)

redis.register_function('hsetxx',function(keys,args)
  local m = redis.call('hexists',keys[1],args[1])
  if m==1 then
    redis.call('hset',keys[1],args[1],args[2])
  end
  return 0
end)

可以通过redis.log打印日志来调试。通过redis.log打印的日志可以通过查看redis服务端的log查看。如果是docker部署,则只需docker logs -f redis。

如果redis所在服务器登不上去,则可以通过redis.call('publish','log_channel','somelog'),即通过调用publish命令把日志信息放到channel中,我们只需在命令行客户端调用psubscribe命令监听这个channel即可看到日志。 也可以把日志放到stream中,在命令行客户端通过相应命令查看。

posted on 2022-09-08 20:05  koushr  阅读(241)  评论(0)    收藏  举报

导航