bigywater.tech
Redis · 34

Redis Functions

用FUNCTION LOAD注册一个带名字的函数库并用FCALL调用,对比裸EVAL脚本,看Functions解决了脚本缓存没有名字、不持久化这两个问题。

8
两次FCALL累加的最终结果
有名字可查询
FUNCTION LIST能列出库和函数元信息
持久化
函数库会被写入RDB/AOF,EVAL脚本缓存不会

做的事:用带名字的库替代裸的EVAL脚本

Redis Functions是比EVAL更晚引入的特性,核心区别是脚本有了名字和归属。

代码lib_code = """#!lua name=mylib redis.register_function('myincr', function(keys, args) local v = tonumber(redis.call('GET', keys[1]) or '0') v = v + tonumber(args[1]) redis.call('SET', keys[1], v) return v end) """ r.execute_command("FUNCTION", "LOAD", lib_code)

验证:FCALL按名字调用,效果跟EVAL一样能读写数据

区别只是入口从"一段脚本文本"变成了"一个注册过的函数名"。

结果FUNCTION LOAD → "mylib" FCALL myincr 1 fn_counter 5 → 5 FCALL myincr 1 fn_counter 3 → 8 GET fn_counter → "8"

验证了myincr这个函数被调用了两次,第一次把fn_counter从空(当0处理)加到5,第二次再加3变成8——底层逻辑跟EVAL脚本没有区别,都是用redis.call去读写数据,Redis保证的原子性、单线程阻塞这些特性也完全一样,Functions只是给这套机制包了一层"有名字、可管理"的外壳。

验证:FUNCTION LIST能查到函数的元信息,重复LOAD会报错

这正是EVAL脚本天然做不到的——裸脚本只有一个SHA1哈希,没有名字,没法"列出我注册过哪些函数"。

结果FUNCTION LIST → [{'library_name': 'mylib', 'engine': 'LUA', 'functions': [{'name': 'myincr', ...}]}] 不带REPLACE重复LOAD同一份代码: → 报错: Library 'mylib' already exists FUNCTION DUMP → 178字节的二进制序列化数据
这组对比揪出了Functions相对EVAL脚本解决的问题:EVAL脚本靠SHA1哈希被动缓存,服务器完全不知道"这段代码是干什么用的、叫什么名字",一旦Redis重启或者调用SCRIPT FLUSH,缓存说没就没,客户端必须自己保存脚本源码以备重新EVAL;而FUNCTION LOAD要求显式声明库名(#!lua name=mylib),加载后的函数库会被持久化进RDB/AOF(普通EVAL脚本缓存不会),Redis重启后FUNCTION LIST依然能看到它,不需要客户端重新推送代码。Library already exists这个报错,以及需要显式加REPLACE参数才能覆盖更新,体现的正是"有名字的库"该有的版本管理语义——防止不同客户端不小心用不同代码覆盖了同一个函数名。

分步演示:Functions比裸EVAL脚本多了什么

每一步都是本文上面用FUNCTION/FCALL命令跑出来的原始结果。

Functions给脚本加上了名字和持久化
这一步新验证的能力

    本文用到的工具

    • 本机 Redis 8.10.1
    • FUNCTION LOAD/LIST/DUMP / FCALL:注册、调用、查询一个函数库
    京ICP备2026067502号-1京公网安备11010502063487号