如果key不存在,则将key初始化为0,然后自增1
如何key不存在,则将key设置成value
同setnx加锁
由于redis是单线程的且性能很快,所以比较适合做全局分布式锁。
基本流程就是在 *** 作可能某个全局冲突资源的时候,使用一个全局唯一key来判断是否有其他线程占用了资源,如果有其他线程占用,则报错退出或者循环等待。如果没有其他线程占用,则就可以通过添加分布式锁来占用这个资源,然后再执行后续的任务,在任务执行完成之后,再释放分布式锁,其他线程就可以继续使用这个资源了。
那么通过redis加锁的动作是什么呢?
简单加锁命令:
命令是:setnx
内部的实现机制就是判断这个key位置是不是有数据,没有数据就设置成value返回,有数据就返回一个特殊数值。
但是这里有一个问题是,如果占用资源的线程错误退出了,没有来得及释放分布式锁,这个锁就被永远的占用了
改进版的加锁:
命令是:1. setnx 2. expire
添加分布式锁的同时,添加一个锁锁过期的时间。这样,当加锁线程退出之后,至少等一段时间之后,锁是有机会释放掉的。
这里有一个小问题是,这两个命令是分开执行的,不是原子 *** 作。那么就存在理论上来说,第一个命令执行完之后,就出现错误,来不及执行expire命令的可能,一种办法是自己写lua脚本,可以实现多条命令的原子化执行。一种办法是引用一些开源库。在2.8版本之后,redis为了解决这个问题,提供了官方版的解法,就是命令:set key value nx expireTimeNum ex,将上述两个命令合并成了一个命令。
有了过期时间之后解决了一部分问题,但是也有可能出现锁都过期了,但是中间执行的任务还没有结束,第一个线程还在执行了,第二个线程已经拿到锁开始执行了,那么这时候第一个线程如果执行完成之后,那么就会将第二个线程的锁释放掉了。第二个线程释放锁的时候,要不然出错,要不然是释放的其他线程的锁,这样也会和预期不符。
如果单纯地要解决这个问题的话,可以在设置value的时候使用一个随机数,释放锁的时候,先判断这个随机数是否一致,如果一致再删除锁,否则就退出。但是判断value和删除key也不是一个原子 *** 作,这时候就需要使用lua脚本了。
上面的方案依然不能解决超时释放的问题,依然违背分布式锁的初衷。怎么办了?
解题思路是另外启动一个线程,它的任务就是每隔一段时间判断一下如果发现当前线程的任务快过期了还没有完成,则定期给当前线程的锁续个期。
有个开源库解决了这个问题,它大概率会比你实现得更好一些。这个库就是redisson,非常好记,就是redis的儿子son,连起来就是reidsson,虽然可能不是亲的,但是也足够了。
这个库里面有一个组件是watchdog,直译过来就是看门狗,它的作用就是每隔一段时间判断的。
再继续思考,还有一个更极端的问题是,redis如果是单节点的,它宕机了;或者是主备节点的,但是备份节点还没有来得及同步主节点的数据,主节点拿到锁之后,在同步数据之前就马上宕机了,则也有可能出现锁不住的问题。如果认为这是一个问题,想要解决这个问题,这个问题怎么解决了?
思路是在加锁的时候多加锁几台redis服务器,通常情况下redis部署的时候是2n+1台,那么在加锁的时候需要保证过半数服务器加锁成功了,也就是说n+1台服务器。这时候除非整个集群都不可用了,则这个安全性将大幅度提升。
这个问题也有开源库解决了,就是redis红锁。
下一个问题是分布式锁可以重入么?
如果想要实现可重入的分布式锁的话,需要在设置value的时候加上线程信息和加锁次数的信息。但是这是简单的思路,如果加上过期时间等问题之后,可重入锁就可能比较复杂了。
我介绍一下Redis分布式锁吧:
一、定义redis实现分布式锁的接口
[java] view plain copy print?package com.iol.common.util.concurrent.locks
import java.io.Serializable
/**
* Description: 定义redis实现分布式锁的算法<br />
* This program is protected by copyright IOL_SMALL_TAIL.<br />
* Program Name: IOL_SMALL_TAIL<br />
* Date: 2015年11月8日
*
* @author 王鑫
* @version 1.0
*/
public interface IRedisLockArithmetic extends Serializable {
/**
* 加锁算法<br />
* @param key
* @return
*/
public boolean lock(String key)
/**
* 解锁算法<br />
* @param key
* @return
*/
public boolean unLock(String key)
}
二、redis分布式锁基础算法实现
[java] view plain copy print?package com.iol.common.util.concurrent.locks.arithmetic
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import com.iol.common.util.concurrent.locks.IRedisComponent
import com.iol.common.util.concurrent.locks.IRedisLockArithmetic
/**
* Description: redis分布式锁基础算法实现<br />
* This program is protected by copyright IOL_SMALL_TAIL.<br />
* Program Name: IOL_SMALL_TAIL<br />
* Date: 2015年11月9日
*
* @author 王鑫
* @version 1.0
*/
public class RedisLockBaseArithmetic implements IRedisLockArithmetic {
/**
*serialVersionUID
*/
private static final long serialVersionUID = -8333946071502606883L
private Logger logger = LoggerFactory.getLogger(RedisLockBaseArithmetic.class)
/**
* redis *** 作方法
*/
private IRedisComponent redisComp
/**
* 超时时间,以毫秒为单位<br />
* 默认为5分钟
*/
private long overtime = 5 * 60 * 1000L
/**
* 休眠时长,以毫秒为单位<br />
* 默认为100毫秒
*/
private long sleeptime = 100L
/**
* 当前时间
*/
private long currentLockTime
/**
* @param redisComp the redisComp to set
*/
public void setRedisComp(IRedisComponent redisComp) {
this.redisComp = redisComp
}
/**
* @param overtime the overtime to set
*/
public void setOvertime(long overtime) {
this.overtime = overtime
}
/**
* @param sleeptime the sleeptime to set
*/
public void setSleeptime(long sleeptime) {
this.sleeptime = sleeptime
}
/* (non-Javadoc)
* @see com.iol.common.util.concurrent.locks.IRedisLockArithmetic#lock(java.lang.String, java.lang.Long)
*/
@Override
public boolean lock(String key) {
while(true) {
// 当前加锁时间
currentLockTime = System.currentTimeMillis()
if(redisComp.setIfAbsent(key, currentLockTime)) {
// 获取锁成功
logger.debug("直接获取锁{key: {}, currentLockTime: {}}", key, currentLockTime)
return true
} else {
//其他线程占用了锁
logger.debug("检测到锁被占用{key: {}, currentLockTime: {}}", key, currentLockTime)
Long otherLockTime = redisComp.get(key)
if(otherLockTime == null) {
// 其他系统释放了锁
// 立刻重新尝试加锁
logger.debug("检测到锁被释放{key: {}, currentLockTime: {}}", key, currentLockTime)
continue
} else {
if(currentLockTime - otherLockTime >= overtime) {
//锁超时
//尝试更新锁
logger.debug("检测到锁超时{key: {}, currentLockTime: {}, otherLockTime: {}}", key, currentLockTime, otherLockTime)
Long otherLockTime2 = redisComp.getAndSet(key, currentLockTime)
if(otherLockTime2 == null || otherLockTime.equals(otherLockTime2)) {
logger.debug("获取到超时锁{key: {}, currentLockTime: {}, otherLockTime: {}, otherLockTime2: {}}", key, currentLockTime, otherLockTime, otherLockTime2)
return true
} else {
sleep()
//重新尝试加锁
logger.debug("重新尝试加锁{key: {}, currentLockTime: {}}", key, currentLockTime)
continue
}
} else {
//锁未超时
sleep()
//重新尝试加锁
logger.debug("重新尝试加锁{key: {}, currentLockTime: {}}", key, currentLockTime)
continue
}
}
}
}
}
/* (non-Javadoc)
* @see com.iol.common.util.concurrent.locks.IRedisLockArithmetic#unLock(java.lang.String)
*/
@Override
public boolean unLock(String key) {
logger.debug("解锁{key: {}}", key)
redisComp.delete(key)
return true
}
/**
* 休眠<br />
* @param sleeptime
*/
private void sleep() {
try {
Thread.sleep(sleeptime)
} catch (InterruptedException e) {
throw new LockException("线程异常中断", e)
}
}
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)