74人参与 • 2025-03-13 • Redis
依赖:在pom.xml
中添加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; } }
以分布式锁为例,实现加锁和解锁的原子操作:
加锁脚本 lock.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
解锁脚本 unlock.lua
local key = keys[1] local value = argv[1] -- 只有锁的值匹配时才删除 if redis.call('get', key) == value then return redis.call('del', key) else return 0 end
定义脚本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; } }
defaultredisscript
会自动管理sha1。确保脚本对象是单例,避免重复加载。问题:keys
和argv
数量或类型不匹配,导致脚本执行失败。
解决:明确区分参数类型:
// 正确传参示例 list<string> keys = arrays.aslist("key1", "key2"); // keys数组 object[] args = new object[]{"arg1", "arg2"}; // argv数组
问题:集群模式下,所有操作的key必须位于同一slot。
解决:使用{}
定义hash tag,强制key分配到同一节点:
string key = "{user}:lock:" + userid; // 所有包含{user}的key分配到同一节点
问题:复杂lua脚本可能阻塞redis,影响性能。
解决:
setnx
、expire
)。问题:脚本执行超时或返回非预期结果。
解决:捕获异常并设计重试机制:
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中,结合redistemplate
和defaultredisscript
,能够高效集成lua脚本。开发时需注意参数传递、集群兼容性和异常处理,避免踩坑。
到此这篇关于用lua脚本实现redis原子操作的示例的文章就介绍到这了,更多相关lua脚本实现redis原子操作内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论