实时平台开发笔记

2023-05-16

文章目录

  • 一、背景
  • 二、功能模块划分
    • 1. 作业台
        • 主要功能
        • 任务生命周期
    • 2.任务列表
        • 主要功能
    • 3.项目管理
    • 4.模板管理
    • 5.UDF管理
  • 三、问题解决
      • 1. kerberos认证问题
      • 2.分布式锁解决Job名称冲突问题
      • 3.自定义线程池用以监控线程运行情况
      • 4. 待补充(TODO)
  • 其他
      • 1. 大量的运用了BeanUtils.copyBean,why?
      • 2. SpringBoot结合Mybatis-plus自动回填获取ID
      • 3. 使用RequestContextHolder获取session会话请求入参来校验登录
  • 总结


一、背景

实时业务刚起步的时候,各个部门无论是使用sparkstreaming还是使用flink做实时任务开发基本上处于最原始的命令行方式提交,然后靠人力去运维和监控。这就造成了N多的项目比如代码维护、监控、通用配置等无法有效管理,造成实时任务开发和运维成本高涨,与此同时我们希望能尽快先解决一个最核心功能类解决任务提交jar/sql实时作业的问题,然后再通用的平台上实现对实时任务的运维管理和简单监控等。以下记录了从0到1各个模块实现的关键点如下:

二、功能模块划分

1. 作业台

主要功能

1、创建Jar任务 2、创建SQL类型任务

任务生命周期

  • 创建(通过任务名称、任务类型等参数新增job并为新增的job生成一个初始版本并返回此job的ID(应用递增))
  • 保存(save) (保存涉及了上一个步骤的 JobMeta以及提交的资源文件和配置信息三大部分) 用来更新job
  • 发布(job release) 自定义接口FlinkClientService 封装flink run运行命令使用线程池异步提交job
  • 取消 (job cancel) 使用flink stop命令:
    flink stop -m yarn-cluster -yid application_1584340574618_0841 -p hdfs:///flink/checkpoints/ 550d2fd38b874db23629e0525c62fc2a
  • 上下线 (设置job标记位)

2.任务列表

主要功能

1、查看JOB列表,可以依据不同的查询条件检索
2、启动(指定savepoint)查看日志、复制任务,取消任务、状态修改等

3.项目管理

该模块以项目为管理方式进行收口,一个项目下可以包含多个实时任务,如没有项目默认已登录者姓名为项目名称,主要是为了方便管理。

4.模板管理

对于固定的模板比如sql样例、或者固定的jar只需修改的参数的任务,可以直接基于模板创建,简单方便快捷。

5.UDF管理

在工作台页面内置常用共有函数,在sql开发页面只需要将对应的函数名拖到sql编辑页就能自动完成导入。如:
CREATE FUNCTION StringLengthUdf AS 'com.test.udx.StringLengthUdf';
同时支持用户创建个人私有函数, 完成发布后方可使用。

三、问题解决

1. kerberos认证问题

由于我们的任务是运行在yarn上,而Hadoop集群又是基于Kerberos进行认证的,所以我们在用户提交jar的时候就封装了认证文件,如下所示:

// flink run 命令会通过 command进行拼接然后开启JAVA进程进行作业提交(jar方式的任务)
        command = "export HADOOP_CLASSPATH=`hadoop classpath` && kinit -kt /home/deploy/kerberos/app_prd.keytab app_prd && " + command;
        try {
            Process process = new ProcessBuilder("sh", "-c", command).redirectErrorStream(true).start();
            StringBuilder builder = new StringBuilder();
            try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = br.readLine()) != null) {
                    if (lineFilter.test(line)) {
                        builder.append(line).append("\n");
                    }
                }
            }
            String runLog = builder.toString();
            return runLog;

启动日志显示认证成功:

2021-04-04 15:06:18,861 INFO  org.apache.hadoop.security.UserGroupInformation               - Login successful for user app_prd@FAYSON.COM using keytab file nf.

2.分布式锁解决Job名称冲突问题

