redis实现分布式锁
锁的目的是保证同一资源在同一个时间内只有一个线程访问该资源,实现ACID种的隔离性;
一般实现锁的方式有这么几种:
- synchorinized 代码锁
- 数据库行锁或者表锁(例如乐观锁)
- redis分布式锁
前2种锁大部分情况下主要用在单体应用下,而redis分布式锁则主要用在分布式系统种。下面简单的列举一下redis分布式锁的实现流程:
在使用锁之前,我们得先考虑一些问题:
- 互斥性
- 防死锁
- 锁的续期
- 加所和解锁必须为同一个进程
带着这些问题,我们简单的介绍一下redis中如何解决:
- 加锁
要想获得锁,我们必须现获得一个锁,redis中获取一个分布式锁的伪代码:
// setnx 命令即为获取redis分布式锁,返回1,则为获取到了锁,反之为没有获取到锁
setnx(key,value)
- 释放锁
获取到锁了以后,在执行同步代码以后,我们得释放锁,伪代码:
del(key)
锁无法释放的问题
那么这里有一个问题:线程A和线程B同时获取分布式锁,线程A先获取到,但是意外发生了,线程A挂掉了,这时候还没来得及进行del释放锁;那么线程B永远获取不到锁,
那么这里我们就要给线程A获取到的锁添加一个过期时间。伪代码:
//获取锁
> setnx(key,value)
//设置过期时间 :30s
> expire(key,30)
....执行同步代码块.....
> del(key)
也就是解决了上面一个问题,但是也引发了另外一个问题:由于setnx(key,value) 和expire(key,expiretime) 这是两个命令,无法保证原子性;这样一来,上叙问题又重现了,没有在根本上解决问题,而setnx又不支持同时设定过期时间,那么有没有一条命令就把两件事干完呢?答案:有 如下伪代码:
// set(key,value,expireTime,NX)
> set("mylock","11121233343dfasdfe12312",30,NX)
锁提前释放的问题
我们在set(key,value,expireTime,NX)时设置了过期时间,但是30s我们的代码还没处理完业务,这个lock就过期了,这个锁就释放了,而这个时候线程B又获取到了锁,当线程A处理完业务后,执行del,其实这个时候del的是线程B的锁,
这时候我们不妨在执行del时,先判断一下是否时线程A的锁。那么我们在set key时,可以把当前节点的ID或者线程ID 一同设置到key中,这样判断起来就有根据;
线程A线程B并发的问题
虽然我们通过 set value时 添加线程ID或者节点ID可以避免误删锁,但是这里又有一个问题,就是在同一时间内,线程A和线程B都访问了同一资源,这显然不符合隔离性;
我们可以通过给线程A添加一个守护线程,隔一段时间就去查一下线程A是否还持有锁,业务有没有处理完,没有,守护线程就给线程A的锁续命20s,线程A业务处理完以后,同步销毁守护线程。