目录

1、OLTP、OLAP和HTAP

1.1 OLTP

​ OLTP(on-line transaction processing),联机事务处理,对数据的「增删改」

 

1.2 OLAP

​ OLAP(On-Line Analytical Processing),联机分析处理,对数据的「查询」
 

1.2.1 OLAP分类

ROLAP与MOLAP

  • ROLAP关系型联机分析处理

    • 传统关系型数据库、MPP分布式数据库、基于Hadoop的Spark/Impala

    • 能同时连接明细数据和汇总数据,实时根据用户提出的需求对数据进行计算后返回给用户,所以用户使用相对比较灵活,可以随意选择维度组合来进行实时计算

    • 当计算的数据量达到一定级别或并发数达到一定级别的时候,一定会出现性能问题

  • MOLAP多维联机分析处理

    • Cognos,SSAS,Kylin
    • 预先将客户的需求计算好以结果的形式存下来,当命中需求后返回非常快(适合常见固定的分析场景),同等资源下支持的数据体量更大,支持的并发更多
    • 当表的维度越多,越复杂,其所需的磁盘存储空间则越大,构建cube也需要一定的时间

image-1691673499707

​   OLTP所产生的业务数据分散在不同的业务系统中,而OLAP往往需要将不同的业务数据集中到一起进行统一综合的分析,这时候就需要根据业务分析需求做对应的数据清洗后存储在数据仓库中,然后由数据仓库来统一提供OLAP分析。

 

1.3 HTAP

​   HTAP是混合 OLTP 和 OLAP 业务同时处理的系统,它打破了事务处理和分析之间的“墙”。它支持更多的信息和“实时业务”的决策

优势

  • 数据不需要从操作型数据库导入到决策类系统;
  • 操作事务,实时地对分析业务可见;
  • 上钻下取等分析操作,时刻操作最新的数据;
  • 减少对副本的要求

 

2、TiDB简介

   TiDB 是 PingCAP 公司自主设计、研发的开源分布式关系型数据库,是一款同时支持在线事务处理与在线分析处理 (Hybrid Transactional and Analytical Processing, HTAP) 的融合型分布式数据库产品,具备水平扩容或者缩容、金融级高可用、实时 HTAP、云原生的分布式数据库、兼容 MySQL 5.7 协议和 MySQL 生态等重要特性。目标是为用户提供一站式 OLTP (Online Transactional Processing)、OLAP (Online Analytical Processing)、HTAP 解决方案。TiDB 适合高可用、强一致要求较高、数据规模较大等各种应用场景。

 

3、TiDB架构

image-1691673512827

TiDB Server

​ TiDB Server 负责接收 SQL 请求,处理 SQL 相关的逻辑,并通过 PD 找到存储计算所需数据的 TiKV 地址,与 TiKV 交互获取数据,最终返回结果。 TiDB Server 是无状态的,其本身并不存储数据,只负责计算,可以无限水平扩展,可以通过负载均衡组件(如LVS、HAProxy 或 F5)对外提供统一的接入地址。

PD Server

​ Placement Driver (简称 PD) 是整个集群的管理模块,其主要工作有三个: 一是存储集群的元信息(某个 Key 存储在哪个 TiKV 节点);二是对 TiKV 集群进行调度和负载均衡(如数据的迁移、Raft group leader 的迁移等);三是分配全局唯一且递增的事务 ID。 PD 是一个集群,需要部署奇数个节点,一般线上推荐至少部署 3 个节点。

TiKV Server

​ TiKV Server 负责存储数据,从外部看 TiKV 是一个分布式的提供事务的 Key-Value 存储引擎。存储数据的基本单位是 Region,每个 Region 负责存储一个 Key Range (从 StartKey 到 EndKey 的左闭右开区间)的数据,每个 TiKV 节点会负责多个 Region 。TiKV 使用 「Raft」 协议做复制,保持数据的一致性和容灾。副本以 Region 为单位进行管理,不同节点上的多个 Region 构成一个 Raft Group,互为副本。数据在多个 TiKV 之间的负载均衡由 PD 调度,这里也是以 Region 为单位进行调度。

 

