spring boot基于redis的LUA脚本 实现分布式锁【都是基于redis单点下】
一.spring boot 1.5.X 基于redis 的 lua脚本实现分布式锁
1.pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.core.script.RedisScript; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.stereotype.Component; import java.util.Collections; /** * spring boot 1.5.X * 使用redis 的 lua脚本 基于单点实现分布式锁 * * lua脚本作为原子性操作,保证加锁和设置超时时间 为原子性操作 * @author sxd * @date 2019/5/27 10:52 */ @Component public class RedisLock { @Autowired RedisTemplate redisTemplate; private static final Long SUCCESS = 1L; /** * 获取锁 * * @param lockKey redis的key * @param value redis的value要求是随机串,防止释放其他请求的锁 * @param expireTime redis的key 的过期时间 防止死锁,导致其他请求无法正常执行业务 * @return */ public boolean lock(String lockKey, String value, int expireTime) { 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), value, String.valueOf(expireTime)); return SUCCESS.equals(result); } /** * 释放锁 * * @param lockKey redis的key * @param value redis的value 只有value比对一致,才能确定是本请求 加的锁 才能正常释放 * @return */ public boolean unlock(String lockKey, String value) { 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); try { Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value); if (SUCCESS.equals(result)) { return true; } } catch (Exception e) { e.printStackTrace(); } return false; } }
二.spring boot 2.x 基于redis 的LUA脚本 实现分布式锁
1.pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--spring2.0集成redis所需common-pool2--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.4.2</version> </dependency> <!-- 使用redis的LUA脚本 需要序列化操作的jar--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency>
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.io.Serializable; /** * @author sxd * @date 2019/5/27 16:13 */ /** * @Description Redis配置类,替代SpringBoot自动配置的RedisTemplate,参加RedisAutoConfiguration */ @Configuration @AutoConfigureAfter(RedisAutoConfiguration.class) public class RedisConfig { @Bean public RedisTemplate<String, Serializable> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Serializable> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); //Jackson序列化器 Jackson2JsonRedisSerializer<Object> 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); //字符串序列化器 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); //普通Key设置为字符串序列化器 template.setKeySerializer(stringRedisSerializer); //Hash结构的key设置为字符串序列化器 template.setHashKeySerializer(stringRedisSerializer); //普通值和hash的值都设置为jackson序列化器 template.setValueSerializer(jackson2JsonRedisSerializer); template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } }
import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; /** * * spring boot 2.x版本 * @author sxd * @date 2019/5/27 16:11 */ @Component public class RedisLock2 { Logger logger = Logger.getRootLogger(); static final Long LOCK_SUCCESS = 1L; static final Long LOCK_EXPIRED = -1L; @Autowired RedisTemplate redisTemplate; //定义获取锁的lua脚本 private final static DefaultRedisScript<Long> LOCK_LUA_SCRIPT = new DefaultRedisScript<>( "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then return redis.call('pexpire', KEYS[1], ARGV[2]) else return 0 end" , Long.class ); //定义释放锁的lua脚本 private final static DefaultRedisScript<Long> UNLOCK_LUA_SCRIPT = new DefaultRedisScript<>( "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return -1 end" , Long.class ); /** * 加锁 * @param key redis键值对 的 key * @param value redis键值对 的 value 随机串作为值 * @param timeout redis键值对 的 过期时间 pexpire 以毫秒为单位 * @param retryTimes 重试次数 即加锁失败之后的重试次数,根据业务设置大小 * @return */ public boolean lock(String key,String value ,long timeout, int retryTimes) { try { logger.debug("加锁信息:lock :::: redisKey = " + key + " requestid = " + value); //组装lua脚本参数 List<String> keys = Arrays.asList(key); //执行脚本 Object result = redisTemplate.execute(LOCK_LUA_SCRIPT, keys,value,timeout); //存储本地变量 if(LOCK_SUCCESS.equals(result)) { logger.info("成功加锁:success to acquire lock:" + Thread.currentThread().getName() + ", Status code reply:" + result); return true; } else if (retryTimes == 0) { //重试次数为0直接返回失败 return false; } else { //重试获取锁 logger.info("重试加锁:retry to acquire lock:" + Thread.currentThread().getName() + ", Status code reply:" + result); int count = 0; while(true) { try { //休眠一定时间后再获取锁,这里时间可以通过外部设置 Thread.sleep(100); result = redisTemplate.execute(LOCK_LUA_SCRIPT, keys); if(LOCK_SUCCESS.equals(result)) { logger.info("成功加锁:success to acquire lock:" + Thread.currentThread().getName() + ", Status code reply:" + result); return true; } else { count++; if (retryTimes == count) { logger.info("加锁失败:fail to acquire lock for " + Thread.currentThread().getName() + ", Status code reply:" + result); return false; } else { logger.warn(count + " times try to acquire lock for " + Thread.currentThread().getName() + ", Status code reply:" + result); continue; } } } catch (Exception e) { logger.error("加锁异常:acquire redis occured an exception:" + Thread.currentThread().getName(), e); break; } } } } catch (Exception e1) { logger.error("加锁异常:acquire redis occured an exception:" + Thread.currentThread().getName(), e1); } return false; } /** * 释放KEY * @param key 释放本请求对应的锁的key * @param value 释放本请求对应的锁的value 是不重复随即串 用于比较,以免释放别的线程的锁 * @return */ public boolean unlock(String key,String value) { try { //组装lua脚本参数 List<String> keys = Arrays.asList(key); logger.debug("解锁信息:unlock :::: redisKey = " + key + " requestid = " + value); // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁 Object result = redisTemplate.execute(UNLOCK_LUA_SCRIPT, keys, value); //如果这里抛异常,后续锁无法释放 if (LOCK_SUCCESS.equals(result)) { logger.info("解锁成功:release lock success:" + Thread.currentThread().getName() + ", Status code reply=" + result); return true; } else if (LOCK_EXPIRED.equals(result)) { //返回-1说明获取到的KEY值与requestId不一致或者KEY不存在,可能已经过期或被其他线程加锁 // 一般发生在key的过期时间短于业务处理时间,属于正常可接受情况 logger.warn("解锁异常:release lock exception:" + Thread.currentThread().getName() + ", key has expired or released. Status code reply=" + result); } else { //其他情况,一般是删除KEY失败,返回0 logger.error("解锁失败:release lock failed:" + Thread.currentThread().getName() + ", del key failed. Status code reply=" + result); } } catch (Exception e) { logger.error("解锁异常:release lock occured an exception", e); } return false; } }
来源 https://www.cnblogs.com/sxdcgaq8080/p/10931246.html
相关推荐
本教程将深入探讨如何在SpringBoot应用中实现基于Redis的分布式锁。 首先,Redis之所以常被用作分布式锁的实现,是因为其具有以下优点: 1. **高可用性**:Redis支持主从复制,可以确保在单点故障时仍有服务可用。...
本文将深入探讨如何使用Redis实现分布式锁,以及如何利用自旋式加锁和Lua脚本实现原子性解锁。 首先,我们来理解分布式锁的基本概念。分布式锁是在多节点之间共享资源时,用于协调各个节点的访问控制机制。在分布式...
“基于Redis和Lua脚本的分布式锁的实现” 基于Redis和Lua脚本的分布式锁的实现是使用Redis和Lua脚本来实现分布式锁的技术。分布式锁是指在分布式系统中,多个节点之间需要协调和同步的机制,以避免同时访问共享资源...
在现代的分布式系统中,服务的高可用性和稳定性通常依赖于多副本部署。然而,这种方式也会引入并发控制问题,特别是当多个...在SpringBoot中,结合RedisTemplate和Lua脚本,我们可以构建高效、可靠的分布式锁解决方案。
例如,使用`PERSIST`命令检查锁是否已过期并续租,或者使用lua脚本原子性地完成获取和设置锁。 7. **Spring Data Redis的RedisLockReleaser**:Spring Data Redis提供了一个`RedisLockReleaser`接口,可以用来在...
同时,Redis还支持lua脚本,可以进一步提高锁操作的原子性。 为了深入了解和实践这两种分布式锁的实现,你可以从压缩包中提取相关代码,分析其设计思路和操作流程。此外,深入学习Zookeeper和Redis的相关文档,理解...
# 使用lua脚本确保原子性 EVAL "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then redis.call('expire', KEYS[1], ARGV[2]) return 1 else return 0 end" 1 lock_key client_id timeout ``` 2. 锁的释放:...
在实际应用中,还要注意锁的性能优化,例如减少网络通信次数,合理设置锁的过期时间,以及使用Lua脚本进行原子操作等。同时,为了监控和调试,还可以记录锁的获取和释放日志,以便分析锁的使用情况。 总结来说,...
现在很多项目单机版已经不满足了,分布式变得越受欢迎,同时也带来很多问题,分布式锁也变得没那么容易实现,分享一个redis分布式锁工具类,里面的加锁采用lua脚本(脚本比较简单,采用java代码实现,无须外部调用...
为了避免这种情况,可以使用Lua脚本来执行加锁和解锁的操作,因为Lua脚本在Redis中是原子性执行的,可以保证操作的原子性,从而保证锁的安全性。 4. 性能与可用性:使用Redis实现分布式锁,可以保证很高的性能,...
Redis 分布式锁是分布式系统中解决并发控制和数据一致性问题的一种常见机制。在大型分布式应用中,单机锁无法满足需求,因为它们局限于单个服务器。Redis 的高可用性和低延迟特性使其成为实现分布式锁的理想选择。...
例如,获取锁和设置过期时间可以写成一个Lua脚本,确保这两个操作的原子性。 6. 锁的释放:释放锁时,使用`DEL`命令删除对应的键。同时,为了防止由于异常导致的锁无法释放,可以使用超时机制或者死锁检测。 7. ...
总结,SpringBoot与Redis的整合为我们提供了便捷的键值存储和操作能力,而引入Lua脚本则进一步增强了Redis的功能,实现了高效、一致的数据处理。通过理解并熟练掌握这些知识点,我们可以构建出稳定且高性能的分布式...
以下是一个简单的分布式锁实现的Lua脚本示例: ```lua if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then redis.call('expire', KEYS[1], ARGV[2]) return true else return false end ``` 在Java中,我们...
Redisson实现分布式锁的原子性是通过lua脚本来实现的。在lock方法中,Redisson会调用tryAcquireAsync方法,传入leaseTime和当前加锁线程的id。tryAcquireAsync方法会根据leaseTime是不是-1来判断使用哪个分支加锁。...
- 用户发起请求,服务端接收到请求后,首先通过Redis Lua脚本尝试获取锁。 - 如果获取成功,执行业务逻辑,然后在完成操作后释放锁。 - 如果获取失败,说明有其他请求正在执行相同操作,此时可以直接返回错误信息...
在处理Redis事务时,我们需要根据实际需求选择合适的方法,可能是简单的`MULTI/EXEC`,也可能是基于Lua脚本的分布式事务,甚至采用更复杂的分布式事务管理框架。理解这些概念和技术,有助于我们在实际项目中更好地...
3. 锁的释放:确保在异常情况下也能正确释放锁,可以使用lua脚本来保证原子操作。 总的来说,Redis作为分布式锁的一种实现方式,通过巧妙的设计和优化,可以在高并发场景下提供高效且可靠的锁服务。但同时,我们也...
Redis 分布式锁基于其原子操作(如 SETNX、EXPIRE)和 Lua 脚本实现,提供了高可用和可扩展的解决方案。 1. **SETNX 命令**: Redis 的 `SETNX` 命令用于设置 key 的值,但只有当 key 不存在时才会执行。这在实现...
- 可以利用`DEL`或`UNLINK`命令释放锁,或者使用lua脚本确保释放锁的原子性。 - RedLock算法通过在多个Redis实例上创建锁,提高锁的可用性。 2. **Zookeeper分布式锁** - 创建临时节点表示获取锁,节点存活代表...