TIDB专题之第三章 TIDB分布式数据库存储原理

1、TiDB的存储模型 - 逻辑存储
  • TIKV:一个高性能高可靠性的巨大的(分布式的) Map,并且提供有序遍历方法。

在这里插入图片描述

(1)这是一个巨大的 Map,也就是存储的是 Key-Value Pairs(键值对)

(2)这个 Map 中的 Key-Value pair 按照 Key 的二进制顺序有序,也就是可以 Seek 到某一个 Key 的位置,然后不断地调用 Next 方法以递增的顺序获取比这个 Key 大的 Key-Value

2、RocksDB-物理存储

​ TiKV 没有选择直接向磁盘上写数据,而是把数据保存在 RocksDB 中,具体的数据落地由 RocksDB 负责,我们可以简单的认为RocksDB 是一个单机的 Key-Value Map

在这里插入图片描述

3、数据一致性保证 - Raft协议
  • 业务场景:TiKV 如何保证单机失效的情况下,数据不丢失,不出错?

​ 需要想办法把数据复制到多台机器上,这样一台机器无法服务了,其他的机器上的副本还能提供服务; 复杂来说,还需要这个数据复制方案是可靠和高效的,并且能处理副本失效的情况。TiKV 选择了 Raft 算法。

(1)Leader(主副本)选举

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

(3)日志复制

在这里插入图片描述

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

4、Region & Replica
  • Region:数据复制的基本单位,类比Hbase的Region

  • Replica:数据复制后的基本名称,即数据副本名称,类比Hbase的Replica

  • 下面以三副本为例:

在这里插入图片描述

(1)数据按照 Key 切分成很多 Region,每个 Region 的数据只会保存在一个节点上面。我们的系统会有一个组件来负责将 Region 尽可能均匀的散布在集群中所有的节点上,这样一方面实现了存储容量的水平扩展(增加新的节点后,会自动将其他节点上的 Region 调度过来),另一方面也实现了负载均衡(不会出现某个节点有很多数据,其他节点上没什么数据的情况)。同时为了保证上层客户端能够访问所需要的数据,我们的系统中也会有一个组件记录 Region 在节点上面的分布情况,也就是通过任意一个 Key 就能查询到这个 Key 在哪个 Region 中,以及这个 Region 目前在哪个节点上。

(2)TiKV 是以 Region 为单位做数据的复制,也就是一个 Region 的数据会保存多个副本,我们将每一个副本叫做一个 Replica。Repica 之间是通过 Raft 来保持数据的一致(终于提到了 Raft),一个 Region 的多个 Replica 会保存在不同的节点上,构成一个 Raft Group。其中一个 Replica 会作为这个 Group 的 Leader,其他的 Replica 作为 Follower。所有的读和写都是通过 Leader 进行,再由 Leader 复制给 Follower。

5、MVCC版本控制
  • 业务场景:两个客户端同时去修改一个 Key 的 Value,如果没有数据的多版本控制,就需要对数据上锁,在分布式场景下,可能会带来性能以及死锁问题。
  • 解决方式:TiKV 的 MVCC 实现是通过在 Key 后面添加版本号来实现,类比Hbase的时间戳。

在这里插入图片描述

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

6、表数据和KV的映射关系
  • 表数据:表中的每一行的数据
  • 索引数据:表中所有索引的数据

(1)表数据与KeyValue的映射关系

①TableID:TiDB 会为每个表分配一个表 ID,用 TableID 表示,表 ID 是一个整数,在整个集群内唯一

②RowID:TiDB 会为表中每行数据分配一个行 ID,用 RowID 表示,行 ID 也是一个整数,在表内唯一,对于行 ID,TiDB 做了一个小优化,如果某个表有整数型的主键,TiDB 会使用主键的值当做这一行数据的行 ID。

  • 示例:T1表的三行数据其中一个副本的物理存储

在这里插入图片描述

(2)索引数据与Key-Value的映射关系

  • 索引支持:TiDB 同时支持主键和二级索引(包括唯一索引和非唯一索引)

①主键索引:对于主键和唯一索引,需要根据键值快速定位到对应的 RowID

Key: tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue

Value: RowID

③二级索引:对于不需要满足唯一性约束的普通二级索引,一个键值可能对应多行,需要根据键值范围查询对应的 RowID

Key: tablePrefix{TableID}_indexPrefixSep{IndexID}_indexedColumnsValue_{RowID}

Value: null

(3)Key - Value映射关系示例

  • 建表 & 数据插入语句
CREATE TABLE User {undefined

ID int,
Name varchar(20),
Role varchar(20),
Age int,
PRIMARY KEY (ID),
KEY idxAge (Age)
};

1, "TiDB", "SQL Layer", 10

2, "TiKV", "KV Engine", 20

3, "PD", "Manager", 30

①表的映射关系:首先每行数据都会映射为一个 (Key, Value) 键值对,同时该表有一个 int 类型的主键,所以 RowID 的值即为该主键的值。假设该表的 TableID 为 10,则其存储在 TiKV 上的表数据为:

t10_r1 --> ["TiDB", "SQL Layer", 10]

t10_r2 --> ["TiKV", "KV Engine", 20]

t10_r3 --> ["PD", "Manager", 30]

②索引的映射关系:该表还有一个非唯一的普通二级索引 idxAge,假设这个索引的 IndexID 为 1,则其存储在 TiKV 上的索引数据为:

t10_i1_10_1 --> null

t10_i1_20_2 --> null

t10_i1_30_3 --> null

(4)关于编码方案

​ 无论是表数据还是索引数据的 Key 编码方案,一个表内所有的行都有相同的 Key 前缀,一个索引的所有数据也都有相同的前缀。这样具有相同的前缀的数据,在 TiKV 的 Key 空间内,是排列在一起的。因此只要小心地设计后缀部分的编码方案,保证编码前和编码后的比较关系不变,就可以将表数据或者索引数据有序地保存在 TiKV 中。采用这种编码后,一个表的所有行数据会按照 RowID 顺序地排列在 TiKV 的 Key 空间中,某一个索引的数据也会按照索引数据的具体的值(编码方案中的 indexedColumnsValue)顺序地排列在 Key 空间内。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

随缘清风殇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值