線程池進行分佈式多任務並行處理模板代碼

場景:多個任務提交到redis的list中,多機器部署,每臺機器都是在啓動項目的時候啓動這個從redis的list中pop任務進行處理,如果redis的list中等待timeout=2s沒有數據就會再次去list中去取task;任務處理比較耗時,大概將近60s處理完畢

  1. 基於redis的分佈式任務提交處理的線程池
public abstract class AbstractDistributedTaskHandler extends ThreadPoolExecutor {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    //根據自己的redis進行相應的調整
    private ListCommands listCommands;
    private HashCommands hashCommands;
    private boolean running;
    private final HashMap<Integer, ProjectFutureTask> tasks = new HashMap<>(100);
    private TimeUnit taskTimeoutTimeUnit = TimeUnit.MINUTES;
    private long taskTimeout = 30;
    
    public AbstractDistributedTaskHandler() {
        this(5, "task");
    }

    /**
     * 構造當前節點線程池
     *
     * @param poolSize 用於提交用戶的線程池大小,另外增加2個內部線程用於提交和檢測超時
     */
    public AbstractDistributedTaskHandler(int poolSize, String poolThreadName) {
        super(poolSize + 2, poolSize + 2, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100), new NamedThreadFactory(poolThreadName), new AbortPolicy());
    }

    /**
     * 初始化方法
     */
    public final void init() {
        listCommands = RedisFactory.getClusterListCommands(getRedisGroupName());
        hashCommands = RedisFactory.getClusterHashCommands(getRedisGroupName());
    }

    /**
     * 開始啓動接收分佈式線程
     *
     * @param taskTimeoutTimeUnit 每個線程超時時間單位
     * @param taskTimeout         每個線程超時時間
     */
    public final void start(TimeUnit taskTimeoutTimeUnit, long taskTimeout) {
        this.taskTimeoutTimeUnit = taskTimeoutTimeUnit;
        this.taskTimeout = taskTimeout;
        this.running = true;
        execute(() -> fetchNewTask());
        execute(() -> checkTaskTimeout());
    }

    @Override
    public void shutdown() {
        running = false;
        super.shutdown();
    }

    /**
     * 開始從redis獲取任務並提交到線程池
     */
    private void fetchNewTask() {
        while (running) {
            if (getActiveCount() >= getCorePoolSize()) {
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                continue;
            }
            Task task = listCommands.rightPop(getRedisNamespace(), getTaskQueueRedisKey(), 2, TimeUnit.SECONDS, true);
            if (task != null) {
                ProjectFutureTask future = new ProjectFutureTask(task);
                execute(future);
                tasks.put(task.getTaskId(), future);
            }
        }
    }

    /**
     * 開始線程池中的線程超時檢查
     */
    private void checkTaskTimeout() {
        while (running) {
            Iterator<Map.Entry<Integer, ProjectFutureTask>> it = tasks.entrySet().iterator();
            while (it.hasNext()) {
                ProjectFutureTask futureTask = it.next().getValue();
                if (futureTask.isDone() || futureTask.isCancelled()) {
                    it.remove();
                } else if (System.currentTimeMillis() - futureTask.getStartTime() > taskTimeoutTimeUnit.toMillis(taskTimeout)) {
                    logger.warn("force cancel task: {}", futureTask.getTask());
                    futureTask.cancel(true);
                }
            }
            try {
                TimeUnit.MINUTES.sleep(1);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    /**
     * 封裝的提交線程池的FutureTask
     */
    private class ProjectFutureTask extends FutureTask {

        private Task task;

        private long startTime = System.currentTimeMillis();

        Task getTask() {
            return task;
        }

        public long getStartTime() {
            return startTime;
        }

        public ProjectFutureTask(Task task) {
            super(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    return taskHandler(task);
                }
            });
            this.task = task;
        }
    }

    /**
     * 獲取記錄任務信息的redis key名稱
     *
     * @param taskId 任務ID
     * @return redis key名稱
     */
    public static final String getTaskRedisKey(Integer taskId) {
        return String.format("project:tasks:flinktasks:%s", taskId);
    }

    /**
     * 獲取任務隊列的redis key名稱
     *
     * @return redis key名稱
     */
    private String getTaskQueueRedisKey() {
        return String.format("project:tasks:%s", getRedisTaskQueueName());
    }

    /**
     * 提交到任務,分佈式提交,不一定當前節點執行該任務
     *
     * @param task 任務
     */
    public final void submitTask(Task task) {
        String taskKey = getTaskRedisKey(task.getTaskId());
        Boolean succeed = hashCommands.putIfAbsent(getRedisNamespace(), taskKey, "taskId", task.getTaskId(), true);
        if (!succeed) {
            throw new TaskExistException(String.format("task: %s exist", task.getTaskId()));
        }
        Map<String, Object> map = new HashMap<>(10);
        map.put("taskId", task.getTaskId());
        map.put("taskName", task.getTaskName());
        map.put("taskStatus", task.getActionEnum().getInitial());
        map.put("taskSubmitTime", System.currentTimeMillis());
        long keyExpire = 12;
        hashCommands.expire(getRedisNamespace(), taskKey, keyExpire, TimeUnit.HOURS, true);
        hashCommands.putAll(getRedisNamespace(), taskKey, map, true);
        String taskQueueKey = getTaskQueueRedisKey();
        listCommands.leftPush(true, getRedisNamespace(), taskQueueKey, task);
        listCommands.expire(getRedisNamespace(), taskQueueKey, keyExpire, TimeUnit.HOURS, true);
        logger.info("submit task: {}", task);
    }

    /**
     * 獲取在排隊等待執行的任務,不包括正在執行中的
     *
     * @return
     */
    public final List<Task> getQueueTasks() {
        return listCommands.range(getRedisNamespace(), getTaskQueueRedisKey(), 0, -1, true);
    }

    /**
     * 獲取任務的信息和執行狀態
     *
     * @param taskIds
     * @return
     */
    public final Map<Integer, Integer> getTaskStatus(List<Integer> taskIds) {
        List<String> taskKeys = new ArrayList<>();
        for (Integer taskId : taskIds) {
            taskKeys.add(getTaskRedisKey(taskId));
        }
        Map<Integer, Integer> map = new HashMap<>(50);
        List<Map<String, Object>> entries = hashCommands.multiEntries(getRedisNamespace(), taskKeys, true, false);
        if (entries != null) {
            for (Map<String, Object> entry : entries) {
                if (entry.isEmpty()) {
                    continue;
                }
                map.put((Integer) entry.get("taskId"), (Integer) entry.get("taskStatus"));
            }
        }
        return map;
    }

    /**
     * 獲取任務的信息和執行狀態
     *
     * @param taskId
     * @return
     */
    public final Integer getTaskStatus(Integer taskId) {
        String taskKey = getTaskRedisKey(taskId);
        return hashCommands.get(getRedisNamespace(), taskKey, "taskStatus", true);
    }

    /**
     * 取消任務
     *
     * @param task  任務
     * @param force 是否取消正在執行的
     */
    public final void cancelTask(Task task, boolean force) {
        String taskKey = getTaskRedisKey(task.getTaskId());
        if (!hashCommands.persist(getRedisNamespace(), taskKey, true)) {
            throw new TaskNotExistException(String.format("task: %s not exist", task.getTaskId()));
        }
        hashCommands.delete(getRedisNamespace(), taskKey, true);
        listCommands.remove(getRedisNamespace(), getTaskQueueRedisKey(), 0, task, true);
        if (force && tasks.containsKey(task.getTaskId())) {
            tasks.get(task.getTaskId()).cancel(true);
        }
    }

    /**
     * 任務線程開始執行前
     *
     * @param t
     * @param r
     */
    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        if (!(r instanceof ProjectFutureTask)) {
            return;
        }
        ProjectFutureTask futureTask = (ProjectFutureTask) r;
        logger.debug("beforeExecute task: {}", futureTask.getTask());
        String taskKey = getTaskRedisKey(futureTask.getTask().getTaskId());
        hashCommands.put(getRedisNamespace(), taskKey, "taskStatus", futureTask.getTask().getActionEnum().getProcessing(), true);
        hashCommands.expire(getRedisNamespace(), taskKey, taskTimeout, taskTimeoutTimeUnit, true);
    }

    /**
     * 任務線程開始執行後
     *
     * @param r
     * @param t
     */
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        if (t != null) {
            logger.error("Execute Error:", t);
        }
        if (!(r instanceof ProjectFutureTask)) {
            return;
        }
        ProjectFutureTask futureTask = (ProjectFutureTask) r;
        logger.debug("afterExecute task: {}", futureTask.getTask());
        String taskKey = getTaskRedisKey(futureTask.getTask().getTaskId());
        hashCommands.delete(getRedisNamespace(), taskKey, true);
    }

    /**
     * 獲取redis的GroupName
     *
     * @return
     */
    public abstract String getRedisGroupName();

    /**
     * 獲取redis的Namespace
     *
     * @return
     */
    public abstract int getRedisNamespace();

    /**
     * 獲取redis的任務隊列名稱
     *
     * @return
     */
    public abstract String getRedisTaskQueueName();

    /**
     * 線程中執行任務的具體方法
     *
     * @param task 任務
     * @return 任務執行結果
     * @throws Exception 執行異常
     */
    public abstract String taskHandler(Task task) throws Exception;

}

  1. 分佈式任務對象接口
