前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis实现分布式锁(集群版)

Redis实现分布式锁(集群版)

作者头像
Tinywan
发布2019-07-15 17:15:56
1.6K0
发布2019-07-15 17:15:56
举报
文章被收录于专栏:开源技术小栈开源技术小栈

单机版

  • 获取锁:set file:9527 ${random_value} NX EX ${timeout}
  • 释放锁,调用lua脚本:
1    if redis.call("get",KEYS[1]) == ARGV[1] then
2        return redis.call("del",KEYS[1])
3    else
4        return 0
5    end
  • 这种实现方式有3大要点(也是面试概率非常高的地方)
    • `set `命令要用 `set key value px milliseconds nx`;
    • `value `要具有唯一性;
    • 释放锁时要验证 `value `值,不能误解锁;

事实上这类琐最大的缺点就是它加锁时只作用在一个Redis节点上,即使Redis通过sentinel保证高可用,如果这个master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况:

  • 在Redis的master节点上拿到了锁;
  • 但是这个加锁的key还没有同步到slave节点;
  • master故障,发生故障转移,slave节点升级为master节点;
  • 导致锁丢失。

正因为如此,Redis作者antirez基于分布式环境下提出了一种更高级的分布式锁的实现方式:Redlock。笔者认为,Redlock也是Redis所有分布式锁实现方式中唯一能让面试官高潮的方式。

集群版

  • 集群分部署架构

在主从架构下,之前那套分布式锁的机制,就已经失效了,原因正如之前说的:

如果A往Master放入了一把锁,然后再数据同步到Slave之前,Master crash,Slave被提拔为Master,这时候Master上面就没有锁了,这样其他进程也可以拿到锁,违法了锁的互斥性。

Redlock算法

针对Redis集群架构,redis的作者antirez提出了Redlock算法,来实现集群架构下的分布式锁。

在Redis的分布式环境中,我们假设有N个Redis master。这些节点完全互相独立,不存在主从复制或者其他集群协调机制。我们确保将在N个实例上使用与在Redis单实例下相同方法获取和释放锁。现在我们假设有5个Redis master节点,同时我们需要在5台服务器上面运行这些Redis实例,这样保证他们不会同时都宕掉。

为了取到锁,客户端应该执行以下操作:

  • 记下开始获取锁的时间 startTime(当前Unix时间,以毫秒为单位)
  • 依次尝试从5个实例,使用相同的key和具有唯一性的value(例如UUID)获取锁。当向Redis请求获取锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。
  • 例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis已经挂掉的情况下,客户端还在死死地等待响应结果。
  • 如果服务器端没有在规定时间内响应,客户端应该尽快尝试去另外一个Redis实例请求获取锁。

按照A->B->C的顺序,依次向这三台Master发送获取锁的命令。客户端在等待每台Master回响应时,都有超时时间timeout。 举个例子,客户端向A发送获取锁的命令,在等了timeout时间之后,都没收到响应,就会认为获取锁失败,继续尝试获取下一把锁

  • 如果获取到超过半数的锁,也就是 3/2+1 = 2把锁,这时候还没完,要记下当前时间endTime
  • 计算拿到这些锁花费的时间 costTime = endTime - startTime,如果costTime小于锁的过期时间expireTime,则认为获取锁成功
  • 如果获取不到超过一半的锁,或者拿到超过一半的锁时,计算出costTime>=expireTime,这两种情况下,都视为获取锁失败。如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。
  • 如果因为某些原因,获取锁失败(没有在至少N/2+1个Redis实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功,防止某些节点获取到锁但是客户端没有得到响应而导致接下来的一段时间不能被重新获取锁)。如果获取锁失败,需要向全部Master节点,都发生释放锁的命令,也就是那段Lua脚本。
