一、组件组成
-
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
继续对外提供服务。