草庐IT

Redis主从复制搭建及原理

<h1>Dvomu 2023-03-28 原文

1 简介

1.1 Redis在单机、单节点、单实例下存在的问题

  • 单机故障
  • 内存容量有限
  • 访问压力
    Redis主从架构主要解决的问题:单机故障和访问压力,通过主从架构可以将访问流量分摊到多台服务器上,加上哨兵机制实现主从架构的高可用。主从架构将主节点数据复制到从节点即数据的冗余备份原理,所以不能解决内存容量的问题,内存容量可以通过Redis集群解决

1.2 主从架构概述

主从复制,是指将一台 Redis 服务器的数据(master 主节点),复制到其他的 Redis 服务器( slave 从节点),数据的复制是单向的,只能由主节点到从节点
默认情况下,每台 redis 服务器都是主节点,且一个主节点可以有多个从节点,但一个从节点只能有一个主节点。

1.3 主从复制作用

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式;
  2. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
  3. 负载均衡:在主从复制的基础上,配合读写分离,写数据时应用连接主节点,读数据时应用连接从节点,分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高 Redis 服务器的并发量。
  4. 高可用:主从复制是哨兵模式和集群能够实施的基础。

2 主从复制实现

2.1 环境准备

  1. CentOS7 3台
  • 192.168.88.110 端口6379 主机名q110(下面统一简称为主机名)
  • 192.168.88.111 端口6379 主机名q111(下面统一简称为主机名)
  • 192.168.88.112 端口6379 主机名q112(下面统一简称为主机名)
  1. Redis-6.2.6

2.2 网络拓扑图

2.3 Redis安装

3台服务器安装Redis6.2.6
官网
中文
Redis安装包解压完成后,详细安装目录可以查看README.md

# 1. 安装gcc
yum install gcc
# 2. 下载安装包
wget https://download.redis.io/releases/redis-6.2.6.tar.gz
tar -xf redis-6.2.6.tar.gz
# 3. 编译 
cd redis-6.2.6 && make
cd src && make install PREFIX=/usr/local/redis
# 4. 配置环境变量
vi /etc/profile
# 添加环境变量
export REDIS_HOME=/usr/local/redis
export PATH=$PATH:$REDIS_HOME/bin

source /etc/profile
# 5. 根据Redis官方提供的脚本一键安装
cd ../../redis-6.2.6/utils
./install_server.sh

运行第5步报错,错误信息如下:

注释掉install_server.sh 中报错部分后重新执行install_server.sh

# 可执行多次。物理机中可以有多个Redis实例,通过port区分
./install_server.sh
# 根据提示内容按需修改,不修改直接回车确认

执行完成后redis实例默认已启动,配置及日志文件路径上图也有输出。

2.4 配置主从复制

从节点开启主从配置,有 3 种方式:

  1. 配置文件:在配置文件加入 slave < masterip > < masterport >
  2. 启动命令:redis-server 启动命令后加入 --slave < masterip > < masterport >
  3. 客户端命令:Redis 服务器启动后,直接通过客户端执行命令:slave < masterip > < masterport >

本文通过常用的配置文件加入。
Master节点修改配置:
修改/etc/redis/6379.conf 中的bind,默认只能本机访问,需添加2台salve 节点ip。
Slave节点修改:
修改q111和q112两台都需要修改

  1. 修改/etc/redis/6379.conf 中的bind,默认只能本机访问,需添加2台salve 节点ip。
    bind 192.168.88.100
  2. 配置主节点IP和端口
vi /etc/redis/6379.conf 
# 修改,如果主节点存在用户名和密码也需要配置上
replicaof 192.168.88.110 6379

重启3台从redis服务

systemctl restart redis_6379

验证
通过查看Master Redis日志可以看到已经将RDB同步到磁盘并发送给Salve节点。

查看Slave Redis日志可以看到已经从Master节点将RDB同步到磁盘

注意: Slave在同步Master节点RDB数据时会先删除自己的RDB文件。
通过客户端连接后在Master节点set 一个key 后 在Slave节点get 该key可以看到数据已经同步。
q112

q111

