本文由红凤凰粉凤凰粉红凤凰队的成员主笔,他们的项目 TiDB Lab 在本届 TiDB Hackathon 2018 中获得了二等奖。TiDB Lab 为 TiDB 培训体系增加了一个可以动态观测 TiDB / TiKV / PD 细节的动画教学 Lab,让用户可以一边进行真实操作一边观察组件之间的变化,例如 SQL 的解析,Region 的变更等等,从而生动地理解 TiDB 的工作原理。

项目简介

简介

TiDB Lab,全称 TiDB Laboratory,是一个集 TiDB 集群状态的在线实时可视化与交互式教学的平台。用户可以一边对 TiDB 集群各个组件 TiKV、TiDB、PD 进行各种操作,包括上下线、启动关闭、迁移数据、插入查询数据等,一边在 TiDB Lab 上以动画形式观察操作对集群的影响,例如数据是怎么流动的,Region 副本在什么情况下发生了变更等等。通过 TiDB Lab 这种对操作进行可视化反馈的交互模式,用户可以快速且生动地理解 TiDB 内部原理。

功能

  • 实时动态展示 TiDB、TiKV 节点的新增、启动与关闭。
  • 实时动态展示 TiDB 收到 SQL 后,物理算子将具体请求发送给某些 TiKV Region Leader 并获取数据的过程。
  • 实时动态展示各个 TiKV 实例上 Region 副本状态的变化,例如新增、删除、分裂。
  • 实时动态展示各个 TiKV 实例上 Region 副本内的数据量情况。
  • 浏览集群事件历史(事件指上述四条功能所展示的各项内容)并查看事件的详细情况,包括事件的具体数据内容、SQL Plan 等等。
  • 按 TiDB、TiKV 或 Region 过滤事件历史。
  • 对事件历史进行时间穿梭:回到任意事件发生时刻重新观察当时的集群状态,或按事件单步重放观察集群状态的变化。
  • 在线获取常用运维操作的操作指南。

愿景

我们其实为 TiDB Lab 规划了更大的愿景,但由于 Hackathon 时间关系,还来不及实现。我们希望能实现 TiDB Lab + TiDB 生态组件的沙盒,从而在 TiDB Lab 在线平台上直接提供命令执行与 SQL 执行功能。这样用户无需离开平台,无需自行准备机器下载部署,就可以直接在平台上根据提供的操作指南进行各类操作,并能观察操作带来的具体影响,形成操作与反馈的闭环,真正地实现零门槛浏览器在线教学。我们期望平台能提供用户以下的操作流程:

1. 用户获得一个 TiDB Lab 账户并登录(考虑到沙盒是占用实际资源的,需要通过账户许可来限制避免资源快速耗尽)。

2. 用户在 TiDB Lab 上获得若干虚拟机器的访问权限,每个机器处于同一内网并具有独立 IP。这些虚拟机器的实际实现是资源受限的虚拟机或沙盒,因为作为教学实验不需要占用很多资源。

例如:平台为用户自动分配了 5 个 IP 独立的沙盒的访问权限,地址为 192.168.0.1 ~ 192.168.0.5。

3. 用户在平台上进行第零章「架构原理」的学习。平台提供了一个默认的拓扑部署,用户可以在平台提供的在线 SQL Shell 中进行数据的插入、删除、更新等操作。用户通过平台观察到 SQL 是如何对应到 TiKV 存储节点上的,以及数据是怎么切分到不同 Region 的等等。

4. 用户在平台上进行第一章「TiDB 部署」的学习,了解到可以通过 ansible 进行部署。教学样例是一个典型的 TiDB + TiKV 三副本部署。对于这个教学样例,平台告知用户 inventory.ini 具体内容应当写成什么样子。用户可以在平台提供的在线 Terminal 上修改 inventory 文件,并执行部署与集群启动命令。部署和启动均能在平台上实时反馈可视化。

5. 用户继续进行后续章节学习,例如「TiDB 单一服务启动与关闭」。用户在可视化界面上点击某个刚才已经部署出来的节点,可以了解启动或关闭单个 TiDB 的命令。用户可以在平台提供的在线 Terminal 上执行这些命令,尝试启动或关闭单一 TiDB。

