Bootstrap

Redis 事务 && 主从复制

Redis事务 

认识Reids的事务

再来回顾一下MySQL中的事务。

MySQL事务的四个属性:

原子性

一致性

持久化

隔离性

在MySQL中的事务实现的比较复杂,而Redis的事务在MySQL面前就是 “弟弟” 级别的。

看看 Redis 的事务和 MySQL 事务的区别:
弱化的原⼦性: redis 没有 "回滚机制". 只能做到这些操作 "批量执⾏". 不能做到 "⼀个失败就恢复到
初始状态".
不保证⼀致性: 不涉及 "约束". 也没有回滚. MySQL 的⼀致性体现的是运⾏事务前和运⾏后 , 结果都
是合理有效的, 不会出现中间⾮法状态.
不需要隔离性: 也没有隔离级别, 因为不会并发执⾏事务 (redis 单线程处理请求) .
不需要持久性: 是保存在内存的. 是否开启持久化, 是redis-server ⾃⼰的事情, 和事务⽆关.而在MySQL中,只要事务提交了,就会被刷新到硬盘上。

 也就是说,Redis的事务只有一个弱原子性,相较于MySQL的事务,其他的属性全都没有。

MySQL中的事务可以回滚,而Redis的不行。所以有越来越多的声音说Redis的事务并没有原子性

Redis中的事务的意义就是为了将命令打包,避免其他客户端的命令插队到中间。

大致就是,当我们输入命令开启一个事务时,此时客户端发送的命令不会被立即执行,而是会被放入到一个队列中,当我们输入 执行事务 的命令后,Redis就会把这些队列中的任务依次执行。这里的执行就是在Redis主线程中执行的,此时就不会处理别的客户端的请求。

看起来Redis的事务很弱,那为什么不设计成MySQL那样呢?
其实设计成MySQL那样的事务就需要付出更多的代价:

在空间上会带来更多的存储上的代价。

在时间上也会有更大的执行开销。

而Redis的最大优势就是速度快,所以就没有设计成MySQL那样了。

那么Redis这里的事务有没有什么应用场景呢?

比如往上购物,避免超卖的情况,就需要将以下的操作打包在一起执行:

因为Redis是单线程的,所以只需要确保这一组操作是同一个客户端打包一起完成的,就不会有什么线程安全问题,也就不会出现超卖的情况。 (不用加锁)

Redis事务的相关命令

MULTI:开启事务。

EXEC:提交事务

DISCRD:放弃当前事务。

可以看到,当我们开启时候后,我们所有的操作都会先放入到一个队列中,并不会立即执行,当我们执行exec命令之后,Redis才会按顺序依次执行队列中的操作。

如果中途执行discrd命令,或者进程挂了 / 机器掉电了,那么这个事务就会被丢弃(中间的操作自然也就不会被执行了)。

另外还有一个命令就是 WATCH命令,这个命令可以监控某个key,监控这个key在multi 与 exec之间,set key之后,这个key是否在外部被其他客户端修改了。

示例:
在一个客户端上:

我们首先将key1设置为 11,然后对进行watch监控,然后开启事务,然后将key1的值修改为22,接着,我们在执行事务前(exec前),在另一个客户端将key1的值修改为33:

最后再执行exec,发现返回了一个nil,说明这个事务等于直接被抛弃了,因为监控的key1发现在外部被修改了。另外执行exec之后,上次的watch监控就失效了,如果需要再监控则需要重新设置

关于watch还有一个unwatch命令,这个命令是不需要加参数的,会解除所有当前客户端正在监控的key,并且如果需要接触监控,则要在执行multi前执行unwatch。

了解watch命令的原理 

watch的实现类似一个 “乐观锁”。

 乐观锁不同于 我们之前学C++线程那里的锁,那里的是悲观锁,悲观锁会让其他线程阻塞挂起。

watch命令这里就是基于版本号机制实现了乐观锁。

大致流程就是,当我们用watch监控一个key时,就会给这个key安排一个版本号(这个版本号是会不断增长的,可以理解为一个整数),每次只要有针对这个key修改操作的时候(比如在其他客户端中修改了这个key),都会让这个版本号增大。最后在执行exec命令的时候,就会判断这个key的版本号是否和最初watch的时候记录的版本号一致,如果一致,那么这个事务就会正常执行,如果不一致,那么就会直接丢弃掉这个事务,exec就会返回nil。

所以这就是为什么watch命令一定要在multi之前执行。 

其实关于版本号机制这样典型的思想有很多的应用场景,在MySQL的MVCC那里就是用到了这个版本号机制。 

Redis事务的总结 

Redis的事务要比MySQL要简单很多。

1.弱原子性:Redis 事务不支持回滚。

2.不保证一致性:Redis并不会保证事务执行前和执行后的内容是统一的。

3.没有持久化:Redis本身就是在内存中存储数据的,这里的事务与持久化无关。

4.不存在隔离性:Redis作为单线程的服务器模型,处理请求本质就是串行执行的。

Redis主从复制 

单点问题  

在分布式系统中,有一个非常关键的问题:单点问题。

单点问题:如果某个服务器程序,只有一个节点。(只有一个物理服务器,部署服务器程序) 

 此时就会存在两个问题:

1.可用性问题,如果这个节点挂了,那么也就意味这个服务中断了。

2.在性能上支持的并发量是有限的。

 在分布式系统中,希望多个服务器来部署Redis,有以下几种Redis的部署方式:
1.主从模式

2.主从 + 哨兵模式

3.集群模式

这里我们重点介绍主从模式。

主从复制解决什么问题?

 在若干个Redis节点中,选择一个作为 “主”节点,其他的作为 “从”节点。

 本来,所有数据都会保存在主节点上,引入从节点后,就是要把主节点的所有数据复制到所有从节点上,后续对于主节点上有任何的修改,都会同步到从节点上。这样看来从节点其实就是主节点的一个副本。

在Redis主从模式中,从节点上的数据是不允许被用户直接修改的,它只能是从主节点上同步数据

我们之前说到单个服务器(单点问题)会有两个问题:

1.可用性问题。

2.在性能上支持的并发量的问题。

那么当引入了主从模式之后,如果某个节点挂了,也不会直接让整个服务器挂掉,这样就大大提高了整个服务的可用性。另外,假设原本对于主节点有一万的并发量,在引入两个从节点之后,平均每个节点的并发量就只有三千多,这样主节点的压力就大大降低了,也就解决了性能上支持的并发量的问题。

不过对于可用性这个问题上,还需要再进行讨论:

我们知道,数据的写入只能是发生在主节点上,然后再由主节点同步到其余的从节点上。

对于用户的访问可以分为 读取操作 && 写入操作。

主从模式可用性的提升主要是针对 “读操作” 上的,因为对于 读操作,主节点和从节点都可以进行

然后对于 “写操作”,是只有主节点可以进行的,如果是主节点挂了,那么写操作就无法进行了,而主节点只能有一个,不能搞多个。

所以总结就是:主从模式对于可用性的提升是主要针对读操作的。

然而实际场景中,读操作往往比写操作要频繁的多。

主从结构是分布式系统中比较经典的一种结构,不仅仅是Redis支持,像MySQL等其他常用的组件也都是支持的。 

启动多个Redis服务 

 要配置Redis的主从结构,首先需要启动多个Redis服务器。

正常来说,每个Redis服务器应该在一个单独的主机上(这才是分布式)

但是这里我们为了演示,就在同一台云服务器上启动三个Redis服务器。

在一个主机上,我们也是可以运行多个redis-server进程的,不过我们需要保证多个redis-server的端口是要不同的。

我们知道Redis的默认端口号是 6379,我们有两种方式来指定Redis的端口号

一是在启动的redis-server的时候,以命令行的方式。

二是直接在配置文件中来设定端口号。

另外,在同一个主机上启动多个redis-server,对于每一个redis-server都需要一份独立的配置文件,所以我们可以单独创建一个目录,然后把主节点的配置文件拷贝两份过来,然后进行修改。

redis默认配置文件的路径:

cd /etc/redis/

 这里在我新建的目录下拷贝过来了

然后进行修改,我们只需要修改从节点的配置文件就可以了,主节点不用修改。

在修改时,有两个地方需要特别关注

1.确保进程是以守护进程的方式运行的: 

 这里要确保是yes。

2.确保端口号不要重复

 比如这里对其中一个修改成为了6380

设置好后,以这样的方式启动

然后我们进行查看

 发现就有三个Redis服务了,其中两个服务的端口号分别是 6380 6381。

配置主从复制

 配置方式有三种:

