本文是Redis系列文章的第三篇,将介绍Reids的事务,数据持久化等相关知识;可以通过上两篇文章分别了解:《Redis系列(一):Redis安装和配置》,以及《Redis系列(二):Redis概述和使用》相关知识。

1. Redis事务

用Multi(Start Transaction)、Exec(Commit)、Discard(Rollback)实现。 在事务提交前,不会执行任何指令,只会把它们存到一个队列里,不影响其他客户端的操作。在事务提交时,批量执行所有指令,并且带有以下两个重要的保证:

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

1.1 事务用法

MULTI 命令用于开启一个事务,它总是返回OK。MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
另一方面,通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务。
一个事务从开始到执行会经历以下三个阶段:

  1. 开始事务;
  2. 命令入队;
  3. 执行事务。

使用实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
127.0.0.1:6379> MULTI //开启事务
OK
127.0.0.1:6379> SET minhow 'Redis MySQL' //设置minhow的值
QUEUED
127.0.0.1:6379> GET minhow //获取minhow的值
QUEUED
127.0.0.1:6379> SADD name 'minhow' // 添加name集合
QUEUED
127.0.0.1:6379> SMEMBERS name // 列出集合name的所有元素
QUEUED
127.0.0.1:6379> EXEC //提交
1) OK
2) "Redis MySQL"
3) (integer) 1
4) 1) "minhow"

EXEC 命令的回复是一个数组,数组中的每个元素都是执行事务中的命令所产生的回复。其中,回复元素的先后顺序和命令发送的先后顺序一致。
当客户端处于事务状态时,所有传入的命令都会返回一个内容为QUEUED 的状态回复(status reply),这些被入队的命令将在EXEC命令被调用时执行。

1.2 事务中的错误

对于事务的执行来说,如果redis开启了AOF持久化的话,那么一旦事务被成功执行,事务中的命令就会通过write命令一次性写到磁盘中去,如果在向磁盘中写的过程中恰好出现断电、硬件故障等问题,那么就可能出现只有部分命令进行了AOF持久化,这时AOF文件就会出现不完整的情况,这时,我们可以使用redis-check-aof工具来修复这一问题,这个工具会将AOF文件中不完整的信息移除,确保AOF文件完整可用。
使用事务时可能会遇上以下两种错误:

  • 事务在执行 EXEC 之前,入队的命令可能会出错。比如说,命令可能会产生语法错误(参数数量错误,参数名错误,等等),或者其他更严重的错误,比如内存不足(如果服务器使用 maxmemory 设置了最大内存限制的话)。
  • 命令可能在 EXEC 调用之后失败。举个例子,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面,诸如此类。

对于发生在EXEC执行之前的错误,客户端以前的做法是检查命令入队所得的返回值:如果命令入队时返回QUEUED,那么入队成功;否则,就是入队失败。如果有命令在入队时失败,那么大部分客户端都会停止并取消这个事务。不过,从Redis 2.6.5开始,服务器会对命令入队失败的情况进行记录,并在客户端调用EXEC命令时,拒绝执行并自动放弃这个事务。实例如下:

1
2
3
4
5
6
7
8
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> minhow //明显的语法错误
(error) ERR unknown command 'minhow'
127.0.0.1:6379> SET minhow mm
QUEUED
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors. //拒绝执行,之前出现了错误

而对于发生在EXEC执行之后的错误,Redis则采取了完全不同的策略,即Redis不会理睬这些错误,而是继续向下执行事务中的其他命令。这是因为,对于应用层面的错误,并不是Redis自身需要考虑和处理的问题,所以一个事务中如果某一条命令执行失败,并不会影响接下来的其他命令的执行。实例如下:

1
2
3
4
5
6
7
8
9
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET minhow 'name'
QUEUED
127.0.0.1:6379> SADD minhow 'name' //minhow不是集合,出错
QUEUED
127.0.0.1:6379> EXEC //第一条OK,第二条error
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value

最重要的是记住这样一条, 即使事务中有某条/某些命令执行失败了, 事务队列中的其他命令仍然会继续执行 —— Redis 不会停止执行事务中的命令。

1.3 事务不支持回滚

如果你有使用关系式数据库的经验, 那么 “Redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。
以下是这种做法的优点:
Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR , 回滚是没有办法处理这些情况的。

1.4 事务WATCH—乐观锁

WATCH命令可以为Redis事务提供check-and-set(CAS)行为。
被WATCH的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在EXEC执行之前被修改了,那么整个事务都会被取消, EXEC返回nil-reply来表示事务已经失败。实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
127.0.0.1:6379>127.0.0.1:6379> SET minhow 109  //设置minhow的值
OK
127.0.0.1:6379> WATCH minhow //监视minhow
OK
127.0.0.1:6379> SET minhow 09 //修改minhow的值
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET minhow 9
QUEUED
127.0.0.1:6379> GET minhow
QUEUED
127.0.0.1:6379> EXEC //执行失败
(nil)

WATCH使得EXEC命令需要有条件地执行: 事务只能在所有被监视键都没有被修改的前提下执行, 如果这个前提不能满足的话,事务就不会被执行。

2. Redis持久化

Redis提供了两种持久化的方式,分别是RDB(Redis DataBase)和AOF(Append Only File)。
RDB,就是在不同的时间点,将Redis存储的数据生成快照并存储到磁盘等介质上;
AOF,则是换了一个角度来实现持久化,那就是将redis执行过的所有写指令记录下来,在下次Redis重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
其实RDB和AOF两种方式也可以同时使用,在这种情况下,如果Redis重启的话,则会优先采用AOF方式来进行数据恢复,这是因为AOF方式的数据恢复完整度更高。
如果你没有数据持久化的需求,也完全可以关闭RDB和AOF方式,这样的话,Redis将变成一个纯内存数据库,就像memcache一样。

2.1 Redis持久化- -RDB

RDB方式,是将Redis某一时刻的数据持久化到磁盘中,是一种快照式的持久化方法。

优点:
RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集。
RDB是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的S3(可能加密),非常适用于灾难恢复。
RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能。
与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些。
缺点:
如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你.虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),是Redis要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据。
RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度。

2.2 Redis持久化- -AOF

AOF,英文是Append Only File,即只允许追加不允许改写的文件;通过配置redis.conf中的appendonly yes就可以打开AOF功能。如果有写操作(如SET等),Redis就会被追加到AOF文件的末尾。

优点:
使用AOF 会让你的Redis更加耐久: 你可以使用不同的fsync策略:无fsync,每秒fsync,每次写的时候fsync.使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据.
AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题。
Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。
缺点:
对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。

2.3 Redis持久化- -如何选择RDB和AOF

一般来说, 你应该同时使用两种持久化功能。
如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。
有很多用户都只使用 AOF 持久化, 但我们并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比AOF恢复的速度要快,除此之外,使用RDB还可以避免之前提到的AOF程序的bug。

3. 总结

本文主要讲解了Redis事务运用实例,以及数据持久化的方式,各自优缺点和如何选择合适的方式等知识,下一篇文章将会介绍Redis的主从复制配置和搭建等知识。

最后更新: 2018年01月06日 19:04

原始链接: http://blog.minhow.com/articles/database/redis-3/

× 请我吃糖~
打赏二维码