我们逐项考察下 Redis 在事务的 ACID 上做出的权衡与取舍:
(1)原子性(Atomicity)
原子意味着操作的不可再分,要么执行要么不执行。Redis 本身提供的所有 API 都是原子操作,那么 Redis 事务其实是要保证批量操作的原子性。Redis 实现批量操作的原理是在一个事务上下文中(通过 MULTI命令开启),所有提交的操作请求都先被放入队列中缓存,在 EXEC 命令提交时一次性批量执行。这样保证了批量操作的一次性执行过程,但 Redis 在事务执行过程的错误情况做出了权衡取舍,那就是放弃了回滚。 Redis 官方文档对此给出的解释是:
- Redis 操作失败的原因只可能是语法错误或者错误的数据库类型操作,这些都是在开发层面能发现的问题不会进入到生产环境,因此不需要回滚。
- Redis 内部设计推崇简单和高性能,因此不需要回滚能力。
- 批量操作在发送 EXEC 命令前被放入队列缓存
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中
注意:get、set是原子操作,但是get+set组合不是原子操作,存在多线程并发问题,使用incr, 或watch保证正确性
一。multi & exec
1. redis只能保证一个client发起的事务中的命令可以连续的执行, 由于redis是单线程来处理所有client的请求的,所以做到这点是很容易的。
redis> multi
OK
redis> incr a
QUEUED
redis> incr b
QUEUED
redis> exec
1. (integer) 1
2. (integer) 1
2. discard: 取消事务
redis> multi
OK
redis> incr a
QUEUED
redis> incr b
QUEUED
redis> discard
OK
redis> get a
"1"
redis> get b
"1"
3. 虽说redis事务在本质上也相当于序列化隔离级别的了。但是由于事务上下文的命令只排队并不立即执行,所以事务中的写操作不能依赖事务中的读操作结果。
redis> multi
OK
redis> incr a
QUEUED
redis> incr b
QUEUED
redis> discard
OK
redis> get a
"1"
redis> get b
"1"
4. watch : 实现乐观锁
redis> watch a
OK
redis> get a
"1"
redis> multi
OK
redis> set a 2
QUEUED
redis> exec
1. OK
redis> get a,
"2"
连接断开,监视和事务都会被自动清除。 exec,discard,unwatch命令都会清除所有监视.
二。存在问题
1.redis只能保证事务的每个命令连续执行,但是如果事务中的一个命令失败了,并不回滚其他命令
redis> set a 5
OK
redis> lpush b 5
(integer) 1
redis> set c 5
OK
redis> multi
OK
redis> incr a
QUEUED
redis> incr b
QUEUED
redis> incr c
QUEUED
redis> exec
1. (integer) 6
2. (error) ERR Operation against a key holding the wrong kind of value
3. (integer) 6
2.当事务的执行过程中,如果redis意外的挂了,只有部分命令执行。如果我们使用的append-only file方式持久化,redis会用单个write操作写入整个事务内容。
即是是这种方式还是有可能只部分写入了事务到磁盘。发生部分写入事务的情况下,redis重启时会检测到这种情况,然后失败退出。可以使用redis-check-aof
工具进行修复,修复会删除部分写入的事务内容。