如果我们只是作为个人玩玩Redis,那么单台服务器已经足够。但在实际企业的项目中,单台Redis服务器几乎是不可能的。理由有以下几个:
(1)从结构上,单个Redis服务器会发生单点故障,同时一台服务器需要承受所有的请求,所以需要对数据做副本并分配在不同的服务器上。
(2)从容量上,单个Redis服务器的内存容易成为存储瓶颈,所以需要进行数据分片。
1. 复制
复制的目的是为了避免单点故障的问题,通常的做法是将数据库复制多个副本以部署在不同的服务器上,这样即使有一台服务器出现故障,其它服务器依然可以继续提供服务。
1.1 搭建主从架构
在复制的概念中,数据库分为两类:一是主数据库,通常称为master;二是从数据库,通常称为slave。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接收主数据库同步过来的数据。一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。如下图:
服务器列表:
服务器ip | 端口号 | 角色 |
---|---|---|
192.168.1.19 | 6379 | 主节点 |
192.168.1.20 | 6379 | 从节点1 |
192.168.1.21 | 6379 | 从节点2 |
(1)首先根据《Redis:简介和安装》在三台服务器上安装Redis。
(2)使用slaveof 主节点ip 主节点端口
命令设置主从关系。(只需要在从节点执行即可)
(3)使用info replication
命令查看节点信息。
- 192.168.1.19
- 192.168.1.20/192.168.1.21
至此,Redis的主从架构即搭建完成。但是这里要说两个配置:
(1)bind 0.0.0.0
:默认bind
的值为127.0.0.1
,也就是只能本机才能访问,外部请求会被过滤。
(2)我们在redis终端使用slaveof
命令配置主从架构,当redis重启之后,那么这个架构中的各个角色将不复存在。如果想要永久地保存这种关系,则可以通过在配置slaveof 主节点ip 主节点端口
永久设置。
2. 原理
Redis的复制分为两个阶段:复制初始化阶段和复制同步阶段。
- 复制初始化阶段
(1)从节点向主节点发送SYNC命令;
(2)主节点接收到SYNC命令后开始在后台保存快照(同RDB持久化的过程),并将保存快照期间接收到命令缓存起来;
(3)快照结束后,Redis会将快照文件和所有缓存的命令发送给从节点;
(4)从节点接收到命令后,会载入快照文件并执行收到的缓存的命令。
- 复制同步阶段
(1)复制初始化阶段结束后,主节点执行的任何会导致数据变化的命令都会异步地传送给从节点,这一过程称之为复制同步阶段。
3. 无硬盘复制
Redis复制的工作原理是基于RDB方式持久化实现的,即主节点在后台保存RDB快照,从节点接收并载入快照文件。这样的实现优点是简化逻辑,复用已有的代码,但也有以下缺点:
(1)当主节点禁用RDB持久化时,如果执行了复制初始化操作,Redis依然会生成RDB快照文件,所以下次启动Redis服务时主节点会以该快照恢复数据。因为复制发生的时间不能确定,这使得恢复的数据可能是任何时间点的。
(2)因为复制初始化时需要在硬盘中创建RDB快照文件,所以如果硬盘性能很慢时这一过程会对性能产生影响。
基于此,Redis引入了无硬盘复制选项,开启该选项(repl-diskless-sync
)时,Redis在与从节点进行复制初始化时不会将快照内容存储到硬盘上,而是字节通过网络发送给从节点,从而避免了硬盘的性能瓶颈。
4. 增量复制
上面说到主从复制原理的初始化阶段,从节点会发送SYNC命令进行一次完整的复制操作。想象一下如果主从节点连接断开后,等从节点再次连接到主节点,如果断开连接期间数据库的变化很小甚至没有,但此时仍然要将数据库中所有数据重新快照并传送一次,这显然不是最优的解决方案。
实际上Redis已经为这个问题提供了解决方案,那就是增量复制。增量复制是基于以下3点实现的:
(1)从节点会存储主节点的运行ID,每个Redis运行实例均会拥有一个唯一的运行ID,每当实例重启后,就会自动生成一个新的运行ID。
(2)在复制同步阶段,主节点每将一个命令传送给从节点时,都会同时将该命令存放到一个积压队列(backlog)中,并记录下当前积压队列中存放的命令的偏移量范围。
(3)同时,从节点接收到主节点传来的命令时,会记录下该命令的偏移量。
有了上面3个基础条件,当主从节点准备就绪连接后,从节点不再向主节点发送SYNC命令,而是发送PSYNC,格式为:PSYNC 主节点的运行ID 断开前最新的命令偏移量
。主节点收到PSYNC命令后,会执行以下判断来决定此次是否可以执行增量复制。
(1)首先主节点会判断从节点传送过来的运行ID是否和自己的运行ID相同,这一步骤的意义在于确保从节点之前确实是和自己同步的,避免从节点拿到错误的数据。(比如主节点服务重启后,运行ID就变了)
(2)然后判断从节点最后同步成功的命令偏移量是否在积压队列中,如果在则可以执行增量复制,并将积压队列中相应的命令发送给从节点。
如果此次重连不满足增量复制的条件,则主节点会进行一次全量复制。
大部分情况下,增量复制的过程对于开发者来说是完全透明的,我们不需要关心增量复制的具体细节,唯一需要开发者设置的就是积压队列的大小了。
积压队列在本质上是一个固定长度的循环队列,默认情况下积压队列的大小为1MB,可以通过配置文件的repl-backlog-size
选项来调整。积压队列越大,其允许的主从节点断开的时间就越长。
与积压队列相关的另一个配置选项是repl-backlog-ttl
,即当从节点与主节点断开连接后,经过多久时间可以释放积压队列的内存空间,默认是1小时,单位是秒。
5. 从节点持久化
Redis中相对耗时的操作是持久化,为了提高性能,我们可以通过主从复制解决该问题。主从复制架构中的从节点启用持久化,同时主节点禁用持久化。当从节点重启后,主节点会自动将数据通过过来,所以无需担心数据丢失。
然而当主节点服务挂掉后,情况就变得稍微有点复杂。我们需要手工通过从节点数据恢复主节点数据时,就需要严格按照以下两步进行:
(1)在从节点中使用slaveof no one
命令将从节点提升成主节点继续服务;
(2)启动之前挂掉的主节点,使用slaveof
命令将其设置成新的主节点的从节点,即可将数据同步回来。
这种情况下,之所以不能直接重启主节点的原因在于主节点重启后,由于没有开启持久化功能,所以数据库中没有数据,这是从节点依然会从主节点接收数据,使得从节点的数据也被清空,这样从节点的持久化也就没有意义了。那么有没有一种自动化的方案来维护主或从节点的重启以及数据恢复呢,答案也是有的,我们下回分解。