远程办公期间,在线会议用户需求激增,腾讯会议8天完成100万核云服务器扩展,Redis集群仅在半小时之内就高效完成了数十倍规模的扩容,单集群的扩容流程后台处理时间不超过30分钟。在这背后,腾讯云Redis是如何作到的呢?本文是伍旭飞老师在「云加社区沙龙online」的分享整理,详细阐述了腾讯云Redis无损扩容的实践和挑战。点击此连接,查看完整直播视频回放
今年疫情带来的挑战很明显,远程办公和在线教育用户暴涨,从1月29到2月6日,日均扩容1.5w台主机。业务7×24小时不间断服务,远程办公和在线教育要求不能停服,停服一分钟都会影响成百上千万人的学习和工做,因此这一块业务对于咱们的要求很是高。html
在线会议和远程办公都大量使用了redis,用户暴增的腾讯会议背后也有腾讯云Redis提供支持,同时海量请求对redis的快速扩容能力提出了要求。咱们有的业务实例,从最开始的3片一天以内扩容到5片,紧接着发现仍是不够,又扩到12片,次日继续扩。redis
腾讯云Redis跟广泛Redis有差异,咱们加入了Proxy,提升了系统的易用性,这个是由于不是全部的语言都支持集群版客户端。为了兼容这部分客户,咱们作了不少的兼容性处理,可以兼容更多普通客户端使用,像作自动的路由管理,切换的时候能够自由处理MOVE和ASK,增长端到端的慢查询统计等功能,Redis默认的slowlog只包含命令的运算时间,不包括网络来回的时间,不包括本地物理机卡顿致使的延时,Proxy能够作端到端的慢日志统计,更准确反应业务的真实延迟。算法
对于多账户,Redis不支持,如今把这部分功能也挪到Proxy,还有读写分离,这个对于客户很是有帮助,客户无须敲写代码,只须要在平台点一下,咱们在Proxy自动实现把读写派发上去。这一块功能放到Redis也是能够,那为何作到Proxy呢?数据库
主要是考虑安全性!由于Redis承载用户数据,若是在Redis作数据会频繁升级功能迭代,这样对于用户数据安全会产生比较大的威胁。安全
腾讯云Redis怎么扩容呢?咱们的扩容从三个维度出发,单个节点容量扩容, 好比说三分片,每一个片4G,咱们能够每节点扩到8G。单节点容量扩容,通常来讲只要机器容量足够,就能够扩容上去。还有副本扩容,如今客户使用的是一主一从,有的同窗开读写分离,把读所有打到从机,这种状况下,增长读qps比较简单的方法就是增长副本的数量,还增长了数据安全性。最近的实际业务,咱们遇到的主要是扩分片,对于集群分片数,最主要就是CPU的处理能力,扩容分片就是至关于扩展CPU,扩容处理能力也间接扩容内存。服务器
最先腾讯云作过一个版本,利用开源的原生版的扩容方式扩容。简单描述一下操做步骤:网络
首先Proxy是要作slot容量计算,不然一旦搬迁过去,容易把新分片的内存打爆。计算完每一个slot内存后,按照算法分配,决定好目标分片有哪些slot。先设置目标节点slot 为importing状态 ,再设置源节点的slot为 migrating状态。架构
这里存在一个小坑,在正常开发中这两个设置你们感受顺序可有可无,可是实际上有影响。若是先设置源节点的slot为migrating,再设置目标节点的slot为importing,那么在这两条命令的执行间隙,若是有对应slot的命令打到源节点,而key又刚好不存在,则会重定向到目标节点,因为目标节点此时slot并将来得及设置为importing, 又会把这条命令重定向给源节点,这样就无限重定向了。并发
好在常见的客户端(好比jedis)对重定向次数是有限制的, 一旦打到上限,就会抛出错误。less
(1)准备
(2)搬迁
设置完了这一步,下一步就是搬迁。从源节点来获取slot的搬迁,从源进程慢慢逐个搬迁到目标节点。此操做是同步的,什么意思呢?
在migrate命令结束以前进程不能直接处理客户请求,其实是源端临时建立一个socket,链接目标节点,同步执行命令,确认执行成功了后,把本地的Key删掉,这个时候源端才能够继续处理客户新的请求。在搬迁过程当中,整个集群仍然是能够处理请求的。
这一块开源Redis有考虑,若是这个时候有Key读请求,恰好这个slot发到源进程,进程能够判断,若是这个Key在本进程有数据,就会当正常的请求返回给它。
那若是不存在怎么办?就会发一个ASK给客户,用户收到ASK知道这个数据不在这个进程上,立刻从新发一个ASKING到目标节点,紧接着把命令发到那边去。这样会有一个什么好处呢?
源端的Key的slot只会慢慢减小,不会增长,由于新增长的都发到目标节点去了。随着搬迁的持续,源端的Key会愈来愈少,目标端的key逐步增长,用户感知不到错误,只是多了一次转发延迟,只有零点零几毫秒,没有特别明显的感知。
(3)切换
方案到何时切换呢?就是slot源进程发现这个slot已经不存在数据了,说明全部数据所有搬到目标进程去了。这个时候怎么办呢?先发送set slot给目标,而后给源节点发送set slot命令,最后给集群全部其余节点发送set slot。
这是为了更快速把路由更新过去,避免经过自身集群版协议推广,下降速度。这里跟设置迁移前的准备步骤是同样,也有一个小坑须要注意。
若是先给源节点设置slot,源节点认为这个slot归属目标节点,会给客户返回move,这个是由于源节点认为Key永久归属目标进程,客户端搜到move后,就不会发ASKing给目标,目标若是没有收到ASK,就会把这个消息从新返回源进程,这样就和打乒乓球同样,来来回回无限重复,这样客户就会感受到这里有错误。
像这样迁移其实也没有问题,客户也不会感受到正常的访问请求的问题。可是依然会面临一些挑战,第一就是大Key的问题。
前文提到的搬迁内部,因为这个搬迁是同步的搬迁,同步搬迁会卡住,这个卡住时间由什么决定的?主要不是网速,而是搬迁Key的大小来决定的,因为搬迁是按照key来进行的,一个list也是一个Key,一个哈希表也是Key,一个list会有上千万的数据,一个哈希表也会有不少的数据。同步搬迁容易卡很是久,同步搬迁100兆,打包有一两秒的状况,客户会以为卡顿一两秒,全部访问都会超时,通常Redis业务设置超时大部分是200毫秒,有的是100毫秒。若是同步搬移Key超过一秒,就会有大量的超时出现,客户业务就会慢。
若是这个卡时超过15秒,这个时间包括搬迁打包时间、网络传输时间、还有loading时间。超过15秒,甚至自动触发切换,把Master判死,Redis会从新选择新的Master,因为migrating状态是不会同步给slave的,因此slave切换成master后,它身上是没有migrating状态的。而后,正在搬迁的目标节点会收到新的master节点对这个slot的全部权声明, 因为这个slot是importing的,因此它会拒绝认可新master拥有这个slot。从而在这个节点看来,slot的覆盖是不全面的, 有的slot无节点提供服务,集群状态为fail。
一旦出现这种状况,假如客户继续在写,因为没有migrating标记了,新Key会写到源节点上,这个key可能在目标节点已经有了,就算人工处理,也会出现哪一边的数据比较新, 应该用哪一边的数据, 这样的一些问题,会影响到用户的可用性和可靠性。
这是整个开源Redis的核心问题,就是容易卡住,不提供服务,甚至影响数据安全。开源版如何解决这个问题呢?老规矩:惹不起就躲,若是这个slot有最大Key超过100M或者200M的阈值不搬这个slot。这个阈值很难设置,因为migrate命令一次迁移不少个key,太小的阈值会致使大部分slot迁移不了,过大的阈值仍是会致使卡死,因此不管如何对客户影响都很是大,并且这个影响是不能被预知的,由于这个Key大小能够从几k到几十兆,不知道何时搬迁到大key就会有影响,只要搬迁未结束,客户在至关长时间都心惊胆战。
除了Key的总体搬迁有这样问题之外,咱们还会有一个问题就是Lua。
假如业务启动的时候经过script load加载代码,执行的时候使用evalsha来,扩容是新加了一个进程,对于业务是透明,因此按照Redis开源版的办法搬迁Key,key搬迁到目标节点了,可是lua代码没有,只要在新节点上执行evalsha,就会出错。
这个缘由挺简单,就是Key搬迁不会迁移代码,并且Redis没有命令能够把lua代码搬迁到另一个进程(除了主从同步)。
这个问题在开源版是无解,最后业务怎么作才可以解决这个问题呢?须要业务那边改一下代码,若是发现evalsha执行出现代码不存在的错误,业务要主动执行一个script load,这样能够规避这个问题。可是对不少业务是不能接受的。由于要面临一个从新加代码而后再发布这样一个流程,业务受损时间是很是长的。
还有一个挑战,就是多Key命令,这个是比较严重的问题,Mget和mset其中一个Key在源进程,另一个Key根本不存在或者在目标进程,这个时候会直接报错,不少业务严重依赖于mget的准确性,这个时候业务是不能正常工做的。
这也是原生版redis没有办法解决的问题,由于只要是Key搬迁,很容易出现mget的一部分key在源端,一部分在目标端。
除了这个问题还有另外一个问题,这个问题跟自己分片扩容无关,可是开源版本存在一个bug,就是咱们这边Redis是提供了一个读写分离的功能,在Proxy提供这个功能,把全部的命令打到slave,这样能够下降master的性能压力。这个业务用得很方便,业务想起来就能够开启,发现不行就能够立刻关闭。
这里比较明显的问题是:当每一个分片数据比较大的时候,举一个例子20G、30G的数据量的时候,咱们刚开始挂slave,slave身份推广跟主从数据是两个机制,可能slave已经被集群承认了,可是还在等master的数据,由于20G数据的打包须要几分钟(和具体数据格式有关系)。这个时候若是客户的读命令来到这个slave, 会出现读不到数据返回错误, 若是客户端请求来到的时候rdb已经传到slave了,slave正在loading, 这个时候会给客户端回loading错误。
这两个错误都是不能接受,客户会有明显的感知,扩容副本原本为了提高性能,可是结果一扩反而持续几分种到十几分钟内出现不少业务的错误。这个问题实际上是跟Redis的基本机制:身份推广机制、主从数据同步机制有关,由于这两个机制是彻底独立的,没有多少关系,问题的解决也须要咱们修改这个状态来解决,下文会详细展开。
最后一点就是扩容速度。前文说过,Redis经过搬Key的方式对业务是有影响的,因为同步操做,速度会比较慢,业务会感觉到明显的延时,这样的延时业务确定但愿越快结束越好。可是咱们是搬迁Key,严重依赖Key的速度。由于搬Key不能全速搬,Redis是单线程,基本上线是8万到10万之间,若是搬太快,就占据用户CPU。用户原本由于同步搬迁卡顿致使变慢,搬迁又要占他CPU,致使雪上加霜,因此通常这种方案是不可能作特别快的搬迁。好比说每次搬一万Key,至关于占到12.5%,甚至更糟,这对于用户来讲是很是难以接受的。
既然开源版有这么多问题,为何不改呢?不改的缘由这个问题比较多。可能改起来不容易,也确实不太容易。
关于搬迁分片扩容是Redis的难点,不少人反馈过,可是目前而言没有获得做者的反馈,也没有一个明显的解决的趋势,行业内最多见就是DTS方案。
DTS方案能够经过下图来了解,首先经过DTS创建同步,DTS同步跟Redis-port是相似,会假装一个slave,经过sync或者是psync命令从源端slave发起一次全量同步,全量以后再增量,DTS接到这个数据把rdb翻译成命令再写入目标端的实例上,这样就不要求目标和源实例的分片数目一致,dts在中间把这个活给干了。
等到DTS彻底迁移稳定以后,就能够一直同步增量数据,不停从源端push目标端,这时候能够考虑切换。
切换首先观察是否是全部DTS延迟都在阈值内,这个延迟指的是从这边Master到那边Master的中间延迟。若是小于必定的数据量,就能够断连客户端,等待必定时间,等目标实例彻底追上来了,再把LB指向新实例,再把源实例删除了。一次扩容就彻底实现了,这是行业比较常见的一种方案。
DTS方案解决什么问题呢?大Key问题获得了解决。由于DTS是经过源进程slave的一个进程同步的。Lua问题有没有解决?这个问题也解决了,DTS收到RDB的时候就有lua信息了,能够翻译成script load命令。多Key命令也获得了解决,正经常使用户访问不受影响,在切换以前对用户来讲无感知。迁移速度也可以获得比较好的改善。迁移速度自己是由于原实例经过rdb翻译,翻译以后并发写入目标实例,这样速度能够很快,能够全速写。这个速度必定比开源版key搬迁更快,由于目标实例在切换前不对外工做,能够全速写入,迁移速度也是获得保证。迁移中的HA和可用性和可靠性也都还能够。固然中间可用性要断连30秒到1分钟,这个时间用户不可用,很是小的时间影响用户的可用性。
DTS有没有缺点?有!首先是其复杂度,这个迁移方案依赖于DTS组件,须要外部组件才能实现,这个组件比较复杂,容易出错。其次是可用性,前文提到步骤里面有一个踢掉客户端的状况,30秒到1分钟这是通常的经验可用性影响,彻底不可访问。还有成本问题,迁移过程当中须要保证全量的2份资源,这个资源量保证在迁移量比较大的状况下,是很是大的。若是全部的客户同时扩容1分片,须要整个仓库2倍的资源, 不然不少客户会失败,这个问题很致命,意味着我要理论上要空置一半的资源来保证扩容的成功, 对云服务商来讲是不可接受的,基于以上缘由咱们最后没有采用DTS方案。
咱们采用方案是这样的,咱们的目标是首先不依赖第三方组件,经过命令行也能够迁。第二是咱们资源不要像DTS那样迁移前和迁移后两份资源都要保留,这个对于咱们有至关大的压力。最后用的是经过slot搬迁的方案。具体步骤以下:
首先仍是计算各slot内存大小,须要计算具体搬迁多少slot。分配完slot以后,还要计算可分配到目标节点的slot。跟开源版不同,不须要设置源进程的migrating状态,源进程设置migrating是但愿新Key自动写入到目标进程,可是咱们这个方案是不须要这样作。
再就是在目标进程发起slot命令,这个命令执行后,目标节点根据slot区间自动找到进程,而后对它发起sync命令(带slot的sync),源进程收到这个sync命令,执行一个fork,将全部同步的slot区间全部的数据生成rdb,同步给目标进程。
每个slot有哪一些Key在源进程是有记录的,这里遍历将每个slot的key生成rdb传输给目标进程,目标进程接受rdb开始loading,而后接受aof,这个aof也是接受跟slot相关的区间数据,源进程也不会把不属于这个slot的数据给目标进程。
一个目标进程能够从一两个源点创建这样的链接,一旦所有创建链接,而且同步状态正常后,当offset足够小的时候,就能够发起failover操做。和Redis官方主动failover机制同样。在failover以前,目标节点是不提供服务的,这个和开源版有巨大的差异。
经过这个方案,大Key问题获得了解决。由于咱们是经过fork进程解决的,而不是源节点搬迁key。切换前不对外提供服务,因此loading一两分钟没有关系,客户感知不到这个节点在loading。
还有就是Lua问题也解决了,新节点接受的是rdb数据,rdb包含了Lua信息在里面。还有多Key命令也是同样,由于咱们彻底不影响客户正常访问,多Key的命令之前怎么访问如今仍是怎么访问。迁移速度由于是批量slot打包成rdb方式,必定比单个Key传输速度快不少。
关于HA的影响,迁移中有一个节点挂了会不会有影响?开源版会有影响,若是migrating节点挂了集群会有一个节点是不可以对外提供服务。但咱们的方案不存在这个问题,切换完了依然能够提供服务。由于咱们原本目标节点在切换以前就是不提供服务的。
还有可用性问题,咱们方案不用断客户端链接,客户端从头至尾没有受到任何影响,只是切换瞬间有小影响,毫秒级的影响。成本问题有没有解决?这个也获得解决,由于扩容过程当中,只建立最终须要的节点,不会建立中间节点,零损耗。
Q:Cluster数量会改变槽位的数量吗?
A:不会改变槽位数量,一直是16814,这个跟开源版是一致的。
Q:迁移以前怎么评估新slot接触数据不会溢出?
A:根据前文所述,咱们有一个准备阶段,计算全部slot各自内存大小,怎么计算咱们会在slave直接执行一次扫描计算,基本上可以计算比较准确。这里没有用前一天的备份数据,而是采用slave实时计算,CPU里有相应控制,这样能够计算出slot总量大小。咱们会预约提早量(1.3倍),用户还在写,保证目标不会迁移中被写爆,万一写爆了也只是流程失败,用户不会受到影响。
Q:对于 redis 扩容操做会发生的问题,大家有采用什么备用方案,和紧急措施吗?
A:新的扩容方案,若是出现问题,不会影响客户的使用,只会存在多余的资源,目前这块依赖工具来处理。数据的安全性自己除了主从,还依赖每日备份来保证。
Q:请问老师,高峰期须要扩容,但总有回归正常请求量的时候,此时的扩容显得有些冗余,怎么样让Redis集群可以既可以快速回收多余容量,同时又能方便下一次高峰请求的再次扩容呢?
A:可能你想了解的是serverless,按需付费自动扩展的模式,目前的数据库大可能是提供的PAAS服务,PASS层的数据库将过去的资源手动管理变成了半自动化,扩缩容仍是须要运维参与。Serverless形式的服务会是下一步的形态,可是就算是serverless可能也会面临着须要资源手动扩展的问题,特别是对超大规模的运算服务。
Q:slot来源于多个分片,同时和多个节点同步进行吗?
A:是,确实会这样作的。
Q:slot产生过程当中产生新数据怎么同步?
A:相似aof概念的机制同步到目标进程。这个aof跟普通aof传输到slave有区别,只会将跟目标slot相关的数据同步过去,而不会同步别的。
Q:还在招人吗?
A:腾讯云Redis还在招人,欢迎你们投简历,简历请投至邮箱:feiwu@tencent.com。你们还能够选择在官网上投递简历,或者搜索关注腾讯云数据库的公众号,查看21日的推送文章,咱们贴上了招聘职位的连接,也能够@社群小助手,小助手会收集上来发到我这边。
伍旭飞,腾讯云高级工程师,腾讯云Redis技术负责人,有多年和游戏和数据库开发应用实践经验, 聚焦于游戏开发和NOSQL数据库在各个领域的应用实践。
关注云加社区公众号,回复“线上沙龙”,便可获取老师演讲PPT~