使用Redis实现分布式锁

栏目: 数据库 · Redis · 发布时间: 5年前

内容简介:1、Redis命令行执行Lua脚本01 、redis.call() 和 redis.pcall() 两个函数的参数可以是任意的 Redis 命令

1、 Redis 命令行执行 Lua 脚本

01 、redis.call() 和 redis.pcall() 两个函数的参数可以是任意的 Redis 命令

127.0.0.1:6379> EVAL "return redis.call('SET','Name','Tinywan')" 0
OK
127.0.0.1:6379> get Name
"Tinywan"

说明: EVAL 和 EVALSHA 命令是从 Redis 2.6.0 版本开始的,使用内置的 Lua 解释器,可以对 Lua 脚本进行求值。

EVAL的第一个参数是一段 Lua 5.1 脚本程序。 这段Lua脚本不需要(也不应该)定义函数。它运行在 Redis 服务器中。

EVAL的第二个参数是参数的个数,后面的参数(从第三个参数),表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。

在命令的最后,那些不是键名参数的附加参数 arg [arg …] ,可以在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。

02、使用KEYS和ARGV

127.0.0.1:6379> EVAL "return redis.call('SET',KEYS[2],ARGV[3])" 3 name01 name02 name03 Tinywan01 Tinywan02 Tinywan03
OK
127.0.0.1:6379> keys *
1) "name02"
127.0.0.1:6379> get name02
"Tinywan03"

说明:返回结果是Redis multi bulk replies的Lua数组,这是一个Redis的返回类型,您的客户端库可能会将他们转换成数组类型。

03 、redis.call() 和 redis.pcall() 的区别

redis.call() 执行一个不存在的Redis命令: SETNGX

127.0.0.1:6379> EVAL "redis.call('SETNGX',KEYS[1],ARGV[1]);redis.call('SET',KEYS[3],ARGV[1])" 3 name01 name02 name03 Tinywan01 Tinywan02 Tinywan03

(error) ERR Error running script (call to f_0adfcdb3f740b2aabfe19f0e80de7cda7ce6262f): @user_script:1: @user_script: 1: Unknown Redis command called from Lua script
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379>

说明:当 redis.call() 在执行命令的过程中发生错误时,脚本会停止执行,并返回一个脚本错误,错误的输出信息会说明错误造成的原因。由于第一个执行错误,导致后面的也没有执行,设置不成功。

redis.pcall() 执行一个不存在的Redis命令: SETNGX

127.0.0.1:6379> EVAL "redis.pcall('SETNGX',KEYS[1],ARGV[1]);redis.call('SET',KEYS[3],ARGV[1])" 3 name01 name02 name03 Tinywan01 Tinywan02 Tinywan03
(nil)
127.0.0.1:6379> keys *
1) "name03"
127.0.0.1:6379> get name03
"Tinywan01"
127.0.0.1:6379>  

说明: redis.pcall() 出错时并不引发(raise)错误,而是返回一个 nil,后面的命令任然可以执行成功。

redis.call() 与 redis.pcall()很类似, 他们唯一的区别是当redis命令执行结果返回错误时, redis.call()将返回给调用者一个错误,而redis.pcall()会将捕获的错误以Lua表的形式返回。

redis.call() 和 redis.pcall() 两个函数的参数可以是任意的 Redis 命令

2、Redis中Lua脚本命令介绍

01、SCRIPT  命令

命令用于将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。

127.0.0.1:6379> SCRIPT LOAD "return redis.call('set',KEYS[1],ARGV[1])"
"c686f316aaf1eb01d5a4de1b0b63cd233010e63d"

02、EVALSHA 命令

根据给定的 SHA1 校验码(也就是 SCRIPT LOAD 执行脚本生成的哈希值),对缓存在服务器中的脚本进行求值。 将脚本缓存到服务器的操作可以通过 SCRIPT LOAD 命令进行。

127.0.0.1:6379> EVALSHA c686f316aaf1eb01d5a4de1b0b63cd233010e63d 2 Github Blog github.tinywan blog.tinywan
OK
127.0.0.1:6379> keys *
1) "Github"
2) "name03"
127.0.0.1:6379> get Github
"github.tinywan"

03、 SCRIPT FLUSH  命令

清空Lua脚本缓存 Flush the Lua scripts cache.

127.0.0.1:6379> SCRIPT FLUSH
OK
127.0.0.1:6379> EVALSHA c686f316aaf1eb01d5a4de1b0b63cd233010e63d 2 Github Blog github.tinywan blog.tinywan
(error) NOSCRIPT No matching script. Please use EVAL.
127.0.0.1:6379> SCRIPT LOAD "return redis.call('set',KEYS[1],ARGV[1])"
"c686f316aaf1eb01d5a4de1b0b63cd233010e63d"
127.0.0.1:6379> EVALSHA c686f316aaf1eb01d5a4de1b0b63cd233010e63d 2 Github Blog github.tinywan blog.tinywan
OK
127.0.0.1:6379>

