JobHistory搜索智能化
前言
做过hadoop集群问题排查工作的同学一定用过JobHistory,这是一个非常好用的"利器",为什么这么说呢?正如这个工具的名称所叫的那样,这个工具能帮你找到历史Job跑过的信息,而信息的记录非常的详细,从Job到Task再到TaskAttempt.假如这时候,1个Job突然执行失败了,你想查明原因,在JobHistory的web界面上依次点击详情链接,基本上都能够找到原因.可是看似非常完美的Job分析工具,也有很多使用起来不是非常方便的地方,于是乎,我们想对此进行一些改进,使其更加易用,同一时候相信能给相同在使用jobHistory的人提供帮助.
现有JobHistory的不足之处
从開始使用这个分析工具到如今,1个让我一直用着特别不爽的地方是,非常难迅速查到历史时间稍稍远一些的Job信息,由于有的时候我要做同一时候段的Job执行情况对照,包含执行时长,Task失败次数等等指标.所以你须要把昨天,前天的数据拉出来.然后非常多人的正常反应就是把Jobhistory页面上默认显示的Job条数加大,以此显示更长时间的Job.这种方法既简单又方便,通过更改以下这个配置项,并重新启动JobHistory就能够立刻做到:
- <property>
- <name>mapreduce.jobhistory.joblist.cache.size</name>
- <value>20000</value>
- <description>Size of the job list cache</description>
- </property>
这个默认值是显示2w条,大家千万不要以为这个值非常大,当你的集群1天能跑上万个job的时候,这个值显然是偏小的,你仅仅能显示到近2,3天的数据,假设突然我想看上周的数据,发现没法看了,这简直就是恶梦.当时我们也遇到了这种情况,然后我们把这个配置项调大到10w,这样就能够保留近一段时间的数据了.可是另外一个问题暴露出来了,页面载入太慢,JobHistory的主页面在1分钟之内是别想显示出来了,大家假设读过jobHistory页面的渲染代码,你能够看到,他的页面是直接全部渲染好之后显示的,并没有说所谓的每页仅仅载入一部分,你要载入10w
条记录,我立即返回10w条记录,构成超级大的html页面,返回到浏览器上,所以我们后来发现非常多的时间开销都花在下载页面的时间上,而非后端返回Job列表信息的时间上.可是没有办法,为了能看到很多其它的历史数据,仅仅能牺牲一下用户体验了.相信博友们其中肯定有一部分人也遇到了这种问题.描写叙述了这么多,事实上我们终于想要达到的1个目标就是,我既想不用显示那么多的Job数据,保留近期1天的就可以,使得页面能迅速打开,其次我又能够查到历史数据.显然,这在原本的jobHistory中是无法兼顾的,所以我们能够改造一下他,使得这个工具能够更加"智能化"一些.
JobHistory使用现状
以下来看看,眼下一般hadoop开发人员是怎样使用jobHistory,一般都会用到以下这个button:
这个一个非常广的搜索button,Job页面信息载入完毕之后,你能够输入你想要的目标Job名称,过滤结果立即就会出现,当你把查询字符串进行清除,Job列表记录又会恢复到原样.非常显然,这是1个非常easy直接化的搜索功能.我就这么多的信息,有就显示,没有就不显示.所以要想达到上小节中提到的优化目标,我们必须在搜索功能上进行优化改造.当然,我们会保留眼下的搜索button,保持其不变.
JobHistory搜索优化目标
从上文中我们知道要做到JobHistory"智能化",须要在搜索功能方面进行改造.那么详细什么样的搜索场景是我们比較easy碰到的呢?
第一个,依据Job名称,我们知道失败的Job名称,然后,进行查询.
第二个,依据Jobid,我们从日志中或其它途径得到失败的Job,直接进行Job搜索,跳转至详情页.
并且还有最关键的1个前提,上述搜索功能的实现是不依赖前端页面显示的Job信息列表,比方我Job显示数量配成10条,我依旧能够查到1周前某某失败Job的信息.以下是几个要点:
1.这里就须要做到jobHistory前后端cache-job数的配置分离,眼下用的都是同一个配置,所以会导致上述这种问题.
2.完毕第二个需求点比第一个easy,由于第二个有jobid,直接进行链接的拼装,直接进行一个重定向就能够解决,全部的Job详情页信息链接都是一个模板,不用实现得过于复杂.
3.第一个需求须要后端通过传进来的Job名称做一些过滤处理,然后再返给前端展示,过滤掉绝大多数没用的Job信息.像相似于这种需求,假设在普通的业务系统开发中,一定是再简单只是了,那么在hadoop中要怎样改造呢,人家的这一套逻辑实现可没这么简单直接.
JobHistory详细代码改造
前面讲述完目标和方法之后,最后须要真正的从代码层面去实现了,所以须要了解一下眼下JobHistory主页面是怎样得到的,数据哪里来,页面前端代码哪里写的,是直接有现成的.html文件?光凭空猜想是没实用的,仅仅有深入源码的研究分析才干有答案.事实上页面的代码实如今HsJobsBlock.java这个类中.页面渲染的逻辑实现就是在render方法中实现的
- /*
- * (non-Javadoc)
- * @see org.apache.hadoop.yarn.webapp.view.HtmlBlock#render(org.apache.hadoop.yarn.webapp.view.HtmlBlock.Block)
- */
- @Override protected void render(Block html) {
- TBODY<TABLE<Hamlet>> tbody = html.
- h2("Retired Jobs").
- table("#jobs").
- thead().
- tr().
- th("Submit Time").
- th("Start Time").
- th("Finish Time").
- th(".id", "Job ID").
- th(".name", "Name").
- th("User").
- th("Queue").
- th(".state", "State").
- th("Maps Total").
- th("Maps Completed").
- th("Reduces Total").
- th("Reduces Completed").
- th("Elapsed Time")._()._().
- tbody();
- LOG.info("Getting list of all Jobs.");
- // Write all the data into a JavaScript array of arrays for JQuery
- // DataTables to display
- StringBuilder jobsTableData = new StringBuilder("[\n");
- for (Job j : appContext.getAllJobs().values()) {
- JobInfo job = new JobInfo(j);
- jobsTableData.append("[\"")
- .append(dateFormat.format(new Date(job.getSubmitTime()))).append("\",\"")
- .append(dateFormat.format(new Date(job.getStartTime()))).append("\",\"")
- .append(dateFormat.format(new Date(job.getFinishTime()))).append("\",\"")
- .append("<a href='").append(url("job", job.getId())).append("'>")
- .append(job.getId()).append("</a>\",\"")
- .append(StringEscapeUtils.escapeJavaScript(StringEscapeUtils.escapeHtml(
- job.getName()))).append("\",\"")
- .append(StringEscapeUtils.escapeJavaScript(StringEscapeUtils.escapeHtml(
- job.getUserName()))).append("\",\"")
- .append(StringEscapeUtils.escapeJavaScript(StringEscapeUtils.escapeHtml(
- job.getQueueName()))).append("\",\"")
- .append(job.getState()).append("\",\"")
- .append(String.valueOf(job.getMapsTotal())).append("\",\"")
- .append(String.valueOf(job.getMapsCompleted())).append("\",\"")
- .append(String.valueOf(job.getReducesTotal())).append("\",\"")
- .append(String.valueOf(job.getReducesCompleted())).append("\",\"")
- .append(
- StringUtils.formatTimeSortable(Times.elapsed(job.getStartTime(),
- job.getFinishTime(), false))).append("\"],\n");
- }
其中获取历史Job信息的方法就是for循环中出现的appContext.getAllJobs方法.这种方法就会触发后端服务的获取Job信息方法.可是appContext是一个基础类,我们要找到相应的详细实现类,appContext的继承关系例如以下:
非常显然,方法在JobHistory这个类中.会调用到以下的方法:
- @Override
- public Map<JobId, Job> getAllJobs() {
- return storage.getAllPartialJobs();
- }
终于会调用到这种方法:
- @Override
- public Map<JobId, Job> getAllPartialJobs() {
- LOG.debug("Called getAllPartialJobs()");
- SortedMap<JobId, Job> result = new TreeMap<JobId, Job>();
- try {
- for (HistoryFileInfo mi : hsManager.getAllFileInfo()) {
- if (mi != null) {
- JobId id = mi.getJobId();
- result.put(id, new PartialJob(mi.getJobIndexInfo(), id));
- }
- }
- } catch (IOException e) {
- LOG.warn("Error trying to scan for all FileInfos", e);
- throw new YarnRuntimeException(e);
- }
- return result;
- }
而全部关于Job执行信息的带.jhist后缀的文件都是由HistoryFileManager这个类所控制的,也就是上述代码中的hsManager.他会返回以下的对象信息:
- public Collection<HistoryFileInfo> getAllFileInfo() throws IOException {
- scanIntermediateDirectory();
- return jobListCache.values();
- }
所以终于返回到前端的信息记录就是jobListCache这个对象.这个对象在初始化的时候就会首先设置cache的大小,就是文章前面提到的那个配置项:
- protected JobListCache createJobListCache() {
- return new JobListCache(conf.getInt(
- JHAdminConfig.MR_HISTORY_JOBLIST_CACHE_SIZE,
- JHAdminConfig.DEFAULT_MR_HISTORY_JOBLIST_CACHE_SIZE), maxHistoryAge);
- }
所以说我们必须把这个配置项分离,使前后端的Job信息控制隔离开.大体流程思路清楚之后,我们又一次回到之前的提到的需求点,要实现Job名过滤,所以我们要添加新的接口.我们能够模仿现有的getAllJobs方法,然后在造出1个带參数的getDisplayedJobs(String fileterName).首先你须要在appContext中新增接口定义:
- Map<JobId, Job> getDisplayedJobs(String filterName);
然后给出默认实现,在他的全部继承类中,開始时直接返回null就可以.然后在HistoryStorage.java中新增相似接口,就是须要被JobHistory调用的方法:
- /**
- * Get partial displayed of the cached jobs.
- * @param filterName the filter job name
- * @return all of the cached jobs
- */
- Map<JobId, Job> getPartialDisplayedJobs(String filterName);
然后在CachedHistoryStorage给予实现,可是在实现之前,还须要对显示数目做控制,不能在一味的显示全部cache中的Job,所以得在此类中新增配置项,姑且叫做以下的名称
- <property>
- <name>mapreduce.jobhistory.joblist.cache-displayed.size</name>
- <value>1000</value>
- <description>The size of job-list cache displayed in the jobHistory web ui.
- </description>
- </property>
然后初始化此配置项到变量中
- @SuppressWarnings("serial")
- private void createLoadedJobCache(Configuration conf) {
- ...
- cacheDisplayedSize =
- conf.getInt(JHAdminConfig.MR_HISTORY_JOBLIST_CACHE_DISPLAYED_SIZE,
- JHAdminConfig.DEFAULT_MR_HISTORY_JOBLIST_CACHE_DISPLAYED_SIZE);
- ...
- }
这个新配置的设计,就是实现前后端Job数量控制的关键一步,然后在用到详细的getJob的方法,实现逻辑例如以下:
- @Override
- public Map<JobId, Job> getPartialDisplayedJobs(String filterName) {
- LOG.debug("Called getPartialDisplayedJobs()");
- String jobName;
- int cacheJobSize = 0;
- SortedMap<JobId, Job> result = new TreeMap<JobId, Job>();
- try {
- for (HistoryFileInfo mi : hsManager.getAllFileInfo()) {
- if (mi != null) {
- cacheJobSize++;
- if (cacheJobSize > cacheDisplayedSize) {
- LOG.info("GetPartialDisplayedJobs operation ends"
- + ", AllFileInfo size is more than cacheDisplayedSize: "
- + cacheDisplayedSize);
- break;
- }
- JobId id = mi.getJobId();
- jobName = mi.getJobIndexInfo().getJobName();
- if (filterName == null || filterName.length() == 0) {
- result.put(id, new PartialJob(mi.getJobIndexInfo(), id));
- } else if (jobName != null && jobName.length() > 0) {
- if (jobName.contains(filterName)) {
- result.put(id, new PartialJob(mi.getJobIndexInfo(), id));
- }
- }
- }
- }
- } catch (IOException e) {
- LOG.warn("Error trying to scan for all FileInfos", e);
- throw new YarnRuntimeException(e);
- }
- return result;
- }
与原先的直接获取Job-cache方法相比,添加数量控制和名字条件筛选过滤.OK,后端代码部分的实现就是如此,以下是前端部分的修改.以下简单阐述一下:
新增參数:
- /**
- * Params constants for the AM webapp and the history webapp.
- */
- public interface AMParams {
- ....
- static final String JOBFILTER_NAME = "jobfilter.name";
- }
修改涉及到链接跳转部分的HsWebApp.java中的代码和导航栏中的链接,默认传入空字符串为Job名称过滤条件:
- route(pajoin("/app", JOBFILTER_NAME), HsController.class);
- li().a(url("app", ""), "Jobs")._()._();
最后在页面代码中更改appContext调用的方法,改为例如以下:
- /*
- * (non-Javadoc)
- * @see org.apache.hadoop.yarn.webapp.view.HtmlBlock#render(org.apache.hadoop.yarn.webapp.view.HtmlBlock.Block)
- */
- @Override protected void render(Block html) {
- ...
- String filterName = $(JOBFILTER_NAME);
- for (Job j : appContext.getDisplayedJobs(filterName).values()) {
- JobInfo job = new JobInfo(j);
- jobsTableData.append("[\"")
- .append(dateFormat.format(new Date(job.getSubmitTime()))).append("\",\"")
- .append(dateFormat.format(new Date(job.getStartTime()))).append("\",\"")
- .append(dateFormat.format(new Date(job.getFinishTime()))).append("\",\"")
- .append("<a href='").append(url("job", job.getId())).append("'>")
- .append(job.getId()).append("</a>\",\"")
- .append(StringEscapeUtils.escapeJavaScript(StringEscapeUtils.escapeHtml(
- job.getName()))).append("\",\"")
- ...
.OK,至此,智能化搜索第一步完毕,另外1个需求点就比較简单了,由于直接在页面上改造下就能够实现,写端js代码,构造一下链接就可以.
- String jobIdSearchClickMethod =
- "function jobsearch() {\n"+
- " var jobid = $('.jobid').val()\n"+
- " window.location ='/jobhistory/job/' + jobid\n"+
- "}\n";
最后加上相应的2个搜索button,和js部分代码嵌在页面中就可以.
- @Override protected void render(Block html) {
- TBODY<TABLE<Hamlet>> tbody = html.
- h2("Retired Jobs").
- table("#jobs").
- thead().
- tr().
- th()
- .$class("ui-state-default").input("jobid").$type(InputType.text)
- .$name("jobid").$value("input jobid")._()._().
- th()
- .input("search_confirm").$type(InputType.button).$name("search")
- .$value("Job Search").$onclick("jobsearch()")._()._()._().
- tr().
- th()
- .$class("ui-state-default").input("jobname").$type(InputType.text)
- .$name("jobname").$value("input filetername")._()._().
- th()
- .input("search_confirm").$type(InputType.button).$name("search")
- .$value("Name Search").$onclick("jobnamesearch()")._()._()._().
- tr().
- ....
- ....
- String jobIdSearchClickMethod =
- "function jobsearch() {\n"+
- " var jobid = $('.jobid').val()\n"+
- " window.location ='/jobhistory/job/' + jobid\n"+
- "}\n";
- String jobNameSearchClickMethod =
- "function jobnamesearch() {\n"+
- " var filtername = $('.jobname').val()\n"+
- " window.location ='/jobhistory/app/' + filtername\n"+
- "}\n";
- html.script().$type("text/javascript").
- _("var jobsTableData=" + jobsTableData + "\n" + jobIdSearchClickMethod
- + jobNameSearchClickMethod)._();
终于呈现的页面效果例如以下所看到的,表格左上角的2个搜索button就是新做的功能,页面看上去会难看一些,可是能用.
搜索功能測试
第一.指定jobid搜索測试
搜索确定后进入相应详情页:
这个链接就是我们用js代码拼装出来的.
第二.指定Job名称搜索
比方我这里已经执行了几个word count測试job.首先任意输入1个无关过滤条件hello(见浏览器链接):
得不到结果,正确.
然后输入word进行匹配:
得到2项记录,满足我们的要求.
前端页面Job个数的数量控制功能,我也測试过了,能够通过,在这里就不截图显示了,大家感兴趣的能够自行把这部分的代码改到自己的hadoop代码中,patch代码链接在下方显示.
相关链接
Github patch链接:https://github.com/linyiqun/open-source-patch/tree/master/mapreduce/MAPREDUCE-hsSearch
JobHistory搜索智能化的更多相关文章
- 如何使用GOOGLE高级搜索技巧
如何使用GOOGLE高级搜索技巧 一,GOOGLE简介 Google(www.google.com)是一个搜索引擎,由两个斯坦福大学博士生Larry Page与Sergey Brin于1998年9月发 ...
- GOOGLE搜索秘籍完全公开
一,GOOGLE简介 Google(www.google.com)是一个搜索引擎,由两个斯坦福大学博士生Larry Page与Sergey Brin于1998年9月发明,Google Inc. 于19 ...
- 百度和 Google 的搜索技术是一个量级吗?
著作权归作者所有. 商业转载请联系作者获得授权,非商业转载请注明出处. 作者:Kenny Chao 链接:http://www.zhihu.com/question/22447908/answer/2 ...
- 【转载】google搜索从入门到精通
原文地址:http://www.cnblogs.com/helloIT/articles/5095668.html /***************************************** ...
- 智能化CRM客户关系管理系统介绍一
智能化CRM客户关系管理系统介绍一 CRM客户关系管理的定义是:企业为提高核心竞争力,利用相应的信息技术以及互联网技术来协调企业与顾客间在销售.营销和服务上的交互,从而提升其管理方式,向客户提供创新式 ...
- MySQL(十)操纵表及全文本搜索
一.创建表 MySQL不仅用于表数据操作,还可以用来执行数据库和表的所有操作,包括表本身的创建和处理. 创建表一般有如下两种方式: ①使用具有交互式创建和管理表的工具: ②直接使用MySQL语句操纵表 ...
- GOOGLE高级搜索的秘籍
一.摘要 本文内容来源自互联网,全面的介绍Google搜索的各种功能和技巧. 二.GOOGLE简介 Google(http://www.google.com/)是一个搜索引擎,由两个斯坦福大学博士生L ...
- GOOGLE高级搜索技巧
前记: 我是完整的看完了.内容有点乱啊,自己没有时间整理,先放在自己的印象笔记里了.... 二,GOOGLE特色 GOOGLE支持多达132种语言,包括简体中文和繁体中文: GOOGLE网站只提 ...
- 使用 Google 高级搜索的一些技巧
一,GOOGLE简介 Google(www.google.com)是一个搜索引擎,由两个斯坦福大学博士生Larry Page与Sergey Brin于1998年9月发明,Google Inc. 于 ...
随机推荐
- iOS规范化时间格式,object-C计算指定时间与当前的时间差
object-c计算指定时间与当前的时间差 头文件(.h): #import <Foundation/Foundation.h> @interface LuDate : NSDate +( ...
- sql剪切数据
实际项目当中用到的案例,个人笔记. USE [CA-SM]GO/****** Object: StoredProcedure [dbo].[PG_SM_AddSum] Script Date: ...
- Java屏幕截图及剪裁
Java标准API中有个Robot类,该类可以实现屏幕截图,模拟鼠标键盘操作这些功能.这里只展示其屏幕截图. 截图的关键方法createScreenCapture(Rectangle rect) ,该 ...
- Android基础TOP6_3:Gally和ImageSwitcher实现画廊
结构: Activity: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...
- python生成动态个性二维码
1 安装工具2 生成普通二维码3 带图片的二维码4 动态 GIF 二维码5 在Python程序中使用 一.安装 首先在python环境下运行, 打开cmd进入python27 进入scripts 然后 ...
- IT项目为什么失败 --美国IT项目管理硕士笔记(一)
IT项目为什么失败 什么是项目 项目可以被看作任何一系列的活动和任务.这些活动和任务有一个特定目标需要在特定要求下完成,并有一个明确的开始结束日期和资金限制(如果有).项目需要消耗人力或非人力资源 ...
- TensorFlow车牌识别实践(1)
本文对公开的文章进行验证,从环境搭建到运行到结果分析. 1,文章:基于TensorFlow的车牌号识别系统 文章(译文) http://www.cnblogs.com/Jsmile2017/p/680 ...
- HDU_1006_Tick and Tick
Tick and Tick Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Tot ...
- CAD设置水印
主要用到函数说明: _DMxDrawX::Watermark 设置控件水印图片显示,字符串用逗号隔开,分为: “文件名,透明度,x方向距离,y方向距离,是否居中”, 是否居中0代表在上角定位,1表示自 ...
- Gym - 101670B Pond Cascade(CTU Open Contest 2017 贪心,二分)
题目: The cascade of water slides has been installed in the park recently and it has to be tested. The ...