1. 在配置⽂件中加⼊ slaveof {masterHost} {masterPort} 随 Redis 启动⽣效。
2. 在 redis-server 启动命令时加⼊ --slaveof {masterHost} {masterPort} ⽣效。
3. 直接使⽤ redis 命令:slaveof {masterHost} {masterPort} ⽣效。

一般开发中使用的是修改配置文件的方式,因为这种方式可以持久生效。

在VIM的普通模式下(esc),用G键可以快速跳到文件的末尾。

这样就可以持久生效了。

另外记得将之前启动的redis-server重启才能生效。

另外关于关闭redis-server

如果我们想关闭一个服务,如果这个服务是以service redis-server start的方式启动的,那么就必须使用service redis-server stop这样的方式来停止。如果我们使用kill -9的方式停止的话,这样启动的redis-server是会立马自动启动的(Ubuntu下)。这是因为一般来说服务器就是需要稳定性和高可用,如果一个服务器挂了我们往往希望它能立马重新启动。所以在Ubuntu下会有另外一个进程来专门监控指定的服务器进程的运行状态。

而我们其余两个redis-server是通过命令行的方式启动的,因此直接用kill -9的方式杀掉就可以了。

重新启动:

这里建议主节点也重启一下。 

然后用netstat查看一下

 这里我们发现多了四条tcp连接,仔细观察就发现了,主节点此时就像一个服务器,从节点就像是客户端。这里就说明主从结构建立完毕,并且是这样的关系:

查看主从结构信息 

在Redis客户端执行

info replication

可以查看当前的主从信息。

在主节点上:

这里简单说下字段信息:

role:角色,这里是master,也就是主节点

connected_slaves:从节点的数量,这里是2

 slave0 / 1 :包含了从节点的一些信息,比如端口号,状态=在线,offset相当于从节点和主节点之间,同步数据的进度。lag用来表示主从之间的网络延迟的,这里是本地环回,所以不会有什么延迟。

repl_backlog那一堆就是指的:挤压缓冲区,是用来支持部分同步机制的实现的。

再来看看从节点的: 

其他的字段不必多说,我们还看到了 connected_slaves字段,说明从节点其实还可以有它的从节点的,作用后面说。

 

上图可以看到,主节点这边修改了数据,就会同步到从节点上。

 断开 && 修改主从结构

要想断开主从结构,直接在客户端上执行:

slaveof no one

 就能断开现有的主从关系,这个命令没必要写在配置文件中,直接在客户端用就行。

当某个从节点断开主从关系后,它就不再从属于其他节点了,自己就会变成一个主节点,此时它就不会再同步之前主节点的数据了,但是已有的数据不会丢弃。

此时我们也可以让这个从节点从属于其他节点,同样在客户端上执行就可以了。

可以变成如下:

这样在客户端通过slaveof 的方式修改了主从结构,不过这样的修改是临时的,如果重启了Redis服务器,那么仍会按照配置文件中的内容来建立主从关系。

主从复制时的安全 & 只读 & 传输延迟

安全性
对于数据⽐较重要的节点,主节点会通过设置 requirepass 参数进⾏密码验证,这时所有的客⼾
端访问必须使⽤ auth 命令实⾏校验。从节点与主节点的复制连接是通过⼀个特殊标识的客⼾端来完成,因此需要配置从节点的masterauth 参数与主节点密码保持⼀致,这样从节点才可以正确地连接到主节点并发起复制流程。

只读
默认情况下,从节点使⽤ slave-read-only=yes 配置为只读模式。由于复制只能从主节点到从节
点,对于从节点的任何修改主节点都⽆法感知,修改从节点会造成主从数据不⼀致。所以建议线上不要修改从节点的只读模式。

传输延迟
主从节点⼀般部署在不同机器上,复制时的⽹络延迟就成为需要考虑的问题,Redis 为我们提供
了 repl-disable-tcp-nodelay 参数⽤于控制是否关闭 TCP_NODELAY,默认为 no,即开启 tcp
nodelay 功能,说明如下:
当关闭时,主节点产⽣的命令数据⽆论⼤⼩都会及时地发送给从节点,这样主从之间延迟会变⼩,
但增加了⽹络带宽的消耗。适⽤于主从之间的⽹络环境良好的场景,如同机房部署。
当开启时,主节点会合并较⼩的 TCP 数据包从⽽节省带宽。默认发送时间间隔取决于 Linux 的内
核,⼀般默认为 40 毫秒。这种配置节省了带宽但增⼤主从之间的延迟。适⽤于主从⽹络环境复杂
的场景,如跨机房部署。

