Alexu
发布于 2025-03-10 / 2 阅读
0
0

TiDB

TiDB 是一个开源的分布式 NewSQL 数据库,它兼容 MySQL 协议,支持混合事务和分析处理(HTAP)工作负载。由 PingCAP 公司开发,旨在解决传统单节点数据库在可扩展性和高可用性方面的限制。以下是 TiDB 的一些关键特性:

  1. 水平扩展:通过添加更多的节点来线性增加存储和计算能力,使得数据库可以轻松应对数据量的增长。

  2. 高可用性:利用 Raft 算法实现数据复制,保证数据的高可用性和容错性。即使部分节点发生故障,系统仍能正常运行。

  3. 分布式事务:支持分布式 ACID 事务,允许跨多个节点执行原子操作,确保数据的一致性和完整性。

  4. MySQL 兼容性:对 MySQL 协议的高度兼容,使现有的 MySQL 应用程序可以几乎不做修改就迁移到 TiDB 上。

  5. 实时 HTAP 支持:通过整合行存储与列存储引擎,TiDB 可以同时满足在线事务处理(OLTP)和在线分析处理(OLAP)的需求,减少数据移动的成本和时间延迟。

  6. 云原生友好:TiDB 能够很好地部署在容器化平台如 Kubernetes 上,并支持多种云环境,提供灵活的部署选项。

  7. 弹性调度:根据集群中的资源使用情况动态调整数据分布和副本位置,优化性能并提高资源利用率。

  8. 多租户支持:能够在同一物理集群上为不同的用户提供服务,通过隔离机制保证各租户之间的安全性。

TiDB 因其优秀的架构设计和强大的功能,在国内外得到了广泛的应用,尤其是在需要处理大规模数据、要求高性能和高可靠性的场景中。

TiDB 整体架构

与传统的单机数据库相比,TiDB 具有以下优势:

  • 纯分布式架构,拥有良好的扩展性,支持弹性的扩缩容

  • 支持 SQL,对外暴露 MySQL 的网络协议,并兼容大多数 MySQL 的语法,在大多数场景下可以直接替换 MySQL

  • 默认支持高可用,在少数副本失效的情况下,数据库本身能够自动进行数据修复和故障转移,对业务透明

  • 支持 ACID 事务,对于一些有强一致需求的场景友好,例如:银行转账

  • 具有丰富的工具链生态,覆盖数据迁移、同步、备份等多种场景

在内核设计上,TiDB 分布式数据库将整体架构拆分成了多个模块,各模块之间互相通信,组成完整的 TiDB 系统。对应的架构图如下:

architecture

  • TiDB Server:SQL 层,对外暴露 MySQL 协议的连接 endpoint,负责接受客户端的连接,执行 SQL 解析和优化,最终生成分布式执行计划。TiDB 层本身是无状态的,实践中可以启动多个 TiDB 实例,通过负载均衡组件(如 TiProxy、LVS、HAProxy、ProxySQL 或 F5)对外提供统一的接入地址,客户端的连接可以均匀地分摊在多个 TiDB 实例上以达到负载均衡的效果。TiDB Server 本身并不存储数据,只是解析 SQL,将实际的数据读取请求转发给底层的存储节点 TiKV(或 TiFlash)。

  • PD (Placement Driver) Server:整个 TiDB 集群的元信息管理模块,负责存储每个 TiKV 节点实时的数据分布情况和集群的整体拓扑结构,提供 TiDB Dashboard 管控界面,并为分布式事务分配事务 ID。PD 不仅存储元信息,同时还会根据 TiKV 节点实时上报的数据分布状态,下发数据调度命令给具体的 TiKV 节点,可以说是整个集群的“大脑”。此外,PD 本身也是由至少 3 个节点构成,拥有高可用的能力。建议部署奇数个 PD 节点。

  • 存储节点

    • TiKV Server:负责存储数据,从外部看 TiKV 是一个分布式的提供事务的 Key-Value 存储引擎。存储数据的基本单位是 Region,每个 Region 负责存储一个 Key Range(从 StartKey 到 EndKey 的左闭右开区间)的数据,每个 TiKV 节点会负责多个 Region。TiKV 的 API 在 KV 键值对层面提供对分布式事务的原生支持,默认提供了 SI (Snapshot Isolation) 的隔离级别,这也是 TiDB 在 SQL 层面支持分布式事务的核心。TiDB 的 SQL 层做完 SQL 解析后,会将 SQL 的执行计划转换为对 TiKV API 的实际调用。所以,数据都存储在 TiKV 中。另外,TiKV 中的数据都会自动维护多副本(默认为三副本),天然支持高可用和自动故障转移。

    • TiFlash:TiFlash 是一类特殊的存储节点。和普通 TiKV 节点不一样的是,在 TiFlash 内部,数据是以列式的形式进行存储,主要的功能是为分析型的场景加速。

