本文作者是矛盾螺旋队的成员刘玮,他们的项目 TiEye 在 TiDB Hackathon 2018 中获得了三等奖。TiEye 是 Region 信息变迁历史可视化工具,通过 PD 记录 Region 的 Split、Merge、ConfChange、LeaderChange 等信息,可以方便的回溯 Region 某个时间的具体状态,为开发人员提供了方便的可视化展示界面及查询功能。
TiKV 的 Region
Region 是 TiKV 的一个数据调度单元,TiKV 将数据按照键值范围划分为很多个 Region,分在集群的多台机器上,通过调度 Region 来实现负载均衡以及数据存储的扩展,同时一个 Region 也是一个 Raft Group,一个 Region 分布在多个 TiKV 实例上(通常是 3 个或者 5 个),通过 Raft 算法保证多副本的强一致性。
动机
这个项目的灵感是之前在查一些问题的时候想到的,因为我们很多时候需要去知道 Region 在某个时间的状态,这就需要通过日志从杂乱的信息中提取出来有用的信息来复原当时的场景,但实际并不是特别方便高效,尤其是在看多个 Region 之间的关系的时候。因此通过将 Region 信息变化历史可视化,希望能为开发者们在定位问题的时候提供一个方便直观的工具,同时还能通过它来分析 PD 的调度策略,以及调度带来的写放大问题等等。
实际方案
一开始我们考虑的是通过一个独立的服务去解析 PD 的日志来获取 Region 信息的变化历史,后来讨论后认为这样做不仅依赖于 PD 中的日志格式,造成系统耦合,同时 PD 的 leader 变迁导致日志内容不连续,以及日志中的信息并不是特别充分等问题也增加了开发难度。因此我们最后决定直接修改 PD 的源代码,在每次 PD 变更 Region 的时候,记录下这些信息并持久化。这样既能保证在 PD 切换 leader 后的变化信息的连续性,又提供了更加丰富的历史信息。同时,PD 添加相关的 API,以供前端进行查询。
Hackathon 回顾
我们的团队由三个人组成,分别是我(刘玮)、周振靖和张博康,都毕业于北京邮电大学。我们在这次 Hackathon 之前就认识,因为大家都在北京,因此交流还是蛮方便的,在开赛大约一周前就确定了这个题目。其实我最初的想法是做一些有关于性能优化的事情,但是在跟队友们交流后还是决定做 Region 历史可视化,其更具有实用性,也更适合在 Hackathon 上来做。
10:00 比赛正式开始。我们之前已经讨论好了项目的大体架构,因此没有再做过多的讨论就各自开始码代码了。博康负责后端框架以及 PD 相应的修改,我负责后端查询 API,振靖负责前端可视化。
12:15 午餐。休息片刻,继续码代码。
14:00 后端框架大体完成,已经可以在 PD 中收集 Region 相应的状态变化;前端部分已经画出简单的 Region 分裂、合并等示意图。
17:30 完成了最简单的查询逻辑,进行了第一次联调,发现大家对于 Region 状态的展示方式理解不一样,于是再次讨论统一了意见。
18:00 晚餐时间。
19:00 ~ 次日 2:30: 我们基本完成了后端开发,而前端这时还剩比较多的工作量。同时晚上在前端展示,后端查询 API,数据持久化方面都发现了几个 bug,大家一直忙到很晚才一一解决。
次日 9:00 返回赛场,抽签确定 Demo 时间,最终为第四个出场。
次日 12:00 前端可视化基本完善,为界面做最后的调整。
次日 12:00 ~ 12:30 午餐时间
次日 13:00 ~ 14:00 准备 PPT 和展示录屏
次日 14:30 ~ 18:30 Demo Time(B 站直播)
TiEye 架构
我们采取了前后端分离的架构。
前端是 Vue.js 框架,使用 Typescript 语言开发。由于看上去现有的图表库啥的并不能很好地满足我们的需求,所以前端同学决定手撸 SVG。
后端则是 PD 提供的 API。数据存储目前暂时存储在 etcd,将来会考虑其它方案来应对数据规模太大的情况。我们将 Region 的变化分成了以下四种:
LeaderChange:Raft Group 选举(或者是主动移交)了新的 leader
ConfChange:Raft Group 成员变更
Split:当某个 Region 数据超过一定阙值时(或被手动干预时)会分裂成键值范围相邻的两个 Region
Merge:两个键值范围连续的 Region 合并成一个
Bootstrap:一个新的集群中第一个 Region 产生
在前端的表示方式如图所示:
给 PD 添加的 API 则有如下几种:
/pd/api/v1/history/list,GET 方法,返回全部历史。
返回结果:
[
{
“timestamp”:1544286220000000,
“leader_store_id”:0,
“event_type”:”Bootstrap”,
“Region”:{
“id”:2,
“start_key”:””,
“end_key”:””,
“Region_epoch”:{
“conf_ver”:1,
“version”:1
},
“peers”:[
{
“id”:3,
“store_id”:1
}
]
},
“parents”:[],
“children”:26
},
…
]
/pd/api/v1/history/Region/{RegionId},GET 方法,查询某个 Region 的变化历史,返回结果同上。
/pd/api/v1/history/key/{key},GET 方法,查询某个 key 所属 Region 的变化历史,返回结果同上。
以上几个 API 均可附加起止时间参数(时间戳),如:
/pd/api/v1/history/list?start=0&end=1544286229000000
顺便一提,前端部分原先打算作为 PD 的一部分来提供,与 PD 一起构建(于是前端的代码也放进了 PD 的一个单独的文件夹里)。但是后来觉得对于不涉及这些前端代码的开发者来说这样做不太好,所以我们之后会抽时间将这些前端代码放进一个单独的仓库里。
测试过程
测试的时候我们部署了 1 个 TiDB,6 个 TiKV,3 个 PD,通过 sysbench 导入少量数据,最后通过开启 random-merge-scheduler 来进行随机合并 Region。下图是我们的测试过程中的结果展示:
此时通过我们的工具还意外发现了一个 bug。
可以在上图看到,在第一个红框处 Region 2 合并进 Region 28,然后第二红框那里已经被 merge 进 Region 28 的 Region 2 莫名其妙地又连接了后面的 Region 40,显然这里是有问题的。经过通过日志确认,这是由于 PD 收到了一个含有过期 Region 2 信息的心跳导致的,追根溯源发现是 TiKV 中的 pd-client 的一个 Bug 导致了在与 PD 重连后会发送一个过期的心跳信息(Bug 地址在 https://github.com/tikv/tikv/…)。
实际运行结果
横轴表示时间,纵轴表示 Region 的存储键值的顺序(仅表示顺序,不代表实际的数据量),矩形上的数字表示 Region id,为了便于理解,所有的 Region 的最终状态都会在最后的时间点上展示出来(即使在这个时间点没有发生 Region 的改变)。
点击右上角可以更改查下的时间范围。
右上角可以设置按照 key 的范围对齐,效果如下图:
点击任何一个节点,会展示当时 Region 的详细信息。
拖动下方的框可以对局部进行缩放(你也可以通过查询更小的时间范围达到同样的效果)。
Hackathon Demo
我们团队的 Demo 展示是博康负责的。一开始他还担心如果演讲的时候忘词了怎么办,不过最后展示效果很不错,整个 Demo show 进行得非常顺利(P.S. 要是展示时间能多给几分钟就好了)。
在展示中,我们也看见了其他团队的作品也都非常棒。这其中让我最感兴趣的是有个团队做的是以 TiKV 作为数据存储的 etcd。这个选题一开始我也考虑过,因为我在工作中实际已经遇到了这个问题,不过最后和队友商量后还是选择了现在这个题目。
总结
我们“矛盾螺旋”团队最终获得了三等奖,这对我来说简直是意外之喜。在演示中,很多别的团队也都做得十分优秀,我们在观看其它团队的演示时几乎都觉得获奖无望了。最后却拿到了三等奖,实在是意料之外。这次我们之所以能够获奖,一方面是选题选得恰到好处,具有一定的实际作用,同时工作量又能保证在 Hackathon 期间完成。
最后感谢我的两位队友,谢谢导师,谢谢评委老师,谢谢 PingCAP 的所有工作人员为这次 Hackathon 所做的努力。
视频 | TiDB Hackathon 2018(上半场)
(????0:36:00 开始)矛盾螺旋队 Demo 演示
TiDB Hackathon 2018 共评选出六个优秀项目,本系列文章将由这六个项目成员主笔,分享他们的参赛经验和成果。我们非常希望本届 Hackathon 诞生的优秀项目能够在社区中延续下去,感兴趣的小伙伴们可以加入进来哦。延伸阅读:
1. TiDB Hackathon 2018 回顾
2. 天真贝叶斯学习机 | 优秀项目分享
3. TiQuery:All Diagnosis in SQL | 优秀项目分享
4. 让 TiDB 访问多种数据源 | 优秀项目分享
5. TiDB Lab 诞生记 | 优秀项目分享