马上要开始第二阶段优化了,赶快把第一阶段优化内容及结果贴下。
•
背景
–
繁忙时段
98%~100%
的
handler
线程被
BLOCK
–
RPC
请求堆积
•
Profiling
工具 (定位瓶颈)
–
jstack
线上环境使用
–
yjp
测试环境使用
优化一:避免频繁调用加锁方法
• 500 次连续 jstack 结果分析
优化一:避免频繁调用加锁方法
• 500 次连续 jstack 结果分析
–
jt.getTasksToKill:15631.2%
-- tip.shouldClose 155 99.3%
-- tip.isComplete 155 100%
-- tip.shouldClose 155 99.3%
-- tip.isComplete 155 100%
•
s
ynchronized
方法比
non-synchronized
方法慢
10-15
倍
•
优化方法:避免调用加锁的TIP
::isComplete
()
优化前,TIP::isComplete()方法占总CPU时间达36%:
优化前,TIP::isComplete()方法占总CPU时间达36%:
优化后:已经在图中消失,即比例非常小。
优化二:避免比较JobID
优化前,TIP::shouldClose()方法占到了总CPU时间的19%
•
优化方案:
–
TreeMap
增加
Comparator
–
新增只比较
id
的
tip.isComplete
(
tid
)
方法
优化后只有1%了:
优化后只有1%了:
优化三:降低JT::getTasksToKill()方法的时间复杂度
•
优化
getTasksToKill
()
–
优化前每次心跳遍历
TaskTracker
上所有
task
–
优化后每次心跳遍历
TaskTracker
上
running task
•
TaskTracker
上
completed tasks >> running task
优化后,心跳已经不占主要操作:
优化后,心跳已经不占主要操作:
顺便说下优化三是非常给力的,举个例子:
一个1w个map+5k个reduce的作业,当执行reduce时,map全部处于完成状态。这段时间每次心跳都要遍历这1w的map;
而tasktracker上running tasks的个数的最大值是个常数,也就是slots的配置。
因此这个改动是可以理解为复杂度降低了一个数量级 。优化四:降低Scheduler::updateTaskCounts()时间复杂度
•
优化
Scheduler.updateTaskCounts
()
–
优化前:遍历
job
的每个
task
统计
runningMaps|Reduces
&
neededMaps|Reduces
–
优化后:直接从
JobInProgress
中读取上述统计值
优化总结
•根本原因:
–
单点
–
心跳需要持有
JobTracker
大锁
•
优化的关键是
定位瓶颈
•
消除一个瓶颈后,很快会出现下一个瓶颈
•
终极方案:
mr2 (0.23)
本次优化的最大成果是,在2000台集群上成功启动了TaskTracker的oob心跳。