TiDB 数据库的存储

本文主要介绍 TiKV 的一些设计思想和关键概念。

storage-architecture

Key-Value Pairs(键值对)

作为数据保存系统,首先要决定数据的存储模型,即数据的保存形式。TiKV 选择使用 Key-Value 模型,并提供有序遍历方法。

TiKV 数据存储的两个关键点:

  • TiKV 实现了一个巨大的 Map(类似于 C++ 的 std::map),用于存储 Key-Value Pairs(键值对)。

  • Map 中的键值对按照键的二进制顺序排序,可以通过 Seek 方法定位某个键,然后使用 Next 方法以递增顺序获取更大的键值对。

注意,TiKV 的 KV 存储模型与 SQL 中的表无关。本文不讨论 SQL 概念,专注于 TiKV 的高性能、高可靠性和分布式键值存储的实现。

本地存储 (RocksDB)

任何持久化的存储引擎,数据终归要保存在磁盘上,TiKV 也不例外。但是 TiKV 没有选择直接向磁盘上写数据,而是把数据保存在 RocksDB 中,具体的数据落地由 RocksDB 负责。这个选择的原因是开发一个单机存储引擎工作量很大,特别是要做一个高性能的单机引擎,需要做各种细致的优化,而 RocksDB 是由 Facebook 开源的一个非常优秀的单机 KV 存储引擎,可以满足 TiKV 对单机引擎的各种要求。这里可以简单的认为 RocksDB 是一个单机的持久化 Key-Value Map。

Raft 协议

接下来 TiKV 的实现面临一件更难的事情:如何保证单机失效的情况下,数据不丢失,不出错?

简单来说,需要想办法把数据复制到多台机器上,这样一台机器无法服务了,其他的机器上的副本还能提供服务;复杂来说,还需要这个数据复制方案是可靠和高效的,并且能处理副本失效的情况。TiKV 选择了 Raft 算法。Raft 是一个一致性协议,本文只会对 Raft 做一个简要的介绍,细节问题可以参考它的论文。Raft 提供几个重要的功能:

  • Leader(主副本)选举

  • 成员变更(如添加副本、删除副本、转移 Leader 等操作)

  • 日志复制

TiKV 利用 Raft 来做数据复制,每个数据变更都会落地为一条 Raft 日志,通过 Raft 的日志复制功能,将数据安全可靠地同步到复制组的每一个节点中。不过在实际写入中,根据 Raft 的协议,只需要同步复制到多数节点,即可安全地认为数据写入成功。

Raft in TiDB

总结一下,通过单机的 RocksDB,TiKV 可以将数据快速地存储在磁盘上;通过 Raft,将数据复制到多台机器上,以防单机失效。数据的写入是通过 Raft 这一层的接口写入,而不是直接写 RocksDB。通过实现 Raft,TiKV 变成了一个分布式的 Key-Value 存储,少数几台机器宕机也能通过原生的 Raft 协议自动把副本补全,可以做到对业务无感知。

Region

为了便于理解,假设所有的数据都只有一个副本。可以将 TiKV 看作一个巨大而有序的 KV Map,为了实现存储的水平扩展,数据将被分散在多台机器上。对于一个 KV 系统,将数据分散在多台机器上有两种比较典型的方案:

  • Hash:按照 Key 做 Hash,根据 Hash 值选择对应的存储节点。

  • Range:按照 Key 分 Range,某一段连续的 Key 都保存在一个存储节点上。

