package com.vip.saturn.job.executor; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.concurrent.Executors; import com.vip.saturn.job.threads.SaturnThreadFactory; import org.apache.commons.lang3.StringUtils; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.cache.ChildData; import org.apache.curator.framework.recipes.cache.TreeCache; import org.apache.curator.framework.recipes.cache.TreeCacheEvent; import org.apache.curator.framework.recipes.cache.TreeCacheEvent.Type; import org.apache.curator.framework.recipes.cache.TreeCacheListener; import org.apache.curator.utils.CloseableExecutorService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Strings; import com.vip.saturn.job.exception.TimeDiffIntolerableException; import com.vip.saturn.job.internal.config.ConfigurationNode; import com.vip.saturn.job.internal.storage.JobNodePath; import com.vip.saturn.job.reg.base.CoordinatorRegistryCenter; import com.vip.saturn.job.utils.LocalHostService; import com.vip.saturn.job.utils.ResourceUtils; import com.vip.saturn.job.utils.SystemEnvProperties; /** * * @author xiaopeng.he * */ public class SaturnExecutorService { static Logger log = LoggerFactory.getLogger(SaturnExecutorService.class); private String executorName; private List<String> jobNames = new ArrayList<String>(); private CoordinatorRegistryCenter coordinatorRegistryCenter; private TreeCache $JobsTreeCache; private String ipNode; private ClassLoader jobClassLoader; private ClassLoader executorClassLoader; public static final int WAIT_JOBCLASS_ADDED_COUNT = 25; public SaturnExecutorService(CoordinatorRegistryCenter coordinatorRegistryCenter, String executorName) { this.coordinatorRegistryCenter = coordinatorRegistryCenter; this.executorName = executorName; if (coordinatorRegistryCenter != null) { coordinatorRegistryCenter.setExecutorName(executorName); } } /** * 获取该域下所有作业名 */ public List<String> registerJobNames() { jobNames.clear(); // be careful, coordinatorRegistryCenter.getChildrenKeys maybe return Collections.emptyList(), it's immutable jobNames.addAll(coordinatorRegistryCenter.getChildrenKeys("/" + JobNodePath.$JOBS_NODE_NAME)); return jobNames; } private void registerExecutor0() throws Exception { String executorNode = SaturnExecutorsNode.EXECUTORS_ROOT + "/" + executorName; ipNode = executorNode + "/ip"; String lastBeginTimeNode = executorNode + "/lastBeginTime"; String versionNode = executorNode + "/version"; String executorCleanNode = executorNode + "/clean"; String executorTaskNode = executorNode + "/task"; // 持久化最近启动时间 coordinatorRegistryCenter.persist(lastBeginTimeNode, String.valueOf(System.currentTimeMillis())); // 持久化版本 final Properties props = ResourceUtils.getResource("properties/saturn-core.properties"); if (props != null) { String executorVersion = props.getProperty("build.version"); if (!Strings.isNullOrEmpty(executorVersion)) { coordinatorRegistryCenter.persist(versionNode, executorVersion); } } // 持久化clean coordinatorRegistryCenter.persist(executorCleanNode, String.valueOf(SystemEnvProperties.VIP_SATURN_EXECUTOR_CLEAN)); // 持久task if (SystemEnvProperties.VIP_SATURN_DCOS_TASK != null) { coordinatorRegistryCenter.persist(executorTaskNode, SystemEnvProperties.VIP_SATURN_DCOS_TASK); } coordinatorRegistryCenter.persistEphemeral(ipNode, LocalHostService.cachedIpAddress); } public void reRegister() throws Exception { registerJobNames(); registerExecutor0(); } public void registerExecutor() throws Exception { checkExecutor(); registerExecutor0(); } /** * 启动前先检查本机与注册中心的时间误差秒数是否在允许范围和Executor是否已启用 */ private void checkExecutor() throws Exception { // 启动时检查本机与注册中心的时间误差秒数是否在允许范围 String executorNode = SaturnExecutorsNode.EXECUTORS_ROOT + "/" + executorName; try { long timeDiff = Math.abs(System.currentTimeMillis() - coordinatorRegistryCenter.getRegistryCenterTime(executorNode + "/systemTime/current")); int maxTimeDiffSeconds = 60; if (timeDiff > maxTimeDiffSeconds * 1000L) { Long timeDiffSeconds = Long.valueOf(timeDiff / 1000); throw new TimeDiffIntolerableException(timeDiffSeconds.intValue(), maxTimeDiffSeconds); } } finally { String executorSystemTimePath = executorNode + "/systemTime"; if (coordinatorRegistryCenter.isExisted(executorSystemTimePath)) { coordinatorRegistryCenter.remove(executorSystemTimePath); } } // 启动时检查Executor是否已启用(ExecutorName为判断的唯一标识) if (coordinatorRegistryCenter.isExisted(executorNode)) { if (coordinatorRegistryCenter.isExisted(executorNode + "/ip")) { // is running throw new Exception( "The executor name(" + executorName + ") is running, cannot running the instance twice."); } } else { coordinatorRegistryCenter.persist(executorNode, ""); } } private TreeCache buildAndStart$JobsTreeCache(CuratorFramework client) throws Exception { TreeCache tc = TreeCache.newBuilder(client, "/" + JobNodePath.$JOBS_NODE_NAME) .setExecutor(new CloseableExecutorService(Executors.newSingleThreadExecutor(new SaturnThreadFactory(executorName + "-$Jobs-watcher", false)), true)) .setMaxDepth(1) .build(); tc.start(); return tc; } public void addNewJobListenerCallback(final ScheduleNewJobCallback callback) throws Exception { $JobsTreeCache = buildAndStart$JobsTreeCache((CuratorFramework) coordinatorRegistryCenter.getRawClient()); $JobsTreeCache.getListenable().addListener(new TreeCacheListener() { @Override public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception { if (event != null) { Type type = event.getType(); ChildData data = event.getData(); if (type != null && data != null) { String path = data.getPath(); if (path != null && !path.equals("/" + JobNodePath.$JOBS_NODE_NAME)) { if (type.equals(Type.NODE_ADDED)) { // add a job String newJobName = StringUtils.substringAfterLast(path, "/"); String jobClassPath = JobNodePath.getNodeFullPath(newJobName, ConfigurationNode.JOB_CLASS); // wait 5 seconds at most until jobClass created. for (int i = 0; i < WAIT_JOBCLASS_ADDED_COUNT; i ++) { if (client.checkExists().forPath(jobClassPath) == null) { Thread.sleep(200); } else { log.info("new job: {} 's jobClass created event received.", newJobName); if (!jobNames.contains(newJobName)) { jobNames.add(newJobName); callback.call(newJobName); } break; } } } } } } } }); } public void removeJobName(String jobName) { if (jobNames.contains(jobName)) { jobNames.remove(jobName); } } private void removeIpNode() { try { if (coordinatorRegistryCenter != null && ipNode != null && coordinatorRegistryCenter.isConnected()) { log.info(" {} is going to delete its ip node {}", executorName, ipNode); coordinatorRegistryCenter.remove(ipNode); } } catch (Throwable t) { log.error(t.getMessage(), t); } } private void close$JobsTreeCache() { try { if ($JobsTreeCache != null) { $JobsTreeCache.close(); } } catch (Throwable t) { log.error(t.getMessage(), t); } } // Attention, catch Throwable and not throw it. public void shutdown() { removeIpNode(); close$JobsTreeCache(); } public CoordinatorRegistryCenter getCoordinatorRegistryCenter() { return coordinatorRegistryCenter; } public String getIpNode() { return ipNode; } public ClassLoader getJobClassLoader() { return jobClassLoader; } public void setJobClassLoader(ClassLoader jobClassLoader) { this.jobClassLoader = jobClassLoader; } public ClassLoader getExecutorClassLoader() { return executorClassLoader; } public void setExecutorClassLoader(ClassLoader executorClassLoader) { this.executorClassLoader = executorClassLoader; } public String getExecutorName() { return executorName; } public List<String> getJobNames() { return jobNames; } public void setJobNames(List<String> jobNames) { this.jobNames = jobNames; } public TreeCache get$JobsTreeCache() { return $JobsTreeCache; } public void set$JobsTreeCache(TreeCache $JobsTreeCache) { this.$JobsTreeCache = $JobsTreeCache; } public void setExecutorName(String executorName) { this.executorName = executorName; } public void setCoordinatorRegistryCenter(CoordinatorRegistryCenter coordinatorRegistryCenter) { this.coordinatorRegistryCenter = coordinatorRegistryCenter; } public void setIpNode(String ipNode) { this.ipNode = ipNode; } }