主从复制的拓扑结构 

Redis 的复制拓扑结构可以⽀持单层或多层复制关系,根据拓扑复杂性可以分为以下三种:⼀主⼀
从、⼀主多从、树状主从结构。

⼀主⼀从结构

⼀主⼀从结构是最简单的复制拓扑结构,⽤于主节点出现宕机时从节点提供故障转移⽀持,如图
所⽰。当应⽤写命令并发量较⾼且需要持久化时,可以只在从节点上开启 AOF,这样既可以保证数据安全性同时也避免了持久化对主节点的性能⼲扰。但需要注意的是,当主节点关闭持久化功能时,如果主节点宕机要避免⾃动重启操作。

一主一从结构,如果写请求太多了,那么就会对主节点造成一些压力,此时就可以通过关闭主节点的AOF,只在从节点上开启AOF,这样就能减轻主节点的压力。

但是这样就有一个严重缺陷:如果主节点一旦挂了,不能让它自动重启,一旦重启就会因为没有AOF文件而丢失数据,并且还会进一步同步从节点,把从节点的数据也删除了。

改进方法:当主节点挂了后,先让主节点从从节点那里获取到AOF文件后,再启动。

⼀主多从结构

⼀主多从结构(星形结构)使得应⽤端可以利⽤多个从节点实现读写分离,如下图 所⽰。对于
读⽐重较⼤的场景,可以把读命令负载均衡到不同的从节点上来分担压⼒。同时⼀些耗时的读命令可以指定⼀台专⻔的从节点执⾏,避免破坏整体的稳定性。对于写并发量较⾼的场景,多个从节点会导致主节点写命令的多次发送从⽽加重主节点的负载。

因为实际开发中,读请求的数量远多于写请求,因此这种结构就能分摊每个节点的压力。、

但是因为这种扁平化的结构,主节点每次修改数据时,都需要将数据同步到从节点,当从节点数量很多时,就会给主节点带来压力。 

树形主从结构 

树形主从结构(分层结构)使得从节点不但可以复制主节点数据,同时可以作为其他从节点的主
节点继续向下层复制。通过引⼊复制中间层,可以有效降低住系欸按负载和需要传送给从节点的数据量,下图 所⽰。数据写⼊节点 A 之后会同步给 B 和 C 节点,B 节点进⼀步把数据同步给 D 和 E 节点。当主节点需要挂载等多个从节点时为了避免对主节点的性能⼲扰,可以采⽤这种拓扑结构。

通过这样的树形结构,就可以把主节点同步数据的压力进行分摊,这样主节点 就不需要那么高的网卡带宽了。

缺点就是:一旦数据进行修改了,那么同步的延迟是比一主多从结构的延迟更长的。

主从复制的基本流程

 

当从节点收到连接主节点的命令时,就会按顺序执行上图的操作。 

其中同步数据那一步可以说是最重要的一步了。

replicationid的作用 

在Redis提供了psync 命令,用来完成数据同步。

psync命令不需要用户手动执行,Redis从节点服务器会在建立好主从结构之后自动执行psync命令,从主节点那边拉取数据。

replicationid 可以简称:replid

主节点的复制 id. 主节点重新启动, 或者从节点晋级成主节点, 都会⽣成⼀个 replicationid. (同⼀个节点, 每次重启, ⽣成的 replicationid 也会变化).
从节点在和主节点建⽴连接之后, 就会获取到主节点的 replicationid.

我们可以用

info replication

 来查看replid

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:1da596acecf5a34b4b2aae45bd35be785691ae69 #就是这个
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

master_replid就是我们所说的replid。同时我们还注意到有一个master_replid2,这个主要是起到一个备份的作用。比如:主节点A与从节点B通信的过程中,出现了网络抖动,此时B就以为主节点A挂掉了,那么B就会自己成为主节点,那么此时它也就有了自己的replid,但是原来保存的A的replid不会丢弃,而是保存在master_replid2中,这样在网络稳定之后,B还可以重新回到原来的主从结构中,其中这里重新回到原来主从结构需要手动干预 或者 哨兵机制可以自动完成。

 offset的作用