4、优势

  1. 高度兼容 MySQL
     大多数情况下,无需修改代码即可从 MySQL 轻松迁移至 TiDB,分库分表后的 MySQL 集群亦可通过 TiDB 工具进行实时迁移。
  2. 水平弹性扩展
     通过简单地增加新节点即可实现 TiDB 的水平扩展,按需扩展吞吐或存储,轻松应对高并发、海量数据场景。
  3. 分布式事务
     TiDB 100% 支持标准的 ACID 事务,无论是一个地方的几个节点,还是跨多个数据中心的多个节点。
  4. 真正金融级高可用
     相比于传统主从 (M-S) 复制方案,基于 Raft 的多数派选举协议可以提供金融级的 100% 数据强一致性保证,且在不丢失大多数副本的前提下,可以实现故障的自动恢复 (auto-failover),无需人工介入。
  5. 一站式 HTAP 解决方案
     TiDB 作为典型的 OLTP 行存数据库,同时兼具强大的 OLAP 性能,配合 TiSpark,可提供一站式 HTAP解决方案,一份存储同时处理OLTP & OLAP(OLAP、OLTP的介绍和比较 )无需传统繁琐的 ETL 过程。
  6. 云原生 SQL 数据库
     TiDB 是为云而设计的数据库,同 Kubernetes (十分钟带你理解Kubernetes核心概念 )深度耦合,支持公有云、私有云和混合云,使部署、配置和维护变得十分简单。

   TiDB 的设计目标是 100% 的 OLTP 场景和 80% 的 OLAP 场景,更复杂的 OLAP 分析可以通过 TiSpark 项目来完成。 TiDB 对业务没有任何侵入性,能优雅的替换传统的数据库中间件、数据库分库分表等 Sharding 方案。同时它也让开发运维人员不用关注数据库 Scale 的细节问题,专注于业务开发,极大的提升研发的生产力。

 

5、核心特性

5.1 水平扩展

  无限水平扩展是 TiDB 的一大特点,这里说的水平扩展包括两方面:计算能力和存储能力。

​   TiDB Server 负责处理 SQL 请求,随着业务的增长,可以简单的添加 TiDB Server 节点,提高整体的处理能力,提供更高的吞吐。

​   TiKV 负责存储数据,随着数据量的增长,可以部署更多的 TiKV Server 节点解决数据 Scale 的问题。

​   PD 会在 TiKV 节点之间以 Region 为单位做调度,将部分数据迁移到新加的节点上。所以在业务的早期,可以只部署少量的服务实例(推荐至少部署 3 个 TiKV, 3 个 PD,2 个 TiDB),随着业务量的增长,按照需求添加 TiKV 或者 TiDB 实例。

 

5.2 高可用

  高可用是 TiDB 的另一大特点,TiDB/TiKV/PD 这三个组件都能容忍部分实例失效,不影响整个集群的可用性。下面分别说明这三个组件的可用性、单个实例失效后的后果以及如何恢复。

5.2.1 TiDB

 
 TiDB 是无状态的,推荐至少部署两个实例,前端通过负载均衡组件对外提供服务。当单个实例失效时,会影响正在这个实例上进行的 Session,从应用的角度看,会出现单次请求失败的情况,重新连接后即可继续获得服务。单个实例失效后,可以重启这个实例或者部署一个新的实例。
 

5.2.2 PD

  PD 是一个集群,通过 Raft 协议保持数据的一致性,单个实例失效时,如果这个实例不是 Raft 的 leader,那么服务完全不受影响;如果这个实例是 Raft 的 leader,会重新选出新的 Raft leader,自动恢复服务。PD 在选举的过程中无法对外提供服务,这个时间大约是3秒钟。推荐至少部署三个 PD 实例,单个实例失效后,重启这个实例或者添加新的实例。

5.2.3 TiKV

  TiKV 是一个集群,通过 Raft 协议(raft一致性哈算法以及Raft 为什么是更易理解的分布式一致性算法 )保持数据的一致性(副本数量可配置,默认保存三副本),并通过 PD 做负载均衡调度。

​   单个节点失效时,会影响这个节点上存储的所有 Region。对于 Region 中的 Leader 结点,会中断服务,等待重新选举;对于 Region 中的 Follower 节点,不会影响服务。当某个 TiKV 节点失效,并且在一段时间内(默认 30 分钟)无法恢复,PD 会将其上的数据迁移到其他的 TiKV 节点上。

 

6、核心原理

6.1 存储原理

6.1.1 Key-Value

