Flink作业执行之 4.JobGraph

news2024/11/25 10:33:36

Flink作业执行之 4.JobGraph

1. 入口

前文了解了由Transformation到StreamGraph的过程,StreamGraph即作业的逻辑拓扑结构。
生成逻辑结构后,接下来的操作往往是对逻辑结构的优化。在很多组件中都是这样的处理,如hive、spark等都会执行“逻辑计划->优化计划->物理计划”的处理过程。

从StreamGraph到JobGraph的过程中很重要的事情是将节点组成节点链。

env.execute()方法中调用executeAsync方法完成JobGraph实例的生成。

@Internal
public JobClient executeAsync(StreamGraph streamGraph) throws Exception {
    checkNotNull(streamGraph, "StreamGraph cannot be null.");
    // 根据启动方式得到具体的PipelineExecutor子实例
    final PipelineExecutor executor = getPipelineExecutor();

    CompletableFuture<JobClient> jobClientFuture =
            // execute方法内生成JobGraph,并提到了JobManager中
            executor.execute(streamGraph, configuration, userClassloader);

    // ...
}

PipelineExecutor接口根据提供的配置执行管道。接口内只有一个execute方法。execute方法负责两件事,首先创建JobGraph,然后提交JobGraph到JobManager。

接口实现体系如下,分别对应不同的作业提交方式。

在这里插入图片描述
在IDEA中启动作业时,使用的是LocalExecutor,其execute方法实现如下。

@Override
public CompletableFuture<JobClient> execute(
        Pipeline pipeline, Configuration configuration, ClassLoader userCodeClassloader)
        throws Exception {
    // ... 
    // 生成JobGraph实例
    final JobGraph jobGraph = getJobGraph(pipeline, effectiveConfig, userCodeClassloader);
    // 将JobGraph提交到集群中
    return PerJobMiniClusterFactory.createWithFactory(effectiveConfig, miniClusterFactory)
            .submitJob(jobGraph, userCodeClassloader);
}

不同的ipelineExecutor实现,提交JobGraph的方式所有不同,但是创建JobGrpah的方式是相同。均使用FlinkPipelineTranslatortranslateToJobGraph方式完成。

2. StreamGraphTranslator

FlinkPipelineTranslator接口负责将Pipeline转化为JobGraph。根据批或流场景分别有不同的实现。

在这里插入图片描述

StreamingJobGraphGenerator负责完成StreamGraph到JobGraph的转换。

// FlinkPipelineTranslator(StreamGraphTranslator)
public JobGraph translateToJobGraph(
        Pipeline pipeline, Configuration optimizerConfiguration, int defaultParallelism) {
    checkArgument(
            pipeline instanceof StreamGraph, "Given pipeline is not a DataStream StreamGraph.");

    //调用StreamGraph方法
    StreamGraph streamGraph = (StreamGraph) pipeline;
    return streamGraph.getJobGraph(userClassloader, null);
}

// StreamGraph
public JobGraph getJobGraph(ClassLoader userClassLoader, @Nullable JobID jobID) {
    return StreamingJobGraphGenerator.createJobGraph(userClassLoader, this, jobID);
}

回顾TransformationTranslator负责Transformation到StreamGraph转换,可以暂时归纳为,一般使用AaTranslator的封装完成由Aa到Bb的转换。方便对源码中某些类的理解。
StreamGraph和JobGraph生成过程封装类的相似之处,二者都提供了相应的Generator和Translator封装类,不同之处在于调用顺序的不同。

在这里插入图片描述

3. JobGraph

JobGraph表示flink数据流程序,处于JobManager接受的低级别。来自更高级别所API的所有程序都将转化为JobGraph。

JobGraph是从Client端到JobManager的最后一层封装,无论批或流作业都会转成JobGraph之后提交到JobManager。目的是为了统一处理批和流处理。

JobGraph同样表示DAG,包含节点JobVertex和边JobEdge。与StreamGraph相比,还多了表示中间结果集的IntermediateDataSet。因此本质上是将节点和中间结果集相连得到DAG。
IntermediateDataSet由JobVertex产生,由JobEdge消费其中的数据。

JobGraph中节点、边和中间结果集的关系示意如下。

在这里插入图片描述

节点之间的联系如下。

在这里插入图片描述
JobGraph中重要属性如下。