offset :也就是偏移量。

 在主节点和从节点上都会维护这个偏移量(整数)。

关于这个偏移量:当主节点收到修改命令时(读命令不算),每个修改命令都会占据几个字节,主节点就会把这些修改命令的字节大小进行累加。

这样从节点就可以用这个偏移量来描述自己与主节点的数据同步到哪了。

从节点每秒钟上报⾃⾝的复制偏移量给主节点,因此主节点也会保存从节点的复制偏移量。

结论: replicationid 和 offset 共同描述了一个 “数据集”。

如果发现有两个Redis服务,它们的replicationid 和 offset都是一样的,那么就说明它们存储的数据是完全一样的。

如果replicationid 不同:说明它们的主节点都不一样,数据一般也就不一样。

如果offset 不同:那么它俩虽然主节点是一样的,但是同步的进度不一样,所以数据也不一样。

全量复制和部分复制 

Redis 使⽤ psync 命令完成主从数据同步,同步过程分为:全量复制和部分复制。
全量复制:⼀般⽤于初次复制场景,Redis 早期⽀持的复制功能只有全量复制,它会把主节点全部
数据⼀次性发送给从节点,当数据量较⼤时,会对主从节点和⽹络造成很⼤的开销。
部分复制:⽤于处理在主从复制中因⽹络闪断等原因造成的数据丢失场景,当从节点再次连上主节
点后,如果条件允许,主节点会补发数据给从节点。因为补发的数据远⼩于全量数据,可以有效避
免全量复制的过⾼开销。

psync语法格式

 PSYNC replicationid offset

 当offset = -1时,就是全量复制。

当offset 是某一个具体的正整数时,就是尝试进行部分复制。

从节点发送psync命令后的三种情况:

关于部分复制,主节点会自行判断,看当前是否方便给部分数据,如果不方便就只能给全量了。

主节点进行全量复制的情况:

1.从节点首次和主节点进行数据同步。

2.主节点不方便进行部分复制的时候。

主节点进行部分复制的情况:

1.从节点之前已经从主节点上复制过数据了,因为网络抖动或者从节点重启了。

2.从节点需要重新从主节点上同步数据,此时大部分数据都是一致的,那么就看能不能只同步一部分数据。

全量复制的流程 

根据图示步骤:

1)从节点发送 psync 命令给主节点进⾏数据同步,由于是第⼀次进⾏复制,从节点没有主节点的运⾏ ID 和复制偏移量,所以发送 psync ? -1。

2)主节点根据命令,解析出要进⾏全量复制,回复 +FULLRESYNC 响应。

3)从节点接收主节点的运⾏信息进⾏保存。

4)主节点执⾏ bgsave 进⾏ RDB ⽂件的持久化。

5)从节点发送 RDB ⽂件给从节点,从节点保存 RDB 数据到本地硬盘。

6)主节点将从⽣成 RDB 到接收完成期间执⾏的写命令,写⼊缓冲区中,等从节点保存完 RDB ⽂件后,主节点再将缓冲区内的数据补发给从节点,补发的数据仍然按照 rdb 的⼆进制格式追加写⼊到收到的 rdb ⽂件中. 保持主从⼀致性。

7)从节点清空⾃⾝原有旧数据。

8)从节点加载 RDB ⽂件得到与主节点⼀致的数据。

9)如果从节点加载 RDB 完成之后,并且开启了 AOF 持久化功能,它会进⾏ bgrewrite 操作,得到最近的 AOF ⽂件。

 其中 第4步和第5步是非常消耗时间的,其中第4步使用的是bgsave命令,来生成一个rdb文件,而不是aof文件的原因就是,rdb文件是二进制的,因此会比较节省空间。

而且,不能使用旧的rdb文件,而是要重新生成新的rdb文件的原因就是旧的rdb文件可能会和当前最新的数据存在差异。

还有对第6步那里进行一个解释,因为数据量过大时,生成rdb文件这个操作也是比较耗时的,在生成rdb文件期间,仍然可能会收到新的修改操作,这些新的操作也是必须要同步给从节点的,因此在生成rdb文件期间,会把这些新的修改操作保存到一个缓冲区中, 当新的rdb文件生成好并发送给从节点时,接着就会把这个缓冲区里的数据也发送给从节点,以此来保证主从之间的同步。

