【字节青训营-5】:初探存储系统与数据库及技术原理,解析关系型、非关系型数据库

本文为笔者参加字节青训营时听字节青训营的课时所做课堂笔记。

本文目录

一、一条数据的生命周期

假设一条新用户注册的信息数据如下:

go 复制代码
{
	"user_name":小明,
	"password":"helloworld",
	"password_hint":"coding",
	...
}

这个数据会发送到后端服务器,然后最后会到数据库系统,只有到了数据库系统,才能算数据的持久化。(一般在数据库系统后面还有其他系统。)

当数据到达数据库系统之后,首先需要进行校验数据的合法性,如果满足,那就用高效的数据结构组织数据(修改内存),写入存储介质(以寿命性能友好的方式写入硬件)。

数据库系统在整个数据传递链路上可以说是最后一环,那么这个过程会产生一些问题,比如:

1、数据库怎么保证数据不丢?

2、数据库怎么处理多人同时修改的问题。

3、除了数据库还能存到别的存储系统吗?

4、数据库只能存储结构化的数据吗?

5、有哪些数据库的方式,用什么编程语言?

二、存储系统

2.1 存储系统的特点

存储系统是一个提供了读写、控制类接口,能够安全有效的把数据持久化的软件,就可以称之为存储系统。

存储系统需要关注很多方面,比如用户、介质、内存(数据结构与组织)、网络等。

存储系统有以下几个特点:

1、作为后端软件的底座,性能敏感。很多都是依赖于持久化状态的服务,所以必然涉及并发、大量的操作。

2、存储系统的代码,简单又复杂。在读和写的方面不能太复杂,需要比较简单。在IO路径的错误上要考虑很多种情况(比如硬件损坏等等)。

3、存储系统的软件架构,容易受到硬件影响,比如各种磁盘,一旦磁盘进行了变革,软件系统也要同步的进行变革。

2.2 存储系统的存储器层级结构

顶端的设备就是极小的容量+超高性能的访问。

对应的底部的特点就是超大的容量+读写速度非常慢,访问方式也有可能及其不友好(比如磁带等,只能顺序播放等)。

在中间层,有两者兼顾的存储器设备,比如 Persistent Memory。

2.3 数据怎么从应用到存储介质

在链路上,缓存很重要,贯穿整个存储体系。

拷贝很昂贵,应该尽量减少。

同时硬件设备五花八门,需要有抽象统一的接入层。

图片摘自linux progamming书。

2.4 RAID技术

单机存储系统怎么做到高性能/高性价比/高可靠性?

RAID: Redundant Array of Inexpensive Disks,(廉价磁盘冗余阵列),是一种将多个磁盘驱动器组合成一个逻辑单元的数据存储虚拟化技术。RAID 的主要目的是提高数据的可靠性、容错性和性能。它通过在多个磁盘上分散数据(称为条带化)以及在某些配置中提供冗余(镜像或奇偶校验)来实现这些目标。

RAID出现的背景有(其实就是追求性价比):单块大容量磁盘的价格>多块小容量磁盘的价格。单块磁盘的写入性能<多块磁盘的并发写入性能。单块磁盘的容错能力有限,并不是很安全。

RAID的常见分级如下:

  • RAID 0(条带化)

数据被分割成多个块,然后分散存储在两个或多个磁盘上。

提高性能,因为没有单点故障,读写操作可以并行执行。

没有冗余,没有容错设计,如果任何一个磁盘失败,所有数据都会丢失。

  • RAID 1(镜像)

数据被复制到两个或多个磁盘上,创建数据的镜像。真实的空间利用率只有50%左右。

提高数据的可靠性,因为即使一个磁盘失败,数据仍然可以从其他磁盘读取。

性能可能略有提升,因为读操作可以并行执行。

成本较高,因为需要相同数量的磁盘来存储数据和其镜像。

  • RAID 5(分布式奇偶校验)

数据和奇偶校验信息被分散存储在三个或更多的磁盘上。

提供冗余和容错能力,可以承受一个磁盘的失败而不会丢失数据。

性能较好,因为读写操作可以利用多个磁盘。

需要至少三个磁盘来实现。

  • RAID 6(双奇偶校验)

类似于 RAID 5,但有两个奇偶校验块,可以承受两个磁盘的同时失败。

提供更高的容错能力。

需要至少四个磁盘来实现。

  • RAID 10(或称 RAID 1+0)

结合了 RAID 1 的镜像和 RAID 0 的条带化,先镜像再条带化。

提供高性能和高可靠性。(真实空间率也只有50%,但是容错能力强,写入带宽好)

成本较高,因为需要较多的磁盘。

三、数据库和存储系统