​ TiDB选用的是Key-Value作为存储模型,并且提供有序遍历的方法,在这个巨大的Map中,Key按照Byte舒张压总的原始二进制比特位比较顺序排列,所以我们可以Seek到某一个Key的位置,然后不断调用Next方法以递增的顺序获取比这个Key大的Key-Value。

 

6.1.2 RocksDB

TiDB选用的是RocksDB作为存储引擎,而不是直接向磁盘上写数据。RocksDB是一个高性能的单机存储引擎(可以认为是一个单机的Key-Value Map),由Facebook的团队在做持续的优化。
 

6.1.3 Raft

TiDB选用Raft协议来做数据复制,保证副本的一致性。

数据的写入是通过Raft的接口写入,然后再通过RocksDB存储到磁盘上。

Raft 是一个一致性协议,提供几个重要的功能:

  1. Leader 选举
  2. 成员变更
  3. 日志复制

 

6.1.4 Region

​ 对于一个KV系统,将数据分散在多台机器上有两种典型方案

  1. 按照 Key 做 Hash,根据 Hash 值选择对应的存储节点
  2. 分 Range,某一段连续的 Key 都保存在一个存储节点上

​ TiDB采用的是第二种方式,将整个 Key-Value 空间分成很多段,每一段是一系列连续的 Key,我们将每一段叫做一个 Region,并且我们会尽量保持每个 Region 中保存的数据不超过一定的大小(这个大小可以配置,目前默认是 96mb)。每一个 Region 都可以用 StartKey 到 EndKey 这样一个左闭右开区间来描述。

​ 将数据划分为Region后:

  • 以 Region 为单位,将数据分散在集群中所有的节点上,并且尽量保证每个节点上服务的 Region 数量差不多
  • 以 Region 为单位做 Raft 的复制和成员管理,一个 Region 的多个 Replica 会保存在不同的节点上,构成一个 Raft Group。其中一个 Replica 会作为这个 Group 的 Leader,其他的 Replica 作为 Follower。所有的读和写都是通过 Leader 进行,再由 Leader 复制给 Follower。

image-1691673536845

6.1.5 MVCC

  MVCC即多版本控制,当两个Client同时修改一个Key的Value时,如果没有MVCC,就需要对数据进行上锁,在分布式的情况下很可能会带来性能和死锁问题。

   TiDB实现MVCC的方式是通过在key后面添加version来实现的。对于同一个Key的多个版本,TiDB把版本号大的放在前面,版本号小的放在后面,当用户通过Key+Version获取Value时,可以直接定位到第一个大于等于这个Key-Version的位置。

 

6.1.6 Percolator

   TiDB采用 Percolator 作为事务模型,并作了大量的优化。TiKV的事务采用乐观锁,在事务的执行过程中,不会检测写写冲突,只有在提交过程中,才会做冲突检测,冲突双方较早完成提交的会写入成功,另一方会尝试重新执行整个事务。

  在写入冲突不严重的情况下,这种模型的性能会很好,但如果存在大量的写入冲突,性能就会变得很差。

 

6.2 计算原理

6.2.1 关系模型到 Key-Value 模型的映射

对于一个 Table 来说,需要存储的数据包括三部分:

  1. 表的元信息
  2. Table 中的 Row
  3. 索引数据

​    TiDB 面向的首要目标是 OLTP 业务,这类业务需要支持快速地读取、保存、修改、删除一行数据,所以采用行存是比较合适的。

​   对于 Index,TiDB 不止需要支持 Primary Index,还需要支持 Secondary Index。Index 的作用的辅助查询,提升查询性能,以及保证某些 Constraint。查询的时候有两种模式,一种是点查,比如通过 Primary Key 或者 Unique Key 的等值条件进行查询;另一种是 Range 查询。Index 还分为 Unique Index 和 非 Unique Index,这两种都需要支持。

   TiDB 对每个表分配一个「 TableID」,每一个索引都会分配一个 「IndexID」,每一行分配一个 「RowID」(如果表有整数型的 Primary Key,那么会用 Primary Key 的值当做 RowID),其中 TableID 在整个集群内唯一,IndexID/RowID 在表内唯一,这些 ID 都是 int64 类型。

每行数据

Key: tablePrefix{tableID}_recordPrefixSep{rowID}
Value: [col1, col2, col3, col4]

Unique Index

Key: tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue
Value: rowID

