上一篇我們介紹了一種redis 分佈式鎖,介紹了其應用場景和一些存在的問題,而今天我們介紹 redis 第二種分佈式鎖的使用;
這種鎖同時採用lua腳本保證原子性能很好解決以上問題。實例如下:
1、代碼如下:
package com.jy.utils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisCommands; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * RedisTemplate工具類 */ @Component public class RedisUtil implements ApplicationContextAware { private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class); private static RedisTemplate<Object, Object> redisTemplate; private static ThreadLocal<String> lockFlag = new ThreadLocal<String>(); public static final String UNLOCK_LUA; static { StringBuilder sb = new StringBuilder(); sb.append("if redis.call('get',KEYS[1]) == ARGV[1] "); sb.append("then "); sb.append(" return redis.call('del',KEYS[1]) "); sb.append("else "); sb.append(" return 0 "); sb.append("end "); UNLOCK_LUA = sb.toString(); } //加鎖核心方法 public static boolean setRedisLock(String key, long expire) { try { String result = redisTemplate.execute(new RedisCallback<String>() { @Override public String doInRedis(RedisConnection connection) throws DataAccessException { JedisCommands commands = (JedisCommands) connection.getNativeConnection(); String uuid = UUID.randomUUID().toString(); lockFlag.set(uuid);//給該線程設置唯一標識,釋放所得時候會用到 return commands.set(key, uuid, "NX", "PX", expire);//鎖和超時時間同時設置,保證改鎖的原子性 } }); logger.info("獲取鎖成功[{}]結果:[{}]",key,result); return StringUtils.isNotEmpty(result); } catch (Exception e) { logger.error("set redis occured an exception", e); } return false; } //釋放鎖的核心方法 public static boolean releaseRedisLock(String key) { /** 釋放鎖的時候,有可能因爲持鎖之後方法執行時間大於鎖的有效期, *此時有可能已經被另外一個線程持有鎖,所以不能直接刪除,需要判斷該線程的value 值(加鎖時的uuid 值), */ try { List<String> keys = new ArrayList<String>(); keys.add(key); List<String> args = new ArrayList<String>(); args.add(lockFlag.get());//設置需要刪除的線程的標識value值(每個線程精準刪除自己設置的鎖) /** 使用lua腳本刪除redis中匹配value的key, * 可以避免由於方法執行時間過長而redis鎖自動過期失效的時候誤刪其他線程的鎖 * 從而避免鎖的錯亂 */ Long result = redisTemplate.execute(new RedisCallback<Long>() { public Long doInRedis(RedisConnection connection) throws DataAccessException { Object nativeConnection = connection.getNativeConnection(); /** 單機模式 */ return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args); } }); return result != null && result > 0; } catch (Exception e) { logger.error("release lock occured an exception", e); } return false; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { redisTemplate = applicationContext.getBean(RedisTemplate.class); } }
2、這種redis 分佈式鎖,支持多線程、安全性更好,可以試一下。下篇我們再介紹一種redis 分佈式鎖,對比一下,根據不同場景選擇合適的分佈式鎖,敬請期待!