至此,主从复制已配置完毕。目前主从配置存在疑问:
当Slave down掉后,重新启动。是从Master从新拉全全量RDB还是增量数据?

3 主从数据同步原理

3.1 测试数据同步

3.1.1 Slave与Master首次建立主从

首次建立主从会全量同步。可查看2.4日志。

3.1.2 建立主从后Slave断开一段时间后重新启动

从上面2.4测试可以看到当Slave跟随Master时,会将RDB文件同步并清除自己所有数据,重新加载Master的RDB数据。我们可以在Slave已经与Master建立好主从关系后(数据已经同步)再将Slave关掉,在Slave关闭期间,往Master中添加数据,然后再重新启动Slave。
查看Slave启动日志:

查看RDB源文件

从Slave RDB源文件中可以看到,已经有保存repla-id 即MasterID。从日志中可以看到,当Slave在与Master已经建立主从关系情况下,宕机重启后并不会将Master数据全量同步,这与首次建立主从关系有不同。

3.1.3 测试AOF开启

上面的测试3.1.2 可以看出可能是因为RDB中已经存在了Master repla-id所有再次启动时不会全量同步。我们再测下将Slave开启AOF开启后,同样在关闭Slave期间Master增加数据后再启动Slave。

  1. 主机q112中执行
# 关闭Redis
systemctl stop redis_6379
# 修改配置
vi /etc/redis/6379.conf
# 开启AOF,配置文件中修改以下内容
appendonly yes

  1. Master中增加一些数据
  2. 重启Slave查看启动日志

通过日志中可以看在,在已经与Master建立了主从情况下并且开启AOF后,重新启动也会将全量数据同步到本地及内存。为了确实是否是第一次开启AOF才会全量同步,可以将此2.5.2实验再重试一次,得到的结论是一样的即:Redis主从中Slave开启AOF后每次重启会加载Master全量数据。里面具体原理是什么呢?

3.2 主从复制原理

3.2.1 数据同步原理

从节点通过发送psync命令请求同步数据,此时根据主从节点当前状态的不同,同步方式可能是全量复制或部分复制。

  • 全量复制:用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作。如果从节点开启了AOF,则会触发bgrewriteaof的执行,从而保证AOF文件更新至主节点的最新状态;
  • 部分复制:用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效。需要注意的是,如果网络中断时间过长,导致主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制。

Redis使用一对Replicaion ID, offset来唯一识别Master节点数据集的版本,要理解这个“版本“的概念需要认识Redis的以下三个概念:

  • Replication ID(复制ID):每个Redis的主节点都用一个随机生成的字符串来表示在某一时刻其内部存储数据的状态,“某一时刻”可以理解为其成为master角色的那一刻,由源码可知在第一个从节点加入时,Redis初始化了复制ID。
  • offset(复制偏移量):主从模式下,主节点会持续不断的向从节点传播引起数据集更改的命令,offset所表示的是主节点向从节点传递命令字节总数。它不是孤立存在的,需要配合复制积压缓冲区才能工作。
  • backlog(复制积压缓冲区):它是一个环形缓冲区,用来存储主节点向从节点传递的命令,它的大小是固定的,可存储的命令有限,超出部分将会被删除。它即可用于部分同步,也可用于命令传播阶段的命令重推。

上图说明:

  • 图示Redis角色为Master,其复制ID(replid)为xxxx,当前的复制偏移量(offset)为1010;
  • 它有一个复制积压缓冲区(backlog),容量(backlog_size)为100,backlog起点相对于offset的偏移量(backlog_off)为1000,当前backlog存储的命令字节数(backlog_hislen)为11个,对应了backlog中【1000,1010】偏移量范围内的字节;
  • offset始终与backlog中最后一个字节的偏移量相同。
    上图分析:
  • Redis-1:replid和offset为默认值,说明它从未与主节点进行过同步操作,所以是进行全量同步;
  • Redis-2:replid主从节点一致,slave_offset>=backlog_off并且slave_offset<offset,说明该从节点丢失的数据可以通过复制积压缓冲区找回,所以可以进行部分同步;
  • Redis-3:replid主从节点一致,slave_offset<backlog_off,说明该节点丢失的数据过多,通过复制积压缓冲区无法找回,所以是进行全量同步;
  • Redis-4:replid主从节点一致,之前不是与当前节点进行主从复制,所以是进行全量同步;
    总结:部分同步其实是以全量同步为基础(得到复制ID),用复制积压缓冲区中的缓存命令做命令重放的增量同步逻辑,不过受制于复制积压缓冲区的容量,它可容忍的范围是有限的。这与持久化机制的AOF混合持久化如出一辙,也与mysql中主从复制的Binlog思路不谋而合。

