当前位置: 首页 > 科技观察

给我10分钟,我可以无感知的处理Redis集群扩缩容_0

时间:2023-03-19 21:13:07 科技观察

1.背景携程的Redis集群规模和数据规模这几年增长很快。我们通过容器化解决了Redis集群的快速部署问题,并根据业务实际情况做了二次调度、自动漂移等一系列尝试,保证了宿主机在案例中的可靠性内存超标。在扩容和缩容方面,我们主要通过纵向扩容和缩容来解决Redis集群容量的问题,但是随着集群规模的扩大,这种方式逐渐遇到了瓶颈。一方面,如果单个Redis实例过大,会带来更大的风险和运维困难;另一方面,宿主机的容量是有上限的,不能无限扩展。考虑到运维便利性和资源利用率的平衡,我们希望单个Redis实例的上限为15GB。但在实际运行中很难做到:一些业务发展迅速,经常需要对Redis进行扩容,导致单实例规模远超15GB;浪费。如何有效控制Redis实例的大小?接下来,本文将考虑到这个问题,逐步讲解携程Redis治理和扩展的演进过程。2、Redis水平扩展和拆分携程一直在使用Redis,一直只是垂直扩展和收缩。原因有二:一是一开始业务规模比较小,纵向扩缩能满足需求。对于Redis来说,垂直伸缩只是Maxmemory的配置变化,对业务是透明的。其次,实现水平拆分/缩放困难且成本高。在上一篇《携程Redis治理演进之路》中提到,携程使用自研的CRedis来访问所有的Redis集群,部署在应用端的CRedis使用一致性哈希来访问真正承载数据的Redis实例。然而,一致性哈希不支持直接水平缩放。因为无论是增加一个节点还是删除一个节点,都会导致整个哈希环的调整。如图1所示,假设原来有4个分片(图1)。增加一个节点的时候,会导致key的某部分写到nodeC,但是现在要写到nodeE,也就是不能命中上一个节点。从客户的角度来看,密钥似乎丢失了。更改的节点越多,丢失的密钥就越多。假设一个集群直接从10个分片增加到20个分片,会直接导致50%的key丢失。删除节点也是如此,这里不再赘述。因此,虽然一致性哈希是一个比较简单优秀的集群方案,但是无法直接横向扩展一直困扰着运维和架构团队。为此,CRedis团队在2019年提出了横向拆分的方案。CRedis水平拆分的思路比较简单,因为在consistenthash的同一水平位置添加节点会造成数据丢失,所以不要改变原有level节点的hash规则,以某一个节点为起点的散列,再进行一致性散列,演化成树状结构(图2)。如上图2所示,树形结构从一级扩展到二级。如果继续拆分新的叶子组,可以将树结构扩展到第三级,拆分方案最多可以支持十级。叶子组是物理分片,直接对应Redis实例,分支组是虚拟分片。当Hash命中分支组时,找不到对应的Redis实例。您需要继续搜索,直到找到叶组。.图3CRedis水平拆分上线后,DBA会将现有的超过15G的大部分实例拆分成更小的实例,在一段时间内缓解大内存实例的运维管理压力。然而,随着Redis规模的快速增长,大型实例集群不断出现。另外,CRedis水平拆分的缺点也逐渐暴露出来:连续周期很长。如果拆分多个组,每个组的数据需要同时对同一个实例做多份拷贝。比如一个60G的实例(图3),如果要拆分成一个5G的实例,那么下层的Groups就必须有12个。拆分,首先要将实例的数据同步到12个60G的实例中,然后使用keyhit规则清除12个60G实例中不会被命中的key,最终演变成12个5G的实例。一般60G组实例拆分需要3小时到6小时。如果一个集群有很多分片,加上观察对业务影响的时间,可能会持续几天或者一两周,只有人才能看串行操作。拆分过程中需要进行两次迁移。如前所述,分裂时中间状态实例的内存需求非常大。拆分完成后,内存需求会急剧下降,所以每次拆分涉及两次迁移,迁移虽然不会影响业务,但是对于进行操作拆分的运维人员来说,精神负担比较大,如果他们不小心,也会造成上网事故。分裂后无法恢复。也就是说,如果拆分后业务收缩,对Redis的需求会变小,但实际拆分的碎片还在,申请的空间还没有释放。客观上造成了资源浪费,降低了Redis的整体利用率。只支持扩容,不支持缩容。这一点上面也有提到。除了一些集群过大需要拆分之外,还有一些实例的应用远远超过需求,需要进行缩减。水平拆分对此无能为力。拆分一次意味着多一次性能损失,因为需要多计算一次hash,虽然不耗时,但对性能敏感的业务还是有影响的。可以看出,水平拆分的方案虽然解决了实例过大的问题,但是无法收缩的弊端也逐渐显现出来。尤其是在今年受疫情影响需要降本增效的背景下,一方面资源配比充足,另一方面宿主机上运行的所有实例无法扩容向下。那么有更好的解决方案吗?答案是肯定的。三、Redis横向扩缩容1、设计思路图4既然分片很难减少,我们首先想到的是业务双写集群的方式,即业务双写两个新旧集群在同时,新旧集群的分片数量不同,大小配置也不同。比如之前申请了4个shard,现在发现资源过剩,让业务创新申请一个新的2个shard的集群,由业务来控制灰度写入哪个集群(图4)。最终会迁移到一个新的集群中,新集群的大小满足当前业务需求,从而达到缩容的目的。双写集群方案虽然解决了我们的一些问题,但是对业务的侵入还是比较深的。另外因为双写集群引入了业务配合和观察的时间,所以整体流程比较长。所以,我们需要找到更好的解决方案。既然业务双写集群可以满足要求,那如果基础设施代替业务来完成这部分不是更好吗?借鉴业务双写集群的思想和云原生不可变基础设施的理念,我们首先想到的是用新集群替换旧集群,而不是就地修改集群;另外,为了节省Redis在公有云上的成本,我们积累了kvrocks的实践经验,结合两者,设计了高效的横向扩缩容方案。该方案的核心是引入一个基于kvrocks变换的中间态binlogserver,它既是旧集群的slave节点,又是新集群的client。一方面,它会从RedisMaster复制全量和增量数据;另一方面,它会作为客户端,根据新集群一致的HASH规则,将复制的数据写入新集群。大致步骤如下,具体步骤过程可参考下图(图5)。根据当前V1集群的分片启动相应数量的binlogservers,获取V2集群一致的HASH规则和分组。每个binlogserver在V1集群的单个片段中成为Master的Slave。执行完salveof后,将V1中Master传过来的RDB文件保存起来分析。对于每一个RDB文件,解析还原成一条Redis命令,按照CRedis一致的哈希规则写入。进入V2,后续V1集群传来的命令也同步到V2。当这个过程完成,binlog追的差不多了,为了数据的一致性,可以停止写V1(客户端报错),然后CRedis推送V2的配置,或者直接推送V2的配置(客户端不报错但是数据可能丢失或不一致),APP端会依次切换到V2;这个过程对用户是完全透明的,应用端不需要做任何操作。图5通过Redis的横向扩展解决方案,我们解决了之前的几个痛点:持续时间大大缩短,基本上和V1集群最大实例的大小正相关,因为是并发执行的,与簇碎片的数量。根据实际运维数据,集群单实例20G,10分钟内即可完成集群的扩缩容,10G以下5分钟即可完成,大大缩短了扩容周期扩缩容,业务是在没有任何感知的情况下完成扩容和缩容的。由于集群可以秒级切换,即使扩缩容后业务受到影响,也可以快速回滚,因为回滚只是改变了集群的路由点。扩缩容过程只需要切换一次集群方向,0次迁移。没有中间状态,不需要使用大内存主机来实现分裂。对于扩容后的集群,再次缩容恢复是很方便的,缩容也是一样的。对于那些已经水平分裂的集群,也可以通过这种方式恢复。可扩容或缩容,甚至不扩容或不缩容,按集群迁移,如本文《携程Cilium+BGP云原生网络实践》中提到的云原生网络安全管控试点项目。因为原来Redis集群下的实例可能同时部署在openstack网络和cilium网络上,但是云原生安全只能控制cilium网络下的实例。此时需要迁移Redis实例。如果按照之前的运维方式,需要分片迁移组,整个项目可能会持续很长时间,消耗更多的人力。但是水平扩展可以一次快速地将一个集群迁移到cilium网络,节省时间。省力。缩放后没有性能损失。2、运维数据横向扩缩容计划上线4个月以来,已成功完成200多次扩缩容。今年,某业务的请求量骤增十几倍,相关集群进行了多次扩容,每次扩容大多在10分钟内完成,有效支撑了业务发展。另一方面,对于应用分片数量较多,但实际使用量非常少的集群,我们也利用水平伸缩的能力,快速减少分片数量和应用量。通过这些减少,有效提高了整体资源利用率。3.一些坑(1)单个key太大,key被驱逐。在实际的横向扩缩容过程中,我们发现有的集群在单实例中可能存在巨大的key(大于3G)。由于V2集群的大小是根据V1大小的平均值实时计算的。一旦V1中的实例过大,写入V2的实例大小可能会大于预期的平均值,导致部分key被逐出。因此,针对这种情况:加强大key的检测逻辑,对于超过512M的key,会发送告警邮件通知主人。V2中所有instance的maxmemory在split前不做限制,统一调整为60G,防止V2中key分布不均匀导致keyeviction。水平伸缩后,在V1和V2的切换过程中,会检查V2中的实例是否被逐出。如果是,则默认拆分失败,不进行切换。(2)mget扩容后,性能会下降。对于极少数场景,我们也发现mget请求的耗时会明显增加。实例数量增加。一般这种情况,我们建议业务控制单次mget的key个数,或者将string类型转成hash类型,通过hmget访问数据,保证一次只访问一个实例,这样扩容后的吞吐量是随机的,分片数量线性增加,延迟没有增加。四、总结及未来规划1、Xpipe支持当前水平伸缩、漂移、二次调度等一系列管理工具和策略,形成了一个比较完整的闭环,有效支撑了上千台主机、上万台的生产带超解析能力的Redis实例的运维管理。但是由于目前的xpipe架构,对于一个连接xpipe的集群来说,首先要对集群进行扩容和缩容,然后在DR端的xpipe需要手动完成。自动化程度不够,完成xpipe需要很长时间。机房Redis集群的APP,在扩缩容后,可能只有一段时间跨机房读取,必然导致延迟增加。而这种延迟的增加会影响我们判断水平伸缩的逻辑是否正确,是否需要回滚。因此,未来我们会像普通集群一样针对xpipe集群,即V2集群是扩容缩容写流量前采用DR架构的集群。2.支持持久化KV存储除了Redis本身被业务广泛使用之外,我们还发现一些业务需要比Redis更可靠的KV存储方式,比如将数据保存在磁盘而不是内存中,或者业务它需要支持一些增减库存逻辑,对某个key的独占访问,实现类似于INCRBY操作的语义,但实际上是对一些字符串的merge操作。另外对数据的可靠性要求更高,不能在master宕机的情况下丢失数据。针对这些需求,我们已经有了一些实践经验,会在后续的文章中分享。