18. 事务

Redis通过MULTI、EXEC、WATCH等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才会去处理其他客户端的命令请求。

 

1.事务的实现

一个事务从开始到结束通常经历三个阶段:

1)事务开始

2)命令入队

3)事务执行

 

1.1 事务开始

MULTI命令的执行标志着事务的开始。

MULTI命令可以将执行该命令的客户端从非事务状态切换到事务状态,这一切换是通过在客户端状态的flags属性中打开REDIS_MULTI标识来完成的。

 

1.2 命令入队

当一个客户端处于非事务状态时,这个客户端发送的命令会立即被服务器执行。与此不同的是,当一个客户端切换到事务状态之后,服务器会根据这个客户端发来的不同命令执行不同的操作:

1)如果客户端发送的命令为EXEC、DISCARD、WATCH、MULTI四个命令的其中一个,那么服务器会立即执行这个命令

2)如果非上述四个命令,那么服务器不会立即执行这个命令,而是将这个命令放入一个事务队列里面,然后向客户端返回QUEUED回复。

 

1.3 事务队列

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

typedef struct redisClient{
    //...

    //事务状态
    multiState mstate;
    
    //...
} redisClient;

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

typedef struct multiState{
    //事务队列,FIFO顺序
    multiCmd *commands;

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

事务队列是一个multiCmd类型的数组,数组中的每个multiCmd结构都保存了一个已入队命令的相关信息:

typedef struct multiCmd{
    //参数
    robj **argv;

    //参数数量
    int argc;

    //命令指针
    struct redisCommand *cmd;
} multiCmd;

事务队列以先进先出(FIFO)的方式保存入队的命令,较先入队的命令会被放到数组的前面,较后入队的命令会被放到数组后面。

 

1.4 执行事务

当一个处于事务状态的客户端向服务器发送EXEC命令时,这个命令将立即被服务器执行。服务器会遍历这个客户端的事务队列,执行队列中保存的所有命令,最后将执行命令所得的结果全部返回客户端。

 

2.WATCH命令的实现

WATCH命令是一个乐观锁(optimistic locking),它可以在EXEC命令执行之前,监视任意数量的数据库键,并在EXEC命令执行时,检查被监视的键是否至少有一个已经被修改过了,如果是的话,服务器将拒绝执行事务,冰箱客户端返回代表事务执行失败的空回复。

 

2.1 使用WATCH命令监视数据库键

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

typedef struct redisDb{
    //...

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

    //...
} redisDb;

通过watched_keys字典,服务器可以清楚的知道哪些数据库键正在被监视,以及哪些客户端正在监视这些数据库键。

 

2.2 监视机制的触发

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

 

2.3 判断事务是否安全

当服务器接收到一个客户端发来的EXEC命令时,服务器会根据这个客户端是否打开了REDIS_DIRTY_CAS标识来决定是否执行事务:

1)如果客户端的REDIS_DIRTY_CAS标识打开,说明有键被修改过,事务不安全,拒绝执行客户端提交的事务

2)没有打开,说明安全,执行

 

3. 事务的ACID性质

所谓ACID就是原子性、一致性、隔离性、耐久性

 

3.1 原子性

事务原子性指:数据库将事务中的多个操作当做一个整体来执行,服务器要么就执行所有操作,要么都不执行。

Redis事务和传统关系型数据库的事务最大区别在于:Redis不支持事务回滚机制,及时事务队列中的某个命令在执行期间出现了问题,整个事务也会继续执行下去,直到执行结束。

不支持回滚的原因:回滚这种复杂的功能和Redis追求简单高效的设计主旨不相符。而且事务的执行错误通常是编程错误导致的,只会出现在开发环境,生产环境很少出现。

 

3.2 一致性

一致性指:如果数据库在执行事务之前是一致的,那么无论事务是否执行成功,执行之后,数据库也还是一致的。

一致指的是数据符合数据库本身的定义和要求,没有包含非法或者无效的错误数据。

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

 

3.2.1 入队错误

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

 

3.2.2 执行错误

除了入队错误,事务还可能在执行的过程中发生错误。

1)执行过程中发生的错误都是一些不能再入队时被服务器发现的错误,这些错误只会在命令执行时触发

2)即使在事务的执行过程中发生了错误,服务器也不会中断事务的执行,它会继续执行事务中余下的其他命令,并且已经执行的命令不会被错误的命令影响。

 

3.2.3 服务器停机

如果Redis服务器在执行事务的过程中出现停机,那么根据服务器的持久化模式,可能会有以下情况:

1)如果在无持久化的内存模式下,那么重启之后数据库就是空白

2)如果在RDB模式下,那么事务中途停机不会导致不一致性,因为服务器会根据RDB来恢复数据

3)如果在AOF模式下,也不会导致不一致性。因为服务器可以根据AOF来恢复数据

 

3.3 隔离性

多个事务并发执行,互相不影响

因为Redis使用单线程的方式执行事务,并且服务器保证在执行事务期间不会中断,所以天然具有隔离性

 

3.4 耐久性

耐久性:当一个事务执行完毕时,执行这个事务所得的结果已经被保存到硬盘,就算服务器之后停机,执行结果也不会丢失。

因为Redis事务不过是简单的用队列包裹起来一组Redis命令,Redis并没有为事务提供任何额外的持久化功能,所以Redis事务的耐久性由Redis所使用的的持久化模式决定:

1)服务器在无持久化的内存模式中运作,不具备耐久性

2)当服务器在RDB持久化模式中运作,服务器只会在特定的保存条件被满足时,才会执行BGSAVE命令,对数据库进行保存,并且异步执行的BGSAVE不能保证事务第一时间保存到硬盘,所以也不具有耐久性

3)AOF模式下,并且appendfsync选项的值为always时,程序会在执行命令之后调用同步函数,将命令真正保存硬盘,具有耐久性

4)AOF模式下,并且appendfsync选项的值为everysec时,程序每秒同步一次命令数据到硬盘,这一秒也有可能数据丢失,也不具有耐久性。

5)AOF模式下,并且appendfsync选项的值为no时,不具有耐久性

6)不管什么模式,事务的最后加上SAVE命令,可以保证耐久性,但是效率太低,不具有实用性

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鹏哥哥啊Aaaa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值