TiDB学习笔记【初阶】

文章目录

    • 一、TiDB简介
      • 五大核心特性
      • 四大核心应用场景
    • 二、快速上手
    • 三、TiDB架构原理
      • 1、TiDB数据库的存储——TiKV Server
        • TiKV
        • Raft和RocksDB
        • Region概念
        • MVCC
        • GC
      • 2、TiDB数据库的计算——TiDB Server
        • SQL映射KV
        • 分布式SQL运算
        • SQL执行流程
      • 3、TiDB数据库的调度——PD Server
        • 调度场景
        • 调度需求
        • 调度操作
        • 调度策略
        • 调度实现
    • 五、TiDB最佳实践
      • Raft
      • 分布式事务
      • 数据分片
      • 负载均衡
      • SQL on KV
      • 二级索引

相关链接:
TiDB官网【超易读型官网】: https://docs.pingcap.com/zh/tidb/stable
TiDB代码库: https://github.com/pingcap/tidb
TiDB系统变量: https://docs.pingcap.com/zh/tidb/stable/system-variables

TiDB设计简单,官网和代码都非常易读,是学习分布式数据库的首选开源项目。

数据库、操作系统和编译器并称为三大系统,可以说是整个计算机软件的基石。

很多人用过数据库,但是很少有人实现过一个数据库,特别是实现一个分布式数据库。了解数据库的实现原理和细节,一方面可以提高个人技术,对构建其他系统有帮助,另一方面也有利于用好数据库。

一、TiDB简介

TiDB 是一个分布式 NewSQL 数据库。它支持水平弹性扩展、ACID 事务、标准 SQL、MySQL 语法和 MySQL 协议,具有数据强一致的高可用特性,是一个不仅适合 OLTP 场景还适合 OLAP 场景的混合数据库

OLTP:On-Line Transaction Processing,联机事务处理
OLAP:On-Line Analytical Processing,联机分析处理

  • 高度兼容 MySQL 5.7

TiDB 高度兼容 MySQL 5.7 协议、MySQL 5.7 常用的功能及语法。虽然 TiDB 支持 MySQL 语法和协议,但是 TiDB 是由 PingCAP 团队完全自主开发的产品,并非基于MySQL开发。

MySQL 5.7 生态中的系统工具 (PHPMyAdmin、Navicat、MySQL Workbench、mysqldump、Mydumper、Myloader)、客户端等均适用于 TiDB。

TiDB 目前还不支持触发器、存储过程、自定义函数、外键。

  • 易用性

TiDB 使用起来很简单,可以将 TiDB 集群当成 MySQL 来用,可以将 TiDB 用在任何以 MySQL 作为后台存储服务的应用中,并且基本上不需要修改应用代码,同时可以用大部分流行的 MySQL 管理工具来管理 TiDB。

只要支持 MySQL Client/Driver 的编程语言,都可以直接使用 TiDB

  • 支持分布式事务

无论是一个地方的几个节点,还是跨多个数据中心的多个节点,TiDB 均支持 ACID 分布式事务

TiDB 事务模型灵感源自 Google Percolator 模型,主体是一个两阶段提交协议,并进行了一些实用的优化。该模型依赖于一个时间戳分配器,为每个事务分配单调递增的时间戳,这样就检测到事务冲突。在 TiDB 集群中,PD 承担时间戳分配器的角色

TiDB不需要像MySQL一样通过支持XA来满足跨数据库事务,TiDO的本身的分布式事务模型无论是在性能上还是在稳定性上都要比 XA 要高出很多,所以不会也不需要支持 XA。

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

  • 纯分布式架构,拥有良好的扩展性,支持弹性的扩缩容
  • 支持 SQL,对外暴露 MySQL 的网络协议,并兼容大多数 MySQL 的语法,在大多数场景下可以直接替换 MySQL
  • 默认支持高可用,在少数副本失效的情况下,数据库本身能够自动进行数据修复和故障转移,对业务透明
  • 支持 ACID 事务,对于一些有强一致需求的场景友好,例如:银行转账
  • 具有丰富的工具链生态,覆盖数据迁移、同步、备份等多种场景

简单来说,TiDB 适合具备下面这些特点的场景

  • 数据量大,单机保存不下
  • 不希望做 Sharding 或者懒得做 Sharding
  • 访问模式上没有明显的热点
  • 需要事务、需要强一致、需要灾备
  • 希望 Real-Time HTAP,减少存储链路

