redis的事务

redis通过 multi, exec等命令来实现事务功能,是一种交互式事务

工作机制: 在一个事务内,将事务内的多个请求打包,然后一次性、按顺序地执行多个命令,在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求

相关命令

  • MULTI :开启事务,redis会将后续的命令逐个放入队列中,然后使用EXEC命令来原子化执行这个命令系列
  • EXEC:执行事务中的所有操作命令
  • DISCARD:取消事务,放弃执行事务块中的所有命令,不等于回滚
  • WATCH:监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令
  • UNWATCH:取消WATCH对所有key的监视

基本使用

通过上文命令执行,很显然Redis事务执行是三个阶段:

  1. 开启事务:以MULTI开始一个事务
  2. 命令入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
  3. 执行事务:由EXEC命令触发事务

假设 a:stock、b:stock 两个键的初始值是 5 和 10

#初始化
127.0.0.1:6379> set a:stock 5
OK
127.0.0.1:6379> set b:stock 10
OK
#开启事务
127.0.0.1:6379> multi
OK
#将a:stock减1,
127.0.0.1:6379(TX)> decr a:stock
QUEUED
#将b:stock减1
127.0.0.1:6379(TX)> decr b:stock
QUEUED
#实际执行事务
127.0.0.1:6379(TX)> exec
1) (integer) 4
2) (integer) 9

执行步骤

当一个客户端切换到事务状态之后, 服务器会根据这个客户端发来的不同命令执行不同的操作:

  • 如果客户端发送的命令为 EXEC 、 DISCARD 、 WATCH 、 MULTI 四个命令的其中一个, 那么服务器立即执行这个命令
  • 与此相反, 如果客户端发送的命令是 EXEC 、 DISCARD 、 WATCH 、 MULTI 四个命令以外的其他命令, 那么服务器并不立即执行这个命令, 而是将这个命令放入一个事务队列里面, 然后向客户端返回 QUEUED 回复

ACID性质

原子性

⚠️ 不支持原子性,没有回滚机制

  • 对于编译错误,exec之前就能识别出来,exec时事务会失败,队列中的命令都不执行

    127.0.0.1:6379> get a:stock
    "3"
    127.0.0.1:6379> get b:stock
    "9"
    #开启事务
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379(TX)> incr a:stock
    QUEUED
    #错误命令
    127.0.0.1:6379(TX)> lpush a:stock
    (error) ERR wrong number of arguments for 'lpush' command
    #执行事务,因为错误命令导致事务被取消
    127.0.0.1:6379(TX)> exec
    (error) EXECABORT Transaction discarded because of previous errors.
    127.0.0.1:6379> get a:stock
    "3"

  • 对于运行时错误,例如类型使用错误,当前错误用法会失败,其他命令会执行成功且不会回滚

    127.0.0.1:6379> get a:stock
    "3"
    127.0.0.1:6379> get b:stock
    "9"
    #开启事务
    127.0.0.1:6379> multi
    OK
    #命令1
    127.0.0.1:6379(TX)> incr a:stock
    QUEUED
    #错误命令2
    127.0.0.1:6379(TX)> lpush b:stock 1
    QUEUED
    #命令3
    127.0.0.1:6379(TX)> incr a:stock
    QUEUED
    127.0.0.1:6379(TX)> exec

    1. (integer) 4 #命令1执行成功
    2. (error) WRONGTYPE Operation against a key holding the wrong kind of value #命令2执行失败
    3. (integer) 5 #命令3执行成功

隔离性

⚠️ 总是具有隔离性的

因为redis使用单线程的方式来执行命令 ,并且服务器保证了在执行事务期间不会对事务进行中断,因此redis的事务总是以串行的方式运行的,总是具有隔离性的

注意:事务真正的执行是从exec开始的,所以串行也是以exec为标志的,multi开始后的key可能被更新,所以redis提供了watch命令实现一个CAS乐观锁

watch命令

watch命令 是一个CAS乐观锁,它在exec命令执行之前,监视任意数量的数据库键,并在exec命令执行时,检查被监视的键,如果有一个已经被修改过了,redis会取消事务, 返回nil

  • watch命令需要客户端主动显式在multi命令外执行
  • watch命令不存在ABA问题
watch的实现机制

每个redis数据库都保存着一个watched_keys字典,字典的键是这个数据库被监视的键, 而字典的值则是一个链表, 链表中保存了所有监视这个键的客户端

如果当前客户端为 client10086 , 那么当客户端执行 WATCH key1 key2 时, 前面展示的 watched_keys 将被修改成这个样子:

通过 watched_keys 字典, 如果程序想检查某个键是否被监视, 那么它只要检查字典中是否存在这个键即可; 如果程序要获取监视某个键的所有客户端, 那么只要取出键的值(一个链表), 然后对链表进行遍历即可

所有对数据库进行修改的命令如:set,lpush,del等,在执行后都会对watched_keys字典检查,若有客户端正在监视被修改的key,则将监视的客户端的REDIS_DIRTY_CAS标识打开,表示该客户端的事务安全性已经被破坏

当服务器接收到一个客户端发来的exec命令时,会根据该客户端是否打开了REDIS_DIRTY_CAS标识来决定是否执行事务,若客户端的REDIS_DIRTY_CAS被打开,服务器会拒绝事务

持久性

⚠️ 不具有持久性

Redis并没有未事务提供任何额外的持久化功能,所以Redis事务的持久性由redis所使用的持久化模式决定:

  • RDB 适合做冷备,保存的是某一个时间的数据快照
  • AOF 适合热备,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次 fsync 操作,最多丢失 1 秒钟的数据

但是不论在什么模式下运行,在一个事务的最后加上SAVE命令总是可以保证事务的持久性的,但是效率太低,不实用。所以,持久性,也不一定保证

一致性

⚠️ 个人观点:redis事务不能保证一致性,首先不具有回滚能力无法保证原子性,事务执行后无法保证数据的状态一起变更,其次不具备持久性,丢失数据后也会导致数据不一致

网上和一些书籍的观点是 Redis事务是支持一致性的,侧重点是没有包含非法或者无效的错误数据

小结

Redis事务只是一组打包好的命令,保证其能一次性、有序地、不被打断的执行,不具备原子性,并不是真正意义上的事务

相关推荐
Allen Bright7 分钟前
Redis介绍
数据库·redis·缓存
cui_win2 小时前
Redis高可用-Cluster(集群)
数据库·redis·bootstrap
Allen Bright3 小时前
Redis安装
数据库·redis·缓存
阿乾之铭5 小时前
Spring Boot中集成Redis与MySQL
spring boot·redis·mysql
哭哭啼8 小时前
Redis环境部署(主从模式、哨兵模式、集群模式)
数据库·redis·缓存
明志致远淡泊宁静10 小时前
记录一次服务器redis被入侵
运维·服务器·redis
WuMingf_10 小时前
redis
数据库·redis
精进攻城狮@12 小时前
Redis(value的数据类型)
数据库·redis
jwybobo200713 小时前
redis7.x源码分析:(3) dict字典
linux·redis