欢迎继续跟随《redis新手指南:从入门到精通》专栏的步伐!在本文中,我们将探讨redis的事务处理机制。了解如何使用事务来保证一系列操作的原子性和一致性,这对于构建可靠的应用程序至关重要
1 什么是redis事务🍀
redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序列化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中
总结说:redis 事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令
2 redis事务相关命令和使用🍀
multi、exec、discard 和 watch 是 redis 事务相关的命令
- multi :开启事务,redis会将后续的命令逐个放入队列中,然后使用exec命令来原子化执行这个命令系列。原子化执行,它们要么全部执行成功,要么全部回滚。
- exec:执行事务中的所有操作命令
- discard:取消事务,放弃执行事务块中的所有命令
- watch:监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令
- unwatch:取消watch对所有key的监视
1.标准的事务执行
给k1、k2分别赋值,在事务中修改k1、k2,执行事务后,查看k1、k2值都被修改
127.0.0.1:6379[1]> set key1 value1 ok 127.0.0.1:6379[1]> set key2 value2 ok 127.0.0.1:6379[1]> multi # 开启事务 ok 127.0.0.1:6379[1](tx)> set key1 11 queued 127.0.0.1:6379[1](tx)> set key2 22 queued 127.0.0.1:6379[1](tx)> exec # 执行事务中所有的操作命令 1) ok 2) ok 127.0.0.1:6379[1]> get key1 "11" 127.0.0.1:6379[1]> get key2 "22"
2.事务取消
127.0.0.1:6379[1]> multi ok 127.0.0.1:6379[1](tx)> set key1 111 queued 127.0.0.1:6379[1](tx)> set key2 222 queued 127.0.0.1:6379[1](tx)> discard # 取消事务 ok 127.0.0.1:6379[1]> exec (error) err exec without multi
3.事务出现错误的处理
- 语法错误(编译器错误)
在开启事务后,修改k1值为11,k2值为22,但k2语法错误,最终导致事务提交失败,k1、k2保留原值
127.0.0.1:6379> set k1 v1 ok 127.0.0.1:6379> set k2 v2 ok 127.0.0.1:6379> multi ok 127.0.0.1:6379> set k1 11 queued 127.0.0.1:6379> sets k2 22 (error) err unknown command `sets`, with args beginning with: `k2`, `22`, 127.0.0.1:6379> exec (error) execabort transaction discarded because of previous errors. 127.0.0.1:6379> get k1 "v1" 127.0.0.1:6379> get k2 "v2" 127.0.0.1:6379>
- redis类型错误(运行时错误)
在开启事务后,修改k1值为11,k2值为22,但将k2的类型作为list,在运行时检测类型错误,最终导致事务提交失败,此时事务并没有回滚,而是跳过错误命令继续执行, 结果k1值改变、k2保留原值
127.0.0.1:6379> set k1 v1 ok 127.0.0.1:6379> set k1 v2 ok 127.0.0.1:6379> multi ok 127.0.0.1:6379> set k1 11 queued 127.0.0.1:6379> lpush k2 22 queued 127.0.0.1:6379> exec 1) ok 2) (error) wrongtype operation against a key holding the wrong kind of value 127.0.0.1:6379> get k1 "11" 127.0.0.1:6379> get k2 "v2" 127.0.0.1:6379>
3 cas操作实现乐观锁🍀
watch 命令可以为 redis 事务提供 check-and-set (cas)行为
- cas? 乐观锁?redis官方的例子帮你理解
被 watch 的键会被监视,并会发觉这些键是否被改动过了。如果有至少一个被监视的键在 exec 执行之前被修改了, 那么整个事务都会被取消, exec 返回nil-reply来表示事务已经失败。
举个例子, 假设我们需要原子性地为某个值进行增 1 操作(假设 incr 不存在)。
首先我们可能会这样做:
val = get mykey val = val + 1 set mykey $val
上面的这个实现在只有一个客户端的时候可以执行得很好。但是, 当多个客户端同时对同一个键进行这样的操作时, 就会产生竞争条件。举个例子, 如果客户端 a 和 b 都读取了键原来的值, 比如 10 , 那么两个客户端都会将键的值设为 11 , 但正确的结果应该是 12 才对。
有了 watch ,我们就可以轻松地解决这类问题了:
watch mykey val = get mykey val = val + 1 multi set mykey $val exec
使用上面的代码, 如果在 watch 执行之后, exec 执行之前, 有其他客户端修改了 mykey 的值, 那么当前客户端的事务就会失败。程序需要做的, 就是不断重试这个操作, 直到没有发生碰撞为止。
这种形式的锁被称作乐观锁, 它是一种非常强大的锁机制。并且因为大多数情况下, 不同的客户端会访问不同的键, 碰撞的情况一般都很少, 所以通常并不需要进行重试。
- watch是如何监视实现的呢?
redis使用watch命令来决定事务是继续执行还是回滚,那就需要在multi之前使用watch来监控某些键值对,然后使用multi命令来开启事务,执行对数据结构操作的各种命令,此时这些命令入队列。
当使用exec执行事务时,首先会比对watch所监控的键值对,如果没发生改变,它会执行事务队列中的命令,提交事务;如果发生变化,将不会执行事务中的任何命令,同时事务回滚。当然无论是否回滚,redis都会取消执行事务前的watch命令