还有关于第9步,也就是最后一步:当从节点加载完RDB之后,并且开启了AOF,那么就会对当前的aof进行重写,目的就是为了剔除冗余的数据,节省空间。

关于有硬盘复制 vs ⽆硬盘复制(diskless):
默认情况下, 进⾏全量复制需要主节点⽣成 RDB ⽂件到主节点的硬盘中, 再把硬盘上的 RDB
⽂件通过发送给从节点.这是就是有硬盘复制。

Redis 从 2.8.18 版本开始⽀持⽆硬盘复制. 主节点在执⾏ RDB ⽣成流程时, 不会⽣成 RDB ⽂
件到硬盘中了, ⽽是直接把⽣成的 RDB 数据通过⽹络发送给从节点. 这样就节省了⼀系列的写
硬盘和读硬盘的操作开销.

 关于runid和replid

在一个Redis服务器上,runid和replid是都存在的,并且是两个不同的id。

在Redis客户端可以用

info server

 来查看runid

runid是标识了一次redis的运行,和主从复制没有关系,主要是支撑哨兵功能。

而replid就是和主从复制有关的,并且之前也说过 replid和offset共同标识了一个 “数据集合”。

部分复制的流程

  从节点从主节点上进行全量复制数据的开销还是很大的,有时候从节点其实已经有主节点的大部分数据了,此时就不大需要进行全量数据的复制了。

  比如出现网络抖动时,主节点修改数据的操作可能就无法实时复制过来,再严重一点的情况,主节点直接就感知不到从节点了。

  但是网络抖动一般是暂时的,过一会恢复了的时候,从节点就可以重新连接主节点了,此时从节点本来就已经有了主节点的大部分数据了,就一定非要进行全量复制了,可以通过部分复制来节省开销。

部分复制主要是 Redis 针对全量复制的过⾼开销做出的⼀种优化措施,使⽤ psync replicationId
offset 命令实现。当从节点正在复制主节点时,如果出现⽹络闪断或者命令丢失等异常情况时,从节点会向主节点要求补发丢失的命令数据,如果主节点的复制积压缓冲区存在数据则直接发送给从节点,这样就可以保持主从节点复制的⼀致性。补发的这部分数据⼀般远远⼩于全量数据,所以开销很⼩。

大致流程图: 

1)当主从节点之间出现⽹络中断时,如果超过 repl-timeout 时间,主节点会认为从节点故障并终止复制连接。

2)主从连接中断期间主节点依然响应命令,但这些复制命令都因⽹络中断⽆法及时发送给从节点,所以暂时将这些命令滞留在复制积压缓冲区中。

3)当主从节点⽹络恢复后,从节点再次连上主节点。

4)从节点将之前保存的 replicationId 和 复制偏移量作为 psync 的参数发送给主节点,请求进⾏部分复制。

5)主节点接到 psync 请求后,进⾏必要的验证。随后根据 offset 去复制积压缓冲区查找合适的数据,并响应 +CONTINUE 给从节点。

6)主节点将需要从节点同步的数据发送给从节点,最终完成⼀致性。

关于积压缓冲区 

复制积压缓冲区是保存在主节点上的⼀个固定⻓度的队列,默认⼤⼩为 1MB,当主节点有连接的从节点(slave)时被创建,这时主节点(master)响应写命令时,不但会把命令发送给从节点,还会写⼊复制积压缓冲区。

由于缓冲区本质上是先进先出的定⻓队列,所以能实现保存最近已复制数据的功能,⽤于部分复制和复制命令丢失的数据补救。复制缓冲区相关统计信息可以通过主节点的 info replication 中:

127.0.0.1:6379> info replication
# Replication
role:master
...
repl_backlog_active:1 // 开启复制缓冲区
repl_backlog_size:1048576 // 缓冲区最⼤⻓度
repl_backlog_first_byte_offset:7479 // 起始偏移量,计算当前缓冲区可⽤范围
repl_backlog_histlen:1048576 // 已保存数据的有效⻓度

当一个从节点发送psync replicationId offset 命令给主节点时,主节点会先判断replid是否是自己,如果是,那么接着再来看offset,如果这个offset在这个积压缓冲区的范围内,那么就可以进行部分复制,否则其余情况就都是全量复制。

这个积压缓冲区的总量是有限的,会随着时间的推移把旧的数据逐渐删掉。

实时复制的流程

