php网站建设培训班,ios个人开发者账号,网站建设费用包括,做网站的目的是啥作者: 申砾 本文档面向 TiDB 社区开发者#xff0c;主要介绍 TiDB 的系统架构、代码结构以及执行流程。 目的是使得开发者阅读文档后#xff0c;可以对 TiDB 项目有一个整体的了解#xff0c;更好的参与进来。首先会介绍一下大体的结构以及 Golang 包的结构#xff0c;然后… 作者: 申砾 本文档面向 TiDB 社区开发者主要介绍 TiDB 的系统架构、代码结构以及执行流程。 目的是使得开发者阅读文档后可以对 TiDB 项目有一个整体的了解更好的参与进来。首先会介绍一下大体的结构以及 Golang 包的结构然后会介绍内部的执行流程最后会对优化器、执行器这两个最重要的组件做一些说明。 系统架构 TiDB Server 在整个系统中位于 Load Balancer(或者是 Application) 与底层的存储引擎之间主要部分分为三层 MySQL Protocol 层 接收 MySQL Client 的请求解析 MySQL Protocol 的包并转换为 TiDB Session 中的各种命令处理完成后将结果结果为 MySQL Protocol 格式返回给 Client。 SQL 层 解析并执行 SQL 语句制定查询计划并优化生成执行器并通过 KV 层读取或写入数据最后返回结果给 MySQL Protocol层。这一层是重点后面会详细介绍。 KV 层 提供带事务的分布式/单机存储在 KV 层和 SQL 层之间有一层抽象使得 SQL 层能够忽略下面不同的 KV 存储的差异看到统一的接口。 代码结构概述 这里首先会把所有的 package 列出来然后介绍其主要功能。这一章会比较散信息量比较大可以结合下一章节一起理解。 tidb 这个包可以认为是 MySQL Protocol Layer 和 SQL Layer 之间的接口主要的文件有三个 session.go: 每一个 session 对象对应一个 MySQL Client 的 connectionMySQL Protocol 层负责管理 connection 与 Session 之间的绑定关系并且各种 MySQL 查询/命令都是调用 Session 的接口来执行。 tidb.go: 一些函数供 session.go 调用 bootstrap.go: 当 TiDB Server 启动后如果发现系统未经初始化会执行初始化流程详细信息会在下面的章节中介绍。 docs 一些简略的文档更详细的文档参见 中文文档 以及英文文档 executor TiDB 执行器SQL 语句最终会转化为一系列执行器物理算子的组合。这个包对外暴露的最主要的接口是 Executor: type Executor interface {// 返回下一行数据如果返回为空则表明没有更多数据
Next() (*Row, error)
// 关闭当前执行器做一些清理工作Close() error// 改执行器返回结果的 Schema包括每个 Field 的详细信息Schema() expression.Schema
} 各种执行器都会实现这个接口TiDB 的执行引擎采用 Volcano 模型执行器之间通过上述三个接口交互每一个执行器只需要通过 Next 接口从其他执行器获取数据以及通过 Schema 接口获取数据的元信息。 plan 这里是整个 SQL 层的核心SQL 语句解析成 AST 之后在这个包中制定出查询计划并对查询计划进行优化包括逻辑优化和物理优化。 这个包中还包括下面几个功能 validator.go对 AST 进行合法性验证 preprocess.go 目前只有 name resolve 这一项 resolver.go名称解析将 SQL 语句中的标识符database/table/column/alias解析并绑定到对应的 column 或者是 Field 上。 typeinferer.go推导结果类型。对于 SQL 语句不需要执行即可推导出结果的类型。 logical_plan_builder.go: 制定出优化的逻辑查询计划 physical_plan_builder.go: 根据逻辑查询计划制定出物理查询计划 privilege 权限控制相关接口具体实现在 privilege/privileges 包中 sessionctx 存放 session 中的状态信息比如 session variable 信息这些信息可以在 session 中获取放在单独的包中主要是理清依赖关系避免循环依赖问题。 table table接口对数据库中的表做了一层抽象提供很多对表的操作如获取表的 column 信息、读取一行数据等具体实现在 table/tables 中。另外这里还有对于 Column 以及 Index 的抽象。 tidb-server tidb-server 程序的 main.go主要是启动为 server 的代码。 server MySQL Procotol 层的实现主要工作是解析协议、传递命令/Query。 ast 的定义一个 visitor 然后调用树中节点的的 Accept 方法对树进行遍历。SQL 语句会解析为一颗抽象语法树AST树中的节点定义都在这个包下。每个节点都实现了 visitor 接口可以很简单 ddl 异步 Schema 变更相关代码类似 Google F1 的论文实现。算法详解。 domain domain 可以认为是一个存储空间可以在其中创建数据库、创建表不同的 domain 之间可以存在相同名称的数据库有点像 Name Space。domain 上会绑定 information schema 信息。 expression 表达式的定义最重要的接口是 : type Expression interface {
.....
} 目前实现这个接口的表达式包括 Scalar Function标量函数表达式Aggregate Function聚合函数表达式Column列表达式Const常量表达式evaluator 表达式求值相关逻辑所有的表达式求值方法都在这里。 infoschema InformationSchema 的实现提供 db/table/column 相关信息。 kv KV 相关的接口定义和部分实现包括 Retriever / Mutator / Transaction / Snapshot / Storage / Iterator 等。对下层的多种KV存储做了一个统一的抽象。 model TiDB 支持的 ddl / dml 相关的数据结构包括 DBInfo / TableInfo / ColumnInfo / IndexInfo 等 parser 语法解析模块主要包括词法解析 (lexer.go) 和语法解析 (parser.y)这个包对外的主要接口是 Parse()用于将 SQL 文本解析成 AST。 store 底层 KV 存储的实现如果要接入新的存储引擎可以将其进行包装代码放在这个包下面目前接入两个引擎分布式引擎tikv和单机引擎localstore/{goleveldb/boltdb}。这里的新引擎需要实现 kv 包中定义的接口。 关于kv和store可以参考 TiDB 存储引擎接入指南 terror 定义了 TiDB 的 error 体系。 context context接口session 是 context 接口的一个实现这里抽象出接口来主要是避免循环依赖。session 各种各种状态信息都通过这个接口存取。 inspectkv TiDB SQL Data 和 KV 辅助 check 包以后会用于外部对于 TiDB KV 的访问将被重新定义和扩展 meta TiDB 的 meta 数据相关常量定义以及常用函数定义。meta/autoid 定义了一个用于生成全局唯一session内自增 ID 的APImeta 信息依赖于这个工具。 mysql MySQL 相关的常量定义。 structure 在 key-value上做了一层封装支持更丰富的支持 Transaction 的 KV 类型包括 string / list / hash 等。主要在异步 Schema 变更中使用。 util 一些工具类这里有一个包比较重要就是 types 包里面有很多和类型的定义以及对各种类型对象的操作。 distsql 分布式 SQL 执行接口如果下层的存储引擎支持分布式执行器可以通过这个接口发送请求后面会详细介绍这个模块。 Protocol 层 协议层是和应用交互的接口目前 TiDB 只支持 MySQL 协议相关的代码都在 server 包中。这一层的主要功能是管理客户端 connection解析 MySQL 命令并返回执行结果。这一层是按照 MySQL 协议实现具体的协议可以参考 14 MySQL Client/Server Protocol 单个 connection 处理命令的入口方法是 clientConn 类的 dispatch 方法这里会解析协议并调用不同的处理函数。 SQL 层 经过上面章节读者已经了解了 TiDB 整体框架以及各个包的细节本章开始讲解核心章节对 TiDB 的 SQL 层如何 work 做一个简要的介绍。这里忽略具体的 KV 层只关注 SQL 层。希望通过本章读者可以了解一条 SQL 语句是如何从一段文本变成执行结果集。整个流程中的详细过程会在接下来的几个章节中说明。 大体上讲一条 SQL 语句需要经过语法解析--合法性验证--制定查询计划--优化查询计划--根据计划生成查询器--执行并返回结果 等一系列流程。TiDB 的执行流程以这个为基础流程图如下 整个流程的入口在 tidb 包的 session.go 中TiDB-Server 调用 Session.Execute() 接口输入为文本格式的 SQL 语句实现在 session.Execute()。 首先会调用 Compile() 对 SQL 语句进行语法解析tidb.Parse()解析后会得到一个 stmt 的列表这里每条语句都是一个 AST抽象语法树每一个语法单元都是数的一个 Node结构定义都在 ast 包中。 得到 AST 之后调用 executor 包中的 Compiler输入 AST得到执行器Compiler.Compile()。在这个过程中会完成合法性验证、制定计划以及优化查询计划。 进入 Compiler.Compile() 函数首先会调用 plan.Validate() 对语句进行合法性验证见 plan/validator.go然后进入 Preprocess 流程目前这个阶段Preprocess 只做了名称解析工作及将 SQL 语句中的提到的 column 名或者 alias name 绑定到对应的 field上。比如”select c from t;”这个语句会将 c 这个名字绑定到 t 这个表的对应列上具体的实现见 plan/resolver.go。之后就进入 optimizer.Optimize()。 Optimize() 方法中首先对 AST 中各个node的结果进行推导如 select 1, xx, c from t; 对于 select fields第一个 field是 1其类型为 Longlong第二个 field 是 xx , 其类型为 VarString第三个field是 c, 其类型为表 t 中 column c 的类型。注意这里除了类别之外还有 charset 等信息都要进行推导具体实现见 plan/typeinferer.go。完成类型推导后进行逻辑优化planBuilderbuild()主要工作是根据代数运算对 AST 进行等价变换化简 AST。比如 select c from t where c 11*2; 可以等价变换为 select c from t where c 3; 逻辑优化完成后进行物理优化生成查询计划树并利用索引、根据一些 rule 以及 cost 模型对树进行变换减少查询过程的代价入口在 plan/optimizer.go 中的 doOptimize()方法。 生成查询计划后会转换为执行器通过 Exec 接口执行得到 RecordSet对象对其调用 Next() 方法获取查询结果。 优化器 优化器是数据库的核心决定了每条语句如何执行。如果说数据库是一支军队那么优化器就是这支军队的主将、军师需要运筹帷幄决胜于千里之外。俗话说一将无能累死三军同样的一条语句选择不同的查询计划最终的运行时间可能会相差很大。对优化器的研究一直是学术界比较活跃的领域优化是永无止境可以说在这块投入多大的精力都不为过。 从优化方法上大致可以分为三类 Rule based optimizer通过启发式规则对 plan 进行优化Cost based optimizer通过计算查询代价对 plan 进行优化History based optimizer通过历史查询信息对 plan 进行优化基于规则的优化器比较容易实现只要选取一些常用的规则就可以对大多数常用的查询有较好的效果。但是其缺陷也比较明显无法根据数据的真实情况选择最优的方案。比如对于查询语句 “select * from t where c1 10 and c2 100” 在选择索引时如果只根据规则那么一定是选择 c1 上面的索引进行查询但是如果 t 中 c1 所有的值都是 10那么这个查询计划就很差。这个时候如果有表中数据分布的信息对选择好的查询计划很有帮助。 基于代价的优化器复杂一些其核心问题有两个一个是如何获取数据的真实分布信息另一个是如何根据这些信息估算出某一个查询计划所需的代价。 基于历史信息的查询优化器用的比较少一般 OLTP 数据库中不会涉及。 TiDB 的优化器相关代码在 plan 包中这个包的主要工作是将 AST 转换为查询计划树树中的节点是各种逻辑算子或者是物理算子对查询计划化的各种优化都是通过调用树根节点的各种方法递归地对所有节点进行优化并且会不断的对树中的节点进行转换和裁剪。 最重要的几个接口在 plan.go 中包括 Plan 所有查询计划的接口LogicalPlan逻辑查询计划所有的逻辑算子都需要实现这个接口PhysicalPlan物理查询计划所有的物理算子都需要实现这个接口逻辑优化的入口是 planbuilder.build()输入是 AST输出是逻辑查询计划树。然后在这棵树上进行逻辑查询优化 调用 LogicalPlan 的 PredicatePushDown 接口将谓词尽可能下推调用 LogicalPlan 的 PruneColumns 接口将不需要的列裁减掉调用 aggPushDownSolver.aggPushDown将聚合算子下推到 Join 之前拿到逻辑优化后的查询计划树之后会进行物理优化代码的入口是对逻辑查询计划树的根节点调用 convert2PhysicalPlan(requiredProperty{})其中 requiredProperty 是对下层返回结果顺序、行数的要求。 逻辑查询计划树从根节点开始不断的递归调用将每个节点从逻辑算子转成物理算子并且根据每个节点的查询代价找到一条比较好的查询路径。 执行器 虽然优化器是最核心的组件但是缺少优秀的执行器依旧无法构成一个优秀的数据库。同样以军队为例执行器就是军队中冲锋陷阵的士兵。再厉害的将军如果没有一群能征善战的士兵同样无法打胜仗。 相比 MySQLTiDB 的执行器有两个有点第一是整个计算框架是一个 MPP 的框架计算会在多台 TiKV 以及 TiDB 节点上进行尽可能提高效率和速度第二是单个算子会尽可能并行比如 Join/Union 等算子会启动多个线程同时计算整个数据计算流程构成一个 pipeline尽可能缩短每个算子的等待时间。所以 TiDB 在处理大量数据时比 MySQL 表现好。 执行器最重要的接口在 executor.go 中: // Executor executes a query.
type Executor interface {Next() (*Row, error)Close() errorSchema() expression.Schema
} 通过优化器得到的物理查询计划树会转换为一个执行器树树中的每个节点都会实现这个接口执行器之间通过 Next 接口传递数据。比如 select c1 from t where c2 10; 最终生成的执行器是 Projection-Filter-TableScan 这三个执行器最上层的 Projection 会不断的调用下层执器的 Next 接口最终调到底层的 TableScan从表中获取数据。 分布式执行器 TiDB 作为分布式数据库内置一个分布式的计算框架Query 在执行的时候会尽量分布式并行。这个框架的入口在 distsql 包中最重要的是下面两个接口 DistSQL 提供的对外最重要的一个接口是 Select()。第一个参数 kv.Client只要 KV 引擎满足带事务、满足 KV 接口并且满足这个 Client 的一些接口就可以接入 TiDB。目前有一些其他的厂商和 TiDB 合作开发在其他的 KV 上 run TiDB并且支持分布式的 SQL。第二个参数是 SelectRequest 。这个东西是由上层执行器构造出来的它把计算上的逻辑比如说一些表达式要不要排序、要不要做聚合所有的信息都放在 req 里边是一个 Protobuf 结构然后发给 Select 接口最终会发送到进行计算的 TiKV region server 上。 分布式执行器在 TiDB 端的主要工作是做任务的分发和结果的收集。Select 接口返回一个数据结构叫 SelectResult 这个结构可以认为是一个迭代器因为下层是有很多 region server每个节点返回的结果是一个 PartialResult。在这些部分结果之上封装了一个 SelectResult 就是一个 PartialResult 的迭代器。通过这个的 next 方法可以拿到下一个 PartialResult 。 SelectResult 的内部实现可以认为是个 pipeline。TiDB 会并发地向各个 region server 发请求并且按照预定的顺序返回结果给上层这里的顺序是由下层结果返回顺序以及 Select 接口的 KeepOrder 参数共同决定。_这部分相关的代码可以参看 distsql 包以及 store/tikv/coprocessor.go。 源码地址https://github.com/pingcap/tidb