作者:周昱行

在整个 SQL 执行过程中,需要经过 Parser,Optimizer,Executor,DistSQL 这几个主要的步骤,最终数据的读写是通过 tikv-client 与 TiKV 集群通讯来完成的。

为了完成数据读写的任务,tikv-client 需要解决以下几个具体问题:

  1. 如何定位到某一个 key 或 key range 所在的 TiKV 地址?
  2. 如何建立和维护和 tikv-server 之间的连接?
  3. 如何发送 RPC 请求?
  4. 如何处理各种错误?
  5. 如何实现分布式读取多个 TiKV 节点的数据?
  6. 如何实现 2PC 事务?

我们接下来就对以上几个问题逐一解答,其中 5、6 会在下篇中介绍。

如何定位 key 所在的 tikv-server

我们需要回顾一下之前 《三篇文章了解 TiDB 技术内幕——说存储》 这篇文章中介绍过的一个重要的概念:Region。

TiDB 的数据分布是以 Region 为单位的,一个 Region 包含了一个范围内的数据,通常是 96MB 的大小,Region 的 meta 信息包含了 StartKey 和 EndKey 这两个属性。当某个 key >= StartKey && key < EndKey 的时候,我们就知道了这个 key 所在的 Region,然后我们就可以通过查找该 Region 所在的 TiKV 地址,去这个地址读取这个 key 的数据。

获取 key 所在的 Region, 是通过向 PD 发送请求完成的。PD client 实现了这样一个接口:

