JStorm源码分析(四)深入解读Task概念与实现(草稿版)

由于Task实现了Runnable接口,所以可以肯定的是,Task将会由某个线程来执行其run()方法,这其中包含的核心逻辑如下:

public void run() {
        try {
            taskShutdownDameon = this.execute();
        } catch (Throwable e) {
            LOG.error("init task take error", e);
            if (reportErrorDie != null) {
                reportErrorDie.report(e);
            } else {
                throw new RuntimeException(e);
            }

        }
    }


run()把核心逻辑放在了execute()方法当中,自己只做了异常捕获和处理的工作。这里有必要提一下,由于run()方法本身无法抛出异常(RuntimeException除外),所以这里借助TaskReportErrorAndDie的实例来汇报异常。如果这个对象为null的话,那么直接抛出RuntimeException

execute()方法依次作了如下几件事:

public TaskShutdownDameon execute() throws Exception {

        taskSendTargets = echoToSystemBolt();

        // create thread to get tuple from zeroMQ,
        // and pass the tuple to bolt/spout
        taskTransfer = mkTaskSending(workerData);
        RunnableCallback baseExecutor = prepareExecutor();
        //set baseExecutors for update
        setBaseExecutors((BaseExecutors)baseExecutor);

        AsyncLoopThread executor_threads = new AsyncLoopThread(baseExecutor, false, Thread.MAX_PRIORITY, true);
        taskReceiver = mkTaskReceiver();

        List<AsyncLoopThread> allThreads = new ArrayList<AsyncLoopThread>();
        allThreads.add(executor_threads);

        LOG.info("Finished loading task " + componentId + ":" + taskId);

        taskShutdownDameon = getShutdown(allThreads, baseExecutor);
        return taskShutdownDameon;
    }


  •  获取该Task对应的目标Task的信息,当前Task从上游节点获得元组之后,将会把元组按需发送给目标Task,从而使下游节点获得元组;
  • 创建TaskTransfer实例,该实例用于发送元组;
  •  实例化Executor实例,该实例包含了用户实现的IRichSpoutIRichBolt实例,用于执行具体的计算逻辑。之后创建并启动一个AsyncLoopThread线程实例executor_threads来执行Executor
  • public BaseExecutors mkExecutor() {
            BaseExecutors baseExecutor = null;
    
            if (taskObj instanceof IBolt) {
                baseExecutor = new BoltExecutors(this);
            } else if (taskObj instanceof ISpout) {
                if (isSingleThread(stormConf) == true) {
                    baseExecutor = new SingleThreadSpoutExecutors(this);
                } else {
                    baseExecutor = new MultipleThreadSpoutExecutors(this);
                }
            }
    
            return baseExecutor;
        }


  •  创建TaskReceiver实例,该实例用于接收元组;
  •  如果用户出于某种原因想要停止任务,那么用户需要停止已经启动的executor_threads。为了达到这一目的,这里需要给用户提供TaskShutdownDameon实例。

在创建TaskTransfer实例时,除了需要提供当前任务的上下文信息之外,还需要提供一个KryoTupleSerializer,它通过Kyro序列化框架,对将要传输的元组进行序列化。

prepareExecutor()mkExecutor()方法则负责根据当前节点的类型创建不同的Executors,总共有三种:BoltExecutors, SingleThreadSpoutExecutors,  MultipleThreadSpoutExecutors

mkTaskReceiver()方法根据上下文信息创建一个TaskReceiver对象,并按照Task编号将其放到反序列化队列中。

public TaskReceiver mkTaskReceiver() {
        String taskName = JStormServerUtils.getName(componentId, taskId);
        //if (isTaskBatchTuple)
        //    taskReceiver = new TaskBatchReceiver(this, taskId, stormConf, topologyContext, innerTaskTransfer, taskStatus, taskName);
        //else
        taskReceiver = new TaskReceiver(this, taskId, stormConf, topologyContext, innerTaskTransfer, taskStatus, taskName);
        deserializeQueues.put(taskId, taskReceiver.getDeserializeQueue());

        return taskReceiver;
    }


echoToSystemBolt()方法将启动信息"startup"传递给了系统节点,告知当前任务已经启动。

public TaskReceiver mkTaskReceiver() {
        String taskName = JStormServerUtils.getName(componentId, taskId);
        //if (isTaskBatchTuple)
        //    taskReceiver = new TaskBatchReceiver(this, taskId, stormConf, topologyContext, innerTaskTransfer, taskStatus, taskName);
        //else
        taskReceiver = new TaskReceiver(this, taskId, stormConf, topologyContext, innerTaskTransfer, taskStatus, taskName);
        deserializeQueues.put(taskId, taskReceiver.getDeserializeQueue());

        return taskReceiver;
    }


getShutdown()方法则获取了当前任务中所有在运行的线程,包括负责进行消息确认的ackerThread,负责接收消息执行反序列化的recvThreads,负责序列化并发送消息的recvThreads等。

public TaskShutdownDameon getShutdown(List<AsyncLoopThread> allThreads, RunnableCallback baseExecutor) {
        AsyncLoopThread ackerThread = null;
        if (baseExecutor instanceof SpoutExecutors) {
            ackerThread = ((SpoutExecutors) baseExecutor).getAckerRunnableThread();

            if (ackerThread != null) {
                allThreads.add(ackerThread);
            }
        }
        List<AsyncLoopThread> recvThreads = taskReceiver.getDeserializeThread();
        for (AsyncLoopThread recvThread : recvThreads) {
            allThreads.add(recvThread);
        }

        List<AsyncLoopThread> serializeThreads = taskTransfer.getSerializeThreads();
        allThreads.addAll(serializeThreads);
        TaskHeartbeatTrigger taskHeartbeatTrigger = ((BaseExecutors) baseExecutor).getTaskHbTrigger();

        TaskShutdownDameon shutdown = new TaskShutdownDameon(taskStatus, topologyId, taskId, allThreads, zkCluster, taskObj, this, taskHeartbeatTrigger);

        return shutdown;
    }

了解了Task 的核心逻辑之后,我们再来看看 Task 是如何被构造出来的:

  •  构造方法获取基本的上下文信息之后,创建一个TaskReportErrorAndDie实例用于记录Task在创建和启动过程中发生的错误以及异常停止的情况,并注册一些ITaskHook钩子对象在用户上下文中。
  •  // create report error callback,
            // in fact it is storm_cluster.report-task-error
            ITaskReportErr reportError = new TaskReportError(zkCluster, topologyId, taskId);
    
            // report error and halt worker
            reportErrorDie = new TaskReportErrorAndDie(reportError, workHalt);
            this.taskStats = new TaskBaseMetric(topologyId, componentId, taskId);
            //register auto hook
            List<String> listHooks = Config.getTopologyAutoTaskHooks(stormConf);
            for (String hook : listHooks) {
                ITaskHook iTaskHook = (ITaskHook) Utils.newInstance(hook);
                userContext.addTaskHook(iTaskHook);
            }


  •  接着是最关键的步骤:从上下文中获取需要执行的计算节点taskObj,这个过程是一个反序列化过程,之后还会详细讲到。
  • LOG.info("Begin to deserialize taskObj " + componentId + ":" + this.taskId);
    
            try {
                WorkerClassLoader.switchThreadContext();
                this.taskObj = Common.get_task_object(topologyContext.getRawTopology(), componentId, WorkerClassLoader.getInstance());
                WorkerClassLoader.restoreThreadContext();
            } catch (Exception e) {
                if (reportErrorDie != null) {
                    reportErrorDie.report(e);
                } else {
                    throw e;
                }
            }


  •  最后,从配置项中判断一下当前的任务是否要执行成批次的BatchTuple,以此决定是否要启动批处理模式。
  • isTaskBatchTuple = ConfigExtension.isTaskBatchTuple(stormConf);
            LOG.info("Transfer/receive in batch mode :" + isTaskBatchTuple);


通过阅读Task的源代码之后,我们发现,Task由以下几个关键部件组成:

  • 要执行的计算节点taskObj,这通过将其实例化为不同的Executors对象,创建并启动线程执行来完成;
  • 负责将元组序列化并传送给其他节点的TaskTransfer实例,这个将在后面单独阐述;
  • 负责将接收消息并反序列元组的TaskReceiver实例,这个也将在后面单独阐述;
  • 一系列按照Task编号管理的DisruptorQueue实例,担当一个消息队列的作用,有关DisruptorQueue的内容将在后面单独阐述。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值