com.kylinolap.job.engine.JobEngine.java Source code

Java tutorial

Introduction

Here is the source code for com.kylinolap.job.engine.JobEngine.java

Source

/*
 * Copyright 2013-2014 eBay Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.kylinolap.job.engine;

/** 
 * @author George Song (ysong1), xduo
 * 
 */
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.math.stat.descriptive.rank.Percentile;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.framework.state.ConnectionStateListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.hadoop.hbase.util.Bytes;
import org.quartz.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.kylinolap.job.JobInstance;
import com.kylinolap.job.JobInstance.JobStep;
import com.kylinolap.job.constant.JobConstants;
import com.kylinolap.job.exception.JobException;

public class JobEngine implements ConnectionStateListener {

    private static Logger log = LoggerFactory.getLogger(JobEngine.class);

    private final String engineID;

    private final JobEngineConfig engineConfig;
    private final QuatzScheduler scheduler;
    private InterProcessMutex sharedLock;
    private CuratorFramework zkClient;

    private static final String ZOOKEEPER_LOCK_PATH = "/kylin/job_engine/lock";
    private static final ConcurrentHashMap<JobEngineConfig, JobEngine> CACHE = new ConcurrentHashMap<JobEngineConfig, JobEngine>();
    private int daemonJobIntervalInSeconds;

    public static JobEngine getInstance(String engineID, JobEngineConfig engineCfg) throws JobException {
        JobEngine r = CACHE.get(engineCfg);
        if (r == null) {
            r = new JobEngine(engineID, engineCfg);
            CACHE.putIfAbsent(engineCfg, r);
        }
        return r;
    }

    private JobEngine(String engineID, JobEngineConfig context) throws JobException {
        if (context.getZookeeperString() == null || context.getZookeeperString().equals("")) {
            throw new IllegalArgumentException("Zookeeper connection string is null or empty");
        }
        log.info("Using metadata url: " + context.getConfig());

        this.engineID = engineID;
        this.engineConfig = context;
        this.scheduler = new QuatzScheduler();

        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        this.zkClient = CuratorFrameworkFactory.newClient(context.getZookeeperString(), retryPolicy);
        this.zkClient.start();

        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                log.debug("Closing HBASE connection");
                releaseLock();
            }
        });
    }

    private void releaseLock() {
        try {
            if (sharedLock != null && sharedLock.isAcquiredInThisProcess()) {
                sharedLock.release();
            }
            if (zkClient.getState().equals(CuratorFrameworkState.STARTED)) {
                // client.setData().forPath(ZOOKEEPER_LOCK_PATH, null);
                if (zkClient.checkExists().forPath(ZOOKEEPER_LOCK_PATH + "/" + this.engineID) != null) {
                    zkClient.delete().guaranteed().deletingChildrenIfNeeded()
                            .forPath(ZOOKEEPER_LOCK_PATH + "/" + this.engineID);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void start(int daemonJobIntervalInSeconds) throws Exception {
        this.daemonJobIntervalInSeconds = daemonJobIntervalInSeconds;

        sharedLock = new InterProcessMutex(zkClient, ZOOKEEPER_LOCK_PATH + "/" + this.engineID);
        log.info("Trying to obtain the shared lock...");
        // current thread will be blocked until the lock is got
        sharedLock.acquire();

        log.info("Obtained the shared lock. Starting job scheduler...");
        zkClient.setData().forPath(ZOOKEEPER_LOCK_PATH + "/" + this.engineID, Bytes.toBytes(this.engineID));
        startScheduler();
    }

    private void startScheduler() throws JobException, IOException {
        // FIXME hard code
        new File("/tmp/kylin/logs/").mkdirs();

        log.info("Starting scheduler.");
        this.scheduler.start();
        this.scheduler.scheduleFetcher(this.daemonJobIntervalInSeconds, this.engineConfig);
    }

    public void start() throws Exception {
        start(JobConstants.DEFAULT_SCHEDULER_INTERVAL_SECONDS);
    }

    public void stop() throws JobException {
        releaseLock();
        this.scheduler.stop();
    }

    @Override
    public void stateChanged(CuratorFramework client, ConnectionState newState) {
        if ((newState == ConnectionState.SUSPENDED) || (newState == ConnectionState.LOST)) {
            releaseLock();
        }
    }

    public void interruptJob(JobInstance jobInstance, JobStep jobStep) throws IOException, JobException {
        // kill the running step
        this.scheduler.interrupt(jobInstance, jobStep);
    }

    public Scheduler getScheduler() {
        return this.scheduler.getScheduler();
    }

    // Job engine metrics related methods

    // <StepID, Duration Seconds>
    public static ConcurrentHashMap<String, Double> JOB_DURATION = new ConcurrentHashMap<String, Double>();

    public int getNumberOfJobStepsExecuted() {
        return JOB_DURATION.values().size();
    }

    public String getPrimaryEngineID() throws Exception {
        byte[] data = zkClient.getData().forPath(ZOOKEEPER_LOCK_PATH + "/" + this.engineID);
        if (data == null) {
            return "";
        } else {
            return Bytes.toString(data);
        }
    }

    public double getMinJobStepDuration() {
        double[] all = getJobStepDuration();
        Arrays.sort(all);

        if (all.length > 0) {
            return all[0];
        } else {
            return 0;
        }
    }

    private double[] getJobStepDuration() {
        Collection<Double> values = JOB_DURATION.values();
        Double[] all = (Double[]) values.toArray(new Double[values.size()]);
        return ArrayUtils.toPrimitive(all);
    }

    public double getMaxJobStepDuration() {
        double[] all = getJobStepDuration();
        Arrays.sort(all);

        if (all.length > 1) {
            return all[all.length - 1];
        } else {
            return 0;
        }
    }

    public double getPercentileJobStepDuration(double percentile) {
        Collection<Double> values = JOB_DURATION.values();
        Double[] all = (Double[]) values.toArray(new Double[values.size()]);
        Percentile p = new Percentile(percentile);
        return p.evaluate(ArrayUtils.toPrimitive(all));
    }

    public Integer getScheduledJobsSzie() {
        return scheduler.getScheduledJobs();
    }

    public int getEngineThreadPoolSize() {
        return scheduler.getThreadPoolSize();
    }

    public int getNumberOfIdleSlots() {
        return scheduler.getIdleSlots();
    }

    public int getNumberOfJobStepsRunning() {
        return scheduler.getRunningJobs();
    }

}