6. 用户继续学习基础运维操作,例如「TiKV 扩容」。平台告知 inventory.ini 应当如何进行修改,用户可以根据指南在在线 Terminal 上进行实践,并通过可视化界面观察扩容的过程,例如其他节点上的副本被逐渐搬迁到新节点上。

前期准备

团队

我们团队有三个人,是一个 PingCAP 同学与 TiDB 社区小伙伴钱同学的混合组队,其中 PingCAP 成员分别来自 TiKV 组与 OLAP 组。我们本着搞事情的想法,团队取名叫「红凤凰粉凤凰粉红凤凰」,想围观主持人念团队名称(然而机智的主持人小姐姐让我们自报团队名称)。

原计划:干掉 gRPC

鉴于从报名开始直到 Hackathon 正式开始前几小时,我们都在为原计划做准备,因此值得详细说一下…

我们一开始规划的 Hackathon 项目是换掉 TiKV、TiDB 之间的 RPC 框架 gRPC,原因有几个:

  • 一是发现 TiKV、TiDB 中 gRPC 经常占用了大量 CPU,尤其是在请求较多但很简单的 benchmark 场景中经常比 Coprocessor 这块儿还高,这在客户机器 CPU 资源比较少的情况下是性能瓶颈;
  • 二是发现 gRPC 性能一般,在各类常用 RPC 框架的性能测试中 gRPC 经常是垫底水平;
  • 三是 gRPC 主要设计用于用户产品与 Google 服务进行通讯,因而考虑到了包括负载均衡友好、流量控制等方面,但对 TiKV 与 TiDB 这类内部通讯来说这些都是用不上的功能,为此牺牲的性能是无谓的开销;
  • 另外近期 TiKV 内部有一个实验是将多个 RPC 请求 batch 到一起再发送(当然处理时候再拆开一个一个执行原来的 handler),性能可以瞬间提高一倍以上,这也从侧面说明了 gRPC 框架自身开销很大,因为用户侧请求总量是一致的,处理模式也是一致的,唯一的区别就是 RPC 框架批量发送或一条一条发送。

我们早在几周前就开始写简单 Echo Server 进行可行性验证和性能测试,是以下三个方面的正交:

1. 协议:CapnProto RPC、brpc over gRPC、裸写 echo。

2. 服务端:Rust、C++,对应用于 TiKV。

3. 客户端:Golang,对应用于 TiDB。

其中 TiKV 侧调研了 Rust 和 C++ 的服务端实现,原因是 Rust 可以通过 binding 方式调用 C++ 服务端。而 TiDB 侧客户端实现不包含 C++ 的原因是 Golang 进行 C / C++ FFI 性能很差,因此可以直接放弃 C/C++ 包装一层 binding 用于 TiDB 的想法。最后测试下来,有以下结果:

  • CapnProto:序列化性能很高,但其 RPC 性能没有很突出。最重要的是,CapnProto 的 Golang Client 实现有 bug,并不能稳定地与 Rust 或 C++ 的 Server 进行 RPC 交互。作者回复说这是一个已知缺陷,涉及重构。这对于 Hackathon 来说是一个致命的问题,我们并没有充足的时间解决这个问题,直接导致我们放弃这个方案。
  • brpc over gRPC:可以实现使用 brpc C++ 客户端 & gRPC Golang 服务端配合(注:brpc 没有 Golang 的实现)。但这个方案本质只是替换服务端的实现,并没有替换协议,并不彻底,我们不是特别喜欢。另外在这个换汤不换药的方案下,测试下来性能的提升有限,且随着 payload 越大会越小。我们最终觉得作为一个 Hackathon 项目如果仅有有限的性能提升(虽然可以在展示的时候掩盖缺陷只展示优点),那么意义不是很大,最终无法用于产品,因而放弃。
  • 裸写:我们三个成员都不是这方面的老司机,裸写大概是写不完的。。

新计划:做一个用于培训的可视化

