方式1:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
比较简洁的一个方式
@Slf4j @Component public class RedisDistributedLock { @Resource private RedisTemplate<String, Object> redisTemplate; private static String PREFIX="rlk_"; /** * 加锁 , redis版本升级2.1以上 **/ public Boolean getLock(String key,String value,Long lockExpireSec){ if(StringUtils.isBlank(key)){ return false;} // key序列化 // value序列化 redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new FastJsonRedisSerializer<>(Object.class)); return this.redisTemplate.opsForValue().setIfAbsent(PREFIX+key,value, Duration.ofSeconds(lockExpireSec)); } /** * 获取一个redis分布锁 * @param lockKey 锁住的key * @param value 锁住的value, 解锁时,value 必须一致 * @param acquireTimeoutMils 获取超时时间 * @param lockExpireSec 锁住的时长。如果超时未解锁,视为加锁线程死亡,其他线程可夺取锁 * @return */ public boolean getLock(String lockKey, String value,long acquireTimeoutMils, long lockExpireSec) { long now = System.currentTimeMillis(); Boolean isLock = false; while (true){ isLock = getLock(lockKey, value, lockExpireSec); if(isLock==true){ return true; } if(System.currentTimeMillis()>=now+acquireTimeoutMils) { //获取超时 return false; } try { Thread.sleep(10l); }catch (Exception e){ e.printStackTrace(); } } } /** * 释放锁 **/ public Boolean releaseLock(String key,String value){ if(StringUtils.isBlank(key)){return false;} String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript,Long.class); // Long releaseStatus = (Long)this.redisTemplate.execute(redisScript,new StringRedisSerializer(), new FastJsonRedisSerializer(Long.class), Collections.singletonList(PREFIX+key),value); Long releaseStatus = (Long)this.redisTemplate.execute(redisScript,Collections.singletonList(PREFIX+key),value); return releaseStatus==1L; } /** * 删除锁 * @param key */ public void delete(String key) { redisTemplate.delete(key); } }
使用 if(getClock()){ todo()}else(todo_other())
获取一个redis分布锁
package com.sea.utils; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.Collections; import java.util.Objects; /** * ************************************************************************* * <PRE> * @ClassName: : RedisDistributedLock * * @Creation Date : Jan 28, 2021 6:47:03 PM * * @Author : Sea * * </PRE> ************************************************************************** */ @Component public class RedisDistributedLock{ @Resource private RedisTemplate<String, Object> redisTemplate; /** * 获取一个redis分布锁 *@param nowValue : 枷锁是的唯一值,只有key-value 匹配才可以删除锁 UUID * @param lockKey 锁住的key * @param lockExpireSec 锁住的时长。如果超时未解锁,视为加锁线程死亡,其他线程可夺取锁 * @return (如果锁住时间比较长,一定要手动释放锁) */ public boolean setLock(String lockKey, String nowValue , long lockExpireSec) { return (Boolean) redisTemplate.execute((RedisCallback) connection -> { Boolean acquire = connection.setNX(lockKey.getBytes(), nowValue.getBytes()); if (acquire) { connection.expire(lockKey.getBytes(), lockExpireSec); return Boolean.TRUE; } return Boolean.FALSE; }); } /** * 获取一个redis分布锁 * @param lockKey 锁住的key * @param lockExpireMils 锁住的时长。如果超时未解锁,视为加锁线程死亡,其他线程可夺取锁 * @return (不用释放锁,用与短暂锁住) */ public boolean setlock(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; }); } /** * 释放锁 * @param key * @param value * @return */ public boolean releaseLock(String key, String value) { String lua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(lua, Long.class); Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), value); return 1L == result; } }
删除锁:(建议使用方式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); }
/** * 释放锁 * @param key * @param value * @return */ public boolean releaseLock(String key, String value) { String lua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(lua, Long.class); Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), value); return 1L == result; }
-----------方式2---------------------------------------------
原文:https://blog.csdn.net/qq_28397259/article/details/80839072
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
package com.icil.bx.common.utils; 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.Arrays; import java.util.List; /** * ************************************************************************* * <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); /** * edis.set(key,value,"NX","EX",timeOut)【保证加锁的原子操作】 * key就是redis的key值作为锁的标识,value在这里作为客户端的标识,只有key-value都比配才有删除锁的权利【保证安全性】 * @param key * @param value * @param expire * @return */ public boolean setLock(String key,String value, long expire) {//1000 * 60 try { RedisCallback<String> callback = (connection) -> { JedisCommands commands = (JedisCommands) connection.getNativeConnection(); return commands.set(key, value, "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 ""; } /** * * @param key * @param value:枷锁时候的值 value在这里作为客户端的标识,只有key-value都比配才有删除锁的权利【保证安全性】 * @return */ public boolean releaseLock(String key,String value) { //使用Lua脚本:先判断是否是自己设置的锁,再执行删除 // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁 // spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本 try { List<String> keys = Arrays.asList(key); List<String> args = Arrays.asList(value); 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; } }