在initJob的时候,这里基于Redis作为分布式来解决并发修改同名job的问题,如下:

   String lockKey = metaDTO.getName();
        String clientId = GlobalConstant.REDIS_KEY_PREFFIX + System.nanoTime();
        // 避免多人同时操作,任务名冲突,获取分布式锁
        try {
            if (redisUtils.tryLock(lockKey, clientId, GlobalConstant.REDIS_LOCK_TIME)) {
                this.updateById(metaDO);
                // 资源文件
                jobResourceFileService.addFile(file, jobId, fullJobInfoDTO.getResourceFileDTO());
                // 任务资源配置
                RealtimeJobResourceConfigDTO resourceConfigDTO = fullJobInfoDTO.getResourceConfigDTO();
                jobResourceConfigService.add(resourceConfigDTO, jobId);
                // 存储当前meta对象至历史版本表
                RealtimeJobMetaDTO currentMetaDTO = this.getByJobId(jobId);
                jobMetaHistoryService.add(currentMetaDTO);
                // 存储当前resourceFile对象至历史版本表
                jobResourceFileHistoryService.add(fullJobInfoDTO.getResourceFileDTO(), currentMetaDTO.getVersionId());
                // 存储当前resourceConfig对象至历史版本表
                jobResourceConfigHistoryService.add(resourceConfigDTO, currentMetaDTO.getVersionId());
            }
        } finally {
            redisUtils.releaseLock(lockKey, clientId);
        }

在这里主要是借助于RedisUtils工具类中获取锁和释放锁,代码逻辑:

    private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

   public Boolean tryLock(String lockKey, String clientId, long seconds) {
        return stringRedisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            String result = jedis.set(lockKey, clientId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, seconds);
            if (LOCK_SUCCESS.equals(result)) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        });
    }
    // 释放锁调用的是Lua脚本执行,具体参考如下示例:
    public Boolean releaseLock(String lockKey, String clientId) {
        return stringRedisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            Object result = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(lockKey),
                    Collections.singletonList(clientId));
            if (RELEASE_SUCCESS.equals(result)) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        });
    }

至于为什么释放锁需要调用脚本的方式进行参考如下:

那么删除锁的正确姿势之一,就是可以使用 Lua 脚本,通过 Redis 的 eval/evalsha 命令来运行:
-- lua删除锁:
-- KEYS和ARGV分别是以集合方式传入的参数,对应上文的Test和uuid。
-- 如果对应的value等于传入的uuid。
if redis.call('get', KEYS[1]) == ARGV[1] 
    then 
    -- 执行删除操作
        return redis.call('del', KEYS[1]) 
    else 
    -- 不成功,返回0
        return 0 
end

这里使用了利用Redis执行 Lua的原子性来达到数据一致性,具体参考https://blog.csdn.net/weixin_39540271/article/details/112229979


3.自定义线程池用以监控线程运行情况

这里我们只需要继承spring框架提供的ThreadPoolTaskExecutor,其实它也是实现了Executor框架,可以进行异步提交任务。
以下时该类的继承关系:

ThreadPoolTaskExecutor extends ExecutorConfigurationSupport implements AsyncListenableTaskExecutor, SchedulingTaskExecutor 

public interface SchedulingTaskExecutor extends AsyncTaskExecutor 

public interface AsyncTaskExecutor extends TaskExecutor 

public interface TaskExecutor extends Executor {
  
// JDK1.5  juc包下的Executor
public interface Executor {
    void execute(Runnable command);
}

暂且在这里做下继承实现简单的日志打印动作,如下:

public class RealJobThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
    @Value("${xx.thread.pool.queue.capacity}")
    private int queueCapacity; // default: 100

    private static double QUEUE_THRESHOLD = 0.8; // 线程队列阈值

    private static final Logger logger = LoggerFactory.getLogger(RealJobThreadPoolTaskExecutor.class);

