【知识点】Redis如何实现事务?为什么不建议用redis自带事务!!!

By | 8月 2, 2022

什么是事务?

事务,简单理解就是,一组动作,要么全部执行,要么就全部不执行.从而避免出现数据不一致的情况

redis事务

基本原理为乐观锁,多个client对操作的key进行watch,一旦有一个client进行了exec,那么其它client的exec就会失效。,可以理解为一组命令的批量执行。

127.0.0.1:6379> multi     #开启事务
OK
127.0.0.1:6379> set name onezero
QUEUED
127.0.0.1:6379> set age 10
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> set age 11
QUEUED
127.0.0.1:6379> exec    #执行事务
1) OK
2) OK
3) "10"
4) OK
127.0.0.1:6379> get age
"11"
127.0.0.1:6379>

eg:

可以看到set命令一开始返回的结果是QUEUED,代表命令并没有真正执行,只是暂时存在redis中.只有当exec执行了.这组命令开始执行,然后可以看到每一条命令的执行结果。

如果事务中的命令出现错误,比如说语法错误, set写成了sest,整个的事务将无法执行


127.0.0.1:6379> multi
OK
127.0.0.1:6379> sest name onezero
(error) ERR unknown command `sest`, with args beginning with: `name`, `onezero`,

127.0.0.1:6379>

众所周知,数据库的事务具有原子性,要么执行,要么不执行,如果中间失败就会回滚

所以说redis不支持事务中的回滚特性.无法实现命令之间的逻辑关系计算。

5个命令

watch [key1] [key2]:监视一个或多个key,在事务开始之前如果被监视的key有改动,则事务被打断
    multi:标记一个事务的开始
    exec:执行事务
    discard:取消事务的执行
    unwatch:取消监视的key
    注意:
        a.执行取消事务命令(discard)后,再进行事务的执行(exec),那么事务是不会执行的;
        b.事务中的命令出现命令性错误(例如:命令语法错误),执行事务时,所有的命令都不会被执行;
        c.事务中出现执行时错误(类似Java的运行时异常),执行事务时,部分命令会被执行成功,也即是不保证原子性;
        进一步说明:假设该事务有三步修改操作,就算在步骤1出现了执行时错误,步骤2、3依然会继续执行的;
        d.使用watch监视key在事务之前被改动,所有命令正常执行;
        e.使用watch监视key,此时在事务执行前key被改动,事务将取消不会执行所有命令;

在开发中,还可以采用lua脚本来实现事务的,简单理解:使用lua语言编写脚本传到redis中执行.

lua脚本

基本原理为使脚本相当于一个redis命令,可以结合redis原有命令,自定义脚本逻辑

两者相同点

很好的实现了一致性、隔离性和持久性,但没有实现原子性,无论是redis事务,还是lua脚本,如果执行期间出现运行错误,之前的执行过的命令是不会回滚的。

Lua脚本的优点

1.lua脚本是作为一个整体执行的,所以中间不会被其他命令插入,无需担心并发;

2.lua脚本把多条命令一次性打包,而代码实现的事务需要向Redis发送多次请求,所以可以有效减少网络开销;

3.lua脚本可以常驻在redis内存中,所以在使用的时候,可以直接拿来复用。

Lua的语法

 从 Redis 2.6.0 版本开始,通过内置的 Lua 解释器,可以使用 EVAL 命令对 Lua 脚本进行求值。

   脚本语法:EVAL script numkeys key [key …] arg [arg …]
   参数说明:
        1) script  参数是一段 Lua 脚本程序,它会被运行在 Redis 服务器上下文中,这段脚本不必(也不应该)定义为一个 Lua 函数。
        2) numkeys 参数用于指定键名参数的个数。
        3) 键名参数 key [key …] 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
        4) 附加参数 arg [arg …] ,可以在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。
   示例:

127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 one
two
1) "key1"
2) "key2"
3) "one"
4) "two"

    说明:
        a. “return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}”      是被求值的 Lua 脚本;
        b. 数字 2 指定了键名参数的数量;
        c. key1 和 key2 是键名参数值,分别使用 KEYS[1] 和 KEYS[2] 访问;
        d. one和 two 则是附加参数,可以通过 ARGV[1] 和 ARGV[2] 访问;

   redis.call()、redis.pcall() 函数

127.0.0.1:6379> eval "return redis.call('set',KEYS[1],'Jeck')" 1 name
OK

127.0.0.1:6379> eval "return redis.call('get','name')" 0
"Jeck"
127.0.0.1:6379>
#如下示例错误场景
127.0.0.1:6379> eval "return redis.call(ARGV[2],KEYS[1])" 1 key get
(error) ERR Error running script (call to f_7a3acf3a58256aab79c670cac4ca3427cf1c
5f83): @user_script:1: @user_script: 1: Lua redis() command arguments must be st
rings or integers
127.0.0.1:6379> eval "return redis.pcall(ARGV[2],KEYS[1])" 1 key get
(error) @user_script: 1: Lua redis() command arguments must be strings or intege
rs

 redis.call()与redis.pcall()的区别:
            redis.call() 在执行命令的过程中发生错误时,脚本会停止执行,并返回一个脚本错误,错误的输出信息会说明错误造成的原因。
            redis.pcall() 出错时并不引发(raise)错误,而是返回一个带 err 域的 Lua 表(table),用于表示错误

发表评论

您的电子邮箱地址不会被公开。