事务的实现

事务是通过MULTI命令开始的,在非事务状态下客户端发送的命令会被立即执行,而在事务状态下,除了EXEC/WATCH/DISCARD这几个命令外,redis会将命令保存在事务队列里。

typedef strcut redisClient{
    //事务状态
    multiState mstate;
    ...
}redisClient;

typedef struct multiState{
    //事务队列 FIFO
    multiCmd *commands;
    //已入队命令计数
    int counts;
}multiState;

typedef struct multiCmd{
    //参数
    robj *args;
    //参数数量
    int argc;
    //命令指针
    struct redisCommand *cmd;
}multiCmd;

当一个处于事务状态的客户端向服务器发送EXEC命令时,服务器会遍历这个客户端的事务队列并执行队列里的所有命令,最后将执行命令后的所有结果返回给客户端。

redis事务的ACID

redis是具有原子性、一致性、隔离性的,不过不具备性格的持久性。

  • 对于redis的事务性来说,事务队列中的命令要么全部执行要么一个都不执行,所以他是具有原子性的(并不是很严格的原子性,见下面说明的‘错误处理’),不过redis并不支持事务的回滚,这是它与关系性数据库很大的一个区别。
  • redis通过对命令正确性检查及持久化模式可以保证一致性。
  • redis是使用单线程的方式来执行事务,且事务在执行的过程中不会被中断,也就是说redis的事务总是以串行的方式运行的,这样其事务也就具有了隔离性。--相当于msyql里的串行化隔离级别?
  • 事务的持久性是指一个事务执行完毕时,执行这个事务所得的结果已经被保存到了永久存储介质里,即使服务器在事务执行后停机,执行所得的结果也不会丢失。redis将数据写入内存后,会根据持久化配置将数据同步到磁盘里,这其实是有一个滞后过程的,显然redis并不具备这一特性

错误处理

如果在事务执行过程上出现错误会怎么样呢?这个分错误情形。

  • 语法错误。只要事务中有一个命令的语法发生错误,那么整个事务中的命令都不会执行
  • 运行错误。运行错误是指在执行命令时出现的错误,这种错误在实际执行之前是无法发现的,当事务中出现这种运行错误时,其它命令仍然会继续执行的。

看示例:

> multi
OK
> set books python
QUEUED
> incr books
QUEUED
> set phone huawei
QUEUED
> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
> get books
"python"
> get phone 
"huawei"

上面的例子是事务执行到中间遇到失败了,因为我们不能对一个字符串进行数学运算,这个属于运行错误,事务在遇到指令执行失败后,后面的指令还继续执行,所以 phone 的值能继续得到设置。

所以从严格意义上看,redis的事务只有一致性与隔离性。


Redis 事务在发送每个指令到事务缓存队列时都要经过一次网络读写,当一个事务内部的指令较多时,需要的网络 IO 时间也会线性增长。所以通常 Redis 的客户端在执行事务时都会结合 pipeline 一起使用,这样可以将多次 IO 操作压缩为单次 IO 操作。

另外这里提一下redis里的多线程,Redis6 版本中引入了多线程,对于redis来说,其较大的一个开销是网络IO,所以redis6在设计上采用将网络数据读写和协议解析通过多线程的方式来处理,对于命令执行来说,仍然使用单线程操作。

redis里的事件
redis里的事件有两大类:

  1. 文件事件,也就是socket事件,比如连接,读,写
  2. 时间事件,定时任务事件,比如定期生成RDB文件

最后贴一张redis执行命令相关的图:
image.png
1,IO多路复用里的主线程负责监听连接,读,写事件,然后将事件依次放入到事件队列里
2,紧接着EventDispatcher事件分发器将不同事件分发给不同处理器。比如连接事件分发给连接事件处理器;读写命令分发给读写处理器
3,命令处理完后,命令回复处理器将结果写入socket返回给客户端。

本文参考的有:
黄健宏的《Redis设计与实现》一书
Redis 笔记(08)— 事务(一次执行多条命令、命令 watch/multi/exec/discard、错误处理)


步履不停
38 声望13 粉丝

好走的都是下坡路