直接拼接str()会导致Redis Lua脚本出错,因key含空格、换行或恶意代码会引发语法错误或命令注入;必须严格区分keys(键名,用于ACL和集群路由)与args(参数值),由redis-py自动序列化。

为什么直接用 str() 拼接 Lua 脚本会出问题
Redis Lua 脚本里不能依赖客户端对参数做字符串化,因为 redis-py 传入的 keys 和 args 是由 Redis 服务端原样接收的:服务端把 keys 当作键名数组、args 当作任意值数组,不经过任何 JSON 或 Python 字符串转义。如果你手动拼接 "SET " + key + " " + str(value),一旦 key 含空格、换行或非 ASCII 字符,Lua 就会报 ERR Error running script,甚至触发命令注入(比如 key = "foo; return redis.call('FLUSHDB')")。
redis-py 的 eval 和 evalsha 怎么传参才安全
必须严格区分 keys 和 args 两组参数,且只传原始 Python 对象(str、int、bytes、None),由 redis-py 自动序列化为 Redis 兼容格式:
-
keys列表里的每个元素都会被 Redis 当作真实键名处理,用于 ACL 权限校验和集群路由;不能传空字符串或含{的键名(除非你明确知道分片规则) -
args列表里的值在 Lua 中通过ARGV[i]访问,自动转为 Redis 字符串(None→nil,True/False→"1"/"0"),但不会被当键名使用 - 脚本里必须用
KEYS[1]、ARGV[1]显式引用,不能硬编码键名或值
示例:
r = redis.Redis()
script = "return redis.call('GET', KEYS[1]) == ARGV[1]"
result = r.eval(script, keys=['mykey'], args=['expected_value'])
常见错误:混淆 keys 和 args 导致 ACL 拒绝或集群失败
Redis 6+ ACL 和 Redis Cluster 都依赖 keys 参数做权限与路由判断。如果该放 keys 的键名错塞进 args,会出现:
- ACL 报错:
NOPERM this script has keys that are not allowed - 集群模式下报错:
CROSSSLOT Keys in request don't hash to the same slot(因为args不参与 slot 计算) - Lua 脚本里误用
ARGV[1]当键名调用redis.call('DEL', ARGV[1]),结果删错 key
原则:只要这个字符串要被 Redis 当作键名参与命令执行(GET、HGET、EXPIRE 等),就必须放进 keys;其他数据(如比较值、过期时间、JSON 字符串)一律走 args。
如何复用脚本并避免 SHA 冲突或加载失败
用 script_load + evalsha 能省网络开销,但要注意:
-
evalsha失败时(NoScriptError)必须 fallback 到eval,不能静默忽略 - 不同 Redis 实例间 SHA 不共享,跨实例部署需确保脚本已预加载,或统一用
eval - 脚本内容有变(哪怕空格增减),SHA 就不同;建议用模块级常量存脚本字符串,避免拼接导致 SHA 波动
安全写法:
sha = r.script_load(script)
try:
result = r.evalsha(sha, keys=['a'], args=['b'])
except redis.exceptions.NoScriptError:
result = r.eval(script, keys=['a'], args=['b'])
最易被忽略的是:Lua 脚本里所有键名都必须显式出现在 keys 参数中 —— 即使是硬编码的键,只要它参与了 Redis 命令调用,就违反了原子性保证和集群约束。别图省事绕过 keys。











