Spring Boot + MySQL 多线程查询与联表查询性能对比分析

Spring Boot + MySQL: 多线程查询与联表查询性能对比分析

背景

在现代 Web 应用开发中,数据库性能是影响系统响应时间和用户体验的关键因素之一。随着业务需求的不断增长,单表查询和联表查询的效率问题日益凸显。特别是在 Spring Boot 项目中,结合 MySQL 数据库进行复杂查询时,如何优化查询性能已成为开发者必须面对的重要问题。

在本实验中,我们使用了 Spring Boot 框架结合 MySQL 数据库,进行了两种常见查询方式的性能对比:多线程查询联表查询。通过对比这两种查询方式的响应时间,本文旨在探讨在实际业务场景中,选择哪种方式能带来更高的查询效率,尤其是在面对大数据量和复杂查询时的性能表现。


实验目的

本实验的主要目的是通过对比以下两种查询方式的性能,帮助开发者选择在不同业务场景下的查询方式:

  1. 联表查询(使用 SQL 语句中的 LEFT JOIN 等连接操作)
  2. 多线程查询(通过 Spring Boot 异步处理,分批查询不同表的数据)

实验环境

  • 开发框架:Spring Boot

  • 数据库:MySQL

  • 数据库表结构

    • test_a:主表,包含与其他表(test_btest_ctest_dtest_e)的关联字段。
    • test_btest_ctest_dtest_e:附表,分别包含不同的数据字段。

    这些表通过外键关联,test_a 表中的 test_b_idtest_c_idtest_d_idtest_e_id 字段指向各自的附表。

  • 数据量:约 100,000 条数据,分别在主表和附表中填充数据。

一.建表语句

主表A

