事务的实现
事务是通过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里的事件有两大类:
- 文件事件,也就是socket事件,比如连接,读,写
- 时间事件,定时任务事件,比如定期生成RDB文件
最后贴一张redis执行命令相关的图:
1,IO多路复用里的主线程负责监听连接,读,写事件,然后将事件依次放入到事件队列里
2,紧接着EventDispatcher事件分发器将不同事件分发给不同处理器。比如连接事件分发给连接事件处理器;读写命令分发给读写处理器
3,命令处理完后,命令回复处理器将结果写入socket返回给客户端。
本文参考的有:
黄健宏的《Redis设计与实现》一书
Redis 笔记(08)— 事务(一次执行多条命令、命令 watch/multi/exec/discard、错误处理)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。