不能回滚的Redis事务还能用吗 (2)

一致性指的就是事务执行前后的数据符合数据库的定义和要求。这一点 Redis 中的事务是符合要求的,上面讲述原子性的时候已经提到,不论是发生语法错误还是运行时错误,错误的命令均不会被执行。

I - 隔离性

事务中的所有命令都会按顺序执行,在执行 Redis 事务的过程中,另一个客户端发出的请求不可能被服务,这保证了命令是作为单独的独立操作执行的。所以 Redis 当中的事务是符合隔离性要求的。

D - 持久性

如果 Redis 当中没有被开启持久化,那么就是纯内存运行的,一旦重启,所有数据都会丢失,此时可以认为 Redis 不具备事务的持久性;而如果 Redis 开启了持久化,那么可以认为 Redis 在特定条件下是具备持久性的。

watch 命令

上面我们讲述 Redis 中事务时,提到的的常用命令还有一个 watch 命令,这个又是做什么用的呢?我们还是先来看一个例子。

首先打开一个客户端一,依次执行以下命令:

flushall //清空数据库 multi //开启事务 get name //获取 name,此时正常返回 nil set name lonely_wolf //设置 name get name //获取 name,此时正常应该返回 lonely_wolf

得到如下效果图:

不能回滚的Redis事务还能用吗

这时候我们先不执行事务,打开另一个客户端二,来执行一个命令 set name zhangsan:

不能回滚的Redis事务还能用吗

客户端二执行成功了,这时候再返回到客户端一执行 exec 命令:

不能回滚的Redis事务还能用吗

可以发现,第一句话返回了 zhangsan。也就是说,name 这个 key 值在入队之后到 exec 之前发生了变化,一旦发生这种情况,可能会引起很严重的问题,所以在关系型数据库可以通过锁来解决这种问题,那么 Redis 当中试如何解决的呢?

是的,在 Redis 当中就是通过 watch 命令来处理这种场景的。

watch 命令的作用

watch 命令可以为 Redis 事务提供 CAS 乐观锁行为,它可以在 exec 命令执行之前,监视任意 key 值的变化,也就是说当多个线程更新同一个 key 值的时候,会跟原值做比较,一旦发现它被修改过,则拒绝执行命令,并且会返回 nil 给客户端。
下面还是让我们通过一个示例来演示一下。

打开一个客户端一,依次执行如下命令:

flushall //清空数据库 watch name //监视 name multi //开启事务 set name lonely_wolf //设置 name set age 18 // 设置 age get name //获取 name get age //获取 age

执行之后得到如下效果图:

不能回滚的Redis事务还能用吗

这时候再打开一个客户端二,执行 set name zhangsan命令:

不能回滚的Redis事务还能用吗

然后再回到客户端一执行 exec命令。这时候会发现直接返回了 nil,也就是事务中所有的命令都没有被执行(即:只要检测到一个 key 值被修改过,那么整个事务都不会被执行):

不能回滚的Redis事务还能用吗

watch 原理分析

下面是一个 Redis 服务的数据结构定义:

typedef struct redisDb { dict *watched_keys; //被 watch 命令监视的 key int id; //Database ID //...省略了其他属性 } redisDb;

可以看到,redisDb 中的 watched_keys 存储了一个字典,这个字典当中的 key 存的就是被监视的 key ,然后字典的值存的就是客户端 id。然后每个客户端还有一个标记属性 CLIENT_DIRTY_CAS,一旦我们执行了一些如 set,sadd 等能修改 key 值对应 value 的命令,那么客户端的 CLIENT_DIRTY_CAS 标记属性将会被修改,后面执行事务提交命令 exec 时发现客户端的标记属性被修改过(乐观锁的体现),则会拒绝执行事务。

总结

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppsdw.html