排版好烦,怎么排版啊!
上一篇写的是基础内容,这篇开始进入正题。
redis最基础的运用之一就是分布式锁,我之前所理解的分布式锁和常规的多线程中的锁机制是一样的(为了保证共享数据的可靠性,不至于出现一种混沌状态),这篇作者就详细解释,并用代码示例解释了一遍什么是分布式锁。
开篇作者也简单介绍了一下原子操作,何谓原子操作,来自wiki的解释为:所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch 线程切换。由于并发的时候对数据的读写并不是原子的,因此就会导致数据出现混沌状态,很显然这是不行的,接下来就是如何解决这个问题的过程。
由于作者解释的setnx让我觉得云里雾里,我从其他地方查询之后去到官方的文档(set,setnx)说明,可能加载比较感人,因此我做了一下搬运和翻译(凑合看,英语渣)(翻译完了才发现上一篇说的文档里有,不过没更新到6.0)
分布式锁的实现就是官方对set功能的一次拓展,具体使用的方式就是官方对于set命令的pattern中的 SET resource-name anystring NX EX max-lock-time (可以看一下我翻译的那篇),在该拓展实现之前为了实现分布式锁是非常复杂的,原因是setnx和expire是两条指令而不是一条(意思就是在两条指令执行过程中间可能导致锁的不正确acquire或者release)。
分布式锁面临的问题
超时问题
该问题产生的原因在于任务执行的时间可能会长于设置锁时的timeout时间。作者举的例子是,第一个线程执行完成之前锁已经超时,这就导致第二个线程成功acquire了这把锁,但是在第二个线程acquire了锁之后,第一个线程的任务终于完成了,将锁release了,这个时候第三个线程就能够acquire这把锁。这就是分布式锁的超时问题。
为了解决这个问题,作者提出了两种方案,第一种就是不要将分布式锁用于执行时间较长的任务,第二种是为set指令的value设置一个参数,在需要release锁的时候先匹配该参数是否一致,如果一致那么再release这把锁。显而易见的,第二种方案是更好的解决方案,但是很容易就能察觉到第二种方案和在拓展set指令之前一样,需要执行两个命令才能完成这个流程,可能这中间就出现了什么乱七八糟的问题,针对这个问题,作者留下了一个lua脚本~,因为lua脚本能够保证多个原子性操作的连续执行。脚本内容如下:
# delifequals if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
可重入性
可重入性就是一个线程能够在持有锁的情况下请求加锁,那么这个锁就是可重入的。比如 Java 语言里有个 ReentrantLock 就是可重入锁(python中的RLock、local)。作者在文中明确表示不推荐使用可重入锁,因为实现起来需要考虑很多东西,导致非常复杂,并且表示完全可以不适用到可重入锁(python中的RLock还会降低性能)。这部分代码我就不附上了,感兴趣的可以找一下。