redis 脚本介绍

redis脚本简介

  脚本使用 Lua 解释器来执行脚本。 Redis 从2.6版本开始,通过内嵌支持 Lua 环境。执行脚本的常用命令为 EVAL。

redis lua脚本的好处

  • 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络延迟。
  • 原子操作。redis将整个脚本当做一个整体去执行,中间不会被其他命令插入。因此无需担心脚本执行过程中会出现竞态条件
  • 复用。客户端发送的脚本会永久保存在redis中,这样,可以复用这一脚本而不用使用代码完成相同的逻辑。

redis lua脚本如何使用

redis内嵌lua解释器,从redis 2.6版本开始,redis可以使用EVAL命令执行lua脚本

  • EVAL script numkeys key [key ...] arg [arg ...]
    • 说明:EVAL命令使用lua解释器执行脚本
    • 参数:
      • script :参数是一段lua5.1脚本程序,也可以是lua脚本的地址。脚本不必(也不应该)定义为一个lua函数
      • numkeys:用于指定键名参数的个数
      • keyN:从EVAL的第三个参数开始算起,表示在脚本中所有用到的redis键(key),这些键名参数可以在lua脚本中通过全局变量KEYS数组的形式使用,用1为基址的形式访问(KEYS[1],KEYS[2],....,KEYS[N])
      • argN:附加参数,在lua脚本中通过全局变量ARGV数组的形式使用,用1为基址的形式访问(ARGV[1],ARGV[2],.....,ARGV[N])
    • 返回:lua脚本中指定的返回值
    • 实例:
    10.117.8.188:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 string1 string2 first second
    1) "string1"
    2) "string2"
    3) "first"
    4) "second"
    
    10.117.8.188:6379> EVAL "redis.call('set',KEYS[1],ARGV[1]);redis.call('set',KEYS[2],ARGV[2]);return 'ok';" 2 string1 string2 str1111 str2222
    "ok"
    
    • 注:
      • {}在lua里是指数据类型table,类似数组。
      • redis.call()可以调用redis命令,redis.pcall()有着相同的功能
  • EVALSHA sha1 numkeys key [key ...] arg [arg ...]
    • 说明:EVALSHA命令根据给定的sha1校验码,执行缓存在redis中的lua脚本;将脚本缓存到服务器可以使用SCRIPT LOAD命令实现。这个命令和EVAL命令基本相似,除了传的第一个参数不同之外,其他都一样
    • 参数:
      • sha1:通过SCRIPT LOAD生成的sha1校验码
      • 其他参数和EVAL命令一样
    • 返回:lua脚本中指定的返回值
    • 实例:
    10.117.8.188:6379> SCRIPT LOAD "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}"
    "a42059b356c875f0717db19a51f6aaca9ae659ea"
    10.117.8.188:6379> EVALSHA a42059b356c875f0717db19a51f6aaca9ae659ea 2 string1 string2 first second
    1) "string1"
    2) "string2"
    3) "first"
    4) "second"
    
  • SCRIPT LOAD script
    • 说明:SCRIPT LOAD命令用于将脚本script添加到脚本缓存中,并不立即执行该脚本,如果给定的脚本已经在缓存中存在了,那不执行任何操作;EVAL命令也会将脚本script添加到脚本缓存中,但是会立即执行;在脚本加入到缓存之后,通过EVALSHA命令,可以使用脚本的sha1校验和调用该脚本;脚本可以在缓存中保存无限长的时间,知道执行 SCRIPT FLUSH命令为止。
    • 参数:script(lua脚本)
    • 返回:给定的lua脚本的sha1校验码
    • 实例:
    10.117.8.188:6379> SCRIPT LOAD "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}"
    "a42059b356c875f0717db19a51f6aaca9ae659ea"
    
  • SCRIPT EXISTS sha1 [sha1 ...]
    • 说明:用于校验指定的脚本是否已被保存到缓存中
    • 参数:shaN(指定的脚本的sha1校验码)
    • 返回:一个列表,包含0和1;0代表指定的脚本未被保存到缓存中,1代表指定脚本已被保存到缓存中
    • 实例:
    10.117.8.188:6379> SCRIPT EXISTS a42059b356c875f0717db19a51f6aaca9ae659ea
    1) (integer) 1
    10.117.8.188:6379> SCRIPT EXISTS np2059b356c875f0717db19a51f6aaca9ae659ea
    1) (integer) 0
    10.117.8.188:6379> SCRIPT EXISTS a42059b356c875f0717db19a51f6aaca9ae659ea np2059b356c875f0717db19a51f6aaca9ae659ea
    1) (integer) 1
    2) (integer) 0
    
  • SCRIPT FLUSH
    • 说明:用于清除所有脚本缓存
    • 返回:总是返回OK
    • 实例:
    10.117.8.188:6379> SCRIPT FLUSH
    OK
    10.117.8.188:6379> SCRIPT EXISTS a42059b356c875f0717db19a51f6aaca9ae659ea
    1) (integer) 0
    
  • SCRIPT KILL
    • 说明:杀死正在执行的脚本,当且仅当该脚本没有执行任何写操作,该命令才生效;主要用于终止执行时间过长的脚本,例如某脚本进入死循环;SCRIPT KILL 执行之后,当前正在执行的脚本会被终止,执行这个脚本的客户端会从EVAL命令的阻塞中退出,并返回一个错误
    • 返回: 执行成功返回OK,失败则返回一个错误信息
    • 实例:
    10.117.8.188:6379> SCRIPT KILL
    (error) NOTBUSY No scripts in execution right now. //没有脚本在执行
    (error) ERR Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in an hard way using the SHUTDOWN NOSAVE command.//尝试杀死一个执行过写操作的脚本
    (error) ERR Error running script (call to f_694a5fe1ddb97a4c6a1bf299d9537c7d3d0f84e7): Script killed by user with SCRIPT KILL... // 脚本被杀死后,客户端返回的错误信息
    

