0 前言
数据库是互联网的基石,存储着海量信息,使信息可被高效地组织、检索和分享。没有数据库,网站无法记忆用户数据,应用无法提供个性化服务,信息交流将失去智能与连贯性。因此,数据库技术极大地推动了互联网的发展,是连接用户与信息、构建动态交互网络世界的桥梁。
由实际的数据存储和计算需求抽象出的数据模型与数据库技术的发展相辅相成,下面是经典数据库的发展简史:
- 1968年,为满足早期导航式数据访问需求,层次数据库 IMS 发布,采用的是一个树状结构组织数据,简化了一对多关系的管理。
- 1970年,因应复杂数据关系管理挑战,网状数据库 IDS 发布,采用多对多数据模型,增强了数据联系的灵活性和表达力。
- 1996年,为解决数据一致性与共享问题,关系数据库 MySQL 面世,通过表格形式及SQL支持,实现了数据的独立性与结构化查询。
- 2008年,针对大规模数据存储与快速查询,列存数据库 HBase 发布,采用列族存储模型,优化了大数据分析的效率与压缩。
- 2009年,为适应非结构化数据处理,文档数据库 MongoDB 诞生,采用 JSON / BSON 文档模型,提升了数据模式的灵活性和扩展性。
- 2010年,为高速访问简单数据类型,键值数据库 Redis 问世,采用键值对存储,强化了基础数据的快速操作与性能。
- 同年,图数据库 Neo4j 发布,针对复杂关系数据查询,采用节点-边-属性模型,专长于多条关系分析及社交网络数据处理。
- 2013年,为满足时间序列数据处理需求,时序数据库 InfluxDB 推出,设计了时间戳时为核心的数据模型,服务于监控与传感器数据分析。
- 2016年,面向海量数据,分布式数据库 TiDB 发布,采用了 Google Spanner 和 F1 论文的设计理念,实现了
HTAP(Hybrid Transactional and Analytical Processing) 模型,提供水平扩展能力与ACID事务支持,旨在实现弹性扩展和大规模在线事务处理。 - 2019年,为应对高维向量数据检索的挑战,向量数据库 Milvus 诞生,借助高效相似性搜索算法,极大地提升了在大规模机器学习、图像识别与自然语言处理等领域中向量数据的搜索速度与准确性。
其中 HTAP 并不能算一种底层数据模型,但是其行列一体、存储计算分离的设计值得借鉴。
参考 db rank: https://db-engines.com/en/ranking
下面是典型数据库的原理与实现。
1 层次数据库 IMS
1.1 层次模型
层次模型使用树形结构来表示实体及其之间的层次关系。在这个模型中,数据被组织成一个有向有序的树状结构,其中节点代表实体或记录类型,边表示实体之间的父子关系,即一对多的联系。层次模型最早应用于数据库领域,以 IBM 的 IMS DBMS 为代表。
特征
- 树形结构:数据以层次分明的树状结构展现,每个节点有零个或多个子节点,但只有一个父节点(根节点除外)。
- 明确的层次关系:通过节点间的连接明确表达了实体间的上下级或包含关系,适合表达如组织结构、家族谱等自然的层次数据。
- 一对多联系:直接支持并强调一对多的数据关系,便于处理具有明显上下层级的数据。
- 数据表示与操作:数据操作遵循严格的层次顺序,从根节点开始逐层访问,需要记录类型码和顺序域来定位数据。
- 完整性约束:确保数据一致性的规则,如插入记录时需指定双亲、删除双亲时子节点随之删除等。
局限性
- 多对多关系处理困难:不直接支持多对多关系,需通过引入冗余节点和结构来间接实现,导致数据冗余和管理复杂。
- 查询效率与灵活性受限:查询操作通常从根节点开始,对于深层次节点的访问效率较低,且不易进行反向查询。
- 数据更新复杂性:插入、删除操作可能影响整个层次结构,特别是删除操作可能导致大量相关数据的变动。
- 物理结构依赖:层次命令要求用户了解底层数据结构,降低了数据抽象级别和应用程序的独立性。
- 数据独立性差:由于紧密依赖于特定的层次结构,当数据结构发生变化时,可能需要对应用程序进行较大调整。
层次模型以其直观的层次表示和简单的一对多关系处理,在早期数据库系统中发挥了重要作用,但其固有的局限性,特别是在处理多对多关系和数据独立性方面的不足,促使了后来网状模型和关系模型等更灵活数据模型的发展。
2 网状数据库 IDS
2.1 网状模型
网状模型是一种基于图的用于表示非层次结构数据关系的数据库模型,在这种模型中,实体之间的联系可以是多对多的复杂关系,不仅允许一个节点拥有多个父节点,也允许一个节点没有直接的父节点。网状模型通过网状结构的数据组织方式来映射现实世界中复杂的数据关联,其中的联系通过有向线段明确表示,并且为不同的联系命名以区分。
特征
- 灵活性高:能够描述和处理复杂多变的数据关系,包括一对多、多对一及多对多关系。
- 结点多样性:允许节点有多个父节点或无父节点,适应更广泛的数据关联场景。
- 数据表示与操作:采用记录和记录值来表示实体集和实体,支持基本的数据操作如查询、插入、删除和更新,但在操作上比层次模型更为复杂。
- 完整性约束相对宽松:相比层次模型,网状模型在完整性约束上较为灵活,但具体实现时仍会加入必要的约束条件以维护数据一致性。
局限性
- 复杂性:网状模型的结构复杂,理解和维护成本较高,对用户不友好。
- 导航式数据访问:数据访问依赖于从一个记录到另一个记录的指针链接,缺乏直接访问数据的简单机制,这使得编写和执行查询变得复杂。
- 设计与实施难度:设计一个有效的网状数据库结构需要深入理解数据间的关系,实施过程中容易出现设计不当,导致数据冗余或一致性问题。
- 数据独立性差:数据结构的改变往往需要应用程序做出相应调整,降低了数据的物理独立性和逻辑独立性。
- 标准化程度低:网状模型倾向于直接反映复杂的数据关系,可能导致数据结构不够规范化,影响数据管理的效率和一致性。
网状模型尽管提供了强大的表达复杂数据关系的能力,但由于其复杂性和操作上的不便,现代数据库系统更多采用关系模型或其他更为灵活和易用的数据模型。
3 关系数据库 MySQL
3.1 关系模型
关系模型核心思想是使用表格的形式来表示实体(数据对象)及其之间的关系。在这种模型中,数据被组织成一系列的表格,每个表格代表一个实体集,表格中的每一行是一个记录(实体实例),每一列则是一个属性。关系模型强调的是数据间的关联通过表格间的共同属性来实现,特别是利用外键来表达实体之间的多对多或一对多关系。
特征
- 统一的结构表示:所有的实体和实体间的关系均被表示为二维表格(关系),简化了数据的表示形式。
支持复杂关系表达:能够很好地表示多对多关系,通过引入关联表(第三张表)来连接两个实体表,实现了灵活的关系描述。 - 规范化要求:强调数据的规范化,避免数据冗余,要求每个属性都是不可分割的基本单元,确保数据的一致性和高效管理。
- 简单明了的结构:以表格形式直观展示数据结构,易于理解与操作。
- 高度数据独立性:用户在操作数据时无需关心数据的物理存储方式,提高了数据的抽象级别和应用程序的可移植性。
- 非过程化查询语言:提供SQL等非过程化查询语言,用户只需指定“做什么”而非“怎么做”,简化了数据检索过程。
局限性
- 查询效率问题:由于关系数据库需要维护数据之间的复杂关系,特别是在处理大规模数据或复杂查询时,可能会遇到性能瓶颈,导致查询效率相对较低。
- 数据过度规范化可能导致性能下降:过分追求数据规范化的结果可能需要更多的联接操作来恢复原本的信息,影响查询速度。
- 缺乏灵活性:对于非结构化或半结构化数据的处理能力有限,不适用于所有类型的数据存储需求。
关系模型的三种约束完整性
- 实体完整性:确保表中的主键字段不允许有空值,保证了每个实体的唯一可识别性。
- 参照完整性:维护表间引用的准确性,确保外键值要么为空,要么在被引用表的主键中存在,保持数据间的一致性。
- 用户定义完整性:根据具体应用需求定义的额外约束条件,如唯一性约束、值的范围限制等,以满足特定业务规则。
3.2 MySQL
逻辑架构
日志系统
MySQL 的日志系统是数据库管理系统中的一个关键组件,它通过记录各种类型的操作信息来帮助进行数据恢复、性能分析、错误排查等任务。MySQL 的日志种类繁多,每种日志都有其特定的用途:
- 二进制日志(Binary Log):记录了对 MySQL 数据库执行更改数据的所有语句,如 INSERT、UPDATE、DELETE 以及数据定义语言(DDL)操作如 CREATE TABLE、ALTER TABLE 等。不包括没有改变数据的查询(如 SELECT)。对于数据恢复和主从复制非常重要。
- 错误日志(Error Log):记录 MySQL 服务器启动、运行过程中遇到的警告信息和错误信息。对于识别和解决服务器问题至关重要。
- 通用查询日志(General Query Log):记录所有到达 MySQL 服务器的连接和查询信息,包括客户端连接的建立与断开、所有的 SQL 语句等,无论这些操作是否影响到数据。由于其会记录大量信息且可能影响性能,通常在生产环境中关闭。
- 慢查询日志(Slow Query Log):记录执行时间超过预设阈值的 SQL 查询,帮助识别和优化性能瓶颈。通过分析慢查询日志,可以找出需要优化的查询语句或索引设计。
- 中继日志(Relay Log):特定于 MySQL 的复制环境中的从库。当主库的二进制日志事件传输到从库时,这些事件被写入中继日志,然后从库从中继日志中读取并执行这些事件以保持与主库的数据同步。
- 事务日志(InnoDB Redo Log):InnoDB 存储引擎特有的日志,用于保证事务的持久性。当事务提交时,先将修改记录到 redo log 中,确保即使在发生故障时也能根据 redo log 恢复数据。
- 回滚日志(InnoDB Undo Log):同样是 InnoDB 存储引擎的一部分,用于事务的回滚操作,即在事务中如果需要撤销某个操作,可以根据 undo log 恢复到操作前的状态。
事务隔离
事务是确保数据库操作完整性的一种机制,其特性概括为 ACID:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。其中,隔离性是通过设置不同的事务隔离级别来实现的,以平衡性能与数据一致性。主要的事务隔离级别包括:
- 读未提交(Read Uncommitted):允许事务查看其他未提交事务的更改,可能导致“脏读”。
- 读提交(Read Committed):一个事务只能看见已经提交的更改,解决了脏读问题,但可能出现“不可重复读”现象。
- 可重复读(Repeatable Read):在一个事务内多次读取同一数据结果一致,解决了不可重复读问题,但可能遇到“幻读”。
- 串行化(Serializable):最高隔离级别,通过完全锁定避免了脏读、不可重复读和幻读,但性能影响最大。
涉及到的并发问题:
- 脏读:在一个事务中,能够读取到另一个未提交事务中的数据,如果这个未提交的事务后来被回滚,那么之前读取的数据就是无效的,即脏数据。
- 不可重复读:在同一个事务中,多次查询同一数据时,由于其他事务的提交,导致查询结果不一致,后面读取到了初次读取后其他事务修改过的数据。
- 幻读:在一个事务内,多次执行相同的查询语句返回的结果集记录数不同,这是因为其他事务插入了新数据,虽然这些新数据符合原事务最初的查询条件,但并没有在第一次查询结果中出现,仿佛凭空出现一样,造成了幻觉。
为了解决隔离级别中的并发问题,MySQL 引入了多版本并发控制(MVCC),它通过维护数据的多个版本来实现事务间的隔离。具体实现涉及:
- 每个事务拥有唯一的事务ID(transaction id)。
- 更新操作会在undo log中保存旧版本数据,并标记新数据的事务ID。
- 查询时根据事务的视图(由已提交的最大事务ID界定)决定可见版本,从而实现不同隔离级别的逻辑。
针对特定问题,如幻读(Phantom Read),InnoDB通过引入间隙锁(Gap Lock)来防止新记录插入造成的幻觉,这主要发生在可重复读隔离级别下进行范围查询时。
索引
- InnoDB 的 B+ 树索引模型:此模型适配磁盘读写特性,减少查询所需的磁盘 I/O。主键索引(聚簇索引)直接存储数据行,而非主键索引(二级索引)存储主键值,并在查询时可能涉及“回表”操作,即先查二级索引再查主键索引。主键索引因需维护唯一性约束,在插入性能上有时逊于非主键索引。
- 索引维护与优化:包括页分裂与页合并机制,以及使用自增主键减少页分裂、优化存储空间。索引重建能整理碎片,提升索引紧凑度和查询效率。
- 覆盖索引:指查询所需数据全部包含在索引中,无需额外访问表数据,可显著加速查询过程,是常见的性能调优策略。
- 最左前缀原则:指导联合索引构建,确保索引高效利用,避免冗余索引。MySQL 解析查询时遵循从左到右匹配索引字段,合理安排索引顺序以减少索引数量。
- 索引下推优化(Index Condition Pushdown, ICP):MySQL 5.6 后引入的特性,允许在索引遍历时提前过滤不满足条件的记录,减少不必要的回表查询,进一步提升查询效率。
- 索引失效场景:包括对索引字段进行函数操作、索引字段类型隐式转换不匹配、以及字符集不同的情况,这些都会导致优化器放弃使用索引。此外,InnoDB 中的行锁依赖于索引,非索引条件查询将退化为表锁,可能增加锁竞争,影响并发性能。
锁
- 全局锁:Flush tables with read lock (FTWRL)。全库逻辑备份,期间数据库变为只读状态。相比 set global readonly=true,FTWRL 在客户端异常断开时能自动释放锁,降低了库长时间不可写的风险。
- 表级锁:分为表锁(通过lock tables ... read/write 命令实现,限制其他线程读写,同时限制本线程后续操作类型)和元数据锁(MDL,自动应用于 DML 和 DDL 操作间,保证数据一致性和操作隔离性,分为读锁和写锁,遵循读读共享、读写/写写互斥原则。)
- 行锁:InnoDB 引擎支持,提供细粒度并发控制,遵循两阶段锁协议,减少锁争用,提升系统并发能力。
- 间隙锁(Gap Lock):锁的就是两个值之间的空隙,解决幻读问题,仅在可重复读隔离级别下生效。降低并发并可能引发死锁,其冲突目标为插入操作而非另一个锁。
- Next-Key Lock:结合间隙锁和行锁,是 InnoDB 默认的行锁算法。基本单位是前开后闭区间,实际加锁依据查询条件及索引情况动态调整,能够退化为行锁或间隙锁。
4 列存数据库 HBase
4.1 列存模型
列存模型中数据按列而不是按行组织。这意味着,同一列的所有数据值在物理存储上是连续存放的,与行内数据的排列无关。这种存储方式尤其适合于数据分析场景,因为能够高效地处理对特定列的聚合操作和筛选。
特征
- 高效分析处理:由于列式存储能够仅读取查询涉及的列,因此在进行大数据分析、聚合计算时能够显著减少 I/O 操作,提高处理效率。
- 压缩效率高:同类数据存储在一起有利于高效的数据压缩,因为相同类型的数据往往具有较高的相似性和重复性,从而节省存储空间。
- 更适合读多写少场景:列存模型在读取和分析大量数据时表现出色,但写入效率相对较低,因为每次写入都需要更新多个列的存储位置。
- 灵活性:支持动态增加列,且对未赋值的列不占用存储空间,这使得列存模型非常适合处理半结构化和不断变化的数据模式。
局限性
- 写入性能:相比行存模型,列存模型在写入数据时需要更新多个列的数据块,增加了写操作的复杂度和开销。
- 事务处理能力弱:列存模型通常不支持复杂的事务处理,如 ACID 特性较弱,不适合需要严格数据一致性的应用场景。
- 查询灵活性受限:虽然对特定列的查询非常高效,但执行涉及多列联合查询或非主键列的查询时,性能可能不如预期,尤其是在需要跨列进行复杂运算时。
- 索引和更新成本:由于数据是按列存储,对单行数据的更新操作可能涉及到多个列的修改,这不仅增加了更新成本,也使得为非键列建立索引变得困难或不可行。
列存模型特别适合大数据分析和 OLAP(在线分析处理)场景,但在需要频繁写入、事务处理或高度灵活查询的 OLTP(在线事务处理)场景中,其局限性较为明显。
4.2 HBase
概述
HBase 是一个构建在 Hadoop 文件系统(HDFS)之上的分布式、可伸缩的宽列存储数据库,它为 HDFS 提供了高并发随机写入能力以及实时查询功能。
设计目标:面向大规模数据存储,支持快速随机读写操作,适用于海量半结构化或非结构化数据场景。
HBase数据模型
- 行键(RowKey):唯一标识每一条记录,决定了数据的存储位置,对查询性能至关重要。
- 列族(Column Family):逻辑上组织列的集合,所有列都属于某个列族,列族是预定义的,且创建表时必须指定。
- 列(Columns):在列族内,列标识符用来区分不同的列,列的数量和内容在不同行间可以灵活变化。
- 时间戳(Timestamp):每条数据记录都会自动附带时间戳,用于实现多版本控制。
HBase存储结构
- 键值对(KV)存储:底层实现为键值存储,Key 由 RowKey、Column Family、Column Qualifier、Timestamp 组成。
- HFile:实际存储在 HDFS 上的数据文件格式,高效压缩,适合顺序读取。
- MemStore & StoreFile:数据先写入内存中的 MemStore,达到阈值后刷写到磁盘上的 StoreFile 中,优化写入效率。
- Write-Ahead Log (WAL):预写日志,确保数据的持久性和容错性。
HBase架构
- Client:客户端接口,负责与 HBase 交互。
- Zookeeper:维护集群元数据,协调服务。
- HMaster:管理集群,负责表的增删改查、Region 分配与负载均衡。
- HRegionServer:处理客户端读写请求,与 HDFS 交互,管理多个 Region。
HRegionServer 工作原理
Region:表按行键范围被水平切分成多个 Region,分布在不同 RegionServer 上。
Store:每个 Region 根据列族划分 Store,每个 Store 包含 MemStore(内存缓存)和 StoreFile(磁盘存储)。
数据写入流程:数据先写入 MemStore,然后定期合并至 StoreFile,并同步更新到 HLog,确保数据不丢失。
Replication(复制)
主从复制:HBase 通过 master-push 模式进行数据复制,增强数据的可靠性和可用性。
故障恢复:当主节点故障时,可通过 Zookeeper 选举新的 HMaster,同时利用复制数据恢复丢失信息。
5 文档数据库 MangoDB
5.1 文档模型
文档模型以文档为中心,这些文档通常是半结构化的数据单元,通常以 JSON(JavaScript Object Notation)格式表示。每个文档可以包含不同类型的数据(如字符串、数字、数组、嵌套对象等),并且文档间不必遵循统一的结构,这为数据模型提供了高度的灵活性和表达能力。
特征
- 灵活性与半结构化:文档数据库允许每条记录(文档)有不同的字段和结构,适合存储复杂和不断变化的数据模式。
- 易用性:JSON 文档直接映射到大多数编程语言中的对象,简化了开发过程,提高了开发效率。
- 层级数据模型:支持嵌套和层次化的数据结构,能够自然地表示和查询复杂的关联数据。
- 动态模式:不需要预先定义严格的数据库模式,可以在运行时轻松添加或修改字段。
- 高性能与可扩展性:通过内置的分布式和复制功能,文档数据库能够水平扩展以处理大量数据和高并发访问,同时保证数据的可用性和容错性。
局限性
- 事务处理能力:相对于关系数据库,文档数据库在处理涉及多个文档的复杂事务时可能不够强大,虽然一些高级的文档数据库开始支持多文档事务,但这仍然是其相对的局限之一。
- 数据一致性:为了实现高性能和高可用性,文档数据库通常采用最终一致性模型,不适合那些需要强一致性的应用场景。
- 查询复杂性:虽然文档数据库支持丰富的查询功能,但对于高度关联的查询或者需要跨文档进行复杂联接操作的场景,查询设计可能变得复杂且效率低下。
- 过度灵活可能导致混乱:缺乏统一的模式约束会导致数据不一致性和维护困难,特别是随着应用规模的扩大,管理非标准化的数据模型会变得更加复杂。
5.2 MangoDB
概述
- MongoDB 是一个基于分布式文件存储的数据库,由 C++ 编写,可以为 WEB 应用提供可扩展、高性能、易部署的数据存储解决方案。
- MongoDB 使用 BSON 来存储数据和网络交换,BSON(Binary JSON)是一种类 JSON 的二进制存储格式,支持内嵌的文档对象和数组对象,是一种 schema-less 的存储形式,比 JSON 数据类型更丰富(eg:Date)。
索引
- MongoDB 是非关系型数据库,schema-less 存储,使用 BSON 存放整条数据,使用B-树做索引,但查询从结构上要快于B+树。
- MySql 是关系型数据库、数据间的关联性很强,区间访问很常见,为了减少磁盘 IO 提升效率,所以 MySql 使用 B+ 树做索引。
MongoDB架构
存储引擎WiredTiger
从架构上看MongoDB和MySql一样采用了可插拔的设计。
WiredTiger 相比 MMAPV1 的优势:
- 文档空间分配方式:WiredTiger 使用 BTree 存储,MMAPV1 使用线性存储,需要 Padding;
- 并发级别:WiredTiger 支持文档级别锁,MMAPV1 引擎使用表级锁;
- 数据压缩:WiredTiger 支持snappy (默认) 和 zlib,相比 MMAPV1(无压缩))空间节省数倍;
- 内存使用:WiredTiger 可以指定内存的使用大小,同时 WiredTiger 使用了二阶缓存 WiredTiger Cache,File System Cache 来保证Disk上的数据的最终一致性,而 MMAPv1 只有 journal 日志;
WiredTiger 引擎在磁盘上的架构
page 包含一个页头(page header)、块头(block header)和真正的数据(key/value),其中页头定义了页的类型、页中实际载荷数据的大小、页中记录条数等信息;块头定义了此页的 checksum、块在磁盘上的寻址位置等信息。
WiredTiger 引擎在内存上的架构
WiredTiger 会按需将磁盘的数据以 page 为单位加载到内存,同时在内存会构造相应的 B-Tree 来存储这些数据。
为了高效的支撑 CRUD 等操作以及将内存里面发生变化的数据持久化到磁盘上,WiredTiger 也会在内存里面维护其它几种数据结构:
- rootpage、internal page:包含指向其 page 子页的指针、不包含数据;
- leaf page:包含数据与父页指针;
- WT_ROW:保存从磁盘读取的kv数据与偏移量;
- WT_UPDATE:记录修改数据,如果对同一条记录有多次修改会使用链表保存;
- WT_INSERT_HEAD:记录插入数据,使用跳转链表提升查询速度;
- WT_PAGE_MODIFY:保存 page 上事务、脏数据字节大小等与 page 修改相关的信息;
- read_gen:page 在 LRU 队列中的位置,决定淘汰先后顺序;
- WT_PAGE_LOOKASIDE:page 关联的 lookasidetable 数据。当对一个 page 进行 reconcile(page刷盘)时,如果系统中还有之前的读操作正在访问此 page 上修改的数据,则会将这些数据保存到 lookasidetable;当 page 再被读时,可以利用 lookasidetable 中的数据重新构建内存 page;
- WT_ADDR:page 被成功 reconciled 后,对应的磁盘上块的地址,将按这个地址将 page 写到磁盘;
- checksum:page 的校验和,如果page从磁盘读到内存后没有任何修改,比较 checksum 可以得到相等结果,那么后续 reconcile 这个 page 时,不会将这个 page 的再重新写入磁盘。
Checkpoint 机制
- Checkpoint 的目的
将内存里面发生修改的数据写到数据文件进行持久化保存,确保数据一致性
实现数据库在某个时刻意外发生故障,再次启动时,缩短数据库的恢复时间。 - Checkpoint包 含的关键信息
本质上来说,Checkpoint 相当于一个日志,记录了上次 Checkpoint 后相关数据文件的变化。
3.Checkpoint 执行流程
事务的数据结构
- id:全局唯一标识;
- snapshot_data:快照,通过 snap_min 和 snap_max 计算事务开始时能看到的数据范围;
- commit_timestamp:事务提交时间;
- durable_timestamp:事务修改的数据已持久化的时间;
- prepare_timestamp:事务开始时间;
- WT_TXN_OP:包含事务的修改操作,用于事务 rollback 和生成事务的 Journal 日志;
- logrec:事务日志的缓存,用于在内存中保存事务日志。
MVCC并发控制机制
要实现事务间的并发操作,可以使用锁机制或 MVCC 控制,MVCC 实现的是一种乐观并发机制,相比锁更轻量。
应用场景与优势
MongoDB 因其独特的设计和特性,广泛适用于以下场景:
- 敏捷开发与快速迭代:适合需求频繁变化、数据模型不确定的新应用,MongoDB 的 schema-less 特性降低了数据库设计的前期成本。
- 高并发写入:对于需要高写入吞吐量(如大于2000-3000 QPS)的应用,MongoDB 提供了高效的写操作处理能力。
- 大数据存储与处理:适合处理 TB 乃至 PB 级别的海量数据,且随着数据量的增长,可通过分片轻松扩展。
- 实时分析与内容管理:MongoDB 支持地理位置查询、全文检索等功能,适合构建实时数据分析、内容管理系统等。
- 微服务与云原生应用:MongoDB 的分布式特性与云环境天然契合,易于集成到微服务架构中,支持自动伸缩和高可用部署。
6 键值数据库 Redis
6.1 键值对模型
键值对模型是一种非关系型数据库存储模型,通过简单的键值对应关系来存储数据。这种模型的核心在于简单性,每个数据项都由一个唯一的键和与之相关联的值组成,键作为查询数据的依据,值则是数据本身的内容,可以是任何类型的数据结构,从简单的字符串、数字到复杂的对象、文档等。
特征
- 简洁性:数据模型简单,不涉及复杂的表结构和关系,降低了设计和维护的复杂度。
- 高性能:由于数据结构简单,通常支持高速的读写操作,尤其在内存中实现时性能更为突出。
- 易扩展性:支持水平扩展,能够通过增加更多存储节点来应对数据量的增长和访问量的增加。
- 灵活性:键值对可以存储任意类型的数据,没有固定的模式约束,适应性强。
- 分区和复制:支持数据分区和复制机制,提高系统的可用性和容错能力。
局限性
- 缺乏复杂查询能力:键值数据库不支持复杂的查询语句和多表联查,对于需要复杂数据分析和关联操作的应用不太适用。
- 数据一致性问题:虽然一些键值数据库支持事务处理,但相比关系型数据库,一致性保证可能较弱,特别是在分布式环境中。
- 数据模型的局限:键值对模型适合存储松散结构的数据,但对于需要严格结构化数据和复杂关系的应用,可能需要额外的逻辑来维护数据间的关系。
- 过度依赖键的设计:高效的数据访问依赖于良好的键设计,否则可能导致数据分布不均,影响性能。
数据管理困难:缺乏强制的模式约束,容易导致数据不一致性和管理混乱,尤其是在团队协作时。
6.2 Redis
Redis 高速原因
- 基于内存:数据存储在内存中,极大减少了磁盘 I/O 操作的延迟,使得读写操作几乎达到内存访问速度。
- 高效数据结构:内置多种优化过的数据结构,如列表、集合、哈希表等,这些结构专门设计用于快速存取和操作,提高数据处理效率。
- 单线程模型:避免了多线程环境下的上下文切换和锁的开销,简化了编程模型并降低了并发控制的复杂度。(官方原因:CPU 不是性能瓶颈)。
- IO 多路复用:利用 epoll 等技术,单线程能高效管理多个网络连接,非阻塞地处理并发请求,减少闲置等待时间。
- 自定义 VM 机制:优化了内存管理和数据处理流程,减少系统调用开销。
数据结构
- String:基础数据类型,支持数字和字符串存储,内部通过 sds 动态字符串实现,优化了内存分配和访问效率,对于数字直接存储为 long 类型以节省空间。
- Hash:适合存储对象,内部根据数据量和值的大小在 ziplist(压缩列表)和 dict(字典)间转换,以平衡空间和访问效率。
- List:双向链表实现,用于队列等场景,Redis 3.2 前使用 ziplist 或 linkedlist,之后引入了 quicklist,结合了 ziplist 的紧凑性和链表的灵活性。
- Set:无序不重复集合,使用 intset(对于整数且数量不大时)或 dict 存储,提供集合运算功能。
- Sorted Set:带权重的集合,支持排序和范围查询,内部可能使用 ziplist 或 zset(结合 dict 与 skiplist )实现,根据数据特性自动选择。
底层数据结构
- sds(Simple Dynamic String):动态字符串,具备长度字段,支持高效长度查询,预分配空间减少内存重分配,适应二进制数据,与 C 字符串兼容,是 Redis String 的基础。
- dict:哈希表实现,提供快速的键值对存储与查找,采用负载因子控制自动扩容与缩容,并通过增量式重哈希保持操作效率。
- ziplist:压缩列表,紧凑型内存布局,通过变长编码节省空间,适用于小规模数据集,但不擅长频繁修改。
- quicklist:基于 ziplist 的链表(链表的每个节点是 ziplist ),结合 ziplist 的紧凑和链表的灵活性,用于优化 List 的存储效率,特别是在数据量较大时。
- intset:整数集合,有序存储整数,支持高效的查找,根据数据大小动态调整编码,节省内存。
- skiplist:跳表,一种可进行快速查找、插入和删除的有序数据结构,通过多级索引实现接近于二分查找的效率,是Sorted Set的一部分实现。
缓存相关问题
缓存雪崩
- 定义:因大量缓存 key 在同一时间失效,导致请求直接冲击数据库,引起数据库压力激增乃至崩溃的现象。
- 问题分析:根源在于 key 的集中失效和 Redis 故障。
- 解决方案:
- 事前预防:均匀过期;分级缓存;热点数据永不过期;Redis高可用,部署主从架构+哨兵或 Redis 集群,确保 Redis 服务稳定。
- 事中控制:互斥锁/队列;熔断限流;
- 事后恢复:Redis持久化,确保Redis故障后能快速从磁盘恢复数据。
缓存击穿
- 定义:单个热点 key 过期后,大量请求直接命中数据库,导致数据库压力骤增。
- 问题分析:热点 key 失效且并发请求集中。
- 解决方案:
- 互斥锁/队列:控制重建缓存的并发线程。
- 热点数据永不过期:采用逻辑或物理不过期策略。
缓存穿透
- 定义:请求的数据既不在缓存中也不在数据库中,频繁请求导致数据库压力。
- 问题分析:查询不存在的 key。
- 解决方案:
- 缓存空值:将查无结果的 key 以 null 值存入缓存,并设置较短过期时间。
- 布隆过滤器:前置判断 key 是否存在,减少无效数据库查询。
数据过期清除策略
- 定期删除:Redis 周期性地检查并删除已过期的 key,避免了定时删除策略带来的高 CPU 负担,但可能无法及时清理所有过期数据。
- 惰性删除:在访问 key 时,Redis 检查该 key 是否已过期,若过期则删除,保证了数据的时效性,但不主动触发,依赖访问行为。
当定期删除和惰性删除无法有效控制过期 key 时,需要依靠内存淘汰策略来进一步管理内存。
Redis 提供了八种淘汰策略,以适应不同场景的需求:
- noeviction:不淘汰任何数据,写操作导致超出内存时返回错误。
- volatile-ttl、volatile-random、volatile-lru、volatile-lfu:仅针对设置了过期时间的 key。
- allkeys-random、allkeys-lru、allkeys-lfu:考虑所有 key。
事务
Redis 事务是一系列命令的集合,保证这些命令要么全部执行,要么都不执行,实现了原子性,但此原子性不包含回滚机制。
持久化机制
- RDB (Redis Database Dump)
- 定义:RDB 是通过创建某一时间点的内存数据快照来实现持久化,生成一个二进制文件(如dump.rdb),包含数据的序列化表示。
- 触发方式:
save 命令:直接在主线程执行,会导致 Redis 阻塞,不建议生产环境使用。
bgsave 命令:创建一个子进程进行快照生成,主进程继续处理请求,推荐使用。
自动触发:根据配置文件中的规则自动执行 bgsave,例如基于时间间隔和键修改次数。 - 优劣势:
优势:简单、恢复速度快、文件紧凑,适合备份和灾难恢复。
劣势:数据可能丢失(最后一次快照到故障期间的数据)、fork 操作可能导致内存瞬间翻倍、频繁执行可能影响性能。
- AOF (Append Only File)
- 定义:AOF 通过记录执行的写命令日志来实现持久化,保证每条写操作都被记录,文件为纯文本格式(如appendonly.aof)。
- 重写机制:当 AOF 文件过大时,通过 bgrewriteaof 命令触发重写,创建一个更精简的日志文件以替代原有文件,保持文件大小可控。
- 同步策略:
appendfsync always:每条写命令都同步到磁盘,安全性最高,但性能影响大。
appendfsync everysec:默认设置,每秒同步一次,平衡了性能和安全性。
appendfsync no:完全依赖操作系统决定何时同步,性能最好但数据最不安全。 - 优劣势:
优势:数据丢失最少、日志可读性强、方便手动修复、支持更精细的数据恢复。
劣势:文件体积通常比RDB大、恢复速度慢、频繁写操作会增加磁盘 I/O 压力。
- 混合持久化(Redis 4.0+)
- 介绍:结合 RDB 和 AOF 的优点,持久化时先生成 RDB 快照,然后在 AOF 日志中仅记录从快照生成后到持久化结束之间的增量操作。
- 优势:利用 RDB 快速恢复和 AOF 的低数据丢失风险,优化了启动时间和数据安全性。
主从复制
主从复制的过程包括全量复制和增量复制两阶段。全量复制时,slave 首次连接 master 时会触发一个 RDB 快照的全量传输,之后切换到增量复制,仅同步 master 执行的写命令。Redis 2.8 及以后版本引入了断点续传特性,增强了复制过程的健壮性,即使网络中断,也能从断点处恢复复制,避免了因小故障引发的全量同步。
为了进一步优化复制效率和减轻主服务器负担,Redis 还提供了无磁盘化复制选项,允许在内存中直接创建 RDB 文件并传输,绕过了磁盘 I/O,适用于磁盘资源紧张或网络环境良好的场景。
哨兵机制
哨兵模式依赖于心跳检测、故障检测、领头哨兵选举及故障转移策略。哨兵之间通过流言协议传播状态信息,并采用投票协议确定是否执行故障转移。在主节点故障时,通过一系列严格的筛选和投票过程,确保选出最适合晋升为新主节点的从节点,同时更新整个集群的认知,维持服务的连续性和数据一致性。
7 图数据库 Neo4j
7.1 图模型
图数据模型是一种用于表示实体(节点)及其之间关系(边)的数据结构,在处理复杂关系数据时表现出色,尤其适合社交网络、推荐系统、知识图谱等应用场景。
特点
- 直观的数据模型:图模型以节点(代表实体)、边(代表关系)和属性(附加信息)为基础,直接映射现实世界中的对象及其关系,使得数据结构更加直观易懂。
- 高效的关系查询:由于直接在图中表达实体间的关系,图模型可以快速地进行复杂的路径查询、模式匹配等操作,这些在传统关系型数据库中可能需要多表连接和复杂的SQL语句才能完成。
- 支持灵活的数据结构:图数据库对数据结构的约束较少,容易适应不断变化的数据模型,特别适合那些关系复杂且多变的应用场景。
- 强大的遍历能力:强大的图遍历功能能够轻松实现多跳关系的查询,非常适合发现数据中的隐藏关联和模式。
局限性
- 资源消耗:与关系型数据库相比,图数据库在存储空间和内存使用上可能更为昂贵,尤其是在处理大量节点和边的密集图时。
- 数值计算和聚合操作:虽然图模型在处理复杂关系数据方面表现出色,但在执行大规模的数值计算、统计分析或聚合操作时可能不如传统关系型数据库或专门的分析工具高效。
- 学习曲线:对于习惯于使用 SQL 的开发者来说,学习图数据库的思维方式可能需要一定时间,尤其是对于复杂的图遍历和模式匹配。
- 事务处理限制:但在某些高级特性(如分布式事务)的支持上可能不如成熟的 SQL 数据库,这可能限制了它在某些金融或银行领域中的应用。
- 数据导入/导出:由于图模型的独特性,将现有数据导入图数据库或从图数据库导出数据到其他系统会比关系型数据库更复杂,需要专门的 ETL 工具或自定义脚本。
7.2 Neo4j
原生图处理
免索引邻接(Index-Free Adjacency):这是 Neo4j 高效图遍历的基础。每个节点直接维护与其相邻节点的链接,从而避免了全局索引的需要。这种设计使得查询操作的时间复杂度与图的遍历深度相关,而与整个图的大小无关,通常为 O(1) 或线性时间复杂度,大大提高了遍历性能。
对比非原生图处理:传统或非原生图数据库依赖全局索引来连接节点,这导致每次遍历时都需要查询索引,增加了时间复杂度(如 O(log n) ),尤其是在深度遍历或大型图谱中,性能下降明显。
原生图存储
存储结构:Neo4j 采用固定大小的记录存储策略,分别在不同的存储文件中保存节点、关系和属性信息,主要文件包括:
- neostore.nodestore.db
- neostore.relationshipstore.db
- neostore.propertystore.db
- ......
- 节点记录结构(neostore.nodestore.db):每个节点记录包含标志位、首个关系ID、首个属性ID、标签信息和一个预留位。节点不直接存储大量属性或关系数据,而是存储指向这些数据(如关系ID和属性ID)的指针,使得节点记录保持轻量。这样的设计允许快速定位节点及其关联关系和属性,得益于固定大小记录的直接寻址能力。
- 关系记录结构(neostore.relationshipstore.db):
包括起始节点ID、结束节点ID、关系类型指针及关系链的前后指针,支持双向遍历。关系并非双倍存储,而是通过双向链表结构在两个节点之间共享,节省空间并保持高效。 - 属性存储(neostore.propertystore.db):属性以固定大小记录存储,每个记录可含多个属性块,并且根据属性值大小采用内联或外联存储策略。大属性值存储于独立的动态字符或数组存储文件中,仍保持高效访问。
关系是双向链表,属性是单向链表,额外的关系会按照链式结构存储在 neostore.relationshipstore.db 中:
遍历算法
图形数据库对于关系问题的解决比较擅长多对多关系的处理。之所以它能够擅长于各种基于图的业务场景的检索处理,就在于其强大的遍历算法。常见遍历算法有15种:
- 广度优先搜索(BFS):适合寻找最近的邻居和最短路径,适用于对等网络搜索、社交网络的局部探索。
- 深度优先搜索(DFS):适合深入探索分支结构,如在游戏中模拟决策树,寻找所有可能路径。
- 单源最短路径:计算一个节点到所有其他节点的最短路径,应用于导航系统、最低成本路由等。
- 全源最短路径:计算图中所有节点对之间的最短路径,支持动态路径选择,如备用网络路由规划。
- 最小生成树(MST):寻找连接所有节点的最低成本路径,应用于网络设计、基础设施规划等领域。
- PageRank:评估节点的重要性,根据链接的数量和质量,广泛应用于搜索引擎排名、社交影响力分析。
- Degree Centrality:通过节点的连接数衡量中心性,有助于识别关键节点或信息传播的源头。
- Closeness Centrality:衡量节点到达其他所有节点的效率,适合分析响应速度、信息扩散能力。
- Betweenness Centrality:测量通过节点的最短路径的数量,评估节点作为信息或资源流通桥梁的重要性,应用于网络瓶颈识别、社交网络影响力分析。
- Label Propagation:基于邻域多数的标签作为推断集群的手段,快速的社区检测方法,适用于共识分析、生物网络模块识别等。
- Strongly Connected Components:找出完全互相可达的节点集,有助于识别强关联群体或循环依赖。
- Union-Find/Connected Components:不考虑边的方向,找到互相可达的节点集,基础的图划分工具。
- Louvain Modularity:通过比较它的关系密度与适当定义的随机网络来测量社团分组的质量(即假定的准确性),用于复杂网络分析、组织结构优化。
- Local Clustering Coefficient:量化节点周围邻接的紧密程度,反映网络的局部凝聚力。
- Triangle-Count and Average Clustering Coefficient:测量网络中的三角形数量和节点聚集趋势,用于理解“小世界”现象、疾病传播模型等。
逻辑架构
- 接口层:这是用户与数据库交互的层面,提供了多样化的访问和操作途径。
Traversal API:专注于图遍历操作,允许用户高效地执行复杂的图路径查找。
Core API:为核心 Java 组件提供接口,支持低级别数据访问,包括读取节点、关系和属性的原始图数据,以及确保数据操作的原子性和一致性。
Cypher:作为 Neo4j 的声明式查询语言,Cypher 简化了图数据查询,类似于 SQL 对于关系型数据库的作用,通过 CQL 接口执行复杂的数据检索和更新。
- 数据管理层:负责数据访问的高级控制和优化,确保数据处理的高效与安全。
并发锁管理:通过有效的锁机制处理多线程或多用户的并发访问,防止数据冲突。
事务管理:确保数据修改的一致性和可靠性,支持事务的提交、回滚及隔离级别管理。
缓存管理:利用内存缓存技术加速数据访问,减少磁盘 I/O,提升整体性能。
存储管理:组织和优化数据结构,为上层提供高效的数据存取策略。
- 存储层:构成数据库的物理基础,负责实际数据的持久化存储。
这一层包含了图数据的实际存储空间,使用专门设计的数据结构(如 Neo4j 的原生图存储格式)来高效存储节点、关系及其属性,确保数据的长期保存和可恢复性。
集群架构
Neo4j 的 Causal Cluster 架构涉及两个关键角色:
- Core Servers:确保数据一致性与高可用性,通过 Raft 协议管理事务复制。
- Read Replicas:扩展读取能力,作为功能完整的只读数据库缓存,异步接收更新以分担查询负载,不参与集群决策。
8 时序数据库 InfluxDB
8.1 时序模型
时序模型是专门设计用于高效存储、索引和查询时间序列数据的系统。时间序列数据是指按时间顺序排列的数据序列,每个数据点通常包含一个时间戳和一个或多个与该时间相关的值,广泛应用于监控系统、金融分析、物联网(IoT)、传感器网络、气象预报等领域。
在时序数据库中,时序模型主要围绕以下几个核心概念定义:
- 时间戳:每个数据点都有一个明确的时间戳,精确到秒、毫秒或微秒,用以表示数据的采集时间。
- 度量值(Metric):数据点的实际数值,可以是整数、浮点数或更复杂的数据结构,反映被监测对象的状态或行为。
- 标签(Tags):一组键值对,用于描述数据点的元数据,如地理位置、设备ID、传感器类型等,便于数据的过滤和聚合查询。
- 字段(Fields):与度量值类似,但可能包含非标识性或可变的数据部分,支持更丰富的数据表达。
时序数据库常用于存储 Metrics 数据:
特征
- 高写入吞吐量:时序数据库优化了大量数据的连续写入操作,能够处理每秒成千上万次的数据点写入。
- 高效压缩与存储:利用时间序列数据的自然特性(如数据点间存在相似性或趋势)进行高效压缩,减少存储空间需求。
- 灵活的查询与分析能力:支持复杂的查询语言,能够快速地根据时间范围、标签过滤条件进行聚合查询(如最大值、最小值、平均值等)。
- 时间序列预测与分析:一些时序数据库还提供时间序列分析功能,支持预测模型的构建,帮助用户预测未来趋势。
局限性
- 复杂查询性能:虽然针对时间范围和标签的简单查询效率很高,但在处理涉及多个维度组合、复杂计算或大数据量的查询时,性能可能会下降。
- 数据更新与删除:时序数据往往被视为不可变的,一旦写入就不易修改或删除。这限制了对数据的后期修正能力,也不利于满足某些合规性要求。
- 非时间序列数据处理能力有限:时序数据库专为时间序列数据设计,对于非时间序列或复杂关联数据的处理能力较弱,可能需要与其他类型数据库结合使用。
8.2 InfluxDB
InfluxDB 是由 InfluxData 公司使用 Go 语言开发的一种时间序列数据库,它设计简洁、无需外部依赖,支持高效的数据存储、高速读写及实时分析,尤其适合 DevOps 监控、物联网(IoT)监控和实时分析场景,尽管起初计划内置集群功能,后调整策略采用 Relay 模式确保高可用性,其设计理念和查询语法深受 OpenTSDB 影响,但相比之下降低了维护复杂度,目前在时序数据库领域中 DB-Engines 排名首位。
存储原理
InfluxDB 采用的时间结构合并树(Time-Structured Merge Tree, TSM)存储引擎是对日志结构化合并树(Log-Structured Merge Tree, LSM)的一种优化和定制。
LSM 树
- Memtable:位于内存中,用于存储新写入的数据,按 Key 排序。Memtable 作为写缓存,提高写入速度,但易受断电影响导致数据丢失,故需配合WAL机制。
- Immutable Memtable:当 Memtable 达到一定大小后,会转换为 Immutable 状态,不再接受写操作,等待刷写到磁盘。
- SSTable(Sorted String Table):Immutable Memtable 经过压缩和排序后形成的持久化文件,存储在磁盘上,数据有序且不可更改,多个 SSTable 通过层次结构进行管理,以支持高效查询。
TSM 存储引擎改进与机构
- Cache:类似于 LSM 中的 Memtable,用于暂存新写入的数据,但针对时间序列数据进行了优化,考虑了时间戳的重要性。
- WAL(Write-Ahead Log):同样用于确保数据的持久性,先于实际数据写入记录,即使系统崩溃也能恢复。
- TSM File:是 InfluxDB 特有的数据文件格式,结合了时间序列特点,相较于 SSTable,TSM 对数据进行了更高效的压缩和组织,包括数据分块、字典压缩等,减少存储空间并加快读取速度。
- Compactor:负责整理和压缩 Cache 及 TSM File,包括将多个小的 TSM File 合并成更大的文件,移除过期数据,以及维护索引以加速查询。这一过程进一步优化存储效率和查询性能。
- Shard:这是 InfluxDB 中数据分区的概念,基于时间范围划分数据,每个 Shard 包含独立的 Cache、WAL和 TSM File 集合,以及 Compactor 作业。Sharding 机制便于管理和优化大规模时间序列数据的存储与查询。
9 HTAP 数据库 TiDB
9.1 HTAP 模型
HTAP是一种数据库架构设计,旨在统一在线事务处理(OLTP)和在线分析处理(OLAP)能力于一身,使系统能在同一数据库平台上同时高效处理实时事务操作和复杂数据分析请求。这种设计打破了传统上将事务型数据库和分析型数据库分离的模式,减少了数据移动和处理的延迟,提升了数据处理的实时性和效率。
特征
- 一体化架构:支持 OLTP 和 OLAP 的混合处理,无需 ETL 过程,直接在事务数据库上进行分析。
- 实时分析:数据分析基于最新的事务数据,支持业务的即时决策需求。
- 高性能与可扩展性:利用分布式计算和云基础设施,提供水平扩展能力,应对高并发事务和复杂查询。
- 数据一致性:通过实时数据同步技术,确保事务处理与分析查询间的数据一致性。
- 降低成本:集成处理能力减少系统维护成本和数据冗余,提高资源使用效率。
局限性
- 技术复杂性:实现 HTAP 需要高级的数据处理和存储技术,系统设计与优化难度较大。
- 性能平衡挑战:同时优化事务处理和分析查询的性能是一大挑战,可能在某些特定场景下不如专精型系统。
- 技术成熟度:相对于传统的 OLTP 和 OLAP 系统,HTAP 作为一个较新的概念,部分技术还在不断发展和完善中。
9.2 TiDB
TiDB 是一款分布式数据库管理系统,旨在提供水平扩展能力的同时,兼具在线事务处理(OLTP)和在线分析处理(OLAP)能力,实现 HTAP(Hybrid Transactional and Analytical Processing)混合事务分析处理。其核心架构由计算引擎层、分布式协调层、以及存储引擎层三大模块组成,每个层次包含不同的组件,协同工作以满足高性能、高并发和复杂查询的需求。下面是对这些组件的梳理总结:
计算引擎层
- TiDB:作为 OLTP 计算引擎,负责处理事务性工作负载,对外提供 MySQL 协议接口,实现 SQL 解析、优化并生成分布式执行计划。TiDB 服务器无状态,易于横向扩展,通过负载均衡器均衡客户端连接,但本身不存储数据,而是将数据操作请求转发给 TiKV。
- TiSpark:集成 Apache Spark,专为复杂的 OLAP 查询设计,直接在 TiKV 数据上运行 Spark SQL 作业,实现大数据分析能力,与大数据生态无缝对接。TiSpark 与 TiFlash 结合,增强了 TiDB 在数据分析领域的表现。
存储引擎层
- TiKV:负责存储行格式数据,支撑 OLTP 业务,是一个分布式、事务性的键值存储系统。数据被切分为多个 Region,每个 Region由多个 TiKV 节点复制存储,确保高可用性和数据一致性。TiKV 支持事务隔离级别,是 TiDB 处理 SQL 查询的底层存储。
- TiFlash:针对 OLAP 场景设计,采用列式存储优化分析查询性能。TiFlash 能够近乎实时地复制 TiKV 中的数据,保持数据一致性的同时,提供高效分析查询能力。它兼容 TiDB 和 TiSpark,分别适用于不同规模的分析需求,与 ClickHouse 的高效分析技术相融合,提升了 TiDB 的分析处理能力。
分布式协调层
- PD(Placement Driver):作为整个集群的元数据管理和调度中心,存储集群拓扑信息及数据分布状态,控制数据的自动均衡与故障恢复,分配事务ID,是集群的控制大脑。PD 集群通常由三个或以上节点构成,确保高可用性。
TiDB 通过一系列精心设计的组件,实现了高度可扩展、高并发的事务处理能力和强大的数据分析能力,满足了现代应用对于数据处理多样化的严格要求,让用户能够在同一系统中灵活进行事务处理和数据分析,无需在不同系统间迁移数据,大大简化了数据管理的复杂度。
数据映射
由于 TiDB 底层基于键值对存储数据,TiDB 表中的行数据需要按照一定格式映射转换为键值对:
- 为了保证同一张表的数据放在一起,方便查找,TiDB 会为每个表分配一个 表 ID,用 TableID 表示。表 ID 是一个整数,在整个 集群内唯一。
- TiDB 会为表中每行数据分配一个 行 ID,用 RowID 表示。行 ID 也是一个整数,在表内唯一。对于行 ID,TiDB 做了一个小优化,如果某个表有整数型的主键,TiDB 会使用主键的值当做这一行数据的行 ID。
每行数据按照如下规则编码成 (Key, Value) 键值对:
Key:tablePrefix{TableID}_recordPrefixSep{RowID} Value:[col1, col2, col3, col4]
TiDB 同时支持 主键索引 和 二级索引。与表数据映射方案类似,TiDB 为表中每个索引分配了一个 索引 ID,用 IndexID 表示。
对于 主键索引 和 唯一索引,需要根据键值快速定位到对应的 RowID,因此,按照如下规则编码成 (Key, Value) 键值对:
Key:tablePrefix{TableID}_indexPrefixSep{IndexID}_indexedColumnsValue Value:RowID
对于非唯一性约束的 普通二级索引,一个键值可能 对应多行,需要根据 键值范围 查询对应的 RowID。因此,按照如下规则编码成 (Key, Value) 键值对:
Key:tablePrefix{TableID}_indexPrefixSep{IndexID}_indexedColumnsValue_{RowID} Value:null
集群架构和数据架构
TiKV 集群:由多个 TiKV 节点构成,每个节点都是自治的,通过 Raft 协议维护数据的一致性。
数据存储结构:数据被分割成多个 Region,每个 Region 在不同节点上有副本以确保高可用。通过 PD 进行全局的 Region 管理和调度,实现数据的自动负载均衡和故障恢复。
TiKV 借鉴 Google Spanner 的设计,利用 multi-raft-group 机制实现数据的高可用和分布式管理。它将数据按 key 范围切分为 Region,每个 Region 拥有多个副本(默认3个),并由一领导副本处理读写请求,所有副本分布在不同的 TiKV 节点上。通过 PD 组件进行智能调度,确保数据和负载均衡,不仅优化了资源利用,还支持随节点增加的水平扩展能力。在这个架构中,PD 作为元数据管理系统,负责跟踪 TiKV 节点状态和数据分布情况,从而指引数据的正确路由,维持整个集群的高效运行。
数据架构四层分层架构:
- TiKV API & Coprocessor API层:TiKV API 处理 gRPC 的 KV 操作逻辑。Coprocessor API 支持 TiDB 的查询算子下推,优化 SQL 查询性能。
- 事务层:支持乐观事务和悲观事务,适应不同业务场景。基于 Percolator 算法,实现事务的 ACI 属性(原子性、一致性、隔离性)。
- 一致性层(Raft):实现Raft一致性算法保证强一致性。引入 multi-raft 算法实现数据自动分片,每个 Region 约 96MB,通过 PD 进行水平扩展管理。
- 存储层(RocksDB):使用 RocksDB 作为底层存储引擎,确保数据的持久化。包含两个 RocksDB 实例:raftdb 存储 Raft 日志,kvdb 存储用户数据及 MVCC 信息,细分为 raft、lock、write、default 四个 ColumnFamily,分别存储元信息、悲观锁、表数据及 MVCC 信息、大字段数据,以高效管理数据。
10 向量数据库 Milvus
10.1 向量模型
向量模型是指通过特定的嵌入(embedding)技术,将原始的非结构化数据转换成一个数值型的向量,在数学表示上,向量是一个由浮点数或者二值型数据组成的 n 维数组。这个过程涉及使用深度学习、自然语言处理或计算机视觉技术提取数据的特征,并将这些特征映射到一个连续的多维空间中。每个数据点在这个空间中的位置反映了其内容的语义信息和特征相似度。
特征
- 高维表示:向量通常具有较高的维度,能够捕捉数据的复杂特征和细微差异。
- 相似性计算:通过计算向量间的距离(如欧氏距离、余弦相似度等),可以快速评估两个数据点的相似性。
- 机器学习友好:向量形式的数据可以直接输入到机器学习模型中,便于进一步的分类、聚类或预测任务。
- 索引友好:可以针对向量数据构建高效的索引结构,如倒排索引、树形结构(如 KD 树、Ball Tree)或近似最近邻搜索算法(如FAISS、HNSW),加速检索过程。
局限性
- 维度灾难:随着向量维度的增加,存储和计算成本急剧上升,且“距离”概念变得模糊,可能遇到“维度灾难”问题。
- 解释性差:高维向量对于人类而言难以直观理解,其背后的语义信息不透明,降低了模型的可解释性。
- 数据稀疏性:在高维空间中,实际数据往往集中在低维流形上,导致大部分空间是空的,这称为“数据的稀疏性”,增加了搜索难度。
- 转换成本:将非结构化数据转换为高质量向量需要计算资源和时间,且对嵌入方法的选择敏感,不同的嵌入方法可能产生截然不同的结果。
- 更新和维护成本:随着数据的动态变化,向量数据库需要不断更新索引和模型,以保持检索效果,这可能带来额外的计算和存储开销。
应用场景
10.2 Milvus
Milvus 是由 Zilliz 开发并开源的高性能分布式向量数据库,最初是由阿里孵化,旨在处理大规模、多模态的向量检索任务。Milvus 是 LF AI & Data 基金会的托管项目之一。
架构
整个系统分为四个层次:
- 接入层(Access Layer):系统的门面,由一组无状态 proxy 组成。对外提供用户连接的 endpoint,负责验证客户端请求并合并返回结果。
- 协调服务(Coordinator Service):系统的大脑,负责分配任务给执行节点。协调服务共有四种角色,分别为 root coord、data coord、query coord 和 index coord。
- 执行节点(Worker Node):系统的四肢,负责完成协调服务下发的指令和 proxy 发起的数据操作语言(DML)命令。执行节点分为三种角色,分别为 data node、query node 和 index node。
- 存储服务 (Storage):系统的骨骼,负责 Milvus 数据的持久化,分为元数据存储(meta store)、消息存储(log broker)和对象存储(object storage)三个部分。
各个层次相互独立,独立扩展和容灾。
存储模型
- 元数据存储:使用 etcd 作为实现,主要负责存储系统运行所必需的元信息,例如集合的 schema 定义、系统节点的状态信息以及消息消费的 checkpoint 状态等。etcd 以其高度的可用性、强一致性保证及事务处理能力,成为元数据管理的理想选择。此外,etcd 还扮演服务发现与健康检查的角色,增强了系统的整体协调性和健壮性。
- 对象存储:采用 MinIO 作为默认方案,并兼容 AWS S3 和 Azure Blob 等云存储服务,用于存放日志快照、索引文件及查询过程中的临时结果。考虑到对象存储的访问延迟及成本问题,Milvus 规划加入基于内存或 SSD 的缓存层,通过冷热数据分离策略,优化查询性能并控制成本开支。
- 消息存储:在分布式环境中采用 Pulsar,在单机版本中使用 RocksDB,作为数据流式处理与持久化的基石。此部分设计旨在通过发布订阅模式实现数据写入的异步处理、查询执行及结果返回,同时确保在节点故障时能够通过消息重放恢复数据完整性。此外,该模块具有高度灵活性,可根据需求替换成 Kafka 或 Pravega 等其他消息队列系统。
Milvus 的设计哲学围绕日志即数据的原则,摒弃了传统意义上的物理表结构,转而利用日志的发布订阅机制来解耦数据的读写操作,实现了系统的高度可扩展性和灵活性。这种设计不仅简化了数据管理的复杂度,还为数据变更的捕捉(CDC, Change Data Capture)和系统的分布式部署提供了强大的基础,使得 Milvus 能有效应对大规模向量数据的实时处理挑战,满足高并发查询和持续增长的数据存储需求。
索引类型
Milvus 支持多种向量索引类型,目前支持的大都属于 ANNS(Approximate Nearest Neighbors Search,近似最近邻搜索)。ANNS 的核心思想是不再局限于只返回最精确的结果项,而是仅搜索可能是近邻的数据项,即以牺牲可接受范围内的精度的方式提高检索效率。
根据实现方式,ANNS 向量索引可分为五大类:基于树的索引、基于图的索引、基于哈希的索引、基于量化的索引、基于量化和图的索引。
Milvus 支持的索引类型如下:
- FLAT:适用于需要 100% 召回率且数据规模相对较小(百万级)的向量相似性搜索应用。
- IVF_FLAT:基于量化的索引,适用于追求查询准确性和查询速度之间理想平衡的场景(高速查询、要求高召回率)。
- IVF_SQ8:基于量化的索引,适用于磁盘或内存、显存资源有限的场景(高速查询、磁盘和内存资源有限、接受召回率的小幅妥协)。
- IVF_PQ:基于量化的索引,适用于追求高查询速度、低准确性的场景(超高速查询、磁盘和内存资源有限、接受召回率的实质性妥协)。
- HNSW:基于图的索引,适用于追求高查询效率的场景(高速查询、要求尽可能高的召回率、内存资源大的情景)。
- ANNOY:基于树的索引,适用于追求高召回率的场景(低维向量空间)。
- IVF_HNSW:基于量化和图的索引,高速查询、需要尽可能高的召回率、内存资源大的情景。
- RHNSW_FLAT:基于量化和图的索引,高速查询、需要尽可能高的召回率、内存资源大的情景。
- RHNSW_SQ:基于量化和图的索引,高速查询、磁盘和内存资源有限、接受召回率的小幅妥协。
- RHNSW_PQ:基于量化和图的索引,超高速查询、磁盘和内存资源有限、接受召回率的实质性妥协。
距离计算公式
Milvus 基于不同的距离计算方式比较向量间的距离。根据插入数据的形式,选择合适的距离计算方式能极大地提高数据分类和聚类性能。
浮点型向量主要使用以下距离计算公式:
- 欧氏距离 (L2):主要运用于计算机视觉领域。
- 内积 (IP):主要运用于自然语言处理(NLP)领域。
二值型向量主要使用以下距离计算公式:
- 汉明距离 (Hamming):主要运用于自然语言处理(NLP)领域。
- 杰卡德距离(Jaccard):主要运用于化学分子式检索领域。
- 谷本距离(Tanimoto):主要运用于化学分子式检索领域。
- 超结构(Superstructure):主要运用于检索化学分子式的相似超结构。
- 子结构(Substructure):主要运用于检索化学分子式的相似子结构。
11 summary
本文系统性回顾了数据库技术的发展历程与现状,从层次数据库 IMS 到新兴的向量数据库 Milvus,每一类数据库的诞生都映射了特定时代的技术挑战与应用需求。我们见证了从传统的关系型数据库如 MySQL,到为应对大数据、非结构化数据、实时分析等挑战而生的 HBase、MongoDB、Redis、Neo4j、InfluxDB、TiDB 乃至 Milvus 等新型数据库的兴起。这一系列创新不仅拓宽了数据管理的边界,也深刻影响了互联网服务、数据分析、人工智能等多个领域的进步。
展望未来,数据库技术的演进趋势清晰指向四个重要方向:垂直领域专业化、分布式架构的普及、云原生化的深入以及数据安全技术的强化。随着各行各业对数据处理需求的精细化,专注于特定行业知识和应用场景的垂直数据库将成为趋势。分布式数据库技术,特别是 HTAP 模型的探索,将持续推动数据库在扩展性、高性能与分析能力上的融合。云原生数据库以其灵活高效、成本优化的特点,正逐步成为企业数字化转型的首选。而在数据安全与隐私保护日益受到重视的今天,结合隐私计算与区块链技术的解决方案,将为数据库的安全性提供新的保障。
综上所述,数据库作为信息时代的基础设施,其发展轨迹与信息技术的革新紧密相连,每一次迭代都是对现实世界复杂需求的直接响应。未来,随着技术边界的不断拓展与市场需求的持续深化,数据库技术将继续进化,支撑起更加智能化、高效化的数字社会。在这个过程中,理解并掌握各类数据库的核心特性与应用场景,对于开发者、数据工程师乃至决策者而言,都将是至关重要的能力。
参考文献:
- InfluxDB 2.0 原理与应用实践
- https://zhuanlan.zhihu.com/p/613309793
- NoSQL DB 系列解读:一文看懂 Neo4j – Graph DB
- https://blog.csdn.net/qq_50216270/article/details/123672272
- https://blog.csdn.net/a745233700/category_10727715.html
- https://tidb.net/blog/01f8c3ee
- https://milvus.io/docs
- https://mongoing.com/archives/76151
- https://milvus.io/docs
来源 | 阿里云开发者公众号
作者 | 祭酒