Redis 7.0 新功能 Redis Functions 是什么?可轻易实现「复杂原子操作」的神器!

·  阅读 1753

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

背景

2022年4月27日,Redis正式发布了7.0更新(其实早在2022年1月31日,Redis已经预发布了7.0rc-1,经过社区的考验后,确认没重大Bug才会正式发布)。

在众多新特性中,Redis团队把Redis Functions放在了第一位:

image.png

可见官方对这个特性是相当重视。今天我们来一起学习下Redis Functions

学习前,需了解:Redis旧版本中的lua脚本

Redis为了给开发者更灵活的能力,内置了lua解释器,可以让开发者执行功能强大的「原子操作」。

例如以下场景,只通过Redis本身的数据结构,实现起来是比较低效的:

  • 某个key的string value自乘2。
  • 若keyA=某个数字,则同时设置keyA和keyB。
  • ……

由于Redis本身并没有暴露上述命令,所以我们需要通过transactions或者watch来实现,而watch又不能保证100%成功,可能还需要引入重试。也可以通过分布式锁来实现。但这些都让系统变得更复杂、效率更低。

最高效的方式,其实是Redis内部实现好,暴露相关指令出来(例如incr指令),因为指令是原子的,所以可以放心使用。当然Redis即使暴露再多指令也没用,肯定无法覆盖各种复杂的实际场景,所以最好的方式就是提供编程能力,允许用户自定义「原子操作」,就是通过lua脚本实现。

有个命令是EVAL

EVAL script numkeys [key [key ...]] [arg [arg ...]]
复制代码

就可以执行一段lua脚本。例如可以这样用:

> EVAL "return ARGV[1]" 0 hello
"hello"
复制代码

在lua中,Redis提供了Redis的几乎所有命令API,你可以读取key、设置key等。在执行lua过程中,Redis不会打断,只有执行完毕,才会去执行其它命令。也就是说,这段脚本是「原子操作」。

但是这种方式有些问题:

  1. 每次执行脚本时,需要先编译再执行,效率低(当然Redis也会针对脚本计算哈希,把编译结果存下来,如果后面的脚本跟之前一致,就不再编译了,直接取之前的结果)。但是第一次执行时,依然存在效率问题,尤其是Redis刚启动时,或者脚本初次执行时。
  2. lua脚本复用比较困难,因为每次都要加载一段新的脚本,函数复用成本较高。

所以,我们继续看看Redis Functions是怎么解决这些问题的。

Redis Functions 相关指令介绍

首先我们先看看Redis Functions提供了哪些指令。

了解Redis Functions必须知道的指令

为了了解这个Redis Functions功能,你至少需要知道这2条:

  • Function Load
  • FCALL

Function Load

先看个例子

首先你可以新建一个lua脚本,名字叫hello.lua:

#!lua name=mylib

redis.register_function('myfunc', function(keys, args) return args[1] end)
复制代码

注意上面的redis.register_function方法,是必须的,就是通过该API(Redis给lua提供的API),来注册函数到Redis中,2个参数分别是函数名称、函数引用。

然后可以执行下面这个shell脚本(前提是你已经安装了redis、redis-cli,并启动了redis):

cat mylib.lua | redis-cli -x FUNCTION LOAD
复制代码

这样,就给redis注册了一个名叫mylib的库,这个库里注册了一个叫myfunc的函数。函数作用是直接return参数1。

也可在redis-cli中直接注册

当然,你也可以直接在redis-cli中调用FUNCTION LOAD注册:

FUNCTION LOAD "#!lua name=mylib \n redis.register_function('myfunc', function(keys, args) return args[1] end)"
复制代码

解释 FUNCTION LOAD

语法:

FUNCTION LOAD [REPLACE] function-code
复制代码

你可以使用该语句加载一个lua模块到redis中,一个模块包含一个或多个函数。后续在redis可以调用这些函数。有如下特性:

  • lua代码需要编译,在FUNCTION LOAD后会自动编译,以后每次调用时都不需要编译,调用速度都是快的。(也许你知道Redis有EVAL,它可以直接执行一段lua代码,但它第一次执行时需要编译,所以初次调用速度不如先FUNCTION LOAD再调用)
  • 支持模块级别的重新加载。只要增加个参数REPLACE,就可以用新模块替换同名旧模块(若该模块是第一次加载,也会加载成功的)。
  • Redis重要特性之一是内存数据可持久化保存。当你加载函数后,关闭Redis时,注册的函数也会被持久化到硬盘。重启Redis时自动重新加载之前加载的函数。
  • Redis Function中执行代码是原子操作,执行过程中不会被打断。

注册完毕了,必然要执行,怎么执行呢,就是用FCALL

FCALL

FCALL myfunc 0 hahahahaha
复制代码

指定了函数名,hahahahaha就是参数1。0我们之后再介绍。

这样执行后,不出意外,会输出hahahahaha

image.png

解释FCALL

语法:

FCALL function numkeys [key [key ...]] [arg [arg ...]]
复制代码

function就是函数名,就是你的lua文件中,redis.register_function的第一个参数。

numkeys是指后面的参数中代表Redis Key的参数有多少个。

后面可以跟一系列参数,参数有2种:代表Redis key的参数、其它参数。

也就是说,如果你要在lua函数中访问Redis的key,必须通过参数传进来,千万不要自己去拼接。 自己拼接key参数在单机Redis没问题,但是分布式Redis中会有大问题。分布式Redis需要知道你在这个「原子操作」中可能读/写哪些key,来做一些并发控制,以免读到脏数据。

其它指令

  • FCALL_RO: 只读模式调用函数(意思是在函数执行时,你无法写入Redis数据,但可以读取)。分布式Redis针对这种模式会有优化。
  • FUNCTION DELETE: 删除加载的lua模块(注意是模块维度删除)。
  • FUNCTION DUMP: 序列化已加载的函数,返回byte串。
  • FUNCTION RESTORE: 通过byte串(恢复)加载函数。有3种模式:FLUSH(清空已加载的再恢复)、APPEND(新增,但是同名的不再加载)、REPLACE(新增,并替代同名)。
  • FUNCTION FLUSH: 清空已加载函数。
  • FUNCTION KILL: 该命令可以终止正在执行的只读的函数。
  • FUNCTION LIST: 返回已加载的模块、函数列表。
  • FUNCTION STATS: 获取正在执行的函数的状态(函数名、参数信息、已经执行了多久)。因为有些函数耗时太久,会导致Redis这个单线程一直卡着,所以通过查询状态,用户可以决策是否通过FUNCTION KILL终止它。

写在最后

推荐阅读官方文档:redis.io/docs/manual…

我是HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,联系我,交个朋友),转发本文前需获得作者HullQin授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋、UNO等游戏,不收费无广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我噢~我有空了会分享做游戏的相关技术,会在这2个专栏里分享:《教你做小游戏》《极致用户体验》

分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改