为什么主从复制使用 RDB 而不使用 AOF?

1、RDB 文件内容是经过压缩的二进制数据(不同数据类型数据做了针对性优化),文件很小。而 AOF 文件记录的是每一次写操作的命令,写操作越多文件会变的很大,其中还包括很多对同一个 key 的冗余操作。在主从全数据同步时,传输 RDB 文件可以尽量降低对主库机器网络宽带的消耗,从库在加载 RDB 文件时,一是文件小,读取整个文件的速度会很快,二是因为 RDB 文件存储的都是二进制数据,从库直接按照 RDB 协议解析还原数据即可,速度会非常快,而 AOF 需要依次重放每个写命令,这个过程会经历冗长的处理逻辑,恢复速度比 RDB 会慢很多,所以使用 RDB 进行主从复制。

2、假设要使用 AOF 做全量复制,意味着必须打开 AOF 功能,打开 AOF 就要选择文件刷盘的策略,选择不当会严重影响 Redis 性能,而 RDB 只有在需要定时备份和主从全量复制数据时才会触发生成一次快照。而在很多丢失数据不敏感的业务场景,其实是不需要开启 AOF 的。

当主服务器不进行持久化时复制的安全性

在进行主从复制设置时,强烈建议在主服务器上开启持久化,当不能这么做时,比如考虑到延迟的问题,应该将实例配置为避免自动重启。

为什么不持久化的主服务器自动重启非常危险呢?为了更好的理解这个问题,看下面这个失败的例子,其中主服务器和从服务器中数据库都被删除了。

  • 我们设置节点 A 为主服务器,关闭持久化,节点 B 和 C 从节点 A 复制数据。
  • 这是出现了一个崩溃,但 Redis 具有自动重启系统,重启了进程,因为关闭了持久化,节点重启后只有一个空的数据集。
  • 节点 B 和 C 从节点 A 进行复制,现在节点 A 是空的,所以节点 B 和 C 上的复制数据也会被删除。
  • 当在高可用系统中使用 Redis Sentinel,关闭主服务器的持久化,并且运行自动重启后,这种情况是很危险的。比如主服务器可能在很短的时间就完成了重启,以至于 Sentinel 都无法检测到这次失败,那么上面说的这种失败的情况就发生了。

如果数据比较重要,并且在使用主从复制时关闭了主服务器持久化功能的场景中,都应该禁止实例自动重启。

为什么还会有从库的从库的设计?

通过分析主从库间的第一次数据同步的过程,你可以看到,一次全量复制中,对于主库来说,需要完成两个耗时的操作:生成 RDB 文件和传输 RDB 文件。

如果从库数量过多,而且都要和主库进行全量复制的话,就会导致主库忙于 fork 子进程生成 RDB 文件,进行数据全量复制。fork 这个操作会阻塞主进程处理正常请求,从而导致主库响应应用程序的请求速度变慢。此外,传输 RDB 文件也会占用主库的网络宽带,同样会给主库的资源使用带来压力。那么,有没有好的解决方法可用分担主库压力呢?

其实是有的,这就是“主-从-从”模式。

在刚才的介绍的主从库模式中,所有的从库都是和主库连接,所有的全量复制也都是和主库进行的。现在,我们可用通过“主-从-从”模式将主库生成 RDB 和传输 RDB 的压力,以级联的方式分散到从库上。

简单来说,我们在部署主从集群的时候,可用手动选择一个从库(比如选择内存资源配置较高的从库),用于级联其他非从库。然后,我们可用再选择一些从库(例如三分之一的从库),在这些从库上执行如下命令,使它们和刚才所选的从库,建立起主从关系。

1
replicaof 所选从库的IP 6379

这样一来,从库就会知道,在进行同步时,不用再和主库进行交互了,只要和级联的从库进行写操作同步就行了,这样可用减轻主库上的压力,如下图所示:

级联的“主-从-从”模式好了,到这里,我们了解了主从库间通过全量复制实现数据同步的过程,以及通过“主-从-从”模式分担主库压力的方式。那么,一旦主库完成了全量复制,它们之间就会一直维护一个网络连接,主库会通过这个连接将后续陆续收到的命令操作再同步给从库,这个过程也称为基于长连接的命令传播,可用避免频繁建立连接的开销。

读写分离及其中的问题

在主从复制基础上实现的读写分离,可用实现 Redis 的读负载均衡:由主节点提高写服务,由一个或多个从节点提供读服务(多个从节点既可以提高数据冗余程度,也可以最大化读负载能力);在读负载较大的应用场景下,可用大大提高 Redis 服务器的并发量。下面介绍在使用 Redis 读写分离时,需要注意的问题。

  • 延迟与不一致问题

前面已经讲到,由于主从复制的命令传播是异步的,延迟与数据的不一致不可避免。如果应用对数据不一致的接受程度较低,可能的优化措施包括:优化主从节点之间的网络环境(如在同机房部署);监控主从节点延迟(通过 offset)判断,如果节点延迟过大,通知应用不再通过该节点读取数据;使用集群同步扩展写负载和读复制等。

  • 数据过期问题

在单机 Redis 中,存在两种删除策略:

  • 惰性删除:服务器不会主动删除数据,只有当客户查询某个数据时,服务器判断该数据是否过期,如果过期则删除。
  • 定期删除:服务器执行定时任务删除过期数据,但是考虑到内存和 CPU 的折中(删除会释放内存,但是频繁的删除操作对 CPU 不友好),该删除的频率和执行时间都受到了限制。

在主从复制场景下,为了主从节点的数据一致性,从节点不会主动删除数据,而是由主节点控制从节点中过期数据的删除。由于主节点的惰性删除和定期删除策略,都不能保证主节点及时对过期数据执行删除操作,因此,当客户端通过 Redis 从节点读取数据时,很容易读到已经过期的数据。

Redis 3.2 中,从节点在读取数据时,增加了对数据是否过期的判断:如果该数据已经过期,则不返回给客户端;

  • 故障切换问题

在没有使用哨兵的读写分离场景下,应用针对读和写分别连接不同的 Redis 节点;当主节点或从节点出现问题而发生变更时;需要及时修改应用程序读写 Redis 数据的连接;连接的切换可以手动进行,或者自己写监控程序进行切换,但前者响应慢、容易出错,后者实现复杂,成本都不算低。

  • 总结

在使用读写分离之前,可用考虑其他方法增加 Redis 的读负载能力:如尽量优化主节点(减少慢查询、减少持久化等其他情况带来的阻塞等)提高负载能力;使用 Redis 集群同时提高读负载能力和写负载能力等。如果使用读写分离,可以使用哨兵,使主节点的故障切换尽可能自动化,并减少对应用程序的侵入。

评论




博客内容遵循 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议

载入天数...载入时分秒... 本站使用 Volantis 作为主题 鲁ICP备-20012065号