命令行中怎么使用redis脚本

直接使用redis-cli命令,格式如下:
redis-cli --eval lua_file key1 key2 , arg1 arg2 arg3

    • redis-cli要想在命令行使用,需要在redis.conf中配置bind 127.0.0.1,否则会报错:Could not connect to Redis at 127.0.0.1:6379: Connection refused
    • eval 后面参数是lua脚本文件,.lua后缀
    • 不用写numkeys,键名和附加参数之间使用“,”隔开。注意,前后要有空格。
  • 实例:
setGet.lua
local num = redis.call('GET',KEYS[1]);
if not ARGV[1] then
   redis.call('DEL',KEYS[1]);
else
   redis.call('SET',KEYS[1],ARGV[1]);
end
return num;

命令行运行
www@iZ23dvyt70vZ:~ $ redis-cli --eval getSet.lua ll , 6
"5"
www@iZ23dvyt70vZ:~ $ redis-cli --eval getSet.lua ll , 8
"6"
www@iZ23dvyt70vZ:~ $ redis-cli --eval getSet.lua ll 
"8"
www@iZ23dvyt70vZ:~ $ redis-cli --eval getSet.lua ll ,9
(nil)

phpredis中使用redis脚本

  • eval(script,[args],[num_keys])
    • 说明:执行redis服务器的一个lua脚本,对应Redis::EVAL
    • 参数:
      • script:一段lua脚本,string类型
      • args:lua脚本中使用到的redis键和附加参数组成的数组,可选参数
      • num_keys:指定键名参数的个数,可选参数
    • 返回:返回值由lua脚本的返回决定
    • 实例:
    public function redisTestAction(){
        $redis = new \Redis();
        $redis->connect('10.117.8.188',6379);
        // $redis->connect('127.0.0.1',6379); 两个都可以
    
        $luaScript = <<eval($luaScript,['ll',20],1);
        var_dump($res);
    // 如果key:ll不存在,则res=false,ll不为空,则返回ll的旧值,并将新值赋值给ll
    }
    
  • evalSha(script_sha,[args],[num_keys])
    • 说明:执行一个redis服务端的lua脚本,只是脚本是有该脚本生成的sha1散列值script_sha代替;script_sha散列值必须在执行evalSha之前,使用SCRIPT LOAD命令生成。
    • 参数:
      • script_sha:lua脚本生成的sha1散列,string类型
      • args:lua脚本的参数,array类型,可选参数
      • num_keys:指定参数的个数,int类型,可选参数
    • 返回:返回值由lua脚本的返回决定
    • 实例:
    public function redisTestAction(){
        $redis = new \Redis();
        $redis->connect('10.117.8.188',6379);
    
        $luaScript = <<script('load',$luaScript);
        var_dump($luasha);
        echo "
    "; $res = $redis->evalSha($luasha,['ll',335],1); var_dump($res); //返回结果 //string(40) "5f31d46a307c1c36487a8af9bd263a29ab4fedcd" //string(3) "330" //string(40) "5f31d46a307c1c36487a8af9bd263a29ab4fedcd" //string(3) "335" }
  • script(command,[options])
    • 说明:执行Redis脚本相关的命令对当前客户端做各种操作
    • 参数:
      • command:redis脚本相关的各个命令
      • options:根据第一个参数,确定第二个参数
    • 返回:
      • SCRIPT LOAD:将在成功时返回所传递脚本的SHA1散列,在失败时返回FALSE
      • SCRIPT FLUSH:总是返回true
      • SCRIPT KILL:如果杀死了当前的脚本则返回true,失败则返回false
      • SCRIPT EXISTS:对所有的脚本进行判断是否存在,返回一个包含true和false的数组
    • 实例:
    public function redisTestAction(){
        $redis = new \Redis();
        $redis->connect('10.117.8.188',6379);
    
        $luaScript = <<script('load', $luaScript);
        var_dump($res); // 5f31d46a307c1c36487a8af9bd263a29ab4fedcd
        $res = $redis->script('exists', '5f31d46a307c1c36487a8af9bd263a29ab4fedcd');
        var_dump($res); // array(1) { [0]=> int(1) }
        $res = $redis->script('flush');
        var_dump($res); // bool(true)
        $res = $redis->script('kill');
        var_dump($res); // bool(false)
    }
    
  • getLastError()
    • 说明:获取最后一个错误消息(如果有的话)
    • 参数:无
    • 返回:如果存在错误,则返回错误信息,不存在错误则返回null
    • 实例:
    public function redisTestAction(){
        $redis = new \Redis();
        $redis->connect('10.117.8.188',6379);
    
        $luaScript = <<getLastError();
        var_dump($err1); //     NULL 
        echo "
    "; $redis->eval($luaScript,['lrre',4]); $err = $redis->getLastError(); var_dump($err); //string(74) "@user_script: 1: Lua redis() command arguments must be strings or integers" }
  • clearLastError()
    • 说明:清除最后的一个错误信息
    • 参数: 无
    • 返回: 总是返回true
    • 实例:
    public function redisTestAction(){
        $redis = new \Redis();
        $redis->connect('10.117.8.188',6379);
    
        $luaScript = <<eval($luaScript,['lrre',4]);
        $err = $redis->getLastError();
        var_dump($err);
        // string(74) "@user_script: 1: Lua redis() command arguments must be strings or integers" 
        echo "
    "; $res = $redis->clearLastError(); var_dump($res); // bool(true) echo "
    "; $err = $redis->getLastError(); var_dump($err);// NULL }
  • _prefix(key)
    • 说明:为指定的键值key设置前缀prefix
    • 参数:key(给定的键值)
    • 返回:返回prefixkey
    • 实例:
    public function redisTestAction(){
        $redis = new \Redis();
        $redis->connect('10.117.8.188',6379);
        $redis->setOption(\Redis::OPT_PREFIX, 'prefix1:'); //所有新建的key都会带有该前缀prefix1:
        $res1 = $redis->set('gg','gggggt'); // key为gg,实际在redis中该键为prefix1:gg
        var_dump($res1);
        $res = $redis->_prefix('gg');
        var_dump($res); // 返回:prefix1:gg
        $res2 = $redis->_prefix('tt'); //键tt在redis中不存在,同样返回prefix1:tt,并不会新建一个键prefix1:tt
        var_dump($res2);// 返回 prefix1:tt
    }
    
  • _serialize(value)
    • 说明:序列化一个值value,可以使用任意的序列化器对value进行序列化,如果没有设置序列化器,phpredis则会把array值更改为“array”,对象Objects值更改为“Objects”
    • 参数:value(准备序列化的值)
    • 返回:序列化之后的值
    • 实例:实例来自phpredis的github,由于我得redis版本内没有_serialize方法,所以没有验证
    public function redisTestAction(){
        $redis = new \Redis();
        $redis->connect('10.117.8.188',6379);
    
        $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE);
        $redis->_serialize("foo"); // returns "foo"
        $redis->_serialize(Array()); // Returns "Array"
        $redis->_serialize(new stdClass()); // Returns "Object"
    
        $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
        $redis->_serialize("foo"); // Returns 's:3:"foo";'
    }
    
  • _unserialize(value)
    • 说明:对序列化后的值value进行反序列化
    • 参数:value(序列化之后的值)
    • 返回:序列化之前的值
    • 实例:
    public function redisTestAction(){
        $redis = new \Redis();
        $redis->connect('10.117.8.188',6379);
        $redis->setOption(\Redis::OPT_SERIALIZER, \Redis::SERIALIZER_PHP);
        $data = ['it'=>'it','if'=>'kl','ig'=>'gj'];
        $res = serialize($data);
        var_dump($res); // string(60) "a:3:{s:2:"it";s:2:"it";s:2:"if";s:2:"kl";s:2:"ig";s:2:"gj";}"
        $ret = $redis->_unserialize($res);
        var_dump($ret);//array(3) {["it"]=>string(2) "it" ["if"]=>string(2) "kl" ["ig"]=>string(2) "gj"}
    }
    

你可能感兴趣的:(redis 脚本介绍)