方式1:
獲取一個redis分布鎖
/** * 獲取一個redis分布鎖 * * @param lockKey 鎖住的key * @param lockExpireMils 鎖住的時長。如果超時未解鎖,視為加鎖線程死亡,其他線程可奪取鎖 * @return */ public boolean lock(String lockKey, long lockExpireMils) { return (Boolean) redisTemplate.execute((RedisCallback) connection -> { long nowTime = System.currentTimeMillis(); Boolean acquire = connection.setNX(lockKey.getBytes(), String.valueOf(nowTime + lockExpireMils + 1).getBytes()); if (acquire) { return Boolean.TRUE; } else { byte[] value = connection.get(lockKey.getBytes()); if (Objects.nonNull(value) && value.length > 0) { long oldTime = Long.parseLong(new String(value)); if (oldTime < nowTime) { //connection.getSet:返回這個key的舊值並設置新值。 byte[] oldValue = connection.getSet(lockKey.getBytes(), String.valueOf(nowTime + lockExpireMils + 1).getBytes()); //當key不存時會返回空,表示key不存在或者已在管道中使用 return oldValue == null ? false : Long.parseLong(new String(oldValue)) < nowTime; } } } return Boolean.FALSE; }); }
刪除鎖:(不建議,建議使用方式2的刪除) // 釋放鎖的時候,有可能因為持鎖之后方法執行時間大於鎖的有效期,此時有可能已經被另外一個線程持有鎖,所以不能直接刪除
/** * redis 解鎖:eval函數在redis集群環境中不支持, 具體查看spring源碼 * @See JedisClusterScriptingCommands * * Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); * @param lockName * @param uniqueId */ public void unlock(String lockName, String uniqueId) { String lockKey = LOCK_PREFIX+lockName; String successMsg = StrUtil.format("{}-{}解鎖成功[Redis]!",lockName,uniqueId); String failMsg = StrUtil.format("{}-{}解鎖失敗[Redis]!",lockName,uniqueId); String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; RedisScript<Boolean> redisScript = new DefaultRedisScript(script,Boolean.class); Boolean result = redisTemplate.execute(redisScript, new StringRedisSerializer(), new FastJsonRedisSerializer(Boolean.class), Collections.singletonList(lockKey),uniqueId); printResult(result, successMsg, failMsg); }
-----------方式2---------------------------------------------
原文:https://blog.csdn.net/qq_28397259/article/details/80839072
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisCluster; import redis.clients.jedis.JedisCommands; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; import java.util.UUID; /** * ************************************************************************* * <PRE> * @ClassName: : RedisDistributedLock * * @Description: : * * @Creation Date : Jan 28, 2021 6:47:03 PM * * @Author : Sea * * * </PRE> ************************************************************************** */ @Component public class RedisDistributedLock{ @Resource private RedisTemplate<String, Object> redisTemplate; 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(); } private final Logger logger = LoggerFactory.getLogger(RedisDistributedLock.class); public boolean setLock(String key, long expire) { try { RedisCallback<String> callback = (connection) -> { JedisCommands commands = (JedisCommands) connection.getNativeConnection(); String uuid = UUID.randomUUID().toString(); return commands.set(key, uuid, "NX", "PX", expire); }; String result = redisTemplate.execute(callback); return !StringUtils.isEmpty(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 ""; } public boolean releaseLock(String key,String requestId) { // 釋放鎖的時候,有可能因為持鎖之后方法執行時間大於鎖的有效期,此時有可能已經被另外一個線程持有鎖,所以不能直接刪除 try { List<String> keys = new ArrayList<>(); keys.add(key); List<String> args = new ArrayList<>(); args.add(requestId); // 使用lua腳本刪除redis中匹配value的key,可以避免由於方法執行時間過長而redis鎖自動過期失效的時候誤刪其他線程的鎖 // spring自帶的執行腳本方法中,集群模式直接拋出不支持執行腳本的異常,所以只能拿到原redis的connection來執行腳本 RedisCallback<Long> callback = (connection) -> { Object nativeConnection = connection.getNativeConnection(); // 集群模式和單機模式雖然執行腳本的方法一樣,但是沒有共同的接口,所以只能分開執行 // 集群模式 if (nativeConnection instanceof JedisCluster) { return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args); } // 單機模式 else if (nativeConnection instanceof Jedis) { return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args); } return 0L; }; Long result = redisTemplate.execute(callback); return result != null && result > 0; } catch (Exception e) { logger.error("release lock occured an exception", e); } finally { // 清除掉ThreadLocal中的數據,避免內存溢出 //lockFlag.remove(); } return false; } }
不管是加鎖還是解鎖,都要保持操作的原子性,否則就有可能產生死鎖。
springboot 項目 redisTempalte 配置
pring.redis.database=0 spring.redis.host=127.0.0.1 spring.redis.port=6379 #spring.redis.password=root #springboot2.x need unit ,db connect time out spring.redis.timeout=60s spring.redis.jedis.pool.max-active=-1 spring.redis.jedis.pool.max-wait=-1s spring.redis.jedis.pool.max-idle=300 spring.redis.jedis.pool.min-idle=5
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
配置類:
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import java.lang.reflect.Method; @Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { @Bean public KeyGenerator keyGenerator() { return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getName()); sb.append(method.getName()); for (Object obj : params) { sb.append(obj.toString()); } return sb.toString(); } }; } /** * 管理緩存 * * @param redisTemplate * @return */ @SuppressWarnings("rawtypes") @Bean public CacheManager CacheManager(RedisTemplate redisTemplate) { RedisCacheManager rcm = new RedisCacheManager(redisTemplate); return rcm; } /** * redisTemplate配置 * * @param factory * @return */ @SuppressWarnings({ "rawtypes", "unchecked" }) @Bean public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) { StringRedisTemplate template = new StringRedisTemplate(factory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } }