【开源项目】Dynamic-Tp告警系统的源码解析

news2024/11/25 3:39:25

序言

【开源项目】Dynamic-Tp核心流程源码解读,继上回解读完DynamicTp这个开源项目的源码,觉得其中的告警机制也是十分精彩,如果能学会,用在自己的项目中,那才能说得上掌握了DynamicTp这个开源项目的源码理解的精髓。所以接下来看看DynamicTp是如何初始化告警机制的,又是如何发送告警信息的。

线程池初始化

DtpExecutor#initialize,线程池初始化。

    @Override
    protected void initialize() {
        NotifyHelper.initNotify(this);
        if (preStartAllCoreThreads) {
            prestartAllCoreThreads();
        }
    }

NotifyHelper#initNotify,会初始化提醒的平台数据和AlarmManager

    public static void initNotify(DtpExecutor executor) {
        val dtpProperties = ApplicationContextHolder.getBean(DtpProperties.class);
        if (Objects.isNull(dtpProperties)) {
            log.warn("DynamicTp notify, cannot find a DtpProperties instance for [{}].",
                    executor.getThreadPoolName());
            return;
        }
        val platforms = dtpProperties.getPlatforms();
        if (CollectionUtils.isEmpty(platforms)) {
            executor.setNotifyItems(Lists.newArrayList());
            executor.setPlatformIds(Lists.newArrayList());
            log.warn("DynamicTp notify, no notify platforms configured for [{}]", executor.getThreadPoolName());
            return;
        }
        if (CollectionUtils.isEmpty(executor.getNotifyItems())) {
            log.warn("DynamicTp notify, no notify items configured for [{}]", executor.getThreadPoolName());
            return;
        }
        fillPlatforms(executor.getPlatformIds(), platforms, executor.getNotifyItems());
        AlarmManager.initAlarm(executor.getThreadPoolName(), executor.getNotifyItems());
    }

NotifyHelper#fillPlatforms,整合两个NotifyPlatform集合,存放到NotifyItem的数据中。

    public static void fillPlatforms(List<String> platformIds,
                                     List<NotifyPlatform> platforms,
                                     List<NotifyItem> notifyItems) {
        if (CollectionUtils.isEmpty(platforms) || CollectionUtils.isEmpty(notifyItems)) {
            log.warn("DynamicTp notify, no notify platforms or items configured.");
            return;
        }
        List<String> globalPlatformIds = StreamUtil.fetchProperty(platforms, NotifyPlatform::getPlatformId);
        // notifyItem > executor > global
        notifyItems.forEach(n -> {
            if (CollectionUtils.isNotEmpty(n.getPlatformIds())) {
                // intersection of notifyItem and global
                n.setPlatformIds((List<String>) CollectionUtils.intersection(globalPlatformIds, n.getPlatformIds()));
            } else if (CollectionUtils.isNotEmpty(platformIds)) {
                n.setPlatformIds((List<String>) CollectionUtils.intersection(globalPlatformIds, platformIds));
            } else {
                // need to compatible with the previous situation that does not exist platformIds
                if (CollectionUtils.isNotEmpty(n.getPlatforms())) {
                    setPlatformIds(platforms, n);
                } else {
                    n.setPlatformIds(globalPlatformIds);
                }
            }
        });
    }

初始化告警器

AlarmManager#initAlarm(String, List<NotifyItem>),初始化AlarmLimiterAlarmCounter

    public static void initAlarm(String poolName, List<NotifyItem> notifyItems) {
        notifyItems.forEach(x -> initAlarm(poolName, x));
    }

    public static void initAlarm(String poolName, NotifyItem notifyItem) {
        AlarmLimiter.initAlarmLimiter(poolName, notifyItem);
        AlarmCounter.init(poolName, notifyItem.getType());
    }

AlarmLimiter#initAlarmLimiter,初始化限流器。

    public static void initAlarmLimiter(String threadPoolName, NotifyItem notifyItem) {
        if (NotifyItemEnum.CHANGE.getValue().equalsIgnoreCase(notifyItem.getType())) {
            return;
        }

        String key = genKey(threadPoolName, notifyItem.getType());
        Cache<String, String> cache = CacheBuilder.newBuilder()
                .expireAfterWrite(notifyItem.getInterval(), TimeUnit.SECONDS)
                .build();
        ALARM_LIMITER.put(key, cache);
    }

