Redis 分布式锁解决方案

在论坛上找到各种版本,应用到项目出运行不了,各种报错。今天自己重新编写调试测试,运行成功。

  • PHP redis使用phpredis
  • 参考redis官方资料
  • 具体代码

    <?php
    namespace app\common\service;
    
    /**
     *
     * redis 加锁 --单Redis实例实现分布式锁
     *
     * -- 分布式请使用:Redlock:https://github.com/ronnylt/redlock-php
     * -- 详情参考: http://www.redis.cn/topics/distlock.html
     *
     * @package app\common\service
     */
    class RedisLock
    {
        const LOCK_SUCCESS = 'OK';
        const IF_NOT_EXISTS = 'NX';
        const MILLISECOND_EXPIRE_TIME = 'PX';
        const EXPIRE_TIME = 60000; // millisecond => 60s
        const LOCK_VALUE = 1;
    
            /**
             * 加锁
             * @param $redis object
             * @param $key
             * @param string $expire_time 60000
             */
            public static function lock($redis, $key, $expire_time='')
            {
                if (empty($expire_time)) {
                        $expire_time = self::EXPIRE_TIME;
                }
    
                $result = $redis->set($key, self::LOCK_VALUE, [self::IF_NOT_EXISTS, self::MILLISECOND_EXPIRE_TIME => $expire_time]);
    
                return self::LOCK_SUCCESS === (string)$result;
            }
    
            /**
             * 解锁
             *
             * 参考: https://github.com/phpredis/phpredis/blob/develop/tests/RedisTest.php
             * @param $redis
             * @param $key
             */
            public static function unlock($redis, $key)
            {
                $lua =<<<EOT
    if redis.call("get",KEYS[1]) == ARGV[1] then
        return redis.call("del",KEYS[1])
    else
        return 0
    end
    EOT;
                $result = $redis->eval($lua, array($key, self::LOCK_VALUE), 1);
                return $result;
        }
    }
本作品采用《CC 协议》,转载必须注明作者和本文链接
欲速则不达
本帖由系统于 5年前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 18
sushengbuhuo

点赞

5年前 评论

官方文件上的 RedisLock

之前翻到的輪子, 不過都沒有機會使用 :stuck_out_tongue_closed_eyes:

5年前 评论

这玩意自己写一个就行了,原理很简单。

5年前 评论
Sqc 4年前
Destiny

看到你头像来的。我有一件衣服就是乔治「乔二娃」

5年前 评论

本意应该是 读写锁 但是这里的代码有可能导致死锁

5年前 评论

@motecshine 不会死锁,可以去官方请仔细查阅set 的用法。

5年前 评论

这个锁有个问题,如果在sapi模式中加锁,获取锁的过程中比较长,比如在php artisan中跑批量任务,在你设置的expire中没有跑完。这个时候锁会自动释放掉,但是你又会说expire可以设置个较长的时间,但是谁又知道这个任务要跑多久了,并且同一个任务有时候跑的时间不一样。这个时候就会有问题,当前获取过锁的进程还在跑,然后锁又因为超时被释放了, 导致新的进程又可以进来。这样就失去了锁的作用。(我想说的是重入锁的问题)

我觉得自己写的总是会遗漏一些场景。如果需要可以使用symfony 的锁组件。https://symfony.com/doc/current/components...

5年前 评论

而且如果是用锁的话,请使用setnx.
官方连接:https://redis.io/commands/setnx
Design pattern: Locking with SETNX

5年前 评论
hbnn1111 4年前
勇敢的心 3年前

@vasar 那也只是加了选项以后才等于setnx。而且是说的版本是2.6.12

5年前 评论
hbnn1111 4年前

明天试着跑一下

4年前 评论

多谢分享!还有一个问题想请教:代码执行完毕之前如果锁过期了怎么办?

3年前 评论

应该是这个?
$lua = <<<EOT
return redis.call('SET', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2])
EOT;
$res = Redis::eval($lua, 1,$key, 1, $time);

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!