Redis分布式锁在高并发场景的实现方式

为了防止分布式系统中的多个进程之间相互干扰,需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。

Redis加锁

原理很简单,set 一个 锁-key,如果成功则说明加锁成功,反之则失败。
为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下几个条件:

互斥性。在任意时刻,只有一个客户端能持有锁。
不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

基于以上条件,采用set扩展参数,保证原子性操作:SET lock-key "lock-client" EX 10086 NX
lock-key "lock-client" 指定 加锁client,解锁时用于判断。
EX 10010 指定过期时间
NX 只在键不存在时,才对键进行设置操作。效果等同于SETNX 命令。

只不过早期版本redis不支持set的扩展参数,这就需要用到 lua 脚本了
加锁可以在高版本借助set命令实现原子操作,但解锁就不可以了,依然得用到lua脚本。

Redis+Lua

Redis在2.6版本推出了 lua 脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下:

  1. 减少网络开销:可以将多个请求通过脚本的形式一次发送,减少网络时延。
  2. 原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他请求插入。因此在脚本运行过程中无需担心会出现竞态条件,无需使用事务。
  3. 复用:客户端发送的脚本会永久存在redis中,这样其他客户端可以复用这一脚本,而不需要使用代码完成相同的逻辑。

Redis 解锁

需要在获得 lock-key 后判断加锁对象是否为当前client,是,则解锁。Lua 脚本如下:
if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end
执行方式:eval;
eval 参数列表:eval lua-script key-num [key1 key2 key3 ....] [value1 value2 value3 ....],参数解析:

eval 代表执行Lua语言的命令;
lua-script 代表Lua语言脚本;
key-num 表示参数中有多少个key,需要注意的是Redis中key是从1开始的,如果没有key的参数,那么写0;
[key1 key2 key3…] 是key作为参数传递给Lua语言,也可以不填,但是需要和key-num的个数对应起来;
[value1 value2 value3 …] 这些参数传递给Lua语言,他们是可填可不填的。

eval执行示例:eval "redis.call('set',KEYS[1],ARGV[1])" 1 lua-key lua-val

完整解锁执行脚本:
eval "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 lock-key client-val

为什么不优先考虑使用 Redis 事务

简单提两句这个事情,redis 本身有提供事务功能,即保证一系列复合操作是原子性执行。不过事务有两个问题:
1、Redis事务不支持Rollback(重点)
2、基于上面1点,对于事务中已成功执行的操作,无法回滚。

其实解锁操作,用事务倒是无所谓,因为是先get到key值,比较后再删除,即便第二步操作失败,第一步的get也没有实际影响;
但如果加锁时,使用set、expire可能会有问题,比如set后未设置过期时间前进程异常挂掉,导致锁没有过期时间产生死锁。所以加锁尽量使用高版本(redis2.6及以上版本)的set附加expire参数执行吧。

参考样例-PHP版

    // 加锁操作
    function lock($timeout = 3) {
        // 加锁的key
        $mtkey = 'lock:your_lock_key';
        // 随机生成id用于解锁操作,也可用自己业务中其它具有唯一性标识的数值
        $mtid = uniqid(mt_rand(1000, 9999));
        // 获取锁的超时时间
        $end = time() + $timeout;
        while (time() <= $end) {
            // NX: 不存在时设置;PX:过期时间(毫秒);
            if ($redis->set($mtkey, $mtid, array('NX', 'PX' => 1000))) {
                return $mtid;
            }
            usleep(1000);
        }
        return '';
    }

    // 解操操作(将自己设置的锁删除)
    function unLock($mtkey = 'lock:your_lock_key', $mtid) {
        $script = <<<LUA
    if redis.call("get", KEYS[1]) == ARGV[1] then
        return redis.call("del", KEYS[1])
    end
LUA;
        /**
         * eval 第一个参数是要执行的LUA脚本内容
         * 第二个参数是传递的参数
         * 第三个参数是指传递的参数中前X个是放到LUA中的 KEYS 表,剩余的则放到LUA中的 ARGV 表
         * LUA中的“表”类似数组,索引以1开始。
         */
        $redis->eval($script, array($mtkey, $mtid), 1);
    }

----------End----------

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容