// job id和名称
private JobID jobID;
private final String jobName;
// 作业类型,流或批
private JobType jobType = JobType.BATCH;
// JobGraph中包含的节点
private final Map<JobVertexID, JobVertex> taskVertices = new LinkedHashMap<JobVertexID, JobVertex>();

3.1. JobVertex

JobGraph中的节点,会将符合条件的多个StreamNode链接在一起生成一个JobVertex。

JobVertex中重要属性如下。

// id,使用hash值表示,区别于StreamNode的id,StreamNode的id直接使用Transformation的id,是自增数字。
private final JobVertexID id;
// 名称
private String name;
// 输入,节点所有的输入边
private final ArrayList<JobEdge> inputs = new ArrayList<>();
// 输出,节点输出的中间结果集
private final Map<IntermediateDataSetID, IntermediateDataSet> results = new LinkedHashMap<>();
// 节点中包含的全部算子id,按深度优先的顺序对节点进行存储
private final List<OperatorIDPair> operatorIDs;
// taskManager根据该类名创建执行任务的类的对象(反射)。取StreamNode中vertexClass值,即StreamTask子类。
private String invokableClassName;

JobVertex使用JobEdge表示输入,使用IntermediateDataSet表示输出。这点区别于StreamNode,StreamNode并没有严格的输入输出概念,只有上下游概念。

operatorIDs中元素个数等于JobVertex中包含的StreamNede节点个数。
OperatorIDPair类用于维护JobVertex中的节点信息,包含两个属性,表示节点id。

// 节点hash值
private final OperatorID generatedOperatorID;
private final OperatorID userDefinedOperatorID;

3.2. IntermediateDataSet

逻辑结构,表示JobVertex的输出,JobVertex产生结果集的个数与对应SteamNode的出边相同。

// id,类型同JobVertexID
private final IntermediateDataSetID id;
// 产生该结果集的JobVertex,在构造方法中完成赋值
private final JobVertex producer;
// 下游消费结果集的JobEdge,产生JobEdge后调用其addConsumer方法赋值
private final List<JobEdge> consumers = new ArrayList<>();

3.3. JobEdge

JobGraph中的JobEdge除表示边含义,连接上游生产的中间数据集和下游的JobVertex之外,更多的表示一种通信通道,上游JobVertex生产的中间结果集,由JobEdge消费数据至下游JobVertex。

// 下游节点
private final JobVertex target;
// 上游结果集
private final IntermediateDataSet source;
// 边的分布模式,决定生产任务的哪些子任务连接到那些消费子任务,
// ALL_TO_ALL:每个下游任务都消费这条边的数据,每个下游任务都消费这条边的数据,
// POINTWISE:一个或多个下游任务消费这条边的数据
private final DistributionPattern distributionPattern;

4. 生成JobGraph

4.1. StreamingJobGraphGenerator

上文提到在流场景中,StreamingJobGraphGenerator负责完成StreamGraph到JobGraph的转换。接下来重点了解下实现过程。

StreamingJobGraphGenerator中关键字段如下,主要为表示链接的信息。

// 生成JobGraph的streamGraph
private final StreamGraph streamGraph;
// 结果JobGraph
private final JobGraph jobGraph;
// JobGraph中的节点集合,key=StreamNode id
private final Map<Integer, JobVertex> jobVertices;
// 已经处理完成的StreamNode id
private final Collection<Integer> builtVertices;
// 物理边,无法连接的两个StreaNode之间的边将成为物理边
private final List<StreamEdge> physicalEdgesInOrder;
// 保存链接信息
private final Map<Integer, Map<Integer, StreamConfig>> chainedConfigs;
// 保存链接的名称
private final Map<Integer, String> chainedNames;
// 保存节点信息
private final Map<Integer, StreamConfig> vertexConfigs;

createJobGraph方法中完成JobGraph的转换,该方法中涉及的内容较多。节点、边和中间结果集的创建将由setChaining方法完成。

4.1.1. 创建JobVertex

而StreamNode的id直接使用Transformation的id是自增数字,而JobVertex使用根StreamNode生成的hash值作为id,这点有所不同。因此首先会计算全部StreamNode的hash值,并维护StreamNode id和hash值的映射关系。为了向后兼容,还会为每个节点生成老版本的Hash值。

