Quick-Task 动态脚本支持框架之结构设计篇

简介: 前面两篇博文,主要是整体介绍和如何使用;接下来开始进入正题,逐步剖析,这个项目是怎么一步一步搭建起来的;本篇博文则主要介绍基本骨架的设计,围绕项目的核心点,实现一个基础的原型系统

image.png


前面两篇博文,主要是整体介绍和如何使用;接下来开始进入正题,逐步剖析,这个项目是怎么一步一步搭建起来的;本篇博文则主要介绍基本骨架的设计,围绕项目的核心点,实现一个基础的原型系统


I. 结构分析



整体设计图如下:

image.png


对于上面的图,得有一个基本的认知,最好是能在脑海中构想出整个框架运行的方式,在正式开始之前,先简单的过一下这张结构图


抓要点


1. 任务执行单元


即图中的每个task就表示一个基本的任务,有下面几个要求


  • 统一的继承关系(面向对象的设计理念,执行同一个角色的类由某个抽象的接口继承而来)
  • 任务的执行之间是没有关系的(即任务在独立的线程中调度执行)


2. 任务队列


在图中表现很明显了,在内存中会保存一个当前所有执行的任务队列(或者其他的容器)


这个的目的是什么?


  • 任务脚本更新时,需要卸载旧的任务(因此可以从队列中找到旧的任务,并停掉)
  • 任务脚本删除时,需要卸载旧的任务


3. 任务管理者


虽然图中并没有明确的说有这么个东西,但也好理解,我们的系统设计目标就是支持多任务的执行和热加载,那么肯定有个任务管理的角色,来处理这些事情


其要做的事情就一个任务热加载


  • 包括动态脚本更新,删除,新增的事件监听
  • 实现卸载内存中旧的任务并加载执行新的任务


4. 插件系统


这个与核心功能关系不大,可以先不care,简单说一下就是为task提供更好的使用的公共类


这里不详细展开,后面再说


II. 设计实现


有了上面的简单认知之后,开始进入正题,编码环节,省略掉创建工程等步骤,第一步就是设计Task的API


1. ITask设计


抽象公共的任务接口,从任务的标识区分,和业务调度执行,很容易写出下面的实现

public interface ITask {
    /**
     * 默认将task的类名作为唯一标识
     *
     * @return
     */
    default String name() {
        return this.getClass().getName();
    }
    /**
     * 开始执行任务
     */
    void run();
    /**
     * 任务中断
     */
    default void interrupt() {}
}
复制代码


前面两个好理解,中断这个接口的目的何在?主要是出于任务结束时的收尾操作,特别是在使用到流等操作时,有这么个回调就比较好了


2. TaskDecorate


任务装饰类,为什么有这么个东西?出于什么考虑的?


从上面可以知道,所有的任务最终都是在独立的线程中调度执行,那么我们自己实现的Task肯定都是会封装到线程中的,在Java中可以怎么起一个线程执行呢?


一个顺其自然的想法就是包装一下ITask接口,让它集成自Thread,然后就可以简单的直接将任务丢到线程池中即可


@Slf4j
public class ScriptTaskDecorate extends Thread {
    private ITask task;
    public ScriptTaskDecorate(ITask task) {
        this.task = task;
        setName(task.name());
    }
    @Override
    public void run() {
        try {
            task.run();
        } catch (Exception e) {
            log.error("script task run error! task: {}", task.name());
        }
    }
    @Override
    public void interrupt() {
        task.interrupt();
    }
}
复制代码


说明:

上面这个并不是必须的,你也完全可以自己在线程池调度Task任务时,进行硬编码风格的封装调用,完全没有问题(只是代码将不太好看而已)


3. TaskContainer


上面两个是具体的任务相关定义接口,接下来就是维护这些任务的容器了,最简单的就是用一个Map来保存,uuid到task的映射关系,然后再需要卸载/更新任务时,停掉旧的,添加新的任务,对应的实现也比较简单


public class TaskContainer {
    /**
     * key: com.git.hui.task.api.ITask#name()
     */
    private static Map<String, ScriptTaskDecorate> taskCache = new ConcurrentHashMap<>();
    /**
     * key: absolute script path
     *
     * for task to delete
     */
    private static Map<String, ScriptTaskDecorate> pathCache = new ConcurrentHashMap<>();
    public static void registerTask(String path, ScriptTaskDecorate task) {
        ScriptTaskDecorate origin = taskCache.get(task.getName());
        if (origin != null) {
            origin.interrupt();
        }
        taskCache.put(task.getName(), task);
        pathCache.put(path, task);
        AsynTaskManager.addTask(task);
    }
    public static void removeTask(String path) {
        ScriptTaskDecorate task = pathCache.get(path);
        if (task != null) {
            task.interrupt();
            taskCache.remove(task.getName());
            pathCache.remove(path);
        }
    }
}
复制代码


说明


为什么有两个map,一个唯一标识name为key,一个是task的全路径为key?


  • 删除任务时,是直接删除文件,所以需要维护一个pathCache
  • 维护name的映射,主要是基于任务的唯一标识出发的,后续可能借此做一些扩展(比如任务和任务之间的关联等)


4. 任务注册


前面介绍了任务的定义和装载任务的容器,接下来可以想到的就是如何发现任务并注册了,这一块这里不要详细展开,后面另起一篇详解;主要说一下思路


在设计之初,就决定任务采用Groovy脚本来实现热加载,所以有两个很容易想到的功能点


  • 监听Groovy脚本的变动(新增,更新,删除),对应的类为 TaskChangeWatcher
  • 加载Groovy脚本到内存,并执行,对应的类为 GroovyCompile


5. 执行流程


有了上面四个是否可以搭建一个原型框架呢?


答案是可以的,整个框架的运行过程

  • 程序启动,注册Groovy脚本变动监听器
  • 加载groovy脚本,注册到TaskContainer
  • 将groovy脚本丢到线程池中调度执行
  • 执行完毕后,清除和回收现场


6. 其他


当然其他一些辅助的工具类可有可无了,当然从使用的角度出发,有很多东西还是很有必要的,如


  • 通用的日志输出组件(特别是日志输出,收集,检索,经典的ELK场景)
  • 报警相关组件
  • 监控相关
  • redis缓存工具类
  • dao工具类
  • mq消费工具类
  • http工具类
  • 其他



相关文章
|
5月前
|
消息中间件 Kafka SQL
|
5月前
|
缓存 前端开发
ProFlow 流程编辑器框架问题之创建一个自定义节点如何解决
ProFlow 流程编辑器框架问题之创建一个自定义节点如何解决
54 1
|
5月前
|
SQL 存储 数据管理
掌握SQL Server Integration Services (SSIS)精髓:从零开始构建自动化数据提取、转换与加载(ETL)流程,实现高效数据迁移与集成——轻松上手SSIS打造企业级数据管理利器
【8月更文挑战第31天】SQL Server Integration Services (SSIS) 是 Microsoft 提供的企业级数据集成平台,用于高效完成数据提取、转换和加载(ETL)任务。本文通过简单示例介绍 SSIS 的基本使用方法,包括创建数据包、配置数据源与目标以及自动化执行流程。首先确保安装了 SQL Server Data Tools (SSDT),然后在 Visual Studio 中创建新的 SSIS 项目,通过添加控制流和数据流组件,实现从 CSV 文件到 SQL Server 数据库的数据迁移。
353 0
|
7月前
|
运维 Serverless 网络安全
Serverless 应用引擎产品使用合集之能否用一个顶层函数,在云端动态的增加函数脚本或删除脚本
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
|
8月前
|
监控 Java 流计算
Flink中的窗口操作是什么?请解释其作用和使用场景。
Flink中的窗口操作是什么?请解释其作用和使用场景。
87 0
|
8月前
|
存储 Java API
Flink中的状态管理是什么?请解释其作用和常用方法。
Flink中的状态管理是什么?请解释其作用和常用方法。
95 0
|
存储 JavaScript 前端开发
企业级项目开发中的交互式解释器以及global全局定义、Stream流的合理运用和实战【Note.js】
企业级项目开发中的交互式解释器以及global全局定义、Stream流的合理运用和实战【Note.js】
EMQ
|
SQL 传感器 JSON
eKuiper Newsletter 2022-07|v1.6.0:Flow 编排 + 更好用的 SQL,轻松表达业务逻辑
eKuiper本年度第二个大版本 v1.6.0正式发布,面向Flow编排的图规则API已开发完成,同时达成了多个SQL语法和函数的提升,期望覆盖更多样的使用场景,帮助用户进一步减少定制开发的需求和成本。
EMQ
321 0
eKuiper Newsletter 2022-07|v1.6.0:Flow 编排 + 更好用的 SQL,轻松表达业务逻辑
|
存储 测试技术 数据安全/隐私保护
RobotFrameWork接口项目分层及通用控制方式
RobotFrameWork接口项目分层及通用控制方式
1012 0
RobotFrameWork接口项目分层及通用控制方式
|
Java 调度 开发工具
QuickTask动态脚本支持框架整体介绍篇
一个简单的动态脚本调度框架,支持运行时,实时增加,删除和修改动态脚本,可用于后端的进行接口验证、数据订正,执行定时任务或校验脚本
266 0
QuickTask动态脚本支持框架整体介绍篇