五大核心特性

  • 一键水平扩容或者缩容

    得益于 TiDB 存储计算分离的架构的设计,可按需对计算、存储分别进行在线扩容或者缩容,扩容或者缩容过程中对应用运维人员透明。

  • 金融级高可用

    数据采用多副本存储,数据副本通过 Multi-Raft 协议同步事务日志,多数派写入成功事务才能提交,确保数据强一致性且少数副本发生故障时不影响数据的可用性。可按需配置副本地理位置、副本数量等策略满足不同容灾级别的要求。

  • 实时 HTAP

    提供行存储引擎 TiKV、列存储引擎 TiFlash 两款存储引擎,TiFlash 通过 Multi-Raft Learner 协议实时从 TiKV 复制数据,确保行存储引擎 TiKV 和列存储引擎 TiFlash 之间的数据强一致。TiKV、TiFlash 可按需部署在不同的机器,解决 HTAP 资源隔离的问题。

  • 云原生的分布式数据库

    专为云而设计的分布式数据库,通过 TiDB Operator 可在公有云、私有云、混合云中实现部署工具化、自动化。

  • 兼容 MySQL 5.7 协议和 MySQL 生态

    兼容 MySQL 5.7 协议、MySQL 常用的功能、MySQL 生态,应用无需或者修改少量代码即可从 MySQL 迁移到 TiDB。提供丰富的数据迁移工具帮助应用便捷完成数据迁移。

四大核心应用场景

  • 对数据一致性及高可靠、系统高可用、可扩展性、容灾要求较高的金融行业属性的场景

    众所周知,金融行业对数据一致性及高可靠、系统高可用、可扩展性、容灾要求较高。传统的解决方案是同城两个机房提供服务、异地一个机房提供数据容灾能力但不提供服务,此解决方案存在以下缺点:资源利用率低、维护成本高、RTO (Recovery Time Objective)RPO (Recovery Point Objective) 无法真实达到企业所期望的值。TiDB 采用多副本 + Multi-Raft 协议的方式将数据调度到不同的机房、机架、机器,当部分机器出现故障时系统可自动进行切换,确保系统的 RTO <= 30s 及 RPO = 0。

  • 对存储容量、可扩展性、并发要求较高的海量数据及高并发的 OLTP 场景

    随着业务的高速发展,数据呈现爆炸性的增长,传统的单机数据库无法满足因数据爆炸性的增长对数据库的容量要求,可行方案是采用分库分表的中间件产品或者 NewSQL 数据库替代、采用高端的存储设备等,其中性价比最大的是 NewSQL 数据库,例如:TiDB。TiDB 采用计算、存储分离的架构,可对计算、存储分别进行扩容和缩容,计算最大支持 512 节点,每个节点最大支持 1000 并发,集群容量最大支持 PB 级别。

  • Real-time HTAP 场景

    随着 5G、物联网、人工智能的高速发展,企业所生产的数据会越来越多,其规模可能达到数百 TB 甚至 PB 级别,传统的解决方案是通过 OLTP 型数据库处理在线联机交易业务,通过 ETL 工具将数据同步到 OLAP 型数据库进行数据分析,这种处理方案存在存储成本高、实时性差等多方面的问题。TiDB 在 4.0 版本中引入列存储引擎 TiFlash 结合行存储引擎 TiKV 构建真正的 HTAP 数据库,在增加少量存储成本的情况下,可以在同一个系统中做联机交易处理、实时数据分析,极大地节省企业的成本。

  • 数据汇聚、二次加工处理的场景

    当前绝大部分企业的业务数据都分散在不同的系统中,没有一个统一的汇总,随着业务的发展,企业的决策层需要了解整个公司的业务状况以便及时做出决策,故需要将分散在各个系统的数据汇聚在同一个系统并进行二次加工处理生成 T+0 或 T+1 的报表。传统常见的解决方案是采用 ETL + Hadoop 来完成,但 Hadoop 体系太复杂,运维、存储成本太高无法满足用户的需求。与 Hadoop 相比,TiDB 就简单得多,业务通过 ETL 工具或者 TiDB 的同步工具将数据同步到 TiDB,在 TiDB 中可通过 SQL 直接生成报表。

二、快速上手

TiDB 是一个分布式系统。最基础的 TiDB 测试集群通常由 2 个 TiDB 实例、3 个 TiKV 实例、3 个 PD 实例和可选的 TiFlash 实例构成。通过 TiUP Playground,可以快速搭建出上述的一套基础测试集群,步骤如下:

  • step1、下载并安装 TiUP。

    curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh
    

