前言
Redis(Remote Dictionary Server) 是一个使用 C 语言编写的,开源的(BSD许可)高性能非关系型(NoSQL)的键值对数据库。
Redis 可以存储键和五种不同类型的值之间的映射。键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合。
与传统数据库不同,Redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。
另外,Redis 也经常用来做分布式锁。除此之外,Redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。
本篇博客将详细介绍 Redis 分布式锁。
为了保证锁正常可用,一般需要满足下面几个条件:
- 互斥性。在同一时刻,只有一个客户端能持有锁。
- 不能发生死锁。
- 具有容错性。
- 加锁和解锁必须是同一个进程。
实现方式
方案1 setnx + setex
首先使用setnx加锁。返回1表示枷锁成功。成功获得锁后,为了避免进程挂掉,锁无法释放的情况,在setnx之后,需要立马执行setex设置过期时间。
但这种情况存在问题,如果在setnx之后,setex之前,进程挂掉了,依然会导致锁无法释放的情况。不过这种情况出现的概率非常低,所以在2.6.12之前,都是这样用。
方案2 set key value nx ex
在2.6.12版本,redis官方给set命令加了一些参数:
SET key value [EX seconds] [PX milliseconds] [NX|XX]
set key value nx ex 30 // 如果不存在key,就设置key-value,有效期30秒
EX,过期时间,单位秒
PX,过期时间,单位毫秒
NX,not exist,如果不存在才设置成功
XX,exist exist 如果存在才设置成功
通过这个命令就可以实现原子操作加锁。但依旧会存在问题,就是我们使用锁之后,如果需要主动释放锁,并不能验证锁是哪个客户端获得的。
方案3 random value + lua script
set时,value取随机数,确保这个随机数只有对应的客户端知道。
然后释放锁时,执行一段lua脚本,先判断value是否匹配。如果匹配,就解锁。
Long RELEASE_SUCCESS = 1L;
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;