存储的本质 | 青训营笔记

175 阅读8分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 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可以应用于 块存储,对象存储,文件存储上。

crush 算法 - 简书 (jianshu.com)

3.3 单机数据库

事务在单机内执行的数据库,也可以通过网络交互实现分布式事务

关系数据库

由于数据预先存在内存中,所以在写到内存之前,先写到WAL,防止数据丢失。

others存储超大表的多表查结果,内存暂时放不下,持久化到硬盘里。定期删除

聊聊redo log是什么? - 知乎 (zhihu.com)

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

能根据数据情况,判断使用行存储还是列存储,还是混合存储

4.3 高性能硬件

4.4 总结