安装完成后显示:

Successfully set mirror to https://tiup-mirrors.pingcap.com
Detected shell: bash
Shell profile:  /home/user/.bashrc
/home/user/.bashrc has been modified to add tiup to PATH
open a new terminal or source /home/user/.bashrc to use it
Installed path: /home/user/.tiup/bin/tiup
===============================================
Have a try:     tiup playground
===============================================
  • step2、声明全局环境变量。 source ${your_shell_profile}

    source /home/user/.bashrc
    
  • step3、在当前 session 执行以下命令启动集群。

    tiup playground
    
  • step4、验证。【现在可以像使用MySQL一样使用TiDB啦

    #新开启一个 session 以访问 TiDB 数据库。
    #使用 TiUP client 连接 TiDB:
    tiup client
    #也可使用 MySQL 客户端连接 TiDB
    mysql --host 127.0.0.1 --port 4000 -u root
    #通过 http://127.0.0.1:9090 访问 TiDB 的 Prometheus 管理界面。
    #通过 http://127.0.0.1:2379/dashboard 访问 TiDB Dashboard 页面,默认用户名为 root,密码为空。
    #通过 http://127.0.0.1:3000 访问 TiDB 的 Grafana 界面,默认用户名和密码都为 admin。
    

三、TiDB架构原理

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

  • TiDB整体架构图
    TiDB学习笔记【初阶】_第1张图片

  • TiDB整体架构图
    TiDB学习笔记【初阶】_第2张图片

1、TiDB数据库的存储——TiKV Server

TiDB存储模型,一个分布式带事务的 KV 引擎一个全局有序的分布式 Key-Value 引擎的分层结构以及如何实现多副本容错。

存储节点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 内部,数据是以列式的形式进行存储,主要的功能是为分析型的场景加速。

TiKV

保存数据需要保证:数据不丢、数据不错→Raft协议。除此之外,还需要考虑以下问题:

1、能否支持跨数据中心的容灾?
2、写入速度是否够快?
3、数据保存下来后,是否方便读取?
4、保存的数据如何修改?如何支持并发的修改?
5、如何原子地修改多条记录?

TiKV项目很好的解决了以上问题。那么如何实现 TiKV 这样一个高性能高可靠性的巨大的(分布式的) Map?

  • TiKV是一个巨大的 Map,也就是存储的是 Key-Value pair。
  • 这个 Map 中的 Key-Value pair 按照 Key 的二进制顺序有序,也就是我们可以 Seek 到某一个 Key 的位置,然后不断的调用 Next 方法以递增的顺序获取比这个 Key 大的 Key-Value。
Raft和RocksDB

TiKV 利用 Raft 来做数据复制,每个数据变更都会落地为一条 Raft 日志,通过 Raft 的日志复制功能,将数据安全可靠地同步到 Group 的多数节点中。

TiKV 没有选择直接向磁盘上写数据,而是把数据保存在 RocksDB 中,具体的数据落地由 RocksDB 负责。【RocksDB 是一个非常优秀的开源的单机存储引擎。】

TiDB学习笔记【初阶】_第3张图片

通过使用 Raft 一致性算法,数据在各 TiKV 节点间复制为多副本,以确保某个节点挂掉时数据的安全性。

实际上在底层,TiKV 使用复制日志 + 状态机 (State Machine) 的模型来复制数据。对于写入请求,数据被写入 Leader,然后 Leader 以日志的形式将命令复制到它的 Follower 中。当集群中的大多数节点收到此日志时,日志会被提交,状态机会相应作出变更。

Region概念

对于一个 KV 系统,将数据分散在多台机器上有两种比较典型的方案:一种是按照 Key 做 Hash,根据 Hash 值选择对应的存储节点;另一种是分 Range,某一段连续的 Key 都保存在一个存储节点上。为了支持范围查询,TiKV 选择了第二种方式,将整个 Key-Value 空间分成很多段,每一段是一系列连续的 Key,我们将每一段叫做一个 Region,并且我们会尽量保持每个 Region 中保存的数据不超过一定的大小(这个大小可以配置,目前默认是 96Mb)。每一个 Region 都可以用 StartKey 到 EndKey 这样一个左闭右开区间来描述。

TiDB学习笔记【初阶】_第4张图片

