redis事务和watch
# 事务
- ACID
- A: 原子性
- C: 一致性
- I: 隔离性
- D: 持久性
# redis事务
redis 事务并不是真正意义上的事务, 原因:
Redis 作者认为发生事务回滚的原因大部分都是程序错误导致,这种情况一般发生在开发和测试阶段,而生产环境很少出现。
对于逻辑性错误,比如本来应该把一个数加 1 ,但是程序逻辑写成了加 2,那么这种错误也是无法通过事务回滚来进行解决的。
Redis 追求的是简单高效,而传统事务的实现相对比较复杂,这和 Redis 的设计思想相违背。
redis可以把一组操作包装在一个事务块中,让这些语句的执行具有原子性
- multi:开启事务
- exec:执行事务
- discard:取消事务
- watch:监视
Redis中的事务原理:就是以multi为起点,在提交之前,先把所有的语句都放在一个队列中,直到exec才做事务的提交
示例
set user?account 1000 get user?account set user:101:account 10 get user:101:account multi decrby user?account 500 // 转出500 incrby user:101:account 500 //转入500 exec
1
2
3
4
5
6
7
8
9Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。
其中,Redis事务分为三个阶段
开始事务
命令入队
执行事务
# a. 正常执行
# b. 放弃事务
# c. 事务执行出错 - 命令错误
若在事务队列中存在命令性错误(类似于java编译性错误),则执行EXEC命令时,所有命令都不会执行
# d. 事务执行出错 - 抛异常
若在事务队列中存在语法性错误(类似于java的1/0的运行时异常),则执行EXEC命令时,其他正确命令会被执行,错误命令抛出异常
# watch 命令
# 1. 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
1
2
3
4
5
6
7执行之后得到如下效果图:
这时候再打开一个客户端二,执行
set name zhangsan
命令:然后再回到客户端一执行 exec命令。这时候会发现直接返回了 nil,也就是事务中所有的命令都没有被执行(即:只要检测到一个 key 值被修改过,那么整个事务都不会被执行):
# 2. watch 原理分析
下面是一个 Redis 服务的数据结构定义:
typedef struct redisDb { dict *watched_keys; //被 watch 命令监视的 key int id; //Database ID //...省略了其他属性 } redisDb;
1
2
3
4
5redisDb 中的
watched_keys
存储了一个字典,这个字典当中的 key 存的就是被监视的 key ,然后字典的值存的就是客户端 id。然后每个客户端还有一个标记属性CLIENT_DIRTY_CAS
,一旦我们执行了一些如 set,sadd 等能修改 key 值对应 value 的命令,那么客户端的CLIENT_DIRTY_CAS
标记属性将会被修改,后面执行事务提交命令 exec 时发现客户端的标记属性被修改过(乐观锁的体现),则会拒绝执行事务。