- watch 命令实现监视
在事务开始前用watch监控k1,之后修改k1为11,说明事务开始前k1值被改变,multi开始事务,修改k1值为12,k2为22,执行exec,发回nil,说明事务回滚;查看下k1、k2的值都没有被事务中的命令所改变。
127.0.0.1:6379> set k1 v1 ok 127.0.0.1:6379> set k2 v2 ok 127.0.0.1:6379> watch k1 ok 127.0.0.1:6379> set k1 11 ok 127.0.0.1:6379> multi ok 127.0.0.1:6379> set k1 12 queued 127.0.0.1:6379> set k2 22 queued 127.0.0.1:6379> exec (nil) 127.0.0.1:6379> get k1 "11" 127.0.0.1:6379> get k2 "v2"
- unwatch取消监视
127.0.0.1:6379> set k1 v1 ok 127.0.0.1:6379> set k2 v2 ok 127.0.0.1:6379> watch k1 ok 127.0.0.1:6379> set k1 11 ok 127.0.0.1:6379> unwatch ok 127.0.0.1:6379> multi ok 127.0.0.1:6379> set k1 12 queued 127.0.0.1:6379> set k2 22 queued 127.0.0.1:6379> exec 1) ok 2) ok 127.0.0.1:6379> get k1 "12" 127.0.0.1:6379> get k2 "22" 127.0.0.1:6379>
4 redis事务执行步骤🍀
通过上文命令执行,很显然redis事务执行是三个阶段:
- 开启:以multi开始一个事务
- 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
- 执行:由exec命令触发事务
当一个客户端切换到事务状态之后, 服务器会根据这个客户端发来的不同命令执行不同的操作:
- 如果客户端发送的命令为 exec 、 discard 、 watch 、 multi 四个命令的其中一个, 那么服务器立即执行这个命令。
- 与此相反, 如果客户端发送的命令是 exec 、 discard 、 watch 、 multi 四个命令以外的其他命令, 那么服务器并不立即执行这个命令, 而是将这个命令放入一个事务队列里面, 然后向客户端返回 queued 回复。

更深入的理解
我们再通过几个问题来深入理解redis事务。
为什么 redis 不支持回滚?
如果你有使用关系式数据库的经验,那么“redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。
以下是这种做法的优点:
- redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
- 因为不需要对回滚进行支持,所以 redis 的内部可以保持简单且快速。
有种观点认为 redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。举个例子, 如果你本来想通过 incr 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 incr , 回滚是没有办法处理这些情况的。
如何理解redis与事务的acid?
一般来说,事务有四个性质称为acid,分别是原子性,一致性,隔离性和持久性
原子性atomicity
# 首先通过上文知道 运行期的错误是不会回滚的,很多文章由此说redis事务违背原子性的;而官方文档认为是遵从原子性的。redis官方文档给的理解是,**redis的事务是原子性的:所有的命令,要么全部执行,要么全部不执行**。而不是完全成功
一致性consistency
# redis事务可以保证命令失败的情况下得以回滚,数据能恢复到没有执行之前的样子,是保证一致性的,除非redis进程意外终结
隔离性isolation
# redis事务是严格遵守隔离性的,原因是redis是单进程单线程模式(v6.0之前),可以保证命令执行过程中不会被其他客户端命令打断.但是,redis不像其它结构化数据库有隔离级别这种设计
持久性durability
# **redis事务是不保证持久性的**,这是因为redis持久化策略中不管是rdb还是aof都是异步执行的,不保证持久性是出于对性能的考虑
5 redis事务其它实现🍀
- 基于lua脚本,redis可以保证脚本内的命令一次性、按顺序地执行,其同时也不提供事务运行错误的回滚,执行过程中如果部分命令运行错误,剩下的命令还是会继续运行完
- 基于中间标记变量,通过另外的标记变量来标识事务是否执行完成,读取数据时先读取该标记变量判断是否事务执行完成。但这样会需要额外写代码实现,比较繁琐
到此这篇关于redis事务处理的实现示例的文章就介绍到这了,更多相关redis事务处理内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论