AlarmCounter#init,初始化计数器。

    public static void init(String threadPoolName, String notifyItemType) {
        String key = buildKey(threadPoolName, notifyItemType);
        val alarmInfo = AlarmInfo.builder()
                .notifyItem(NotifyItemEnum.of(notifyItemType))
                .build();
        ALARM_INFO_CACHE.putIfAbsent(key, alarmInfo);
    }

告警

AlarmManager#doAlarmAsync(DtpExecutor, NotifyItemEnum, Runnable),增加次数,调用线程池执行责任链

    public static void doAlarmAsync(DtpExecutor executor, NotifyItemEnum notifyType, Runnable currRunnable) {
        MDC.put(TRACE_ID, ((DtpRunnable) currRunnable).getTraceId());
        AlarmCounter.incAlarmCounter(executor.getThreadPoolName(), notifyType.getValue());
        ALARM_EXECUTOR.execute(() -> doAlarm(ExecutorWrapper.of(executor), notifyType));
    }

    public static void doAlarm(ExecutorWrapper executorWrapper, NotifyItemEnum notifyItemEnum) {
        NotifyHelper.getNotifyItem(executorWrapper, notifyItemEnum).ifPresent(notifyItem -> {
            val alarmCtx = new AlarmCtx(executorWrapper, notifyItem);
            ALARM_INVOKER_CHAIN.proceed(alarmCtx);
        });
    }

告警责任链初始化

AlarmManager初始化类信息会加载InvokerChain责任链。

    private static final InvokerChain<BaseNotifyCtx> ALARM_INVOKER_CHAIN;

    static {
        ALARM_INVOKER_CHAIN = NotifyFilterBuilder.getAlarmInvokerChain();
    }

NotifyFilterBuilder#getAlarmInvokerChain,获取到容器中的NotifyFilter,使用工厂类构造责任链

    public static InvokerChain<BaseNotifyCtx> getAlarmInvokerChain() {
        val filters = ApplicationContextHolder.getBeansOfType(NotifyFilter.class);
        Collection<NotifyFilter> alarmFilters = Lists.newArrayList(filters.values());
        alarmFilters.add(new AlarmBaseFilter());
        alarmFilters = alarmFilters.stream()
                .filter(x -> x.supports(NotifyTypeEnum.ALARM))
                .sorted(Comparator.comparing(Filter::getOrder))
                .collect(Collectors.toList());
        return InvokerChainFactory.buildInvokerChain(new AlarmInvoker(), alarmFilters.toArray(new NotifyFilter[0]));
    }

InvokerChainFactory用于构造InvokerChain

public final class InvokerChainFactory {

    private InvokerChainFactory() { }

    @SafeVarargs
    public static<T> InvokerChain<T> buildInvokerChain(Invoker<T> target, Filter<T>... filters) {

        InvokerChain<T> invokerChain = new InvokerChain<>();
        Invoker<T> last = target;
        for (int i = filters.length - 1; i >= 0; i--) {
            Invoker<T> next = last;
            Filter<T> filter = filters[i];
            last = context -> filter.doFilter(context, next);
        }
        invokerChain.setHead(last);
        return invokerChain;
    }
}

AlarmBaseFilter,判断是否告警过,判断容量,存活等阈值。AlarmLimiter有时间限制,在一定时间内会自动清除数据。

@Slf4j
public class AlarmBaseFilter implements NotifyFilter {

    private static final Object SEND_LOCK = new Object();