在代码中可以看到hasheslegacyHashes两个集合变量,分别存储新和老的hash值映射关系。

以StreamNode中的Source节点作为起点,通过递归方式处理全部节点。将连续且可进行连接的节点组合在一起生成一个JobVertex节点。举个例子,假如有上下游关系为1->2->3->4->5的节点,且3到4之间进行keyBy等操作,则1、2和3将链接在一起生成一个JobVertex节点,4和5将生成一个JobVertex节点。

通过Map<Integer, OperatorChainInfo>集合维护每个JobVertex中全部StreamNode的节点关系。
key为起点StreamNode id,key个数将和作业中生成算子链个数一致,初始时会将Source节点加入到集合中。
value放入一个封装类,用于createChain方法递归调用期间帮助维护operator chain的信息。

OperatorChainInfo类是定义在StreamingJobGraphGenerator中的内部类,其chainedOperatorHashes属性表示同一个算子链中全部的算子连接关系。

private static class OperatorChainInfo {
    // OperatorChain的起点id
    private final Integer startNodeId;
    // 上文提到的StreamNode id和hash值的映射关系
    private final Map<Integer, byte[]> hashes;
    // 老版本的hash值映射关系
    private final List<Map<Integer, byte[]>> legacyHashes;
    // 链接到一起的算子关系,key是算子链的开始节点,list部分存放链接关系
    // f0是当前节点的hash值,f1是legacyHashes相关的数据,大多数为null
    private final Map<Integer, List<Tuple2<byte[], byte[]>>> chainedOperatorHashes;
    private final Map<Integer, ChainedSourceInfo> chainedSources;
    private final List<OperatorCoordinator.Provider> coordinatorProviders;
    private final StreamGraph streamGraph;

    private OperatorChainInfo(
            int startNodeId,
            Map<Integer, byte[]> hashes,
            List<Map<Integer, byte[]>> legacyHashes,
            Map<Integer, ChainedSourceInfo> chainedSources,
            StreamGraph streamGraph) {
        this.startNodeId = startNodeId;
        this.hashes = hashes;
        this.legacyHashes = legacyHashes;
        this.chainedOperatorHashes = new HashMap<>();
        this.coordinatorProviders = new ArrayList<>();
        this.chainedSources = chainedSources;
        this.streamGraph = streamGraph;
    }
}

createChain方法负责递归操作,递归过程为后序位置的处理方式,后序位置指的是先向下进行递归调用,直到不满足继续递归条件为止,然后按照递归过程由内向外方向执行每个节点的处理逻辑,后序位置的递归示意如下。

public void recursion(){
    if(condition == true){
        // 递归调用
        recursion();
    }
    // 处理逻辑
    // ...
}

createChain方法内部通过3个集合变量来维护节点下游信息。
每次递归处理时,依次判断与当前节点直接相连的下游节点是否可以与当前节点连接到一起,并分别加入到各自的集合中。然后对各自集合进行遍历递归处理。

private List<StreamEdge> createChain(StreamNode) {
    // 物理出边,即算子链和算子链之间的边,算子链内部不存在边,一个算子链是一个整体。
    // 物流出边,只有存在无法和前节点链接的节点时,当前边才会成为出边
    List<StreamEdge> transitiveOutEdges = new ArrayList<StreamEdge>();
    // 可以被链接的出边
    List<StreamEdge> chainableOutputs = new ArrayList<StreamEdge>();
    // 不可以被链接的出边
    List<StreamEdge> nonChainableOutputs = new ArrayList<StreamEdge>();
    StreanNode currentNode = ;
    // 遍历当前节点全部出边,判断是否可链接
    for (SteamEdge outEdge : 当前节点的全部出边) {
        if (isChainable(outEdge)) {
            // 可链接的边加入到chainableOutputs集合中
            chainableOutputs.add(outEdge);
        } else {
            // 无法连接的边加入到nonChainableOutputs集合中
            nonChainableOutputs.add(outEdege);
        }
    }

    for (StreamEdge chainable : chainableOutputs) {
        // 递归调用
        transitiveOutEdges.addAll(createChain(StreamNode));
    }

    for (StreamEdge nonChainable : nonChainableOutputs) {
        // 无法连接的将生成物理边,链接JobVertext节点
        transitiveOutEdges.add(nonChainable);
        // 递归调用
        createChain(StreamNode);
    }

    // ===下面逻辑为节点具体的转化逻辑===
    // 生成链接名称,并加入到chainedNames中
    chainedNames.put(key,name);
    // 将当前StreamNode加入到OperatorChainInfo的chainedOperatorHashes集合中
    chainedOperatorHashes.put(startNodeId,list);
    
    StreamConfig config = currentNodeId.equals(startNodeId)
        // 当处理到算子链的第一个节点时,生成JobVertex
        ? createJobVertex(startNodeId, chainInfo)
        : new StreamConfig(new Configuration());
    // StreamConfig设置
    config.set...;

    // 算子链的起点时
    if (currentNodeId.equals(startNodeId)) {
        // 遍历出边,在connect方法中生成jobEdge和ntermediateDataSet
        for (StreamEdge edge : transitiveOutEdges) {
            NonChainedOutput output =
                    opIntermediateOutputs.get(edge.getSourceId()).get(edge);
            connect(startNodeId, edge, output);
        }
    }
    return transitiveOutEdges
}

