redis实现setnx,setex连用实现分布式锁
redis实现分布式锁
1.主要命令:
-
setnx setex
2.主要问题:
使用redis实现分布式锁,利用上面两个命令的特性。但是最重要的是锁要有过期时间,不然万一服务器宕机或者redis宕机,redis锁将永远得不到释放,出现死锁,其他线程一直获取不到资源。为了避免这种情况发生就必须保证这两个命令setnx与setex(设置过期时间)执行的原子性。
还有一个问题就是如果设置了过期时间,但是线程a在过期时间内没有执行完,这时可能会出现: B执行到一半,serverA才执行完,这是serverA会释放serverB得锁,会导致无锁。 解决方法保证释放删除得锁与当前锁住得锁是同一个资源,即key,value完全一样,可通过lua脚本,getValue和delKey是一个原子性操作。
3.解决办法:
1.lua脚本实现setnx,setex两个命令的原子性:
读取lua脚本
/** * 获取lua结果 * @param key * @param value * @return */ public Boolean luaExpress(String key,String value) { lockScript = new DefaultRedisScript<Boolean>(); lockScript.setScriptSource( new ResourceScriptSource(new ClassPathResource("add.lua"))); lockScript.setResultType(Boolean.class); // 封装参数 List<Object> keyList = new ArrayList<Object>(); keyList.add(key); keyList.add(value); Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList); Long expire = redisTemplate.getExpire(key); System.out.println("超时时间:"+expire); return result; }
lua脚本:
local lockKey = KEYS[1] local lockValue = KEYS[2] -- setnx info local result_1 = redis.call(SETNX, lockKey, lockValue) if result_1 == 1 then local result_2= redis.call(SETEX, lockKey,3600, lockValue) return result_1 else return result_1 end
2. lua脚本保证释放的是当前锁:
/** * 释放锁操作 * @param key * @param value * @return */ private boolean releaseLock(String key, String value) { lockScript = new DefaultRedisScript<Boolean>(); lockScript.setScriptSource( new ResourceScriptSource(new ClassPathResource("unlock.lua"))); lockScript.setResultType(Boolean.class); // 封装参数 List<Object> keyList = new ArrayList<Object>(); keyList.add(key); keyList.add(value); Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList); return result; }
unlock.lua
local lockKey = KEYS[1] local lockValue = KEYS[2] -- get key local result_1 = redis.call(get, lockKey) if result_1 == lockValue then local result_2= redis.call(del, lockKey) return result_2 else return false end
3. 采用RedisConnection获取JRedisCommand得java客户端:
-
https://docs.spring.io/spring-data/redis/docs/2.0.3.RELEASE/api/ 搜索redisconnection 点击RedisStringCommands 搜索set方法,即可查找到
public boolean setLock(String key, long expire) { try { Boolean result = redisTemplate.execute(new RedisCallback<Boolean>() { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { return connection.set(key.getBytes(), getHostIp().getBytes(), Expiration.seconds(expire) ,RedisStringCommands.SetOption.ifAbsent()); } }); return result; } catch (Exception e) { logger.error("set redis occured an exception", e); } return false; } public String get(String key) { try { RedisCallback<String> callback = (connection) -> { JedisCommands commands = (JedisCommands) connection.getNativeConnection(); return commands.get(key); }; String result = redisTemplate.execute(callback); return result; } catch (Exception e) { logger.error("get redis occured an exception", e); } return ""; }