Java客户端读取Hive 3.0任务进度(二)

上文说道,我们初步的思路是通过HIve的日志和yarn的接口获取任务进度。

可能用到的其他知识:

Hook介绍

Hive添加Jar

在实际的执行过程中,我遇到一个问题:在代码中写死的yarn的接口,如果yarn的服务地址发生改变怎么办?如果面对多个不同的yarn集群怎么办?可见我们需要动态的yarn的接口地址。

如何获取yarn集群的地址?

首先需要明白的是,Hive一定知道yarn集群地址。我突然想到Hive中提供了Hook的钩子(在Hive相应的阶段,执行对应的Jar包的方法)。通过Hook我们可以在项目执行之前,获取到任务的提交的yarn集群地址。然后在通过访问我们Java客户端提供的接口,将yarn集群的地址以JSON的格式发过来。

最终思路

  1. Java客户端提交任务给Hive集群,Hive执行Hook钩子,将yarn集群地址发送到Java客户端的接口。
  2. Java客户端获取到yarn集群地址,Java客户端读取Hive的日志,获取到任务id。
  3. 通过yarn集群地址和任务Id,由yarn的接口获取到相应的任务进度。

编码

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();
}

你可能感兴趣的:(大数据组件)