    private void showThreadPoolInfo(String prefix) {
        ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();
        if (null == threadPoolExecutor) {
            return;
        }
        // 大于最大队列容量阈值,则告警
        if (threadPoolExecutor.getQueue().size() > queueCapacity * QUEUE_THRESHOLD) {
            logger.error("{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}] is near overflow",
                    this.getThreadNamePrefix(),
                    prefix,
                    threadPoolExecutor.getTaskCount(),
                    threadPoolExecutor.getCompletedTaskCount(),
                    threadPoolExecutor.getActiveCount(),
                    threadPoolExecutor.getQueue().size());
        } else {
            // 监控线程池使用情况
            logger.info("{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]",
                    this.getThreadNamePrefix(),
                    prefix,
                    threadPoolExecutor.getTaskCount(),
                    threadPoolExecutor.getCompletedTaskCount(),
                    threadPoolExecutor.getActiveCount(),
                    threadPoolExecutor.getQueue().size());
        }
    }

    @Override
    public void execute(Runnable task) {
        showThreadPoolInfo("1. do execute");
        super.execute(task);
    }

    @Override
    public void execute(Runnable task, long startTimeout) {
        showThreadPoolInfo("2. do execute");
        super.execute(task, startTimeout);
    }

    @Override
    public Future<?> submit(Runnable task) {
        showThreadPoolInfo("1. do submit");
        return super.submit(task);
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        showThreadPoolInfo("2. do submit");
        return super.submit(task);
    }

    @Override
    public ListenableFuture<?> submitListenable(Runnable task) {
        showThreadPoolInfo("1. do submitListenable");
        return super.submitListenable(task);
    }

    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
        showThreadPoolInfo("2. do submitListenable");
        return super.submitListenable(task);
    }
}

基于此、配置Spring管理下的Configuration以便于自动注入:

@Configuration
public class ThreadPoolConfig {

    @Value("${xx.thread.pool.core.size}")
    private int corePoolSize;
    @Value("${xx.thread.pool.max.size}")
    private int maxPoolSize;
    @Value("${xx.thread.pool.queue.capacity}")
    private int queueCapacity;
    @Value("${xx.thread.pool.keep.alive.seconds}")
    private int keepAlive;

    /**
     * 可执行定时任务的线程池
     * @return
     */
    @Bean(name = "ThreadPool2CronJob")
    public JindowinJobThreadPoolTaskExecutor threadPool2CronJob() {
        RealJobThreadPoolTaskExecutor executor = new RealJobThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAlive);
        executor.setThreadNamePrefix("ThreadPool2CronJob");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

4. 待补充(TODO)

其他

1. 大量的运用了BeanUtils.copyBean,why?

BeanUtils好用而且便捷,将开发者从繁重的get set操作中解放出来。但是要注意:

  • 两个拷贝类之间需要拷贝的属性字段名必须要一样,当它们字段值不一样的时候,这时候就需要手动获取并赋值

参考:https://www.jianshu.com/p/357b55852efc

2. SpringBoot结合Mybatis-plus自动回填获取ID

  // 用之前的对象获取ID的方式即可
  this.save(metaDO);
  System.out.println(metaDO.getId());

3. 使用RequestContextHolder获取session会话请求入参来校验登录

POST /sql/template/search?currentUserId=828&currentUserName=%E5%85%B0%E5%85%B0 HTTP/1.1
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
public class SessionUtils {
    private static final String CURRENT_USER_ID = "currentUserId";
    private static final String CURRENT_USERNAME = "currentUserName";
    public static Long currentUserId() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String idStr = request.getParameter(CURRENT_USER_ID);
        return StringUtils.isBlank(idStr) ? null : Long.valueOf(idStr);
    }
    public static String currentUsername() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String idStr = request.getParameter(CURRENT_USERNAME);
        return StringUtils.isBlank(idStr) ? null : idStr;
    }
    public static boolean validUser() {
        return currentUserId() != null && currentUsername() != null;
    }
}

总结

该平台以flink-sql-submit为基础封装了任务提交的的相关命令,然后结合自身业务情况打造出这样一个平台。其实也有开源的实现如:flink-streaming-platform-we 如果为了快速应用不妨试试,而且目前来看主体的功能很全,适合二次开发。我们当前的平台还处于早期,主要聚焦于最核心功能的实现,其他还在持续完善中。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

