Redis Functions 怎样管理函数版本发布
我在把一段 Lua 逻辑从应用代码迁到 Redis Functions 时,最容易混淆的不是 FCALL 的参数,而是“版本”到底属于函数、函数库,还是客户端调用名。Redis Functions 的正确发布单位是完整 library:库里的函数一起加载、一起替换,调用方依赖稳定的函数名。这样做,发布包可以保留、盘点和回滚,客户端也不必跟着每次改库换一套 API。
官方地址:https://redis.io/docs/latest/develop/programmability/functions-intro/
把每次发布保存成一份完整的 library 文件,使用固定的 library 名和函数名,通过FUNCTION LOAD REPLACE替换当前库;发布前保留上一份制品,发布后用FUNCTION LIST盘点函数集合。Redis Cluster 还要把加载动作覆盖到所有主节点。
先把版本边界放在 library 上
Redis Functions 与临时脚本不同:函数属于 Redis 管理的 library,库会随数据持久化并复制到副本;一个 library 可以注册多个函数,但库内容不能只挑其中一个函数单独更新。每次更新都应提交一份完整定义,这就是版本管理的第一条边界。
因此,建议把发布文件当作制品管理,例如 order_ops-v1.lua、order_ops-v2.lua。文件名可以带版本,Redis 内部的 library 名 order_ops 和对外函数名 reserve_order、cancel_order 则尽量保持稳定。
用稳定函数名隔离客户端变化
一个可维护的库通常有三个层次:shebang 声明库名和执行引擎;注册函数提供对外入口;客户端用 FCALL 调用入口。版本差异放在函数实现内部,只有确实改变了输入输出契约时,才增加新的函数名或新库名。
#!lua name=order_ops
-- 版本标记只用于发布制品追踪,不改变客户端调用名
-- release: r2
local function reserve_order(keys, args)
-- keys[1] 是订单键,所有会访问的键都显式放在 KEYS 区域
local order_key = keys[1]
local user_id = args[1]
if user_id == nil or user_id == '' then
-- 参数不完整时返回 Redis 错误,而不是写入半成品状态
return redis.error_reply('user id is required')
end
return redis.call('HSET', order_key, 'owner', user_id, 'status', 'reserved')
end
local function cancel_order(keys, args)
-- 取消逻辑也属于同一份 library,和预留逻辑一起发布
local order_key = keys[1]
return redis.call('HSET', order_key, 'status', 'cancelled')
end
-- 注册名是客户端 API 的稳定边界,内部实现可以随库版本变化
redis.register_function('reserve_order', reserve_order)
redis.register_function('cancel_order', cancel_order)
调用时,数字 1 表示后面有一个 key 参数;之后的参数才进入函数的普通参数表。不要把会被函数访问的键藏在普通参数里,这既不利于 Cluster 路由,也让发布后的接口边界变得模糊。
# 第一个参数是 key 数量;order:1001 进入 KEYS,u-42 进入 ARGV redis-cli FCALL reserve_order 1 order:1001 u-42 # 函数名不携带 r1、r2,客户端只依赖稳定的业务 API redis-cli FCALL cancel_order 1 order:1001
用完整文件执行一次库替换
发布时不要只把改动后的函数片段拼到命令行里,而是把完整 library 文件作为唯一制品。首次加载使用 FUNCTION LOAD;同名库已经存在时,加上 REPLACE 表示用新定义替换现有库。
# 发布制品是完整文件,便于审阅、留档和回滚 release_file=order_ops-v2.lua # -x 从标准输入读取完整 library;REPLACE 替换同名 order_ops cat "$release_file" | redis-cli -x FUNCTION LOAD REPLACE # 业务客户端继续调用同一组函数名,不需要切换版本号 redis-cli FCALL reserve_order 1 order:1001 u-42
这里的“替换”不是给现有 library 打补丁。Redis 文档明确说明,library 的内容按整体更新,不能选择性更新其中某一个函数。因此,发布目录至少应同时保留当前文件和上一份可回滚文件,例如:
releases/ order_ops-v1.lua # 上一份可回滚制品 order_ops-v2.lua # 当前待发布制品 current # 记录当前已选中的制品名
发布后用 FUNCTION LIST 盘点实际结构
版本文件写入磁盘并不等于 Redis 已经加载了它。发布动作之后,应该把 FUNCTION LIST 当作结构盘点命令,关注 library 名、引擎、函数集合和 flags,而不是只看加载命令是否返回了库名。
# 盘点当前服务端的库和注册函数 redis-cli FUNCTION LIST # 只查看 order_ops,便于在发布记录中保存结构摘要 redis-cli FUNCTION LIST LIBRARYNAME order_ops
如果新库增加了函数,列表里应出现新的注册名;如果删除了函数,客户端仍可能保留旧调用路径,所以删除 API 要先做调用方迁移,再提交整库更新。对于只读函数,还应在注册时明确 no-writes 等 flags,避免后续使用 FCALL_RO 时才发现服务端按默认写函数处理。
发布、盘点和回滚怎样连成闭环
回滚不要重新手写“旧逻辑”,而是重新加载上一份完整制品。这样回滚内容与曾经发布过的版本一致,也能避免只恢复一个函数却漏掉同库辅助函数、flags 或 shebang 的情况。
# 回滚目标必须是已经留档的完整旧库文件 rollback_file=order_ops-v1.lua # 用同一个库名替换当前定义,客户端 API 保持不变 cat "$rollback_file" | redis-cli -x FUNCTION LOAD REPLACE # 回滚后再次盘点库和函数集合 redis-cli FUNCTION LIST LIBRARYNAME order_ops
如果部署的是 Redis Cluster,不能只在一个节点加载。Redis 会把函数复制到副本,但 Cluster 不会自动替所有主节点完成加载;应由发布流程显式对所有主节点执行相同的整库加载动作。示例命令的具体地址和认证参数按环境补齐:
# 对集群所有主节点执行同一份 library 加载动作 redis-cli --cluster-only-masters --cluster call redis-cluster:6379 \ FUNCTION LOAD REPLACE "$(cat order_ops-v2.lua)" # 每个主节点都应按同一制品盘点,避免节点结构不一致 redis-cli -h redis-master-a FUNCTION LIST LIBRARYNAME order_ops redis-cli -h redis-master-b FUNCTION LIST LIBRARYNAME order_ops
另外,函数执行会阻塞 Redis 服务端直到执行结束,版本发布解决的是管理边界,不会自动解决长函数带来的延迟问题。库内逻辑应保持短小,发布记录里同时标明函数集合、输入 key 数量、flags 和回滚制品名。
一份可执行的发布清单
| 环节 | 要固定的内容 | 容易踩的坑 |
|---|---|---|
| 制品 | 完整 library 文件、版本号、上一份制品 | 只保存改动函数,回滚时缺少同库函数 |
| 契约 | library 名、注册函数名、KEYS/ARGV 约定 | 把版本号塞进函数名,迫使客户端同步改名 |
| 替换 | FUNCTION LOAD REPLACE | 误以为可以单独替换 library 内的一个函数 |
| 盘点 | FUNCTION LIST LIBRARYNAME 的结构记录 | 只看命令返回,不记录函数集合和 flags |
| 回滚 | 重新加载上一份完整 library | 临时手写旧逻辑,造成旧版本不可复现 |
| 集群 | 所有主节点使用同一份制品 | 只在一个节点加载,节点间能力不一致 |
最后的判断
Redis Functions 的版本管理核心不是给每个函数加编号,而是把 library 当成可留档、可盘点、可整体替换的发布制品。稳定的函数名让客户端 API 少变化,完整的库文件让回滚可复现,FUNCTION LIST 让服务端结构有迹可循;在 Cluster 环境中,再把同一制品明确分发到所有主节点,就能把发布边界补完整。
相关问题
- Redis Functions 可以只更新 library 里的一个函数吗?
FUNCTION LOAD和FUNCTION LOAD REPLACE有什么区别?- Redis Cluster 为什么需要给每个主节点加载 Functions?
- Redis Functions 的函数名应该包含版本号吗?

Redis ACL 怎样按命令类别授予最小权限