TiKV 选择了第二种方式,将整个 Key-Value 空间分成很多段,每一段是一系列连续的 Key,将每一段叫做一个 Region,可以用 [StartKey,EndKey) 这样一个左闭右开区间来描述。每个 Region 中保存的数据量默认维持在 256 MiB 左右(可以通过配置修改)。

Region in TiDB

注意,这里的 Region 还是和 SQL 中的表没什么关系。 这里的讨论依然不涉及 SQL,只和 KV 有关。

将数据划分成 Region 后,TiKV 执行两项重要操作:

  • 按 Region 将数据分散到集群中的所有节点,并尽量保证每个节点上的 Region 数量大致相同。

  • 以 Region 为单位进行 Raft 的复制和成员管理。

以下两点非常关键:

  • 数据按 Key 切分成多个 Region,每个 Region 的数据仅保存在一个节点上(暂不考虑多副本)。TiDB 系统中的 PD 组件负责将 Region 尽可能均匀地分布在集群节点上,实现存储容量的水平扩展(增加新节点后,会自动调度其他节点上的 Region)和负载均衡(避免某节点存储过多数据而其他节点存储较少)。为了确保上层客户端能访问所需数据,PD 组件会记录 Region 的分布情况,可通过任意 Key 查询其所在的 Region 及其对应的节点(即 Key 的位置路由信息)。

  • TiKV 以 Region 为单位进行数据复制,一个 Region 的数据会保存多个副本,称为 Replica。Replica 之间通过 Raft 保持数据一致性,构成一个 Raft Group,其中一个 Replica 作为 Leader,其他作为 Follower。默认情况下,所有读写操作均通过 Leader 进行,读操作在 Leader 上即可完成,而写操作由 Leader 复制给 Follower。

大家理解了 Region 之后,应该可以理解下面这张图:

TiDB Storage

以 Region 为单位做数据的分散和复制,TiKV 就成为了一个分布式的具备一定容灾能力的 KeyValue 系统,不用再担心数据存不下,或者是磁盘故障丢失数据的问题。

MVCC

TiKV 支持多版本并发控制 (Multi-Version Concurrency Control, MVCC)。假设有这样一种场景:某客户端 A 在写一个 Key,另一个客户端 B 同时在对这个 Key 进行读操作。如果没有数据的多版本控制机制,那么这里的读写操作必然互斥。在分布式场景下,这种情况可能会导致性能问题和死锁问题。有了 MVCC,只要客户端 B 执行的读操作的逻辑时间早于客户端 A,那么客户端 B 就可以在客户端 A 写入的同时正确地读原有的值。即使该 Key 被多个写操作修改过多次,客户端 B 也可以按照其逻辑时间读到旧的值。

TiKV 的 MVCC 是通过在 Key 后面添加版本号来实现的。没有 MVCC 时,可以把 TiKV 看作如下的 Key-Value 对:

Key1 -> Value
Key2 -> Value
……
KeyN -> Value

有了 MVCC 之后,TiKV 的 Key-Value 排列如下:

Key1_Version3 -> Value
Key1_Version2 -> Value
Key1_Version1 -> Value
……
Key2_Version4 -> Value
Key2_Version3 -> Value
Key2_Version2 -> Value
Key2_Version1 -> Value
……
KeyN_Version2 -> Value
KeyN_Version1 -> Value
……

注意,对于同一个 Key 的多个版本,版本号较大的会被放在前面,版本号小的会被放在后面(见 Key-Value 一节,Key 是有序的排列),这样当用户通过一个 Key + Version 来获取 Value 的时候,可以通过 Key 和 Version 构造出 MVCC 的 Key,也就是 Key_Version。然后可以直接通过 RocksDB 的 SeekPrefix(Key_Version) API,定位到第一个大于等于这个 Key_Version 的位置。

分布式 ACID 事务

TiKV 的事务采用的是 Google 在 BigTable 中使用的事务模型:Percolator,TiKV 根据这篇论文实现,并做了大量的优化。详细介绍参见事务概览


评论