public interface Task extends Serializable {
    /**
     * 任務ID
     * @return 任務ID
     */
    public Integer getTaskId();

    /**
     * 任務名稱
     * @return 任務名稱
     */
    public String getTaskName();

    /**
     * 任務的動作枚舉
     * @return 任務的動作枚舉
     */
    public TaskActionEnum getActionEnum();
}
  1. 分佈式任務行爲類型
public enum TaskActionEnum {

    /**
     * 準備啓動、啓動中
     */
    START(TaskStatusEnum.PRESTART, TaskStatusEnum.STARTING),

    /**
     * 準備停止、停止中
     */
    STOP(TaskStatusEnum.PRESTOP, TaskStatusEnum.STOPING),

    /**
     * 準備遷移、遷移中
     */
    MIGRATE(TaskStatusEnum.PREMIGRATE,TaskStatusEnum.MIGRATEING);

    private TaskStatusEnum initial;
    private TaskStatusEnum processing;

    TaskActionEnum(TaskStatusEnum initial, TaskStatusEnum processing) {
        this.initial = initial;
        this.processing = processing;
    }

    public Integer getInitial() {
        return initial.getValue();
    }

    public Integer getProcessing() {
        return processing.getValue();
    }

}

  1. 分佈式任務狀態
public enum TaskStatusEnum {

