这是我参与「第五届青训营 」伴学笔记创作活动的第 9 天
1. 数据的产生
用户输入 ==> 前端收集 ==> ajax/http请求 ==> 以特定序列化方式传入后端
数据流动
- 检验数据合法性(前端/后端)
- 使用高效的数据结构组织数据
- 以寿命&友好的方式持久化到硬件
2. 存储系统和数据库
存储系统:提供了读写和控制类的接口,能够安全有效的把数据持久化到硬盘的结构
特点
- 性能敏感:几乎所有的请求都要和后台底座--存储系统交互
- 硬件依赖:由于数据最后要落盘,因此存储系统的性能依赖于硬件存储(操作系统API,硬件驱动,硬件成本等) ==> 新型的硬件可能直接导致软件重构
- 代码简单又复杂:简单==> IO路径代码简单,以保证性能。 复杂 ==> 需要考虑硬件损坏,软件问题,考虑多种异常情况,分支复杂(java.io包几乎每个方法都要抛出各种各样的异常)
2.1 存储系统结构
- 上一层是下一层的高速缓存
- 越往上,存储容量越小,速度越快,代价(单位存储需要的金额)越大
2.2 数据如何应用到存储设备
操作系统API
硬件驱动实现:厂商很可能一个byte一个byte的写入数据。
软件分层:buffer的重要性
拷贝很重要:拷贝很耗费CPU资源
Disk:硬件存储设备五花八门,需要一个统一接入层。
2.3 RAID技术
- 条带化处理 ==> 并行写的能力,大幅度提高吞吐量
- 冗余存盘 ==> 提高数据可靠性
- 容错校验 ==> 提高数据可靠性
2.4 数据库和存储系统的区别
数据库是存储系统,但是提供了很多额外的功能
关系:元素通过关系运算产生的集合。
SQL:领域特定语言(DSL),在特定领域方便人类阅读的关系代数表达形式
关系代数:笛卡尔积 ==> JOIN 等等...
SQL通常是表达式语言,但是SQL也有块级的作用域,比如存储过程,触发器这些。主要看SQL的执行引擎实现
2.5 数据库 vs 经典存储系统
- 存储形式
存储形式:表结构
落盘形式:自定义数据结构
-
事务能力
-
复杂查询能力
领域特定语言的好处:虽然引入了额外的学习成本,但是在领域特定方向,想要表达相同含义时,DSL往往具有灵活简洁的特性
2.6 数据库使用方式
SQL ==> DQL, DDL ...
3. 主流产品剖析
- 单机存储:存储在本机,不涉及网络交互。分为本地文件系统,KV存储等
3.1 单机存储 -- 本地文件系统
文件系统:提供了对数据的高校存储和操作方法。
不同的文件系统:xfs,ext,fat,msdos。。。 ==> 遵循统一规范:VFS
linux一切皆文件 ==> 重要数据结构 index node, dictionary entry 用目录树的方式,以inode管理所有文件。
- inode是文件的唯一标识,inode不存储文件名信息
- dictionary entry是内存结构,和inode 的关系为1:N,dictionary可以看作Map<fileName, Inode>,并且存储了层级关系,构成了目录树
3.1 单机存储 -- KV存储
KV存储同样提供了对KV数据的高效存储和操作
存储结构:LSM Tree
操作:put,get ... scan_prefix
LSM树,并不是传统的树形数据结构。只是其组织形式很像一颗树。
Log-structure-merge tree
特点:顺序写入(写入性能提高,顺序写入对于直接寻址的存储器,效果不错)。但是读取性能下降(分层设计)
Memtable + WAL(write ahead log)。
数据首先写入缓存中,对于KV存储而言很实用。需要保证KV数据按照key进行有序存储。因此需要借助辅助的数据结构,如HBase中存储有序KV,使用了跳表
同时,由于数据写入内存中,一旦宕机数据丢失,于是采取WAL,write ahead 写在内存之前,持久化到WAL,记录了所有的写入操作
Immutable MemTable 数据从内存到落盘的中间状态。从Memtable到Immutable MemTable 这意味着Memtable是可变的,因此以HBase为例,通常修改Memtable的内存存储格式,将其由冗余较大的skiplist 展开为数组存储,到了Immutable MemTable意味着数据不会再发生改变
SSTable:KV数据最终的落盘存储格式。缺点:合并过程中存在冗余数据,可以在compact阶段消除冗余数据。同时可以建立索引加速查找(因为key是有序的,所以索引有效)。还可以借助辅助数据结构,如布隆过滤器加速查找。
compact策略
- LSM是分层的,而且层越靠下,数据量越大。这也使得LSM有了树形的结构。
compact解决&引入了什么问题
- 读放大:由于分层结构,读一次需要递归读取。MemTable没有,则去Immutable Memtable读,然后继续SSTable0,SSTable1 ... 什么时候读到什么时候结束 ===> 至少读一次
- 写放大:写一条KV可能导致某一层达到阈值,会进行compact,导致一次写入的数据量远大于一个KV。
- 空间放大:存储大量冗余数据,导致空间放大,需要进行合并
1) size-tiered 策略
通过限制每一层SSTable的数量,限制每一层的容量,如果超出容量,则进行compact,将多个SSTable合并为一个更底层的SSTable。
缺点:最底层的SSTable量极大。这就导致下一次compact操作来的很慢,进而导致冗余数据无法被及时压缩,导致空间放大
2) leveled策略
分层思想:限制每一层的最大容量
超过每一层的阈值时,进行compact。
将levelN层和levelN+1层冗余数据进行压缩。压缩后,levelN+1层保留冗余数据的最新数据,levelN层和levelN+1无冗余。
优点:levelN和levelN+1如果互相独立,可以并行执行
缺点:如果levelN层数据跨度比较大,可能会导致levelN+1层全量合并 (因为levelN层相当于levelN+1层的高速缓存,理论上levelN层的数据只是levelN+1层的小部分,就算有冗余也应该集中在levelN+1层的小部分块。但如果levelN层冗余数据分布很广,也可能导致levelN层的每一个小块,都在levelN+1层的大块上存在冗余数据,正常来说应该是levelN层多个块对应levelN+1层一个块)
3.2 分布式存储HDFS
分布式存储:
单机存储 + 分布式协议
弱POSIX语义:hdfs不支持单机文件系统上的一些操作。比如rename在hdfs的某个实现上是很难做到的一件事。
3.2 分布式存储Ceph
Ceph可以应用于 块存储,对象存储,文件存储上。
3.3 单机数据库
事务在单机内执行的数据库,也可以通过网络交互实现分布式事务
关系数据库
由于数据预先存在内存中,所以在写到内存之前,先写到WAL,防止数据丢失。
others存储超大表的多表查结果,内存暂时放不下,持久化到硬盘里。定期删除
3.4 单机数据库-非关系数据库
非关系数据库的内卷化
努力支持SQL
努力扩大事务的生命周期
没有特定的表结构。查询语言多样性,比如es采取领域特定语言,序列化方式采取JSON,通信协议基于http
es采取倒排索引,如果关键词含有索引,就可以定位到文档,这使得es天生具备模糊查询功能 ==> 结构决定功能
3.5 分布式数据库
解决问题:容量,弹性,性价比
容量+弹性:存储资源池化 ==> NFS
存储资源的聚合 对外提供逻辑磁盘。磁盘使用者无需关注逻辑磁盘的内部,可以想象自己工作在一个支持无限扩缩容的大存储空间上
挑战
- 单写 ==> 多写
- 磁盘弹性 ==> 内存弹性(LinkedBuffer),TLAB 这些都是软件层面的弹性内存,能否构建硬件化的内存池?(如果采取池化,通常都是网络文件系统,时延比较高,这意味着如果用网络存储系统充当内存,其开销是无法忍受的)
- 分布式事务优化 ==> zookeeper+状态机+状态处理函数 两阶段提交
4. 新技术演进
4.1 SPDK
传统阻塞IO ==> 执行系统调用,需要切换到用户态,涉及一次线程调度。
当IO事务比较多时,应当用非阻塞的方式,CPU主动轮询,而不是阻塞 + 异步等待中断答复
尽量减少锁的使用,减少数据的拷贝等
4.2 AI Storage
能根据数据情况,判断使用行存储还是列存储,还是混合存储