RDB
本文关键字: LSM、RocksDB、Raft、Redis Cluster
诞于脑洞
KV 的现状
Redis
目前大部分场景下处于「性能过剩」而「存储短缺」的状态,当然也可以发动「钞能力」HBase
作为老牌的LSM
树实现的DB
,写性能还算过得去,但是读性能就十分磕碜了- 早期也有
Twiter
开源的twemproxy
方案来做Redis、Memcached
基于Hash
的Cluster
实现可惜只能「单线程跑」,且数据扩缩容十分不便 Nefix
参考AWS DyanmoDB
白皮书实现的Dynomite
,目前已经不怎么维护了,且一堆Bug
,有过邮件交流但是无果。PS: 当初接SpringCloud
也被坑惨了- 近几年也涌现了基于
TiKV
生态的兼容Redis 协议
的KV
例如:Titan
等 - 另起炉灶的
Pika
目前还是推荐用15
年左右火爆的Codis
引入更多的「基础组件」来做集群管理,同时也在自行实现另一套Cluster
的管理逻辑 - 大部分的方案最底层采用
Facebook
基于Google
的LSM tree
实现的LevelDB
再针对SSD
优化而来的RocksDB
- 当然还有很多很多
KV
存储在冒出来,例如: 腾讯云的Tendis
、阿里大佬整的Tair
、美图大佬整的KVrocks
,兼容Redis
协议,字节魔改的RocksDB
等等,是不是发现很眼熟似乎大部分都是基于RocksDB
的 Redis Labs
也推出了基于SSD
的存储版本,当然不是免费给你用的啦,可惜的是我还没去实际测试过它- 似乎存在不少的场景只要简单的
KV
存储,但是需要大量的存储空间,再贪心一点点「追求一下」「性能」似乎能选择的就不多了 - 这里扯到了「性能」我为什么试着搞一个简单的
KV
而不是像Redis
一样全都要,甚至还支持各种数据结构,譬如:SQL
的拓展插件,个人感觉 Redis 是成功到奇葩的数据库了- 其实原因很简单,之前也有参与以为小伙伴的
sdb
前期实现的讨论,结论是如果要支持各种数据结构那就是要「加锁」的,甚至incr
也是,有兴趣的小伙伴可以跳转到 sdb 去了解一波
- 其实原因很简单,之前也有参与以为小伙伴的
- 说白了
KV
的集群方式也就是两种Hash
- 一致性
Hash
: 譬如Dynomite
,相比普通Hash
当节点上下线时数据移动少 - 普通
Hash
: 譬如twemproxy
- 一致性
Raft
- 单
Raft
: 譬如sdb
,Raft
之前以ETCD
为代表,如果完全参考Raft
论文实现,性能会非常差,况且所有的写请求都只能由Leader
处理,可参考Raft
作者实现的 Logcabin Mutl-raft
: 譬如TiKV
,有多少个Raft
实例就相当于有多少个写入口
- 单
- 总结下现在已经迈出了当初需要自己解决
Redis
集群问题的时代,但是依然没有解决持久化
的问题 - 正好兄弟团队业务场景上需要一款高性能写入同时读性能不能太差的业务场景,之前有试过
Hbase
,据说写了2
个星期数据还没写完... - 所以我决定撸一个能够「开箱即用」、「可持久化」、「可扩展」、「够稳定」、「够简单」的数据库,仅仅聚焦于简单的
KV
存储- 之前有看到过一篇文章让我感触很深「为什么国内出不了百亿的 SaaS 公司?」
- 大概有这么几点:
- 不敢做取舍,什么都想要,但是很可能什么都做不好
- 这里
AWS
让我肃然起敬,基本是专注于最底层,然后拉合作,譬如CloudWatch
真的很难用,但是大名鼎鼎的DataDog
的底层却是用的它,还有EKS、ELB
等等等,跑题了...
- 这里
- 「用户成功」和 「为客户创造」价值是口号
- 譬如: 有用
A/B
两款省成本的平台,A
是海外的公司的收费标准是从省掉的钱里抽x%
,B
是家国内的公司,采用一次性付费的方式,当我知道他们的收费模式之后心中已经有倾向了。
- 譬如: 有用
- 不敢做取舍,什么都想要,但是很可能什么都做不好
目标
大致的方向
- 低成本(高性能)
- 大存储(持久化)
- 易使用(
Redis Cluster Client
接入,单体架构没有Proxy
,没有注册中心) - 可扩展 (基于
Single Raft
管理集群) - 高可用 (拥抱
Redis Cluster
生态)
具体的实现
- 借用
NewSQL
中的一极CockRoach
参考RocksDB
实现的Pebble
,来实现单点的可持久化的KV
存储,来解「决低成本」、「高性能」的存储问题LSM
的读写复杂度O(1) / O(logn)
、而 B+ 的读写复杂度O(logn)
LSM
Red Black Tree
VSLevelDB
的Skip List
实现,Skip List
用数据概率来实现了大概率的均分分配,代码逻辑相对简单,也能免去树的旋转。
- 兼容
Redis Cluster
协议- 主流的技术栈都可通过
Redis Cluster Client
快速接入(这样可以帮我解决很多其他类型DB
必须在Server
上实现的逻辑,其实也可以考虑另起炉灶上RPC
) - 这里做了些思考最终做了「取舍」,如果「自定义协议」大概率在性能上是可以胜过
Redis
协议的,举个例子:Memcached
更为紧凑的二进制协议 - 其实就是在「易用性」和 「效率」中做选择,实际的 DB 性能表现瓶颈也是在硬盘的
IO
上
- 主流的技术栈都可通过
- 借助
Redis Cluster Slots
实现可扩展的集群,支持基于Cluster
的Hash Tag
- 接入
Single Raft
来替代过于臃肿的Gossip
协议管理集群信息 - 其他功能,原则上「不引入任何降低」目前命令(
get/set/mget/mset
)性能的特性- 添加
Cache
层,做数据的冷热分离 - 魔改
Redis Cluster
协议实现Leader - Follower
的读写分离
- 添加
- 实现必要的
Redis
运维命令,如: 服务的关键性指标的统计、一键扩缩容等
调研
-
Redis
「可持久化」存储版百家争鸣 -
Redis Cluster
的发展历程14 - 16
年打杀四方的Codis
,听说当时很多大厂也在用,顺便提一嘴,其实Codis
的核心开发就是现在PingCAP
的CTO
-Edward Huang
大佬- 借助
Codis
的各种组件套壳的KV Cluster
而实现从单机到集群的组建 Redis Cluster
官方的简单易用的实现停止了Redis Cluster
还需要各种折腾的时代
-
之前提到的解法基本都是
RocksDB
,既然有了TiDB
的TiKV
甚至天生就能组集群了,还有必要自己整KV
吗?- 不在乎成本是可以的,
19
年试用TiDB Cloud
至少是3 * 8C16G TiKV
+2 * 8C16G PD
起步了 TiDB
的基本思路RocksDB
解决存储问题Raft
解决集群问题- 设计
KV-SQL
的映射规则
TiDB
的基本概念Region
Raft
&&Muti-Raft
- 那
TiDB
是如何把KV
变成SQL
的呢? - 最近
TiDB
又弄了一个TiFlash
似乎想一统OLAP
和OLTP
,emmmmm
既然扯到了SQL
了就和rdb
没啥关系了
- 不在乎成本是可以的,
选型
B+ / LSM or LSM + B+
?LSM
:HBase
LevelDB -> RocksDB -> Pebble(CockRoachdb)
- 基于
LSM tree
在存储引擎的级别已经做好了Key
的排序 - 必然存在「读写放大」的问题,而且「数据越多」就「越严重」
- 单节点
500G
的情况下能做到「线上使用情况」的性能数据 - 如果从极限的角度去算把
Redis Cluster Slots
全部耗尽,将是16384 * 500G
约为8PB
的数据,当然如果没那么看重读性能,可以往单节点1T
的数据量级试试
- 基于
B+
:botlDB
:ETCD
的底层数据就是存储在Golang
版本的实现,这是一个非常精简的B+
/MVCC
实现,核心代码不过几千行,非常值得一读
B+ + LSM
: 一位B
站应届生写的,同样代码十分精简,想法十分骚气,我差点就用它来做底层存储了。LotusDB
- 文档型数据库
B or B+(Append-only B+
MongoDB
: 特别有意思的选择了B
,因为如果当做KV
文档存储来用,不需要太多的范围查询,B
的查询效率反而会更高- CouchDB: 总结一下,只有追加操作并利用
MVCC
去锁化
Memcached
、Redis
orCoucseBD
Memcached
: 单从KV
来看性能是优于Redis
的,据说快手的重保项目上来就是几十万个缓存节点,Redis Cluster
也没法组这么大的集群,之前用C++
标准库撸过一个Memcached
的Demo
比官方面向内存编程的C
的实现多消耗了30%
的内存CoucseBD
: 用Restful
的方式提供服务,所以存在HTTP
报文比实际数据还要大的问题
Lucene-FST
与Tire Tree
: 这里更多的检索类(倒排索引)的DB
了,Lucene-FST
类似Hashmap
但是内存用得更少而查询速度要慢,朴素版的Tire Tree
就很吃内存了,当然也有不少优化的版本,例如: 三元Tire Tree
。啊哈哈哈该项显然不是我们的选择方向。
Roadmap
-
- 单节点支持
pebble
- 单节点支持
-
-
set/mset/get/mget
常用命令支持
-
-
- 支持
Redis Cluster
通过redis-benchmark
&&redis-py-cluster
的测试
- 支持
-
- 引入
Single Raft
- 引入
-
- []
cluster
数据交给Raft
管理
- []
-
- [] 支持一键扩缩容
-
- [] 支持更多的数据压缩算法(目前是
snappy
,想试试Kafka
较新版本所支持的的zstd
),得到硬盘与 CPU「成本和性能」的关系性结论
- [] 支持更多的数据压缩算法(目前是
-
- [] 添加
Prometheus
的关键性监控指标
- [] 添加
-
- [] 添加
Cache
层,做数据的冷热层
- [] 添加
-
- [] 魔改
Redis Cluster
协议实现Leader - Follower
的读写分离
- [] 魔改
-
- []
sdb
特性冻结
深入pebble
进一步的性能优化
- []
上线使用情况
集群总数据大概为 2T
左右,目前 7
台 8C
的节点可承载 30W QPS
大概 100Bytes
的 KV
写入,大概 10W QPS
的读取(瓶颈在 IO
...),顺便一提 15
个小时内即可完成兄弟团队的数据写入需求了