doc: 阅读redis scripting with lua文档
This commit is contained in:
@@ -162,6 +162,15 @@
|
||||
- [Read-only scripts](#read-only-scripts)
|
||||
- [Sandboxed script context](#sandboxed-script-context)
|
||||
- [Maximum execution time](#maximum-execution-time)
|
||||
- [Scripting with Lua](#scripting-with-lua)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Script Parameterization](#script-parameterization)
|
||||
- [Interact with redis from a script](#interact-with-redis-from-a-script)
|
||||
- [Script Cache](#script-cache)
|
||||
- [Cache volatility](#cache-volatility)
|
||||
- [evalsha in context of pipelining](#evalsha-in-context-of-pipelining)
|
||||
- [Script Cache Semantics](#script-cache-semantics)
|
||||
- [Script Command](#script-command)
|
||||
|
||||
|
||||
# redis
|
||||
@@ -2666,3 +2675,135 @@ script仅应该操作`redis中存储的数据`和`脚本执行时传入的作为
|
||||
- redis开始从其他clients接收commands,但是会对所有发送normal commands的clients返回`BUSY error`。在该场景下,唯一被允许的命令为`SCRIPT_KILL, FUNCTION_KILL, SHUTDOWN NOSAVE`
|
||||
- 对于`read-only script`,可以使用`SCRIPT_KILL`和`FUNCTION_KILL`命令,因为该脚本未对数据造成任何修改
|
||||
- 如果`script`在执行过程中哪怕执行了一个write operation,那么只能使用`SHUTDOWN NOSAVE`命令,其会停止server,并且不会将当前的data set保存到磁盘中
|
||||
|
||||
### Scripting with Lua
|
||||
redis允许用户上传lua脚本到server并在server端执行,故而在脚本执行时,对数据的读取和写入操作十分高效,并不需要网络开销。
|
||||
|
||||
redis保证脚本的执行是原子的。在执行脚本时,server的其他活动都会被阻塞。
|
||||
|
||||
lua允许在脚本中集成部分属于应用的逻辑,例如跨多个keys的条件更新等。
|
||||
|
||||
#### Getting Started
|
||||
可以通过`EVAL`命令在redis中执行script,示例如下:
|
||||
```redis-cli
|
||||
> EVAL "return 'Hello, scripting!'" 0
|
||||
"Hello, scripting!"
|
||||
```
|
||||
在上述示例中,`EVAL`命令接收两个参数:
|
||||
- 第一个参数为lua script的source code内容,该脚本内容将在redis engine's context下运行
|
||||
- 第二个参数为`the number of arguments that follow the script's body`
|
||||
|
||||
#### Script Parameterization
|
||||
虽然不建议这样做,但是仍可以在应用程序中动态的生成script source code,示例如下:
|
||||
```redis-cli
|
||||
redis> EVAL "return 'Hello'" 0
|
||||
"Hello"
|
||||
redis> EVAL "return 'Scripting!'" 0
|
||||
"Scripting!"
|
||||
```
|
||||
虽然`在应用程序中动态生成script source code`的操作并未被redis禁止,但是其是不推荐的使用方式,可能会在`script cache`方面造成问题。在该场景下,不应该生成多个拥有微小差异的不同脚本,而是应该将细微的差异提取为script param。
|
||||
|
||||
如下实例则展示了参数化后的标准实现方式:
|
||||
```redis-cli
|
||||
redis> EVAL "return ARGV[1]" 0 Hello
|
||||
"Hello"
|
||||
redis> EVAL "return ARGV[1]" 0 Parameterization!
|
||||
"Parameterization!"
|
||||
```
|
||||
对于lua script执行的参数,其分为如下两种:
|
||||
- input arguments that are names of keys
|
||||
- input arguments that are not names of keys
|
||||
|
||||
> 为了确保lua script的正确执行,不管是在standalone还是在clustered的部署模式下,script访问的所有key names都必须`显式的作为input key arguments被指定`。
|
||||
>
|
||||
> script只应该访问那些在`input key argments`中被显式指定的key name。在编写script时,应该遵守该要求。`在redis集群环境下,应通过redis可能操作的键,从而对脚本进行路由。`
|
||||
>
|
||||
> 如果用户编写的脚本使用了未在`input key arguments`中指定的key,redis script engine虽然不会在执行时对此进行校验,但是可能导致script路由到错误的节点,错误的节点执行脚本时仍可能报错。
|
||||
|
||||
在调用redis脚本时,作为`非key name`被传递给redis脚本的参数,即是`regular input argument`。
|
||||
|
||||
在上述示例中,`Hello`和`Parameterization!`都是作为常规参数被传递的,上述示例中脚本并未用到任何redis key,故而第二个参数被指定为`0`,代表`input key arguments`的个数为0。
|
||||
|
||||
在redis script context中,可以通过`KEYS`和`ARGV`这两个global runtime variables来访问传递的参数。
|
||||
- `KEYS`针对的是input key arguments
|
||||
- `ARGV`针对regular argument
|
||||
|
||||
如下就示例展示了input arguments在`KEYS`和`ARGV`之间分配的情况:
|
||||
```redis-cli
|
||||
redis> EVAL "return { KEYS[1], KEYS[2], ARGV[1], ARGV[2], ARGV[3] }" 2 key1 key2 arg1 arg2 arg3
|
||||
1) "key1"
|
||||
2) "key2"
|
||||
3) "arg1"
|
||||
4) "arg2"
|
||||
5) "arg3"
|
||||
```
|
||||
|
||||
#### Interact with redis from a script
|
||||
在lua script中,可以通过`redis.call`或`redis.pcall`来调用redis commands。
|
||||
|
||||
`call`和`pcall`大致是相同的,都会执行redis command,但是在针对runtime error的处理上这两个function之间有所不同:
|
||||
- `redis.call`:对于`redis.call`方法抛出的error会被直接返回给客户端
|
||||
- `redis.pcall`: 对于`redis.pcall`方法抛出的异常,其会被返回给script's execution context,`for possible handling`
|
||||
|
||||
lua script中和redis的交互示例如下所示:
|
||||
```redis-cli
|
||||
> EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 foo bar
|
||||
OK
|
||||
```
|
||||
|
||||
#### Script Cache
|
||||
当调用`EVAL`时,在请求中也会包含source code内容,如果对相同的script content进行重复调用,不仅会浪费网络带宽,redis也会产生额外开销。为了节省网络带宽和计算资源,redis对script提供了缓存机制。
|
||||
|
||||
在redis中,每个通过`EVAL`执行的script,都会被存储在由server维护的专用缓存中。缓存的内容按照script的`SHA1 digest sum`来进行组织,SHA1 digest sum在cache中能够唯一标识脚本。
|
||||
|
||||
就像上述中提到的,在应用中动态生成脚本内容是不推荐的。如果不对脚本进行参数化,那么动态生成脚本的source code,并多次对脚本进行调用,将会占用redis额外的内存资源来对脚本内容进行缓存。脚本source code应该尽量通用。
|
||||
|
||||
脚本可以通过`SCRIPT LOAD`命令加载到cache中,server并不会实际对脚本进行执行,而是只会对其编译并加载到cache中。一旦完成加载后,可以通过脚本的SHA1 digest sum来执行脚本。
|
||||
|
||||
`SCRIPT LOAD`的使用示例如下所示:
|
||||
```redis-cli
|
||||
redis> SCRIPT LOAD "return 'Immabe a cached script'"
|
||||
"c664a3bf70bd1d45c4284ffebb65a6f2299bfc9f"
|
||||
redis> EVALSHA c664a3bf70bd1d45c4284ffebb65a6f2299bfc9f 0
|
||||
"Immabe a cached script"
|
||||
```
|
||||
|
||||
在实际使用`eval`时,`org.springframework.data.redis.core.script.DefaultScriptExecutor#execute`方法中已经实现了`evalSha first, and eval it if script not exists`的逻辑,故而无需手动eval。
|
||||
|
||||
|
||||
|
||||
##### Cache volatility
|
||||
redis script cache是易失的。script cache并不会被看作是database的一部分,且其不会被持久化。缓存的脚本随时都由可能丢失。
|
||||
|
||||
在如下场景下,script cache会被清空
|
||||
- when server restarts
|
||||
- during fail-over
|
||||
- 显式调用`SCRIPT FLUSH`时
|
||||
|
||||
在应用使用scripts时,应该一直使用`EVALSHA`来执行脚本,当sha1 digest不存在时redis cache会返回error,例如
|
||||
```redis-cli
|
||||
redis> EVALSHA ffffffffffffffffffffffffffffffffffffffff 0
|
||||
(error) NOSCRIPT No matching script
|
||||
```
|
||||
在cache中不存在脚本的场景下,应用需要通过`SCRIPT LOAD`先导入脚本内容,然后调用`EVALSHA`命令来根据sha1 digest运行cache中的脚本。对于大多数的api,都提供了自动执行该过程的API。
|
||||
|
||||
##### evalsha in context of pipelining
|
||||
在pipeline场景下执行`EVALSHA`命令时,pipeline中的commands是按顺序执行的,但是执行的间隙中可能穿插来自其他clients的commands`。
|
||||
|
||||
在pipeline执行时,可能返回`NOSCRIPT error`,但是该错误无法被处理。
|
||||
|
||||
因此,在pipeline环境下,client library通常直接使用`eval`而不是`evalsha -> script load -> evalsha`。
|
||||
|
||||
##### Script Cache Semantics
|
||||
在执行常规操作时,来自应用的脚本会被一直保存在cache中,直到server被重启或缓存被清空。
|
||||
|
||||
清空缓存的唯一方式是通过显式调用`SCRIPT FLUSH`命令,执行该命令会完全清空script cache,移除目前执行过的所有脚本内容。通常来说,该命令只在`当前redis实例需要初始化,并后续为其他应用服务`时被调用。
|
||||
|
||||
#### Script Command
|
||||
`SCRIPT`命令提供了一系列用于控制scripting subsystem的方法:
|
||||
- `SCRIPT FLUSH`: 该命令是清空redis script cache的唯一方式
|
||||
- `SCRIPT EXISTS`: 可接收一个或多个sha1 digests作为参数,该命令会返回值为`0 or 1`的数组。其中`1`代表脚本在cache中存在,而`0`代表脚本在cache中不存在。
|
||||
- `SCRIPT LOAD`: 该命令会向redis cache中注册一个特定的脚本。在执行`EVALSHA`命令之前执行该命令,能够确保`EVALSHA`命令不会失败
|
||||
- `SCRIPT KILL`: 该命令是打断`long-running script`的唯一方式。当script的执行时长超过限制(默认为5s)时,如果该脚本执行时未对数据进行任何修改,则可以通过`SCRIPT KILL`命令来终止
|
||||
- `SCRIPT DEBUG`: 用于控制`redis lua scripts debugger`的使用
|
||||
|
||||
|
||||
Reference in New Issue
Block a user