    /**
     * 未開始
     */
    WAIT(0, "未開始"),

    /**
     * 運行中
     */
    RUNNING(1, "運行中"),

    /**
     * 異常中止
     */
    ERROR(2, "異常中止"),

    /**
     * 終止運行
     */
    STOP(3, "終止運行"),

    /**
     * 等待啓動
     */
    PRESTART(4, "等待啓動"),

    /**
     * 啓動中
     */
    STARTING(5, "啓動中"),

    /**
     * 等待停止
     */
    PRESTOP(6, "等待停止"),

    /**
     * 停止中
     */
    STOPING(7, "停止中"),

    /**
     * 等待遷移
     */
    PREMIGRATE(8, "等待遷移"),

    /**
     * 遷移中
     */
    MIGRATEING(9, "遷移中");

    private int value;
    private String description;

    private TaskStatusEnum(int value, String desc) {
        this.value = value;
        this.description = desc;
    }

    public int getValue() {
        return this.value;
    }

    public String getDescription() {
        return this.description;
    }

    public static String getDescription(int index) {
        for (TaskStatusEnum i : TaskStatusEnum.values()) {
            if (i.getValue() == index) {
                return i.description;
            }
        }
        return "";
    }
}
  1. 任務已經存在異常
public class TaskExistException extends RuntimeException implements Serializable {
    public TaskExistException() {
        super();
    }

