文章目录
- 一、组件组成
- 二、整体架构
- 三、组件特性
- 缓存层 `Redis Cluster`
- 同步层 `Redis-sync`
- 存储层 `Tendis Cluster`
一、组件组成
Proxy
组件:
- 负责对客户端请求进行路由分发,将不同的
Key
的命令分发到正确的分片 Proxy
还负责了部分监控数据的采集,以及高危命令在线禁用等功能
- 缓存层
Redis Cluster
:
- 缓存层
Redis
基于 社区Redis4.0
进行开发 - 集成的
Redis
基于社区版并新增以下功能:
- 版本控制
- 自动将冷数据从缓存层中淘汰, 将热数据从存储层加载到缓存层
- 使用
Cuckoo Filter
表示全量Keys
, 防止缓存穿透 - 智能淘汰算法
- 基于
RDB + AOF
扩缩容方式, 扩缩容更加高效便捷
- 存储层
Tendis Cluster
:
Tendis 存储版
是腾讯基于RocksDB
自研的 兼容Redis
协议的KV
存储引擎- 该引擎已经在腾讯集团内部运营多年,性能和稳定性得到了充分的验证
- 在混合存储系统中主要负责全量数据的存储和读取, 以及数据备份, 增量日志备份等功能
- 同步层 Redis-sync:
- 并行数据导入 存储层 Tendis
- 服务无状态, 故障重新拉起
- 数据自动路由
二、整体架构
- 整体架构图
- 重要特性
- 缓存层
Redis Cluster
和 存储层Tendis Cluster
分别进行扩缩容, 集群自治管理 - 冷数据自动降冷, 降低内存成本; 热数据自动缓存, 降低访问延迟
三、组件特性
缓存层 Redis Cluster
- 版本控制
- 首先基于社区版
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;
- 版本控制带来的优势
- 增量
RDB
社区版Redis
主备在断线重连后, 如果slave
发送的psync_offset
对应的数据不在当前的master
的repl_backlog
中, 则主备需要重新进行全量同步。再引入Version
之后,slave
断线重连, 给master
发送带Version
的PSYNC replid psync_offset
version
命令。如果出现上述情况,master
将大于等于Version
的数据生成增量RDB
, 发给slave
, 进而解决需要增量, 同步比较慢的问题 - AOF 幂等
如果同步层Redis-sync
出现网络瞬断(短暂的和缓存层或者存储层断开), 作为一个无状态的同步组件,Redis-sync
会重新拉取未同步到Tendis
的增量数据, 重新发送给Tendis
。每条AOF
都具有一个Version
,Tendis
在执行的时候仅会执行比当前Version
大的AOF
, 避免AOF
执行多次导致的数据不一致
- 冷热数据交互
冷数据的恢复指当用户访问的Key
不在缓存层,需要将数据从存储层重新加载到缓存层。数据恢复这里是缓存层直接和存储层直接交互, 当冷Keys
访问的请求比较大, 数据恢复很容易成为瓶颈, 因此为每个Tendis
节点建立一个连接池, 专门负责与这个Tendis
节点进行冷热数据恢复 Key
降冷 与Cuckoo Filter
- 为了进一步释放内存空间, 提高缓存的效率, 产品放弃了
Redis
缓存全量Keys
的方案, 驱逐的时候将key
和Value
都从缓存层淘汰 Cuckoo Filter
解决缓存击穿和缓存穿透如果缓存层不存储全量的Keys
,就会出现缓存击穿和缓存穿透的问题。为了解决这一问题, 缓存层引入Cuckoo Filter
表示全量的keys
。我们需要一个支持删除、可动态伸缩并且空间利用率高的Membership Query
结构, 产品最终选择Dynamic Cuckoo Filter
。Dynamic Cuckoo Filter
实现项目初期参考了RedisBloom
中Cuckoo Filter
的实现Key
降冷的收益最终采用将Key
和Value
同时从缓存层淘汰, 降低内存的收益很大。比如现网的一个业务, 总共有6620W
个Keys
, 在缓存全量Keys
的时候 占用18408MB
的内存, 在Key
降冷后仅仅占用593MB
- 智能淘汰 / 加载策略
- 作为冷热混合存储系统,热数据在缓存层,全量数据在存储层。关键的问题是淘汰和加载策略, 这里直接影响缓存的效率, 细分主要有两点:
- 当缓存层内存满时, 选择哪些数据淘汰
- 当用户访问存储层的数据时, 是否需要将其放入缓存层
- 首先介绍混合存储的淘汰策略, 主要有以下两个淘汰策略:
-
maxmemory-policy
当缓存层Redis
内存使用到达maxmemory
, 系统将按照maxmemory-policy
的内存策略将Key/Value
从缓存层驱逐,释放内存空间。(驱逐是指将Key/Value
从缓存层中淘汰掉, 存储层 和 缓存层的Cuckoo Filter
依然存在该Key
) -
value-eviction-policy
如果配置value-eviction-policy
, 后台会定期将用户N
天未访问的Key/Value
被驱逐出内存
- 缓存加载策略
- 为了避免缓存污染的问题(比如类似
Scan
的访问, 遍历存储层的数据,将缓存层真正的热数据淘汰, 从而造成了缓存效率低下) 。我们实现缓存加载策略:
- 仅仅将规定时间内访问频率超过某个阈值的数据加载到缓存中,这里的时间和阈值都是可配置的
- 基于
RDB + AOF
扩缩容
- 社区版
Redis
扩容存在的一些问题:
-
importing
和migrating
的设置不是原子的 - 搬迁以
Key
为粒度, 效率较低 - 大
Key
问题
- 基于社区版
Redis
的弥补流程是实现基于RDB + AOF
扩缩容
- 管控添加新节点, 规划待搬迁
slots
- 管控端向目标节点下发
slot
同步命令:cluster
slotsync
beginSlot
endSlot
[[beginSlot endSlot]...]
- 目标节点向源节点发送
sync [slot ...]
, 命令请求同步slot
数据 - 源节点生成指定
slot
数据的一致性快照全量数据(RDB
), 并将其发送给目标节点 - 源节点开始持续发送增量数据(
AOF
) - 管控端定位获取源节点和目标节点的落后值 (
diff_bytes
),如果落后值在指定的阈值内, 管控端向目标节点发送cluster slotfailover
(流程类似Redis
的cluster failover
, 首先阻塞源节点写入, 然后等待目标节点和源节点的落后值为 0, 最后将 搬迁的slots
归属目标节点)
同步层 Redis-sync
同步层 Redis-sync
模拟 Redis Slave
的行为, 接收 RDB
和 AOF
,然后并行地导入到存储层 Tendis
。同步层主要需要解决以下问题:
- 并发地导入到存储层
Tendis
, 如何保证时序正确? - 特殊命令的处理, 比如
FLUSHALL/FLUSHDB/SWAPDB/SELECT/MULTI
等? - 作为一个无状态的同步组件, 如何保证故障后, 数据断点续传?
- 缓存层和存储层 分别进行扩缩容, 如何将请求路由到正确的
Tendis
节点?
解决方案:
Slot
内串行,Slot
间并行 针对问题 1,Redis-sync
中采用与Redis
相同的计算Slot
的算法, 解析到具体的命令后, 根据Key
所属的slot
, 将其放到对应的 队列中(slot % QueueSize
)。因此同一个Slot
的数据是串行写入, 不同slot
的数据可以并行写入, 不会出现时序错乱的行为。- 串并转换 针对问题 2,
Redis-sync
会在并行和串行模式之间进行转换。比如收到FLUSHDB
命令, 这是需要将FLUSHDB
命令 前的命令都执行完, 再执行FLUSHDB
命令。 - 定期上报 针对问题 3,
Redis-sync
会定期将已发送给存储层的aof
的Version
持久化到 存储层。如何Redis-sync
故障, 首先从 存储层获取上次已发送的位置, 然后向对应的Redis
节点发送psync
, 请求同步。 - 数据自动路由 针对问题 4,
Redis-sync
会定期从存储层获取Slot
到Tendis
节点的映射关系, 并且维护这些Tendis
节点的连接池。请求从缓存层到达, 然后计算请求所属的slot
, 然后发送到正确的Tendis
节点。
存储层 Tendis Cluster
Tendis
是兼容 Redis
核心数据结构与协议的分布式高性能 KV
数据库, 主要具有以下特性:
- 兼容
Redis
协议 完全兼容redis
协议,支持redis
主要数据结构和接口,兼容大部分原生 Redis 命令。 - 持久化存储 使用
rocksdb
作为存储引擎,所有数据以特定格式存储在rocksdb
中,最大支持PB
级存储。 - 去中心化架构 类似于
redis cluster
的分布式实现,所有节点通过gossip
协议通讯,可指定hashtag
来控制数据分布和访问,使用和运维成本极低。 - 水平扩展集群支持增删节点,并且数据可以按照
slot
在任意两节点之间迁移,扩容和缩容过程中对应用运维人员透明,支持扩展至 1000 个节点。 - 故障自动切换 自动检测故障节点,当故障发生后,
slave
会自动提升为master
继续对外提供服务。