redis的原理和源码-事务机制

本文详细介绍了Redis事务的常用命令,如multi、exec和watch,展示了3阶段事务流程,以及如何通过队列管理和乐观锁实现事务监控。重点讲解了ACID特性在Redis中的应用,特别指出其原子性、一致性及隔离性的特点。
摘要由CSDN通过智能技术生成

Redis的事务

可以一次执行多个命令,本质是一组命令的集合。一个事物中的所有命令都会被序列化,按顺序的串行执行而不会被其他命令插入,不许加塞。

常用命令

命令描述
multi标记一个事务的开始
exec执行所有事务块内的命令
discard取消事务,放弃执行事务块内的所有命令
watch key [key]监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
unwatch取消watch命令对所有 key 的监视。

3阶段

开启:以multi开启事务

入队:将多个命令入队到事务中,接到这些命令不会立刻执行,而是放到等待执行的事务队列里面

执行:有exec命令触发事务

开启
入队
执行

事务队列

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

每一个Redis客户端都有自己的事务状态:

typedef struct client {
    //....
    multiState mstate;      /* MULTI/EXEC state 事务状态*/
    //...
}

事务状态包含一个事务队列以及一个命令计数器:

typedef struct multiState {
    multiCmd *commands;    //事务队列,FIFO
    int count;             //计数器
    int cmd_flags;          /* The accumulated command flags OR-ed together.
                               So if at least a command has a given flag, it
                               will be set in this field. */
    int minreplicas;        /* MINREPLICAS for synchronous replication */
    time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */
} multiState;

事务队列是一个multiCmd类型的数组,数组中的每个multiCmd结构都保存了一个已入队命令的相关信息,包括指向命令实现函数的指针、命令的参数,以及参数的数量:

typedef struct multiCmd {
    robj **argv;  //命令的参数
    int argc;  //参数数量
    struct redisCommand *cmd; //指向命令实现函数的指针
} multiCmd;

例子

  1. 正常执行

  2. 放弃事务

  3. 冤头债主

Watch监控

watch指令,类似乐观锁,WATCH命令是一个乐观锁( optimistic locking),它可以在EXEC命令执行之前,监视任意数量的数据库键,并在EXEC命令执行时,检查被监视的键是否至少有一个已经被修改过了,如果key的值已经被修改了,那么整个事务队列都不会被执行,同时返回一个Nullmulti-bulk应答以通知调用者事务执行失败。

注意:一旦执行了exec或者discard,之前加的所有监控锁都会被取消掉了。

例子:

  • 初始化信用卡的可用余额和欠额

  • 无加塞篡改

  • 有加塞篡改,当watch的key被修改,后面的那个事务全部执行失败

  • unwatch

每个Redis数据库都保存着一个watched_keys字典,这个字典的键是某个被WATCH命令监视的数据库键,而字典的值则是一个链表,链表中记录了所有监视相应数据库键的客户端:

typedef struct redisDb {
    dict *dict;                 /* The keyspace for this DB */
    dict *expires;              /* Timeout of keys with a timeout set 用来存放key的过期时间。*/
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP)* 处于阻塞状态的键和相应的client(主要用于List类型的阻塞操作)/
    dict *ready_keys;           /* Blocked keys that received a PUSH 准备好数据可以解除阻塞状态的键和相应的client*/
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS 被watch命令监控的key和相应client */
    int id;                     /* Database ID 数据库ID标识*/
    long long avg_ttl;          /* Average TTL, just for stats 数据库内所有键的平均TTL(生存时间) */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;

客户端c1和c2正在监视键“name";客户端c3正在监视键"age";客户端c2和c4正在监视键"address"。

所有对数据库进行修改的命令,比如 SET、LPUSH、SADD、ZREM、DEL、FLUSHDB等等,在执行之后都会调用multi.c/touchwatchKey函数对watched_keys字典进行检查,查看是否有客户端正在监视刚刚被命令修改过的数据库键,如果有的话,那么touchwatchKey函数会将监视被修改键的客户端的REDIS_DIRTY_CAS标识打开,表示该客户端的事务安全性已经被破坏。

ACID

原子性:redis的事务具有部分原子性,事务队列中的命令要么全部执行,要么都不执行,但Redis的事务和传统的关系型数据库事务的最大区别在于,Redis不支持事务回滚机制( rollback ),即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止。Redis的作者在事务功能的文档中解释说,不支持事务回滚是因为这种复杂的功能和Redis追求简单高效的设计主旨不相符,并且他认为,Redis事务的执行时错误通常都是编程错误产生的,这种错误通常只会出现在开发环境中,而很少会在实际的生产环境中出现,所以他认为没有必要为Redis开发事务回滚功能。

一致性:redis并不具有一致性,因为redis舍弃了回滚,错误的命令并不会影响事务的执行。

隔离性:redis没有隔离级别的概念,因为redis是单线程的执行,且服务器保证事务执行期间不会被打断。(其实就相当于mysql中的串行化隔离级别)

持久性:redis具有持久性,包括RDB和AOF两种持久化方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值