Redis6之(四)Redis中的事务

一、Redis事务简介

1.1 什么是 Redis事务?

Redis 事务的本质就是一组命令的集合。是指一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

总的来说:Redis 事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。

1.2 为什么要使用事务?

Redis 事务的主要作用就是串联多个命令防止别的命令插队。

注意:

  1. 在 Redis 事务中没有隔离级别的概念;
  2. 在 Redis 单条命令是保证原子性的,但是事务不保证原子性;
  3. Redis 是不支持事务回滚的。

二、Redis事务的相关命令

从输入 multi 命令开始,输入的命令都会依次进入命令队列中,但不会执行。直到输入 exec 后,Redis会将之前的命令队列中的命令以此执行。组队的过程中可以通过 discard 来放弃组队。
在这里插入图片描述

2.1 MULTI—标记事务块的开始

用于标记事务块的开始。

Redis 会将后续的命令逐个放入队列中,然后才能使用 EXEC 命令原子化地执行这个命令序列。

# 标记事务块的开始
multi

在这里插入图片描述
此命令的返回值是一个字符串—OK。

2.2 EXEC—执行所有事务块内的命令

执行所有事务块内的命令。

在一个事务中执行所有先前放入队列的命令,然后回复正常的连接状态。

当使用 WATCH 命令时,只有当受监视的键没有被修改时,EXEC 命令才会执行事务中的命令,这种方式利用了检查再设置(CAS)的机制。

# 执行所有事务块内的命令
exec

在这里插入图片描述
此命令的返回值是一个数组,其中的没歌元素分别是原子化事务中的每个命令的返回值。当时用 WATCH 命令时,如果事务执行中止,exec 命令就会返回一个 null 值。

2.3 DISCARD—取消事务

取消事务,放弃执行事务块内的所有命令。

清除所有先前在一个事务中放入队列的命令,然后恢复正常的连接状态。

如果使用了 WATCH 命令,那么 DISCARD 命令就会将当前连接监控的所有键取消监控。

# 取消事务
discard

在这里插入图片描述

此命令的返回值是一个简单的字符传——OK。

2.4 WATCH—监视一个(或多个key)

Redis中使用WATCH实现乐观锁!

监视一个(或多个)key,如果在事务执行之前这个(或这些)key被其他命令(其他线程)所改动,那么事务将被打断。

当某个事务需要按条件执行时,就要使用这个命令将给定的键设置为受监控的,一旦其中有一个键被修改(或删除),之后的事务就不会执行。监控一直持续到 exec命令(事务中的命令是在 exec之后才执行的,所以在 multi命令后可以修改 watch监控的键值)。假设我们通过 watch命令再事务执行之前监控了多个 keys,倘若在 watch之后有任何 key的值发生了变化,exec命令执行的事务都将放弃,同时会应答调用者事务执行失败。

watch key [key ...]

在这里插入图片描述

注意:
这个监视是指在多线程的情况下,别的线程来修改自己线程中的值时,exec命令执行的事务都将被抛弃,表示事务执行失败!

2.5 UNWATCH—取消WATCH命令对所有key的监视

取消WATCH命令对所有 key的监视。

如果调用了 EXEC或 DISCARD命令,那么就不需要手动调用 UNEATCH命令。

unwatch

在这里插入图片描述
此命令的返回值是一个简单的字符串——OK。

三、Redis事务的错误处理

在一个事务的运行期间,可能会遇到两种类型的命令错误:命令被放入队列时失败调用exec命令后事务的某个命令执行失败

3.1 命令被放入队列时失败

在调用 exec 命令之前,这些客户端可以检查被放入队列的命令的返回值:如果命令的返回值是QUEUE字符串,那么就表示已经正确地将这个命令放入队列;否则,Redis将返回一个错误。如果将某个命令放入队列时发生错误,那么大多数客户端将会中止事务,并且丢弃这个事务。

but,从Redis 2.6.5版本开始,服务器会记住事务累积命令期间发生的错误,然后 Redis会拒绝执行这个事务,在运行EXEC命令之后便会返回一个错误消息。最后 Redis会自动丢弃这个事务。