判断下游节点是否可以与当前节点进行链接由isChainable方法完成,首先需满足下游节点的入边为1的前提条件,满足该条件时,再根据上下游节点是否在相同slot、算子类型、上游算子ChainingStrategy类型、分区器、执行模型、并行度、StreamGraph是否允许链接等信息进行判断。

从每个算子链的最后一个节点开始处理链接关系。当处理到算子链的起点时,执行createJobVertex(startNodeId, chainInfo)逻辑来生成JobVertex,将JobVertex加入到JobGraph的jobVertices集合中。

4.1.2. 构键JobEdge和IntermediateDataSet

处理到算子链的起点时,遍历物理出边,依次生成JobEdge和IntermediateDataSet。connect方法完成此逻辑,核心逻辑如下伪代码所示。

private void connect(Integer headOfChain, StreamEdge edge, NonChainedOutput output) {
    // 将边加入到physicalEdgesInOrder集合中
    physicalEdgesInOrder.add(edge);

    // 上游算子链
    JobVertex headVertex = jobVertices.get(headOfChain);
    // 下游算子链
    JobVertex downStreamVertex = jobVertices.get(edge.getTargetId());

    // JobEdge
    JobEdge jobEdge = downStreamVertex.connectNewDataSetAsInput(
                        headVertex,
                        DistributionPattern.POINTWISE,
                        resultPartitionType,
                        opIntermediateOutputs.get(edge.getSourceId()).get(edge).getDataSetId(),
                        partitioner.isBroadcast());
}

// 生成IntermediateDataSet和边,并将二者加入到JobVextex中相应的集合中
public JobEdge connectNewDataSetAsInput(
        JobVertex input,
        DistributionPattern distPattern,
        ResultPartitionType partitionType,
        IntermediateDataSetID intermediateDataSetId,
        boolean isBroadcast) {

    // 生成结果集的同时,将节点作为结果集的生产者
    IntermediateDataSet dataSet =
            input.getOrCreateResultDataSet(intermediateDataSetId, partitionType);

    JobEdge edge = new JobEdge(dataSet, this, distPattern, isBroadcast);
    this.inputs.add(edge);
    // 边加入到结果集的消费者中
    dataSet.addConsumer(edge);
    return edge;
}

将JobVextex添加到IntermediateDataSet的produder属性中,将JobEdge加入到consumer属性中,分别作为中间结果集的生产者和消费者。

以上即为JobGrpah、JobVertex、JobEdge和IntermediateDataSet生成的简要逻辑。

4.1.3. 举个例子

生成JobGraph的代码逻辑比较复杂,生成各自实例后,还有相应的环境、配置等信息。本文主要对骨架逻辑进行了简要说明。通过一个稍复杂一些的示例来理解下上述过程。
假如有如下结构的StreamGraph,示例中边上存在红色X标记表示节点之间无法连接成算子链。

