redis入门-聊一聊

119 阅读12分钟

11、聊一聊redis持久化

redis提供了两种持久化的方式,分别是RDB(Redis DataBase) 和 AOF(Append Only File)

RDB,简而言之,就是在不同的时间点,将redis存储的数据生成快照并存错到磁盘等介质上

AOP,则是换一个角度来实现持久化,那就是将redis执行过的说有写指令都记录下来,在下次redis重启时,只要把这些写指令从前到后再重复执行一次,就可以实现数据恢复

其实RDB和AOF两种返回时也可以同时使用,再这种情况下,如果redis重启的话,则会优先采用AOF的方式进行数据恢复,这是因为AOF方式的数据恢复完整度较高,如果没有数据持久化的要求,也可以关闭RDB和AOF方式,这样的话,redis就将变成一个纯内存的数据库,就像memcache。

RDB

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

redis再进行数据持久化的过程中,会先将数据写入到一个临时文件中,待持久化或称都结束了,才会用这个临时文件替换上次持久化好的文件,正是这种特性,让我们可以随时进行备份,因为快照文件总是完整可用的

对于RDB方式,redis会单独创建(fork)一个子进程来进行持久化,而主进程是不会进行任何的IO操作,这样确保了redis极高的性能

如果需要大规模数据大的恢复,且对于数据恢复的完整性不是非常敏感,那么RDB方式要比AOF更加高效

虽然RDB有不少优点,但是它的缺点也是不容忽略的,如果你对数据的完整性非常敏感,那么RDB方式就不太适合你,因为即使你每五分钟都持久化一次,当redis故障时,仍然会有近五分钟的数据会丢失,所以redis还提供了另一种持久化的方式。

AOF

AOF,英文时Append only File,即只允许追加不允许写的文件

如前面介绍的,AOF方式是将执行过的写指令记录下来,数据恢复时按照从前到后的顺序再将指令都执行一遍,那么就简单了。

我们通过配置redis.conf中的appendonly yes就可以打开AOF的功能,如果有写操作,redis就会被追加到AOF文件的末尾

默认的AOF持久化策略时每秒钟fsync一次(fsync是指把缓存中的写指令记录到磁盘中),因为这种情况下,redis仍然可以保持很好的处理性能,即使redis发生故障,也只会丢失近一秒的数据

如果在追加日志时恰好遇到磁盘空间满,inode满或者断电等情况导致写入的日志不完整,也没有关系,redis提供了redis-check-aof工具,可以用来进行日志恢复

因为采用追加方式,如果不做任何处理的话AOF的文件会变得越来越大,为此,redis提供了AOF文件重写机制,即当AOF文件的大小超过阈值,redis就会启动AOF文件压缩,只保留可以恢复数据的最小指令集。举个例子,假如我们调用了100次incr指令,在AOF文件中就会存储100条指令,但这明显效率很低,完全可以把这100条指令合并写成1条set指令,这就是重写机制大的原理。

在进行AOF重写时,仍然采用先写临时文件,全部完成后再替换的流程,所以断电,磁盘满等问题都不会影响AOF文件的可用性

AOF方式的另外一个好处就是我们通过一个情景再现来说明:某同学在操作redis时,不小心执行了FLUSHALL,导致redis内存中的数据全部被清空,这是一个很悲剧的事情,不过也不是世界末日,不用跑路,只要redis配置了AOF持久化,且AOF文件还没有被重写,我们就可以用最快的数据暂停redis并编辑AOF文件,将最后一行的FLUSHALL命令删除,然后重新启动redis,就可以恢复redis的所有数据到FLUSHALL之前的状态,但是如果AOF的文件已经被重写,那就要考虑跑路了。 虽然AOF的优点多多,但是童谣也存在缺陷,比如在同样数据规模下,AOF文件要比RDB文件的体积大,而且AOF方式恢复速度也是较慢与RDB方式

如果你直接执行BGREWRITEAOF命令,那么redis会生成一个全新的AOF文件,其中便包括了可以恢复现有数据的最少命令集,如果运气比较差,AOF文件出现被写坏的情况,也不必过分担忧,redis并不会贸然加载这个有问题的文件,而是报错退出,这时可以通过以下步骤来解决:

1、备份被写坏的AOF文件

2、运行redis-check-aof进行修复

3、用diff -u来查看两个文件的差异,确认问题

4、重启redis,加载恢复后的AOF文件

12、聊聊redis持久化 – AOF重写

AOF重写的内部运行原理,我们有必要了解一下。

在重写即将开始之际,redis会创建(fork)一个“重写子进程”,这个子进程会首先读取现有的AOF文件,并将其包含的指令进行分析压缩并写入到一个临时文件中。

与此同时,主工作进程会将新接收到的写指令一边累积到内存缓冲区中,一边继续写入到原有的AOF文件中,这样做是保证原有的AOF文件的可用性,避免在重写过程中出现意外。

当“重写子进程”完成重写工作后,它会给父进程发一个信号,父进程收到信号后就会将内存中缓存的写指令追加到新AOF文件中。

当追加结束后,redis就会用新AOF文件来代替旧AOF文件,之后再有新的写指令,就都会追加到新的AOF文件中了。

13、聊聊redis持久化 – 如何选择RDB和AOF

对于我们应该选择RDB还是AOF,官方的建议是两个同时使用。这样可以提供更可靠的持久化方案。

14、聊聊主从 – 用法

像MySQL一样,redis是支持主从同步的,而且也支持一主多从以及多级从结构。

主从结构,一是为了纯粹的冗余备份,二是为了提升读性能,比如很消耗性能的SORT就可以由从服务器来承担。

