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

相关推荐
风向决定发型丶2 小时前
redis集群搭建
数据库·redis·缓存
梦想的颜色4 小时前
硬核实践:使用 Docker 部署生产级 Redis(持久化 + 安全配置 + 高可用)
redis·docker·redis持久化·docker compose·redis哨兵·rdb aof
宠友信息6 小时前
多端数据互通场景下Spring Boot仿小红书源码结构设计
数据库·spring boot·redis·缓存·架构
长不胖的路人甲6 小时前
Redis 缓存的数据持久化方案讲解
数据库·redis·缓存
长不胖的路人甲6 小时前
Redis 单线程为什么速度很快
数据库·redis·缓存
彦为君7 小时前
算法思维与经典智力题
java·前端·redis·算法
彦为君8 小时前
Redis最新版本特性
java·数据库·redis·算法·bootstrap
长不胖的路人甲9 小时前
Redis 数据删除策略
数据库·redis·spring
尽兴-9 小时前
Redis 为什么快?
数据库·redis·内存
一嘴一个橘子11 小时前
redis.windows.conf 的 保护模式
redis