打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
【Redis13】Redis基础:事务与Lua脚本命令

Redis基础学习:事务与Lua脚本命令

对于一个传统关系型数据库系统来说,事务是非常重要的一个组成部分。但是,在 NoSQL 相关的数据库中,为了效率以及实现形式的不同,事务远达不到真正的关系型数据库中的那种 ACID 的控制级别。今天,我们就来学习一下 Redis 中的事务操作。另外,我们还会简单地看一下在 Redis 中如何去执行 Lua 脚本程序。

事务

在 Redis 中,事务其实就是先使用 MULTI 开启事务操作,然后把每次执行的命令放到一个队列中,当执行 EXEC 命令后才会真正的执行队列中的所有命令。

// 客户端1
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set a 111
QUEUED
127.0.0.1:6379(TX)> incr a
QUEUED

// 客户端2
127.0.0.1:6379> get a
(nil)

// 客户端1
127.0.0.1:6379(TX)> exec
OK
112

// 客户端2
127.0.0.1:6379> get a
"112"

我们在第一个客户端开启了事务,然后设置一个 a 的值为 111 ,并且 INCR 一下,也就是加 1 。此时还没有执行 EXEC 命令,然后在客户端2查询 a ,会发现 a 还是空的。接着我们在客户端1执行 EXEC 后,a 数据正式写入并且加1,所有的客户端都可以访问到了。在执行事务的时候,命令行会很明显的显示一个 TX 标识。

既然是事务,如果不想执行了,是不是可以回滚?不不不,在 Redis 中没有回滚一说,因为命令只是在队列中,并没有真正被执行,所以自然也不能叫做回滚,而是叫做丢弃 DISCARD 。

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set a 111
QUEUED
127.0.0.1:6379(TX)> DISCARD
OK

127.0.0.1:6379> get a
"112"

上面的例子中,我们在事务中尝试重新将 a 的值设置为 111 ,但最后选择了 DISCARD ,这样后面再访问的时候 a 的值就不会发生变化。

好了,接下来我们测试一下 Redis 事务的原子性和隔离性。先看看隔离性,也就是一个事务在执行的时候会不会受到外部的干扰。

// 客户端1
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> incr a
QUEUED

// 客户端2
127.0.0.1:6379> set a 456
OK

// 客户端1
127.0.0.1:6379(TX)> EXEC
457
127.0.0.1:6379> get a
457

从例子中可以看出,客户端2在客户端1的事务未提交时修改了 a 的数据,这时客户端1进行了事务的提交,结果是在客户端2修改后的数据上进行了 INCR 操作。

可以说,完全没有隔离性,同时Redis 中也没有事务隔离级别的概念。但是这样的操作似乎会带来问题呀?没错,隔离性对于数据的准确性会有很大的影响,银行转账的例子大家在学 MySQL 的时候相信也已经听烦了。那么在 Redis 中如何解决这种情况呢?

在 Redis 中,实现的是一种 乐观锁 机制。之前学习 MySQL 中的锁时,我们已经学习过,MySQL 中所有的锁相关操作都是 悲观锁 ,同时我们可以通过版本字段或者时间字段之类的来实现 乐观锁 。在 Redis 中,我们可以通过 WATCH 实现乐观锁。

// 客户端1
127.0.0.1:6379> WATCH a
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> INCR a
QUEUED

// 客户端2
127.0.0.1:6379> set a 789
OK

// 客户端1
127.0.0.1:6379(TX)> EXEC

127.0.0.1:6379> get a
789

看出来是什么意思了吗?乐观锁 就是信任其他人不会修改数据,如果发生了修改,自己就不更新了。WATCH 就是这个意思,在事务开始前,我们通过 WATCH 监控 a 这个 key ,如果在事务的执行过程中,有其它的客户端修改了 a 的数据,那么事务在 EXEC 时,就不会执行。

上面的例子中可以看到,EXEC 执行后未返回任何内容,同时客户端2设置的 789 也并没有 INCR 数据。这就是乐观锁的应用。在实际的业务开发中,可以查看 EXEC 返回的结果来确定事务是否正常执行,如果没有返回信息,说明有其它客户端修改了数据。那么我们就可以再次开启事务进行操作。

看完了隔离性,再来看看原子性,原子性说的是事务提交要么全部成功,要么全部失败回滚。在 Redis 中,如果在事务执行中间发生了异常,那么事务会出现两种情况,我们一个一个来看。

第一种情况是运行时异常,比如使用的命令参数出现了错误,但不影响其它命令的执行,这时只有出问题的命令不会执行,其它的命令还是会执行,这种情况其实是非原子性的。

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> INCR a
QUEUED
127.0.0.1:6379(TX)> set b 222 121
QUEUED
127.0.0.1:6379(TX)> EXEC
1) (integer) 790
2) (error) ERR syntax error

另一种情况是编译型异常,比如使用了错误的命令,这时整个事务的提交都会被丢弃,这个才是原子性的。

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> INCR a
QUEUED
127.0.0.1:6379(TX)> setget b 123 112
(error) ERR unknown command `setget`, with args beginning with: `b`, `123`, `112`,
127.0.0.1:6379(TX)> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get a
"790"

综上所述,Redis 中的事务就像我们最开始讲的那样,其实就是一个命令执行队列,并不是完全意义上的关系型数据库中的事务的概念。在面试的时候要注意面试官在这里挖坑哦!

Lua脚本

Lua脚本是非常轻量级的脚本语言,同时也是受到 Nginx 和 Redis 所支持的一种脚本语言。怎么说呢,就是 Redis 可以直接运行或通过 Lua 脚本进行一些操作。

不过我对 Lua 并不熟悉,所以这里也就是演示一下在 Redis 中去执行或加载操作 Lua 脚本的一些命令。