CREATE TABLE `test_a` (`id` int NOT NULL AUTO_INCREMENT,`name` varchar(255) NOT NULL,`description` varchar(255) DEFAULT NULL,`test_b_id` int DEFAULT NULL,`test_c_id` int DEFAULT NULL,`test_d_id` int DEFAULT NULL,`test_e_id` int DEFAULT NULL,`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

附表b,c,d,e

CREATE TABLE `test_b` (`id` int NOT NULL AUTO_INCREMENT,`field_b1` varchar(255) DEFAULT NULL,`field_b2` int DEFAULT NULL,`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=792843462 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;CREATE TABLE `test_c` (`id` int NOT NULL AUTO_INCREMENT,`field_c1` varchar(255) DEFAULT NULL,`field_c2` datetime DEFAULT NULL,`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100096 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;CREATE TABLE `test_d` (`id` int NOT NULL AUTO_INCREMENT,`field_d1` text,`field_d2` tinyint(1) DEFAULT NULL,`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100300 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;CREATE TABLE `test_e` (`id` int NOT NULL AUTO_INCREMENT,`field_e1` int DEFAULT NULL,`field_e2` varchar(255) DEFAULT NULL,`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100444 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

二.填充数据

@SpringBootTest
class DemoTestQuerySpringbootApplicationTests {@Autowiredprivate TestAMapper testAMapper;@Autowiredprivate TestBMapper testBMapper;@Autowiredprivate TestCMapper testCMapper;@Autowiredprivate TestDMapper testDMapper;@Autowiredprivate TestEMapper testEMapper;@Testvoid contextLoads() {// 随机数生成器Random random = new Random();for (int i = 1; i <= 100000; i++) {// 插入 test_b 数据int testBId = insertTestB(random);// 插入 test_c 数据int testCId = insertTestC(random);// 插入 test_d 数据int testDId = insertTestD(random);// 插入 test_e 数据int testEId = insertTestE(random);// 插入 test_a 数据insertTestA(testBId, testCId, testDId, testEId, random);}}private int insertTestB(Random random) {TestB testB = new TestB();testB.setFieldB1("B Field " + random.nextInt(1000));testB.setFieldB2(random.nextInt(1000));testBMapper.insert(testB);  // 插入数据return testB.getId();  }private int insertTestC(Random random) {TestC testC = new TestC();testC.setFieldC1("C Field " + random.nextInt(1000));testC.setFieldC2(new java.sql.Timestamp(System.currentTimeMillis()));testCMapper.insert(testC);  // 插入数据return testC.getId();  }private int insertTestD(Random random) {TestD testD = new TestD();testD.setFieldD1("D Field " + random.nextInt(1000));testD.setFieldD2(random.nextBoolean());testDMapper.insert(testD);  // 插入数据return testD.getId();  }private int insertTestE(Random random) {TestE testE = new TestE();testE.setFieldE1(random.nextInt(1000));testE.setFieldE2("E Field " + random.nextInt(1000));testEMapper.insert(testE);  // 插入数据return testE.getId();  }private void insertTestA(int testBId, int testCId, int testDId, int testEId, Random random) {TestA testA = new TestA();testA.setName("Test A Name " + random.nextInt(1000));testA.setDescription("Test A Description " + random.nextInt(1000));testA.setTestBId(testBId);testA.setTestCId(testCId);testA.setTestDId(testDId);testA.setTestEId(testEId);testAMapper.insert(testA);  // 插入数据}}

三.配置线程池

3.1配置

/*** 实现AsyncConfigurer接口* 并重写了 getAsyncExecutor方法,* 这个方法返回 myExecutor(),* Spring 默认会将 myExecutor 作为 @Async 方法的线程池。*/
@Configuration
@EnableAsync
public class ThreadPoolConfig implements AsyncConfigurer {/*** 项目共用线程池*/public static final String TEST_QUERY = "testQuery";@Overridepublic Executor getAsyncExecutor() {return myExecutor();}@Bean(TEST_QUERY)@Primarypublic ThreadPoolTaskExecutor myExecutor() {//spring的线程池ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//线程池优雅停机的关键executor.setWaitForTasksToCompleteOnShutdown(true);executor.setCorePoolSize(10);executor.setMaxPoolSize(10);executor.setQueueCapacity(200);executor.setThreadNamePrefix("my-executor-");//拒绝策略->满了调用线程执行,认为重要任务executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//自己就是一个线程工程executor.setThreadFactory(new MyThreadFactory(executor));executor.initialize();return executor;}}

3.2异常处理

public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {private static final Logger log = LoggerFactory.getLogger(MyUncaughtExceptionHandler.class);@Overridepublic void uncaughtException(Thread t, Throwable e) {log.error("Exception in thread",e);}
}

3.3线程工厂

@AllArgsConstructor
public class MyThreadFactory implements ThreadFactory {private static final MyUncaughtExceptionHandler MyUncaughtExceptionHandler = new MyUncaughtExceptionHandler();private ThreadFactory original;@Overridepublic Thread newThread(Runnable r) {//执行Spring线程自己的创建逻辑Thread thread = original.newThread(r);//我们自己额外的逻辑thread.setUncaughtExceptionHandler(MyUncaughtExceptionHandler);return thread;}
}

四.Service查询方法

4.1left join连接查询

    @Overridepublic IPage<TestAll> getTestAllPage_1(int current, int size) {// 创建 Page 对象,current 为当前页,size 为每页大小Page<TestAll> page = new Page<>(current, size);return testAMapper.selectAllWithPage(page);}

对应的xml 的sql语句

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.fth.demotestqueryspringboot.com.test.mapper.TestAMapper"><!-- 基本的 ResultMap 映射 --><resultMap id="BaseResultMap" type="org.fth.demotestqueryspringboot.com.test.entity.vo.TestAll"><id column="test_a_id" jdbcType="INTEGER" property="testAId" /><result column="name" jdbcType="VARCHAR" property="name" /><result column="description" jdbcType="VARCHAR" property="description" /><result column="test_b_id" jdbcType="INTEGER" property="testBId" /><result column="test_c_id" jdbcType="INTEGER" property="testCId" /><result column="test_d_id" jdbcType="INTEGER" property="testDId" /><result column="test_e_id" jdbcType="INTEGER" property="testEId" /><result column="created_at" jdbcType="TIMESTAMP" property="createdAt" /><result column="updated_at" jdbcType="TIMESTAMP" property="updatedAt" /><!-- TestB --><result column="field_b1" jdbcType="VARCHAR" property="fieldB1" /><result column="field_b2" jdbcType="INTEGER" property="fieldB2" /><result column="test_b_created_at" jdbcType="TIMESTAMP" property="testBCreatedAt" /><!-- TestC --><result column="field_c1" jdbcType="VARCHAR" property="fieldC1" /><result column="field_c2" jdbcType="TIMESTAMP" property="fieldC2" /><result column="test_c_created_at" jdbcType="TIMESTAMP" property="testCCreatedAt" /><!-- TestD --><result column="field_d1" jdbcType="VARCHAR" property="fieldD1" /><result column="field_d2" jdbcType="BOOLEAN" property="fieldD2" /><result column="test_d_created_at" jdbcType="TIMESTAMP" property="testDCreatedAt" /><!-- TestE --><result column="field_e1" jdbcType="INTEGER" property="fieldE1" /><result column="field_e2" jdbcType="VARCHAR" property="fieldE2" /><result column="test_e_created_at" jdbcType="TIMESTAMP" property="testECreatedAt" /></resultMap><!-- 分页查询 TestA 和其他表的数据 --><select id="selectAllWithPage" resultMap="BaseResultMap">SELECTa.id AS test_a_id,a.name,a.description,a.test_b_id,a.test_c_id,a.test_d_id,a.test_e_id,a.created_at,a.updated_at,-- TestBb.field_b1,b.field_b2,b.created_at AS test_b_created_at,-- TestCc.field_c1,c.field_c2,c.created_at AS test_c_created_at,-- TestDd.field_d1,d.field_d2,d.created_at AS test_d_created_at,-- TestEe.field_e1,e.field_e2,e.created_at AS test_e_created_atFROM test_a aLEFT JOIN test_b b ON a.test_b_id = b.idLEFT JOIN test_c c ON a.test_c_id = c.idLEFT JOIN test_d d ON a.test_d_id = d.idLEFT JOIN test_e e ON a.test_e_id = e.id</select></mapper>

4.2多线程查询

 @Overridepublic IPage<TestAll> getTestAllPage_2(int current, int size) {IPage<TestA> testAPage = testAMapper.selectPage(new Page<>(current, size), null);List<TestA> testAS = testAPage.getRecords();CompletableFuture<List<TestB>> futureBs = selectTestBids(testAS.stream().map(TestA::getTestBId).collect(Collectors.toSet()));CompletableFuture<List<TestC>> futureCs = selectTestCids(testAS.stream().map(TestA::getTestCId).collect(Collectors.toSet()));CompletableFuture<List<TestD>> futureDs = selectTestDids(testAS.stream().map(TestA::getTestDId).collect(Collectors.toSet()));CompletableFuture<List<TestE>> futureEs = selectTestEids(testAS.stream().map(TestA::getTestEId).collect(Collectors.toSet()));// 等待所有异步任务完成并收集结果CompletableFuture<Void> allFutures = CompletableFuture.allOf(futureBs, futureCs, futureDs, futureEs);try {// 等待所有异步任务完成allFutures.get();} catch (InterruptedException | ExecutionException e) {e.printStackTrace();throw new RuntimeException("Failed to fetch data", e);}// 获取异步查询的结果List<TestB> bs = futureBs.join();List<TestC> cs = futureCs.join();List<TestD> ds = futureDs.join();List<TestE> es = futureEs.join();// 将结果映射到Map以便快速查找Map<Integer, TestB> bMap = bs.stream().collect(Collectors.toMap(TestB::getId, b -> b));Map<Integer, TestC> cMap = cs.stream().collect(Collectors.toMap(TestC::getId, c -> c));Map<Integer, TestD> dMap = ds.stream().collect(Collectors.toMap(TestD::getId, d -> d));Map<Integer, TestE> eMap = es.stream().collect(Collectors.toMap(TestE::getId, e -> e));List<TestAll> testAllList = testAS.stream().map(testA -> {TestAll testAll = new TestAll();testAll.setTestAId(testA.getId());testAll.setName(testA.getName());testAll.setDescription(testA.getDescription());testAll.setCreatedAt(testA.getCreatedAt());// 根据 testBId 填充 TestB 的字段if (testA.getTestBId() != null) {TestB testB = bMap.get(testA.getTestBId());if (testB != null) {testAll.setFieldB1(testB.getFieldB1());testAll.setFieldB2(testB.getFieldB2());testAll.setTestBCreatedAt(testB.getCreatedAt());}}// 根据 testCId 填充 TestC 的字段if (testA.getTestCId() != null) {TestC testC = cMap.get(testA.getTestCId());if (testC != null) {testAll.setFieldC1(testC.getFieldC1());testAll.setFieldC2(testC.getFieldC2());testAll.setTestCCreatedAt(testC.getCreatedAt());}}// 根据 testDId 填充 TestD 的字段if (testA.getTestDId() != null) {TestD testD = dMap.get(testA.getTestDId());if (testD != null) {testAll.setFieldD1(testD.getFieldD1());testAll.setFieldD2(testD.getFieldD2());testAll.setTestDCreatedAt(testD.getCreatedAt());}}// 根据 testEId 填充 TestE 的字段if (testA.getTestEId() != null) {TestE testE = eMap.get(testA.getTestEId());if (testE != null) {testAll.setFieldE1(testE.getFieldE1());testAll.setFieldE2(testE.getFieldE2());testAll.setTestECreatedAt(testE.getCreatedAt());}}return testAll;}).collect(Collectors.toList());// 创建并返回新的分页对象IPage<TestAll> page = new Page<>(testAPage.getCurrent(), testAPage.getSize(), testAPage.getTotal());page.setRecords(testAllList);return page;}@Asyncpublic CompletableFuture<List<TestB>> selectTestBids(Set<Integer> bids) {return CompletableFuture.supplyAsync(() -> testBMapper.selectBatchIds(bids));}@Asyncpublic CompletableFuture<List<TestC>> selectTestCids(Set<Integer> cids) {return CompletableFuture.supplyAsync(() -> testCMapper.selectBatchIds(cids));}@Asyncpublic CompletableFuture<List<TestD>> selectTestDids(Set<Integer> dids) {return CompletableFuture.supplyAsync(() -> testDMapper.selectBatchIds(dids));}@Asyncpublic CompletableFuture<List<TestE>> selectTestEids(Set<Integer> eids) {return CompletableFuture.supplyAsync(() -> testEMapper.selectBatchIds(eids));}

五.结果测试

5.1连接查询

在这里插入图片描述

在这里插入图片描述

查询结果表格

currentsize响应时间
12016ms
502023ms
1002022ms
5002052ms
200200213ms
500200517ms

5.2多线程查询

在这里插入图片描述

在这里插入图片描述

查询结果表格

currentsize响应时间
12018ms
502017ms
1002017ms
5002021ms
20020056ms
50020080ms

总结与建议

  • 选择联表查询:当数据量较小,或者查询逻辑较为简单时,使用联表查询可以更简单直接,查询性能也较为优秀。
  • 选择多线程查询:当面对大数据量或者复杂查询时,采用多线程查询将带来更显著的性能提升。通过异步并行查询,可以有效缩短响应时间,提升系统的整体性能。

在实际开发中,可以根据具体的业务需求和数据库的规模,合理选择查询方式,从而提高数据库查询效率,优化系统性能

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

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

相关文章

Navicat 连接 SQL Server 详尽指南

Navicat 是一款功能强大的数据库管理工具&#xff0c;它提供了直观的图形界面&#xff0c;使用户能够轻松地管理和操作各种类型的数据库&#xff0c;包括 SQL Server。本文将详尽介绍如何使用 Navicat 连接到 SQL Server 数据库&#xff0c;包括安装设置、连接配置、常见问题排…

【多模型能力测试记录】ArgoDB分布式分析型数据库与图数据库StellarDB联合查询

前言 随着数据量的爆炸性增长和业务需求的日益复杂化&#xff0c;传统的单一模型数据库已经难以满足复杂多变的业务需求。尽管当前针对不同的数据类型&#xff0c;例如关系型数据、文档数据、图数据和时序数据业内提供了多种数据库以应对存储及处理需求&#xff0c;但是在实际…

Ansible自动化运维

1 ansible介绍和架构 1.1 什么是ansible ansible是新出现的自动化运维工具&#xff0c;基于Python开发&#xff0c;集合了众多运维工具&#xff08;puppet、chef、func、fabric&#xff09;的优点&#xff0c;实现了批量系统配置、批量程序部署、批量运行命令等功能。 ansible…

玩FPGA不乏味

玩FPGA不乏味 Hello&#xff0c;大家好&#xff0c;之前给大家分享了大约一百多个关于FPGA的开源项目&#xff0c;涉及PCIe、网络、RISC-V、视频编码等等&#xff0c;这次给大家带来的是不枯燥的娱乐项目&#xff0c;主要偏向老的游戏内核使用FPGA进行硬解&#xff0c;涉及的内…

工商业光伏系统踏勘、设计、施工全流程讲解

随着全球能源结构的转型和环保意识的提升&#xff0c;光伏发电作为一种清洁、可再生的能源形式&#xff0c;正越来越受到工商业领域的青睐。商场、学校、医院、各类工厂等地&#xff0c;安装光伏发电系统不仅能降低运营成本&#xff0c;还可以为企业树立良好的环保形象。 一、前…

mongo开启慢日志及常用命令行操作、数据备份

mongo开启慢日志及常用命令行操作、数据备份 1.常用命令行操作2.mongo备份3.通过命令临时开启慢日志记录4.通过修改配置开启慢日志记录 1.常用命令行操作 连接命令行 格式&#xff1a;mongo -u用户名 -p密码 --host 主机地址 --port 端口号 库名&#xff1b; 如&#xff1a;连…

Vue跨标签通讯(本地存储)(踩坑)

我司有一个需求【用户指引】 需求是根标签有一个用户指引总开关&#xff0c;可以控制页面所有的用户指引是否在页面进入后初始是否默认打开&#xff0c;但是有些页面会新开标签这就设计到跨标签通讯了 我采取的方案是本地存储 重点:首先本地存储在页面是同源(即域名协议端口三…

Scrapy解析JSON响应v

在 Scrapy 中解析 JSON 响应非常常见&#xff0c;特别是当目标网站的 API 返回 JSON 数据时。Scrapy 提供了一些工具和方法来轻松处理 JSON 响应。 1、问题背景 Scrapy中如何解析JSON响应&#xff1f; 有一只爬虫(点击查看源代码)&#xff0c;它可以完美地完成常规的HTML页面…

机器学习生物医学

Nature与Science重磅&#xff01;AI与生物医药迎来百年来最重磅进展&#xff01;https://mp.weixin.qq.com/s/Vw3Jm4vVKP14_UH2jqwsxA 第一天 机器学习及生物医学中应用简介 1. 机器学习及生物医学中应用简介 2. 机器学习基本概念介绍 3. 常用机器学习模型介绍&#xff0…

ISIS五

L1路由器的次优路径问题 路由渗透 可以打标签 等价路由上面下面都把骨干区域引入非骨干 强制ATT位不置位为1 attached-bit advertise never 在AR2上禁止ATT置位为1 在AR3没有禁止呀还是有默认路由 ISIS选路机制&#xff1a; L1的路由优于L2的路由 星号bit 叫DU-bit 知道…

BFC的理解

BFC的理解 BFC是什么&#xff1f;BFC如何触发&#xff1f;BFC的作用问题解决Margin重叠问题Margin塌陷问题高度塌陷 BFC是什么&#xff1f; BFC是块级格式上下文&#xff08;Block Formatting Context&#xff09;&#xff0c;是CSS布局的一个概念&#xff0c;在BFC布局里面的…

C++入门基础

一、C的第一个程序 C兼容C语⾔绝大多数的语法&#xff0c;所以C语言实现的hello world依旧可以运行&#xff0c;C中需要把定义⽂件 代码后缀改为.cpp&#xff0c;vs编译器看到是.cpp就会调⽤C编译器编译&#xff0c;linux下要⽤g编译&#xff0c;不再是gcc #include<stdio.h…

VMware 安装国产操作系统UOS过程

VMware是一个虚拟化的平台&#xff0c;在这个平台上能训练操作系统&#xff08;客户端版本和服务器端版本&#xff09;&#xff0c;在真机的条件下虚拟出更多的应用场景。&#xff08;如果你的硬件设备足够强悍&#xff0c;可以通常这个平台虚拟出256个终端&#xff08;可能会更…

仿蝠鲼软体机器人实现高速多模态游动

近期&#xff0c;华南理工大学周奕彤老师研究团队最新成果"Manta Ray-Inspired Soft Robotic Swimmer for High-speed and Multi-modal Swimming"被机器人领域会议 IEEE/RSJ International Conference on Intelligent Robots and Systems&#xff08;IROS 2024&#…

稀土阻燃剂:电子设备的安全守护者

稀土阻燃剂是一类以稀土元素为基础的阻燃材料&#xff0c;广泛应用于电子设备中&#xff0c;主要用于提高材料的阻燃性能和热稳定性&#xff0c;以满足现代电子设备对安全性和可靠性的要求。稀土阻燃剂在电子设备中的应用具有以下特点&#xff1a; 1. 电路板&#xff1a;稀土阻…

Issue id: AppLinkUrlError 应用intent-filter 配置深链接 URL 问题分析 | AndroidManifest

AndroidManifest.xml 配置文件中&#xff0c;对 activity 组件进行声明的时候&#xff0c;独立应用在 IDE 显示 intent-filter 报错&#xff0c;但不影响实际编译&#xff0c;因为是系统应用&#xff0c;肯定会有此 URL 的存在。 AOSP 源码&#xff1a; <activity android:…

QT 中 sqlite 数据库使用

一、前提 --pro文件添加sql模块QT core gui sql二、使用 说明 --用于与数据库建立连接QSqlDatabase--执行各种sql语句QSqlQuery--提供数据库特定的错误信息QSqlError查看qt支持的驱动 QStringList list QSqlDatabase::drivers();qDebug()<<list;连接 sqlite3 数据库 …

扫二维码进小程序的指定页面

草料二维码解码器 微信开发者工具 获取二维码解码的参数->是否登陆->跳转 options.q onLoad: function (options) {// console.log("options",options.q)if (options && options.q) {// 解码二维码携带的链接信息let qrUrl decodeURIComponent(optio…

微信小程序介绍-以及写项目流程(重要)

前言&#xff1a;本篇文章介绍微信小程序以及项目介绍&#xff1a; 文章介绍&#xff1a;介绍了微信小程序常用的指令、组件、api。tips&#xff1a;最好按照官方文档来进行学习&#xff0c;大致可以我的目录来学习&#xff0c;对于写项目是没有问题的 微信小程序官方文档https…

Apache Dolphinscheduler可视化 DAG 工作流任务调度系统

Apache Dolphinscheduler 关于 一个分布式易扩展的可视化 DAG 工作流任务调度系统。致力于解决数据处理流程中错综复杂的依赖关系&#xff0c;使调度系统在数据处理流程中开箱即用。 DolphinScheduler 的主要特性如下&#xff1a; 易于部署&#xff0c;提供四种部署方式&am…