Redis4.0,增加了新特性PSync 2(部分同步版本2);主要解决Redis运维管理中从实例重启、主实例故障转移等场景导致的全重同步问题。
1什么是Redis部分再同步-psync
Redis部分重新同步:当复制由于某种原因中断后,从库中重新同步redis时,只同步主实例的差异数据(写指令),复制整个RDB文件而不进行bgsave。
本文中的名词约定:
部分再同步:以下简称psync
完全再同步:以下称为完全同步
Redis2.8版本1部分再同步:以下简称psync1
Redis4.0版本2部分再同步:以下简称psync2。
在解释psync2的功能之前,先简单解释一下redis2.8版本发布的psync1。
Redis2.8 psync1解决了什么问题
在psync1函数出现之前,第二级的redis复制中断从实例触发了fullsync。
每次fullsync,集群的性能和资源使用都可能带来抖动;如果redis处于不稳定的网络环境中,fullsync的传出频率可能会很高。为了解决这个问题,redis2.8引入了psync1,有效地解决了这种拷贝闪存的影响。Redis的fullsync对业务的影响相对“沉重”;性能和可用性存在一定的风险。
以下是fullsync的一些常见效果:
Master需要运行bgsave,出现fork(),可能导致master在毫秒或秒内堵塞(latest_fork_usec状态监控);
redis进程分叉导致写时复制内存使用(以下简称COW)的消耗,最多可以导致主进程内存使用的消耗。(在eg日志中输出RDB:写入时复制使用5213兆内存)
redis从负载RDB进程会导致复制线程的客户端输出缓冲区大幅增长;增加主进程的内存消耗;
Redis节省了RDB(不考虑丢失复制),导致服务器磁盘IO和CPU(压缩)资源的消耗
发送几千兆字节的RDB文件将导致服务器网络出口的爆炸。如果使用千兆网卡服务器,会影响正常的服务请求响应时间(以及其他连锁效应)
psync1的基本实现因为psync2是基于psync1的增强实现,所以在介绍psync2之前,先简单分析一下psync1的实现。
为了支持psync1,redis2.8引入了复制积压缓冲区(以下简称复制积压缓冲区);复制积压缓冲区是redis维护的固定长度缓冲区队列(由参数repl-backlog-size设置,默认值为1MB),主机的写命令与从机同步,缓冲区中写入一个副本(主机只有一个积压缓冲区,由所有从机共享)。
redis复制中断时,从机会尝试使用psync来报告当前同步的主机的原始主机runid+offset(复制offset,类似于mysql binlog文件和位置);
如果runid与master的一致,且拷贝偏移量仍在master的拷贝积压缓冲区(即偏移量>:= min(积压值),则master认为部分再同步成功,不会进行完全同步。
部分重新同步成功。主日志显示如下:30422: m04aug 14: 33: 48.505 *从xxxxx: 10005请求同步30422: m04aug 14: 33: 48.506 *接受来自xxx: 10005的部分再同步请求。从偏移量6448313开始发送0字节的积压。
redis2.8的部分同步机制有效解决了网络环境不稳定和redis执行高时间复杂度命令导致的复制中断,导致完全同步。然而,在处理从机重启和主机故障转移的情况时,psync1仍然需要完全同步。
psync1的缺点
从上面可以看出,psync1需要同时满足两个条件才能成功:主运行id不变,复制偏移量在主复制产品缓冲区中。
然后,当Redissslave重新启动时,主runid和副本偏移量将丢失,因此需要完全重新同步;redis主机故障转移,因为主机runid已更改;故障切换后,新的从机需要完全重新同步。而从维护重启和主故障转移是redis运维的常见场景,针对redis的psync1无法解决这两种场景的部分再同步成功问题。
所以redis4.0 -psync2的增强型部分再同步功能主要解决这两种场景的部分再同步。
2 psync2实现简介
在redis集群的实际生产操作中,实例的维护重启和主实例的故障转移(如集群故障转移)是常见的操作(如实例升级、重命名命令和释放实例内存碎片等)。).在redis 4.0版本之前,这种维护处理,Redis会有完全的重新同步,导致对性能敏感服务的少量破坏。
如前所述,psync2主要使redis能够在从属实例重启和主实例故障转移的情况下使用部分重新同步。在本节中,将简要描述psync2在这两种情况下的逻辑实现。
名词解释:
Master_replid:重复ID1(以下简称replid1),长度为41字节(40个随机字符串+'0 ')的字符串。还有所有redis实例,都和runid没有直接关系,但是都是getRandomHexChars函数生成的,和runid生成规则一样。当一个实例成为从实例时,它自己的replid1将被主实例的replid1覆盖。
Master_replID2:复制ID2(以下称为replID2),默认初始化为全零,用于存储最后一个主实例的replid1
可以通过信息复制查看实例的replid信息;例子如下:
127.0.0.1:6385 >信息复制# Replicationrole:slave master _ host:xxxx//IP模糊处理master _ port:6382 master _ link _ status:up slave _ repl _ offset:119750 master _ repl id:Fe 093 add 4 ab 71544 ce 6508 d2e 0 B1 FD 0 b 7d 7 D1 C5 b 2//这是主实例的相同master _ repl id 1:00000000000000000000000000000000000000000000000000000000000000000
在以前的版本中,redis重启后,复制信息完全丢失;因此,从实例重新启动后,只能执行完全重新同步。
Redis4.0重启后仍然可以进行部分再同步,主要做以下三点:
当redis关闭时,复制信息作为辅助字段存储在RDB文件中。从而实现同步信息持久性;
当redis开始加载RDB文件时,它会将复制信息分配给相关字段;
redis再同步时,会上报repl-id和repl-offset的同步信息。如果它与主实例匹配,并且偏移量仍在主实例的复制积压缓冲区中,则只会执行部分重新同步。
接下来,我们详细分析每个步骤的简单实现
当redis关闭时,持续将信息复制到RDB
redis关闭时,关闭保存将调用rdbSaveInfoAuxFields函数。
将当前实例的复制标识和复制偏移量保存到RDB文件中。
注意:当前RDB存储的数据内容和复制信息是一致的。熟悉MySQL的同学可以认为MySQL中的备份总数与binlog信息一致。
rdbSaveInfoAuxFields函数在rdb.c源文件中实现,省略的代码如下:
/*保存几个默认的辅助字段,其中包含RDB生成的信息。*/int rdbSaveInfoAuxFields(Rio * rdb,int flags,rdbSaveInfo *rsi) { /*添加几个关于RDB创建时的状态的字段。*/if(rdbsaveauxfieldstr(RDB," redis-ver ",REDIS _ VERSION)= =-1)return-1;//将实例的repl-id和repl-offset作为辅助字段存储在RDB if (rdbsaveauxfieldstr (RDB,“repl-id”,服务器。replid = =-1)return-1;if (rdbSaveAuxFieldStrInt(rdb," repl-offset ",server . master _ repl _ offset)= =-1)return-1;返回1;}
对于生成的RDB文件,可以通过redis-check-RDB工具查看辅助字段信息。
复制器信息的两个字段与信息中的字段相同;
$shell>。/src/redis-check-RDB dump . RDB[offset 0]Checking RDB文件dump . RDB[offset 26]AUX FIELD redis-ver = ' 4 . 0 . 1 '[offset 133]AUX FIELD repl-id = ' 44873 f 839 AE 3a 57572920 cdaf 70399672 b 842691 '[offset 148]AUX FIELD repl-offset = ' 0 '[offset 167]o/RDB看起来还可以!0/[信息] 1密钥读取[信息] 0过期[信息] 0已经过期redis开始读取RDB的复制信息
redis实例开始读取RDB文件,这是通过rdb.c文件中的rdbLoadRio()函数实现的。
redis加载RDB文件时,会处理文件中辅助字段的信息,并将repl_id和repl_offset加载到实例中,并分别赋予master_replid和master_repl_offset两个变量值。
下面的代码从RDB文件中读取两个辅助字段值。
Int rdbloadrio (Rio * RDB,rdbsaveinfo * RSI){-省略-else if(!strcasecmp(auxkey->;Ptr," repl-id")) {//读取的aux字段为repl-id if(RSI & amp:& amp;sdslen(auxval->;ptr = = CONFIG _ RUN _ ID _ SIZE){ memcpy(RSI-& gt;repl_id,auxval->ptr,CONFIG _ RUN _ ID _ SIZE+1);rsi->。repl _ id _ is _ set = 1;} } else if(!strcasecmp(auxkey->;ptr," repl-offset")) { if (rsi) rsi->。repl_offset = strtoll(auxval->;ptr,NULL,10);} else { /*我们忽略我们不理解的字段,如AUX字段*合同。*/服务器日志(LL_DEBUG),"无法识别的RDB辅助字段:“%s”,(char *)AUX key->;ptr);}} redis试图从实例进行部分重新同步
重新启动redis实例后,从RDB文件加载Master_replid和master _ repl _ offset(注意:这里不讨论AOF和RDB加载优先级);等效于实例的server.cached_master。当我们将其视为一个实例的从库(包括被动集群从或主动执行slaveof指令)时,该实例向主实例报告master_replid和master _ repl _ offset+1;当实例同时满足以下两个条件时,部分重新同步是可能的:
1.来自实例的report master_replid字符串,它等于主实例的master_replid1或replid2
2.从实例报告的master_repl_offset+1字节仍然存在于主实例的复制积压缓冲区中
请尝试从实例(在replication.c文件中)部分重新同步slavertrypartialresynchronization函数;
主实例判断是否可以执行部分再同步功能MasterTrypArtialSynchronization(在replication.c文件中)。
redis重新启动时,临时调整主实例的复制积压缓冲区大小
redis的复制积压缓冲区由参数repl-backlog-size设置,为1MB默认情况下。为了确保从实例重新启动后的部分重新同步,应该设置一个合理的repl-backlog-size值。
1计算合理的复件积压量值
根据以主库每秒为增量的主复制偏移量master _ repl _ offset(由信息复制指令获得),
如果偏移量每秒增加5MB,主实例副本的backlog缓冲区应该保留最后60秒写入的内容,backlog_size设置应该大于300MB(60*5)。但是,从实例中重新启动和加载RDB文件是一个耗时的过程。如果重启某个实例需要120秒(RDB大小与CPU配置有关),那么主实例的backlog_size必须设置为至少600MB。
计算公式:backlog_size =重启从实例时间*主实例偏移量每秒写入量
2在重新启动从属实例之前,调整主实例的动态调整repl-backlog-size值。
因为当redis的repl-backlog-size通过配置集动态调整时,redis会释放当前的backlog缓冲区,并重新分配一个指定大小的缓冲区。因此,在重新启动从属实例之前,我们必须调整主实例的repl-backlog-size。
调整backlog_size处理函数resizeReplicationBacklog,代码逻辑如下:
void ResizeReplicationbacklog(long long newsize){ if(newsize & lt;config _ repl _ backlog _ min _ size)//如果设置的新值小于16 KB,则修改为16KB新大小= config _ repl _ backlog _ min _ sizeif(server . repl _ backlog _ size = = newsize)return;//如果新值与原始值相同,则不做任何处理返回。server . repl _ backlog _ size = newsize;//如果(server.repl_backlog!= NULL) {//当backlog的内容不是空时,释放当前backlog。并根据新的值/*分配一个新的积压,我们实际上要做的是刷新旧的缓冲区,并重新锁定一个新的*空缓冲区。它将随着新数据的增加而增加。*原因是复制几千兆字节会增加延迟,更糟糕的是,我们经常需要在释放旧缓冲区之前分配额外的空间。*/zfree(server . repl _ backlog);server . repl _ backlog = zmalloc(server . repl _ backlog _ size);server . repl _ backlog _ hist len = 0;//将backlog内容的长度和第一个字节的偏移量修改为0 server.repl _ backlog _ idx = 0/*我们的下一个字节是...因为缓冲区是空的。*/server . repl _ backlog _ off = server . master _ repl _ offset+1;}} 3 psync2实现Redis集群故障转移的部分新同步
为了解决主实例故障转移后重新同步新主实例数据时使用psync的问题,fullsync改为使用。
1 redis4.0使用两组replid和offset来替换原来的master runid和offset。
2 Redissslave默认开启复印积压缓冲功能;以便其他后向从设备在从设备故障转移后更改主设备时,可以从缓冲区获取写指令。
第一组:master_replid和master_repl_offset
如果redis是主实例,则表示为自己的replid和复制偏移量;如果redis是一个从实例,它被表示为自己的主实例的replid1和同步主实例的复制偏移量。
第二组:master_replid2和second_repl_offset
无论主实例和从实例是什么,它们都代表它们的最后一个主实例repid1和复制偏移量;用于同级实例或级联复制、主库故障转移psync。
初始化时,前者为40个字符,长度为0,后者为-1;只有当主实例进行故障转移时,redis才会将其replid1和master_repl_offset+1分别分配给master_replid2和second_repl_offset。
这个切换逻辑在函数shiftReplicationId中实现。
void shift ReplicationId(void){ memcpy(server.replid 2,server . replid,sizeof(server . replid));//replid分配给replid2/*我们将第二个replid偏移量设置为主偏移量+1。由于*从机将请求尚未收到的第一个字节,因此*我们需要向偏移量添加一个字节:例如,如果作为从机,我们*确定我们与主机有相同的50个字节的历史,在我们*变成主机后,我们可以接受偏移量为* 51的PSYNC请求,因为从机请求直到第50个*字节都有相同的历史,并且正在请求从偏移量51开始的新字节。*/server . second _ replid _ offset = server . master _ repl _ offset+1;changeReplicationId();服务器日志(LL_WARNING),"将辅助复制标识设置为%s,在偏移量%lld之前有效。新复制ID为%s ",server.replid2,server.second_replid_offset,server . replid);}
通过这种方式,主库可以进行故障转移,以下三种常见结构可以执行psync:
1一主一从开关,a->: B开关变成B->:A;
2一主多从切换时,兄弟节点变成父子节点;
3级复制被切换,a->:B->;C开关变成b->:C->;A
主实例判断psync的逻辑功能是否可以在mastertrypartiresynchronization()中执行
int master try manual synchronization(client * c){//如果从机提供的master_replid与主机的replid不同,与主机的replid2不同,或者同步速度比主机快;full sync . if(str escmp(master _ replid,server . replid)&:& amp;(str secmp(master _ replid,server . replid 2)| | psync _ offset & gt;server . second _ replid _ offset)){/* Run id "?"由想要强制完全重新同步的从机使用。*/ if (master_replid[0]!= '?'){ if(str cascmp(master _ replid,server . replid)& amp;& ampstr secmp(master_replid,server.replid 2)){ Serverlog(LL _ NOTICE,"不接受部分重新同步:" "复制id不匹配(从机要求' %s ',我的" "复制id为' %s '和' %s ')",master _ replid,server . replid,server . replid 2);} else { serverLog(LL_NOTICE,“不接受部分重新同步:“”第二个ID的请求偏移量为%lld,但我可以回复“”,最多%lld”,psync_offset,server . second _ replid _ offset);} } else { serverLog(LL_NOTICE,“从%s请求完全重新同步”,replicationGetslavename(c));} goto need _ full _ resync} /*我们还有奴隶要的数据?*/ if(!server . repl _ backlog | | psync _ offset & lt;server . repl _ backlog _ off | | psync _ offset & gt;(server . repl _ backlog _ off+server . repl _ backlog _ histlen)){ Serverlog(LL _ NOTICE,“由于缺少backlog,无法与从属%s部分重新同步(从属请求为:%lld)。”,replicationGetSlaveName(c),psync _ offset);if (psync_offset >;server . master _ repl _ offset){ ServerLog(LL _ WARNING),"警告:从%s试图使用大于主复制偏移量的偏移量进行PSYNC。",replicationGetSlaveName(c));} goto need _ full _ resync}
-结束-
推荐订阅原作者微信官方账号DBACoder
▼
1.《repl Redis4.0新特性-PSYNC2》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《repl Redis4.0新特性-PSYNC2》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/yule/1444782.html