• 设为首页
  • 收藏本站
  • 积分充值
  • VIP赞助
  • 手机版
  • 微博
  • 微信
    微信公众号 添加方式:
    1:搜索微信号(888888
    2:扫描左侧二维码
  • 快捷导航
    福建二哥 门户 查看主题

    用Lua脚本实现Redis原子操作的示例

    发布者: 涵韵3588 | 发布时间: 2025-6-19 12:41| 查看数: 93| 评论数: 0|帖子模式

    1. 环境准备

    依赖:在
    1. pom.xml
    复制代码
    中添加Spring Data Redis:
    1. <dependency>
    2.     <groupId>org.springframework.boot</groupId>
    3.     <artifactId>spring-boot-starter-data-redis</artifactId>
    4. </dependency>
    复制代码
    配置RedisTemplate:
    1. @Configuration
    2. public class RedisConfig {
    3.     @Bean
    4.     public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    5.         RedisTemplate<String, Object> template = new RedisTemplate<>();
    6.         template.setConnectionFactory(factory);
    7.         template.setKeySerializer(new StringRedisSerializer());
    8.         template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    9.         return template;
    10.     }
    11. }
    复制代码
    2. 编写Lua脚本

    以分布式锁为例,实现加锁和解锁的原子操作:
    加锁脚本
    1. lock.lua
    复制代码
    1. local key = KEYS[1]
    2. local value = ARGV[1]
    3. local expire = ARGV[2]
    4. -- 如果key不存在则设置,并添加过期时间
    5. if redis.call('setnx', key, value) == 1 then
    6.     redis.call('expire', key, expire)
    7.     return 1 -- 加锁成功
    8. else
    9.     return 0 -- 加锁失败
    10. end
    复制代码
    解锁脚本
    1. unlock.lua
    复制代码
    1. local key = KEYS[1]
    2. local value = ARGV[1]
    3. -- 只有锁的值匹配时才删除
    4. if redis.call('get', key) == value then
    5.     return redis.call('del', key)
    6. else
    7.     return 0
    8. end
    复制代码
    3. 加载并执行脚本

    定义脚本Bean:
    1. @Configuration
    2. public class LuaScriptConfig {
    3.     @Bean
    4.     public DefaultRedisScript<Long> lockScript() {
    5.         DefaultRedisScript<Long> script = new DefaultRedisScript<>();
    6.         script.setLocation(new ClassPathResource("lock.lua"));
    7.         script.setResultType(Long.class);
    8.         return script;
    9.     }
    10. }
    复制代码
    调用脚本:
    1. @Service
    2. public class RedisLockService {
    3.     @Autowired
    4.     private RedisTemplate<String, Object> redisTemplate;
    5.     @Autowired
    6.     private DefaultRedisScript<Long> lockScript;

    7.     public boolean tryLock(String key, String value, int expireSec) {
    8.         List<String> keys = Collections.singletonList(key);
    9.         Long result = redisTemplate.execute(
    10.                 lockScript,
    11.                 keys,
    12.                 value,
    13.                 String.valueOf(expireSec)
    14.         );
    15.         return result != null && result == 1;
    16.     }
    17. }
    复制代码
    开发中的常见问题与解决方案


    1. Lua脚本缓存问题


    • 问题:每次执行脚本会传输整个脚本内容,增加网络开销。
    • 解决:Redis会自动缓存脚本并返回SHA1值,Spring Data Redis的
      1. DefaultRedisScript
      复制代码
      会自动管理SHA1。确保脚本对象是单例,避免重复加载。

    2. 参数传递错误

    问题:
    1. KEYS
    复制代码
    1. ARGV
    复制代码
    数量或类型不匹配,导致脚本执行失败。
    解决:明确区分参数类型:
    1. // 正确传参示例
    2. List<String> keys = Arrays.asList("key1", "key2"); // KEYS数组
    3. Object[] args = new Object[]{"arg1", "arg2"};      // ARGV数组
    复制代码
    3. Redis集群兼容性

    问题:集群模式下,所有操作的Key必须位于同一slot。
    解决:使用
    1. {}
    复制代码
    定义hash tag,强制Key分配到同一节点:
    1. String key = "{user}:lock:" + userId; // 所有包含{user}的Key分配到同一节点
    复制代码
    4. 脚本性能问题

    问题:复杂Lua脚本可能阻塞Redis,影响性能。
    解决:

    • 避免在Lua中使用循环或复杂逻辑。
    • 优先使用Redis内置命令(如
      1. SETNX
      复制代码
      1. EXPIRE
      复制代码
      )。

    5. 异常处理

    问题:脚本执行超时或返回非预期结果。
    解决:捕获异常并设计重试机制:
    1. public boolean tryLockWithRetry(String key, int maxRetry) {
    2.     int retry = 0;
    3.     while (retry < maxRetry) {
    4.         if (tryLock(key, "value", 30)) {
    5.             return true;
    6.         }
    7.         retry++;
    8.         Thread.sleep(100); // 短暂等待
    9.     }
    10.     return false;
    11. }
    复制代码
    完整示例:分布式锁
    1. // 加锁
    2. public boolean lock(String key, String value, int expireSec) {
    3.     return redisTemplate.execute(
    4.         lockScript,
    5.         Collections.singletonList(key),
    6.         value,
    7.         String.valueOf(expireSec)
    8.     ) == 1;
    9. }

    10. // 解锁
    11. public void unlock(String key, String value) {
    12.     Long result = redisTemplate.execute(
    13.         unlockScript,
    14.         Collections.singletonList(key),
    15.         value
    16.     );
    17.     if (result == null || result == 0) {
    18.         throw new RuntimeException("解锁失败:锁已过期或非持有者");
    19.     }
    20. }
    复制代码
    调试与优化建议

    Redis CLI调试:
    1. # 直接在Redis服务器测试脚本
    2. EVAL "return redis.call('setnx', KEYS[1], ARGV[1])" 1 mykey 123
    复制代码
    日志配置:
    1. # application.properties
    2. logging.level.org.springframework.data.redis=DEBUG
    复制代码
    监控脚本执行时间:
    1. # Redis慢查询日志
    2. slowlog-log-slower-than 5
    3. slowlog-max-len 128
    复制代码
    总结

    通过Lua脚本,可以轻松实现Redis复杂操作的原子性,解决高并发下的竞态条件问题。在Spring Boot中,结合
    1. RedisTemplate
    复制代码
    1. DefaultRedisScript
    复制代码
    ,能够高效集成Lua脚本。开发时需注意参数传递、集群兼容性和异常处理,避免踩坑。
    到此这篇关于用Lua脚本实现Redis原子操作的示例的文章就介绍到这了,更多相关Lua脚本实现Redis原子操作内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

    来源:https://www.jb51.net/database/337574n9l.htm
    免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

    最新评论

    QQ Archiver 手机版 小黑屋 福建二哥 ( 闽ICP备2022004717号|闽公网安备35052402000345号 )

    Powered by Discuz! X3.5 © 2001-2023

    快速回复 返回顶部 返回列表