TiDB Hackathon 2018 共评选出六个优秀项目,本系列文章将由这六个项目成员主笔,分享他们的参赛经验和成果。我们非常希望本届 Hackathon 诞生的优秀项目能够在社区中延续下去,感兴趣的小伙伴们可以加入进来哦~
本文作者是来自 DSG 团队的杨文同学,他们的项目《PD 热点调度贝叶斯模型》在本届 Hackathon 中获得了三等奖+最佳创意奖。

“在 TiDB Hackathon 2018 学习到不少东西
希望明年再来”
“pd ctl 天真学习机”
具体做法:用 naive bayes 模型来根据系统指标和人的 pd ctl 调用,来得到一个模型去根据系统指标去自动提供 pd ctl 调用的命令。
1. 贝叶斯算法举例
贝叶斯模型可以用来干这种事:
比如一个妈妈根据天气预报来跟儿子在出们的时候叮嘱:
天气预报[ 晴, 温度: 28, 风力: 中 ], 妈妈会说 [好好玩]
天气预报[ 雨, 温度: 15, 风力: 低 ], 妈妈会说 [带上伞]
天气预报[ 阴, 温度: 02, 风力: 大 ], 妈妈会说 [多穿点]...把这些输入输入到贝叶斯模型里以后, 模型可以根据天气预报来输出:
天气预报[ 晴, 温度: 00, 风力中], 模型会说 [ 多穿点:0.7, 好好玩0.2, 带上伞0.1]
天气预报[ 雨, 温度: 10, 风力大], 模型会说 [ 带上伞:0.8, 多穿点0.1, 好好玩0.1]这样通过一个妈妈的叮嘱就可以训练出一个也会根据天气预报给出叮嘱的模型。
2. 初步想法
我们可以把一个模型单独的部署在一个 pod 里, 暴露一个 service ,然后集群上每次有人去调用 pd_ctl 的时候就在后台用 rest call 到模型服务上记录一下操作(叮嘱)和当前的系统指标(好比天气预报). 这样慢慢用一段时间以后,积累的操作多了以后,就可以打开某个自动响应,或者打开自动建议应该执行的命令的功能。
这样模型可以在某一组系统指标出现之前类似学习过的状态之后,给出相应的建议,当这些建议都很正确的时候直接让 pd 直接采纳,完全智能的自动化运作。
3. 实际 Hackathon 方案
在跟导师交流探讨后发现,目前 PD 已经比较自动化了,很少需要人为介入进行操作,需要的时候也是比较复杂的场景,或者自动化运作比较慢的场景。
我们团队在跟多名导师的沟通交流下,将初步想法进行了一些调整:
首先,介绍一下我们团队(DSG),分别来自:丹麦、北京(山西)、广州。
D 先生是在比赛前一天早上到达北京的,我是比赛前一天晚上从广州出发,于比赛当日早上 6:38 才抵达北京的。
说实话,时差和疲惫对于参赛还是有一点影响的。
废话不多说,我就来回顾一下我的整个参赛过程。








