大家好,我是鸭哥。
Redis作为高性能缓存被广泛应用到各个业务,比如游戏的排行榜,分布式锁等场景。经过在IEG的长期运营,我们也遇到Redis一些痛点问题,比如内存占用高,数据可靠性差,业务维护缓存和存储的一致性繁琐。由腾讯互娱CROSDBA团队腾讯云数据库团队联合研发的Tendis推出了:缓存版、混合存储版和存储版三种不同产品形态,针对不同的业务需求,本文主要介绍混合存储版的整体架构,并且详细揭秘内部的原理。
本文首先介绍腾讯IEG运营Redis遇到的一些痛点问题,然后介绍由腾讯互娱CROSDBA团队腾讯云数据库团队联合研发的Tendis的三种不同的产品形态。最后重点介绍冷热混合存储版的架构,并且重点介绍各个组件的功能特性。背景介绍Redis有哪些痛点?在使用的过程中,主要遇到以下一些痛点问题:内存成本高业务不同阶段对QPS要求不同比如游戏业务,刚上线的新游戏特别火爆,为了支持上千万同时在线,需要不断的进行扩容增加机器。运营一段时间后,游戏玩家可能变少,访问频率(QPS)没那么高,依然占用大量机器,维护成本很高。需要为Fork预留内存Redis保存全量数据时,需要Fork一个进程。Linux的fork系统调用基于CopyOnWrite机制,如果在此期间Redis有大量的写操作,父子进程就需要各自维护一份内存。因此部署Redis的机器往往需要预留一半的内存。缓存一致性的问题对于Redis+MySQL的架构需要业务方花费大量的精力来维护缓存和数据库的一致性。数据可靠性Redis本质上是一个内存数据库,用户虽然可以使用AOF的Always来落盘保证数据可靠性,但是会带来性能的大幅下降,因此生产环境很少有使用。另外不支持回档,Master故障后,异步复制会造成数据的丢失。异步复制Redis主备使用异步复制,这个是异步复制固有的问题。主备使用异步复制,响应延迟低,性能高,但是Master故障后,会造成数据丢失。Tendis是什么?Tendis是集腾讯众多海量KV存储优势于一身的Redis存储解决方案,并%兼容Redis协议和Redis4.0所有数据模型。作为一个高可用、高性能的分布式KV存储数据库,从访问时延、持久化需求、整体成本等不同维度的考量,Tendis推出了缓存版、混合存储版和存储版三种不同产品形态,并将存储版开源。Tendis缓存版适用于对延迟要求特别敏感,并且对QPS要求很高的业务。基于社区Redis4.0版本进行定制开发。Tendis存储版适用于大容量,延迟不敏感型业务,数据全部存储在磁盘,适合温冷数据的存储。Tendis存储版是腾讯互娱CROSDBA团队腾讯云数据库团队自主设计和研发的开源分布式高性能KV存储系统。另外在可靠性、复制机制、并发控制、gossip实现以及数据搬迁等做了大量的优化,并且解决了一些Rediscluster比较棘手的问题。完全兼容Redis协议,并使用RocksDB作为底层存储引擎。Tendis冷热混合存储版冷热混合存储综合了缓存版和存储版的优点,缓存层存放热数据,全量数据存放在存储层。这既保证了热数据的访问性能,同时保证了全量数据的可靠性,同时热数据支持自动降冷。Tendis冷热混合存储版整体架构Tendis冷热混合存储版主要由Proxy、缓存层Redis、存储层Tendis存储版和同步层Redis-sync组成,其中每个组件的功能如下:Proxy组件:负责对客户端请求进行路由分发,将不同的Key的命令分发到正确的分片,同时Proxy还负责了部分监控数据的采集,以及高危命令在线禁用等功能。缓存层RedisCluster:缓存层Redis基于社区Redis4.0进行开发。Redis具有以下功能:1)版本控制2)自动将冷数据从缓存层中淘汰,将热数据从存储层加载到缓存层;3)使用CuckooFilter表示全量Keys,防止缓存穿透;4)基于RDB+AOF扩缩容方式,扩缩容更加高效便捷。存储层TendisCluster:Tendis存储版是腾讯基于RocksDB自研的兼容Redis协议的KV存储引擎,该引擎已经在腾讯集团内部运营多年,性能和稳定性得到了充分的验证。在混合存储系统中主要负责全量数据的存储和读取,以及数据备份,增量日志备份等功能。同步层Redis-sync:1)并行数据导入存储层Tendis;2)服务无状态,故障重新拉起;3)数据自动路由。Tendis冷热混合存储的一些重要特性介绍:缓存层RedisCluster和存储层TendisCluster分别进行扩缩容,集群自治管理等。冷数据自动降冷,降低内存成本;热数据自动缓存,降低访问延迟缓存层RedisCluster冷热混合存储缓存层Redis在社区版的基础上增加了以下功能:版本控制冷热数据交互CuckooFilter避免缓存穿透智能淘汰算法基于RDB+AOF扩缩容下面分别对这几个特性进行详细的讲解。版本控制首先基于社区版Redis改动是版本控制。我们为每个Key和每条Aof增加一个Version,并且Version是单调递增的。在每次更新/新增一个Key后,将当前节点的Version赋值给Key和Value,然后对全局的Version++;如下所示,在redisObject中添加64bits,其中48bits用于版本控制。typedefstructredisObject{unsignedtype:4;unsignedencoding:4;unsignedlru:LRU_BITS;/*LRUtime(relativetogloballru_clock)or*LFUdata(leastsignificant8bitsfrequency*andmostsignificant16bitsaccesstime).*/intrefcount;/*forhybridstorage*/unsignedflag:4;/*OBJ_FLAG_...*/unsignedreserved:4;unsignedcounter:8;/*forcold-data-cache-policy*/unsignedlonglongrevision:REVISION_BITS;/*forvalueversion*/void*ptr;}robj;引入版本控制主要带来以下优势:
增量RDB社区版Redis主备在断线重连后,如果slave发送的psync_offset对应的数据不在当前的Master的repl_backlog中,则主备需要重新进行全量同步。再引入Version之后,slave断线重连,给Master发送带Version的PSYNCreplidpsync_offsetversion命令。如果出现上述情况,Master将大于等于Version的数据生成增量RDB,发给Slave,进而解决需要增量,同步比较慢的问题。
Aof的幂等如果同步层Redis-sync出现网络瞬断(短暂的和缓存层或者存储层断开),作为一个无状态的同步组件,Redis-sync会重新拉取未同步到Tendis的增量数据,重新发送给Tendis。每条Aof都具有一个Version,Tendis在执行的时候仅会执行比当前Version大的Aof,避免aof执行多次导致的数据不一致。
冷热数据交互冷数据的恢复指当用户访问的Key不在缓存层,需要将数据从存储层重新加载到缓存层。数据恢复这里是缓存层直接和存储层直接交互,当冷Keys访问的请求比较大,数据恢复很容易成为瓶颈,因此为每个Tendis节点建立一个连接池,专门负责与这个Tendis节点进行冷热数据恢复。用户访问一个Key的具体流程如下:首先判断Key是否在缓存层,如果缓存层存在,则执行命令;如果缓存层不存在,查询CuckooFilter,判断Key是否有可能在存储层;如果Key可能在存储层,则向存储层发送dumpxdbidkeywithttl命令尝试从存储层获取数据,并且阻塞当前请求的客户端;存储层收到dumpx,如果Key在存储层,则向缓存层返回RESTOREEXdbidkeyttlvalue;如果Key不在存储层(CuckooFilter的误判),则向缓存层返回DUMPXERRORkey;存储层收到RESTOREEX或者DUMPXERROR后,将冷数据恢复。然后就可以唤醒阻塞的客户端,执行客户端的请求。Key降冷与CuckooFilter这里主要讲解混合存储从1:1版的缓存层缓存全量Keys,到N:M版的缓存层将Key和Value同时驱逐的演进,以及我们引入CuckooFilter避免缓存穿透,同时节省大量内存。Key降冷的背景介绍年6月份上线的1:1版的冷热混合存储,缓存层Redis存储全量的Keys和热Values(AllKeys+Hotvalues),存储层Tendis存储全量的Keys和Values(AllKeys+Allvalues)。在上线运行了一段时间后,发现全量Keys的内存开销特别大,冷热混合的收益并不明显。为了进一步释放内存空间,提高缓存的效率,我们放弃了Redis缓存全量Keys的方案,驱逐的时候将key和Value都从缓存层淘汰。
CuckooFilter解决缓存击穿和缓存穿透如果缓存层不存储全量的Keys,就会出现缓存击穿和缓存穿透的问题。为了解决这一问题,缓存层引入CuckooFilter表示全量的keys。我们需要一个支持删除、可动态伸缩并且空间利用率高的MembershipQuery结构,经过我们的调研和对比分析,最终选择DynamicCuckooFilter。
DynamicCuckooFilter实现项目初期参考了RedisBloom中CuckooFilter的实现,在开发的过程中也遇到了一些坑,RedisBloom实现的CuckooFilter在删除的时候会出现误删,最终给RedisBloom提PR(FixCuckoofilter