    @Override
    public void doFilter(BaseNotifyCtx context, Invoker<BaseNotifyCtx> nextInvoker) {

        val executorWrapper = context.getExecutorWrapper();
        val notifyItem = context.getNotifyItem();
        if (Objects.isNull(notifyItem) || !satisfyBaseCondition(notifyItem, executorWrapper)) {
            return;
        }

        boolean ifAlarm = AlarmLimiter.ifAlarm(executorWrapper.getThreadPoolName(), notifyItem.getType());
        if (!ifAlarm) {
            log.debug("DynamicTp notify, alarm limit, threadPoolName: {}, notifyItem: {}",
                    executorWrapper.getThreadPoolName(), notifyItem.getType());
            return;
        }

        if (!AlarmManager.checkThreshold(executorWrapper, context.getNotifyItemEnum(), notifyItem)) {
            return;
        }
        synchronized (SEND_LOCK) {
            // recheck alarm limit.
            ifAlarm = AlarmLimiter.ifAlarm(executorWrapper.getThreadPoolName(), notifyItem.getType());
            if (!ifAlarm) {
                log.warn("DynamicTp notify, concurrent send, alarm limit, threadPoolName: {}, notifyItem: {}",
                        executorWrapper.getThreadPoolName(), notifyItem.getType());
                return;
            }
            AlarmLimiter.putVal(executorWrapper.getThreadPoolName(), notifyItem.getType());
        }
        nextInvoker.invoke(context);
    }