在 Hackathon 开始的前一个晚上,我们决定推翻重来,于是 brain storm 了几个想法,最后觉得做一个用于教学的可视化比较可行,并且具有比较大的实际意义。另外,这个新项目「集群可视化」相比原项目「换 RPC」来说更适合 Hackathon,主要在于:

1. 具有图形化界面,容易拿奖可以直观地展现成果。

2. 并不是一个「非零即一」的任务。新项目有很多子功能,可以逐一进行实现,很稳妥,且不像老项目那样只有换完才知道效果。

我们继续在这个「可视化」的想法上进行了进一步的思考,想到如果可以做成在线教学的模式,则可以进一步扩展其项目意义,形成一个完整的在线教学体系,因此最终决定了项目的 scope。

具体实现

在 TiKV、TiDB 与 PD 中各个关键路径上将发生的具体「事件」记录下来,并在前端进行一一可视化。

事件

我们将 TiDB Lab 进行可视化所需要的信号称为「事件」,并规划了以下「事件」:

  • Ansible 事件:Ansible 部署 TiDB
  • Ansible 事件:Ansible 部署 TiKV
  • TiDB 事件:TiDB 启动
  • TiDB 事件:TiDB 关闭
  • TiDB 事件:TiDB 收到一条 SQL
  • TiKV 事件:TiKV 启动
  • TiKV 事件:TiKV 关闭
  • TiKV 事件:TiKV 收到一条 KvGet 请求
  • TiKV 事件:TiKV 收到一条 PreWrite / Commit 请求
  • TiKV 事件:TiKV 收到一条 Coprocessor 请求
  • TiKV 事件:TiKV Region Peer 创建
  • TiKV 事件:TiKV Region Peer 删除
  • TiKV 事件:TiKV Region 分裂
  • TiKV 事件:TiKV Region Snapshot 复制
  • TiKV 事件:TiKV Region 数据量发生显著变化

最后,由于时间关系、技术难度和可视化需要,实际实现的是以下事件:

  • TiDB 事件:TiDB 启动,若首次启动认为是新部署
  • TiDB 事件:TiDB 关闭
  • TiDB 事件:TiDB 收到 SQL 并发起 KvGet 读请求
  • TiDB 事件:TiDB 收到 SQL 并发起 PreWrite / Commit 写入请求
  • TiDB 事件:TiDB 收到 SQL 并发起 Coprocessor 读请求
  • TiKV 事件:TiKV 启动,若首次启动认为是新部署
  • TiKV 事件:TiKV 关闭
  • PD 事件:TiKV Region Peer 创建(通过 Region 心跳实现)
  • PD 事件:TiKV Region Peer 删除(通过 Region 心跳实现)
  • PD 事件:TiKV Region 分裂(通过 Region 心跳实现)
  • PD 事件:TiKV Region 数据量发生显著变化(通过 Region 心跳实现)

可视化

可视化部分由前端(lab-frontend)和事件收集服务(lab-gateway)组成。

事件收集服务是一个简单的 HTTP Server,各个组件通过 HTTP Post 方式告知事件,事件收集服务将其通过 WebSocket 协议实时发送给前端。事件收集服务非常简单,使用的是 Node.js 开发,基于 ExpressJs 启动 HTTP Server 并基于 SocketIO 实现与浏览器的实时通讯。ExpressJs 收到事件 JSON 后将其通过 SocketIO 进行广播,总代码仅仅十几行。

可视化前端采用 Vue 实现,动画使用 animejs 和 CSS3。

  • 通过模板实现的可视化

一部分事件通过「由事件更新集群状态数据 – 由集群状态通过 Vue 渲染模板」进行可视化。

这类可视化是最简单的,以 TiDB 启动与否为例,TiDB 的启动与否在界面上呈现为一个标签显示为「Started」或「Stopped」,那么就是一个传统的 Vue MVVM 流程:

1. 数据变量 instances.x.online 代表 TiDB x 是否已启动。

2. 收到 TiDB Started 事件后,更新 instances.x.online = true 。

3. 收到 TiDB Stopped 事件后,更新 instances.x.online = false 。

4. 前端模板上,根据 instances.x.online 渲染成 Started 或 Stopped 对应的界面。

