浅析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){ ValueOperations operations=template.opsForValue(); template.setKeySerializer(newGenericJackson2JsonRedisSerializer()); template.setValueSerializer(newGenericJackson2JsonRedisSerializer()); operations.set(key,0,expireSeconds*1000); } /** *释放锁 *@paramkey */ publicvoidreleaseLock(Stringkey){ ValueOperations operations=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; } }