[GetRegion(ctx context.Context, key []byte) (metapb.Region, metapb.Peer, error)](https://github.com/pingcap/ti...

通过调用这个接口,我们就可以定位这个 key 所在的 Region 了。

如果需要获取一个范围内的多个 Region,我们会从这个范围的 StartKey 开始,多次调用 GetRegion 这个接口,每次返回的 Region 的 EndKey 做为下次请求的 StartKey,直到返回的 Region 的 EndKey 大于请求范围的 EndKey。

以上执行过程有一个很明显的问题,就是我们每次读取数据的时候,都需要先去访问 PD,这样会给 PD 带来巨大压力,同时影响请求的性能。

为了解决这个问题,tikv-client 实现了一个 RegionCache 的组件,缓存 Region 信息, 当需要定位 key 所在的 Region 的时候,如果 RegionCache 命中,就不需要访问 PD 了。RegionCache 的内部,有两种数据结构保存 Region 信息,一个是 map,另一个是 b-tree,用 map 可以快速根据 region ID 查找到 Region,用 b-tree 可以根据一个 key 找到包含该 key 的 Region。

严格来说,PD 上保存的 Region 信息,也是一层 cache,真正最新的 Region 信息是存储在 tikv-server 上的,每个 tikv-server 会自己决定什么时候进行 Region 分裂,在 Region 变化的时候,把信息上报给 PD,PD 用上报上来的 Region 信息,满足 tidb-server 的查询需求。

当我们从 cache 获取了 Region 信息,并发送请求以后, tikv-server 会对 Region 信息进行校验,确保请求的 Region 信息是正确的。

如果因为 Region 分裂,Region 迁移导致了 Region 信息变化,请求的 Region 信息就会过期,这时 tikv-server 就会返回 Region 错误。遇到了 Region 错误,我们就需要清理 RegionCache,重新获取最新的 Region 信息,并重新发送请求。

如何建立和维护和 tikv-server 之间的连接

当 TiDB 定位到 key 所在的 tikv-server 以后,就需要建立和 TiKV 之间的连接,我们都知道, TCP 连接的建立和关闭有不小的开销,同时会增大延迟,使用连接池可以节省这部分开销,TiDB 和 tikv-server 之间也维护了一个连接池 connArray。

TiDB 和 TiKV 之间通过 gRPC 通信,而 gPRC 支持在单 TCP 连接上多路复用,所以多个并发的请求可以在单个连接上执行而不会相互阻塞。

理论上一个 tidb-server 和一个 tikv-server 之间只需要维护一个连接,但是在性能测试的时候发现,单个连接在并发-高的时候,会成为性能瓶颈,所以实际实现的时候,tidb-server 对每一个 tikv-server 地址维护了多个连接,并以 round-robin 算法选择连接发送请求。连接的个数可以在 config 文件里配置,默认是 16。

如何发送 RPC 请求

tikv-client 通过 tikvStore 这个类型,实现 kv.Storage 这个接口,我们可以把 tikvStore 理解成 tikv-client 的一个包装。外部调用 kv.Storage 的接口,并不需要关心 RPC 的细节,RPC 请求都是 tikvStore 为了实现 kv.Storage 接口而发起的。

实现不同的 kv.Storage 接口需要发送不同的 RPC 请求。比如实现 Snapshot.BatchGet 需要tikvpb.TikvClient.KvBatchGet 方法;实现 Transaction.Commit,需要 tikvpb.TikvClient.KvPrewrite, tikvpb.TikvClient.KvCommit 等多个方法。

在 tikvStore 的实现里,并没有直接调用 RPC 方法,而是通过一个 Client 接口调用,做这一层的抽象的主要目的是为了让下层可以有不同的实现。比如用来测试的 mocktikv 就自己实现了 Client 接口,通过本地调用实现,并不需要调用真正的 RPC。

rpcClient 是真正实现 RPC 请求的 Client 实现,通过调用 tikvrpc.CallRPC,发送 RPC 请求。tikvrpc.CallRPC 再往下层走,就是调用具体每个 RPC  生成的代码了,到了生成的代码这一层,就已经是 gRPC 框架这一层的内容了,我们就不继续深入解析了,感兴趣的同学可以研究一下 gRPC 的实现。

如何处理各种错误

我们前面提到 RPC 请求都是通过 Client 接口发送的,但实际上这个接口并没有直接被各个 tikvStore 的各个方法调用,而是通过一个 RegionRequestSender 的对象调用的。

RegionRequestSender 主要的工作除了发送 RPC 请求,还要负责处理各种可以重试的错误,比如网络错误和部分 Region 错误。

RPC 请求遇到的错误主要分为两大类:Region 错误和网络错误。

Region  错误 是由 tikv-server 收到请求后,在 response 里返回的,常见的有以下几种:

  1. NotLeader

    这种错误的原因通常是 Region 的调度,PD 为了负载均衡,可能会把一个热点 Region 的 leader 调度到空闲的 tikv-server 上,而请求只能由 leader 来处理。遇到这种错误就需要 tikv-client 重试,把请求发给新的 leader。

  2. StaleEpoch

    这种错误主要是因为 Region 的分裂,当 Region 内的数据量增多以后,会分裂成多个新的 Region。新的 Region 包含的 range 是不同的,如果直接执行,返回的结果有可能是错误的,所以 TiKV 就会拒绝这个请求。tikv-client 需要从 PD 获取最新的 Region 信息并重试。

  3. ServerIsBusy

    这个错误通常是因为 tikv-server 积压了过多的请求处理不完,tikv-server 如果不拒绝这个请求,队列会越来越长,可能等到客户端超时了,请求还没有来的及处理。所以做为一种保护机制,tikv-server 提前返回错误,让客户端等待一段时间后再重试。

另一类错误是网络错误,错误是由 SendRequest 的返回值 返回的 error 的,遇到这种错误通常意味着这个 tikv-server 没有正常返回请求,可能是网络隔离或 tikv-server down 了。tikv-client 遇到这种错误,会调用 OnSendFail 方法,处理这个错误,会在 RegionCache 里把这个请求失败的 tikv-server 上的所有 region 都 drop 掉,避免其他请求遇到同样的错误。

当遇到可以重试的错误的时候,我们需要等待一段时间后重试,我们需要保证每次重试等待时间不能太短也不能太长,太短会造成多次无谓的请求,增加系统压力和开销,太长会增加请求的延迟。我们用指数退避的算法来计算每一次重试前的等待时间,这部分的逻辑是在 Backoffer 里实现的。

在上层执行一个 SQL 语句的时候,在 tikv-client 这一层会触发多个顺序的或并发的请求,发向多个 tikv-server,为了保证上层 SQL 语句的超时时间,我们需要考虑的不仅仅是单个 RPC 请求,还需要考虑一个 query 整体的超时时间。

为了解决这个问题,Backoffer 实现了 fork 功能, 在发送每一个子请求的时候,需要 fork 出一个 child Backofferchild Backoffer 负责单个 RPC 请求的重试,它记录了 parent Backoffer 已经等待的时间,保证总的等待时间,不会超过 query 超时时间。

对于不同错误,需要等待的时间是不一样的,每个 Backoffer 在创建时,会根据不同类型,创建不同的 backoff 函数。

以上就是 tikv-client 上篇的内容,我们在下篇会详细介绍实现分布式计算相关的 copIterator 和实现分布式事务的 twoPCCommiter。

TiDB 源码阅读系列文章(十八)tikv-client(上)相关推荐

  1. TiDB 源码阅读系列文章(十五)Sort Merge Join

    2019独角兽企业重金招聘Python工程师标准>>> 什么是 Sort Merge Join 在开始阅读源码之前, 我们来看看什么是 Sort Merge Join (SMJ),定 ...

  2. TiDB 源码阅读系列文章(十六)INSERT 语句详解

    在之前的一篇文章 <TiDB 源码阅读系列文章(四)INSERT 语句概览> 中,我们已经介绍了 INSERT 语句的大体流程.为什么需要为 INSERT 单独再写一篇?因为在 TiDB ...

  3. TiDB 源码阅读系列文章(十九)tikv-client(下)

    上篇文章 中,我们介绍了数据读写过程中 tikv-client 需要解决的几个具体问题,本文将继续介绍 tikv-client 里的两个主要的模块--负责处理分布式计算的 copIterator 和执 ...

  4. TiDB 源码阅读系列文章(六)Select 语句概览

    在先前的 TiDB 源码阅读系列文章(四) 中,我们介绍了 Insert 语句,想必大家已经了解了 TiDB 是如何写入数据,本篇文章介绍一下 Select 语句是如何执行.相比 Insert,Sel ...

  5. TiDB 源码阅读系列文章(六)Select 语句概览 1

    在先前的 TiDB 源码阅读系列文章(四) 中,我们介绍了 Insert 语句,想必大家已经了解了 TiDB 是如何写入数据,本篇文章介绍一下 Select 语句是如何执行.相比 Insert,Sel ...

  6. TiDB 源码阅读系列文章(五)TiDB SQL Parser 的实现

    本文为 TiDB 源码阅读系列文章的第五篇,主要对 SQL Parser 功能的实现进行了讲解,内容来自社区小伙伴--马震(GitHub ID:mz1999 )的投稿. TiDB 源码阅读系列文章的撰 ...

  7. TiDB 源码阅读系列文章(十八)tikv-client(上) 1

    2019独角兽企业重金招聘Python工程师标准>>> 作者:周昱行 在整个 SQL 执行过程中,需要经过 Parser,Optimizer,Executor,DistSQL 这几个 ...

  8. TiDB 源码阅读系列文章(二十二)Hash Aggregation

    2019独角兽企业重金招聘Python工程师标准>>> 作者:徐怀宇 聚合算法执行原理 在 SQL 中,聚合操作对一组值执行计算,并返回单个值.TiDB 实现了 2 种聚合算法:Ha ...

  9. DM 源码阅读系列文章(二)整体架构介绍

    2019独角兽企业重金招聘Python工程师标准>>> 作者:张学程 本文为 DM 源码阅读系列文章的第二篇,第一篇文章 简单介绍了 DM 源码阅读的目的和规划,以及 DM 的源码结 ...

最新文章

  1. c#正则表达式使用详解
  2. 工作流引擎设计之退回任务定义
  3. BAT常爱问的面试智力题,你能答对几道?
  4. python中赋值不正确的_python中关于赋值、浅拷贝与深拷贝的问题
  5. 解决python多版本环境下pip报错Fatal error in launcher: Unable to create process using问题
  6. 1090 Highest Price in Supply Chain (25)(25 分)
  7. 做系统ghost步骤图解_用好这工具,小孩都能会重装系统!
  8. 基于nginx实现缓存功能及uptream模块详细使用方法
  9. axis1 c# 接口 调用_java调用c#的Webservice接口数据报错
  10. Java并发线程之线程池
  11. matlab中的count函数,Excel 中COUNT函数的使用详解,详情介绍
  12. 横河涡街流量计安装参数说明及要求
  13. 基于SSH框架的管理系统【完整项目源码】
  14. java clh_【死磕Java并发】-J.U.C之AQS:CLH同步队列 - Java 技术驿站-Java 技术驿站
  15. 《游戏编程》第一部 基础篇
  16. 计算机网络工程的话术,话术工具电脑版
  17. 511遇见安卓手机投屏支持一键转无线群控
  18. 九度 1420 Jobdu MM分水果 -- 动态规划、深度优先搜索
  19. 4.4OC10-内存管理2-set方法的内存管理
  20. python面试题常考的超市购物车系统

热门文章

  1. 关于网易云音乐爬虫的api接口?
  2. 京东网络开放之路——自研交换机探索与实践
  3. windows下使用cmd查看连接过的wifi密码
  4. MAC安装Win10出现”在efi系统上,windows只能安装到gpt分区”问题的解决
  5. 目录/文件攻击防范策略研究
  6. STL六大组件:分配器、容器、迭代器、算法、仿函数、适配器
  7. vista系统 金山词霸 不取词翻译
  8. Discuz! 6.0.0 安装图文教程
  9. 数字签名、数字证书、对称加密算法、非对称加密算法、单向加密(散列算法)...
  10. spark 入门及集群环境搭建