1. 环境准备
依赖:在中添加Spring Data Redis:- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
复制代码 配置RedisTemplate:- @Configuration
- public class RedisConfig {
- @Bean
- public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
- RedisTemplate<String, Object> template = new RedisTemplate<>();
- template.setConnectionFactory(factory);
- template.setKeySerializer(new StringRedisSerializer());
- template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
- return template;
- }
- }
复制代码 2. 编写Lua脚本
以分布式锁为例,实现加锁和解锁的原子操作:
加锁脚本- local key = KEYS[1]
- local value = ARGV[1]
- local expire = ARGV[2]
- -- 如果key不存在则设置,并添加过期时间
- if redis.call('setnx', key, value) == 1 then
- redis.call('expire', key, expire)
- return 1 -- 加锁成功
- else
- return 0 -- 加锁失败
- end
复制代码 解锁脚本- local key = KEYS[1]
- local value = ARGV[1]
- -- 只有锁的值匹配时才删除
- if redis.call('get', key) == value then
- return redis.call('del', key)
- else
- return 0
- end
复制代码 3. 加载并执行脚本
定义脚本Bean:- @Configuration
- public class LuaScriptConfig {
- @Bean
- public DefaultRedisScript<Long> lockScript() {
- DefaultRedisScript<Long> script = new DefaultRedisScript<>();
- script.setLocation(new ClassPathResource("lock.lua"));
- script.setResultType(Long.class);
- return script;
- }
- }
复制代码 调用脚本:- @Service
- public class RedisLockService {
- @Autowired
- private RedisTemplate<String, Object> redisTemplate;
- @Autowired
- private DefaultRedisScript<Long> lockScript;
- public boolean tryLock(String key, String value, int expireSec) {
- List<String> keys = Collections.singletonList(key);
- Long result = redisTemplate.execute(
- lockScript,
- keys,
- value,
- String.valueOf(expireSec)
- );
- return result != null && result == 1;
- }
- }
复制代码 开发中的常见问题与解决方案
1. Lua脚本缓存问题
- 问题:每次执行脚本会传输整个脚本内容,增加网络开销。
- 解决:Redis会自动缓存脚本并返回SHA1值,Spring Data Redis的会自动管理SHA1。确保脚本对象是单例,避免重复加载。
2. 参数传递错误
问题:和数量或类型不匹配,导致脚本执行失败。
解决:明确区分参数类型:- // 正确传参示例
- List<String> keys = Arrays.asList("key1", "key2"); // KEYS数组
- Object[] args = new Object[]{"arg1", "arg2"}; // ARGV数组
复制代码 3. Redis集群兼容性
问题:集群模式下,所有操作的Key必须位于同一slot。
解决:使用定义hash tag,强制Key分配到同一节点:- String key = "{user}:lock:" + userId; // 所有包含{user}的Key分配到同一节点
复制代码 4. 脚本性能问题
问题:复杂Lua脚本可能阻塞Redis,影响性能。
解决:
- 避免在Lua中使用循环或复杂逻辑。
- 优先使用Redis内置命令(如、)。
5. 异常处理
问题:脚本执行超时或返回非预期结果。
解决:捕获异常并设计重试机制:- public boolean tryLockWithRetry(String key, int maxRetry) {
- int retry = 0;
- while (retry < maxRetry) {
- if (tryLock(key, "value", 30)) {
- return true;
- }
- retry++;
- Thread.sleep(100); // 短暂等待
- }
- return false;
- }
复制代码 完整示例:分布式锁
- // 加锁
- public boolean lock(String key, String value, int expireSec) {
- return redisTemplate.execute(
- lockScript,
- Collections.singletonList(key),
- value,
- String.valueOf(expireSec)
- ) == 1;
- }
- // 解锁
- public void unlock(String key, String value) {
- Long result = redisTemplate.execute(
- unlockScript,
- Collections.singletonList(key),
- value
- );
- if (result == null || result == 0) {
- throw new RuntimeException("解锁失败:锁已过期或非持有者");
- }
- }
复制代码 调试与优化建议
Redis CLI调试:- # 直接在Redis服务器测试脚本
- EVAL "return redis.call('setnx', KEYS[1], ARGV[1])" 1 mykey 123
复制代码 日志配置:- # application.properties
- logging.level.org.springframework.data.redis=DEBUG
复制代码 监控脚本执行时间:- # Redis慢查询日志
- slowlog-log-slower-than 5
- slowlog-max-len 128
复制代码 总结
通过Lua脚本,可以轻松实现Redis复杂操作的原子性,解决高并发下的竞态条件问题。在Spring Boot中,结合和,能够高效集成Lua脚本。开发时需注意参数传递、集群兼容性和异常处理,避免踩坑。
到此这篇关于用Lua脚本实现Redis原子操作的示例的文章就介绍到这了,更多相关Lua脚本实现Redis原子操作内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
来源:https://www.jb51.net/database/337574n9l.htm
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
|