    private boolean satisfyBaseCondition(NotifyItem notifyItem, ExecutorWrapper executor) {
        return executor.isNotifyEnabled()
                && notifyItem.isEnabled()
                && CollectionUtils.isNotEmpty(notifyItem.getPlatformIds());
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

AlarmManager#checkThreshold,检查容量,存活率。

    public static boolean checkThreshold(ExecutorWrapper executor, NotifyItemEnum itemEnum, NotifyItem notifyItem) {

        switch (itemEnum) {
            case CAPACITY:
                return checkCapacity(executor, notifyItem);
            case LIVENESS:
                return checkLiveness(executor, notifyItem);
            case REJECT:
            case RUN_TIMEOUT:
            case QUEUE_TIMEOUT:
                return checkWithAlarmInfo(executor, notifyItem);
            default:
                log.error("Unsupported alarm type, type: {}", itemEnum);
                return false;
        }
    }

    private static boolean checkLiveness(ExecutorWrapper executorWrapper, NotifyItem notifyItem) {
        val executor = (ThreadPoolExecutor) executorWrapper.getExecutor();
        int maximumPoolSize = executor.getMaximumPoolSize();
        double div = NumberUtil.div(executor.getActiveCount(), maximumPoolSize, 2) * 100;
        return div >= notifyItem.getThreshold();
    }

    private static boolean checkCapacity(ExecutorWrapper executorWrapper, NotifyItem notifyItem) {

        val executor = (ThreadPoolExecutor) executorWrapper.getExecutor();
        BlockingQueue<Runnable> workQueue = executor.getQueue();
        if (CollectionUtils.isEmpty(workQueue)) {
            return false;
        }

        int queueCapacity = executor.getQueue().size() + executor.getQueue().remainingCapacity();
        double div = NumberUtil.div(workQueue.size(), queueCapacity, 2) * 100;
        return div >= notifyItem.getThreshold();
    }

    private static boolean checkWithAlarmInfo(ExecutorWrapper executorWrapper, NotifyItem notifyItem) {
        AlarmInfo alarmInfo = AlarmCounter.getAlarmInfo(executorWrapper.getThreadPoolName(), notifyItem.getType());
        return alarmInfo.getCount() >= notifyItem.getThreshold();
    }

AlarmInvoker,进行短信发送。

public class AlarmInvoker implements Invoker<BaseNotifyCtx> {

    @Override
    public void invoke(BaseNotifyCtx context) {

        val alarmCtx = (AlarmCtx) context;
        val executorWrapper = alarmCtx.getExecutorWrapper();
        val notifyItem = alarmCtx.getNotifyItem();
        val alarmInfo = AlarmCounter.getAlarmInfo(executorWrapper.getThreadPoolName(), notifyItem.getType());
        alarmCtx.setAlarmInfo(alarmInfo);

        try {
            DtpNotifyCtxHolder.set(context);
            NotifierHandler.getInstance().sendAlarm(NotifyItemEnum.of(notifyItem.getType()));
            AlarmCounter.reset(executorWrapper.getThreadPoolName(), notifyItem.getType());
        } finally {
            DtpNotifyCtxHolder.remove();
        }
    }
}

NotifierHandler#sendAlarm,发送告警。获取所有的平台,根据平台获取DtpNotifier,发送告警信息。

    public void sendAlarm(NotifyItemEnum notifyItemEnum) {
        NotifyItem notifyItem = DtpNotifyCtxHolder.get().getNotifyItem();
        for (String platformId : notifyItem.getPlatformIds()) {
            NotifyHelper.getPlatform(platformId).ifPresent(p -> {
                DtpNotifier notifier = NOTIFIERS.get(p.getPlatform().toLowerCase());
                if (notifier != null) {
                    notifier.sendAlarmMsg(p, notifyItemEnum);
                }
            });
        }
    }

AbstractDtpNotifier#sendAlarmMsg,构建告警内容

    @Override
    public void sendAlarmMsg(NotifyPlatform notifyPlatform, NotifyItemEnum notifyItemEnum) {
        String content = buildAlarmContent(notifyPlatform, notifyItemEnum);
        if (StringUtils.isBlank(content)) {
            log.debug("Alarm content is empty, ignore send alarm message.");
            return;
        }
        notifier.send(notifyPlatform, content);
    }

    protected String buildAlarmContent(NotifyPlatform platform, NotifyItemEnum notifyItemEnum) {
        AlarmCtx context = (AlarmCtx) DtpNotifyCtxHolder.get();
        ExecutorWrapper executorWrapper = context.getExecutorWrapper();
        String threadPoolName = executorWrapper.getThreadPoolName();
        val executor = (ThreadPoolExecutor) context.getExecutorWrapper().getExecutor();
        NotifyItem notifyItem = context.getNotifyItem();
        AlarmInfo alarmInfo = context.getAlarmInfo();

        val alarmCounter = AlarmCounter.countStrRrq(threadPoolName, executor);
        String receivesStr = getReceives(platform.getPlatform(), platform.getReceivers());

        String content = String.format(
                getAlarmTemplate(),
                CommonUtil.getInstance().getServiceName(),
                CommonUtil.getInstance().getIp() + ":" + CommonUtil.getInstance().getPort(),
                CommonUtil.getInstance().getEnv(),
                populatePoolName(executorWrapper),
                notifyItemEnum.getValue(),
                notifyItem.getThreshold(),
                executor.getCorePoolSize(),
                executor.getMaximumPoolSize(),
                executor.getPoolSize(),
                executor.getActiveCount(),
                executor.getLargestPoolSize(),
                executor.getTaskCount(),
                executor.getCompletedTaskCount(),
                executor.getQueue().size(),
                executor.getQueue().getClass().getSimpleName(),
                getQueueCapacity(executor),
                executor.getQueue().size(),
                executor.getQueue().remainingCapacity(),
                getRejectHandlerName(executor),
                alarmCounter.getLeft(),
                alarmCounter.getMiddle(),
                alarmCounter.getRight(),
                Optional.ofNullable(alarmInfo.getLastAlarmTime()).orElse(UNKNOWN),
                DateUtil.now(),
                receivesStr,
                Optional.ofNullable(MDC.get(TRACE_ID)).orElse(UNKNOWN),
                notifyItem.getInterval()
        );
        return highlightAlarmContent(content, notifyItemEnum);
    }

AlarmCounter#countStrRrq,告警信息中就包含当前线程池REJECTRUN_TIMEOUTQUEUE_TIMEOUT的线程数量。

    public static Triple<String, String, String> countStrRrq(String threadPoolName, ThreadPoolExecutor executor) {

        if (!(executor instanceof DtpExecutor)) {
            return new ImmutableTriple<>(DEFAULT_COUNT_STR, DEFAULT_COUNT_STR, DEFAULT_COUNT_STR);
        }

        DtpExecutor dtpExecutor = (DtpExecutor) executor;
        String rejectCount = getCount(threadPoolName, REJECT.getValue()) + " / " + dtpExecutor.getRejectCount();
        String runTimeoutCount = getCount(threadPoolName, RUN_TIMEOUT.getValue()) + " / "
                + dtpExecutor.getRunTimeoutCount();
        String queueTimeoutCount = getCount(threadPoolName, QUEUE_TIMEOUT.getValue()) + " / "
                + dtpExecutor.getQueueTimeoutCount();
        return new ImmutableTriple<>(rejectCount, runTimeoutCount, queueTimeoutCount);
    }

提醒工具类

EmailNotifier实现了Notifier,会进行邮件发送。

@Slf4j
public class EmailNotifier implements Notifier {

    @Value("${spring.mail.username}")
    private String sendFrom;

    @Value("${spring.mail.title:ThreadPool Notify}")
    private String title;

    @Resource
    private JavaMailSender javaMailSender;

    @Resource
    private TemplateEngine templateEngine;

    @Override
    public String platform() {
        return NotifyPlatformEnum.EMAIL.name().toLowerCase();
    }

    @Override
    public void send(NotifyPlatform platform, String content) {
        try {
            MimeMessage mimeMessage = javaMailSender.createMimeMessage();
            MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
            messageHelper.setSubject(title);
            messageHelper.setFrom(sendFrom);
            messageHelper.setTo(platform.getReceivers().split(","));
            messageHelper.setSentDate(new Date());
            messageHelper.setText(content, true);
            javaMailSender.send(mimeMessage);
            log.info("DynamicTp notify, email send success.");
        } catch (Exception e) {
            log.error("DynamicTp notify, email send failed...", e);
        }
    }

    public String processTemplateContent(String template, Context context) {
        return templateEngine.process(template, context);
    }

}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/571420.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

零碳光储 数能未来 | 全系光储产品实力吸睛,科士达精彩亮相SNEC 2023

5月24日&#xff0c;光伏行业最具影响力的全球性展会——“SNEC 2023”在上海盛大开幕。作为行业领先的全能方案供应商&#xff0c;科士达以“零碳光储 数能未来”为主题&#xff0c;携全系光储产品及解决方案重磅亮相。 展会现场&#xff0c;科士达展出的全系解决方案涵盖分布…

APACHE-ATLAS-2.1.0简介(三)

APACHE-ATLAS-2.1.0简介(一) APACHE-ATLAS-2.1.0简介(二) 写在前面 ATLAS为组织提供开放式的元数据管理和治理功能&#xff0c;用以构建其数据资产目录&#xff0c;对这些资产进行分类和管理&#xff0c;形成数据字典。 名词解释 元数据&#xff1a;就是用于描述数据的数据…

js实现PDF 预览和文件下载

在开发过程中要求对 PDF 类型的发票提供 预览 和 下载 功能&#xff0c;PDF 类型文件的来源又包括 H5 移动端 和 PC 端&#xff0c;而针对这两个不同端的处理会有些许不同&#xff0c;下文会有所提及。 针对 PDF 预览 的文章不在少数&#xff0c;但似乎都没有提及可能遇到的问…

【Java-Crawler】SpringBoot集成WebMagic实现爬虫出现的问题集(一)

SpringBoot集成WebMagic实现爬虫出现的问题集&#xff08;一&#xff09; 一、SpringBoot集成WebMagic框架日志异常问题及解决方案二、使用 Firefox 驱动&#xff08;geckodriver&#xff09;三、设置WebMagic中site中的User-Agent&#xff08;避免反爬虫&#xff09; 一、Spri…

【网络编程】demo版TCP网络服务器实现

文章目录 一、引入二、服务端实现2.1 创建套接字socket2.2 绑定bind2.3 设置监听状态listen2.4 获取新链接accept2.5 获取信息与返回信息&#xff08;文件操作&#xff09; 三、客户端实现3.1 创建套接字socket3.2 绑定问题3.3 发起链接connect3.4 客户端并行3.4.1 多进程版3.4…

公网远程访问本地Jupyter Notebook服务

文章目录 前言视频教程1. Python环境安装2. Jupyter 安装3. 启动Jupyter Notebook4. 远程访问4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5. 固定公网地址 转载自cpolar的文章&#xff1a;公网远程访问Jupyter Notebook【Cpolar内网穿透】 前言 Jupyter Notebook&am…

针对UDP协议的攻击与防御

一、UDP协议概述 UDP&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09;是TCP/IP协议栈中的一种无连接的传输协议&#xff0c;能够提供面向事务的简单不可靠数据传输服务。 1&#xff0e;UDP的报文格式 UDP的报文格式如图1所示。 图1 UDP报文格式 …

怎么在pdf文件上添加水印

怎么在pdf文件上添加水印&#xff1f;PDF添加水印是一种十分实用的方式&#xff0c;可以大大提高PDF文档的安全性和防护能力。在实际操作中&#xff0c;我们可以根据具体需求在PDF文件的各个页面上添加水印。这样即使你的PDF文件被他人恶意盗用&#xff0c;也可以快速、准确地找…

堤防安全自动化监测系统

项目背景 我国河系众多&#xff0c;海岸线漫长&#xff0c;在江边、海边修筑修筑着几万公里的提防设施保卫着沿江、沿海居民的生命安全&#xff0c;也保卫着经济发展的累累硕果。近年来&#xff0c;政府加大了堤防建设改造力度&#xff0c;提高了部分堤段的防洪能力。同时&…

统信UOS系统开发笔记(一):国产统信UOS系统搭建开发环境之虚拟机安装

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/130876940 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…

vue 弹窗实现方法

Vue实现弹窗的方法有很多种&#xff0c;这里给出一个简单的示例&#xff1a; 1. 首先&#xff0c;在Vue项目中创建一个名为Modal.vue的组件文件&#xff1a; html <template> <div class"modal-mask" v-show"visible" click.self"close"…

基于遗传算法和非线性规划的函数寻优算法(matlab实现)

以下内容大部分来源于《MATLAB智能算法30个案例分析》&#xff0c;仅为学习交流所用。 1 理论基础 1.1 非线性规划 非线性规划是20世纪50年代形成的一门新兴学科。1951年库恩和塔克发表的关于最优性条件(后来称为库恩塔克条件)的论文是非线性规划诞生的标志。非线性规划研究…

报表控件FastReport使用指南-在Ubuntu LTS中创建PDF文档

FastReport 是功能齐全的报表控件&#xff0c;可以帮助开发者可以快速并高效地为.NET&#xff0c;VCL&#xff0c;COM&#xff0c;ActiveX应用程序添加报表支持&#xff0c;由于其独特的编程原则&#xff0c;现在已经成为了Delphi平台最优秀的报表控件&#xff0c;支持将编程开…

如何避免Salesforce Apex代码中5个常见错误,提升开发技巧?

编码是一门需要严谨和谨慎的技术&#xff0c;即使是有经验的开发人员也会犯错。一些最常见的编程错误&#xff0c;可能会导致严重的后果。因此&#xff0c;作为一名开发人员&#xff0c;了解并避免这些错误是非常重要的。 本篇文章将为学习者介绍在编写Apex代码时一定要规避的…

Java核心锁基准测试

测试模型 基于JMH基准测试库 测试代码 package com.lsy.study.benchmark;import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.results.format.ResultFormatType; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.options.Options; import…

《人工智能算法案例大全:基于Python》——实践AI算法,驭智创新之路

导语 随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;AI算法成为推动智能化进程的核心要素。而在这个领域中&#xff0c;一本名为《人工智能算法案例大全&#xff1a;基于Python》的书籍引起了广泛关注。本文将深入探讨这本书所呈现的丰富案例&#xff0c;…

Linux——网络套接字2|Tcp服务器编写

本篇博客先看后面的代码,再回来看上面这些内容。 .hpp文件,基本调用 服务器基本框架

Ubuntu下Docker部署Gitlab CI

1. ubuntu gitlab安装步骤 1.1 更新系统软件包列表&#xff1a; sudo apt update1.2 安装必要的依赖项&#xff1a; sudo apt install curl openssh-server ca-certificates tzdata perl1.3 下载并安装 GitLab 包&#xff1a; curl -LO https://packages.gitlab.com/instal…

Springboot2.5.x版本之自动创建(H2/DERBY/HSQL)数据源源码分析-yellowcong

场景&#xff1a;当我们没有配置mysql&#xff0c;postgresql等数据源的时候&#xff0c;pom.xml里面引入了H2/DERBY/HSQL jar包&#xff0c;也没有配置连接&#xff0c;却有数据源创建的情况。 springboot启动的第一步 1.DataSourceAutoConfiguration 配置类启动 2.DataSource…

DolphinScheduler远程启动任务

我本地有JAVA程序&#xff0c;需要调用DolphinScheduler的接口启动任务&#xff0c;动态去调用 1、DolphinScheduler的内容逻辑关系 先明确DolphinScheduler内部任务的逻辑关系 项目 |——工作流 |——任务&#xff08;节点&#xff09; 我起的是工作流&#xff0c;一个任务完…