Administrator
发布于 2022-04-08 / 6527 阅读
82

Redis 7 新特性与 Redis Functions

升级 Redis 7.0 时,我把一段 Lua 脚本换成了 Functions

4 月 Redis 7.0 正式发布,我们把集群从 6.2 升到 7.0。升级本身平滑(RDB/AOF 兼容),但翻 release notes 时发现一个我一直想要的东西:Redis Functions。我们原本用 Lua 脚本做"扣库存 + 写日志"的原子操作,每次调用都要把整段脚本传一遍,又臭又长。Functions 正好解决这个。

这篇记三个对我最有用的 7.0 新特性,都是实战能落地的。

Redis Functions:脚本只存一次,调用只传名字

老 Lua 的用法,每次 EVAL 都要把脚本原文发到服务端:

# 老方式:每次都把脚本全文传过去
EVAL "local k=KEYS[1] local n=tonumber(ARGV[1]) \
      if redis.call('GET',k) >= n then \
        redis.call('DECRBY',k,n) return 1 else return 0 end" \
      1 stock:1001 3

脚本长、网络开销大,而且多个客户端各传各的,服务端没法复用

Functions 把脚本在Ubuntu 服务器端注册一次,之后只需调用函数名:

# 注册一次
FUNCTION LOAD "redis.register_function('deduct', function(keys, args)
  local k = keys[1]
  local n = tonumber(args[1])
  if tonumber(redis.call('GET', k)) >= n then
    redis.call('DECRBY', k, n)
    return 1
  end
  return 0
end)"

# 之后所有客户端只传名字调用
FCALL deduct 1 stock:1001 3

对比很清楚:脚本只在注册时传一次,之后 FCALL deduct 只传名字和参数,网络包小了一个数量级。我们那个扣库存脚本注册后,单次调用请求体从 480 字节降到 32 字节。

还有个好处:Functions 是集群感知的,FUNCTION LOAD 会通过 FUNCTION FLUSH / 传播机制在集群里保持一致,而 Lua 脚本在集群里要每个节点单独 SCRIPT LOAD

多 AOF:重写时不再阻塞

6.2 的 AOF 重写是单一文件,BGREWRITEAOF 时要把所有命令重放成新文件,文件大时 fork 出来的子进程写盘慢,还跟主进程抢磁盘 IO。

7.0 把 AOF 拆成多个文件(multi-part AOF):基础文件(base)+ 增量文件(incr)。重写时只处理增量部分,大幅减少单次重写的数据量。

# redis.conf(7.0)
appendonly yes
appendfilename "appendonly.aof"   # 现在是个目录

升级后我们实测,AOF 重写耗时从平均 11 秒降到 2.3 秒(数据量 4 GB 时),重写期间的命令延迟毛刺明显少了。旧版单个大 AOF 文件在重写时 P99 会到 30 ms,新版基本稳定在 3 ms 以内。

listpack:Hash/List/ZSet 的小数据更省内存

7.0 把 Hash、List、ZSet、Set 里的小元素底层编码从 ziplist / linkedlist 统一换成 listpack。listpack 是 ziplist 的替代品,解决了 ziplist 的级联更新问题(改一个元素可能要重写整段),也更省内存。

这不是改配置,是升级后自动生效的。我们升级后同一批用户标签数据(约 120 万个小 Hash,每个 5~8 字段),内存占用从 1.8 GB 降到 1.5 GB,省了约 17%。元素越小、越多,收益越明显。

一个注意点:listpack 是底层编码变化,对客户端完全透明,不用改任何代码,也不用做数据迁移,重启加载新版 RDB 后自然就是 listpack。

小结

  • Redis Functions:脚本服务器端注册一次,FCALL 只传名字,网络开销降一个数量级,且集群内自动一致,比 Lua 更适合复用逻辑。
  • 多 AOF:AOF 拆成 base + incr,重写只处理增量,我们实测重写耗时从 11 秒降到 2.3 秒,延迟毛刺大幅减少。
  • listpack:小数据结构的默认编码,升级后自动生效、对客户端透明,我们标签数据省了 17% 内存。

这次升级最大的感受是:7.0 的几个改动都很"实",没有花哨 API,全是把原来别扭的地方(传脚本、重写卡顿、ziplist 缺陷)补好。Functions 我已经在新写的扣库存逻辑全面铺开,老 Lua 脚本准备逐步迁掉。

参考