    public TaskExistException(String message) {
        super(message);
    }

    public TaskExistException(String message, Throwable cause) {
        super(message, cause);
    }
}
  1. 任務不存在異常
public class TaskNotExistException extends RuntimeException implements Serializable {
    public TaskNotExistException() {
        super();
    }

    public TaskNotExistException(String message) {
        super(message);
    }

    public TaskNotExistException(String message, Throwable cause) {
        super(message, cause);
    }
}
  1. 具體的任務處理, 對task進行邏輯處理,如果提交完了任務,需要定時的檢查參見
    延遲和定期執行的線程池分佈式並行處理模板代碼
@Component
@Slf4j
public class FlinkSubmitTaskHandler extends AbstractDistributedTaskHandler implements CommandLineRunner {

	public FlinkSubmitTaskHandler() {
        super(5, "submittask");
    }
	
	/**
     * 實現spring的CommandLineRunner接口,在項目啓動之後會spring會自動調用run方法
     */
	@Override
    public void run(String... strings) throws Exception {
        init();
        //刪除平臺文件夾,防止該文件夾磁盤滿了
        deleteLocalFlinkHomeAndFlinkSqlAndUserJobs();
        //開發環境不啓動
        if (!EnvironmentConstant.DEVELOPMENT.equals(poseidonxConfig.getEnvironment())) {
            start(TimeUnit.MINUTES, poseidonxConfig.getFlinkTaskStartThreadTimeoutMinutes());
        }
    }
	
	/**
     * 刪除平臺本地flinkHome和flinkSql和userJobs文件夾
     */
    private void deleteLocalFlinkHomeAndFlinkSqlAndUserJobs() {
//        String flinkHomeLocation = poseidonxConfig.getFlinkHomeLocation();
//        FileService.deleteAllFile(new File(flinkHomeLocation));
//        String flinkSqlLocation = poseidonxConfig.getFlinkSqlLocation();
//        FileService.deleteAllFile(new File(flinkSqlLocation));
        String userJobsLocation = poseidonxConfig.getUserJobsLocation();
        FileService.deleteAllFile(new File(userJobsLocation));
    }
	
	@Override
    public String getRedisGroupName() {
        return poseidonxConfig.getRedisGroupName();
    }

    @Override
    public int getRedisNamespace() {
        return poseidonxConfig.getRedisNamespace();
    }

    @Override
    public String getRedisTaskQueueName() {
        return "flinksubmittasks";
    }
	
	@Override
    public String taskHandler(Task task) throws Exception {
        log.info("$$$$$$$$$$$$$ begin submit task, task: {}", task);
		//todo 對task進行邏輯處理,如果提交完了任務,需要定時的檢查參見
		log.info("$$$$$$$$$$$$$ end submit task, task: {}", task);
	}
	
}
  1. 線程池命名工廠類
public class NamedThreadFactory implements ThreadFactory {

    private final AtomicInteger mThreadNum = new AtomicInteger(1);

    private final String mPrefix;

    private final boolean mDaemo;

    private final ThreadGroup mGroup;

    public NamedThreadFactory(String prefix) {
        this(prefix, false);
    }

    public NamedThreadFactory(String prefix, boolean daemo) {
        mPrefix = prefix + "-thread-";
        mDaemo = daemo;
        SecurityManager s = System.getSecurityManager();
        mGroup = (s == null) ? Thread.currentThread().getThreadGroup() : s.getThreadGroup();
    }

    @Override
    public Thread newThread(Runnable runnable) {
        String name = mPrefix + mThreadNum.getAndIncrement();
        Thread ret = new Thread(mGroup, runnable, name, 0);
        ret.setDaemon(mDaemo);
        return ret;
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章