实时平台开发笔记 的相关文章

  • GNVM - Node.js 多版本管理器

    GNVM Node js 多版本管理器 GNVM 是一个简单的 Windows 下 Node js 多版本管理器 xff0c 类似的 nvm nvmw nodist c gt gnvm install latest 1 0 0 x86 1
  • hadoop入门-wordcount

    hadoop是阿帕奇基金会的一个顶级项目 xff0c 主要用于大量的廉价机器组成的集群去执行大规模运算 xff0c 主要是海量数据的处理 在hadoop官网 xff08 http hadoop apache org xff09 hadoop
  • aptitude和apt-get的区别和联系【转,有添加和修改】

    起初GNU Linux系统中只有 tar gz 用户 必须自己编译他们想使用的每一个程序 在Debian出现之後 xff0c 人们认为有必要在系统 中添加一种机 制用来管理 安装在计算机上的软件包 人们将这套系统称为dpkg 至此着名的 p
  • Mac终端配置代理

    export http proxy 61 socks5 127 0 0 1 49719 配置http访问 export https proxy 61 socks5 127 0 0 1 49719 配置https export all pro
  • 如何将Java程序打成可执行jar包

    前几天 xff0c 公司运维找我让我帮他写个Java小程序 xff0c 读取磁盘指定目录的文件 xff0c 然后根据读取的内容查询第三方接口 xff0c 再将第三方接口响应的数据写入磁盘文件 然后我花了半天给他写了这个小程序 xff0c 但
  • javafx_scenebuilder-2_0-windows.msi 百度云盘下载

    javafx scene builder 官网下载很慢 网上有很多人分享 xff0c 都要付积分下载 下面是从官网下载好的 xff0c 传我百度网盘了 xff0c 有需要的大家去下载吧 链接 xff1a https pan baidu co
  • 100+套Axure数据可视化大屏展示原型模板及通用主键库

    内置多种实用美观的可视化组件库及行业模板库 xff0c 行业模板涵盖 xff1a 金融 教育 医疗 政府 交通 制造等多个行业 xff0c 提供设计参考 随着大数据的发展 xff0c 可视化大屏在各行各业得到越来越广泛的应用 可视化大屏不再
  • 数据可视化大屏UI界面

    数据可视化大屏 科技大屏展示 智慧城市 智慧农业 领导页展示大屏 PSD文件 UI可视化大屏模板PSD文件 156套可视化大屏PSD设计文件 xff0c 送给有需要的人 格式格式 xff1a PSD jpg 适合人群 xff1a 可视化大屏
  • SpringBoot 自定义注解实现Redis缓存功能

    背景 最近小A的公司要做一个大屏可视化平台 xff0c 主要是给领导看的 xff0c 领导说这个项目要给领导演示 xff0c 效果好不好直接关系到能不能拿下这个项目 xff0c 领导还补了一句 这项目至少是百万级的 xff0c 大伙要全力以
  • Linux 下 chmod 777 修改权限

    一 rwxrwxrwx 777 Unix Linux 的操作系统 xff0c 每个文件 文件夹也被看作是文件 都按读 写 运行设定权限 例如用ls l命令列文件表时 xff0c 得到如下输出 xff1a rw r r 1 mchopin u
  • Spring创建对象初始化bean的时机分为两种形式:

    import org junit Test import org springframework context ApplicationContext import org springframework context support C
  • 页面动态数据的滚动效果——jquery滚动组件(vticker.js)

    lt script language 61 34 javascript 34 src 61 34 lirms Test jquery 1 4 2 js 34 gt lt script gt lt script language 61 34
  • MySQL 查询结果以百分比显示

    找了一些资料 xff0c 然后我是用到了MySQL字符串处理中的两个函数concat 和left 1 span style color ff0000 CONCAT span str1 str2 返回来自于参数连结的字符串 如果任何参数是 N
  • rpm包安装过程中依赖问题“libc.so.6 is needed by XXX”解决方法

    转自 xff1a http raksmart idcspy com 781 rpm包安装过程中依赖问题 libc so 6 is needed by XXX 解决方法 与本教程高度相关文章 xff08 读完应该可以解决你的问题 xff09
  • 艺博: linux命令大宝典系列之mkdir创建目录

    在成长的过程中 人总要经历一些痛苦和挫折才能更加成熟与坚强 目录 mkdir是什么mkdir的语法mkdir的选项含义mkdir的实例1 使用mkdir命令创建一个dir1目录 默认权限775 2 使用mkdir m命令新建一个dir2目录
  • linux命令xrandr修改桌面分辨率

    xrandr临时修改分辨率 方法一 打开终端xrandr Screen 0 minimum 8 x 8 current 1366 x 768 maximum 32767 x 32767 eDP1 connected primary 1366
  • Python 教你训练一个98%准确率的微博抑郁文本分类模型(含数据)

    Paddle是一个比较高级的深度学习开发框架 xff0c 其内置了许多方便的计算单元可供使用 xff0c 我们之前写过PaddleHub相关的文章 xff1a 1 Python 识别文本情感就这么简单 2 比PS还好用 xff01 Pyth
  • 记一次神奇的时间转换问题(SheetJS)

    最近在写一个功能 xff0c 使用SheetJS读取Excel表格 xff0c 在读取日期的时候发现了一个隐藏很深的坑 xff0c 特此记录一下 SheetJS读取Excel文件时 xff0c 可指定参数 cellDates true xf
  • 通信常识

    bsc指的是基站控制器 xff08 Base Station Controller xff09 由一下模块组成 xff1a AM CM模块 xff1a 话路交换和信息交换的中心 BM模块 xff1a 完成呼叫处理 信令处理 无线资源管理 无
  • python解析基于xml格式的日志文件

    大家中午好 xff0c 由于过年一直还没回到状态 xff0c 好久没分享一波小知识了 xff0c 今天 xff0c 继续给大家分享一波python解析日志的小脚本 首先 xff0c 同样的先看看日志是个啥样 都是xml格式的 xff0c 是