首先看一下如何执行一段 Lua 脚本。

127.0.0.1:6379> EVAL "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 a b 111 222
1) "a"
2) "b"
3) "111"
4) "222"

EVAL 命令后面可以跟着一段 Lua 脚本,后面的参数中,2 表示有两个 KEYS ,a 和 b 就是在 Lua 中可以获取到的 KEYS 数组中的内容。后面的 111 和 222 其实就是对应到 Lua 脚本中 ARGV 数组中的数据。

来个更实际一点的例子,比如我们要为 a 这个 key 赋值。

127.0.0.1:6379> EVAL "redis.call('set', KEYS[1], ARGV[1]);return 'ok'" 1 a 111
"ok"
127.0.0.1:6379> get a
"111"
127.0.0.1:6379> EVAL "return redis.call('get', 'a')" 0
"111"

这个例子就明显很多了吧,我们使用的是 redis.call 这个函数,然后使用 set 命令,通过外部传值把数据传给 Lua 脚本中的 KEYS 和 ARGV ,这样就执行了一个 set a 111 的操作。同样的,我们也可以通过 redis.call 来执行其它的 Redis 命令。

除了 EVAL 命令之外,还有一套 SCRIPT 命令,也是用来操作 Lua 脚本的。SCRIPT 是一套复合命令,它的子命令包括下面这些。

127.0.0.1:6379> SCRIPT HELP
 1) SCRIPT <subcommand> [<arg> [value] [opt] ...]. Subcommands are:
 2) DEBUG (YES|SYNC|NO)
 3)     Set the debug mode for subsequent scripts executed.
 4) EXISTS <sha1> [<sha1> ...]
 5)     Return information about the existence of the scripts in the script cache.
 6) FLUSH [ASYNC|SYNC]
 7)     Flush the Lua scripts cache. Very dangerous on replicas.
 8)     When called without the optional mode argument, the behavior is determined by the
 9)     lazyfree-lazy-user-flush configuration directive. Valid modes are:
10)     * ASYNC: Asynchronously flush the scripts cache.
11)     * SYNC: Synchronously flush the scripts cache.
12) KILL
13)     Kill the currently executing Lua script.
14) LOAD <script>
15)     Load a script into the scripts cache without executing it.
16) HELP
17)     Prints this help.

那么 SCRTIP 命令和 EVAL 命令有什么区别呢?EVAL 是直接执行,而 SCRIPT 可以通过 SCRIPT LOAD 命令将脚本先加载到 Redis 中,但并不马上执行,之后我们可以通过 EVALSHA 命令来执行,就像下面这样。

127.0.0.1:6379> SCRIPT LOAD "return redis.call('get', 'a')"
"b2dc80c45e350e7bf2b3fc26fb0451ee65259785"
127.0.0.1:6379> EVALSHA b2dc80c45e350e7bf2b3fc26fb0451ee65259785 0
"111"

看出来什么意思了吧,SCRIPT LOAD 返回一个哈希签名,然后 EVALSHA 可以直接使用这个签名去运行之前加载进来的脚本。

SCRIPT EXISTS 用于判断给定的签名是否已经加载在当前的服务器环境中。

127.0.0.1:6379> SCRIPT EXISTS b2dc80c45e350e7bf2b3fc26fb0451ee65259785
1) (integer) 1
127.0.0.1:6379> SCRIPT EXISTS b2dc80c45e350e7bf2b3fc26fb0451ee65259784
1) (integer) 0

SCRIPT FLUSH 则是清除所有已加载的脚本。

127.0.0.1:6379> SCRIPT FLUSH
OK
127.0.0.1:6379> SCRIPT EXISTS b2dc80c45e350e7bf2b3fc26fb0451ee65259785
1) (integer) 0

最后,我们再来看一下如何去运行一个外部的 Lua 脚本文件。

➜  ~ vim get.lua
return redis.call('get', 'a')


➜  ~ redis-cli --eval get.lua
"111"

通过 redis-cli --eval 就可以在外部去加载运行一个指定 .lua 文件,这样其实我们就可以写一些自己的 Lua 脚本,比如说预热数据之类的,通过外部执行的方式就能够利用语言优势来批量、循环的操作数据。

Lua 脚本在 Redis 中有非常重要的作用,虽说我们可能平时用不到,但是,在很多框架中,比如 Laravel 或者 Java 的 Redisson 中,都大量频繁地使用了 Lua 脚本。这是为啥呢?那是因为一段 Lua 脚本的执行,在 Redis 中是可以保证完全的原子性的,也就是真正的要么全部成功,要么全部失败,而不是 Redis 事务中的监视事务这种乐观锁机制。

之前我们在学习 Laravel 框架的时候,其实就见过 Redis 配合 Lua 脚本在 Laravel 中的应用,不记得的小伙伴可以去看看视频 【Laravel系列7.7】队列系统https://mp.weixin.qq.com/s/55-wp3YIQpLSrktIlZKMow 

总结

今天的重点很明显就是事务相关命令的学习,如果你会 Lua 的话,当然也可以在 Redis 中大展身手了。对于日常的工作来说,如果只是将 Redis 作为缓存或者实现一些简单的队列应用的话,事务也都是可有可无的,毕竟我们也不完全依赖于 Redis 来实现真正的需要强事务的功能操作。但是,这一块却又是很多面试官喜欢问的东西,所以了解一下总没坏处。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
redis中的事务、lua脚本和管道的使用场景
阿里云Redis lua命令支持及相关限制说明
Redis源代码笔记 – Lua脚本支持| Just Carry On
造了一个 Redis 分布锁的轮子,没想到还学到这么多东西!
缓存对事务的支持
Redis Lua scripting is badass
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服