这类可视化的动画采用的是 CSS3 动画。由于 Region Peer 位置是由 left, top CSS 属性给出的,因此为其加上 transition,即可实现 Region Peer 在屏幕上显示的位置改变的动画。位置改变会主要发生在分裂时,分裂时 Region 列表中按顺序会新增一个,那么后面各个 Region 都要向后移动(或换到下一行等)。

最后,使用 Vue Group Transition 功能,即可为 Region Peer 的新增与删除也加入动画效果。

  • 通过动画实现的可视化

另一部分事件并不反应为一个持久化 DOM 的变化,例如 TiDB 收到 SQL 并发请求到某个具体 TiKV 上 Region peer 的事件,在前端展示为一个 TiDB 节点到 TiKV Region Peer 的过渡动画。动画开始前和动画结束后,DOM 没有什么变化,动画是一个临时的可视元素。这类动画通过 animejs 实现。

TiDB Lab 诞生记 | TiDB Hackathon 优秀项目分享-脚本宝典
<center>这个浮夸的开场动画效果也是 animjs 做的</center>

  • 时间穿梭

时间穿梭是一个在目前前端框架中提供的很时髦的功能,我们准备借鉴一波。主要包括:1. 回退到任意一个历史事件发生的时刻展示集群的状态;2. 从当前事件开始往后进行单步可视化重现。

时间穿梭的本质是需要实现两个基础操作:

1. 对于单一事件实现正向执行,即事件发生后,更新对应集群数据信息(如果采用「通过模板实现的可视化」),或创建临时动画 DOM(如果采用「通过动画实现的可视化」)。另外允许跳过「通过动画实现的可视化」这一步。

2. 对于单一事件实现反向执行,即撤销这个事件造成的影响。对于「通过模板实现的可视化」,我们需要根据事件内容反向撤销它对集群数据信息的修改。对于「通过动画实现的可视化」,我们什么都不用做。

时间穿梭的功能可以通过组合这两个基础操作实现。

1. 回退到任意历史事件发生时刻:若想要前往的事件早于当前呈现的事件,则对于这期间的事件逐一进行反向执行。若想要前往的事件晚于当前呈现的事件,则进行无动画的正向执行。

2. 从当前事件开始单步可视化:执行一次有动画的正向执行。

3. 实时展示新事件:执行一次有动画的正向执行。

TiDB 事件收集

TiDB 的历史事件收集略为 Hack。由于需要过滤任何非用户发起的查询(类似 GC 或者 meta 查询会由背景协程频繁发起打扰使用体验),因此在用户链接入口处添加了 context 标记一路携带到执行层,再修改相应的协程同步数据结构添加需要转发的标记信息。比较麻烦的是类似 Point Get 这样接口允许携带信息非常少的调用,只好将标记位编码进 Key 本身了。Plan 的可视化其实并没有花多少功夫,因为找到 TiDB 本身已经做了类似的功能,我们无非只是将这块代码直接偷来了。

PD 事件收集

原本不少事件希望在 TiKV 端完成侦听,不过显然 KV 一小时编译一次的效率无法满足 Hackathon 中多次试错的需要。因此我们改为在 PD 中侦测心跳和汇报事件。其实并没有什么神秘,在原本 PD 自己检查 Region 变更的代码拆分成 Region 和 Peer 变更:每次 PD 接到 Region 心跳会在 PD Cache 中进行 Version 和 confVer 变更的检测,主要涉及 Peer 的增加和减少等。而 Region Split 会单独由 RegionSplitReport 进行汇报,这里也会做一次 Hook。另外就是每次心跳会检查是否有未上报给 Lab 的 Region 信息,如果有就转换成 Peer 信息进行补发。

TiKV 事件收集

如上,原本我们计划了很多 TiKV 事件,但由于开发机器配置不佳,每次修改都要等待一小时进行一次编译,考虑到 Hackathon 上时间紧迫,因此最终大大缩减了 TiKV 上收集的事件数量,改为只收集启动和停止。基本架构是事件发生的时候,事件异步发送给一个 Channel,Channel 的另一端有一个异步的 worker 负责不断处理各个事件并通过 Hyper HTTP Client 发送出去。这个流程其实与 TiKV 中汇报 PD 事件有些类似,只是事件内容和汇报目标不一样。

感悟

以下文字 by 马晓宇 @ OLAP Team, PingCAP
“由于比较能调侃干的活相对少一些,所以大王要我来巡山写感悟。”

就像队长说的,这个项目原本是希望做一个 bRPC 替换 gRPC 的试验,只是由于种种原因临到 Hackathon 前夜我们才确定这是个大概率翻车的点子。于是乎我们只好在酒店里抓耳挠腮,一边讨论可能的补救措施。

参加过、围观过几次 Hackathon,见过现场 Demo 效果最震撼的一次其实并不是一个技术上最优秀的作品,但是它的确赢得了大奖。那是一个脑洞奇大的点子:用 Kinect 体感加上 DirectX 的全 3D 展示做的网络流量实时展示;程序根据实时数据(举办方提供了实时网络测量统计信息)聚合显示不同粗细的炫酷弧光特效,而演示者则用手势操控地球模型的旋转。

于是在完全不了解大家是否能搞定前端的情况下,我们还是很轻率地决定了要做个重前端的项目。至于展示什么?既然时间不够,那么秀一个需要生产上线的可视化工具,翻车的概率就大的多,不如直接定位为 For Educational Purpose Only。而且大概是过度解读了炫酷对于成功的重要性,因此队长也轻率地决定了这个项目的基调是「极尽浮夸」。就在这样友好且不靠谱的讨论氛围下,这个项目的策划出炉了。

之后就是艰苦卓绝连绵无休的代码过程了。

龙毛二哥,晓峰和胡家属的 TiNiuB Team 就在我们对面开工。讲道理这其实是我个人最喜欢的项目之一。第一天放学的时候,看着他们的进度其实我心里虚得不行:看看别人家的项目,我们的绝对主力还在折腾 TiDB Logo 动画,是我敢怒不敢言的状态。

原来我想偷一下懒回家睡个觉啥的,就临时改变主意继续配合队长努力干活;钱同学也抱着「TiKV 一天只能 Build 24 次必须珍惜」的态度写着人生的第一个 Rust 项目。

因此,这里必须鸣谢龙哥他们对我们的鞭策。

事实证明,误打误撞但又深谋远虑的浮夸战略是有效的。Demo 的时候我很认真地盯着评委:在本项目最浮夸的 Logo 展示环节,大家的眼睛是发着光的,一如目睹了摩西分开红海。我个人认为这个动画 Logo 生生拉高了项目 50% 的评分。

只是具体要说感悟的话(似乎好多年没写感悟了呢),首先这次我们三个组员虽然有两个是 PingCAP 员工,不过由于技能的缺口大于人力,因此负责的任务都不是自己所属的模块:队长是 TiKV 组的但是在写前端,我是 OLAP 组的但是在改 PD 和 DB,钱同学也在做自己从来没写过的 Rust(参赛前一周简单入门了一下),因此其实还蛮有挑战的(笑)。然后偶尔写写自己不熟悉的语言,搞搞自己不熟悉的模块,会有一种别样的新鲜刺激感,这大概就是所谓的路边野花更香吧(嗯)?除此之外,Hackathon 更像一个大型社交活动,满足了猫一样孤僻的程序员群体被隐藏的社交欲,有利于码农的身心发展,因此可以多搞搞 :) 。

回到我们项目本身的话,其实 TiDB 的源码阅读或者其他介绍类文章其实并不能非常直观地帮助一头雾水的初学者理解这个系统。我们做这个项目的最大目的是能降低学习门槛,让所有人能以非常直观,互动的方式近距离理解她。所以希望这个项目能给大家带来方便吧。

TiDB Hackathon 2018 共评选出六个优秀项目,本系列文章将由这六个项目成员主笔,分享他们的参赛经验和成果。我们非常希望本届 Hackathon 诞生的优秀项目能够在社区中延续下去,感兴趣的小伙伴们可以加入进来哦。

本文固定链接: http://www.js-code.com/cpp/cpp_59600.html