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

百亿请求揭秘高可用Redis(codis)分布式集群

时间:2023-03-20 13:15:56 科技观察

一、背景随着直播元年的开启,越来越多的直播产品如雨后春笋般涌现。产品在拉动营收的过程中,想尽各种办法刺激用户的消费欲望,而这类活动的基本形式就是榜单。2016年,我们实现了基于cmem和扫描水表的榜单排名。从2017年开始,我们进行了原有系统的重构,使用redis作为我们列表的基础存储,在重构过程中接到了研究redis分布式方案的任务,比较了业界各种开源产品,最终选择了Codis,和分析细节我做了一些研究,在和Codis作者的交流过程中,有幸了解到增值产品部的simotang在该部门引入codis已经将近2年,然后加入运营和codis的维护。目前部门部署codis运维集群15套,2T容量,日总访问量100亿+。支持互动视频产品部基础存储、运营活动、榜单业务2年多,累计活动100多个,榜单上千个。同时感谢codis作者spinlock在接入codis过程中给予的指导和帮助。参见自旋锁github和codis地址。2.Redis相关基础概述2.1Redis简介Redis是一个基于内存的高性能、低延迟的KV数据库,具有数据持久化能力。value的数据结构可以是string、hashtable、list(列表)、set(集合)、sortedset(有序集合)。Redis(RemoteDictionaryServer)Redis是一种开源(BSD许可)内存数据结构存储,用作数据库、缓存和消息代理。支持strings,hashes,lists,sets,sortedsetswithrangequeries等数据结构,实践:http://try.redis.io/2.2Redis的特点1.单线程异步架构(单线程,数据包接收,包发送、解析、执行、多路复用io多路复用接收文件事件)2.k-v结构,丰富的值支持数据结构(string、hash、list、set、sortset)3.高性能,低延迟,基于内存操作,Get/Set10w+,高性能,基于RDB,AOF实现,保证数据可靠性4.丰富的特性,可用于缓存,消息队列,TTL过期5.支持事务,操作是原子的,要么全部提交,要么都不提交。2.3Redis应用场景2.4写在前面:codis和redis的关系codis和redis的关系是codis是在多个redis实例的基础上做一层路由层进行数据路由,每个redis实例承担一定的量数据碎片化。2.5Redis学习资料由于本文主要介绍redis分布式解决方案,所以与redis相关的基础部分,可以参考两本书和相关源码分析文章。1.Redis开发与运维(傅雷)2.Redis设计与实践(黄建宏)(值得翻两遍)3.Redis分布式方案公司内外部对比方案对比之前,我们先根据自己的经验输出我们的选择标准是根据我们期望解决方案具备的能力来衡量的。基于此,我们对公司内外做了如下对比:【公司内部组件对比】【公司外部组件对比】基于以上对比,codis作为一个开源产品,可以直观的看出:codis运维成本低,平滑扩容是核心优势。对于数据安全,我们目前是基于机器的48小时滚动备份加上公司的刘备备份(每天定期备份目录的系统)。对于监控,我们目前访问监控单机备份和MiG监控警报)。4.codis的架构设计4.1Codis整体架构设计Codis官网【图codis架构图】如上图所示,codis整体属于二层架构,proxy+storage,相比ckv的设计+没有代理,整体设计会比较简单,同时当客户端连接数据逐渐增多时,不需要扩容数据层的副本,只需要扩容代理层即可。从这个角度来说,成本会更低,但是对于连接数较少的情况,需要单独部署代理。从这个角度来说,成本会更高。其中,开源codisproxy服务的注册发现是通过zk实现的,目前系基于l5。从整体架构设计图来看,codis的整体架构比较清晰。其中,codisproxy是分布式方案设计的核心部分。存储路由和分片迁移都离不开codisproxy。让我们来看看codisproxy。设计实施。4.2Codisproxy的架构设计与实现Codisproxy的架构实现分为两部分,分别是4.2.1路由映射的细节和4.2.2代理请求处理的细节。4.2.1路由映射细节如下图所示:这部分主要涉及codis的路由细节,主要涉及如何将一个key映射到具体的物理节点上。【图】路由映射细节如上图所示:这部分主要涉及codis的路由细节。|相关词汇描述槽:分片信息,在redis中只代表一个数字,代表分片索引。每个分片将属于一个特定的redis实例。group:主要是一个虚拟节点,由多台redis机器组成,形成一主多从的模式,是一个逻辑节点。为了帮助大家更深入的了解代理路由映射的细节,我整理了几个常见的路由映射相关问题,以帮助大家理解。问题一:proxy如何将请求映射到具体的redis实例?Codis根据crc32算法%1024得到对应的slot,slot就是所谓的逻辑分片。同时codis将对应的逻辑分片映射到对应的虚拟节点上,每个虚拟节点由一主多从的物理redis节点组成。至于为什么用crc32,我没有详细研究过。笔者也参考了rediscluster中的实现进行了介绍。通过引入逻辑存储节点组,即使底层宿主机实例发生变化,也不会映射上层映射数据,对上层映射透明,便于分片管理。问题2:proxy是如何实现读写分离的?如上图所示,当key映射到具体的虚拟节点时,可以感知到该虚拟节点对应的主备机实例。此时redisproxy层可以识别出具体的redis命令对应的读写命令,然后根据集群的配置是否支持读写分离的特性,如果配置支持,则随机路由到master和slave实例,如果配置不支持,会路由到hostCompletion。问题3,代理目前支持哪些命令,是否支持批处理命令,如何保证原子命令支持链接命令支持部分:Prxoy支持的命令分为三种:不支持命令,半支持命令,以及支持的命令,除了上表中显示的命令外,其他命令都是proxy支持的。不支持该命令的主要原因是这些命令参数中没有key,因此无法识别路由信息,不知道路由到哪个实例。半支持的命令部分通常是操作多个key,codis是基于简单实现的,以第一个key的路由为准。因此,业务方需要保持多个密钥路由到同一个插槽。当然,业务不一定能保证,具体后果要看业务假设是弱验证模式,公司级产品ckv+对多key操作有强验证。如果多键不在同一个插槽上,它将返回错误。多键操作&原子部分:redis本身对于mset等一些多键操作是原子的,而在分布式操作下,多键会分布在多个redis实例中,涉及到分布式事务,所以进行了简化处理out在codis中,多键操作拆分为多个单键命令操作,所以codis中的mset多键操作不具备原子语义。问题四:如何保证多个key在一个槽位?在某些场景下,我们希望使用lua或者一些半支持的命令来保证我们操作的原子性。因此,我们需要在业务层面保证多个key在一个slot中。codis采用了和rediscluster一样的模式,基于hashtags,比如我要把7天的anchor列表全部路由到同一个slot,可以支持{anchor_rank}day1,{anchor_rank}day2,{anchor_rank}day3,yes是大括号的模式,codis会识别大括号,只取大括号内的字符串进行hash运算。4.2.2Proxy请求处理细节如下图所示:这部分主要涉及到proxy的处理细节,涉及到如何接受请求和响应数据包的过程。【图】Proxy请求处理细节如上图所示:这部分主要涉及到proxy的处理细节。Codisproxy主要是基于go语言实现的,go语言是一种在语言层面天然支持协程的语言。1)proxy收到client的连接后,创建一个新的session,同时启动session中的reader和writer两个协程。reader主要用于接收并解析客户端请求的数据,在多key场景下拆分command,然后通过router将请求分发到具体的redis实例,将redis处理后的数据结果写入到channel,writer从channel接收到相应的结果,并写回给client。2)路由器层主要通过crc命令获取key对应的路由信息??。从源码可以看出hashtag的特点,codis其实是支持的。至此,代理相关的路由映射和请求处理细节已经完成。整体是不是很简单?5.数据可靠性&高可用&容灾&故障转移&裂脑处理作为存储层,数据可靠性和服务高可用是稳定性的核心指标,直接影响上层核心服务的稳定性。本节将主要对这两个指标进行阐述。5.1数据可靠性作为codis的实现,数据的高可靠性主要是redis自身的能力。通常,存储层数据的高可靠性主要通过单机数据+异地数据热备份+定期冷备份归档的高可靠性来实现。单机数据的高可靠性主要是靠redis本身的持久化能力,rdb模式(periodicaldum)和aof模式(flowlog)。这可以通过参考上面显示的两本书来理解。aof模式的安全性比较高,目前我们也在线开启了aof开关,文末会详细介绍。远程数据热备份主要是基于redis本身的主从同步特性,实现全量同步和增量同步,让redis具备远程热备份的能力。定期冷备份归档由于存储服务运行过程中可能出现人员误操作数据、机房网络故障、硬件问题等导致数据丢失的情况,我们需要一些备份方案。目前主要是对最近48小时的数据进行单机滚动备份,sng目前的刘备系统作为冷备份,万一出现意外问题导致数据丢失,可以快速恢复。5.2HighAvailability&DisasterRecovery&Failovercodis本身的架构分为代理集群+redis集群。proxy集群的高可用可以基于zk或者l5做failover,而redis集群的高可用是基于redis开源的sentinel集群来实现的,codis是那边的非redis组件,有一个问题需要解决的是如何集成redissentinel集群。本节将问题分为三个部分,介绍redissentinel集群如何保证redis的高可用,codisproxy如何感知redissentinel集群的故障转移动作,以及redis集群如何降低“裂脑”概率。5.2.1哨兵集群如何保证Redis的高可用哨兵(Sentinel,Sentinel)是Redis的一种高可用解决方案:由一个或多个哨兵实例组成的哨兵系统,可以监控任意数量的主服务器,以及这些主服务器的属性并且当被监控的主服务器下线时,会自动将下线的主服务器下的一个从服务器升级为新的主服务器,然后主服务器会代替下线的主服务器继续处理命令请求。一般来说,要实现服务的高可用,需要做两件事:故障检测和故障转移(即主从选择和主从切换)。5.2.2codis如何感知sentinel集群的failover动作?codis本身的架构分为代理集群+redis集群。Redis集群的高可用由sentinel集群保证。那么proxy如何感知到redis主机的故障,然后切换到新的master上来保证服务的高可用呢?如上图所示,proxy本身会监听sentinle集群的+switch-master事件。这个事件意味着redis集群主机有问题。哨兵集群开始选举和切换主机。代理监听哨兵。主从切换事件,proxy收到主从切换事件后,会做一个动作,就是拉出所有sentinel集群感知到的当前hosts,选择一半以上的hostssentinel认为是当前集群主机。说到这里,大家可能忽略了一个问题,就是配置存储。配置中心的存储还是老主机。重启proxy后,故障主机仍然被拉取。其实dashboard和proxy也做了同样的事情。接收到主从切换事件后,新的master会被持久化到storage(目前是zk)5.2.3脑裂处理脑裂集群通常发生在集群中的一些节点之间,因为无法访问。当出现以下情况时,不同的分裂小集群会独立选择一个master节点,导致原集群同时拥有多个master节点,导致系统混乱和数据损坏。关于这个问题,这里,simotang已经解释的很好了,大规模codis集群的治理和实践,这里简单介绍一下,因为redis集群不能简单的依赖过半选举的模式,因为redismaster本身做的不会检查自己的健康状态和降级动作,所以我们需要一种方式让master的健康状态来辅助判断降级。具体实现是1)下调双主概率,使Quorums判断更严格,对主机下线的判断时间更严格。我们部署了5台哨兵机覆盖各大运营商的IDC,只有4台主观认为主机不在线。是时候下线了。2)被隔离的master被降级。基于共享资源的判断方式,redis服务器上的agent会定时持续检测zk是否正常。如果无法连接,它会向redis发送降级命令,使其不可读,并牺牲可用性来保证一致性。6.Codis水平扩展细节&迁移异常处理由于codis是redis的分布式解决方案,在redis单点容量不足的情况下,必然会面临水平扩展的问题。本节主要针对codis水平扩展和迁移异常的细节进行讲解,先来看两个问题。问题一是迁移过程中如何处理被迁移key的读写请求。问题2是如何处理迁移过程中出现的异常(如失败、超时等)。6.1Codis扩容迁移详解【图】迁移过程影响:第一阶段的影响:从通知到通知成功结束,代理读写请求阻塞,不丢失,延迟增加(时间极短,并行通知,只修改state,使proxy中slots的状态一致)迁移过程:可读,正在迁移的batches不可写,已经迁移的batches涉及两个网络ios如上图,其实redis平滑迁移过程主要实现三点,迁移准备,迁移动作,迁移性能保障。迁移准备主要是在执行迁移动作之前,所有的请求都可以感知到路由的变化,所以有一个单阶段的处理流程,这里是通过并行发送给所有代理来实现的,代理会写入对应的slotLock,所以请求在队列中排队,直到所有代理通知dashboard,代理锁被释放。这时候请求的延迟会稍微增加,但是因为是并行响应,所以影响时间很短,视图会轻微抖动。迁移动作主要由dashboard批量触发,直到所有key都迁移ok。在迁移过程中,slot上的key可能有两种情况,一种是在新的redis实例A上,一种是在旧的redis实例B上,所以对于一个有迁移状态的slot,所有发送到的命令这个插槽由redis中的自定义命令SLOTSMGRT-EXEC-WRAPPER处理。该命令是基于3.2分支新增的。这条命令主要做这些事情,1)判断key是否存在,如果存在,但不在迁移批次中,则直接调用key上的real方法,如果存在,但在迁移批次中,则允许读操作,不允许写操作,2)如果key没有如果存储很大,key可能已经迁移到新的实例,或者key可能不存在,会通知proxy去新实例进行手术。迁移性能Codis的迁移性能在之前的2.x版本中其实并不高,而在3.x之前性能有了很大的提升。***迁移其他zset结构只需要10多秒,但在原始模式下需要50多秒。具体原因是迁移性能数据6.2迁移异常处理另外,不知道大家看到这个有没有问题,不过这里我准备了一些问题,看看codis是怎么处理的,尤其是在复杂不稳定的网络环境下.问题1.将大密钥拆分成小批进行迁移。批量迁移失败超时怎么办?我们知道分布式场景下的网络调用有成功、失败、超时三种状态。比较适合失败,和超时的情况,我们是否可以盲目重试,这里显然不行,通常是为了数据层面的重试,我们需要保证一个很重要的原则,幂等性,但是在redis结构中除了zset,set,hash,stringstructureheavy试用理论不会受影响,list怎么办?所以codis使用了比较暴力的方法。当批量迁移重试成功后,会先带一个del命令让目标节点先删除key,然后再重试。问题2:在有过期时间的key迁移过程中,应该在传输数据前在目标节点上设置过期时间,还是先传输数据再设置过期时间?首先在传输前在目标节点上设置过期时间数据问题:机器B有一半的key过期了,后面的key没有过期时间。如果不符合我们的预期,我们来看一下先传输***中的数据,然后设置过期时间的问题:如果Acrash重新开始传输一半,此时key过期,数据会落在B机上,成为僵尸数据,不符合我们的要求。预计。那么codis是怎么做到的呢?为了保证迁移过程中的分片在迁移异常时能够自动销毁,每传输一个分片,key过期时间重新设置为90秒(大于超时时间30秒)。key迁移完成后,重置为真实的过期时间,这样即使迁移过程中出现Acrash、key过期等异常,分片数据也只会在目标节点上存活90秒后被销毁。问题三:迁移过程中崩溃。此时shard对应的数据一半在A,一半在B,怎么办?常在河边走,没有不吃亏的。我们遇到过一个codismigrationduetoexpire执行不当导致的血案,还好发生在测试环境。此时不要拉起A,因为A上可能有旧数据,此时迁移的key会被重新迁移,导致B的数据丢失。正确的姿势是把A的备用机放在上面继续迁移。A的备份机虽然是异步复制的,但是基本接近A的数据全量,所以问题不是太大。但是,在所有迁移过程中,最好备份数据和分片信息,以防止数据丢失。这时候千万不能把B的数据反向迁移回A,因为B上可能还残留了一些迁移后的数据,会覆盖掉A的所有数据。问题4:出于性能考虑,A可以不作为吗备份机,AOF和RDB不开启。这也是万万不能的,因为如果A在崩溃后被智云拉起,就相当于一个空实例,备机上的数据会被清空,导致数据丢失。7.Codis相关数据其中,压测环境:压测服务器(v4-8-100)+代理(v4-8-100)+redis(B5(4-32-100))可以看出上图,当一次获取的数据量越来越大时,代理的性能会下降得非常快。比如ZRANGE_500直连的性能是代理的两倍。每次主备切换后,确保被切换的主备机上的conf文件都被rewriteok了。grep"GeneratedbyCONFIGREWRITE"-C10{redis_confpath}/*.conf8.2迁移数据:关键操作前,备份数据,如果涉及到分片信息,备份分片信息A迁移B查看时间过长:connect到Acodisserver,在命令行执行slotsmgrt-async-status查看正在迁移的分片信息(尤其是大键),这样你就知道会发生什么。***其他密钥可以在大约20秒内迁移。8.3异常处理:Redis宕机重启。重启后loadingkey加载的差不多了,页面报错。8.4客户端出现大量超时。硬盘同步最常用的策略是everysec,用于平衡性能和数据安全。对于这种方法,Redis使用另一个线程每秒执行一次fsync来同步硬盘。当系统硬盘资源繁忙时,Redis主线程会被阻塞。8.7不小心握手执行flushdb,如果配置appendonlyno,赶紧调大rdb触发参数,然后备份rdb文件。如果备份失败,就逃跑。配置了appedonlyyes,方法是增加AOF重写参数auto-aof-rewrite-percentage和auto-aof-rewrite-minsize,或者直接kill进程,让Redis无法产生AOF自动重写。·拒绝手动bgrewriteaof。备份aof文件,同时把备份的aof文件里面写的flushdb命令杀掉,然后恢复。如果无法恢复,则依赖于冷备份。8.8在线redis如果想把rdb模式改成aof模式,必须直接修改conf重启正确方法:备份rdb文件,通过configset打开aof,同时configrewrite写回配置,执行bgrewriteof,内存数据备份到文件9.参考资料Redis开发与运维(付雷)Redis设计与实践(黄建宏)大规模codis集群治理与实践【本文为原稿专栏作者《腾讯技术工程》转载请联系原作者(微信ID:Tencent_TEG)】点此阅读更多该作者好文