Lua脚本在Redis中具备原子性,根本原因在于Redis单线程串行执行整个脚本,而非语法或长度;SpringBoot中需确保传参、加载、调用三步正确,否则即使简单脚本也会失败。

直接说结论:Lua 脚本在 Redis 中的原子性,不是靠“语法正确”或“写得短”实现的,而是靠 Redis 单线程串行执行整个脚本这一底层机制。Spring Boot 里只要传参、加载、调用三步没踩坑,就能稳住原子性;反之,哪怕脚本逻辑再简单,EVALSHA 没预热、KEYS 传错、序列化器乱设,照样崩。
为什么不能用多个 Redis 命令代替 Lua
Redis 命令本身是原子的(比如 INCR、DECR),但“检查库存是否 > 0”+“执行 DECR”这两步合起来就不是原子操作。网络往返之间存在时间窗口,高并发下必然超卖。Lua 脚本把这两步压进一次 EVAL 请求,在 Redis 服务端一次性跑完,中间不会被其他命令插入——这才是真正能防超卖的原子性。
- 常见错误现象:
redis.clients.jedis.exceptions.JedisDataException: ERR Error running script (call to f_): @user_script:,多数是脚本里用了集群不支持的命令(如KEYS)或参数类型错(比如把整数当字符串传) - 必须显式通过
KEYS和ARGV传参,不能在脚本里硬编码 key 或拼接字符串 - 脚本中禁止使用
TIME、SRANDMEMBER等非确定性命令,否则在 Redis Cluster 下会报错
DefaultRedisScript 加载脚本的两种方式怎么选
用内联字符串还是外部 .lua 文件,本质是可维护性与部署可控性的权衡。线上环境强烈建议用外部文件 + 预加载,避免每次启动都拼字符串出错。
- 外部文件方式:把脚本放在
src/main/resources/lua/limit.lua,用new ClassPathResource("lua/limit.lua")加载,配合@Bean注册为 Spring Bean,启动时自动注册到 Redis(触发SCRIPT LOAD) - 内联字符串方式:适合极简逻辑(比如单条
SET加过期),但脚本一变就得改 Java 代码,且容易因引号、换行导致字符串拼错 - 关键点:
script.setResultType(Long.class)必须和脚本实际返回值类型一致,否则反序列化失败抛ClassCastException
执行时 keys 和 args 怎么传才不出错
redisTemplate.execute(script, keys, args...) 这一步最容易翻车。Redis 不认 Java 对象,只认原始字符串,所以序列化器必须对齐。
- 必须用
StringRedisTemplate或显式设置RedisTemplate的 key/value 序列化器为StringRedisSerializer,否则KEYS[1]可能变成\xac\xed\x00\x05t\x00\x06mykey这种乱码 -
keys参数只能传List<string></string>,对应脚本里的KEYS[1]、KEYS[2]……不能为null,哪怕只用一个 key 也要写Arrays.asList("user:123") -
args是Object...,但最终都会转成字符串,所以Integer、Long、String都可以,但别传LocalDateTime这类复杂对象 - 集群模式下,所有 key 必须落在同一个 slot,否则报
CROSSSLOT错误;解决办法是用{user:123}这种带大括号的 key 命名,让 Redis 按括号内内容哈希
eval 和 evalsha 到底该用哪个
生产环境必须用 evalsha,但首次调用前得确保脚本已 LOAD。Spring Boot 的 DefaultRedisScript 在注入 Bean 时默认会做一次 SCRIPT LOAD,但这个行为在某些低版本 spring-data-redis 中可能静默失败。
- 上线前务必验证:连上 Redis 执行
SCRIPT EXISTS <sha1></sha1>,确认返回 1;或者用SCRIPT FLUSH清空后重启应用,看日志有没有Loaded script字样 - 如果脚本变更但没清缓存,旧 SHA1 还在,新逻辑根本不会执行——这种问题排查起来非常隐蔽
- 不要在代码里手动拼
EVALSHA命令,一律走redisTemplate.execute(),它内部会自动 fallback 到EVAL(当 SHA1 不存在时)
最常被忽略的一点:Lua 脚本里所有业务逻辑必须是确定性的,不能依赖外部状态(比如系统时间、随机数),也不能做 HTTP 调用或文件读写。它只是 Redis 的一段嵌入式逻辑,越简单、越纯粹,越可靠。