随机推荐

  • 如何在无显示屏的情况下调试树莓派

    一 准备 1 树莓派 xff1b 2 SD卡 读卡器 网线 xff1b 3 系统镜像下载链接 xff1b 4 软件 xff1a SD Card Formatter下载链接 xff1b balenaEtcher下载链接 xff1b VNC V
  • VNC怎么和宿主机共享粘贴板

    VNC怎么和宿主机共享粘贴板 假设目标主机是linux xff0c 终端主机是windows xff08 就是在windows上使用VNC登陆linux xff09 在linux中执行vncconfig nowin amp 在linux选中
  • 系统调用,进程切换

    模式切换 不等同于 进程上下文切换 当进程调用系统调用或者发生中断时 xff0c CPU从用户模式 xff08 用户态 xff09 切换成内核模式 xff08 内核态 xff09 xff0c 此时 xff0c 无论是系统调用程序还是中断服务
  • brew换源

    bin zsh c 34 curl fsSL https gitee com cunkai HomebrewCN raw master Homebrew sh 34 mac安装homebrew失败怎么办 xff1f 金牛肖马的回答 知乎 h
  • 2022年书单

    2022年书单 纸质书 类别序号书名进度社会科学0 从零开始的女性主义 x1f44c 社会科学1 如何抑制女性写作 x1f44c 社会科学2 父权制与资本主义 社会科学3 下流社会 x1f44c 社会科学4 低欲望社会 x1f44c 社会科
  • 书店漫游记录

    目录 北京 上海 杭州 天津 南京 青岛 深圳 香港 北京 万圣书园 豆瓣书店 野草书店 三联韬奋书店 xff08 三里屯 xff09 三联韬奋书店 xff08 美术馆 xff09 Pageone xff08 北京坊 xff09 Pageo
  • C++ std::string 不可初始化为NULL及基本用法

    偶然看到一个问题 xff0c 顺便总结一下std string C 43 43 basic string S construct null not valid stackoverflow例子 std string 字符串不可以初始化为NUL
  • 通过查看端口状态查看mongodb是否已经启动

    LINUX环境下 xff0c 可以通过查看端口27017的状态查看mongod是否已经启动 netstat lanp span class hljs string grep 34 span span class hljs number 27
  • linux & windows C++开发差异

    新手注意事项 1 文件与目录的大小写以及路径分隔符的差别 windows下不区分大小写 xff0c 路径分隔符一般使用 xff1b linux下区分大小写 xff0c 路径分隔符使用 2 itoa 函数在linux下并不存在 所以使用类似s
  • 深度学习结合SLAM的研究思路/成果整理之(一)使用深度学习方法替换SLAM中的模块

    整理了部分近两年深度学习结合SLAM的一些研究成果 xff08 参考知乎帖子https www zhihu com question 66006923 和泡泡机器人公众号 xff0c 附上论文链接和已找到的源代码 数据集链接 xff0c 大
  • 深度学习与自动驾驶领域的数据集(KITTI,Oxford,Cityscape,Comma.ai,BDDV,TORCS,Udacity,GTA,CARLA,Carcraft)

    http blog csdn net solomon1558 article details 70173223 Torontocity HCI middlebury caltech 行人检测数据集 ISPRS航拍数据集 mot challe
  • 又一遍……ORB_SLAM2+ZED相机(SDK2.2.1)+CUDA9.0+ROS Kinetic 安装测试 some tips

    很久没碰过ORB SLAM2了 xff0c 今天有需要 xff0c 再来试一遍 xff5e ORB SLAM2的github链接 1 安装ORB SLAM2的依赖库 按照链接一步一步来就可以 eigen直接用命令安装就可以 sudo apt
  • MacOS设置终端代理

    前言 国内的开发者或多或少都会因为网络而烦恼 xff0c 因为一些特殊原因有时候网络不好的时候需要使用代理才能完成对应的操作 原来我一直都是使用斐讯路由器然后刷了梅林的固件 xff0c 直接在路由器层面设置转发代理 xff0c 把一些国内网
  • Linux SIGPIPE信号产生原因与解决方法

    TCP 四次握手 产生SIGPIPE的原因 SIGPIPE信号产生的原因 xff1a 简单来说 xff0c 就是客户端程序向服务器端程序发送了消息 xff0c 然后关闭客户端 xff0c 服务器端返回消息的时候就会收到内核给的SIGPIPE
  • Homebrew最新安装--解决安装超时的问题

    更新 2021 1 20 可以直接用下边的脚本进行安装 bin zsh c span class token string 34 span class token variable span class token variable spa
  • TIDB使用时的注意点笔记

    场景 xff1a 虽然TiDB号称完全兼容MySQL 5 7 协议 MySQL 5 7 常用的功能及语法 xff0c 但是其与MySQL数据库仍然存在一些差异 xff0c 可能会导致下游TiDB环境故障 以下是我们使用TiDB时需要重点关注
  • SpringBoot结合MyBatis-Plus快速CRUD笔记

    提示 xff1a 文章写完后 xff0c 目录可以自动生成 xff0c 如何生成可参考右边的帮助文档 文章目录 前言一 DTO amp DO二 示例1 定义Controller2 定义Service和实现3 定义Mapper4 前端访问测试
  • Java最佳实践笔记

    一 常量定义最佳实践 span class token keyword public span span class token keyword final span span class token keyword class span
  • 聊聊JavaSPI

    文章目录 前言一 SPI 示例二 SPI原理与双亲委派机制1 MySQL Driver2 DataX 插件的热插拔也是破坏双亲委派的一种3 Tomcat类加载同样是破坏了双亲委托 总结参考文章 前言 SPI 全称为 Service Prov
  • 实时平台开发笔记

    文章目录 一 背景二 功能模块划分1 作业台主要功能任务生命周期 2 任务列表主要功能 3 项目管理4 模板管理5 UDF管理 三 问题解决1 kerberos认证问题2 分布式锁解决Job名称冲突问题3 自定义线程池用以监控线程运行情况4