redis實現分散式鎖(基於lua指令碼操作)
阿新 • • 發佈:2018-12-25
lua指令碼能保證redis的原子性操作,redis使用springboot的redistemplate
/** * create by abel * create date 2018/11/16 11:28 * describe:請輸入專案描述 */ public class RedisLockService { private static Logger logger = LoggerFactory.getLogger(RedisLockService.class); private RedisTemplate<String, Object> redisTemplate; private static final Long SUCCESS = 1L; /** * 鎖的key */ private String lockKey; /** * 預設過期時間/ms */ private int expireTime = 30 * 1000; /** * 預設重試時間/ms */ private int retryTime = 60 * 1000; /** * 預設睡眠時間/ms */ private int sleepTime = 200; /** * uuid */ private static final String requestId = UUID.randomUUID().toString().replace("-", ""); /** * 是否鎖定標誌 */ private volatile boolean locked = false; /** * 構造器 * * @param redisTemplate * @param lockKey 鎖的key */ public RedisLockService(RedisTemplate<String, Object> redisTemplate, String lockKey) { this.redisTemplate = redisTemplate; this.lockKey = lockKey; } /** * 構造器 * * @param redisTemplate * @param lockKey 鎖的key * @param retryTime 獲取鎖的超時時間 */ public RedisLockService(RedisTemplate<String, Object> redisTemplate, String lockKey, int retryTime) { this(redisTemplate, lockKey); this.retryTime = retryTime; } /** * 構造器 * * @param redisTemplate * @param lockKey 鎖的key * @param retryTime 獲取鎖的超時時間 * @param expireTime 鎖的有效期 */ public RedisLockService(RedisTemplate<String, Object> redisTemplate, String lockKey, int retryTime, int expireTime) { this(redisTemplate, lockKey, retryTime); this.expireTime = expireTime; } /** * 構造器 * * @param redisTemplate * @param lockKey 鎖的key * @param retryTime 獲取鎖的超時時間 * @param expireTime 鎖的有效期 * @param sleepTime 鎖的睡眠期 */ public RedisLockService(RedisTemplate<String, Object> redisTemplate, String lockKey, int retryTime, int expireTime, int sleepTime) { this(redisTemplate, lockKey, retryTime, expireTime); this.sleepTime = sleepTime; } public String getLockKey() { return lockKey; } /** * 獲取鎖 * * @return */ private boolean lock() { try { long startTime = System.currentTimeMillis(); while (true) { if (this.setNx()) { locked = true; logger.info("[" + lockKey + "]redis get lock success"); return true; } //這一步是key存在返回false時執行,原因可能是key未刪除或者未到過期時間 //在這個時間(retryTime)內,可以重試多次,如果這個時間內鎖還未釋放,那麼需要主動釋放 if (System.currentTimeMillis() - startTime > retryTime) { locked = false; this.del(); logger.info("[" + lockKey + "]redis get lock error"); return false; } Thread.sleep(sleepTime); } } catch (InterruptedException e) { logger.error("[" + lockKey + "]redis lock error::{}", e); locked = false; return false; } } /** * 釋放鎖 * * @return */ private boolean unLock() { try { //刪除key boolean delFlag = this.del(); logger.info("[" + lockKey + "]redis unlock success->" + delFlag); return delFlag; } catch (Exception e) { logger.error("[" + lockKey + "]redis unlock error::{}", e); return false; } } /** * lua指令碼執行能保證原子性: * 1.如果key不存在,設定key成功,同時設定過期時間,返回true; * 2.如果key存在,設定key失敗,返回false; * * @return true-成功,false-失敗 */ private boolean setNx() { String script = "if redis.call('setNx',KEYS[1],ARGV[1]) then " + " if redis.call('get',KEYS[1])==ARGV[1] then " + " return redis.call('expire',KEYS[1],ARGV[2]) " + " else " + " return 0 " + " end " + "end"; RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class); //對非string型別的序列化 redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new StringRedisSerializer()); Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId, String.valueOf(expireTime)); return SUCCESS.equals(result); } /** * 刪除key * * @return true-成功,false-失敗 */ private boolean del() { String script = "if redis.call('get', KEYS[1]) == ARGV[1] " + "then " + " return redis.call('del', KEYS[1])" + "else " + " return 0 " + "end"; RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new StringRedisSerializer()); Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId); return SUCCESS.equals(result); } /** * 匿名類包裝:無返回值 * * @param runnable 需要鎖住的執行程式碼 * @return true 獲鎖成功;false 獲鎖失敗 */ public boolean wrap(Runnable runnable) { if (lock()) { try { runnable.run(); } catch (Exception e) { logger.error(e.getMessage(), e); if (e instanceof BusinessException) throw (BusinessException) e; else throw new BusinessException(ErrorCode.SERVER_BUSY, e.getMessage()); } finally { unLock(); } return true; } else return false; } /** * 匿名類包裝:帶返回值 */ public <V> V wrap(Callable<V> callable) { if (lock()) { try { return callable.call(); } catch (Exception e) { logger.error(e.getMessage(), e); if (e instanceof BusinessException) throw (BusinessException) e; else throw new BusinessException(ErrorCode.SERVER_BUSY, e.getMessage()); } finally { unLock(); } } else return null; } }
使用方法:
String lockKey = "key"; RedisLockService lock = new RedisLockService(redisTemplate, lockKey); boolean result = lock.wrap(() -> { try { //程式碼塊 } catch (Exception e) { logger.info("lock error::{}", e); if (e instanceof BusinessException) { if (ErrorCode.SERVER_ERROR.equals(((BusinessException) e).getErrorCode())) { throw new BusinessException(ErrorCode.SERVER_ERROR, "lock error"); } } } }); if (!result) { throw new BusinessException(ErrorCode.SERVER_ERROR, "lock error"); }