Redis 7.0+ 新特性:Functions / ACL v2
Functions —— 把 Lua 脚本从”代码里的字符串”变成”Redis 上的函数”
先搞清楚:Redis 为啥要跑 Lua 脚本?
你写两个 Redis 操作,要保证中间没人插一脚:
Python 代码里:
stock = r.get("stock:iphone18")
if int(stock) > 0:
r.decr("stock:iphone18")
问题:
第 1 行 GET 完,还没 DECR,另一个请求也 GET 到了库存=1
→ 两个人都觉得有货 → 都 DECR → 超卖 ❌
解法:把两个操作塞进 Lua 脚本,让 Redis 原子执行
lua = """
local stock = redis.call('GET', KEYS[1])
if tonumber(stock) > 0 then
return redis.call('DECR', KEYS[1])
end
return -1
"""
r.eval(lua, 1, "stock:iphone18")
Lua 脚本 = 让 Redis 一次性执行完,中间谁都插不进来。
这就叫原子性。
Functions 要解决什么?
你的项目里可能有几个常用的 Lua 脚本:
Script A: 扣库存(检查→减→返回结果)
Script B: 分布式锁(SET NX EX → Lua 解锁)
Script C: 滑动窗口限流(检查次数→+1→设过期→返回)
7.0 之前这些脚本住在哪?——住在你的 Python 代码里:
"""app/utils/lua_scripts.py"""
DECR_STOCK = """
local stock = redis.call('GET', KEYS[1])
if tonumber(stock) and tonumber(stock) > 0 then
return redis.call('DECR', KEYS[1])
end
return -1
"""
UNLOCK = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
end
return 0
"""
每次调用都要把脚本全文发给 Redis:
r.eval(DECR_STOCK, 1, "stock:iphone18")
→ 发送脚本全文,Redis 编译一次,执行
问题在哪?
① 脚本存在代码里 → 三个微服务就存三份 → 改脚本要改三处
② 每次调用都发脚本 → 浪费带宽(脚本可能几百行)
③ 脚本在代码里藏着 → 运维不知道 Redis 跑着哪些脚本
④ Redis 每次都要重新编译脚本
像什么?
你手机里存了"红烧肉的做法"。
每次做饭,你都要把全文念给厨师听。
厨师每次听完都要重新理解一遍。
你换了手机,做法得重新存一遍。
Functions 怎么解决?——把脚本”装进” Redis
你在 Redis 里注册一个函数,给它起个名字。
以后要用的时候,直接喊名字。
注册一次:
FUNCTION LOAD mylib "#!lua name=mylib\n redis.register_function('decr_stock', function(keys, args) ... end)"
翻译成人话:
"Redis,我注册一个叫 mylib 的函数库,
里面有个函数叫 decr_stock,
以后我喊这个名,你就执行这段 Lua。"
以后调用:
redis-cli FCALL decr_stock 1 stock:iphone18
→ 不用传脚本全文,就说"执行 decr_stock"
效果对比:
之前:你每次都要把"红烧肉做法"念给厨师听
现在:厨师把做法背下来了,你说"来个红烧肉"就行
直观对比 Functions 和 EVAL
EVAL(7.0 之前):
调用:
r.eval("local s=redis.call('GET',KEYS[1])..." , 1, "stock")
Redis 收到:
① 收到"local s=redis.call('GET',KEYS[1])..." (2KB)
② 编译成字节码
③ 执行
每次都要经历 ① 和 ②。
Functions(7.0 之后):
注册:
redis-cli FUNCTION LOAD mylib "lua脚本..." → 编译好存着
调用:
redis-cli FCALL decr_stock 1 "stock:iphone18"
Redis 收到:
① 按名字找到已编译好的字节码
② 直接执行(没有 ① 和 ② 的开销)
省了传脚本的带宽,省了编译的时间。
一个具体的项目场景
你在做 AI Agent 后端,有个需求:
限制每个用户每秒最多调用 10 次 LLM API。
你需要一个原子操作的限流器——不能有两个请求同时+1 导致计数不准。
不用 Functions 时:
你在 Python 代码里写一段 Lua 脚本(滑动窗口限流)。
每次请求都要把这段脚本全文发给 Redis。
用了 Functions 后:
Step 1:运维注册一次限流函数
FUNCTION LOAD rate_limit_lib "...脚本..."
→ 注册一个叫 rate_limit 的函数到 Redis
Step 2:你的代码调用
r.fcall("rate_limit", 1, f"ratelimit:user:{user_id}", "10", "60")
→ 按名字调,不传脚本全文
Step 3 :哪天想改限流策略(从 10 次改成 20 次)
不用改代码。
重新注册一次函数就行。
所有服务立刻生效。
ACL v2 —— 从”一把钥匙开所有门”到”每人一把不同的钥匙”
ACL 解决什么问题?
你项目里的 Redis 是这么用的:
一个密码,所有人共用。
你的 FastAPI 后端用这个密码 → 正常读写 √
你的监控脚本用这个密码 → 正常读 √
你的同事想看看 Redis 里有啥 → 也用这个密码 √
你的 CI/CD 脚本不小心跑了 FLUSHALL → 库没了 ❌
问题在哪?
所有人的权限是一样的。
后端只需要读写业务 key,但它也能执行 FLUSHALL。
监控只需要读 INFO,但它也能删数据。
你只想让同事查 key,但他也能改配置。
像什么?
你租了个房子,给所有租客同一个钥匙。
这把钥匙能开大门、能开卧室、能开保险柜。
保洁阿姨用这把钥匙 → 能进你卧室。
修水管的用这把钥匙 → 也能开你保险柜。
ACL 做了什么?
现在你可以创建多个"用户",
每个用户有独立的密码和独立的权限。
具体能控制三件事:
① 控制能执行哪些命令
+@read → 只能读(GET、HGET、MGET...)
+@write → 只能写(SET、HSET、LPUSH...)
-FLUSHALL → 禁止执行 FLUSHALL
-SHUTDOWN → 禁止关服务器
-CONFIG → 禁止改配置
+@all → 全部允许
② 控制能操作哪些 key
~* → 所有 key
~user:* → 只能操作 user: 开头的 key
~order:* → 只能操作 order: 开头的 key
③ 控制从哪里连
可选限制客户端 IP
一个具体的项目场景
你的项目里,至少需要三种用户:
用户 1:app_user(FastAPI 后端用的)
ACL SETUSER app_user on >password123 ~app:* ~cache:* +@read +@write
→ 只能读和写 app: 和 cache: 开头的 key
→ 不能删库、不能改配置、不能 KEYS 全量扫
用户 2:monitor_user(监控用的)
ACL SETUSER monitor on >monitor_pass ~* +@read +@connection
→ 只能读,任何 key 都可以读(监控要看指标)
→ 不能写、不能删
用户 3:admin_user(你手动操作的)
ACL SETUSER admin on >admin_pass ~* +@all
→ 全部权限
→ 你也可以创建这个用户但不设密码,因为本地连才用它
三把钥匙,各干各的:
后端 app_user → 只读写业务 key ✅
监控 monitor_user → 只能读 ✅
你 admin_user → 什么都能干,但只有你有这个密码 ✅
CI/CD 脚本想跑 FLUSHALL?→ 它用的是 app_user 的密码 → 被拒绝 ✅
ACL 落地后你就不用怕了
之前怕的事:
"线上不小心敲了个 FLUSHALL,库就没了"
现在:
ACL 限制了 app_user 不能执行 FLUSHALL。
就算你在代码里写了 r.flushall() → Redis 报错:无权限。
想删库?用 admin_user 的密码手动连上去敲。
之前怕的事:
"同事想查线上数据,给了他密码,
结果他手滑 SET 错了 key 搞出事故"
现在:
给他开一个 reader 用户,只能读不能写。
他查任他查,写不了。 ✅
一句话讲清
延伸提问:"Redis 7.0 有什么新特性?"
你:
"我比较关注 Functions 和 ACL v2。
Functions 是对 Lua 脚本的管理升级。
以前用 EVAL 每次传脚本全文,
现在注册到 Redis 上按名字调用,
省带宽、省编译、好管理,
改脚本不用改应用代码,重新注册就行。
ACL v2 是 Redis 终于有了"用户权限"。
以前一个密码通吃,能读能写能删库。
现在可以创建多个用户,
限制每个用户能执行的命令和能操作的 key。
我们项目就分了三个用户:
后端只读写业务 key、监控只能读、运维才有全部权限。
这样就算 CI/CD 脚本出了 bug 也不会把 Redis 搞炸。"
记忆口诀
Functions = 把 Lua 脚本注册成”名字”存在 Redis 上,按名字调用,省带宽省编译好管理。
ACL = 创建多个 Redis 用户,每个用户开不同权限——后端的只能读写业务 key,运维的才能 FLUSHALL。
Functions 解决”脚本散落在代码里不好管”的问题。
ACL 解决”一把钥匙开所有门,不安全”的问题。
速记卡(面试闪卡)
Q1:一句话讲清「Redis 7.0+ 新特性:Functions / ACL v2」到底是什么?
A:Functions 把 Lua 脚本注册成名字存 Redis 按名调用;ACL v2 让 Redis 终于有了多用户权限。
Q2:Functions 解决啥(stored functions) —— 怎么理解?
A:类比:以前用 EVAL 每次把几百行 Lua 脚本全文发给 Redis,像每次做饭都把菜谱念给厨师听、他还得重新理解。Functions 是 FUNCTION LOAD 注册一次、FCALL 喊名字就执行——菜谱背下来了,省带宽省编译好管理,改策略重新注册全服务生效。(Server-side functions)
Q3:EVAL vs Functions 对比(bandwidth / compile) —— 怎么理解?
A:类比:EVAL 每次经历”收脚本→编译→执行”三步,脚本散落在三个微服务就存三份、改要改三处。Functions 注册时编译好存着,调用只按名找字节码直接跑,省了传脚本的带宽和编译的时间,运维也看得见 Redis 跑了哪些函数。(Pay per call)
Q4:ACL v2 是什么(access control) —— 怎么理解?
A:类比:以前一个密码通吃,后端能读写、CI 脚本也能 FLUSHALL 把库删了——像租房给所有租客同一把能开保险柜的钥匙。ACL 创建多个用户,用 +@read/-FLUSHALL/~user:* 限制能执行的命令和能动的 key,后端/监控/运维各一把不同的钥匙。(Least privilege)
Q5:落地三用户场景(app/monitor/admin) —— 怎么理解?
A:类比:项目分三把钥匙:app_user 只读写 app:/cache: 开头的 key、不能删库;monitor_user 只能读任何 key 看指标、不能写;admin_user 才有全部权限。CI 脚本用的是 app_user 密码,它想 FLUSHALL 会被 Redis 直接拒——就算代码写了 r.flushall() 也报错无权限。(Defense in depth)
Q6:核心速记主线有哪些?
-
Functions:Lua 脚本注册成名字存 Redis,FCALL 按名调用,省带宽省编译
-
ACL v2:创建多用户,限制命令(+@read/-FLUSHALL)和 key(~user:*)
-
EVAL 每次传脚本全文+重编译;Functions 注册一次编译好复用
-
落地:app_user 读写业务、monitor_user 只读、admin_user 全权
-
效果:CI 脚本就算误调 FLUSHALL 也会被 ACL 拒绝
口诀
A:Functions 注册名,FCALL 喊一声;
Lua 背下来,编译不再停;
ACL 分三钥,权限划得明;
一把只开一,漏洞扫干净。