上文说道,我们初步的思路是通过HIve的日志和yarn的接口获取任务进度。
可能用到的其他知识:
Hook介绍
Hive添加Jar
在实际的执行过程中,我遇到一个问题:在代码中写死的yarn的接口,如果yarn的服务地址发生改变怎么办?如果面对多个不同的yarn集群怎么办?可见我们需要动态的yarn的接口地址。
首先需要明白的是,Hive一定知道yarn集群地址。我突然想到Hive中提供了Hook的钩子(在Hive相应的阶段,执行对应的Jar包的方法)。通过Hook我们可以在项目执行之前,获取到任务的提交的yarn集群地址。然后在通过访问我们Java客户端提供的接口,将yarn集群的地址以JSON的格式发过来。
Java客户端读取任务进度类
public class HiveTaskProgressServer {
private HiveStatement hiveStatement;
/**
* 停止线程的标志,true代表停止
*
*/
private Boolean isStop=false;
public HiveStatement getHiveStatement() {
return hiveStatement;
}
public void setHiveStatement(HiveStatement hiveStatement) {
this.hiveStatement = hiveStatement;
}
public Boolean getStop() {
return isStop;
}
public void setStop(Boolean stop) {
isStop = stop;
}
/**
* 功能描述:
* 检索日志是否输出appId
* @author twalk
* @date 2018/12/9 11:08 PM
* @param
* @return
*/
private String updateQueryLog() {
try {
List<String> queryLogs = hiveStatement.getQueryLog();
for (String log : queryLogs) {
System.out.println("进度信息-->"+log);
// 获取appId
if(log.indexOf("Status: Running (Executing on YARN cluster with App id")>-1){
String[] strings = log.split("application");
String appId = "application"+strings[strings.length-1].split("\\)")[0];
return appId;
}
}
}
catch (Exception e) {
System.out.println(e);
}
return null;
}
/**
* 功能描述:
* 异步获取hive任务的进度
* @author twalk
* @date 2018/12/10 12:06 AM
* @param taskId
* 任务的id
* @param taskType
* 任务的类型
* @return
*/
@Async(value = "asyncServiceExecutor")
public void runPrintHiveTaskProgressServer(String taskId,String taskType) {
this.isStop=false;
String reMark = null;
DataSynchroTask dataSynchroTask = null;
DataFusionTask dataFusionTask = null;
DataGovernanceTask dataGovernanceTask = null;
Double lastDataGovernanceTaskProgress = Double.valueOf(0);
TaskTypeEnum value=TaskTypeEnum.getTaskTypeEnumByIndex(Integer.parseInt(taskType));
// 等待/hiveserver2/task接受到hive发出的yarn地址,获取yarn地址(从数据库中获取)
while (reMark==null){
if(isStop==true) return;
dataTask = dataTaskDao.findByTaskId(taskId);
reMark = dataTask.getRemark();
System.out.println("我还没出去");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(dataGovernanceTask!=null){
lastDataGovernanceTaskProgress = dataGovernanceTask.getTaskState();
}
String appId = this.getAppIdStr();
if(appId!=null&&reMark!=null){
// yarn读取进度的接口
String url = "http://"+reMark+"/ws/v1/cluster/apps/"+appId;
while(true){
if(isStop==true) return;
// 从yarn地址处读取进度
JSONObject jsonObject = GetHtmlInform.GetHttpRequest(url);
Double progress = jsonObject.getJSONObject("app").getDouble("progress");
Boolean isContinue = true;
// 更新任务进度
isContinue = this.updataDataTaskProgress(taskId,progress);
if (!isContinue){
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 功能描述:
* 访问hive日志,获取yarn的appId
* @author twalk
* @date 2018/12/9 11:08 PM
* @param
* @return
*/
private String getAppIdStr(){
String appId = null;
try {
while (hiveStatement.hasMoreLogs()) {
String temp = updateQueryLog();
if(appId == null && temp!=null) {
appId = temp;
break;
}
Thread.sleep(1000);
}
}
catch (InterruptedException e) {
e.getStackTrace();
}
return appId;
}
}
Java客户端接受Hook的接口
@RestController
@RequestMapping("/hiveserver2")
public class Hiveserver2Controller {
/**
* 功能描述:
* 接收hive发出的任务信息
* @author twalk
* @date 2018/12/11 10:13 PM
* @param jsonStr
* @return
*/
@RequestMapping("/task")
public void task(String jsonStr) {
JSONObject jsonObject = JSONObject.parseObject(jsonStr);
// yarn集群地址
String yarnAddress = jsonObject.getString("yarnAddress");
// 任务在数据库中的主键
String taskId = jsonObject.getString("taskId");
// 写入数据库
// 设置yarn的地址
dataTask.setRemark(yarnAddress);
// 设置此时任务的状态
dataTask.setTaskState(stateNum);
}
}
Hive集群Hook的钩子
注意:
在获取yarn集群地址的过程中,我们需要将Java客户端中提交的任务主键与MapReduce任务id关联起来。所以我们在提交SQL时,可以在SQL的外层封装一层,如下:
select * from (
select count(*) from table1
) a where ‘big_data3_task_id_666666’=‘big_data3_task_id_666666’;
将任务id:666666放入其中,这样既不影响查询结果,同时又将数据库的主键id关联起来。
为了能动态的修改Java客户端的接口地址,我们最好将接口地址写入Hive的自定义配置中,这样可以省去每次修改接口后去修改代码,重新编译Jar包,然后在代码中读取自定义的Hive配置,如下:
//获取配置中的端口、接口
String postAndInf = (String)conf.getAllProperties().get("hiveserver.execute.hook.task");
public class HiveMyHook implements ExecuteWithHookContext {
@Override
public void run(HookContext hookContext) throws Exception {
HiveConf conf = new HiveConf(hookContext.getConf());
SessionState sessionState = SessionState.get();
QueryPlan plan = hookContext.getQueryPlan();
if(sessionState!=null&&conf!=null){
// 获取用户ip
String userIpAddress = sessionState.getUserIpAddress();
// 获取执行的sql
String queryStr = plan.getQueryStr().trim();
// 执行sql中携带的信息,如果没有则不是需要的sql
if (queryStr.indexOf("big_data3_task_id_")==-1){
return;
}
String[] stringArry = queryStr.split("big_data3_task_id_");
stringArry = stringArry[stringArry.length-1].split("'")[0].split("_");
// 获取到当前任务在数据库中的id
String taskId = stringArry[0];
// 获取hadoop的url
String yarnAddress = (String)conf.getAllProperties().get("yarn.resourcemanager.webapp.address");
//获取配置中的端口、接口
String postAndInf = (String)conf.getAllProperties().get("hiveserver.execute.hook.task");
// 访问的地址
String userUrl = "http://"+userIpAddress+":"+postAndInf;
Map<String,String> informMap = new LinkedHashMap<String, String>();
informMap.put("userUrl",userUrl);
informMap.put("yarnAddress",yarnAddress);
informMap.put("taskId",taskId);
JSONObject jsonObject = new JSONObject(informMap);
log(jsonObject.toString());
// 发送任务状态给客户端
try {
HttpSender.doPost(userUrl, "jsonStr="+jsonObject.toString());
} catch (Exception e) {
log("do post error: "
+ ExceptionUtils.getFullStackTrace(e));
}
}
}
// 在Hive的CLI模式下输出日志
private static void log(String Info) {
SessionState.LogHelper console = SessionState.getConsole();
if (console != null) {
console.printInfo(Info);
}
}
}
执行
// 执行读取进度线程
hiveTaskProgressServer.setHiveStatement((HiveStatement) statement);
hiveTaskProgressServer.runPrintHiveTaskProgressServer(taskId,taskType);
try {
System.out.println(sql);
this.statement.execute(sql);
DataTask dataTaskTemp = dataTaskDao.findByTaskId(dataTask.getTaskId());
// 任务执行成功,写入数据库
dataTaskTemp.setTaskState(Double.valueOf(100));
dataTaskDao.save(dataTaskTemp);
} catch (SQLException e) {
DataTask dataTaskTemp = dataTaskDao.findByTaskId(dataTask.getTaskId());
// 任务执行失败,写入数据库
dataTaskTemp.setTaskState(Double.valueOf(-1));
dataTaskDao.save(dataTaskTemp);
e.printStackTrace();
}