首先有个比较经典的问题"数据库和存储系统不一样吗?"

要弄清楚这个问题我们需要知道关系型数据库和非关系型数据库的区别。

首先我们定义"关系"是什么,就是实体之间的联系(集合、反映了事物间的关系)。那么对应的关系代数就是指的对关系进行运算的语言。

另外就是SQL,SQL=一种DSL(Domain Specific Language)=方便人类阅读的关系代数表达形式。

所以我们可以给出结论,关系型数据库是存储系统,但是在存储之外,又发展出了其他能力

其他的能力包括:对结构化数据友好、支持事物ACID、支持复杂查询语言。

非关系型数据库也是存储系统,但是一般不要求严格的结构化,对半结构化的数据友好,可能支持事务ACID,也可能支持复杂查询语言。

事务具有ACID:

A(Aotmicity),即原子性,事务内的操作要么全做,要么不做。如果事务中的某个操作失败,那么整个事务将会回滚到事务开始前的状态,就像这个事务从未执行过一样。这是通过数据库的回滚机制(rollback)来实现的,确保了事务的不可分割性。

C(Consistency),事务执行的前后,数据状态是一致的。这意味着即使在系统故障的情况下,数据库也不会处于不一致或无效的状态。

I(Isolation),可以隔离多个并发事务,避免影响。隔离性保证了并发执行的事务之间的操作不会互相干扰。每个事务都像是在一个独立的环境中运行,对其他事务不可见,直到事务完成并提交。数据库通常提供不同的隔离级别,如读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable),以在并发控制和性能之间取得平衡。

D(Durability),事务一旦提交成功,数据保证持久性。持久性意味着一旦事务被提交,它对数据库的更改就是永久性的,即使系统发生故障也不会丢失。这通常通过将事务的更改持久化到磁盘上的日志文件或数据库文件来实现。

四、单机存储系统

单机存储=单个计算机节点上的存储软件系统,一般不涉及网络交互。

第一个单机存储系统就是本地文件系统,第二个比较常见的是key-value存储。

从Linux来引进的话,可以联想到其经典哲学,一切皆文件。

文件系统的管理单元:文件。

文件系统接口:文件系统繁多,如Ext2/3/4,sysfs,rootfs等,但都遵循vfs的统一抽象接口。

Linux文件系统的两大数据结构有:Index Node 和 Directory Entry。

Index Node:记录文件元数据,如id、大小、权限、磁盘位置等。inode是一个文件的唯一标识,会被存储到磁盘上,inode的总数在格式化文件系统时就固定了。

Directory Entry:记录文件名、inode指针、层级关系等。dentry是全内存结构,并不会被持久化到磁盘中,与inode的关系是N:1(也就是linux中的硬链接的一种表现形式,即hardlink的实现。)

一个inode可以对应多个dentry,但一个dentry只能对应一个inode。这是因为一个文件可以在文件系统中的不同位置被引用(例如,通过硬链接),每个引用都需要一个dentry来表示,但所有这些引用都指向同一个inode。

kv存储也比较经典,理解成为键值对就可以了。

常见的数据结构有:LSM-Tree,某种程度上来说是牺牲读性能,追求写入性能(顺序写入)的。其经典产品就是RocksDB。(可以先看看leveldb的源码,再去看看rocksdb的源码。)

上面可以看到其数据结构,左边是内存数据结构,右边是磁盘数据结构。

当数据被写入LSM-tree时,它首先被写入到内存中的结构,这通常是一个有序的数据结构,如跳表(Skip List)或红黑树。这个内存中的结构被称为"MemTable"(内存表)。由于数据是顺序写入的,所以写入操作非常快速。

当MemTable达到一定大小后,它会首先被刷到内存里面不可变的MemTable,就是图中右边的灰色部分,然后后面被写入到磁盘上,并且变成一个不可变的文件,称为"SSTable"(Sorted String Table、可以理解为有序的Key-Value的集合)。

随着时间的推移,磁盘上会积累大量的SSTable文件。为了减少磁盘空间的使用并提高读性能,这些SSTable文件会定期进行合并(Compaction)操作。一般是先到level0的sstable,然后满了之后下发到level1的sstable,不断往下刷。

合并操作会读取多个SSTable文件,并将它们合并成一个新的、更大的SSTable文件,同时删除重复的键和过期的数据。由于SSTable文件可能包含多个版本的同一数据(由于合并操作的延迟),读取操作可能需要检查多个SSTable文件,这使得读取操作相对较慢。

由于读取操作可能需要检查多个SSTable文件,LSM-tree的读性能通常不如传统的B树索引。

五、分布式存储

分布式存储就是在单机存储基础上实现了分布式协议,涉及了大量的网络交互。

