前言
如果某个服务器程序,只部署在一个物理服务器上就可能会面临一下问题(单点问题)
- 可用性问题,如果这个机器挂了,那么对应的客户端服务也相继断开
- 性能/支持的并发量有限
所以为了解决这些问题,就要引入分布式系统,在分布式系统中,会在多台服务器上部署redis服务,从而形成一个redis集群,此时就可以让这个集群给整个分布式系统提供更稳定有效的服务。
redis存在多种部署方式
- 主从模式
- 主从+哨兵模式
- 集群模式
主从模式
介绍
在若干个redis节点(物理服务器)中,有的节点被作为主节点,有的则作为从节点分别部署在redis-server进程中,从节点上的数据跟随主节点上的数据变化并保持一致。也就是说如果主节点上保存了一些数据,那么就要将这些数据复制一份出来给从节点,当主节点对这些数据有修改时,从节点也要进行对应的修改,并且从节点上的数据是不能主动或直接修改的,只能读取数据。
//从节点也可以看作是主节点的副本。
通过引入从节点,让我们的服务器有了更多的硬件资源,后续如果有客户端来进行读取数据,就可以在所有节点中随机挑选一个进行读取,因为从节点的数据和主节点的数据是保持一致的,也就可以互相分担更多的请求量,从而提高了服务器的性能。
而且引入多台服务器,也能大大提高服务器的稳定性,当有一台机器挂了的时候,还可以访问其他服务器,如果是主节点挂了,那么影响的也只是写操作,读操作依然可以通过从节点进行(主节点只有一个)。但是这种方式只能提高读操作的效率和稳定性,对于写操作因为只能在主节点上完成所以并没有什么提高。
通过单机实现主从模式
虽然主从模式需要多台服务器才能部署,但是我们可以通过在一台机器上,开启多个redis进程服务来模拟一个主从模式,但是这些进程的端口号必须不同。
这里我们来模拟部署一个主节点两个从节点
首先先复制两个配置文件,在配置文件中配置不同的redis端口
mkdir redis-conf
cp /etc/redis/redis.conf ./slave1.conf
cp /etc/redis/redis.conf ./slave2.conf
接着配置端口号
这个daemonize表示让redis按照后台进程的方式来运行
最后运行redis服务器
vim slave1.conf
vim slave2.conf
redis-server ./slave1.conf
redis-server ./slave2.conf
可以看到三个端口都成功运行,但是这只是三个redis服务器,互相独立并没有构成主从结构,想要实现主从结构还需要进一步配置
想要配置成主从结构,需要使用slaveof
- 在配置文件中加入slaveof (masterHost) (masterPort)随redis启动时生效
- 在redis-server启动命令时加入 --slaveof (masterHost) (masterPort)随redis关闭
- 直接使用redis命令:slaveof (masterHost) (masterPort)
这里我们使用配置文件的方式
将两个文件都加上该配置,配置完之后需要重新启动redis,这里世界使用kill -9
netstat -anp | grep redis
kill -9 3748325 3748331
不过这种停止redis-server的方式是和之前直接运行redis-server命令的方式搭配,如果使用的是service redis-server start 的方式启动,就必须使用service redis-server stop停止,因为使用service redis-server start的方式启动的进程,如果使用kill -9杀掉之后,这个redis-server进程会立刻自动重启
当重新启动后会看到多了几个tcp连接,这些连接就代表着主从节点直接的连接,每两个是一对。主节点就相当于服务器,从节点就相当于客户端。
//另外一个连接则是单独启动redis-cli来和redis主节点建立的连接
我们来看看到底有没有连上
可以看到我们在6379端口插入的key,在6380依然可以查看到 并且在6380端口不能进行写操作,说明我们已经构建了一个主从模式。
查看redis节点信息
info replication
主节点信息
- role:表示是主节点还是从节点
- connected_slaves:表示该主节点有几个从节点
- slave:ip:表示从节点地址
- port:表示从节点端口号
- state:表示从节点在线状态
- offset: 表示主节点和从节点之间同步数据的进度
- lag:表示延迟
从节点信息
可以看到从节点也有 connected_slaves表示从节点也可以有自己的从节点,主从结构不一定就是一个主机连多台从节点,每个从节点也可以再连其他从节点。
断开主从连接
slaveof no one
直接使用这个命令,就会断开当前的主从关系,当从节点断开主节点时,虽然已经不属于这个从节点了,但是里面的数据是不会被删除的,只是后面主节点的数据再发生更改,这个从节点就不会再同步数据了。
主节点
从节点
从信息中可以看到主从节点的关系已经断开,接下来我们实现是否还能同步数据
数据已经无法同步
因为6379和6380已经不是主从节点,所以我们就可以更换6380的主节点
看以看到已经成功把6380的主节点更换成6381,主从模式的结构也发生了改变
此时6380端口的redis服务的数据又和6379一致了,不过虽然此时的6381看起来像是6380的主节点,但是实际上并不是,他仍然是一个从节点不能进行写操作,只是作为6380同步数据的来源。
//刚才通过slaveof修改了主从结构,但是此处的主从结构修改是临时的,如果重启了redis服务器,仍然会变成刚开始的结构,因为我们的配置文件里所配置的结构就是和刚开始的结构一样。
拓扑结构
一主一从
一主一从结构是最简单的复制拓扑结构,用于主节点出现故障时向从节点提供故障转移支持。
这种结构可以帮主节点分担读数据请求,但是如果写数据请求太多,此时也是会给主节点造成不小的压力,可以通过关闭主节点的AOF,只在从节点上开启。也就是只让主节点进行写内存操作,这样就可以减小主节点的压力。
但是这样的设定有一个问题,如果主节点挂了,不能让他自动重启,因为如果自动重启了,此时因为没有AOF文件,就会导致主节点数据丢失,并且因为主从复制,就会把从节点的数据也给删了。所以当主节点挂了之后,应该让主节点先在从节点那里获取AOF文件后,在启动。
一主多从(星形结构)
一种多从的结构方式,可以使节点读写分离,当有大量读请求时可以将请求分摊给各个从节点,还可以让一个从节点专门处理一些比较复杂的读操作以此来提高整体稳定性。
但是主节点上的数据改变时,需要把数据同时同步给所以从节点,也就意味着,随着从节点个数的增加,每次同步数据都会带来更大的开销。而且主从节点之间需要通过网络通信,这就要求主节点有更大的带宽,进而提高整体设备的成本。
树形主从结构
通过这样的结果就可以让主节点有固定的从节点,可以通过从节点来对其他从节点的数据进行同步,但是相应的同步的效率也会变低
主从复制原理
主从复制流程
- 保存主节点信息,开始配置主从同步关系后,从节点只保存主节点的地址信息
- 主从节点建立tcp连接(三次握手),从节点内部通过每秒运行的定时任务,维护复制相关逻辑,当定时任务发现存在存在新的主节点后,会尝试与主节点建立基于tcp的网络连接,如果连接失败,定时任务会无限重试直到连接成功或者用户停止主从复制。
- 发送ping命令,验证主节点是否正常工作
- 如果redis主节点设置了密码(requirepass参数),就要验证权限,从节点通过配置masterauth参数来设置密码。
- 同步数据集,主节点会把当前的所有数据全部发送给从节点,这步也是耗时最大的,分为全量同步和部分同步
- 命令持续复制,从节点复制主节点数据后,主节点会持续把命令发给从节点,从节点执行修改命令,确保主从数据一致
redis提供了psync replicationid offset命令来完成数据同步,不过一般这个命令是由服务器建立tcp连接后自动执行。
replicationid
这个id是由主节点生成的,每次服务器重启主节点的replicationid都会不同,并且每个主节点有两个id,id1和id2,但是一般只是用id1,从节点和主节点建立关系就会获取主节点的replicationid。另外当从节点通过一些心跳包确定主节点已经挂了的话,就会自己成为主节点,并且给自己生成一个replicationid,但是此时这个节点会通过id2任然记住之前的主节点的id,等到主节点上线后这个节点就可以通过id2再找到原来的主节点。
offset 偏移量
主节点和从节点上都会维护offset偏移量,主节点的偏移量就是,主节点会有很多修改操作,每个命令都会占用几个字节,主节点就会把这些字节累加。从节点的偏移量就描述了同步数据的进度,如果从节点和主节点的replicationid和offset偏移量都一样,就可以认为主从节点数据一致。
psync流程
从节点发送psync命令给主节点,主节点会根据psync的参数(id和offset)以及自身数据情况决定响应情况。
- FULLRESYNC:全量数据同步
- CONTINFU:增量数据同步
- -ERR:比较老版本的redis不支持psync
psync可以从主节点获取全部数据也可以获得部分数据主要看参数offset,如果是-1(默认)的话就是获取全量数据,如果是一个具体的值就是部分复制而且从当前偏移量开始复制,不过也不是从节点想从哪个地方复制就从那个地方复制,主节点也会根据实际判断。
什么时候全量复制
- 首次和主节点进行数据同步
- 主节点不方便进行部分复制
什么时候部分复制
- 从节点已经从主节点上复制过数据了,因为一些原因断开连接了,从节点需要重新从主节点这边同步数据,此时因为从节点上已经有数据了所以只需要复制一部分就行
全量复制流程
1)从节点发送psync命令向主节点要数据,因为是第一次进行复制则psync参数就是(?,-1)表示进行全量复制。
2)主节点收到命令解析后回复FULLRESYNC响应
3)从节点收到响应后会保存主节点的运行信息
4)之后主节点会自动执行bgsave命令,进行快照操作完成持久化,并复制出一个新的rdb文件。(因为从节点的数据和主节点的数据要求完全同步,所以必须使用新的rdb文件以保证数据的实时性)
5)主节点将新复制的rdb文件传输给从节点(因为复制rdb文件是一个较高消耗的操作,而且这次是全量复制所以要传输的数据也比较多,所以整体过程较为耗时。就会导致在这个过程中会有新的修改命令,那么我们从节点接收到的rdb文件就不是最新的了)
6)因为无法保证传输的rdb文件是最新的,所以主节点会将复制和传输rdb文件期间接收到的写命令放入一个缓冲区,当从节点保存完rdb文件后,主节点就会将缓冲区中的数据传输给从节点,这些数据任然是按照rdb的二进制格式追加到从节点的rdb文件中,以保证主从节点数据的完全一致。
7)从节点清除自身的旧数据
8)从节点加载rdb文件
9)如果从节点开启了AOF,那么在加载的时候就会产生大量的aof日志,因为全量复制的数据量很大就会出现很多冗余的日志,因此这里就会执行bgrewriteaof,将aof文件里该删的删,该合并的合并,对文件进行一个整理。
无硬盘模式全量复制
在刚刚的流程中主节点复制一份新的rdb文件目的就是为了,将数据传输给从节点,那么为了节省开销,就可以省去这个读写硬盘的操作(复制rdb),直接将数据通过网络传输给从节点,这样就节省了一次复制rdb的时间,从节点接收到数据后也是写入到rdb文件然后加载数据,那么将这个过程也省略掉,直接把收到的数据进行加载,这样就又省了一系列读写硬盘的操作。
部分复制流程
当主从节点都正常工作一段时间后,此时从节点已经有了主节点的复制,如果在某一时刻因为网络抖动,或者其他原因主从节之间的连接断开了的话,后续再连接上就只需要进行部分复制(具体连接过程上文有提到)。
1)如果主从节点断开的时间超过repi-timeout设定的时间,那么主节点就认为从节点断开了
2)从节点在断开连接时依然会响应命令,但是由于这些复制命令发不出去都会放在一个积压缓冲区里(一个用数组实现的环形队列)
3)重新连接后,从节点会根据之前保存的主节点的replicationid和offset通过psync的参数发送给主节点
4)主节点接收到psync命令后会对replicationid和offset(描绘的就是主从节点的复制进度)进行判断,如果replicationid不是自己的replicationid主节点就会认为这个从节点是新连接上的就会进行全量复制,如果replicationid正确则进行部分复制。判断offset的值,主要是判断从节点当前的复制进度是多少,如果从节点当前缺失的部分刚好是积压缓冲区里的数据,就可以直接将这部分数据复制过去,如果不是则需要将之前缺失的数据也复制过去,最后主节点还要响应CONTINUE表示这是一次部分复制。
实时复制
从节点已经和主节点同步好了数据,但是主节点依然会又源源不断的新的请求,主节点的数据改变,从节点的数据也就要改变。
主从节点之间会通过TCP建立长连接,然后主节点会把自己收到的修改数据的请求通过连接发送给从节点,从节点在根据这些请求修改自己的数据。(如果主从结构是树型的那么发送的过程就会较为耗时)
因为在实时复制时,主从节点需要保证连接的稳定,就会通过心跳机制来确定对方是否正常在线。
- 主节点:默认是每10s给从节点发送一个ping命令,从节点收到后就会返回pong
- 从节点:默认每个1s就给主节点发送一个特殊的请求,就会通知主节点自己当前的复制进度offset
总结
全量复制的部分复制都是在从节点第一次连接主节点时进行的,如果以前没连过就进行全量复制,如果连接过又断开的就进行部分复制,也就是说全量复制和部分复制都是给从节点进行一个初始化的。而后续的主从节点间数据的同步则是通过实时复制。