Redis(三十七):事务

之前已经学习过,Redis的命令是保存在服务器状态里面的命令字典里面,字典里面的值就是redisCommand。

执行事务

当服务器接收到客户端传来的exec命令之后,服务器就会遍历该客户端状态里面的事务队列,然后执行队列中保存的所有命令,最后是将命令所得的结果全部返回给该客户端,执行命令的顺序当然是按照队列先进先出的原则。

但这里要注意的是,命令执行完的结果是也是放在一个队列里面,执行完后要进行释放资源和修改对应客户端的状态

  • 移除客户端状态的事务标识,也就是移除REDIS_MULTI标识,让客户端回到非事务的状态

  • 对事务队列进行释放

  • 对入队命令计数器进行清零

伪代码表示

def exec():

#创建空白的回复队列,用于保存命令的执行结果

reply_queue = []

#遍历事务队列中的每个项,这里要遍历的

for multiCmd in client.mstate.commands:

#使用队列项里的参数、参数个数和命令来执行命令

reply = execute_command(multiCmd.cmd,multiCmd.argv,multiCmd.argc)

#将返回值追加到回复队列里面

reply_queue.append(reply)

End For

#移除客户端状态的事务标识

#计算方法是与上事务标识的非

client.flags &= ~REDIS_MULTI;

#最后还要释放资源

#计数器清0

client.mstate.commands.argc = 0;

#队列释放

release_transaction_queue(client.mstate.commands);

#最后就是将回复队列返回给客户端

send_reply_to_client(client,reply_queue);

end exec;

事务的整体过程已经解释完了。

整个过程就是

  1. 服务端接收到客户端的multi命令,将客户端标记为事务状态

  2. 客户端发送键值对命令给服务端,服务端检测到客户端是事务状态,将命令保存到客户端里面的命令队列里面

  3. 客户端发送exec命令,就要进行提交事务,通过遍历命令队列,将回复一一保存在回复队列里面,命令执行完后,就将回复队列返回给客户端

WATCH命令的实现


WATCH命令是用来监视数据库键的

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

在这里插入图片描述

这里要注意是,WATCH命令监视的键值对是保存在数据库状态中的

typedef struct redisDb(

//…

//正在被WATCH命令监视的键

dict *watched_keys;

//…

)redisDb;

通过该字典,服务器就可以知道哪些数据库键被哪些客户端监视中

客户端执行WATCH命令,那么服务端就会将其与对应的键值对关联起来,本质上就是在对应的数据库中的watch_keys字典中,先在字典中查找有没有改键值

  • 如果有,证明已经有客户端在监视该键值对了,那么就在对应的链表后面加上此时要监视该键值对的客户端

  • 如果没有,证明该客户端是第一个键值该键值对的客户端,需要在数组中新增该键值,然后初始化链表,然后往链表中增添该客户端状态

监视机制的触发

在服务器执行客户端传来的对键值对修改的命令,比如set、lpush、sadd等。。在执行之后都会调用touchWatchKey函数对watched_kets字典进行检查(这也是为什么被监视的键值对要放在数据库状态中,为了方便全局检查),看看字典中有保存着该键值,如果有,就证明有客户端正在监视这个键值对,那么就要对监视该键值对的客户端进行修改

对应的修改操作为,将监视该键值对的所有客户端的flags修改为REDIS_DIRTY_CAS标识,表示该客户端的事务安全性已经被破坏了,即事务不完整了,不可以提交

判断事务是否安全


当服务器接收到一个客户端发来的EXEC命令之后,服务器会去看客户端对应的客户端状态是否打开了REDIS_DIRTY_CAS标识,也就是查看事务的安全性是否被破坏,分下面两种情况来进行讨论

  • 如果客户端的REDIS_DIRTY_CAS标识已经被打开,那么说明客户端所监视的键当中,至少有一个键是被修改了(事务未提交前),事务已经不再安全了,所以服务器会拒绝执行客户端提交的事务

  • 如果客户端的REDIS_DIRTY_CAS标识没有被打开,那么说明客户端监视的键都没有被修改过(事务未提交前),所示认定事务仍然是安全的,服务器会执行客户端的提交命令

为什么会出现事务不安全呢?

这里还要注意的是,WATCH命令是不可以在事务里面使用的,也就是说,必须用在Multi命令之前,而且事务只会在提交时发生阻塞,命令入队一半不会阻塞,所以在提交之前,其他客户端是可以对任意键值对操作的,所以就会发生事务不安全,因为事务里已经不满足一致性和隔离性了

事务执行失败的栗子


在这里插入图片描述

事务的ACID性质


  • 原子性

  • 一致性

  • 隔离性

  • 持久性

具体是怎样就不赘述了

Redis对于原子性的实现

事务中的命令只要有一个失败,那么所有命令都会执行失败的,也就是不允许事务提交

在这里插入图片描述

Redis对于一致性的实现

一致性是指没有非法或者无效的数据,也就是说要保持约束

Redis是通过谨慎的错误检测和简单的设计来保证事务的一致性

简单的设计就是指Redis数据库的键值对是没有关联性的,所以,一致性基本可以忽略。

入队错误

一个事务在入队命令的过程中,如果出现了命令不存在,或者命令的格式不正确,那么Redis会拒绝执行这个事务

执行错误

当事务在提交时,可能会发生命令执行错误,即使发生了错误,服务器也是不会去中断事务的执行,依然会去接着执行下面的命令

在这里插入图片描述

我们可以看到,msgTwo是一个字符串对象,使用了rpush命令对其操作,所以肯定会报错,但事务依然不会停止,msgFour依然创建成功了

但这样允许是不会改变Redis的一致性,因为前面已经提到过,Redis的键值对是没有关联的,所以一致性只是保证了自己键值对的一致,与其他键值对无关,键值对命令发送错误,不去修改,不就保证了一致性了嘛

服务器停机

服务器停机会产生以下几种情况

  • 没有开启持久化操作,停机之后所有数据消失,重启之后数据库变为空白,都消失了,不就一致了

  • 开启了持久化操作,无论是RDB还是AOF,恢复到的状态肯定是一致性的

Redis对于隔离性的实现

Redis是一个单线程,事务提交之后肯定都是串行执行的,会发生阻塞的,不允许并发提交,最重要的是,命令是入队执行的,不是马上执行的,要提交才会生效,所以隔离性是可以保证的。

Redis对于持久性的实现

持久性的实现完全靠持久化功能

  • 没开持久化功能,事务提交之后,也没有持久化进磁盘,肯定不能保证

  • 开了RDB持久化功能,RDB持久化是要满足一定条件才会生效的,而且使用的是BGSAVE,并不确保是否真的写进了磁盘,也是不可以保证持久性的

  • 开了AOF持久化功能

  • 如果appendfsync的选项为always时,程序总会在执行命令之后调用同步sync函数,将数据持久化进磁盘,这是可以保证持久性的

  • 如果appendfsync为everysec时,程序是每一秒同步一次数据到磁盘中,可能会丢失这一秒的所有事务,所以,不可以保证持久性

  • 如果appendfsync为no时,程序会交由操作系统来决定合适将命令数据持久化进磁盘,所以也是不可以保证持久性的

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值