前言
说起锁,其实还是一个很宽泛的概念,在java中,基础的就是synchronized关键字,ReentrantLock,但是就这两个就可以引出非常多的知识点,包括操作系统层面的monitor锁,AQS,volatile等等。不过在分布式环境中,在对共享资源进行操作时(最常见的就是一个秒杀的场景),单机的锁肯定是行不通的,于是就引申出了分布式锁的概念,分布式锁的实现方式主要有3种,这篇只讲讲redis实现的版本。
具体实现
redis实现的分布式锁网上还是有很多的,我看自己公司也有一个redis锁,不过看完以后感觉还是差了味道。在github上看到了一个很不错的,附上地址jedis-lock,里面的测试也很全,可以下载下来参考参考,后面的代码也主要是基于此分析。
其实里面的东西也没有太多,相信大部分同学还是能比较快去消化,里面最主要的就是acquire函数的实现,涉及到一些容易出现问题的场景以及解决的方案。直接上代码吧。
protected synchronized boolean acquire(Jedis jedis) throws InterruptedException {
//acquiryTimeoutInMillis为尝试获取锁超时时间
int timeout = acquiryTimeoutInMillis;
while (timeout >= 0) {
//lockExpiryInMillis:锁超时时间,防止这种情况:锁被其中一台机器占用,在释放锁之前,发生宕机,则锁会被持续占用,产生死锁
final Lock newLock = asLock(System.currentTimeMillis() + lockExpiryInMillis);
//若key=localKeyPath不存在,则set一个新锁,表示锁被占用
if (jedis.setnx(lockKeyPath, newLock.toString()) == 1) {
this.lock = newLock;
return true;
}
//判断锁超时的情况
final String currentValueStr = jedis.get(lockKeyPath);
final Lock currentLock = Lock.fromString(currentValueStr);
//如果考虑不全面,很可能代码是这么写的:
// if (currentLock.isExpiredOrMine(lockUUID)) {
// this.lock = newLock;
// return true;
// }
// 其实这会出现这么一个问题,比如有三台机器A,B,C,A目前占用着锁,B和C都去判断锁超时情况,然而就在B和C都在获取当前锁的信息时候,A突然挂了,
// 这个时候,若B和C都判断锁已经超时,那么,B和C都将返回true,麻烦就来了。。。
// 这时getset以及value比对可确定占有锁的对象,按上面模拟走一遍,另外说一句,redis时单进程单线程的,所以不会存在并发问题,
// 还有getset是指根据key插入新的value并且返回老的value,那么在执行jedis.getSet(lockKeyPath, newLock.toString())时,B和C必然有执行的
// 先后顺序,假设B先C后,B的oldValueStr为A锁的信息,C的oldValueStr为B锁的信息,B和C的currentValueStr均为A锁的信息,
// 那么通过currentValueStr和oldValueStr的比对,可以看出来
if (currentLock.isExpiredOrMine(lockUUID)) {
String oldValueStr = jedis.getSet(lockKeyPath, newLock.toString());
if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
this.lock = newLock;
return true;
}
}
//暂停一段时间进行重试,尝试获取锁超时时间减去单次暂停时间
timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;
Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
}
return false;
}
小坑
在测试文件中设计到一个JEDIS_AUTH的变量,在通过redis-cli连接进入交互模式后要使用CONFIG SET requirepass “your password”配置认证密码
思考
在学习时其实想到一个问题,这边的代码中时间都用了currentTimeMillis()函数来获取时间,如果在分布式环境中,如果说其中一台机器的时间遭到了恶意窜改,那不是出大问题了,换言之,如何去保证时间不同机器的时间同步的问题,后来通过一位师兄的帮助下,了解到ntp时间服务器的概念,代码网上也有很多,java通过NTP时间服务器获得时间,不过里面的服务器地址已失效,阿里云提供了ntp服务器地址,详见内网和公网NTP服务器和其他互联网基础服务,毕竟是爸爸啊XD.