将数据划分成 Region 后,会做 两件重要的事情

  • 以 Region 为单位,将数据分散在集群中所有的节点上,并且尽量保证每个节点上服务的 Region 数量差不多。

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

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

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

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

TiDB学习笔记【初阶】_第5张图片

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

MVCC

如果两个 Client 同时去修改一个 Key 的 Value,如果没有 MVCC,就需要对数据上锁,在分布式场景下,可能会带来性能以及死锁问题。 TiKV 的 MVCC 实现是通过在 Key 后面添加 Version 来实现。

对于同一个 Key 的多个版本,把版本号较大的放在前面,版本号小的放在后面。这样当用户通过一个 Key + Version 来获取 Value 的时候,可以将 Key 和 Version 构造出 MVCC 的 Key,也就是 Key-Version。然后可以直接 Seek(Key-Version),定位到第一个大于等于这个 Key-Version 的位置。

#简单来说,没有 MVCC 之前,可以把 TiKV 看做这样的
Key1 -> Value
Key2 -> Value
……
KeyN -> Value
#有了 MVCC 之后,TiKV 的 Key 排列是这样的:
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
……
GC

TiDB 的事务的实现采用了 MVCC(多版本并发控制)机制,当新写入的数据覆盖旧的数据时,旧的数据不会被替换掉,而是与新写入的数据同时保留,并以时间戳来区分版本。Garbage Collection (GC) 的任务便是清理不再需要的旧数据。

  • GC整体流程

一个 TiDB 集群中会有一个 TiDB 实例被选举为 GC leader,GC 的运行由 GC leader 来控制。

GC 会被定期触发。每次 GC 时,首先,TiDB 会计算一个称为 safe point 的时间戳,接下来 TiDB 会在保证 safe point 之后的快照全部拥有正确数据的前提下,删除更早的过期数据。每一轮 GC 分为以下三个步骤

step1:“Resolve Locks” 【清理锁】阶段会对所有 Region 扫描 safe point 之前的锁,并清理这些锁。

step2:“Delete Ranges” 【删除区间】阶段快速地删除由于 DROP TABLE/DROP INDEX 等操作产生的整区间的废弃数据。

step3:“Do GC”【进行GC清理】阶段每个 TiKV 节点将会各自扫描该节点上的数据,并对每一个 key 删除其不再需要的旧版本。

默认配置下,GC 每 10 分钟触发一次,每次 GC 会保留最近 10 分钟内的数据(即默认 GC life time 为 10 分钟,safe point 的计算方式为当前时间减去 GC life time)。如果一轮 GC 运行时间太久,那么在一轮 GC 完成之前,即使到了下一次触发 GC 的时间也不会开始下一轮 GC。另外,为了使持续时间较长的事务能在超过 GC life time 之后仍然可以正常运行,safe point 不会超过正在执行中的事务的开始时间 (start_ts)。

2、TiDB数据库的计算——TiDB Server

从 SQL 的角度了解了数据是如何存储,以及如何用于计算。

TiDB 在 TiKV 提供的分布式存储能力基础上,构建了兼具优异的交易处理能力与良好的数据分析能力的计算引擎。

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

SQL映射KV

可以将关系模型简单理解为 Table 和 SQL 语句,那么问题变为如何在 KV 结构上保存 Table 以及如何在 KV 结构上运行 SQL 语句。 SQL 和 KV 结构之间存在巨大的区别,那么如何能够方便高效地进行映射,就成为一个很重要的问题。一个好的映射方案必须有利于对数据操作的需求。

TiDB学习笔记【初阶】_第6张图片

分布式SQL运算

首先我们需要将计算尽量靠近存储节点,以避免大量的 RPC 调用。其次,我们需要将 Filter 也下推到存储节点进行计算,这样只需要返回有效的行,避免无意义的网络传输。最后,我们可以将聚合函数、GroupBy 也下推【计算下推】到存储节点,进行预聚合,每个节点只需要返回一个 Count 值即可,再由 tidb-server 将 Count 值 Sum 起来【并行算子】。 这里有一个数据逐层返回的示意图:

TiDB学习笔记【初阶】_第7张图片

实际上 TiDB 的 SQL 层要复杂的多,模块以及层次非常多,下面这个图【SQL引擎架构】列出了重要的模块以及调用关系:

TiDB学习笔记【初阶】_第8张图片

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

SQL执行流程

