前言

做过hadoop集群问题排查工作的同学一定用过JobHistory,这是一个非常好用的"利器",为什么这么说呢?正如这个工具的名称所叫的那样,这个工具能帮你找到历史Job跑过的信息,而信息的记录非常的详细,从Job到Task再到TaskAttempt.假如这时候,1个Job突然执行失败了,你想查明原因,在JobHistory的web界面上依次点击详情链接,基本上都能够找到原因.可是看似非常完美的Job分析工具,也有很多使用起来不是非常方便的地方,于是乎,我们想对此进行一些改进,使其更加易用,同一时候相信能给相同在使用jobHistory的人提供帮助.

现有JobHistory的不足之处

从開始使用这个分析工具到如今,1个让我一直用着特别不爽的地方是,非常难迅速查到历史时间稍稍远一些的Job信息,由于有的时候我要做同一时候段的Job执行情况对照,包含执行时长,Task失败次数等等指标.所以你须要把昨天,前天的数据拉出来.然后非常多人的正常反应就是把Jobhistory页面上默认显示的Job条数加大,以此显示更长时间的Job.这种方法既简单又方便,通过更改以下这个配置项,并重新启动JobHistory就能够立刻做到:

  1. <property>
  2. <name>mapreduce.jobhistory.joblist.cache.size</name>
  3. <value>20000</value>
  4. <description>Size of the job list cache</description>
  5. </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方法中实现的

  1. /*
  2. * (non-Javadoc)
  3. * @see org.apache.hadoop.yarn.webapp.view.HtmlBlock#render(org.apache.hadoop.yarn.webapp.view.HtmlBlock.Block)
  4. */
  5. @Override protected void render(Block html) {
  6. TBODY<TABLE<Hamlet>> tbody = html.
  7. h2("Retired Jobs").
  8. table("#jobs").
  9. thead().
  10. tr().
  11. th("Submit Time").
  12. th("Start Time").
  13. th("Finish Time").
  14. th(".id", "Job ID").
  15. th(".name", "Name").
  16. th("User").
  17. th("Queue").
  18. th(".state", "State").
  19. th("Maps Total").
  20. th("Maps Completed").
  21. th("Reduces Total").
  22. th("Reduces Completed").
  23. th("Elapsed Time")._()._().
  24. tbody();
  25. LOG.info("Getting list of all Jobs.");
  26. // Write all the data into a JavaScript array of arrays for JQuery
  27. // DataTables to display
  28. StringBuilder jobsTableData = new StringBuilder("[\n");
  29. for (Job j : appContext.getAllJobs().values()) {
  30. JobInfo job = new JobInfo(j);
  31. jobsTableData.append("[\"")
  32. .append(dateFormat.format(new Date(job.getSubmitTime()))).append("\",\"")
  33. .append(dateFormat.format(new Date(job.getStartTime()))).append("\",\"")
  34. .append(dateFormat.format(new Date(job.getFinishTime()))).append("\",\"")
  35. .append("<a href='").append(url("job", job.getId())).append("'>")
  36. .append(job.getId()).append("</a>\",\"")
  37. .append(StringEscapeUtils.escapeJavaScript(StringEscapeUtils.escapeHtml(
  38. job.getName()))).append("\",\"")
  39. .append(StringEscapeUtils.escapeJavaScript(StringEscapeUtils.escapeHtml(
  40. job.getUserName()))).append("\",\"")
  41. .append(StringEscapeUtils.escapeJavaScript(StringEscapeUtils.escapeHtml(
  42. job.getQueueName()))).append("\",\"")
  43. .append(job.getState()).append("\",\"")
  44. .append(String.valueOf(job.getMapsTotal())).append("\",\"")
  45. .append(String.valueOf(job.getMapsCompleted())).append("\",\"")
  46. .append(String.valueOf(job.getReducesTotal())).append("\",\"")
  47. .append(String.valueOf(job.getReducesCompleted())).append("\",\"")
  48. .append(
  49. StringUtils.formatTimeSortable(Times.elapsed(job.getStartTime(),
  50. job.getFinishTime(), false))).append("\"],\n");
  51. }

其中获取历史Job信息的方法就是for循环中出现的appContext.getAllJobs方法.这种方法就会触发后端服务的获取Job信息方法.可是appContext是一个基础类,我们要找到相应的详细实现类,appContext的继承关系例如以下:

非常显然,方法在JobHistory这个类中.会调用到以下的方法:

  1. @Override
  2. public Map<JobId, Job> getAllJobs() {
  3. return storage.getAllPartialJobs();
  4. }

终于会调用到这种方法:

  1. @Override
  2. public Map<JobId, Job> getAllPartialJobs() {
  3. LOG.debug("Called getAllPartialJobs()");
  4. SortedMap<JobId, Job> result = new TreeMap<JobId, Job>();
  5. try {
  6. for (HistoryFileInfo mi : hsManager.getAllFileInfo()) {
  7. if (mi != null) {
  8. JobId id = mi.getJobId();
  9. result.put(id, new PartialJob(mi.getJobIndexInfo(), id));
  10. }
  11. }
  12. } catch (IOException e) {
  13. LOG.warn("Error trying to scan for all FileInfos", e);
  14. throw new YarnRuntimeException(e);
  15. }
  16. return result;
  17. }

而全部关于Job执行信息的带.jhist后缀的文件都是由HistoryFileManager这个类所控制的,也就是上述代码中的hsManager.他会返回以下的对象信息:

  1. public Collection<HistoryFileInfo> getAllFileInfo() throws IOException {
  2. scanIntermediateDirectory();
  3. return jobListCache.values();
  4. }

所以终于返回到前端的信息记录就是jobListCache这个对象.这个对象在初始化的时候就会首先设置cache的大小,就是文章前面提到的那个配置项:

  1. protected JobListCache createJobListCache() {
  2. return new JobListCache(conf.getInt(
  3. JHAdminConfig.MR_HISTORY_JOBLIST_CACHE_SIZE,
  4. JHAdminConfig.DEFAULT_MR_HISTORY_JOBLIST_CACHE_SIZE), maxHistoryAge);
  5. }

所以说我们必须把这个配置项分离,使前后端的Job信息控制隔离开.大体流程思路清楚之后,我们又一次回到之前的提到的需求点,要实现Job名过滤,所以我们要添加新的接口.我们能够模仿现有的getAllJobs方法,然后在造出1个带參数的getDisplayedJobs(String fileterName).首先你须要在appContext中新增接口定义:

  1. Map<JobId, Job> getDisplayedJobs(String filterName);

然后给出默认实现,在他的全部继承类中,開始时直接返回null就可以.然后在HistoryStorage.java中新增相似接口,就是须要被JobHistory调用的方法:

  1. /**
  2. * Get partial displayed of the cached jobs.
  3. * @param filterName the filter job name
  4. * @return all of the cached jobs
  5. */
  6. Map<JobId, Job> getPartialDisplayedJobs(String filterName);

然后在CachedHistoryStorage给予实现,可是在实现之前,还须要对显示数目做控制,不能在一味的显示全部cache中的Job,所以得在此类中新增配置项,姑且叫做以下的名称

  1. <property>
  2. <name>mapreduce.jobhistory.joblist.cache-displayed.size</name>
  3. <value>1000</value>
  4. <description>The size of job-list cache displayed in the jobHistory web ui.
  5. </description>
  6. </property>

然后初始化此配置项到变量中

  1. @SuppressWarnings("serial")
  2. private void createLoadedJobCache(Configuration conf) {
  3. ...
  4.  
  5. cacheDisplayedSize =
  6. conf.getInt(JHAdminConfig.MR_HISTORY_JOBLIST_CACHE_DISPLAYED_SIZE,
  7. JHAdminConfig.DEFAULT_MR_HISTORY_JOBLIST_CACHE_DISPLAYED_SIZE);
  8.  
  9. ...
  10. }

这个新配置的设计,就是实现前后端Job数量控制的关键一步,然后在用到详细的getJob的方法,实现逻辑例如以下:

  1. @Override
  2. public Map<JobId, Job> getPartialDisplayedJobs(String filterName) {
  3. LOG.debug("Called getPartialDisplayedJobs()");
  4. String jobName;
  5. int cacheJobSize = 0;
  6.  
  7. SortedMap<JobId, Job> result = new TreeMap<JobId, Job>();
  8. try {
  9. for (HistoryFileInfo mi : hsManager.getAllFileInfo()) {
  10. if (mi != null) {
  11. cacheJobSize++;
  12. if (cacheJobSize > cacheDisplayedSize) {
  13. LOG.info("GetPartialDisplayedJobs operation ends"
  14. + ", AllFileInfo size is more than cacheDisplayedSize: "
  15. + cacheDisplayedSize);
  16. break;
  17. }
  18.  
  19. JobId id = mi.getJobId();
  20. jobName = mi.getJobIndexInfo().getJobName();
  21. if (filterName == null || filterName.length() == 0) {
  22. result.put(id, new PartialJob(mi.getJobIndexInfo(), id));
  23. } else if (jobName != null && jobName.length() > 0) {
  24. if (jobName.contains(filterName)) {
  25. result.put(id, new PartialJob(mi.getJobIndexInfo(), id));
  26. }
  27. }
  28. }
  29. }
  30. } catch (IOException e) {
  31. LOG.warn("Error trying to scan for all FileInfos", e);
  32. throw new YarnRuntimeException(e);
  33. }
  34. return result;
  35. }

与原先的直接获取Job-cache方法相比,添加数量控制和名字条件筛选过滤.OK,后端代码部分的实现就是如此,以下是前端部分的修改.以下简单阐述一下:

新增參数:

  1. /**
  2. * Params constants for the AM webapp and the history webapp.
  3. */
  4. public interface AMParams {
  5. ....
  6. static final String JOBFILTER_NAME = "jobfilter.name";
  7. }

修改涉及到链接跳转部分的HsWebApp.java中的代码和导航栏中的链接,默认传入空字符串为Job名称过滤条件:

  1. route(pajoin("/app", JOBFILTER_NAME), HsController.class);
  1. li().a(url("app", ""), "Jobs")._()._();

最后在页面代码中更改appContext调用的方法,改为例如以下:

  1. /*
  2. * (non-Javadoc)
  3. * @see org.apache.hadoop.yarn.webapp.view.HtmlBlock#render(org.apache.hadoop.yarn.webapp.view.HtmlBlock.Block)
  4. */
  5. @Override protected void render(Block html) {
  6. ...
  7. String filterName = $(JOBFILTER_NAME);
  8. for (Job j : appContext.getDisplayedJobs(filterName).values()) {
  9. JobInfo job = new JobInfo(j);
  10. jobsTableData.append("[\"")
  11. .append(dateFormat.format(new Date(job.getSubmitTime()))).append("\",\"")
  12. .append(dateFormat.format(new Date(job.getStartTime()))).append("\",\"")
  13. .append(dateFormat.format(new Date(job.getFinishTime()))).append("\",\"")
  14. .append("<a href='").append(url("job", job.getId())).append("'>")
  15. .append(job.getId()).append("</a>\",\"")
  16. .append(StringEscapeUtils.escapeJavaScript(StringEscapeUtils.escapeHtml(
  17. job.getName()))).append("\",\"")
  18. ...

.OK,至此,智能化搜索第一步完毕,另外1个需求点就比較简单了,由于直接在页面上改造下就能够实现,写端js代码,构造一下链接就可以.

  1. String jobIdSearchClickMethod =
  2. "function jobsearch() {\n"+
  3. " var jobid = $('.jobid').val()\n"+
  4. " window.location ='/jobhistory/job/' + jobid\n"+
  5. "}\n";

最后加上相应的2个搜索button,和js部分代码嵌在页面中就可以.

  1. @Override protected void render(Block html) {
  2. TBODY<TABLE<Hamlet>> tbody = html.
  3. h2("Retired Jobs").
  4. table("#jobs").
  5. thead().
  6. tr().
  7. th()
  8. .$class("ui-state-default").input("jobid").$type(InputType.text)
  9. .$name("jobid").$value("input jobid")._()._().
  10. th()
  11. .input("search_confirm").$type(InputType.button).$name("search")
  12. .$value("Job Search").$onclick("jobsearch()")._()._()._().
  13. tr().
  14. th()
  15. .$class("ui-state-default").input("jobname").$type(InputType.text)
  16. .$name("jobname").$value("input filetername")._()._().
  17. th()
  18. .input("search_confirm").$type(InputType.button).$name("search")
  19. .$value("Name Search").$onclick("jobnamesearch()")._()._()._().
  20. tr().
  21. ....
  1. ....
  2. String jobIdSearchClickMethod =
  3. "function jobsearch() {\n"+
  4. " var jobid = $('.jobid').val()\n"+
  5. " window.location ='/jobhistory/job/' + jobid\n"+
  6. "}\n";
  7.  
  8. String jobNameSearchClickMethod =
  9. "function jobnamesearch() {\n"+
  10. " var filtername = $('.jobname').val()\n"+
  11. " window.location ='/jobhistory/app/' + filtername\n"+
  12. "}\n";
  13. html.script().$type("text/javascript").
  14. _("var jobsTableData=" + jobsTableData + "\n" + jobIdSearchClickMethod
  15. + 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搜索智能化的更多相关文章

  1. 如何使用GOOGLE高级搜索技巧

    如何使用GOOGLE高级搜索技巧 一,GOOGLE简介 Google(www.google.com)是一个搜索引擎,由两个斯坦福大学博士生Larry Page与Sergey Brin于1998年9月发 ...

  2. GOOGLE搜索秘籍完全公开

    一,GOOGLE简介 Google(www.google.com)是一个搜索引擎,由两个斯坦福大学博士生Larry Page与Sergey Brin于1998年9月发明,Google Inc. 于19 ...

  3. 百度和 Google 的搜索技术是一个量级吗?

    著作权归作者所有. 商业转载请联系作者获得授权,非商业转载请注明出处. 作者:Kenny Chao 链接:http://www.zhihu.com/question/22447908/answer/2 ...

  4. 【转载】google搜索从入门到精通

    原文地址:http://www.cnblogs.com/helloIT/articles/5095668.html /***************************************** ...

  5. 智能化CRM客户关系管理系统介绍一

    智能化CRM客户关系管理系统介绍一 CRM客户关系管理的定义是:企业为提高核心竞争力,利用相应的信息技术以及互联网技术来协调企业与顾客间在销售.营销和服务上的交互,从而提升其管理方式,向客户提供创新式 ...

  6. MySQL(十)操纵表及全文本搜索

    一.创建表 MySQL不仅用于表数据操作,还可以用来执行数据库和表的所有操作,包括表本身的创建和处理. 创建表一般有如下两种方式: ①使用具有交互式创建和管理表的工具: ②直接使用MySQL语句操纵表 ...

  7. GOOGLE高级搜索的秘籍

    一.摘要 本文内容来源自互联网,全面的介绍Google搜索的各种功能和技巧. 二.GOOGLE简介 Google(http://www.google.com/)是一个搜索引擎,由两个斯坦福大学博士生L ...

  8. GOOGLE高级搜索技巧

    前记:  我是完整的看完了.内容有点乱啊,自己没有时间整理,先放在自己的印象笔记里了....   二,GOOGLE特色 GOOGLE支持多达132种语言,包括简体中文和繁体中文: GOOGLE网站只提 ...

  9. 使用 Google 高级搜索的一些技巧

      一,GOOGLE简介 Google(www.google.com)是一个搜索引擎,由两个斯坦福大学博士生Larry Page与Sergey Brin于1998年9月发明,Google Inc. 于 ...

随机推荐

  1. iOS规范化时间格式,object-C计算指定时间与当前的时间差

    object-c计算指定时间与当前的时间差 头文件(.h): #import <Foundation/Foundation.h> @interface LuDate : NSDate +( ...

  2. sql剪切数据

    实际项目当中用到的案例,个人笔记. USE [CA-SM]GO/****** Object:  StoredProcedure [dbo].[PG_SM_AddSum]    Script Date: ...

  3. Java屏幕截图及剪裁

    Java标准API中有个Robot类,该类可以实现屏幕截图,模拟鼠标键盘操作这些功能.这里只展示其屏幕截图. 截图的关键方法createScreenCapture(Rectangle rect) ,该 ...

  4. Android基础TOP6_3:Gally和ImageSwitcher实现画廊

    结构: Activity: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

  5. python生成动态个性二维码

    1 安装工具2 生成普通二维码3 带图片的二维码4 动态 GIF 二维码5 在Python程序中使用 一.安装 首先在python环境下运行, 打开cmd进入python27 进入scripts 然后 ...

  6. IT项目为什么失败 --美国IT项目管理硕士笔记(一)

    IT项目为什么失败 什么是项目   项目可以被看作任何一系列的活动和任务.这些活动和任务有一个特定目标需要在特定要求下完成,并有一个明确的开始结束日期和资金限制(如果有).项目需要消耗人力或非人力资源 ...

  7. TensorFlow车牌识别实践(1)

    本文对公开的文章进行验证,从环境搭建到运行到结果分析. 1,文章:基于TensorFlow的车牌号识别系统 文章(译文) http://www.cnblogs.com/Jsmile2017/p/680 ...

  8. HDU_1006_Tick and Tick

    Tick and Tick Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Tot ...

  9. CAD设置水印

    主要用到函数说明: _DMxDrawX::Watermark 设置控件水印图片显示,字符串用逗号隔开,分为: “文件名,透明度,x方向距离,y方向距离,是否居中”, 是否居中0代表在上角定位,1表示自 ...

  10. 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 ...