在Redis 2.6.5版本之前,如果发生了上述的错误,那么在客户端调用EXEC命令之后,Redis还是会运行这个出错的事务,执行已经成功放入事务队列的命令,而不会关心先前发生的错误。从2.6.5版本开始,Redis在遭遇上述错误时,会采用先前描述的新行为,这样便能轻松地混合使用事务和管道。在这种情况下,客户端可以一次性地将整个事务发送至Redis服务器,稍后再一次性地读取所有的返回值。
在这里插入图片描述

3.2 调用exec命令后事务的某个命令执行失败

在调用EXEC命令之后发生的事务错误,Redis不会进行任何特殊处理:在事务运行期间,即使某个命令运行失败,所有其他的命令也会继续执行。
在这里插入图片描述

四、Redis事务三特性

4.1 单独的隔离操作

事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中不会被其他客户端发送来的命令请求所打断。

4.2 没有隔离级别的概念

队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行。

4.3 不保证原子性

事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。

为什么Redis不支持回滚?
在事务运行期间,虽然Redis命令可能会执行失败,但是Redis仍然会执行事务中余下的其他命令,而不会执行回滚操作,你可能会觉得这种行为很奇怪。但是也有其合理之处:
只有当被调用的Redis命令有语法错误时,这条命令才会执行失败(在将这个命令放入事务队列期间,Redis能够发现此类问题),或者对某个键执行不符合其数据类型的操作。实际上,这就意味着只有程序错误才会导致Redis命令执行失败,这种错误很有可能在程序开发期间发现,一般很少在生产环境发现。
Redis已经在系统内部进行功能简化,这样可以确保更快的运行速度,因为Redis不需要事务回滚的能力。
对于Redis事务的这种行为,有一个普遍的反对观点,那就是程序有可能会有bug。但是事务回滚并不能解决任何程序错误——没有人能解决程序猿自己的错误,这种错误可能会导致Redis命令执行失败。正因为这些程序错误不大可能会进入生产环境,所以我们在开发Redis时选用更加简单和快速的方法,没有实现错误的回滚的功能。

五、悲观锁和乐观锁

5.1 悲观锁

5.1.1 简介

通俗来讲,悲观锁就是很悲观。每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁、表锁等,读锁、写锁等都是在操作之前先上锁让别人无法操作该数据。

5.1.2 使用场景

比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。

悲观锁比较适合强一致性的场景,但效率比较低,特别是读的并发低。

5.2 乐观锁

5.2.1 简介

通俗来讲,乐观锁就是很乐观。每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁,但是在更新数据库中的数据时需要判断该数据是否被别人修改过。如果数据被其他线程修改,则不进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。

一般使用版本号机制进行判断。乐观锁大多数情况是基于数据版本号(version)的机制实现的。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表添加一个“version”字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1。此时,将提交数据的版本号与数据库表对应记录的当前版本号进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据,不予更新。

5.2.2 适用场景

比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。

乐观锁则适用于读多写少,并发冲突少的场景。

5.2.3 Redis使用WATCH实现乐观锁

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set cost 0
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 30
QUEUED
127.0.0.1:6379(TX)> incrby cost 30
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 70
2) (integer) 30

六、Redis脚本和事务

根据定义,Redis脚本也是事务型的。因此,可以通过Redis事务实现的功能,同样也可以通过Redis脚本来实现,而且通常脚本更简单、更快速。

由于Redis从2.6版本才开始引入脚本特性,而事务特性是很久以前就已经存在的,所以目前的版本才有两个看起来重复的特性。但是,我们不太可能在短时间内移除对事务特性的支持。因为,即使不用求助于Redis脚本,用户仍然能够规避竞争状态,这从语义上来看是适宜的。还有另一个更重要的原因,Redis事务特性的实现复杂度是最小的。

但是,在相当长的一段时间之内,我们不大可能看到整个用户群体都只使用Redis脚本。如果发生这种情况,那么我们可能会废弃,甚至最终移除Redis事务。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值