ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

008-redis应用-01-分布式锁

2020-03-30 10:00:26  阅读:286  来源: 互联网

标签:01 key redis 指令 线程 expire setnx 008 分布式


一、概述

1.1、概念理解

  分布式应用进行逻辑处理时经常会遇到并发问题。分布式锁来限制程序的并发执行

  原子操作是指不会被线程调度机制打断的操 作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch 线程切换。

  分布式锁本质上要实现的目标就是在 Redis 里面占一个位置,当别的进程也要来占时,发现已经被占用,就只好放弃或者稍后再试。一个排它锁。

1.2、使用

  占位一般是使用 setnx(set if not exists) 指令,只允许被一个客户端占用。先来先占, 用完了,再调用 del 指令释放位置。

> setnx lockkey true 
OK
... do something critical ... 
> del lock
 (integer) 1

问题1、如果逻辑执行到中间出现异常了,可能会导致 del 指令没有被调用,这样 就会陷入死锁,锁永远得不到释放。

  于是在拿到锁之后,再给锁加上一个过期时间,比如 5s,这样即使中间出现异常也 可以保证 5 秒之后锁会自动释放。

> setnx lockkey true 
OK
> expire lockkey 5 
... do something critical ... 
> del lockkey
 (integer) 1

 

问题2、如果在 setnx 和 expire 之间服务器进程突然挂掉了,可能是因 为机器掉电或者是被人为杀掉的,就会导致 expire 得不到执行,也会造成死锁。

  这种问题的根源就在于 setnx 和 expire 是两条指令而不是原子指令。如果这两条指令可 以一起执行就不会出现问题。也许你会想到用 Redis 事务来解决。但是这里不行,因为 expire 是依赖于 setnx 的执行结果的,如果 setnx 没抢到锁,expire 是不应该执行的。事务里没有 if- else 分支逻辑,事务的特点是一口气执行,要么全部执行要么一个都不执行。

  为了解决这个疑难,Redis 开源社区涌现了一堆分布式锁的 library,专门用来解决这个问 题。实现方法极为复杂,小白用户一般要费很大的精力才可以搞懂。如果你需要使用分布式锁, 意味着你不能仅仅使用 Jedis 或者 redis-py 就行了,还得引入分布式锁的 library。

  为了治理这个乱象,Redis 2.8 版本中作者加入了 set 指令的扩展参数,使得 setnx 和 expire 指令可以一起执行,彻底解决了分布式锁的乱象。从此以后所有的第三方分布式锁 library 可以休息了。 

> set lockkey true ex 5 nx 
OK 
... do something critical ... 
> del lock:codehole

 

   上面这个指令就是 setnx 和 expire 组合在一起的原子指令,它就是分布式锁的 奥义所在。

二、使用中的问题

2.1、超时问题

  Redis 的分布式锁不能解决超时问题,

  问题描述,如果在加锁和释放锁之间的逻辑执行的太长,以至 于超出了锁的超时限制,就会出现问题。因为这时候锁过期了,第二个线程重新持有了这把锁, 但是紧接着第一个线程执行完了业务逻辑,就把锁给释放了,第三个线程就会在第二个线程逻 辑执行完之间拿到了锁。

  如,一个任务使用了分布式锁,开始时候设置锁key,10分钟过期,程序执行完毕删除key。

    看似没问题,如果任务实际执行需要12分钟,此时会出现上述情况,redis10分钟自动删除锁key,第二个任务获得锁执行,执行过程中第一个结束,释放锁key。会彻底乱掉的节奏。

  解决方案,

    方案一、Redis 分布式锁不要用于较长时间的任务。如果真的偶尔出现了,数据出现的小波错乱可能需要人工介入解决。

    方案二、更加安全的方案是为 set 指令的 value 参数设置为一个随机数,释放锁时先匹配 随机数是否一致,然后再删除 key。但是匹配 value 和删除 key 不是一个原子操作,Redis 也 没有提供类似于 delifequals 这样的指令,这就需要使用 Lua 脚本来处理了,因为 Lua 脚本可 以保证连续多个指令的原子性执行。

    设置value随机值

tag = random.nextint() # 随机数
if redis.set(key, tag, nx=True, ex=5):
    do_something()
    redis.delifequals(key, tag) # 假象的 delifequals 指令

    lua匹配删除

# delifequals
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

 

2.2、可重入性

  可重入性是指线程在持有锁的情况下再次请求加锁,如果一个锁支持同一个线程的多次加 锁,那么这个锁就是可重入的。

  比如 Java 语言里有个 ReentrantLock 就是可重入锁。Redis 分 布式锁如果要支持可重入,需要对客户端的 set 方法进行包装,使用线程的 Threadlocal 变量 存储当前持有锁的计数。不推荐使用。

 

 

标签:01,key,redis,指令,线程,expire,setnx,008,分布式
来源: https://www.cnblogs.com/bjlhx/p/12596757.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有