分布式存储的基石:HDFS,大数据时代的基石。HDFS刚出现的时候,专用的高级硬件很贵,同时数据存量很大,要求超高吞吐。(当年Google在千禧年初提出的,Google的开源论文)。

HDFS的特点就是需要支持海量数据存储,需要支持高容错性(因为硬件不是很高级,所以这种故障率容易比高级硬件要高,所以需要高容错率),弱POSIX语义,可以使用x86服务器,性价比比较高。

下面就是HDFS的架构,先去namenode获取数据的位置,然后拿到了位置之后才能去storage node中读取对应的数据。

第二个开源分布式存储系统里的"万金油":Ceph。

Ceph的核心特点:一套系统支持对象接口、块接口 、文件接口,但是一切皆对象 。数据写入采用主备复制 模型,数据分布模型采用CRUSH算法。

数据分布模型是指数据在存储系统中如何被分布和组织。在Ceph中,数据分布是通过CRUSH算法来实现的。CRUSH算法(Controlled Replication Under Scalable Hashing)是一种伪随机数据分布函数,它为异构且动态的不可靠对象存储设备(OSDs)集群设计。CRUSH算法负责将对象映射到OSD上,并确保数据在故障域中均匀分布。CRUSH算法的主要思想是使用伪随机函数和散列函数,将对象ID(或其他属性)转换为一个数字值。这个数字值决定了对象在存储集群中的位置。CRUSH算法通过事先配置好的CRUSH映射表,使用这个数字值计算出一个或多个适合存储对象的存储设备。

简单来说CRUSH算法就是:HASH+权重+随机抽签的方式来决定。

数据写入采用主备复制模型,这是一种数据冗余策略,用于提高数据的可靠性和容错能力。在Ceph中,数据首先被写入一个主OSD,然后复制到一个或多个备OSD上。这种模型确保了即使某个OSD发生故障,数据仍然可以从其他副本中恢复。Ceph中的复制策略可以根据需求进行配置,以实现不同的可靠性和性能平衡

CRUSH算法在Ceph中用于数据分布和数据位置计算。它是Ceph存储系统的核心算法,用于确定数据在存储集群中的位置。CRUSH算法通过一个映射表来决定数据应该存储在哪些OSD上,这个过程是分布式的,不需要中央控制器。CRUSH算法允许Ceph客户端直接与OSDs通信,而不是通过中央服务器或代理,从而避免了单点故障、性能瓶颈和可扩展性限制。CRUSH算法使用集群的映射(CRUSH map)将数据映射到OSDs,根据配置的复制策略和故障域在集群中分布数据。CRUSH算法根据存储设备的状态、负载和拓扑结构等信息,结合散列函数和CRUSH映射表,选择最佳存储设备。CRUSH算法的两个关键优势是:它是完全分布式的,任何一方(客户端、OSD或MDS)都可以独立计算任何对象的位置;而且,OSD集群图谱更新不频繁,几乎消除了与分布相关的元数据交换。

六、单机数据库

单机数据库=单个计算机节点上的数据库系统。

也可以说是事务在单机内执行,也可能通过网络交互实现分布式事务。

接下来先讲讲关系型数据库。在商业产品中Oracle是非常出色的,开源产品中Mysql和PostgreSQL则是比较知名。

关系型数据库,以这个图为例,左边是内存数据结构,右边是磁盘的数据结构。在内存中,基本上是以树状结构进行组织的。每个节点都可以理解为一个Page,如果说有个用户想Update一个数据,本质上就是来更新这个这个Page,还需要记录一条数据日志Redo Log,要把操作本身给记下来。

Temp Data是临时数据,可能是某些数据join的结果,到时候需要拼起来一起返回给用户。

磁盘结构有三类,Page Files,对应的是内存结构里面的Page。第二类就是Redo Log Files,记录的是事务执行中的各种Log,Others是对应的Temp data。

非关系型数据库一般是三足鼎立的,MongoDB、Redis、Elasticsearch。

非关系型数据库交互方式各不相同,没有关系约束之后,schema相对灵活。

非关系型数据库也在尝试支持SQL子集和事务的特性,可以理解为非关系数据库也想去做做关系型数据库的特点。

Elasticsearch主要是面向文档存储,文档可序列化为Json,支持嵌套,存在index,index=文档的集合。存储和构建索引能力依赖Lucene引擎,实现了大量搜索数据结构和算法,支持Restful API,也支持弱SQL交互。支持模糊搜索、关联度的搜索,也就是内嵌了某种文本的关联程度的搜索算法。

