我们一般使用redis作为缓存来提高我们的应用性能,我们听过很多redis的功能:主从复制,主从切换,持久化(RDB,AOF,AOF重写),今天我们从降低redis服务的不可用的角度来讲解,redis从单体到集群架构的演进过程,以及这些功能的运用。
1.单机架构:
用户访问程序,先访问redis,在redis上没有找到结果,就从数据库查找,再将数据库结果放到redis缓存中,下次可以直接从redis缓存中获取,最后再返回给用户。
redis在单体架构时,redis宕机后,需要人工进行重启,重启后redis内存的数据是空的,由于redis数据都是存储在内存里的,如果重启后,内存的数据就会全部丢失,这时候用户访问程序,就只能从数据库获取,如果有大量请求到来,就会给数据库造成很大的压力,甚至把数据库压垮。
单机模式下:redis服务的不可用时间= 人工发现故障所需时间 + 加载数据库数据到内存所需时间 (大量请求可能会导致数据库宕机)
消除加载数据库数据到内存所需时间:
我们可以配置redis持久化来消除这种情况的发生,redis持久化有两种方式
- RDB方式:根据内存的数据快照,生成二进制文件,保存到磁盘。恢复数据的时候,读取二进制文件到内存就能完成恢复。
通过bgsave命令,redis主线程fork出子线程,子线程共享主线程的内存数据,由子线程生成rdb文件,不会阻塞redis主线程,主线程可以正常处理请求,如果子线程生成rdb文件时,有请求的对内存的数据做了修改,这时候会使用操作系统的写时复制技术,生产子线程的专属内存,这样避免了生产的快照数据不一致问题。
- 过程:通过save命令,redis主线程根据内存数据,生成rdb文件,会阻塞redis主线程;
- 优点:恢复速度快,由于文件保存的就是内存的数据,读取文件内容到内存就可以恢复数据了,所以恢复数据是很快的。
- 缺点:持久化时间长,由于每次都是保存整个内存的数据,所以保存一次时间比较长,如果执行太频繁会造成磁盘压力大,如果执行间隔太久就会存在丢失较多数据的情况。
- AOF方式:保存修改数据的redis命令,文件中存储的就是一条一条的redis命令,回放redis命令就能完成内存数据的恢复。
- 过程:接收到redis的修改命令,先执行命令修改内存,然后再将命令保存到文件中,文件的斜盘可以选择不一样的策略(always ,everysec,no),同步写还是异步写盘,取决于你对数据的可靠性要求高还是性能要求高。
- 优点:持久化时间快,每次只需要追加一条命令到文件,所以保存起来很快。
- 缺点:恢复时间长+文件大,因为保存了所有的修改命令,导致AOF的文件会很大,恢复数据时需要回放所有的命令,所以恢复数据时间会很长。
简单来说就是使用RBD持久化方式的缺点是丢失数据比较多,AOF持久化方式恢复的时间比较长和文件较大。那有没有一种方式既可以不丢太多数据,而且恢复速度又快的。
先解决AOF文件比较大的问题:可以通过AOF重写的方式解决,根据内存保存的数据,生成redis命令,保存到文件中,替换旧的AOF文件。因为一个key经过100次修改,AOF就会保存一百条命令,如果根据当前内存生产redis命令,就只有一条,所以大大降低了AOF文件的大小。
AOF文件重写过程:主线程fork出子线程,子线程根据当前内存的数据,生成redis命令,然后生成新的AOF文件;主线程在这期间接收到新的命令,会保存到旧的AOF文件+AOF重写缓存区,等待子线程生成完新的AOF文件,主线程会把AOF重写缓存区的数据,写到新的AOF文件中,替换旧的AOF文件,完成AOF重写。
RDB+AOF混合持久化方式,基于AOF重写的优化,需要redis 4.0以上才支持。先经过RDB持久化保存一次快照,在下一次持久化期间,使用AOF的方式保存期间的修改命令,这样恢复的时候,先读取RDB文件到内存,然后再执行AOF文件的命令,由于AOF比较小,所以执行起来是很快的。
redis单机+持久化的模式:redis的不可用时间= 人工发现故障所需的时间 + 加载持久化文件所需时间 。加载持久化的时间比加载数据库的时间缩短了很多,如果持久化文件太多,恢复的时间也会很长,有没有什么办法减少这个时间,提高服务的可用性呢?
2.主从模式
给redis配置从节点,实时同步主节点的数据,这样主节点发送故障宕机不可用时,可以人工将从节点切换到主节点,快速让redis提供服务。
主从同步过程:
全量同步:
- 从库先发送psync命令给主库(主库的 runID :?和复制进度 offset:-1),告知主库需要全量同步数据
- 主库发送FULLRESYNC命令带上runID+offset
- 主库通过bgsave命令,生成RDB文件,传输给从库,从库会先清空数据库数据,然后再执行RDB文件到内存中
- 主库在从库同步过程中,有新请求时,会把数据放入replication buffer(每个从库独享),待从库同步完,主库会把replication buffer同步给从库
- 完成全量同步后,主从节点会保持长连接,会把新命令传输给从节点
注意在全量同步过程的c步骤,主库需要fork出子线程,这个过程需要阻塞主线程,传输RDB文件也会占用网络带宽,这样会影响正常的请求,如果从库很多的情况下,就会影响redis的性能;可以通过主从级联模式,让一些从节点承担全量复制的职责。
增量同步:
在全量同步完成后,主从节点会一直保持着长连接,通过长连接发送新命令给从节点,会一直保持着数据的一致,如果长连接断开了,数据怎么进行同步,这时候就需要用到增量同步了,2.8版本以上才支持。
- 主库会把新增的命令保存到环形缓冲区 repl_backlog_buffer(所有从库共享)
- 主库会记录写到位置(master_repl_offset),从库会记录读到位置(slave_repl_offset)
- 待网络恢复后,从库重新连接主库后,通过psync命令把主库的 runID + slave_repl_offset发给主库
- 主库收到后通过对比自己的runID相同,并且slave_repl_offset范围的数据还在repl_backlog_buffer缓存区中,就满足增量同步的要求,否则只能是全量同步。
- 根据slave_repl_offset,在repl_backlog_buffer查找把断连期间的数据发给从库,完成增量同步过程。
注意:repl_backlog_buffer是环形缓冲区,如果配置的太小,很容易就导致全量同步的触发,所以可以根据自身的业务来配置它的大小。
从节点有两个作用:
- 主节点发送故障时,通过人工切换成主节点,相比重启恢复主节点的方式,不需要加载持久化的RDB和AOF文件,直接发送切换命令就可以完成切换,恢复时间更短
- 可以承接部分的读请求,降低主节点的压力。
主从模式:可以消除加载持久化文件所需时间,这时候服务不可用时间 = 人工发现故障所需的时间 。这时候的服务不可用的时间,取决于人工发现故障时间,这个时间是不可控的,怎么可以进一步减少这个时间,可不可以将人工切换改成自动切换呢?当然是有的,下篇我们再来讨论主从切换的方式怎么进一步降低redis的不可用时间