主从节点在建⽴复制连接后,主节点会把⾃⼰收到的 修改操作 , 通过 tcp ⻓连接的⽅式, 源源不断的传输给从节点. 从节点就会根据这些请求来同时修改⾃⾝的数据. 从⽽保持和主节点数据的⼀致性

另外, 这样的⻓连接, 需要通过⼼跳包的⽅式来维护连接状态. (这⾥的⼼跳是指应⽤层⾃⼰实现的⼼跳,⽽不是 TCP ⾃带的⼼跳) :

1)主从节点彼此都有⼼跳检测机制,各⾃模拟成对⽅的客⼾端进⾏通信。
2)主节点默认每隔 10 秒对从节点发送 ping 命令,判断从节点的存活性和连接状态。
3)从节点默认每隔 1 秒向主节点发送 replconf ack {offset} 命令,给主节点上报⾃⾝当前的复制偏移量。
如果主节点发现从节点通信延迟超过 repl-timeout 配置的值(默认 60 秒),则判定从节点下线,断开复制客⼾端连接。从节点恢复连接后,⼼跳机制继续进⾏。

像这些数据比如 10s 1s 60s 都是不用刻意去记忆的,因为这些都是可以在配置文件中修改的。 

主从复制的小结: 

主从复制解决的问题:  
单点问题.
1. 单个 redis 节点, 可⽤性不⾼.
2. 单个 redis 节点, 性能有限.

主从复制的特点:
1. Redis 通过复制功能实现主节点的多个副本。
2. 主节点⽤来写, 从节点⽤来读. 这样做可以降低主节点的访问压⼒.
3. 复制⽀持多种拓扑结构,可以在适当的场景选择合适的拓扑结构。
4. 复制分为全量复制, 部分复制和实时复制。

 5. 主从节点之间通过⼼跳机制保证主从节点通信正常和数据⼀致性。

主从复制配置的过程:
1. 主节点配置不需要改动.
2. 从节点在配置⽂件中加⼊ slaveof 主节点 ip 主节点端⼝ 的形式即可.

主从复制的缺点:
1. 从机多了, 复制数据的延时⾮常明显.
2. 主机挂了, 从机不会升级成主机. 只能通过⼈⼯⼲预的⽅式恢复.

主从复制最大的问题还是在主节点上:
如果主节点挂了,从节点虽然还能提供读操作,但是从节点不能自动的升为主节点(不能直接替换原有主节点的角色),此时是需要程序员 / 运维手动的恢复主节点的(非常繁琐的)。

后面就会讲到哨兵机制,可以自动的对挂了的主节点进行替换。

补充问题

关于从节点何时晋升成主节点的问题 

首先从节点和主节点之间断开存在两种情况:

1.从节点和主节点主动断开连接。比如执行 salveof no one 命令,此时从节点就能晋升为主节点。

2.主节点挂掉了,这个时候从节点不会自动晋升为主节点,而是要通过人工干预的方式才能恢复到原有的主从结构中的主节点角色。

关于redis主节点无法重启的问题 

  刚刚我们是一台主机启动了三个redis-server。当我们把主节点关闭之后(service redis-server stop), 然后再用命令 service redis-server start 的方式启动时,发现启动不了了。

这是因为我们之前对于这三个redis-server,它们的工作目录都是相同的,并且它们默认生成的aof文件名也都是相同的,那么也就是说它们仨共用了一个aof文件,但是对于那两个从节点,是以root的方式启动的,那么创建出来的aof文件的权限自然也就是root的

而我们的主节点通过 service redis-server start这种方式启动的,它其实是通过一个redis这样的用户启动的,而redis-server启动时要加载aof文件是以读写的方式加载的,那么此时redis用户的权限不够,自然就启动不了了。

关于为什么使用redis用户启动而不是root用户?

因为root的方式启动的话,权限太大了,一旦redis被黑客攻破了,那么后果就会很严重(虽然数据被拿到了就已经很严重了)。

 

解决方案:可以把三个redis-server的生成的文件区分开,或者更靠谱一点就是直接把三个redis服务器的工作目录给分开。

解决步骤:

1.停止之前的redis服务器。

2.删除之前工作目录下已经生成的aof文件,或者用chown命令修改aof文件所属的用户:

 3.给从节点创建新的目录,然后修改其配置文件中的dir选项,让其成为工作目录。

4.启动redis服务器。

 

 

;