04、SCRIPT EXISTS

命令用于校验指定的脚本是否已经被保存在缓存当中

127.0.0.1:6379> SCRIPT EXISTS c686f316aaf1eb01d5a4de1b0b63cd233010e63d
1) (integer) 1
127.0.0.1:6379> SCRIPT FLUSH
OK
127.0.0.1:6379> SCRIPT EXISTS c686f316aaf1eb01d5a4de1b0b63cd233010e63d
1) (integer) 0
127.0.0.1:6379>

05、SCRIPT KILL

杀死当前正在运行的 Lua 脚本

3、调试

script.lua脚本

local foo = redis.call("ping")
return foo

执行脚本

$ redis-cli --eval script.lua 
PONG

loop.lua脚本

local i = 0
while true do
    i = i + 1
    redis.debug(i)
end
return "OK"

进入调试模式

$ redis-cli --ldb --eval loop.lua set set , wet set
Lua debugging session started, please use:
quit    -- End the session.
restart -- Restart the script in debug mode again.
help    -- Show Lua script debugging commands.

* Stopped at 1, stop reason = step over
-> 1   local i = 0
^C

打开另外一个 shell 窗口

www@iZ2zec3dge6rwz2uw4tveuZ:~$ redis-cli 
127.0.0.1:6379> keys *
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
127.0.0.1:6379> keys *
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
127.0.0.1:6379> keys *
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
127.0.0.1:6379> SCRIPT KILL
OK
127.0.0.1:6379> keys *
  1) "REDIS_CACHE:RESTY_VOD_DETAIL:55"

4、实现分布式锁

使用 PHP 实现分布式锁

<?php
/**.-------------------------------------------------------------------------------------------------------------------
 * |  Github: https://github.com/Tinywan
 * |  Blog: http://www.cnblogs.com/Tinywan
 * |--------------------------------------------------------------------------------------------------------------------
 * |  Author: Tinywan(ShaoBo Wan)
 * |  DateTime: 2018/9/13 22:28
 * |  Mail: 756684177@qq.com
 * |  Desc: 使用Redis实现分布式锁
 * '------------------------------------------------------------------------------------------------------------------*/

class RedisLock
{
    /**
     * 获取锁
     * @param string $lock_name 锁名
     * @param int $acquire_time 重复请求次数
     * @param int $lock_timeout 请求超时时间
     * @return bool|string
     */
    public static function acquireLock($lock_name, $acquire_time = 3, $lock_timeout = 120)
    {
        $identifier = md5($_SERVER['REQUEST_TIME'] . mt_rand(1, 10000000));
        $lock_name = 'LOCK:' . $lock_name;
        $lock_timeout = intval(ceil($lock_timeout));
        $end_time = time() + $acquire_time;
        while (time() < $end_time) {
            $script = <<<luascript
                 local result = redis.call('setnx',KEYS[1],ARGV[1]);
                    if result == 1 then
                        redis.call('expire',KEYS[1],ARGV[2])
                        return 1
                    elseif redis.call('ttl',KEYS[1]) == -1 then
                       redis.call('expire',KEYS[1],ARGV[2])
                       return 0
                    end
                    return 0
luascript;
            $result = location_redis()->evaluate($script, array($lock_name, $identifier, $lock_timeout), 1);
            if ($result == '1') {
                return $identifier;
            }
            usleep(100000); //  函数延迟代码执行若干微秒
        }
        return false;
    }

    /**
     * 释放锁
     * @param string $lock_name 锁名
     * @param string $identifier 获取锁返回的标识
     * @return bool
     */
    public static function releaseLock($lock_name, $identifier)
    {
        $lock_name = 'LOCK:' . $lock_name;
        while (true) {
            $script = <<<luascript
                local result = redis.call('get',KEYS[1]);
                if result == ARGV[1] then
                    if redis.call('del',KEYS[1]) == 1 then
                        return 1;
                    end
                end
                return 0
luascript;
            $result = location_redis()->evaluate($script, array($lock_name, $identifier), 1);
            if ($result == 1) {
                return true;
            }
            break;
        }
        //进程已经失去了锁
        return false;
    }
}

官方教程


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Java算法

Java算法

塞奇威克 / 赵文进 / 清华大学出版社 / 2004-06-01 / 59.0

《Java算法》用Java语言全面实现了当今最重要的计算机算法,并用大量图表和数学公式对算法进行了详尽的描述和分析。全书共分3卷,本书是其中的第1卷(第1至第4部分)。内容包括基本概念(第1部分)、数据结构(第2部分)、排序算法(第3部分)和查找算法(第4部分)。本书概念清楚,内容翔实,新颖,由浅入深地描述了算法。本书可作为高等院校计算机相关专业本科生和研究生的教材和补充读物,也可作为Java爱好一起来看看 《Java算法》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

html转js在线工具
html转js在线工具

html转js在线工具