Redis 作为高性能缓存被广泛应用到各个业务, 比如游戏的排行榜, 分布式锁等场景。经过在 IEG 的长期运营, 我们也遇到 Redis 一些痛点问题, 比如内存占用高, 数据可靠性差, 业务维护缓存和存储的一致性繁琐。由 腾讯互娱 CROS DBA 团队 & 腾讯云数据库团队联合研发的 Tendis 推出了: 缓存版 、 混合存储版 和 存储版 三种不同产品形态, 针对不同的业务需求, 本文主要介绍 混合存储版 的整体架构, 并且详细揭秘内部的原理。
本文首先介绍腾讯 IEG 运营 Redis 遇到的一些痛点问题, 然后介绍由 腾讯互娱 CROS DBA 团队 & 腾讯云数据库团队联合研发的 Tendis 的三种不同的产品形态。最后重点介绍冷热混合存储版的架构, 并且重点介绍各个组件的功能特性。
在使用的过程中, 主要遇到以下一些痛点问题:
Tendis 是集腾讯众多海量 KV 存储优势于一身的 Redis 存储解决方案, 并 100% 兼容 Redis 协议和 Redis4.0 所有数据模型。作为一个高可用、高性能的分布式 KV 存储数据库, 从访问时延、持久化需求、整体成本等不同维度的考量, Tendis 推出了 缓存版 、 混合存储版 和 存储版 三种不同产品形态,并将存储版开源。感兴趣的小伙伴 可以去 Github 关注我们的项目: Tencent/Tendis
Tendis 缓存版适用于对延迟要求特别敏感, 并且对 QPS 要求很高的业务。基于社区 Redis 4.0 版本进行定制开发。
Tendis 存储版适用于大容量, 延迟不敏感型业务, 数据全部存储在 磁盘, 适合温冷数据的存储。Tendis 存储版是腾讯互娱 CROS DBA 团队 & 腾讯云数据库团队 自主设计和研发的开源分布式高性能 KV 存储系统。另外在 可靠性、复制机制、并发控制、gossip 实现以及数据搬迁等做了大量的优化, 并且解决了一些 Redis cluster 比较棘手的问题。完全兼容 Redis 协议, 并使用 RocksDB 作为底层存储引擎。
Tendis 冷热混合存储版冷热混合存储 综合了缓存版和存储版的优点, 缓存层存放热数据, 全量数据存放在存储层。这既保证了热数据的访问性能,同时保证了全量数据的可靠性,同时热数据支持自动降冷。
Tendis 冷热混合存储版主要由 Proxy 、缓存层 Redis、 存储层 Tendis 存储版 和 同步层 Redis-sync 组成, 其中每个组件的功能如下:
Proxy 组件: 负责对客户端请求进行路由分发,将不同的 Key 的命令分发到正确的分片,同时 Proxy 还负责了部分监控数据的采集,以及高危命令在线禁用等功能。
缓存层 Redis Cluster: 缓存层 Redis 基于 社区 Redis 4.0 进行开发。Redis 具有以下功能: 1) 版本控制 2) 自动将 冷数据从缓存层中淘汰, 将热数据从存储层加载到缓存层; 3) 使用 Cuckoo Filter 表示全量 Keys, 防止缓存穿透; 4) 基于 RDB+AOF 扩缩容方式, 扩缩容更加高效便捷。
存储层 Tendis Cluster: Tendis 存储版 是腾讯基于 RocksDB 自研的 兼容 Redis 协议的 KV 存储引擎, 该引擎已经在腾讯集团内部运营多年, 性能和稳定性得到了充分的验证。在混合存储系统中主要负责全量数据的存储和读取, 以及数据备份, 增量日志备份等功能。
同步层 Redis-sync: 1) 并行数据导入 存储层 Tendis; 2) 服务无状态, 故障重新拉起; 3) 数据自动路由。
Tendis 冷热混合存储的一些重要特性介绍:
冷热混合存储缓存层 Redis 在社区版的基础上增加了以下功能:
下面分别对这几个特性进行详细的讲解。
首先基于社区版 Redis 改动是版本控制。我们为每个 Key 和 每条 Aof 增加一个 Version , 并且 Version 是单调递增的。在每次更新/新增一个 Key 后, 将当前节点的 Version 赋值给 Key 和 Value, 然后对全局的 Version++; 如下所示, 在 redisObject 中添加 64bits, 其中 48bits 用于版本控制。
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
int refcount;
/* for hybrid storage */
unsigned flag:4; /* OBJ_FLAG_... */
unsigned reserved:4;
unsigned counter:8; /* for cold-data-cache-policy */
unsigned long long revision:REVISION_BITS; /* for value version */
void *ptr;
} robj;
引入版本控制主要带来以下优势:
社区版 Redis 主备在断线重连后, 如果 slave 发送的 psync_offset 对应的数据不在当前的 Master 的 repl_backlog 中, 则主备需要重新进行全量同步。再引入 Version 之后, slave 断线重连, 给 Master 发送 带 Version 的 PSYNC replid psync_offset version命令。如果出现上述情况, Master 将大于等于 Version 的数据生成增量 RDB, 发给 Slave, 进而解决需要增量, 同步比较慢的问题。
如果同步层 Redis-sync 出现网络瞬断(短暂的和缓存层或者存储层断开), 作为一个无状态的同步组件, Redis-sync 会重新拉取未同步到 Tendis 的增量数据, 重新发送给 Tendis。每条 Aof 都具有一个 Version, Tendis 在执行的时候仅会执行比当前 Version 大的 Aof, 避免 aof 执行多次导致的数据不一致。
冷数据的恢复指当用户访问的 Key 不在缓存层, 需要将数据从存储层重新加载到缓存层。数据恢复这里是缓存层直接和存储层直接交互, 当冷 Keys 访问的请求比较大, 数据恢复很容易成为瓶颈, 因此为每个 Tendis 节点建立一个连接池, 专门负责与这个 Tendis 节点进行冷热数据恢复。
用户访问一个 Key 的具体流程如下:
这里主要讲解混合存储从 1:1 版的缓存层缓存全量 Keys, 到 N:M 版的缓存层将 Key 和 Value 同时驱逐的演进, 以及我们引入 Cuckoo Filter 避免缓存穿透, 同时节省大量内存。
作为冷热混合存储系统, 热数据在缓存层, 全量数据在存储层。关键的问题是淘汰和加载策略, 这里直接影响缓存的效率, 细分主要有两点: 1) 当缓存层内存满时, 选择哪些数据淘汰; 2) 当用户访问存储层的数据时, 是否需要将其放入缓存层。
社区版 Redis 的扩容流程:
社区版 Redis 扩容存在的一些问题:
先设置目标节点 slot 为 importing 状态, 再设置源节点的 slot 为 migrating 状态。如果反过来, 由于两次操作非原子: 源节点设置为 migrating , 目标节点还未设置 migrating 状态, 请求在这两个节点间反复 Move 。
Migrate 命令每次搬迁一个或者多个 Keys, 将整个 Slot 搬迁到目标节点需要多次网络交互。
由于 Migrate 命令是同步命令, 在搬迁过程中是不能处理其他用户请求的, 因此可能会影响业务。(延迟时间波动较大)
由于社区版 Redis 存在的上述问题, 我们实现了基于 RDB+Aof 的扩缩容方式, 大致流程如下:
同步层 Redis-sync 模拟 Redis Slave 的行为, 接收 RDB 和 Aof, 然后并行地导入到存储层 Tendis。同步层主要需要解决以下问题:
为了解决上述的三个问题, 我们实现了下面的功能:
Tendis 是兼容 Redis 核心数据结构与协议的分布式高性能 KV 数据库, 主要具有以下特性: