您的位置:首页 > 数据库 > Redis

redis事务和管道浅析

2016-03-18 23:08 369 查看

redis事务和管道浅析

redis IO模型

redis的事务首先得从redis的网络IO模型讲起,redis使用的是“单线程-多路复用IO模型”,自己封装了一个简单的AeEvent事件处理框架,避免了使用锁,对于IO操作来说,单线程可以将速度发挥到最大。

IO模型浅析

基于这种机制,redis轻而易举的实现了事务的隔离性,因为所有事务都是串行执行的。

redis 通讯机制

redis客户端服务端通过tcp连接进行数据交互,采用自己的实现的二进制安全协议。

在这个协议中, 所有发送至 Redis 服务器的参数都是二进制安全(binary safe)的。

以下是这个协议的一般形式:

<参数数量> CR LF

$<参数 1 的字节数量> CR LF

<参数 1 的数据> CR LF



$<参数 N 的字节数量> CR LF

<参数 N 的数据> CR LF

译注:命令本身也作为协议的其中一个参数来发送。

举个例子, 以下是一个命令协议的打印版本:

*3

$3

SET

$5

mykey

$7

myvalue

这个命令的实际协议值如下:

“*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n”

Redis 命令会返回多种不同类型的回复。

通过检查服务器发回数据的第一个字节, 可以确定这个回复是什么类型:

状态回复(status reply)的第一个字节是 “+”

错误回复(error reply)的第一个字节是 “-“

整数回复(integer reply)的第一个字节是 “:”

批量回复(bulk reply)的第一个字节是 “$”

多条批量回复(multi bulk reply)的第一个字节是 “*”

redis管道-pipeline

执行一个命令就进行一次请求和响应是相当消耗网络IO的,因此,redis实现了管道。

Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,服务端接收到所有管道命令后,一次性执行管道中所有命令,并最终一次性读取所有服务端的响应。pipeline期间将“独占”链接,此期间将不能进行非“管道”类型的其他操作,直到pipeline关闭。因此具备原子性、隔离性。

管道实例:

$(echo -en “PING\r\n SET w3ckey redis\r\nGET w3ckey\r\nINCR visitor\r\nINCR visitor\r\nINCR visitor\r\n”; sleep 10) | nc localhost 6379

+PONG

+OK

redis

:1

:2

:3

redis事务

redis事务与管道类似,但是比管道拥有更多特性。缺点是每次命令入队都会进行网络交互。

事务可以一次执行多个命令, 并且带有以下两个重要的保证:

事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

MULTI 、 EXEC 、 DISCARD 和 WATCH 命令是 Redis 事务的基础。

MULTI 命令用于开启一个事务,它总是返回 OK 。

EXEC 命令负责触发并执行事务中的所有命令

DISCARD 命令时, 事务会被放弃, 事务队列会被清空, 并且客户端会从事务状态中退出

而WATCH 命令可以监视键的改动,为 Redis 事务提供 check-and-set (CAS)行为。

事务命令详情请见redisfans-Redis 命令参考

事务例子

redis 127.0.0.1:6379> MULTI

OK

redis 127.0.0.1:6379> SET book-name “Mastering C++ in 21 days”

QUEUED

redis 127.0.0.1:6379> GET book-name

QUEUED

redis 127.0.0.1:6379> SADD tag “C++” “Programming” “Mastering Series”

QUEUED

redis 127.0.0.1:6379> SMEMBERS tag

QUEUED

redis 127.0.0.1:6379> EXEC

1) OK

2) “Mastering C++ in 21 days”

3) (integer) 3

4) 1) “Mastering Series”

2) “C++”

3) “Programming”

事务机制

以下事务机制描述主要摘自《redis设计与实现》



事务队列

每个redis客户端都有自己的事务状态,这个事务状态保存在客户端状态的mstate属性里。事务状态包含一个事务队列用来保存入队命令。事务队列以先进先出(FIFO)的方式保存命令

事务执行

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

Watch命令

Watch命令是redis对乐观锁的实现。他可以在exec命令执行之前,监视任意数量的数据库键,并在exec命令执行时,检查被监视的键是否至少有一个已经被修改过,如果是的话,服务器将拒接执行事务,并向客户端返回代表事务执行失败的空回复。

Watch使用字典实现

当一个c10086客户端执行如下watch命令后

Redis> watch “name” “age”



当键name被修改后c1,c2,c10086三个客户端的REDIS_DIRTY_CAS标识被打开。

当键age被修改后c3,c10086两个个客户端的REDIS_DIRTY_CAS标识被打开。

判断事务是否安全

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



Redis事务的ACID性质

ACID描述请见百度百科

原子性Atomicity

对于redis来说,事务队列中的命令要么全部执行,要么就一个都不执行,因此redis的事务是具有原子性的。

一致性Consistency

为了追求性能,redis通过谨慎的错误检测和简单的设计来保证事务的一致性。

入队错误

如果一个事务在入队命令过程中,出现命令不存在,或者命令格式不正确等情况,redis 将拒接执行这个事务

执行错误

执行错误是一些不能在入队是被服务器发现的错误,这些命令只会在执行的时候被触发

即使在事务的执行过程中发生了错误,服务器也不会中断事务的执行,它会继续执行队列中余下的命令。

因为在事务执行过程中,出错的命令会被服务器识别出来,错误命令不会对数据库进行任何修改,也不会对事务的一致性产生影响。

(与关系型数据库如mysql的一致性实现不同,mysql中出现执行错误,会回滚所有的已执行命令,将状态还原到事务的起点,这也是redis事务不完善之处)

3.事务执行中,服务器停机

如果redis服务器在执行过程中停机

在无持久化模式下: 重启后服务器是空白的,因此数据是一致的

在rdb和AOF模式下:

重启后服务器根据现有的rdb和aof文件来恢复数据,会将数据库还原到一个一致性状态,即回到未结束事务的起始状态,如果找不到文件,则数据库是空白的,也保证了一致性

隔离线Isolation

因为redis是使用单线程的方式来执行事务,并且服务器保证不会在事务执行期间对事务中断,因此,redis的事务总是以串行的方式运行的,保证了隔离线

持久性Durability

因为redis的事务不过是简单的用队列包裹起一组redis命令,redis并没有对事务提供额外的持久化功能,因此redis事务的持久性由redis所使用的持久化模式决定。

在单纯的内存模式下,事务肯定是不持久的。

在 RDB 模式下,服务器可能在事务执行之后、RDB 文件更新之前的这段时间失败,所以

RDB 模式下的 Redis 事务也是不持久的。

在 AOF 的“总是 SYNC ”模式下,事务的每条命令在执行成功之后,都会立即调用 fsync 或 fdatasync 将事务数据写入到 AOF 文件。但是,这种保存是由后台线程进行的,主线程不会阻塞直到保存成功,所以从命令执行成功到数据保存到硬盘之间,还是有一段非常小的间隔,所以这种模式下的事务也是不持久的。

其他 AOF 模式也和“总是 SYNC ”模式类似,所以它们都是不持久的。

redis事务的不足

redis事务在执行的中途遇到错误,不会回滚,而是继续执行后续命令

持久性的不足,如上文持久性所述

遇到有查询的情况穿插在事务中间,不会返回结果;

事务中的每条命令都与redis服务器进行了一次网络交互
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: