浅析Redis分布式锁
近期工作遇到需要业务场景如下,需要每天定时推送给另一系统一批数据,但是由于系统是集群部署的,会造成统一情况下任务争用的情况,所以需要增加分布式锁来保证一定时间范围内有一个Job来完成定时任务.前期考虑的方案有采用ZooKeeper分布式任务,Quartz分布式任务调度,但是由于Zookeeper需要增加额外组件,Quartz需要增加表,并且项目中现在已经有Redis这一组件存在,所以考虑采用Redis分布式锁的情况来完成分布式任务抢占这一功能
记录一下走过的弯路.
第一版本:
@Override publicLongset(Stringkey,Tvalue,LongcacheSeconds){ if(valueinstanceofHashMap){ BoundHashOperationsvalueOperations=redisTemplate.boundHashOps(key); valueOperations.putAll((Map)value); valueOperations.expire(cacheSeconds,TimeUnit.SECONDS); } else{ //使用map存储 BoundHashOperationsvalueOperations=redisTemplate.boundHashOps(key); valueOperations.put(key,value); //秒 valueOperations.expire(cacheSeconds,TimeUnit.SECONDS); } returnnull; } @Override publicvoiddel(Stringkey){ redisTemplate.delete(key); }
采用set和del完成锁的占用与释放,后经测试得知,set不是线程安全,在并发情况下常常会导致数据不一致.
第二版本:
/**
*分布式锁
*@paramrange锁的长度允许有多少个请求抢占资源
*@paramkey
*@return
*/
publicbooleangetLock(intrange,Stringkey){
ValueOperationsvalueOper1=template.opsForValue();
returnvalueOper1.increment(key,1)<=range;
}
/**
*初始化锁,设置等于0
*@paramkey
*@paramexpireSeconds
*@return
*/
publicvoidinitLock(Stringkey,LongexpireSeconds){
ValueOperationsoperations=template.opsForValue();
template.setKeySerializer(newGenericJackson2JsonRedisSerializer());
template.setValueSerializer(newGenericJackson2JsonRedisSerializer());
operations.set(key,0,expireSeconds*1000);
}
/**
*释放锁
*@paramkey
*/
publicvoidreleaseLock(Stringkey){
ValueOperationsoperations=template.opsForValue();
template.setKeySerializer(newGenericJackson2JsonRedisSerializer());
template.setValueSerializer(newGenericJackson2JsonRedisSerializer());
template.delete(key);
}
采用redis的increament操作完成锁的抢占.但是释放锁时,是每个线程都可以删除redis中的key值.并且initLock会降上一次的操作给覆盖掉,所以也废弃掉此方法
最终版本:
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.data.redis.connection.RedisConnectionFactory;
importorg.springframework.data.redis.connection.jedis.JedisConnection;
importorg.springframework.stereotype.Service;
importorg.springframework.util.ReflectionUtils;
importredis.clients.jedis.Jedis;
importjava.lang.reflect.Field;
importjava.util.Collections;
@Service
publicclassRedisLock{
privatestaticfinalStringLOCK_SUCCESS="OK";
privatestaticfinalStringSET_IF_NOT_EXIST="NX";
privatestaticfinalStringSET_WITH_EXPIRE_TIME="PX";
privatestaticfinalLongRELEASE_SUCCESS=1L;
@Autowired
privateRedisConnectionFactoryconnectionFactory;
/**
*尝试获取分布式锁
*@paramlockKey锁
*@paramrequestId请求标识
*@paramexpireTime超期时间
*@return是否获取成功
*/
publicbooleanlock(StringlockKey,StringrequestId,intexpireTime){
FieldjedisField=ReflectionUtils.findField(JedisConnection.class,"jedis");
ReflectionUtils.makeAccessible(jedisField);
Jedisjedis=(Jedis)ReflectionUtils.getField(jedisField,connectionFactory.getConnection());
Stringresult=jedis.set(lockKey,requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,expireTime);
if(LOCK_SUCCESS.equals(result)){
returntrue;
}
returnfalse;
}
/**
*释放分布式锁
*@paramlockKey锁
*@paramrequestId请求标识
*@return是否释放成功
*/
publicbooleanreleaseLock(StringlockKey,StringrequestId){
Stringscript="ifredis.call('get',KEYS[1])==ARGV[1]thenreturnredis.call('del',KEYS[1])elsereturn0end";
Objectresult=getJedis().eval(script,Collections.singletonList(lockKey),Collections.singletonList(requestId));
if(RELEASE_SUCCESS.equals(result)){
returntrue;
}
returnfalse;
}
publicJedisgetJedis(){
FieldjedisField=ReflectionUtils.findField(JedisConnection.class,"jedis");
ReflectionUtils.makeAccessible(jedisField);
Jedisjedis=(Jedis)ReflectionUtils.getField(jedisField,connectionFactory.getConnection());
returnjedis;
}
}