MongoDB也是面向文档存储的,可以序列化为JSON或者BSON,支持嵌套,存储和构建索引的能力依赖wiredTiger引擎,4.0之后开始支持事务(多文档、跨分片多文档等),常用client\SDK进行交互,可以通过插件转译支持弱SQL。

Redis数据结构丰富,比如hash表、set、zset、list等,C语言实现,超高性能,主要基于内存,支持AOF\RDB持久化,用redis-cli和多语言SDK进行交互。

七、分布式数据库

单机数据库一般离不开三个问题:容量、弹性、性价比等。

单机的数据库单点容量有限,受硬件(挂载的硬盘有限)限制。所以提出了存储节点池化(存储池由物理机或者虚拟机构成),能够动态扩缩容,数据库不需要感知底下的池化技术,只需要存储即可。

分布式数据库还面临未来的挑战:1、单写和多写。2、从磁盘弹性到内存弹性。3、分布式事务优化。

八、存储与数据库演进

首先演进可以分为三个方面:

1、软件架构变更:Bypass OS kernel:,

即绕过操作系统内核来处理特定的任务,以提高性能。这种策略通常应用于性能要求较高的程序,在设计和实现过程中要尽可能减少切换到内核态。通过绕过内核,可以减少系统调用和上下文切换的开销,从而提升效率。例如,在网络协议栈中,传统的处理方式需要基于多层网络协议处理数据包,存在用户态和内核态的切换,而采用kernel bypass的技术,如RDMA,可以直接把用户态虚拟内存映射给网卡,减少拷贝开销,减少CPU开销。这种方法在高速科学计算、低延迟交易设备中常中用到。软件架构变更以Bypass

OS kernel,还涉及到存储介质、计算单元、网络硬件变更。在网络密集型应用中,内核态与用户态的频繁切换、复杂的网络协议栈处理,常常使Linux内核成为性能瓶颈。因此,一些技术如DPDK和XDP提出了内核旁路(Kernel bypass)思想的技术方案,以提升性能。而DPDK(Data Plane Development Kit)是一种流行的内核旁路技术,它允许用户态程序直接操作网卡,避免了内核协议栈的处理。

2、AI增强:智能存储格式转换。

3、硬件革命:存储介质变更、计算单元变更、网络硬件变更。

8.1 Bypass OS kernel:SPDK

SPDK:Storage Performance Development Kit。核心是用户态、轮询模式、异步、无锁的NVMe驱动,它提供了零拷贝、高并发直接从用户态访问SSD的特性。SPDK通过将所有必要的驱动程序移到用户空间,并采用轮询硬件完成而不是依赖中断,从而降低总延迟和延迟变化。它避免了所有锁的使用,而是依赖消息传递。

简单来说就是绕开OS内核,把用户态的操作直接连上磁盘等,避免内核态syscall带来的性能损耗。另外就是磁盘性能提高之后,(中断对性能就会有影响,因为以前需要中断是因为访问外设,但是外设的速度很慢,所以通过中断的信号方式通知CPU已经外设已经准备好了,现在随着磁盘的性能和介质的可持久化越来越强,如果还频繁用中断的机制,那么就会有很大的性能损耗。),中断次数随之上升,不利于IO性能,SPDK poller(也就是把工作线程)可以绑定特定的cpu核不断轮询,减少cs,提高相对应的性能。另外就是Lock-free queue无锁队列,降低并发的时间开销。

8.2高性能硬件

比如说刚刚的RDMA网络,RDMA是kernel bypass的代表。

还有SSD和Main Mermory之间的一种存储产品,Persistent Memory,IO时延介于SSD和Memory之间,百纳秒级别,可以作为 易失性内存,也可以作为 持久化介质。

还有可编程交换机等,比如P4 switch,有编译器、计算单元、DRAM,可以在交换机层面对网络包进行计算逻辑,在数据库的场景下,可以实现缓存一致性协议等。

相关推荐
夜泉_ly1 小时前
MySQL -安装与初识
数据库·mysql
qq_529835352 小时前
对计算机中缓存的理解和使用Redis作为缓存
数据库·redis·缓存
月光水岸New4 小时前
Ubuntu 中建的mysql数据库使用Navicat for MySQL连接不上
数据库·mysql·ubuntu
狄加山6754 小时前
数据库基础1
数据库
我爱松子鱼4 小时前
mysql之规则优化器RBO
数据库·mysql
chengooooooo5 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
Rverdoser6 小时前
【SQL】多表查询案例
数据库·sql
Galeoto6 小时前
how to export a table in sqlite, and import into another
数据库·sqlite
人间打气筒(Ada)6 小时前
MySQL主从架构
服务器·数据库·mysql
leegong231116 小时前
学习PostgreSQL专家认证
数据库·学习·postgresql