redis源码剖析——事务

事务

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

      事务首先以一个MULTI命令为开始,接着将多个命令放入事务当中,最后由EXEC命令将事务提交给服务器执行

事务开始

      MULTI命令通过在客户端状态的flags属性中打开REDIS_MULTI标识,将执行该命令的客户端从非事务状态切换至事务状态。

def MULTI():

  # 打开事务标识
  client.flags |= REDIS_MULTI

  # 返回OK回复
  replyOK()

命令入队

  1. 客户端处于非事务状态时,客户端发送的命令会立即被服务器执行。
  2. 客户端切换到事务状态,服务器会根据这个客户端发来的不同命令执行不同的操作。

事务队列

每个Redis客户端将自己的事务状态保存在客户端状态的mstate属性里面:

typedef struct redisClient {

  // ...

  // 事务状态
  multiState mstate;  /* MULTI/EXEC state */

  // ...

} redisClient;

事务状态为一个multiState结构,包含一个事务队列,以及一个已入队命令的计数器

typedef struct multiState {

  // 事务队列,FIFO顺序
  multiCmd *commands;

  // 已入队命令计数
  int count; 

} multiState;

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

typedef struct multiCmd { 

  // 参数
  robj **argv;

  // 参数数量
  int argc;

  // 命令指针
  struct redisCommand *cmd;

} multiCmd;

执行事务

当处于事务状态的客户端向服务器发送EXEC命令时,EXEC命令立即被服务器执行。服务器会遍历这个客户端的事务队列,执行队列中保存的所有命令。队列中的命令全部执行完再集中全部返回给客户端,不是执行一条返回一条。

def EXEC():
  # 创建空白的回复队列
  reply_queue = []
 
  # 遍历事务队列中的每个项
  # 读取命令的参数,参数的个数,以及要执行的命令
  for argv, argc, cmd in client.mstate.commands:

    # 执行命令,并取得命令的返回值
    reply = execute_command(cmd, argv, argc)

    # 将返回值追加到回复队列末尾
    reply_queue.append(reply)

  # 移除REDIS_MULTI标识,让客户端回到非事务状态
  client.flags & = ~REDIS_MULTI

  # 清空客户端的事务状态,包括:
  #1 )清零入队命令计数器
  #2 )释放事务队列

  client.mstate.count = 0
  release_transaction_queue(client.mstate.commands)

  # 将事务的执行结果返回给客户端
  send_reply_to_client(client, reply_queue)

WATCH 命令的实现

WATCH命令是一个乐观锁,在EXEC命令执行时,检查被监视的键是否至少有一个已经被修改过,如果有,服务器将拒绝执行事务,并向客户端返回代表事务执行失败的空回复。

eg:在时间T4,客户端B修改了"name"键的值,当客户端A在T5执行EXEC命令时,服务器会发现WATCH监视的键"name"已经被修改,因此服务器拒绝执行客户端A的事务,并向客户端A返回空回复。

使用WATCH 命令监视数据库键

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

typedef struct redisDb {
  // ...

  // 正在被WATCH命令监视的键
  dict *watched_keys;

  // ...
} redisDb;

 客户端为c10086,执行 WATCH "name" "age" 命令前后对比如下:

                 

导致整个事务无法提交的情况

 

事务的ACID 性质

事务总是具有原子性一致性隔离性,并且当Redis 运行在某种特定的持久化模式下时,事务也具有耐久性

原子性

要么就执行事务中的所有操作,要么就一个操作也不执行。

Redis的事务和传统的关系型数据库事务的最大区别在于,Redis不支持事务回滚机制,即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止。  注意以下两种情况除外:

1.由于事务入队的命令不存在,或者参数类型,参数个数不符合要求,整个事务将执行失败;
2.由于客户端监视的键被修改,造成事务的安全性被破坏,事务也不会执行;

一致性

  1. 入队错误:如果一个事务在入队命令的过程中,出现了命令不存在,或者命令的格式不正确等情况,那么Redis将拒绝执行这个事务。
  2. 执行错误即使在事务的执行过程中发生了错误,服务器也不会中断事务的执行,它会继续执行事务中余下的其他命令,并且已执行的命令(包括执行命令所产生的结果)不会被出错的命令影响。
  3. 服务器停机如果服务器运行在无持久化的内存模式下,那么重启之后的数据库将是空白的;如果服务器运行在RDB 模式下,服务器可以根据现有的RDB 文件来恢复数据;如果服务器运行在AOF模式下,根据现有的AOF文件来恢复数据。

隔离性

因为Redis使用单线程的方式来执行事务,所以Redis的事务总是以串行的方式运行的,并且事务也总是具有隔离性的。

耐久性

事务的耐久性指,当一个事务执行完毕时,执行这个事务所得的结果已经被保存到永久性存储介质里面了,即使服务器在事务执行完毕之后停机,执行事务所得的结果也不会丢失。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值