非 Unique Index:可能有多行数据的 ColumnsValue是一样的,所以再加上rowID使其唯一

Key: tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue_rowID
Value: null

其中 Key 的 Prefix都是特定的字符串常量,用于在 KV 空间内区分其他数据。

Memcomparable
  保证编码前和编码后的比较关系不变的方案,对于任何类型的值,两个对象编码前的原始类型比较结果,和编码成 byte 数组后的比较结果保持一致。采用这种编码后,一个表的所有 Row 数据就会按照 RowID 的顺序排列在 TiKV 的 Key 空间中,某一个 Index 的数据也会按照 Index 的 ColumnValue 顺序排列在 Key 空间内。

 

6.2.2 元信息管理

Database/Table 都有元信息,也就是其定义以及各项属性。每个 Database/Table 都被分配了一个唯一的 ID,这个 ID 作为唯一标识,并且在编码为 Key-Value 时,这个 ID 都会编码到 Key 中,再加上 m_前缀。这样可以构造出一个 Key,Value 中存储的是序列化后的元信息。

 

6.2.3 分布式 SQL运算

将 SQL 查询映射为对 KV 的查询,再通过 KV 接口获取对应的数据,最后执行各种计算。首先我们需要将计算尽量靠近存储节点,以避免大量的 RPC 调用。其次,我们需要将 Filter 也下推到存储节点进行计算,这样只需要返回有效的行,避免无意义的网络传输。最后,我们可以将聚合函数、GroupBy 也下推到存储节点,进行预聚合,每个节点只需要返回一个 Count 值即可,再由 tidb-server 将 Count 值 Sum 起来。

image-1691673553804

6.2.4 SQL 层架构

  用户的 SQL 请求会直接或者通过 Load Balancer 发送到 tidb-server,tidb-server 会解析 MySQL Protocol Packet,获取请求内容,然后做语法解析、查询计划制定和优化、执行查询计划获取和处理数据。数据全部存储在 TiKV 集群中,所以在这个过程中 tidb-server 需要和 tikv-server 交互,获取数据。最后 tidb-server 需要将查询结果返回给用户。

image-1691673568332

6.3 调度原理

作为一个分布式高可用存储系统,必须满足的需求,包括四种:

  • 副本数量不能多也不能少
  • 副本需要分布在不同的机器上
  • 新加节点后,可以将其他节点上的副本迁移过来
  • 节点下线后,需要将该节点的数据迁移走

作为一个良好的分布式系统,需要优化的地方,包括:

  • 维持整个集群的 Leader 分布均匀
  • 维持每个节点的储存容量均匀
  • 维持访问热点分布均匀
  • 控制 Balance 的速度,避免影响在线服务
  • 管理节点状态,包括手动上线/下线节点,以及自动下线失效节点

 

6.3.1 调度的基本操作
  • 增加一个 Replica

  • 删除一个 Replica

  • 将 Leader 角色在一个 Raft Group 的不同 Replica 之间 transfer

    刚好 Raft 协议能够满足这三种需求,通过 AddReplica、RemoveReplica、TransferLeader 这三个命令,可以支撑上述三种基本操作。

 

6.3.2 信息收集

调度依赖于整个集群信息的收集,包括每个 TiKV 节点的状态以及每个 Region 的状态。

每个 TiKV 节点会定期向 PD 汇报节点的整体信息

TiKV 节点(Store)与 PD 之间存在心跳包,一方面 PD 通过心跳包检测每个 Store 是否存活,以及是否有新加入的 Store;另一方面,心跳包中也会携带这个 Store 的状态信息 ,主要包括:

  • 总磁盘容量
  • 可用磁盘容量
  • 承载的 Region 数量
  • 数据写入速度
  • 发送/接受的 Snapshot 数量(Replica 之间可能会通过 Snapshot 同步数据)
  • 是否过载
  • 标签信息(标签是具备层级关系的一系列 Tag)

每个 Raft Group 的 Leader 会定期向 PD 汇报信息