在 TiDB 中,从输入的查询文本到最终的执行计划执行结果的过程可以见下图:
TiDB学习笔记【初阶】_第9张图片
首先经过 parser 对原始查询文本的解析以及一些简单的合法性验证后,TiDB 首先会对查询做一些逻辑上的等价变化——查询逻辑优化
通过这些等价变化,使得这个查询在逻辑执行计划上可以变得更易于处理。在等价变化结束之后,TiDB 会得到一个与原始查询等价的查询计划结构,之后根据数据分布、以及一个算子具体的执行开销,来获得一个最终的执行计划——查询物理优化
同时,TiDB 在执行 PREPARE 语句时,可以选择开启缓存来降低 TiDB 生成执行计划的开销——执行计划缓存

3、TiDB数据库的调度——PD Server

PD (Placement Driver) 是 TiDB 集群的管理模块,同时也负责集群数据的实时调度。

调度场景

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

调度需求

第一类:作为一个分布式高可用存储系统,必须满足的需求,包括几种:【 容灾功能 】

  • 副本数量不能多也不能少。
  • 副本需要根据拓扑结构分布在不同属性的机器上。
  • 节点宕机或异常能够自动合理快速地进行容灾。

第二类:作为一个良好的分布式系统,需要考虑的地方包括:【资源利用率更高且合理,具备良好的扩展性】

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

为了满足这些需求,需要收集足够的信息,比如每个节点的状态、每个 Raft Group 的信息、业务访问操作的统计等;其次需要设置一些策略,PD 根据这些信息以及调度的策略,制定出尽量满足前面所述需求的调度计划。

调度操作

调度的基本操作指的是为了满足调度的策略。上述调度需求可整理为以下三个操作:

  • 增加一个副本
  • 删除一个副本
  • 将 Leader 角色在一个 Raft Group 的不同副本之间 transfer(迁移)

刚好 Raft 协议通过 AddReplicaRemoveReplicaTransferLeader 这三个命令,可以支撑上述三种基本操作。

TiKV Store 的状态具体分为 Up,Disconnect,Offline,Down,Tombstone。各状态的关系如下:

TiDB学习笔记【初阶】_第10张图片

调度策略
  • 一个 Region 的副本数量正确。
  • 一个 Raft Group 中的多个副本不在同一个位置。
  • 副本在 Store 之间的分布均匀分配。
  • Leader 数量在 Store 之间均匀分配。
  • 访问热点数量在 Store 之间均匀分配。
  • 各个 Store 的存储空间占用大致相等。
  • 控制调度速度,避免影响在线服务。
调度实现

PD 不断的通过 Store 【即TiKV节点】或者 Leader 的心跳包收集信息,获得整个集群的详细数据,并且根据这些信息以及调度策略生成调度操作序列,每次收到 Region Leader 发来的心跳包时,PD 都会检查是否有对这个 Region 待进行的操作,通过心跳包的回复消息,将需要进行的操作返回给 Region Leader,并在后面的心跳包中监测执行结果。注意这里的操作只是给 Region Leader 的建议,并不保证一定能得到执行,具体是否会执行以及什么时候执行,由 Region Leader 自己根据当前自身状态来定。

五、TiDB最佳实践

TiDB 的最佳实践与其实现原理密切相关,建议先了解一些基本的实现机制,包括 Raft、分布式事务、数据分片、负载均衡、SQL 到 KV 的映射方案、二级索引的实现方法、分布式执行引擎。

Raft

Raft 是一种一致性协议,能提供强一致的数据复制保证,TiDB 最底层用 Raft 来同步数据。每次写入都要写入多数副本,才能对外返回成功,这样即使丢掉少数副本,也能保证系统中还有最新的数据。比如最大 3 副本的话,每次写入 2 副本才算成功,任何时候,只丢失一个副本的情况下,存活的两个副本中至少有一个具有最新的数据。

相比 Master-Slave 方式的同步,同样是保存三副本,Raft 的方式更为高效,因为写入的延迟取决于最快的两个副本,而不是最慢的那个副本。所以使用 Raft 同步的情况下,异地多活成为可能。在典型的两地三中心场景下,每次写入只需要本数据中心以及离得近的一个数据中心写入成功就能保证数据的一致性,而并不需要三个数据中心都写成功。

分布式事务

