【Redis】事务

如果你对Redis总体上没有认识,建议先看看我之前的博客,对Redis有详细的介绍。
和面试官从NoSql谈起【Redis】
【Redis】的五大基本类型

前言:
大白头同学今天问我,说她最近在使用Redis做缓存,问我Redis有事务管理吗?我的第一意识就是:既然Redis是数据库,那么它就肯定像MySQL一样是有事务的。大白头同学又问Redis中的事务也能保证原子性吗?我说不能。但是为什么不能,我一时语塞不能回答出来。

Redis中有事务吗?

Redis中可以实现事务,也就是能够将多条命令(多个操作)变成一个操作去执行。但是Redis中事务不保证原子性

Redis事务的本质:一组命令的集合

原因:

Redis中的事务其实是Redis中命令的组合,Redis中单条命令是保证原子性的,但是事务中组合多条命令的时候并不能保证原子性。也就是说,多条命令组合的事务在Redis中如果前面的命令正确执行,但是后面的命令出错(这里指的是运行时错误),那么前面的命令是可以正常执行的,而后面的命令是没有执行的。

画个图简单理解一下:

image-20201228104318658

一个事务中的所有命令会被序列化,在事务执行过程中,按照顺序执行

Redis事务的特性:一次性,顺序性,排他性。

Redis的事务就好像一个队列,多个Redis的命令在队列中排列,一个一个出队执行,因此前面的执行成功,后面的不一定执行成功,后面的命令即使执行失败,也不会对已经执行的命令造成影响。

Redis的事务如何执行

注意:事务在执行之后需要再次开启事务,一个事务是不能重复利用的

  • 开启事务:multi
  • 命令入队:Redis自动压入队
  • 执行事务:exec
mylocalhost:0>multi  # 开启事务
"OK"
mylocalhost:0>set k1 v1  # 命令入队
"QUEUED"
mylocalhost:0>set k2 v2  # 命令入队
"QUEUED"
mylocalhost:0>set k3 v3  # 命令入队
"QUEUED"
mylocalhost:0>exec  # 执行事务
 1)  "OK"
 2)  "OK"
 3)  "OK"
 4)  "OK"
 5)  "OK"
 6)  "OK"
 7)  "OK"
mylocalhost:0>get k1
"v1"
mylocalhost:0>get k2
"v2"
mylocalhost:0>get k3
"v3"
  • 除了在开启事务之后可以执行事务以外,也可以放弃事务,不去执行事务:discard

一旦放弃事务,那么事务队列中的所有命令都不会执行,这也从侧面说明了Redis的事务是在调用执行命令的时候才真正执行(也就是出队),而不是在命令添加到队列的时候

Redis事务中如果有错误,Redis如何处理?

在编写Redis事务时,可以会导致编写的事务命令是错误,这种错误大致可以分为两类,编译时错误和运行时错误,了解Redis对于这两种错误的处理方式,能更加合理的使用Redis(首先你就知道了Redis在发生错误时,到底做了什么事)。

编译时错误

Redis的编译时错误指的是:代码错误,也就是命令有错。事务中所有的命令都不会被执行

mylocalhost:0>multi
"OK"
mylocalhost:0>set k1 v1
"QUEUED"
mylocalhost:0>set k2 v2
"QUEUED"
mylocalhost:0>getset k2   #命令出错
"ERR wrong number of arguments for 'getset' command"
mylocalhost:0>set k3 v3
"QUEUED"
mylocalhost:0>get k3
"QUEUED"
mylocalhost:0>exec  # 导致所有的命令都没有执行
"EXECABORT Transaction discarded because of previous errors."

运行时错误

运行时错误(代码逻辑错,比如像1/0)。事务中除了该出错命令以外,其他命令是能正常执行的

mylocalhost:0>set k1 "str" #在事务开启之前,我们将k1设置为字符串tr
"OK"
mylocalhost:0>multi #开启事务
"OK"
mylocalhost:0>incr k1  # k1自增1  这里的代码会运行出错,因为自增只能是数字类型的
"QUEUED"
mylocalhost:0>set k2 v2  
"QUEUED"
mylocalhost:0>set k3 v3
"QUEUED"
mylocalhost:0>get k3
"QUEUED"
mylocalhost:0>exec  #执行事务
 1)  "OK"
 2)  "ERR value is not an integer or out of range"  # 该命令执行出错
 3)  "OK"   # 其余的代码都能正常执行
 4)  "OK"
 5)  "OK"
 6)  "OK"
 7)  "OK"
 8)  "v3"
 9)  "OK"

Redis实现乐观锁

什么是悲观锁?

悲观锁,顾名思义就是很悲观,当前线程在执行事务时,总感觉其他事务也会执行(可能会导致线程安全问题),因此它就给自己的操作上锁,只允许当前线程访问,当当前线程访问结束之后才让其他线程访问。

什么是乐观锁?

乐观锁,顾名思义就是很乐观,认为自己访问的时候其他线程是不会访问的,因此不加锁,但是如果保证数据的一致性呢?是通过对照之前的版本(类似于之前在MySQL中学习的version和JUC中的CAS思想)。

Redis是可以实现乐观锁的。使用的是监控的方式。

local:0>set money 100  #设置初始金钱100
"OK"
local:0>set out 0  #设置初始消费0
"OK"
local:0>watch money  # 监控金钱
"OK"
local:0>multi  #开启事务  在事务执行期间没有其他线程改变金钱100,那么事务正常执行
"OK"
local:0>decrby money 20  # 金钱-20
"QUEUED"
local:0>incrby out 20   #消费+20 
"QUEUED"
local:0>exec  # 执行事务
 1)  "OK"
 2)  "80"
 3)  "OK"
 4)  "20"
 5)  "OK"

模拟多线程下的Redis事务

第一个客户端

local:0>set money 100  # 设置初始金币100
"OK"
local:0>set money 20  #设置初始花费20
"OK"
local:0>watch money #监控金钱
"OK"
local:0>multi  #开启事务
"OK"
local:0>decrby money 20  # 金钱-20
"QUEUED"
local:0>incrby out 20 # 消费+20
"QUEUED"

第二个客户端

mylocalhost:0>set money 1000 # 在第一个客户端开启事务,并启动监控时,修改了金钱的值
"OK"

第一个客户端

local:0>exec  # 执行事务,发现没有输出,也就是没有执行任何事务中的操作
local:0>get money
"1000"
local:0>unwatch  #放弃监视之后,如果后续还需要对该数据进行操作,那么就用watch去重新监视  然后去执行
"OK"
local:0>watch money  #重新开启了监视
"OK"
local:0>multi #开启事务
"OK"
local:0>decrby money 20  #金币-20
"QUEUED"
local:0>incrby out 20  #消费-20
"QUEUED"
local:0>exec #执行事务,发现执行成功
 1)  "OK"
 2)  "980"
 3)  "OK"
 4)  "20"
 5)  "OK"

其实这就是乐观锁的思想:在当前线程执行时,发现了别的线程对这个数据进行了修改,那么我们这次就不对这个数据进行操作,而是获取新的数据后然后再对数据进行修改,很类似CAS的思想。是一种自旋的思想。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

炒冷饭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值