3.2.2 命令的执行

Master-Slave建立主从关系后,当Master执行完写操作命令后,会通过psync把写命令追加至复制积压缓冲区,然后异步地发送给slave。slave接收命令并执行,同时更新slave维护的复制偏移量offset。

复制积压缓冲区是由主节点维护的、固定长度的、先进先出(FIFO)队列,默认大小1MB(通过配置repl-backlog-size)。例如如果网络中断的平均时间是60s,而主节点平均每秒产生的写命令(特定协议格式)所占的字节数为100KB,则复制积压缓冲区的平均需求为6MB,保险起见,可以设置为12MB,来保证绝大多数断线情况都可以使用部分复制。
从节点将offset发送给主节点后,主节点根据offset和缓冲区大小决定能否执行部分复制:

  • 如果offset偏移量之后的数据,仍然都在复制积压缓冲区里,则执行部分复制;
  • 如果offset偏移量之后的数据已不在复制积压缓冲区中(数据已被挤出),则执行全量复制。

有关Redis主从复制搭建及原理的更多相关文章

  1. ruby - 如何在 ruby​​ 中复制目录结构,不包括某些文件扩展名 - 2

    我想编写一个ruby​​脚本来递归复制目录结构,但排除某些文件类型。因此,给定以下目录结构:folder1folder2file1.txtfile2.txtfile3.csfile4.htmlfolder2folder3file4.dll我想复制这个结构,但不包含.txt和.cs文件。因此,生成的目录结构应如下所示:folder1folder2file4.htmlfolder2folder3file4.dll 最佳答案 您可以使用查找模块。这是一个代码片段:require"find"ignored_extensions=[".cs"

  2. ruby - 在两个 ActiveRecord 类之间合并/复制属性的好方法? - 2

    之前有人问过这个问题,我发现了以下clip关于如何一次设置一个类对象的所有属性,但由于批量分配保护,这在Rails中是不可能的。(例如,您不能Object.attributes={})有没有一种很好的方法可以将一个类的属性合并到另一个类中?object1.attributes=object2.attributes.inject({}){|h,(k,v)|h[k]=vifObjectModel.column_names.include?(k);h}谢谢。 最佳答案 利用assign_attributes使用:without_prote

  3. Ruby:我怎样才能复制这个数组? - 2

    (跟进我之前的问题,Ruby:howcanIcopyavariablewithoutpointingtothesameobject?)我正在编写一个简单的Ruby程序来在.svg文件中进行一些替换。第一步是从文件中提取信息并将其放入数组中。为了避免每次调用此函数时都从磁盘读取文件,我尝试使用memoize设计模式-在第一次调用后的每次调用中都使用缓存结果。为此,我使用了一个在函数之前定义的全局变量。但是,即使我在返回局部变量之前将该变量.dup为局部变量,调用该变量的函数仍在修改全局变量。这是我的实际代码:#memoizetokeepfromhavingtoreadoriginalfi

  4. 【Unity游戏破解】外挂原理分析 - 2

    文章目录认识unity打包目录结构游戏逆向流程Unity游戏攻击面可被攻击原因mono的打包建议方案锁血飞天无限金币攻击力翻倍以上统称内存挂透视自瞄压枪瞬移内购破解Unity游戏防御开发时注意数据安全接入第三方反作弊系统外挂检测思路狠人自爆实战查看目录结构用il2cppdumper例子2-森林whoishe后记认识unity打包目录结构dll一般很大,因为里面是所有的游戏功能编译成的二进制码游戏逆向流程开发人员代码被编译打包到GameAssembly.dll中使用il2ppDumper工具,并借助游戏名_Data\il2cpp_data\Metadata\global-metadata.dat

  5. ruby - 如何将文件从一台服务器复制到另一台服务器? - 2

    我有一台服务器,其中只有xls日志文件。每个文件为5-15Mb,并且在文件随时添加的意义上是动态的。现在我需要一种使用Ruby执行以下过程的方法。通过将文件名从一台只有日志文件的服务器发送到另一台服务器来复制文件。我需要将服务器密码作为参数传递。一切都在后台发生,由Ruby脚本触发。 最佳答案 查看Net::SCP和Net::SSHgem。第一个让您使用安全副本检索文件,第二个让您轻松找到可用于检索的文件的名称。在Net::SSH中,ssh.exec!将成为您的friend。来自Net::SCP文档:Net::SCPimplemen

  6. ruby - 复制.blank?在标准的 Ruby 中 - 2

    Rails有一个.blank?如果对象为空,将返回true的方法?还是零?可以找到实际代码here.当我尝试在1.9.2上复制它时:classObjectdefblank?respond_to?(:empty?)?empty?:!selfendend调用“.blank”?返回true但调用"".blank?根据railsdocumentation返回false对于.blank,空白字符串应该eval为true?在查看我最初编写的代码之前:classObjectdefblank?!!self.empty?||!!self.nil?endend结果相同。我错过了什么?

  7. Slowloris DoS攻击的原理与简单实现 - 2

    前言    Slowloris攻击是我在李华峰老师的书——《MetasploitWeb 渗透测试实战》里面看的,感觉既简单又使用,现在这种攻击是很容易被防护的啦。不过我也不敢真刀实战的去试,只是拿个靶机玩玩罢了。         废话还是写在结语里面吧。(划掉)结语可以不看(划掉)Slowloris攻击的原理        Slowloris是一种资源消耗类DoS攻击,它利用部分HTTP请求进行操作。也叫做慢速攻击,这里的慢速并不是说发动攻击慢,而是访问一条链接的速度慢。Slowloris攻击的功能是打开与目标Web服务器的连接,然后尽可能长时间的保持这些连接打开。如果由多台电脑同时发起Slo

  8. ruby-on-rails - 无法使用 Assets :precompile 在 rails rake 上复制 NilClass - 2

    当我尝试预编译我的Assets时,出现以下错误。这是具有完整输出的跟踪:RAILS_ENV=productionbundleexecrakeassets:precompile--trace**Invokeassets:precompile(first_time)**Invokeassets:environment(first_time)**Executeassets:environment**Invokeenvironment(first_time)**Executeenvironmentrakeaborted!TypeError:can'tdupNilClass/var/lib/ge

  9. ruby-on-rails - 在对象空间 object_id 中复制类 - 2

    我遇到一个奇怪的问题,我正在使用的Rails引擎中的某些模型在对象空间中被复制。(rdb:1)ObjectSpace.each_object(::Class).each{|klass|putsklass.to_s+":"+klass.object_id.to_sifklass.to_s.eql?("DynamicFieldsets::Field")}DynamicFieldsets::Field:66866100DynamicFieldsets::Field:718363802479发生这种情况时,我不能使用is_a?或相等性检查以测试对象是否是Field类的实例。这个问题只发生在开发

  10. ruby - 使用 rake 复制保留目录结构的文件 - 2

    我的目标是将一组由模式指定的文件复制到目标目录。源目录中的文件可以有子目录。我试过:cp_r(Dir.glob('**/*.html'),@target_dir):和cp_r(FileList['**/*.html'],@target_dir):但都不起作用。它只在我做类似的事情时有效:cp_r(Dir['.'],@target_dir):但我只需要复制*.html文件而不是其他任何文件。我需要什么cp--parents命令确实对使用现有的Ruby/Rake方法有什么建议吗?更新看起来用Ant更容易做的事情,用Ruby/Rake堆栈是不可能的-可能我需要研究其他东西。我不想编写自定义代

随机推荐