分布式锁,是控制分布式系统或者不同系统之间共同访问共享资源的一种锁的实现,当我们的不同主机共享了某一种资源的时候往往通过互斥来除去彼此的干扰,来保证一致性。
那么,什么是分布式锁,以及分布式锁需要解决什么问题呢?
- 互斥性
指,任意时刻,只能有一个客户端获得锁
- 安全性
锁,只能被持有该锁的客户端删除,不能被其他客户端删除
- 死锁
获取锁的客户端,出了意外宕机了,锁无法释放,资源也就被永远锁住了,也就出现了死锁
- 容错
当客户端出现了宕机,可以实现高可用
接下来说说,我们如何通过Redis来实现分布式锁
- 首先,我们使用 get <key> 检查被上了分布式锁的key,是否已经被上锁,返回nil就是没锁,返回string就是锁的name
- 然后使用setnx < key > < lockname > 来设置锁住的key,和锁的名字。如果已经被锁了,就返回0,反之就返回1
既然设置了锁,那锁也总是得有失效时间的,总不能一直去锁着手动去释放,很麻烦,所以我们就需要设置锁的过期时间,操作如下:
假设我们给刚才的锁,设置10s的过期时间,然后再来检查锁的状态:
可以明显发现,设置之后,锁的状态有了明显转变。
实现的伪代码如下:
但是这么写,有一个问题,如果我们用setnx设置了锁之后,设置过期时间之前就过期了,就会一直处于被锁的状态,其他的线程也就无法获得锁了。也就是说,也就是说,无法保证原子性。
于是,redis就又有了一种新方法,可以让setnx和expire揉在一起,创造出有过期时间的锁。
操作如下:
SET key value [EX seconds] [PX milliseconds] [NX|XX]
其中:
EX seconds:设置键的过期时间为second秒
PX milliseconds:设置键的过期时间为millisecond毫秒
NX:只在键不存在的时候,才对键进行设置操作
XX:只在键已经存在的时候,才对键进行设置操作
最后,在Set成功完成的时候,返回OK,否则返回nil
不难看出,一开始设置分布式锁,设置了10s过期时间,所以第二次失败了,过了十秒重新设置就成功了,另外,ex和px nx和xx都是二选一的参数。
在开发的时候,伪代码的操作如下,和redis操作,如出一辙。
但如果大量的key同时过期,该怎么办呢?
集中过期,因为清除大量的key会很耗时间,就会出现短暂的卡顿现象。我们可以给每个key在一定范围内设置随机的过期时间。