1. 分布式锁实现的三种方式
- .数据库乐观锁;
- .基于Redis的分布式锁;
- .基于ZooKeeper的分布式锁。
这里主要介绍 第二种方式基于Redis的分布式锁。
2.可靠性
首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
1.互斥性。在任意时刻,只有一个客户端能持有锁。
2.不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
3.具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
4.解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
关于组件依赖自行下载
代码:
/**
* 获取分布式同步锁
*
* @param key
* @param value 可空
* @param expireTimeSeconds
* @return
*/
public static boolean getDistribLock(String key, int expireTimeSeconds)
{
long expired = new Date().getTime() + expireTimeSeconds + 1;
String rs = JedisUtils.setnx(key, String.valueOf(expired), expireTimeSeconds);
// TODO
// 如果拿不到锁的话, 可以考虑是否发生deadlock
return rs != null;
}
这里用redis里面setnx方法是为了保证key的唯一性,我们具体来看看setnx()方法的实现
/**
* 不存在则设置值,可用于同步锁
*
* @param key
* @param value
* @param expireTimeSeconds
* @return
*/
public static String setnx(String key, String value, int expireTimeSeconds)
{
Jedis jedis = getResource();
try
{
if (logger.isDebugEnabled())
{
logger.debug("setnx: key={}|value={}|expireTimeSeconds={}", key, value,
expireTimeSeconds);
}
else
{
logger.info("setnx: key={}|expireTimeSeconds={}", key, expireTimeSeconds);
}
return jedis.set(key, value, "NX", "EX", expireTimeSeconds);
}
finally
{
jedis.close();
}
}
jedis.set(key, value, “NX”, “EX”, expireTimeSeconds);分别用了5个参数,具体来看看这5个参数
第一个key, 使用key来当锁,因为key是唯一的。
第二个value 是通过 long expired = new Date().getTime() + expireTimeSeconds + 1;通过当前时间加上传进来的超时时间组成的时间戳转成字符串。 使用value的目的是要足上面的第四个条件 解铃还须系铃人,通过给value赋值知道 ,当前锁是谁请求加的,在解锁的时候就有了依据。
第三个参数"NX" 这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
第四个参数"EX" 我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
第五个参数为expireTimeSeconds,与第四个参数相呼应,代表key的过期时间
set()方法的结果就是:
1.当前没有锁(key不存在),就进行加锁操作,并设置过期时间,value表示加锁的客户端
2.已经存在锁,不做任何处理
由于我们是单机版的Redis,我们只需要满足可靠性即可,容错性暂不考虑。
互斥性: set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性
不会发生死锁: 由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁
解铃还须系铃人: 通过时间戳来赋值给value,代表加锁的客户端请求标识,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端
JedisUtils工具类是项目中封装好的了,由于线上是集群环境,考勤统计和定时推送都需要用到分布式锁