每个 Raft Group 的 Leader 和 PD 之间存在心跳包,用于汇报这个 Region 的状态 ,主要包括下面几点信息:

  • Leader 的位置

  • Followers 的位置

  • 掉线 Replica 的个数

  • 数据写入/读取的速度

    PD 不断的通过这两类心跳消息收集整个集群的信息,再以这些信息作为决策的依据。除此之外,PD 还可以通过管理接口接受额外的信息,用来做更准确的决策。比如当某个 Store 的心跳包中断的时候,PD 并不能判断这个节点是临时失效还是永久失效,只能经过一段时间的等待(默认是 30 分钟),如果一直没有心跳包,就认为是 Store 已经下线,再决定需要将这个 Store 上面的 Region 都调度走。

    但是有的时候,是运维人员主动将某台机器下线,这个时候,可以通过 PD 的管理接口通知 PD 该 Store 不可用,PD 就可以马上判断需要将这个 Store 上面的 Region 都调度走。

 

6.3.3 调度策略

一个 Region 的 Replica 数量正确

当 PD 通过某个 Region Leader 的心跳包发现这个 Region 的 Replica 数量不满足要求时,需要通过 Add/Remove Replica 操作调整 Replica 数量。出现这种情况的可能原因是:

  • 某个节点掉线,上面的数据全部丢失,导致一些 Region 的 Replica 数量不足
  • 某个掉线节点又恢复服务,自动接入集群,这样之前已经补足了 Replica 的 Region 的 Replica 数量多过,需要删除某个 Replica
  • 管理员调整了副本策略,修改了 max-replicas的配置

 

一个 Raft Group 中的多个 Replica 不在同一个位置

在一般情况下,PD 只会保证多个 Replica 不落在一个节点上,以避免单个节点失效导致多个 Replica 丢失。在实际部署中,还可能出现下面这些需求:

  • 多个节点部署在同一台物理机器上

  • TiKV 节点分布在多个机架上,希望单个机架掉电时,也能保证系统可用性

  • TiKV 节点分布在多个 IDC 中,希望单个机房掉电时,也能保证系统可用

    这些需求本质上都是某一个节点具备共同的位置属性,构成一个最小的容错单元,我们希望这个单元内部不会存在一个 Region 的多个 Replica。这个时候,可以给节点配置 lables 并且通过在 PD 上配置 location-labels 来指明哪些 lable 是位置标识,需要在 Replica 分配的时候尽量保证不会有一个 Region 的多个 Replica 所在结点有相同的位置标识。

副本在 Store 之间的分布均匀分配

   每个副本中存储的数据容量上限是固定的,维持每个节点上面,副本数量的均衡,会使得总体的负载更均衡。

Leader 数量在 Store 之间均匀分配

  Raft 协议要读取和写入都通过 Leader 进行,所以计算的负载主要在 Leader 上面,PD 会尽可能将 Leader 在节点间分散开。

访问热点数量在 Store 之间均匀分配

  每个 Store 以及 Region Leader 在上报信息时携带了当前访问负载的信息,比如 Key 的读取/写入速度。PD 会检测出访问热点,且将其在节点之间分散开。

各个 Store 的存储空间占用大致相等

  每个 Store 启动的时候都会指定一个 Capacity 参数,表明这个 Store 的存储空间上限,PD 在做调度的时候,会考虑节点的存储空间剩余量。

控制调度速度,避免影响在线服务

  调度操作需要耗费 CPU、内存、磁盘 IO 以及网络带宽,我们需要避免对线上服务造成太大影响。PD 会对当前正在进行的操作数量进行控制,默认的速度控制是比较保守的,如果希望加快调度(比如已经停服务升级,增加新节点,希望尽快调度),那么可以通过 pd-ctl 手动加快调度速度。

支持手动下线节点

  当通过 pd-ctl 手动下线节点后,PD 会在一定的速率控制下,将节点上的数据调度走。当调度完成后,就会将这个节点置为下线状态。

7、TiDB与SQL的兼容性

​   TiDB 支持包括跨行事务,JOIN 及子查询在内的绝大多数 MySQL 的语法。一些 MySQL 语法在 TiDB 中可以解析通过,但是不会做任何后续的处理。

不支持的特性:

  • 存储过程
  • 视图
  • 触发器
  • 自定义函数
  • 外键约束
  • 全文索引
  • 空间索引
  • 非 UTF8 字符集

  
参考链接:
https://www.zhihu.com/question/24110442/answer/851671343
https://blog.csdn.net/weixin_34112030/article/details/92512973
https://zhuanlan.zhihu.com/p/71073707
https://docs.pingcap.com/zh/tidb/stable
  
如有不对,烦请指出,感谢!