1. 搭建 TiDB 集群
完全参考文档。
测试 TiDB 集群,可能遇到的坑(MySQL 8 client On MacOSX):
mysql -hx.x.x.x --default-character-set utf8
2. 天真贝叶斯的服务接口
/model/service1 PUT 上报数据:{
"updates": [
[
"transfer leader from store 7 to store 2",
[
{
"feature_type": "Category",
"name": "hotRegionsCount1",
"value": "true"
},
{
"feature_type": "Category",
"name": "minRegionsCount1",
"value": "true"
},
{
"feature_type": "Category",
"name": "hotRegionsCount2",
"value": "true"
},
{
"feature_type": "Category",
"name": "minRegionsCount2",
"value": "true"
},
{
"feature_type": "Category",
"name": "srcRegion",
"value": "7"
}
]
],
]}/model/service1 POST 获取模型结果:输入参数:上报的参数
{
"predictions": [
{
"transfer leader from store 1 to store 2": 0.27432775221072137,
"transfer leader from store 1 to store 7": 0.6209064350448428,
"transfer leader from store 2 to store 1": 0.024587894827775753,
"transfer leader from store 2 to store 7": 0.01862719305134528,
"transfer leader from store 7 to store 1": 0.02591609468013258,
"transfer leader from store 7 to store 2": 0.03563463018518229
}
]} 3. PD 集群部署
首先将 pd-server 替换到集群所在 ansible/resources/bin 目录下,那如何让集群上的 PD 更新生效呢?
更新:
$ ansible-playbook rolling_update.yml --tags=pd在实操过程中, 如果你在更新到一半的时候就关门了,可能会导致整个 PD 挂掉(非集群环境),可能是因为逻辑不严谨所导致的问题
直接停止了 ansible,导致 PD 集群机器节点有停止的情况,这个时候你可以通过以下命令启动它。
启动:
$ ansible-playbook start.yml --tags=pd4. PD 调度
4.1 取消热点数据调度
大家都以为可以通过配置来解决:(调度开关方法: 用 config set xxx 0 来关闭调度)
配置如下:(虽然找的地方错误了,但是错打错着,我们来到了 Demo Time:
config set leader-schedule-limit 0
config set region-schedule-limit 0
scheduler add hot-region-scheduler
config show
config set leader-schedule-limit 4
config set region-schedule-limit 8实测发现,根本不生效,必须要改源代码。
func (h *balanceHotRegionsScheduler) dispatch(typ BalanceType, cluster schedule.Cluster) []*schedule.Operator {
h.Lock()
defer h.Unlock()
switch typ {
case hotReadRegionBalance:
h.stats.readStatAsLeader = h.calcScore(cluster.RegionReadStats(), cluster, core.LeaderKind)
// return h.balanceHotReadRegions(cluster) // 将这一行注释
case hotWriteRegionBalance:
h.stats.writeStatAsLeader = h.calcScore(cluster.RegionWriteStats(), cluster, core.LeaderKind)
h.stats.writeStatAsPeer = h.calcScore(cluster.RegionWriteStats(), cluster, core.RegionKind)
// return h.balanceHotWriteRegions(cluster) // 将这一行注释
}
return nil
}但是,我们要的不是不调度,而只是不给调度结果:
func (h *balanceHotRegionsScheduler) balanceHotReadRegions(cluster schedule.Cluster) []*schedule.Operator {
// balance by leader
srcRegion, newLeader := h.balanceByLeader(cluster, h.stats.readStatAsLeader)
if srcRegion != nil {
schedulerCounter.WithLabelValues(h.GetName(), "move_leader").Inc()
// step := schedule.TransferLeader{FromStore: srcRegion.GetLeader().GetStoreId(), ToStore: newLeader.GetStoreId()} // 修改为不返回值或者返回 _
_ = schedule.TransferLeader{FromStore: srcRegion.GetLeader().GetStoreId(), ToStore: newLeader.GetStoreId()}
// return []*schedule.Operator{schedule.NewOperator("transferHotReadLeader", srcRegion.GetID(), srcRegion.GetRegionEpoch(), schedule.OpHotRegion|schedule.OpLeader, step)} // 注释这一行,并 return nil
return nil
}
// balance by peer
srcRegion, srcPeer, destPeer := h.balanceByPeer(cluster, h.stats.readStatAsLeader)
if srcRegion != nil {
schedulerCounter.WithLabelValues(h.GetName(), "move_peer").Inc()
return []*schedule.Operator{schedule.CreateMovePeerOperator("moveHotReadRegion", cluster, srcRegion, schedule.OpHotRegion, srcPeer.GetStoreId(), destPeer.GetStoreId(), destPeer.GetId())}
}
schedulerCounter.WithLabelValues(h.GetName(), "skip").Inc()
return nil
}
......
func (h *balanceHotRegionsScheduler) balanceHotWriteRegions(cluster schedule.Cluster) []*schedule.Operator {
for i := 0; i < balanceHotRetryLimit; i++ {
switch h.r.Int() % 2 {
case 0:
// balance by peer
srcRegion, srcPeer, destPeer := h.balanceByPeer(cluster, h.stats.writeStatAsPeer)
if srcRegion != nil {
schedulerCounter.WithLabelValues(h.GetName(), "move_peer").Inc()
fmt.Println(srcRegion, srcPeer, destPeer)
// return []*schedule.Operator{schedule.CreateMovePeerOperator("moveHotWriteRegion", cluster, srcRegion, schedule.OpHotRegion, srcPeer.GetStoreId(), destPeer.GetStoreId(), destPeer.GetId())} // 注释这一行,并 return nil
return nil
}
case 1:
// balance by leader
srcRegion, newLeader := h.balanceByLeader(cluster, h.stats.writeStatAsLeader)
if srcRegion != nil {
schedulerCounter.WithLabelValues(h.GetName(), "move_leader").Inc()
// step := schedule.TransferLeader{FromStore: srcRegion.GetLeader().GetStoreId(), ToStore: newLeader.GetStoreId()} // 修改为不返回值或者返回 _
_ = schedule.TransferLeader{FromStore: srcRegion.GetLeader().GetStoreId(), ToStore: newLeader.GetStoreId()}
// return []*schedule.Operator{schedule.NewOperator("transferHotWriteLeader", srcRegion.GetID(), srcRegion.GetRegionEpoch(), schedule.OpHotRegion|schedule.OpLeader, step)} // 注释这一行,并 return nil
return nil
}
}
}
schedulerCounter.WithLabelValues(h.GetName(), "skip").Inc()
return nil
}当修改了 PD 再重新编译得到 pd-server,将其放到
tidb-ansible/resources/bin/pd-server 并替换原来的文件,然后执行
ansible-playbook rolling_update.yml --tags=pd,即可重启 pd-server 服务。
在调优的过程中发现,当前 hot-region-scheduler 的调度时对于目标机器的选择并不是最优的,代码如下:
https://github.com/pingcap/pd/blob/master/server/schedulers/hot_region.go#L374
简述:循环遍历 candidateStoreIDs 的时候,如果满足条件有多台,那么最后一个总会覆盖前面已经存储到 destStoreID 里面的数据,最终我们拿到的 destStoreID 有可能不是最优的。(pd issue: https://github.com/pingcap/pd/issues/1359)
// selectDestStore selects a target store to hold the region of the source region.
// We choose a target store based on the hot region number and flow bytes of this store.
func (h *balanceHotRegionsScheduler) selectDestStore(candidateStoreIDs []uint64, regionFlowBytes uint64, srcStoreID uint64, storesStat core.StoreHotRegionsStat) (destStoreID uint64) {
sr := storesStat[srcStoreID]
srcFlowBytes := sr.TotalFlowBytes
srcHotRegionsCount := sr.RegionsStat.Len()
var (
minFlowBytes uint64 = math.MaxUint64
minRegionsCount = int(math.MaxInt32)
)
for _, storeID := range candidateStoreIDs {
if s, ok := storesStat[storeID]; ok {
if srcHotRegionsCount-s.RegionsStat.Len() > 1 && minRegionsCount > s.RegionsStat.Len() {
destStoreID = storeID
minFlowBytes = s.TotalFlowBytes
minRegionsCount = s.RegionsStat.Len()
continue // 这里
}
if minRegionsCount == s.RegionsStat.Len() && minFlowBytes > s.TotalFlowBytes &&
uint64(float64(srcFlowBytes)*hotRegionScheduleFactor) > s.TotalFlowBytes+2*regionFlowBytes {
minFlowBytes = s.TotalFlowBytes
destStoreID = storeID
}
} else {
destStoreID = storeID
return
}
}
return
}4.2 PD 重要监控指标详解之 HotRegion:
本次我们只 hack 验证了 Write Region Leader 这部分,所以我们重点关注一下监控和问题:
监控数据有一定的延时(粗略估计1-2分钟)
5. 模拟热点数据
修改 tidb-bench 的 Makefile#load 模块对应的主机地址,然后执行 make tbl, make load 即可往服务器 load 数据了。
注意,这里你也需要进行一些配置修改:--default-character-set utf8
犯的错:受限于本地-服务器间网络带宽,导入数据很慢。
$ ./go-ycsb run mysql -p mysql.host=10.9.x.x -p mysql.port=4000 -p mysql.db=test1 -P workloads/workloada注:go-ycsb 支持 insert,也支持 update,你可以根据你的需要进行相对应的调整workloada#recordcount 和 workloada#operationcount 参数。
6.本地构建 rust-nb-server
rust 一天速成……
Demo Time 的时候听好几个团队都说失败了。我以前也尝试过,但是被编译的速度以及耗能给击败了。
环境都可以把你 de 自信心击溃。
rustup install nightly
cargo run
...Mac 本地打包 Linux 失败:缺少 std 库,通过 Docker 临时解决。
7. 导师指导
从比赛一开始,导师团就非常积极和主动,直接去每个项目组,给予直接指导和建议,我们遇到问题去找导师时,他们也非常的配合。
导师不仅帮我们解决问题(特别是热点数据构建,包括对于代码级别的指导),还跟我们一起探讨课题方向和实际可操作性,以及可以达到的目标。
非常感谢!!!
我们的准备和主动性真的不足,值得反思--也希望大家以后不要怕麻烦,有问题就大胆的去问。
整个 Demo show 进行的非常顺利,为每一个团队点赞!
很多团队的作品都让人尖叫,可想而知他们的作品是多么的酷炫和牛逼,印象中只有一个团队在 Demo 环境出现了演示时程序崩溃的问题(用Java Netty 基于 TiKV 做的 memcache(实现了大部分的协议))。
遗憾!!!
我们 DSG 团队荣获三等奖+最佳创意两项大奖,但是很遗憾我未能跟团队一起分享这一刻。
因为我要赶着去火车站,所以在周日下午6点的时候,我跟队友和一些朋友道别后,我就去火车站了,后面几组的 Demo Show 也很非常遗憾未能参加。
得奖感言:
谢谢 DSG 团队,谢谢导师,谢谢评委老师,谢谢 PingCAP 给大家筹备了这么好的一次黑客马拉松比赛活动。
本次比赛的各个方面都做的完美,除了网络。
1. 环境(一定要提前准备)----这次被坑了不少时间和精力;
2. 配置文档中有一些注意事项,一定要认真阅读:ext4 必须要每台机器都更新;
3. [10.9.97.254]: Ansible FAILED! => playbook: bootstrap.yml; TASK: check_system_optional : Preflight check - Check TiDB server's RAM; message: {"changed": false, "msg": "This machine does not have sufficient RAM to run TiDB, at least 16000 MB."} - 内存不足的问题
4. 如果磁盘挂载有问题,可以重新清除数据后再重新启动;
ansible-playbook unsafe_cleanup_data.yml (https://github.com/pingcap/docs/blob/master/op-guide/ansible-operation.md)TiDB Hackathon 2018 共评选出六个优秀项目,本系列文章将由这六个项目成员主笔,分享他们的参赛经验和成果。我们非常希望本届 Hackathon 诞生的优秀项目能够在社区中延续下去,感兴趣的小伙伴们可以加入进来哦。
原文作者:;如有侵权,请联系管理员删除。