方式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; } }