案例
  1class RedisLock
  2{
  3    /**
  4     * 获取锁
  5     * @param string $lock_name 锁名
  6     * @param int $acquire_time 重复请求次数
  7     * @param int $lock_timeout 请求超时时间
  8     * @return bool|string
  9     */
 10    public static function acquireLock($lock_name, $acquire_time = 3, $lock_timeout = 20)
 11    {
 12        $identifier = md5($_SERVER['REQUEST_TIME'] . mt_rand(1, 10000000));
 13        $lock_name = 'AGENT_ORDER_LOCK:' . $lock_name;
 14        $lock_timeout = intval(ceil($lock_timeout));
 15        $end_time = time() + $acquire_time;
 16        while (time() < $end_time) {
 17            $script = <<<luascript
 18                 local result = redis.call('setnx',KEYS[1],ARGV[1]);
 19                    if result == 1 then
 20                        redis.call('expire',KEYS[1],ARGV[2])
 21                        return 1
 22                    elseif redis.call('ttl',KEYS[1]) == -1 then
 23                       redis.call('expire',KEYS[1],ARGV[2])
 24                       return 0
 25                    end
 26                    return 0
 27luascript;
 28            $result = location_redis()->evaluate($script, array($lock_name, $identifier, $lock_timeout), 1);
 29            if ($result == '1') {
 30                return $identifier;
 31            }
 32            // 锁饥饿的问题。获取锁失败后,随机休息一段时间
 33            usleep(100000); //  函数延迟代码执行若干微秒
 34        }
 35        return false;
 36    }
 37
 38    /**
 39     * 释放锁
 40     * @param string $lock_name 锁名
 41     * @param string $identifier 获取锁返回的标识
 42     * @return bool
 43     */
 44    public static function releaseLock($lock_name, $identifier)
 45    {
 46        $lock_name = 'AGENT_ORDER_LOCK:' . $lock_name;
 47        while (true) {
 48            $script = <<<luascript
 49                local result = redis.call('get',KEYS[1]);
 50                if result == ARGV[1] then
 51                    if redis.call('del',KEYS[1]) == 1 then
 52                        return 1;
 53                    end
 54                end
 55                return 0
 56luascript;
 57            $result = location_redis()->evaluate($script, array($lock_name, $identifier), 1);
 58            if ($result == 1) {
 59                return true;
 60            }
 61            break;
 62        }
 63        //进程已经失去了锁
 64        return false;
 65    }
 66
 67    /**
 68     * 添加黑白名单
 69     * @param string $ip IP地址
 70     * @param bool $isWhite 是白名单,否则为黑
 71     * @return int
 72     */
 73    public function addBlackWhiteList($ip, $isWhite = true)
 74    {
 75        if($isWhite){
 76            $result = location_redis()->sAdd('IP_WHITE_LIST:AGENT',$ip);
 77        }else{
 78            $result = location_redis()->sAdd('IP_BLACK_LIST:AGENT',$ip);
 79        }
 80        return $result;
 81    }
 82
 83    /**
 84     * 获取远程服务端IP
 85     * @return array|false|null|string
 86     */
 87    public static function getRemoteIp()
 88    {
 89        if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
 90            $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
 91        } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
 92            $ip = $_SERVER['HTTP_CLIENT_IP'];
 93        } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
 94            $ip = getenv('HTTP_X_FORWARED_FOR');
 95        } elseif (getenv('HTTP_CLIENT_IP')) {
 96            $ip = getenv('HTTP_CLIENT_IP');
 97        } elseif ($_SERVER['REMOTE_ADDR']) {
 98            $ip = $_SERVER['REMOTE_ADDR'];
 99        } else {
100            $ip = null;
101        }
102        Log::debug('[获取用户的IP]  '.$ip);
103        return $ip;
104    }
105}

参考

  • 如何用Redis实现分布式锁(1)——单机版
  • 如何用Redis实现分布式锁(2)——集群版
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-12-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Tinywan的杂货摊 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 单机版
  • 集群版
  • Redlock算法
    • 案例
    • 参考
    相关产品与服务
    云数据库 Redis
    腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档