引言
Redis是基于键值对的非关系型数据库,秉承设计简单的原则内部也提供事务机制,但是提供的事务可能不满足ACID原则
本文将深入浅出的介绍如何使用事务、事务命令运行的流程以及Redis事务是否满足ACID原则
事务
Redis提供了multi、exec、discord、watch、unwatch命令来实现事务机制
multi、exec、discord命令
multi命令用来开启事务,exec和discord分别用来执行事务和丢弃事务
#开启事务 127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED #丢弃事务 127.0.0.1:6379> discard OK 127.0.0.1:6379> get k2 (nil)
使用multi命令开启事务后,会将所有的命令放入事务队列中
使用exec命令开始执行时,会依次执行事务队列中的命令
#开启事务 127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> set k3 v3 QUEUED 127.0.0.1:6379> mget k2 k3 QUEUED #执行事务 127.0.0.1:6379> exec 1) OK 2) OK 3) OK 4) 1) "v2" 2) "v3"
watch、unwatch命令
watch命令用于multi开启事务之前,用来监听某个键对象,执行事务时判断这个键对象是否改变,如果改变则事务不会执行
使用unwatch命令来对监视的键值对进行解锁
127.0.0.1:6379> set money 100 OK 127.0.0.1:6379> set out 0 OK 127.0.0.1:6379> watch money # 监视 money OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> decrby money 50 QUEUED 127.0.0.1:6379> incrby out 50 QUEUED 127.0.0.1:6379> exec #执行之前,另外一个线程修改了money为666 (nil) 127.0.0.1:6379> unwatch OK 127.0.0.1:6379> watch money OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> decrby money 10 QUEUED 127.0.0.1:6379> incrby out 10 QUEUED 127.0.0.1:6379> exec 1) (integer) 656 2) (integer) 10
watch功能由watch字典实现,watch字典Key为监视的key,Value为监视Key的所有客户端
当发生写命令后检查watch字典是否存在操作的KEY,存在则拿到对应Value中客户端,对客户端进行标志,当客户端执行事务exec前判断客户端是否标志,标志说明其他事务修改过拒绝执行
watch监控的key发生变动后,保证事务不会执行成功,并返回null,可以用来做乐观锁(上游业务收到为null就重试【先unwatch再watch】)
ACID原则
事务ACID原则分别代表着原子性、一致性、隔离性、持久性
原子性
原子性是一组事务要么都成功,要么都失败
当命令输入错误会在执行时直接报错,这种情况下能够满足原子性
127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 k1 QUEUED 127.0.0.1:6379> seg k2 k2 #人为错误命令 (error) ERR unknown command `seg`, with args beginning with: `k2`, `k2`, 127.0.0.1:6379> exec (error) EXECABORT Transaction discarded because of previous errors.
当命令出现语法错误时,不在执行时直接报错,会执行到具体命令时才报错,这种情况下除了不报错的命令不执行,事务中其他正常的命令会执行,不能满足原子性
127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 k1 QUEUED 127.0.0.1:6379> incr k1 #人为语法错误 QUEUED 127.0.0.1:6379> exec 1) OK 2) (error) ERR value is not an integer or out of range 127.0.0.1:6379> keys * 1) "k1"
Redis认为以上两种错误是开发、测试阶段才出现的,生产环境不会出现,秉承简单设计原则,没有提供回滚功能
当Redis执行事务到一半时,发生宕机也不能满足原子性
只有当命令不出现语法错误、服务不宕机的情况下才能够满足原子性
隔离性
隔离性问题的产生在于数据库系统多线程执行,但Redis是单线程执行命令的,并且执行事务时是对事务队列中的命令依次执行,因此Redis不会出现隔离性问题
一致性
一致性通常由业务层来校验保证,在不宕机的情况下是满足一致性的
持久性
持久性是事务执行成功时就持久化到磁盘
默认使用RDB进行持久化,这种方式是不能满足持久性的
当持久化策略为AOF always每次刷盘时,事务执行成功时能够保证每条命令持久化
不了解Redis持久化的同学,可以看上一篇文章深入浅出Redis(四):Redis基于RDB、AOF的持久化
总结
本篇文章围绕Redis提供的事务机制,深入浅出的介绍了事务相关命令的使用与原理以及事务ACID原则
事务开始时会将事务中的命令放入事务队列,事务执行时是对事务队列中的命令依次执行
watch命令用watch字典实现,Key为监控的key,Value为监视Key的所有客户端;当对监控的key写操作时,会监控key的所有客户端做标记,客户端执行事务时服务端判断客户端是否有标记,有则说明监视期间被修改,不允许执行事务
命令错误会在事务执行时就报错,导致事务不能执行;命令的语法错误则是会在事务执行中单独报错,未提供回滚机制
当持久化策略改为aof每次刷盘时,则能够满足持久性
最后
- 参考资料
- 《Redis深度理解》
- 《Redis设计与实现》