在这里插入图片描述

  • 从source节点开始递归处理,当递归到节点2时,节点2存在3条出边,节点8无法与节点2链接,因此e2、e5加入到chainableOutputs,e9加入到nonChainableOutputs。然后分别遍历两个集合。
  • e2方向当递归到节点3,节点4无法与节点3进行连接,e3将会成为一条物理边,并从节点4开始生成新的JobVertex,4->sink1节点生成JobVertex1。
  • e5方向同理,e7将成为物理边,7->sink1节点生成JobVertex2。
  • e9方向,e9为物理边,8->sink2节点生成JobVertex3。
  • 当节点2的下游节点全部处理完成,回到e2节点,再回到Source节点时,Source作为最开始算子链的起点,将开始生成最后一个JobVertex4,包含source、2、3、5、6节点。JobVertex4包含3条出边,因此将生成3个中件结果集和3条边,链接上面生成的JobVertex1、JobVertex2、JobVertex3。

最终生成的JobGrhap示例如下所示。

在这里插入图片描述

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

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

相关文章

【linux】从零到入门

linux概述 Linux是一个免费使用和自由传播的一套操作系统。用户可以无偿地得到它地源代码&#xff0c;和大量地应用程序&#xff0c;并且可以随意修改和增加它们。 Linux的内核起初由林纳斯编写。内核是啥&#xff1f; 驱动设备&#xff0c;文件系统&#xff0c;进程管理&…

『MySQL 实战 45 讲』22 - MySQL 有哪些“饮鸩止渴”提高性能的方法?

MySQL 有哪些“饮鸩止渴”提高性能的方法&#xff1f; 需求&#xff1a;业务高峰期&#xff0c;生产环境的 MySQL 压力太大&#xff0c;没法正常响应&#xff0c;需要短期内、临时性地提升一些性能 短连接风暴 短连接模式&#xff1a;执行很少的 SQL 语句就断开&#xff0c;…

【Sklearn-驯化】一文搞懂机器学习树模型建模可视化过程

【Sklearn-驯化】一文搞懂机器学习树模型建模可视化过程 本次修炼方法请往下查看 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我工作、学习、实践 IT领域、真诚分享 踩坑集合&#xff0c;智慧小天地&#xff01; &#x1f387; 免费获取相关内容文档关注&#xff…

LeetCode刷题之HOT100之数组中的第K个最大元素

2024 6/29 今天天气很好啊&#xff0c;想爬山&#xff0c;奈何下午还有最后的一个汇报。做个题先 1、题目描述 2、算法分析 看到这个题我想到的就是: public int findKthLargest(int[] nums, int k) {Arrays.sort(nums);return nums[nums.length - k ];}哈哈&#xff0c;我提…

计算机网络 —— 基本概念

基本概念 1. 通信协议2. 面向连接 v.s. 面向无连接3. 电路交换 v.s. 分组交换4. 单工通信 v.s. 双工通信 1. 通信协议 通信协议就是计算机与计算机之间通过网络实现通信时事先达成的一种“约定”。这种“约定”使那些由不同厂商的设备、不同的CPU 以及不同的操作系统组成的计算…

记录一下MATLAB优化器出现的问题和解决

今天MATLAB优化器出了点问题。我想了想&#xff0c;决定解决一下&#xff0c;不然后面项目没有办法进行下去。 我忘了截图了。 具体来说&#xff0c;是出现了下面的问题。 Gurobi: Cplex: 在上次为了强化学习调整了Pytoch环境以后&#xff08;不知道是不是这个原因&#…

background 与 background-image

相同点&#xff1a;background 与 background-image都可以用于设置背景图 区别. background既可以用于设置背景图&#xff0c; 又可以用于设置CSS样式&#xff0c;还可以用于设置背景属性。 background-image只能用于设置背景图 background能设置的背景属性&#xff0c;如下&…

绝了!Stable Diffusion做AI治愈图片视频,用来做副业简直无敌!10分钟做一个爆款视频保姆教程

一 项目分析 这个治愈类视频的玩法是通过AI生成日常生活场景&#xff0c;制作的vlog&#xff0c;有这样的一个号&#xff0c;发布了几条作品&#xff0c;就涨粉了2000多&#xff0c;点赞7000多&#xff0c;非常的受欢迎。 下面给大家看下这种作品是什么样的&#xff0c;如图所…

大语言模型LLM基础:推理/不同模型/量化对显存、推理速度和性能的影响

通过本文&#xff0c;你将了解以下几个方面的内容&#xff1a; 要运行一个LLM需要多少显存&#xff1f;&#xff08;我的GPU可以运行多大LLM&#xff1f;&#xff09;不同LLM推理速度如何&#xff1f;量化对显存、推理速度和性能的影响&#xff1f;vLLM、DeepSeed等工具的加速…