redis的主从同步是异步进行的,这意味着主从同步不会影响主逻辑,也不会降低redis的处理性能。

主从架构中,可以考虑关闭主服务器的数据持久化功能,只让从服务器进行持久化,这样可以提高主服务器的处理性能。

在主从架构中,从服务器通常被设置为只读模式,这样可以避免从服务器的数据被误修改。但是从服务器仍然可以接受CONFIG等指令,所以还是不应该将从服务器直接暴露到不安全的网络环境中。如果必须如此,那可以考虑给重要指令进行重命名,来避免命令被外人误执行。

15、聊聊主从 – 同步原理

从服务器会向主服务器发出SYNC指令,当主服务器接到此命令后,就会调用BGSAVE指令来创建一个子进程专门进行数据持久化工作,也就是将主服务器的数据写入RDB文件中。在数据持久化期间,主服务器将执行的写指令都缓存在内存中。

在BGSAVE指令执行完成后,主服务器会将持久化好的RDB文件发送给从服务器,从服务器接到此文件后会将其存储到磁盘上,然后再将其读取到内存中。这个动作完成后,主服务器会将这段时间缓存的写指令再以redis协议的格式发送给从服务器。

另外,要说的一点是,即使有多个从服务器同时发来SYNC指令,主服务器也只会执行一次BGSAVE,然后把持久化好的RDB文件发给多个下游。在redis2.8版本之前,如果从服务器与主服务器因某些原因断开连接的话,都会进行一次主从之间的全量的数据同步;而在2.8版本之后,redis支持了效率更高的增量同步策略,这大大降低了连接断开的恢复成本。

主服务器会在内存中维护一个缓冲区,缓冲区中存储着将要发给从服务器的内容。从服务器在与主服务器出现网络瞬断之后,从服务器会尝试再次与主服务器连接,一旦连接成功,从服务器就会把“希望同步的主服务器ID”和“希望请求的数据的偏移位置(replication offset)”发送出去。主服务器接收到这样的同步请求后,首先会验证主服务器ID是否和自己的ID匹配,其次会检查“请求的偏移位置”是否存在于自己的缓冲区中,如果两者都满足的话,主服务器就会向从服务器发送增量内容。

增量同步功能,需要服务器端支持全新的PSYNC指令。这个指令,只有在redis-2.8之后才具有。

16、聊聊redis的事务处理

众所周知,事务是指“一个完整的动作,要么全部执行,要么什么也没有做”。

在聊redis事务处理之前,要先和大家介绍四个redis指令,即MULTI、EXEC、DISCARD、WATCH。这四个指令构成了redis事务处理的基础。

1.MULTI用来组装一个事务; 2.EXEC用来执行一个事务; 3.DISCARD用来取消一个事务; 4.WATCH用来监视一些key,一旦这些key在事务执行之前被改变,则取消事务的执行。

纸上得来终觉浅,我们来看一个MULTI和EXEC的例子:

redis> MULTI //标记事务开始
OK
redis> INCR user_id //多条命令按顺序入队
QUEUED
redis> INCR user_id
QUEUED
redis> INCR user_id
QUEUED
redis> PING
QUEUED
redis> EXEC //执行
1) (integer) 1
2) (integer) 2
3) (integer) 3
4) PONG

在上面的例子中,我们看到了QUEUED的字样,这表示我们在用MULTI组装事务时,每一个命令都会进入到内存队列中缓存起来,如果出现QUEUED则表示我们这个命令成功插入了缓存队列,在将来执行EXEC时,这些被QUEUED的命令都会被组装成一个事务来执行。

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

有关事务,大家经常会遇到的是两类错误:

1.调用EXEC之前的错误 2.调用EXEC之后的错误

“调用EXEC之前的错误”,有可能是由于语法有误导致的,也可能时由于内存不足导致的。只要出现某个命令无法成功写入缓冲队列的情况,redis都会进行记录,在客户端调用EXEC时,redis会拒绝执行这一事务。(这时2.6.5版本之后的策略。在2.6.5之前的版本中,redis会忽略那些入队失败的命令,只执行那些入队成功的命令)。我们来看一个这样的例子:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> haha //一个明显错误的指令
(error) ERR unknown command 'haha'
127.0.0.1:6379> ping
QUEUED
127.0.0.1:6379> exec
//redis无情的拒绝了事务的执行,原因是“之前出现了错误”
(error) EXECABORT Transaction discarded because of previous errors.
 

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

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set age 23
QUEUED
//age不是集合,所以如下是一条明显错误的指令
127.0.0.1:6379> sadd age 15 
QUEUED
127.0.0.1:6379> set age 29
QUEUED
127.0.0.1:6379> exec //执行事务时,redis不会理睬第2条指令执行错误
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) OK
127.0.0.1:6379> get age
"29" //可以看出第3条指令被成功执行了

好了,我们来说说最后一个指令“WATCH”,这是一个很好用的指令,它可以帮我们实现类似于“乐观锁”的效果,即CAS(check and set)。

WATCH本身的作用是“监视key是否被改动过”,而且支持同时监视多个key,只要还没真正触发事务,WATCH都会尽职尽责的监视,一旦发现某个key被修改了,在执行EXEC时就会返回nil,表示事务无法触发。

复制代码代码如下:

127.0.0.1:6379> set age 23
OK
127.0.0.1:6379> watch age //开始监视age
OK
127.0.0.1:6379> set age 24 //在EXEC之前,age的值被修改了
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set age 25
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> exec //触发EXEC
(nil) //事务无法被执行

\