1 持久化选项
Redis 提供了两种持久化方法来讲数据存储到硬盘里。一种是快照,它将某一时刻的所有数据写如硬盘里。另一种是追加文件 AOF,在执行写命令时,将被执行命令复制到硬盘里。两种方法可以单独使用,也可以同时使用。
1.1 快照持久化
快照数据可以用于备份,也可以复制到其他服务器从而创建据有相同数据的服务器副本,也可也用作服务重启时使用。
可以在配置文件中指定快照被写入的文件名,文件路径。在快照创建完成前,系统宕机会导致丢失最近一次快照创建之后写入的所有数据。
创建快照的方法:
- 客户端向 Redis 发送 BGSAVE 命令来创建快照,Redis 会 fork 一个子进程来复制快照写入硬盘,而父进程继续处理请求。
- 客户端向 Redis 发送 SAVE 命令来创建快照,此时 Redis 会在快照创建完成之前不在响应其他命令,SAVE 命令不常用,只有在没有足够内存执行 BGSAVE 或者暂停处理请求也无所谓时使用 SAVE 命令。
- 如果用户设置了 save 配置项,比如:save 60 10000,那么从 Redis 最近一次创建快照起,当满足 60 秒内有 10000 次写入,Redis 就会自动触发 BGSAVE。如果有多 save 个配置项,满足任意一个就触发一次 BGSAVE。
- 当 Redis 收到关闭服务器请求时,或者收到标准 TERM 信号时,阻塞所有客户端不再执行命令,并执行 SAVE 命令,执行完毕后关闭服务器。
- 当 Redis 服务器连接另一个 Redis 服务器时,并向对方发送 SYNC 命令来开始一次复制操作时,如果主服务器没有在执行 BGSAVE 操作,那么主服务器会触发 BGSAVE。
当数据量过大时,BGSAVE 创建的子进程会导致 Redis 停顿,为了防止出现停顿问题,可以考虑关闭自动保存,转而通过手动发送 BGSAVE 命令或者 SAVE 命令来持久化。如果不介意暂停处理情况,SAVE 的效率比 BGSAVE 效率更高。
1.2 AOF持久化
AOF 持久化会将被执行的写命令写到 AOF 文件末尾,以此来记录数据发生的变化。Redis 只要从头到尾重新执行一次 AOF 文件包含的命令,就可以恢复 AOF 文件中记录的所有数据。
AOF 可以通过 appendonly yes 选项来打开。同时通过 appendfsync 来控制同步的频率:always 每个redis 写命令都要同步到磁盘,这样会严重降低 redis 的速度;everysec:每秒执行一次同步,可以保证宕机时只丢失一秒钟的数据,且性能基本不受影响;no:不指定,由操作系统自己决定同步的时间,如果宕机,数据丢失量不确定,性能不受影响。另外如果缓冲区被待写数据填满,redis 的写操作也会被阻塞,从而导致 redis 处理请求速度变慢,所以 no 选项也不推荐。
1.3 重写/压缩 AOF 文件
随着 redis 运行,aof 文件体积会持续变大占用磁盘空间,同时重启时还会导致操作执行的时间特别长。
为了解决这个问题,可以向 redis 发送 bgrewriteaof 命令,这个命令会通过移除 aof 中的冗余命令来重写 aof 文件,从而使得文件尽可能小。 bgrewriteaof 工作原理与 bgsave 类似,会 fork 一个子进程然由子进程对 aof 文件重写。
子进程重写 aof 文件也会有 性能和内存占用问题,而且 aof 在重写并删除一个体积达到数十个 gb 的旧 aof 文件可能导致操作系统挂起数秒。
aof 持久化也可以通过 auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size 来控制自动执行 bgrewriteaof。比如:auto-aof-rewrite-percentage 100 和 auto-aof-rewrite-min-size 64mb,并且启用了 aof,那么当 aof 文件大于 64mb,并且 aof 文件的体积比上一次重写之后体积大了至少一倍(100%)的时候, redis 会执行 bgrewriteaof 命令。
2 复制
redis 主从服务器配置及复制过程。
2.1 redis 复制相关配置项
当从服务器连接主服务器的时候,主服务器会执行 bgsave 操作。因此要正确使用复制,必须要保证正确的设置了快照文件保存的目录和文件名选项,并且这个目录和文件对 redis 来说都是可以写的。
在 redis 从服务的配置文件中,配置 slaveof host port 选项即让当前 redis 服务作为指定的 host port redis 的一个从服务。对于正在运行的从服务,可以通过 slaveof no one 来终止服务,不再接受主服务的数据更新,也可以发送 slaveof host port 来让它复制一个新的主服务器数据。
当主从服务配置成功,客户端就会随机选择服务器,从而将负载平均分配到各个服务器上。
2.2 Redis复制过程
从服务器连接主服务器的复制过程:
- 主服务器等待从服务器的连接,从服务器连接到主服务器,并发送 sync 命令。
- 主服务器开始执行 bgsave ,并使用缓冲区记录在 bgsave 期间所执行的写命令,此时从服务器根据配置决定是否继续用已有数据处理客户端的请求,还是向客户端的请求返回错误。
- 主服务器在 bgsave 执行完成后,向从服务器发送快照,并且继续用缓冲区记录被执行的写命令,从服务收到快照后,丢弃掉旧数据,开始载入主服务器的快照文件。
- 快照发送完毕后,开始向从服务器发送存储在缓冲区里的写命令,从服务器解释完快照后,开始提供服务。
- 缓冲区的写命令发送完毕,之后接受到每个写命令都向从服务器发送相同的写命令,从接受并执行缓存区里的写命令,并且开始接受来自主服务器的每个写命令。
在实践中,最好让主服务器只使用 50%-65/% 的内存,留下部分用于执行 bgsave 命令和创建写命令的缓冲区。
需要注意,从服务器在载入主服务器的数据时,会丢弃掉自己的所有数据。
当多个从服务器连接一个主服务器时,如果还没有开始向从服务器发送快照,那么主服务器就会复用快照和缓冲区数据。如果已经开始传送快照,那么新的从服务器的复制会从头开始。
2.3 主从链
当读的请求数量明显远远超出一台 redis 服务器能处理的范围时,就需要添加新的从服务器来处理,随着从服务器增加,主服务器可能无法快速的更新所有从服务器,或者主服务器因为重新同步从而导致服务器超载。为了解决这个问题,我们可以为从节点创建从节点,从而减轻主节点更新从服务器的压力。
从服务器对它的从服务器进行复制与主服务器与和从服务器的复制没有本质区别,唯一的区别是在从服务器完成快照读取后,会断开它的从服务器,需要从它的从服务器重新连接并同步数据。
3 处理系统故障
reids 是软件,运行在硬件之上。无论软硬件本身有多完美,也无法避免比如断电、自然灾害等情况。当出现这些情况我们应该采取是什么措施?
3.1 验证快照文件和 aof 文件
快照和 aof 都提供了在系统遇到故障时进行数据恢复的工具。redis 还提供了 redis-check-aof 和 redis-check-dump 来检查 aof 文件和快照文件,并在有需要的时候进行修复。
快照和 aof 校验的基本用法:redis-check-aof [--fix] <file.aof>,redis-check-dump <dump.rdb>。如果指定了 redis-check-aof --fix 参数,可以对 aof 文件进行修复。修复的过程就是将出现错的命令之后的所有命令删除。但是 rdb 快照本身是经过压缩的,所以 rdb 快照文件不支持修复。如果快照文件出现问题会导致快照文件剩余部分无法读取,所以最好保留多个快照文件备份。在进行恢复时通过计算 crc64 校验和 sha256 散列值进行校验,crc64 用于发现网络传输错误,sha256 用于检查文件中是否有错误。
3.2 更换故障主服务器
当主从服务器进行服务时,如果主服务器出现异常宕机,我们进行切换的思路有两种:
- 对从服务器执行 save 命令创建 rdb 快照,快照复制到新的机器上,让新机器读取 rdb 快照启动,让之前的从服务器把主服务指定为新的服务器。
- 把从服务提升为主服务,为新的主服务启用新的从服务器。
4.4 redis 事务
redis 事务相关的命令,watch、multi、exec、unwatch、discard。
redis 的事务,以 multi/exec 包裹一系列的命令。当 exec 执行后,被包裹的命令会顺序执行。
watch 的使用:
- watch 可以监视一个或者多个 key 的变化。
- watch 需要在开始事务(multi)之前执行。
- 不管事务成功、失败还是 discard,都会自动 unwatch。
- 如果 watch 了后,没有执行事务,需要进行手动 unwatch。
multi/exec 的使用:
- multi 命令开始一个事务,如果后续加入队列的命令有格式错误,exec 命令执行后,会让所有命令回滚,都不会执行成功。如果后续加入队列的命令没有格式错误,但是有执行报错,比如对字符串进行自增操作,会导致发生错误之后的所有命令回滚,之前的命令正确执行。
- multi 命令开始一个事务后,可以用 discard 放弃事务,清空执行队列。
27.0.0.1:6379> set key1 1
ok
27.0.0.1:6379> watch key1 # 开始 watch key1,在 exec 执行前,key1 被修改,都会导致事务回滚
ok
27.0.0.1:6379> multi
ok
27.0.0.1:6379> incrby key1 1
queued
27.0.0.1:6379> incrby key1 1
queued
27.0.0.1:6379> exec # 可以执行 discard 放弃事务,清空队列
2
3
为什么 redis 不采用关系型数据库的锁来加锁?关系型数据库在访问以写入为目的的数据时,会对数据进行加锁,直到事务被提交或者回滚后,其他客户端才能访问,这种锁非常有效,但是缺点是如果持有锁的客户端运行很慢,等待锁的客户端就会被阻塞很久。
redis 为了尽可能少的减少客户端等待,并不会在 watch 时对数据加锁,相反的只会把数据被其他客户端修改的事通知到 执行了 watch 的客户端,这种实现叫做乐观锁,而关系型数据库的实现叫做悲观锁。乐观锁在获取锁失败的时候不需要去等待,只需要重试即可。
4.5 非事务流水线(pipeline)
对于 redis 执行批量命令操作有三种方式:
- 使用可以接受多个参数的命令,比如:mget、mset、hmget、hmset、lpush、sadd、zadd 等。
- 使用 multi/exec 事务,它会一次包裹多个命令,它的底层会使用 pipeline 批量将命令发送给服务端,从而提高效率。
- 使用非事务性的 pipeline。它的核心逻辑是,如果一条一条执行命令,每执行一条命令需要一次网络请求来回,如果一次传递多个命令,并依次执行,可以节约大量网络请求往返时间。
4.6 关于性能方面的注意事项
要对 redis 进行性能优化,可以使用 redis 自带的性能测试程序 redis-benchmark 来查看 redis 的性能。
redis-benchmark 一次会跑 50 个客户端来进行性能测试,为了方便对比,可以指使用一个客户端来进行性能测试,方便与自己的应用程序性能进行比较。
一般来说,应用程序的性能大概能达到 redis-benchmark 所测试的新能的 50%-60%。