智慧校园-档案管理系统总体概述

智慧校园档案管理系统&#xff0c;作为教育信息化进程中的重要一环&#xff0c;它运用现代信息技术的力量&#xff0c;彻底改变了传统档案管理的面貌&#xff0c;为学校档案资源的收集、整理、存储、检索与利用开辟了全新的途径。这一系统全面覆盖学生、教职工、教学科研及行政…

Rocky Linux设置静态IP

[connection] idens160 uuidcd246f67-c929-362a-809d-f1b44ddc5d25 typeethernet autoconnect-priority-999 interface-nameens160 timestamp1719094243[ethernet][ipv4] ## 在IPV4下面修改如下内容 methodmanual address192.…

常见的反爬手段和解决思路(爬虫与反爬虫)

常见的反爬手段和解决思路&#xff08;爬虫与反爬虫&#xff09; 学习目标1 服务器反爬的原因2 服务器长反什么样的爬虫&#xff08;1&#xff09;十分低级的应届毕业生&#xff08;2&#xff09;十分低级的创业小公司&#xff08;3&#xff09;不小心写错了没人去停止的失控小…

nuxt实现vuex持久化

前言&#xff1a; 此处不借助插件实现 store 本地持久化 所有状态持久化 使用 vuex 里面的 replaceState 方法还原 store 的根状态 API 参考 | Vuex 创建 store-cache.js 文件 在 plugins 目录下创建 store-cache.js 文件&#xff1b; store-cache.js export default (ctx) &g…

深度之眼(二十八)——神经网络基础知识(三)-卷积神经网络

文章目录 一、前言二、卷积操作2.1 填充&#xff08;padding&#xff09;2.2 步长2.3 输出特征图尺寸计算2.4 多通道卷积 三、池化操作四、Lenet-5及CNN结构进化史4.1 Lenet-5 一、前言 卷积神经网络–AlexNet(最牛)-2012 Lenet-5-大规模商用&#xff08;1989&#xff09; 二、…

如何保护磁盘数据?电脑磁盘数据怎么保护?

电脑磁盘是存储数据的基础&#xff0c;可以将各种重要数据保存在其中。为了避免数据泄露&#xff0c;我们需要保护磁盘数据。那么&#xff0c;电脑磁盘数据怎么保护呢&#xff1f;下面我们就一起来了解一下吧。 文件夹加密超级大师 文件夹加密超级大师是一款优秀的电脑数据加密…

收银系统源码-千呼新零售2.0【宠物、养生、大健康行业解决方案】

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货、宠物、中医养生、大健康等连锁店…

ubuntu丢失网络/网卡的一种原因解决方案

现象 开机进入ubuntu后发现没有网络&#xff0c;无论是在桌面顶部状态栏的快捷键 还是 系统设置中&#xff0c;都没有”有线网“和”无线网“的选项&#xff0c;”代理“的选项是有的使用数据线连接电脑和手机&#xff0c;手机开启”通过usb共享网络“&#xff0c;还是没有任何…

【shell脚本实战案例】主机状态监控脚本

文章目录 案例需求脚本应用场景解决问题脚本思路实现代码 &#x1f308;你好呀&#xff01;我是 山顶风景独好 &#x1f388;欢迎踏入我的博客世界&#xff0c;能与您在此邂逅&#xff0c;真是缘分使然&#xff01;&#x1f60a; &#x1f338;愿您在此停留的每一刻&#xff0c…

日期类(java)

文章目录 第一代日期类 Date常用构造方法SimpleDateFormat 日期格式化类日期转字符串&#xff08;String -> Date)字符串转日期 (String->Date) 第二代日期类 Calendar常用字段与如何得到实例对象相关 API 第三代日期类&#xff08;LocalDate\TIme)日期&#xff0c;时间&…

datax入门(data-web的简单使用)——02

datax入门&#xff08;data-web的简单使用&#xff09;——02 1. 前言1.1 关于data-web官网1.1.1 源码下载1.1.2 datax-Web部署手册1.1.2.1 Linux环境部署手册1.1.2.2 本地开发环境部署手册 1.2 关于datax入门 2. 下载之后打包、启动、登录2.1 我的本地环境2.2 修改配置2.3 初始…