X_DB需解决的问题
X_DB往往是在线系统和离线系统的边界。
- 存储的数据类型(table/kv/graph/file/embedding)
- 数据接口以如何形式组织
- 内部存储数据以如何形式组织
- 数据如何更新
- 数据有效期
- 数据如何版本回滚
- 如何扩容
- 最大可存储的数据量
- 最大可写的速度
- 最大可服务的吞吐量
- 最大可提供的吞吐速度
结合应用场景说话
- OLTP 写多读少。 每次读都读极小数据。 读要求低延时, 要求高并发
- OLAP 写少读多。 每次读会读大量数据。
- HTAP
- 机器学习场景: 离线对数据的要求是海量, 在线对数据的要求是低延迟
缓存无底洞
http://ifeve.com/redis-multiget-hole/
缓存雪崩
同一时刻大量cache时效,导入对底层数据库的冲击。 缓存雪崩发生场景 : 当Redis服务器重启或者大量缓存在同一时期失效时,此时大量的流量会全部冲击到数据库上面,数据库有可能会因为承受不住而宕机
缓存击穿
缓存击穿发生场景: 查询的数据在cache中过期了,在DB数据库里存在。一个并发访问量比较大的key在某个时间过期,导致所有的请求直接打在DB上。
- 大量缓存穿透该怎么办?
- 加锁(避免统一时间大量相同的key去击穿
缓存穿透
缓冲穿透发生场景 : 此时要查询的数据不存在,缓存无法命中所以需要查询完数据库,但是数据是不存在的,此时数据库肯定会返回空,也就无法将该数据写入到缓存中,那么每次对该数据的查询都会去查询一次数据库。
- 缓存穿透的解法:
- cache 空值
读热key
单个key读太热,使得分布式负载不平衡。
- 增加多副本,分担读流量
- 不要求强一致的用户,启用客户端本地缓存
- 对于瞬时流量突增场景,调整链接池,保持少量空闲链接
单个key写太热
- 应避免该情况发生
- 对数据可靠性要求不高场景,建议去掉从副本,降低由于写的过快增量同步跟不上,触发全量同步,进而阻塞master的风险
缓存预热
并发控制
- 悲观并发控制: 使用锁
- 读写锁
- 两阶段锁协议(2PL)
- 乐观并发控制
- 基于时间戳的协议:每一个事务都会具有一个全局唯一的时间戳,它即可以使用系统的时钟时间,也可以使用计数器,只要能够保证所有的时间戳都是唯一并且是随时间递增的就可以。
- 基于验证的协议
- MVCC
X_DB 技术架构
存储引擎
B+ Tree模型
LSM-Tree模型(log-structured merge-tree)
分片 和 弹性伸缩
分片在不同系统中有各自的别名,Spanner 和 YugabyteDB 中被称为 Tablet,在 HBase 和 TiDB 中被称为 Region,在 CockraochDB 中被称为 Range。
分布式哈希寻址
https://www.jianshu.com/p/fe7b7800473e
读写分离
数据一致性
Paxos
Raft 寻找一种易于理解的一致性算法
Gossip 弱
2PL两阶段锁
两阶段锁协议(2PL)是一种能够保证事务可串行化的协议,它将事务的获取锁和释放锁划分成了增长(Growing)和缩减(Shrinking)两个不同的阶段。在增长阶段,一个事务可以获得锁但是不能释放锁;而在缩减阶段事务只可以释放锁,并不能获得新的锁.
MVCC
(Multi-Version Concurrency Control):多版本并发控制 每一个写操作都会创建一个新版本的数据,读操作会从有限多个版本的数据中挑选一个最合适的结果直接返回;在这时,读写操作之间的冲突就不再需要被关注,而管理和快速挑选数据的版本就成了 MVCC 需要解决的主要问题。
事务处理能力
分布式kv存储具备海量的存储和服务能力,但没有事务能力。
NewSQL
NewSQL的基础是NoSQL. 在架构设计上倾向计算节点与存储节点分离。 Google的分布式数据库,有怎么样的特性。
- 关系型数据库,支持SQL、ACID事务
- 无论schema变更、主从同步,都保持99.999%以上可用率
- 根据请求负载和数据大小自动分片
PostgreSQL-XC
从单体数据库出发进行拓展分布式化,最大程度复用了单体数据库的工程实现,通过协调节点来协调大量的数据节点(单体数据库)。
WAL 机制
WAL(Write Ahead Log)预写日志,是数据库系统中常见的一种手段,用于保证数据操作的原子性和持久性。 修改并不直接写入到数据库文件中,而是写入到另外一个称为 WAL 的文件中;如果事务失败,WAL 中的记录会被忽略,撤销修改;如果事务成功,它将在随后的某个时间被写回到数据库文件中,提交修改。
硬件加速
持久化内存 AEP AEP是Intel最新研发的持久内存产品,具备掉电数据不丢失、低成本(DRAM的1/3)、高性能(DRAM的1/7~1/4)等特性 AEP的读写不对称,读性能明显优于写性能
固态硬盘 SSD 写放大问题: 写入放大(WA)是闪存和固态硬盘之间相关联的一个属性,因为闪存必须先删除才能改写(我们也叫“编程“),在执行这些操作的时候,移动(或重写)用户数据和元数据(metadata)不止一次。这些多次的操作,不但增加了写入数据量,减少了SSD的使用寿命,而且还吃光了闪存的带宽(间接地影响了随机写入性能)
SQL引擎 query planner
SQL是一种查询语言,是对数据库查询的抽象。SQL不仅可用于简单的查询,借助UDF、UDAF、UDTF的能力,可以完成复杂的计算逻辑,因而就成了一门编程语言。
sql数据库的服务端,可以划分为执行器 (Execution Engine) 和存储引擎 (Storage Engine) 两部分。
- 执行器
- 输入是SQL
- 把SQL解析构建成AST 抽象语法树
- 把AST解析成plan逻辑执行计划
- 存储引擎
https://github.com/google/zetasql https://github.com/apache/calcite https://github.com/antlr/antlr4
执行计划定义了:
- 访问存储表的顺序
- 用于从每个表提取数据的方法
- 用于计算的方法
X_DB 开源实现
数据库的实现类型非常之多,没有最好的数据库,只有最适合的数据库,一定要根据业务的特点进行选择。 2021年,开源数据库的Popularity首次超越了商用数据库。https://db-engines.com/en/ranking_osvsc
- 键值存储:常见的键值存储数据库有 Redis、Memcached、leveldb、rocksdb、tair、etcd
- 行式存储:常见的行式数据库有 MySQL、PostgreSQL、MS SQL Server。
-
列式存储:常见的列式数据库有 Hbase、Kudu、ClickHouse、 Vertica、 Paraccel (Actian Matrix,Amazon Redshift)、 Sybase IQ、 Exasol、 Infobright、 InfiniDB、 MonetDB (VectorWise, Actian Vector)、 LucidDB、 SAP HANA、 Google Dremel、 Google PowerDrill、 Druid、 kdb+。
- 关系型数据库 : mysql
- 时序数据库: InfluxDB
- 键值数据库: redis
- 图数据库: neo4j
SQLite
https://sqlite.org/arch.html
SQLite的工作方式是将SQL语句转换为字节码,然后执行该字节码。
- sqlite3_prepare_v()接口是一个将SQL语句转换为字节码的编译器
- sqlite3_step()接口 是负责运行含有预处理语句的字节码的虚拟机
VDBE实现了以虚拟机语言运行程序的虚拟计算机。每个程序的目标是询问或更改数据库。为此,VDBE实现的机器语言专门用于搜索,读取和修改数据库。
InnoDB
MySQL 关系数据库
需要了解什么场景适合mysql,什么场景不合适mysql
Redis
出于访问性能,将数据库放在内存。
redis 实现高并发主要依靠主从架构,一主多从
哨兵模式 Sentinel
pipeline命令 允许client将多个请求依次发给服务器(redis的客户端,如jedisCluster,lettuce等都实现了对pipeline的封装),过程中而不需要等待请求的回复,在最后再一并读取结果即可。
Pika Pika是一个可持久化的大容量redis存储服务 https://github.com/Qihoo360/pika
redis支持存储类型除了字符串之外还有list/hashtable/set等容器结构。不过并不能支持容器中每个元素单独设置自己的过期时间 只能自己想办法达到近似的效果
Redis slot 使用分配slot的方式进行key路由。Redis Cluster为整个集群定义了一共16384个slot,并通过crc16的hash函数来对key进行取模,将结果路由到预先分配过slot的相应节点上。 假设有集群设置50个分片,那么每个分片节点上均摊16384/50个slot.
Redis的过期策略 设置指定key的过期时间
- 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
- 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
- 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。(expires字典会保存所有设置了过期时间的key的过期时间数据,其中key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)
Redis的内存淘汰策略 系统内存不足时,主动淘汰一些key。
- no-eviction:当内存不足以容纳新写入数据时,新写入操作会报错。
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。
- allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
- volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
- volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
- volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
Tair
LevelDB
单机数据库,一次只允许一个进程访问一个特定的数据库 适合场景 高频写、低频读
- Memtable内存结构: 跳表结构,新数据首先写入这里
- Immutable Memtable内存结构: 写入数数据逐渐变为不可写入的数据
- SST文件:磁盘数据存储文件
Arena内存池
RocksDB
RocksDB 的核心数据结构是 LSM-Tree
PinnableSlice 零拷贝方式的读
RocksDB Column Family
每一个KV对都会关联一个Column Family, 其中默认的Column Family是 “default”. Column Family主要是提供给RocksDB一个逻辑的分区. 从实现上来看不同的Column Family共享WAL,而都有自己的Memtable和SST
RocksDB 是一个基于 LevelDB 衍生的键值存储数据库,它内部的数据以 ColumnFamily(列族,亦有译为列簇)为逻辑单位进行存储.
当用户发出读取请求时,RocksDB 先从 MemTable 查找;如果没找到,再查找不可变的 MemTable,随后再磁盘上进行逐级查找.
MongoDB
Cassandra
etcd
OPENTSDB 时序数据库
Time Series Database, 对比传统数据库仅仅记录了数据的当前值,时序数据库则记录了所有的历史数据。同时时序数据的查询也总是会带上时间作为过滤条件。
InfluxDB 时序数据库
InfluxDB是一个由InfluxData开发的开源时序型数据。它由Go写成,着力于高性能地查询与存储时序型数据。InfluxDB被广泛应用于存储系统的监控数据,IoT行业的实时数据等场景。
Lucene
Java实现的文本搜索库。使用倒排索引来实现高效的文本查询。
ElasticSearch
你说它是搜索引擎也好,也是有很多场景把ELK作为数据库的。
ES-Hadoop 项目, 相当于hadoop MR的一个 ES connector。
Hbase
Hbase,其实是Hadoop Database的简称,本质上来说就是Hadoop系统的数据库,为Hadoop框架当中的结构化数据提供存储服务,是面向列的分布式数据库。
HDFS是Hadoop的存储系统,它的优点是可以存储超大量数据,但是缺点是速度慢。 HBase在HDFS中存储的数据文件称为HFile。这些HFile文件是HBase用来存储表数据的底层文件格式。HFile是HBase的核心存储格式,设计用来高效地进行随机读写操作。 HBase建立在HDFS之上,以KV的形式存储,提供实时访问。 HBase原生只提供了Java 的API 接口。 snapshot是HBase非常核心的一个功能,使用snapshot的不同用法可以实现很多功能
- Region Server
- HBase 表(Table)根据 rowkey 的范围被水平拆分成若干个 region。每个 region 都包含了这个region 的 start key 和 end key 之间的所有行(row)
- HFile 文件
- KeyValues 有序存储, 文件内部是append形式顺序写的。
- HBase Master
- 负责 Region 的分配,DDL(创建,删除表)等操作
HBase中的数据是按照Rowkey的ASCII字典顺序进行全局排序的 scan查询,用于读取多行数据。可以指定起始行键和结束行键来定义扫描的范围。
查询结果保序 https://hbase.apache.org/book.html#dm.sort
Neo4j
https://github.com/neo4j/neo4j
NebulaGraph 图数据库
https://github.com/vesoft-inc/nebula https://docs.nebula-graph.io/3.2.0/
Dgraph 图数据库
https://github.com/dgraph-io/dgraph
TiDB 分布式事务数据库
在线场景
- TiKV Server 存储引擎
- 分布式事务
- TiDB Server 查询引擎(OLTP)
- SQL -> KV操作
离线场景
- TiFlash Server 存储引擎
- 列式存储,异步复制,一致性
- TiFlash 以低消耗不阻塞 TiKV 写入的方式,实时复制 TiKV 集群中的数据,并同时提供与 TiKV 一样的一致性读取,且可以保证读取到最新的数据。TiFlash 中的 Region 副本与 TiKV 中完全对应,且会跟随 TiKV 中的 Leader 副本同时进行分裂与合并。
- 当前的设计是TiFlash只读不写,任何数据必须先写入 TiKV 再同步到 TiFlash。
- TiSpark 查询引擎(OLAP)
- Most of the TiSpark logic is inside a thin layer, namely, the tikv-client library.
- tidb_catalog
- tidb_snapshot tidb_snapshot 是可以指定任意的时间吗?
OpenMLDB
OpenMLDB 致力于融合离线数据库和在线数据库。亮点在于一致性执行引擎。 [openmldb的技术博客]https://www.zhihu.com/column/c_1417199590352916480
一致性执行引擎:内部去把 SQL 做了一个转换,转换成线上的执行计划和线下的执行计划,保证这两个值是从定义和执行逻辑上都是一致的。 性能:在线支持毫秒级别延迟的取数+计算! 计算语义:SQL, 提供C++语言的UDF方式来支持用户实现复杂的处理逻辑
Tablet 是 OpenMLDB 用来执行 SQL 和数据存储的模块,也是整个 OpenMLDB 功能实现的核心以及资源占用的瓶颈。Tablet 从功能上来看,进一步包含了 SQL engine 和 storage engine 两个模块。Tablet 也是 OpenMLDB 部署资源的可调配的最小粒度,一个 tablet 不能被拆分到多个物理节点;但是一个物理节点上可以有多个 tablets。
- SQL Engine
- SQL engine 负责执行 SQL 查询计算。SQL 引擎通过 ZetaSQL 把 SQL 解析成AST语法树。因为我们加入了 LAST JOIN,WINDOW UNION 等针对特征工程扩展的特殊 SQL 语法,所以对开源的 ZetaSQL 做了优化。经过如上图一系列的编译转化、优化,以及基于 LLVM 的 codegen 之后,最终生成执行计划。
- Compute Engine
- SQL 引擎基于执行计划,通过 catalog 获取存储层数据做最终的 SQL 执行运算。在分布式版本中,会生成分布式的执行计划,会把执行任务发到其他 tablet 节点上执行。目前 OpenMLDB 的 SQL 引擎采用 push 的模式,将任务分发到数据所在的节点执行,而不是将数据拉回来。这样做的好处可以减少数据传输。
- 离线 spark
- 在线 hybridse
- Storage Engine
- Storage engine 负责 OpenMLDB 数据的存储。
- 内存存储引擎是自研的(MemTable), 内部它是一个双层的跳表结构,这有利于我们去高效地拿到窗口化的数据。
- 双层跳表:在第一层跳表中key是对应索引列的值,value指向二级跳表。二级跳表中的key是时间戳,value是一行数据编码后的值。二级跳表是按时间排好序的,这样就很容易查询一段时间内的数据。
- 磁盘存储引擎是rocksdb(DiskTable)
- 内存存储引擎是自研的(MemTable), 内部它是一个双层的跳表结构,这有利于我们去高效地拿到窗口化的数据。
- 导数:
- OpenMLDB本身不提供离线存储引擎,但需要指定离线存储的地址,即taskmanager配置项offline.data.prefix,可以是本地目录、hdfs、s3等存储介质。
- Storage engine 负责 OpenMLDB 数据的存储。
PlanAPI::CreatePlanTreeFromScript
zetasql::ParseScript
auto planner_ptr = std::make_unique<SimplePlannerV2>(node_manager, is_batch_mode, is_cluster,
enable_batch_window_parallelization, extra_options);
status = planner_ptr->CreateASTScriptPlan(script, plan_trees);
ConvertASTScript
CreatePlanTree
YugabyteDB
cockroachDB
X_FS
广义上讲,文件系统是数据库系统的一种形式。
- 文件存储
- 对象存储:与文件存储相反,这些对象存储在单个平面结构中,没有文件夹层次结构。在对象存储中,与文件存储使用的嵌套分层结构不同,所有对象都存储在平面地址空间中。
存储接口标准: AWS S3标准协议 (Amazon Simple Storage Service (Amazon S3) )
ChubaoFS
ChubaoFS(CFS)是京东开发的分布式文件系统和对象存储系统 https://chubaofs.readthedocs.io/zh_CN/latest/