TiDB 提供完整的分布式事务,事务模型是在 Google Percolator 的基础上做了一些优化。

  • 乐观锁

    TiDB 的乐观事务模型,只有在真正提交的时候,才会做冲突检测。如果有冲突,则需要重试。这种模型在冲突严重的场景下,会比较低效,因为重试之前的操作都是无效的,需要重复做。举一个比较极端的例子,就是把数据库当做计数器用,如果访问的并发度比较高,那么一定会有严重的冲突,导致大量的重试甚至是超时。但是如果访问冲突并不十分严重,那么乐观锁模型具备较高的效率。在冲突严重的场景下,推荐使用悲观锁,或在系统架构层面解决问题,比如将计数器放在 Redis 中。

  • 悲观锁

    TiDB 的悲观事务模式,悲观事务的行为和 MySQL 基本一致,在执行阶段就会上锁,先到先得,避免冲突情况下的重试,可以保证有较多冲突的事务的成功率。悲观锁同时解决了希望通过 select for update 对数据提前锁定的场景。但如果业务场景本身冲突较少,乐观锁的性能会更有优势。

  • 事务大小限制

    由于分布式事务要做两阶段提交,并且底层还需要做 Raft 复制,如果一个事务非常大,会使得提交过程非常慢,并且会卡住下面的 Raft 复制流程。为了避免系统出现被卡住的情况,我们对事务的大小做了限制【单个事务包含的 SQL 语句不超过 5000 条(默认)】。

数据分片

TiKV 自动将底层数据按照 Key 的 Range 进行分片。每个 Region 是一个 Key 的范围,从 StartKeyEndKey 的左闭右开区间。Region 中的 Key-Value 总量超过一定值,就会自动分裂。这部分对用户透明。

负载均衡

PD 会根据整个 TiKV 集群的状态,对集群的负载进行调度。调度是以 Region 为单位,以 PD 配置的策略为调度逻辑,自动完成。

SQL on KV

TiDB 自动将 SQL 结构映射为 KV 结构。简单来说,TiDB 执行了以下操作:

  • 一行数据映射为一个 KV,Key 以 TableID 构造前缀,以行 ID 为后缀
  • 一条索引映射为一个 KV,Key 以 TableID+IndexID 构造前缀,以索引值构造后缀

可以看到,对于一个表中的数据或者索引,会具有相同的前缀,这样在 TiKV 的 Key 空间内,这些 Key-Value 会在相邻的位置。那么当写入量很大,并且集中在一个表上面时,就会造成写入的热点,特别是连续写入的数据中某些索引值也是连续的(比如 update time 这种按时间递增的字段),会在很少的几个 Region 上形成写入热点,成为整个系统的瓶颈。同样,如果所有的数据读取操作也都集中在很小的一个范围内(比如在连续的几万或者十几万行数据上),那么可能造成数据的访问热点。

二级索引

TiDB 支持完整的二级索引,并且是全局索引,很多查询可以通过索引来优化。如果利用好二级索引,对业务非常重要,很多 MySQL 上的经验在 TiDB 这里依然适用,不过 TiDB 还有一些自己的特点,需要注意,这一节主要讨论在 TiDB 上使用二级索引的一些注意事项。

  • 二级索引不是越多越好。

  • 对区分度【基数】比较大的列建立索引比较合适。有多个查询条件时,可以选择组合索引,注意最左前缀原则。

  • 通过索引查询和直接扫描 Table 的区别。

  • 查询并发度。

    数据分散在很多 Region 上,所以 TiDB 在做查询的时候会并发进行,默认的并发度比较保守,因为过高的并发度会消耗大量的系统资源。

    对于 OLTP 类型的查询,往往不会涉及到大量的数据,较低的并发度已经可以满足需求。
    对于 OLAP 类型的 Query,往往需要较高的并发度。

    所以 TiDB 支持通过 System Variable 来调整查询并发度。【tidb_distsql_scan_concurrency、tidb_index_lookup_size、tidb_index_lookup_concurrency、tidb_index_serial_scan_concurrency等等】

  • 通过索引保证结果顺序。【索引除了可以用来过滤数据之外,还能用来对数据排序,首先按照索引的顺序获取行 ID,然后再按照行 ID 的返回顺序返回行的内容,这样可以保证返回结果按照索引列有序。】

  • 也支持逆序索引。【目前速度比顺序 Scan 慢一些,通常情况下慢 20%,在数据频繁修改造成版本较多的情况下,会慢的更多。如果可能,建议避免对索引的逆序 Scan】

你可能感兴趣的:(GO,数据库,中间件,TiDB,分布式数据库)