当前位置: 首页 > news >正文

DataX源码分析-插件机制

系列文章目录

一、DataX详解和架构介绍
二、DataX源码分析 JobContainer
三、DataX源码分析 TaskGroupContainer
四、DataX源码分析 TaskExecutor
五、DataX源码分析 reader
六、DataX源码分析 writer
七、DataX源码分析 Channel
八、DataX源码分析-插件机制


文章目录

  • 系列文章目录
  • 前言
  • 一、插件分类
  • 插件目录结构
  • 插件加载原理


前言

DataX的插件机制是其核心特性之一,它使得DataX能够灵活地适应各种不同的数据源的数据同步。这一机制主要基于插件开发框架,该框架主要包括Reader插件、Transformer插件、Writer插件。

DataX的插件机制还采用了框架+插件的架构。框架负责连接Reader和Writer插件,作为两者的数据传输通道,并处理缓冲、流控、并发、数据转换等核心技术问题。这种架构使得插件只需关心数据的读取或写入本身,而同步的共性问题则由框架来处理。

此外,DataX的插件机制还具有良好的扩展性和可维护性。开发者可以根据需要开发新的Reader或Writer插件来支持新的数据源类型,而无需修改DataX的核心框架代码。这种插件化的设计使得DataX能够适应不断变化的业务需求和技术环境。

在插件的加载和初始化方面,DataX使用了类似Java SPI(Service Provider Interface)的机制。它会在指定的插件目录中查找并加载插件,然后将其注册到插件注册中心。这样,当需要使用某个插件时,就可以从注册中心中获取其实例,并进行相应的操作。

总的来说,DataX的插件机制是一种非常灵活和可扩展的设计,它使得DataX能够适应各种不同的数据源和数据存储需求,同时也为开发者提供了丰富的扩展和定制化的可能性。


一、插件分类

按照功能分:
reader, 读插件,例如mysqlReader,从mysql读取数据
writer, 写插件。例如mysqlWriter,给mysql写入数据;
transformer, 中间结果转换,例如SubstrTransformer用于字符截取;
按照运行类型分:
Job级别的插件
Task级别的插件

插件目录结构

datax\plugin下分2个reader和writer目录,下面以mysql为例
在这里插入图片描述
plugin.json内容:

{"name": "mysqlreader","class": "xxx.plugin.reader.mysqlreader.MysqlReader","description": "useScene: prod. mechanism: Jdbc connection using the database, execute select sql, retrieve data from the ResultSet. warn: The more you know about the database, the less problems you encounter.","developer": "xx"
}

在这里插入图片描述

插件加载原理

  • DataX进程启动入口为com.alibaba.datax.core.Engineengine.entry()
    public static void entry(final String[] args) throws Throwable {Options options = new Options();options.addOption("job", true, "Job config.");options.addOption("jobid", true, "Job unique id.");options.addOption("mode", true, "Job runtime mode.");BasicParser parser = new BasicParser();CommandLine cl = parser.parse(options, args);String jobPath = cl.getOptionValue("job");// 如果用户没有明确指定jobid, 则 datax.py 会指定 jobid 默认值为-1String jobIdString = cl.getOptionValue("jobid");RUNTIME_MODE = cl.getOptionValue("mode");Configuration configuration = ConfigParser.parse(jobPath);}
  • 读取并解析插件配置
    ConfigParser.parse(final String jobPath)传入job路径,该方法组装解析,最后返回一个Configuration对象,Configuration里解析出了reader,writer,handler等插件名称;提取完插件名称后,会去reader目录和writer目录,寻找插件的位置。
  • 动态加载插件
    插件的加载都是通过自定义类加载器JarLoader动态加载,提供插件相关Jar隔离的加载机制。插件的加载接口由LoadUtil类负责,当要加载一个插件时,需要实例化一个JarLoader,然后切换thread class loader之后,才加载插件。这个主要由ClassLoaderSwapper实现。
  • JarLoader类
    JarLoader 负责加载指定路径下的插件 JAR 文件。它会检查 JAR 文件的合法性、有效性以及是否包含必要的插件实现类。继承自URLClassLoader提供Jar隔离的加载机制,会把传入的路径、及其子路径、以及路径中的jar文件加入到class path。
/*** 提供Jar隔离的加载机制,会把传入的路径、及其子路径、以及路径中的jar文件加入到class path。*/
public class JarLoader extends URLClassLoader{public JarLoader(String[] paths) {this(paths, JarLoader.class.getClassLoader());}public JarLoader(String[] paths, ClassLoader parent) {super(getURLs(paths), parent);}private static URL[] getURLs(String[] paths) {Validate.isTrue(null != paths && 0 != paths.length,"jar包路径不能为空.");List<String> dirs = new ArrayList<String>();for (String path : paths) {dirs.add(path);JarLoader.collectDirs(path, dirs);}List<URL> urls = new ArrayList<URL>();for (String path : dirs) {urls.addAll(doGetURLs(path));}return urls.toArray(new URL[0]);}private static void collectDirs(String path, List<String> collector) {if (null == path || StringUtils.isBlank(path)) {return;}File current = new File(path);if (!current.exists() || !current.isDirectory()) {return;}for (File child : current.listFiles()) {if (!child.isDirectory()) {continue;}collector.add(child.getAbsolutePath());collectDirs(child.getAbsolutePath(), collector);}}private static List<URL> doGetURLs(final String path) {Validate.isTrue(!StringUtils.isBlank(path), "jar包路径不能为空.");File jarPath = new File(path);Validate.isTrue(jarPath.exists() && jarPath.isDirectory(),"jar包路径必须存在且为目录.");/* set filter */FileFilter jarFilter = new FileFilter() {@Overridepublic boolean accept(File pathname) {return pathname.getName().endsWith(".jar");}};/* iterate all jar */File[] allJars = new File(path).listFiles(jarFilter);List<URL> jarURLs = new ArrayList<URL>(allJars.length);for (int i = 0; i < allJars.length; i++) {try {jarURLs.add(allJars[i].toURI().toURL());} catch (Exception e) {throw DataXException.asDataXException(FrameworkErrorCode.PLUGIN_INIT_ERROR,"系统加载jar包出错", e);}}return jarURLs;}
}
  • LoadUtil
    LoadUtil 是一个工具类,用于辅助插件的加载和初始化过程。LoadUtil 类通常包含静态方法,这些方法简化了插件加载的逻辑,使得 DataX 的核心框架能够与具体的插件进行交互。
    LoadUtil 的主要职责包括:
    插件加载:LoadUtil 提供了加载插件的方法。这些方法会根据配置文件中指定的插件类型和名称,使用 Java 的反射机制来加载插件的类定义。加载过程可能包括查找类路径下的 JAR 文件、读取插件的元数据以及验证插件的合法性。
    插件实例化:一旦插件类被加载,LoadUtil 会负责创建插件的实例。这通常涉及到调用插件类的无参构造函数,并返回该实例的引用。LoadUtil 会处理任何与实例化相关的异常,以确保在出现问题时能够给出适当的错误消息。
    插件注册:加载并实例化插件后,LoadUtil 可能会将插件实例注册到一个全局的插件注册中心。这样,DataX 的其他部分就可以在需要时获取并使用这些插件实例。
    配置传递:LoadUtil 还可能负责将配置文件中针对插件的配置参数传递给插件实例。这确保了插件能够根据用户的配置进行正确的初始化。
    错误处理:如果在加载、实例化或配置插件过程中发生错误,LoadUtil 会负责处理这些错误。这可能包括记录日志、抛出异常或采取其他恢复措施。
public class LoadUtil {private static final String pluginTypeNameFormat = "plugin.%s.%s";private LoadUtil() {}private enum ContainerType {Job("Job"), Task("Task");private String type;private ContainerType(String type) {this.type = type;}public String value() {return type;}}/*** 所有插件配置放置在pluginRegisterCenter中,为区别reader、transformer和writer,还能区别* 具体pluginName,故使用pluginType.pluginName作为key放置在该map中*/private static Configuration pluginRegisterCenter;/*** jarLoader的缓冲*/private static Map<String, JarLoader> jarLoaderCenter = new HashMap();/*** 设置pluginConfigs,方便后面插件来获取** @param pluginConfigs*/public static void bind(Configuration pluginConfigs) {pluginRegisterCenter = pluginConfigs;}private static String generatePluginKey(PluginType pluginType,String pluginName) {return String.format(pluginTypeNameFormat, pluginType.toString(),pluginName);}private static Configuration getPluginConf(PluginType pluginType,String pluginName) {Configuration pluginConf = pluginRegisterCenter.getConfiguration(generatePluginKey(pluginType, pluginName));if (null == pluginConf) {throw DataXException.asDataXException(FrameworkErrorCode.PLUGIN_INSTALL_ERROR,String.format("DataX不能找到插件[%s]的配置.",pluginName));}return pluginConf;}/*** 加载JobPlugin,reader、writer都可能要加载** @param pluginType* @param pluginName* @return*/public static AbstractJobPlugin loadJobPlugin(PluginType pluginType,String pluginName) {Class<? extends AbstractPlugin> clazz = LoadUtil.loadPluginClass(pluginType, pluginName, ContainerType.Job);try {AbstractJobPlugin jobPlugin = (AbstractJobPlugin) clazz.newInstance();jobPlugin.setPluginConf(getPluginConf(pluginType, pluginName));return jobPlugin;} catch (Exception e) {throw DataXException.asDataXException(FrameworkErrorCode.RUNTIME_ERROR,String.format("DataX找到plugin[%s]的Job配置.",pluginName), e);}}/*** 加载taskPlugin,reader、writer都可能加载** @param pluginType* @param pluginName* @return*/public static AbstractTaskPlugin loadTaskPlugin(PluginType pluginType,String pluginName) {Class<? extends AbstractPlugin> clazz = LoadUtil.loadPluginClass(pluginType, pluginName, ContainerType.Task);try {AbstractTaskPlugin taskPlugin = (AbstractTaskPlugin) clazz.newInstance();taskPlugin.setPluginConf(getPluginConf(pluginType, pluginName));return taskPlugin;} catch (Exception e) {throw DataXException.asDataXException(FrameworkErrorCode.RUNTIME_ERROR,String.format("DataX不能找plugin[%s]的Task配置.",pluginName), e);}}/*** 根据插件类型、名字和执行时taskGroupId加载对应运行器** @param pluginType* @param pluginName* @return*/public static AbstractRunner loadPluginRunner(PluginType pluginType, String pluginName) {AbstractTaskPlugin taskPlugin = LoadUtil.loadTaskPlugin(pluginType,pluginName);switch (pluginType) {case READER:return new ReaderRunner(taskPlugin);case WRITER:return new WriterRunner(taskPlugin);default:throw DataXException.asDataXException(FrameworkErrorCode.RUNTIME_ERROR,String.format("插件[%s]的类型必须是[reader]或[writer]!",pluginName));}}/*** 反射出具体plugin实例** @param pluginType* @param pluginName* @param pluginRunType* @return*/@SuppressWarnings("unchecked")private static synchronized Class<? extends AbstractPlugin> loadPluginClass(PluginType pluginType, String pluginName,ContainerType pluginRunType) {Configuration pluginConf = getPluginConf(pluginType, pluginName);JarLoader jarLoader = LoadUtil.getJarLoader(pluginType, pluginName);try {return (Class<? extends AbstractPlugin>) jarLoader.loadClass(pluginConf.getString("class") + "$"+ pluginRunType.value());} catch (Exception e) {throw DataXException.asDataXException(FrameworkErrorCode.RUNTIME_ERROR, e);}}public static synchronized JarLoader getJarLoader(PluginType pluginType,String pluginName) {Configuration pluginConf = getPluginConf(pluginType, pluginName);JarLoader jarLoader = jarLoaderCenter.get(generatePluginKey(pluginType,pluginName));if (null == jarLoader) {String pluginPath = pluginConf.getString("path");if (StringUtils.isBlank(pluginPath)) {throw DataXException.asDataXException(FrameworkErrorCode.RUNTIME_ERROR,String.format("%s插件[%s]路径非法!",pluginType, pluginName));}jarLoader = new JarLoader(new String[]{pluginPath});jarLoaderCenter.put(generatePluginKey(pluginType, pluginName),jarLoader);}return jarLoader;}
}
  • ClassLoaderSwapper

ClassLoaderSwapper有一个属性storeClassLoader, 用于保存着当前线程的classLoader切换之前的ClassLoader。

/*** 为避免jar冲突,比如hbase可能有多个版本的读写依赖jar包,JobContainer和TaskGroupContainer,就需要脱离当前classLoader去加载这些jar包,执行完成后,又退回到原来classLoader上继续执行接下来的代码*/
public final class ClassLoaderSwapper {private ClassLoader storeClassLoader = null;private ClassLoaderSwapper() {}public static ClassLoaderSwapper newCurrentThreadClassLoaderSwapper() {return new ClassLoaderSwapper();}/*** 保存当前classLoader,并将当前线程的classLoader设置为所给classLoader** @param* @return*/public ClassLoader setCurrentThreadClassLoader(ClassLoader classLoader) {this.storeClassLoader = Thread.currentThread().getContextClassLoader();Thread.currentThread().setContextClassLoader(classLoader);return this.storeClassLoader;}/*** 将当前线程的类加载器设置为保存的类加载* @return*/public ClassLoader restoreCurrentThreadClassLoader() {ClassLoader classLoader = Thread.currentThread().getContextClassLoader();Thread.currentThread().setContextClassLoader(this.storeClassLoader);return classLoader;}
}

相关文章:

DataX源码分析-插件机制

系列文章目录 一、DataX详解和架构介绍 二、DataX源码分析 JobContainer 三、DataX源码分析 TaskGroupContainer 四、DataX源码分析 TaskExecutor 五、DataX源码分析 reader 六、DataX源码分析 writer 七、DataX源码分析 Channel 八、DataX源码分析-插件机制 文章目录 系列文章…...

容器高级知识: 适配器模式与 Sidecar 模式的区别

适配器模式与 Sidecar 模式的区别 在 Kubernetes 中&#xff0c;适配器模式和 Sidecar 模式都是扩展您的主应用程序容器功能的方法&#xff0c;但它们具有不同的目的和功能&#xff1a; Sidecar 模式&#xff1a; 通用目的&#xff1a; 为主应用程序提供 补充功能&#xff0…...

使用Xdisplay将ipad作为扩展显示器Agent闪退问题

1. 正常流程贴子挺多&#xff0c;可以参考这几篇 https://blog.csdn.net/Shi_Xueqing/article/details/129744496 如何将ipad作为win10的扩展屏&#xff08;使用USB线连接&#xff09;_ipad win usb 上网-CSDN博客 2.在进行pc端软件设置的时候发生闪退 解决方法&#xff1a…...

DVXplorer事件相机入门

DV官方文档&#xff1a;Get Started DV (inivation.gitlab.io) DV事件相机的ROS包&#xff1a;https://github.com/uzh-rpg/rpg_dvs_ros 事件相机的资源汇总&#xff1a;https://github.com/uzh-rpg/event-based_vision_resources 1.DV事件相机ROS包驱动程序安装 注意&#x…...

ubuntu屏幕小的解决办法

1. 安装vmware tools , 再点自适应客户机 执行里面的vmware-install.pl这个文件 &#xff1a;sudo ./vmware-install.pl 执行不了可以放到家目录&#xff0c;我放在了/home/book 里面 最后点这个自适应客户机 然后我这里点不了是因为我点了控制台视图和拉伸客户机&#xff0c…...

黑群晖一键修复:root、AME、DTS、转码、CPU型号等

食用方法&#xff1a;SSH连接群晖使用临时root权限执行 AME3.x激活补丁 只适用于x86_64的&#xff1a;DSM7.x Advanced Media Extensions (AME)版本3.0.1-2004、3.1.0-3005 激活过程需要下载官方的解码包&#xff0c;过程较慢&#xff0c;耐心等待。。。 DSM7.1和7.2的AME版…...

Repo命令使用实例(三十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…...

2024年华为OD机试真题-分披萨-Python-OD统一考试(C卷)

题目描述: “吃货”和“馋嘴”两人到披萨店点了一份铁盘(圆形)披萨,并嘱咐店员将披萨按放射状切成大小相同的偶数扇形小块。但是粗心服务员将披萨切成了每块大小都完全不同奇数块,且肉眼能分辨出大小。 由于两人都想吃到最多的披萨,他们商量了一个他们认为公平的分法:从…...

找负环(图论基础)

文章目录 负环spfa找负环方法一方法二实际效果 负环 环内路径上的权值和为负。 spfa找负环 两种基本的方法 统计每一个点的入队次数&#xff0c;如果一个点入队了n次&#xff0c;则说明存在负环统计当前每个点中的最短路中所包含的边数&#xff0c;如果当前某个点的最短路所…...

无人机飞控算法原理基础研究,多旋翼无人机的飞行控制算法理论详解,无人机飞控软件架构设计

多旋翼无人机的飞行控制算法主要涉及到自动控制器、捷联式惯性导航系统、卡尔曼滤波算法和飞行控制PID算法等部分。 自动控制器是无人机飞行控制的核心部分&#xff0c;它负责接收来自无人机传感器和其他系统的信息&#xff0c;并根据预设的算法和逻辑&#xff0c;对无人机的姿…...

关于内存相关的梳理

1 关键字 总结 &#xff08;lowmemory&#xff0c;anr in&#xff09; 2 知识储备 虚拟机原理 垃圾回收算法 又包含标记 和清除两种算法 标记&#xff1a;程序计数器-已过时&#xff0c;可达性分析 具体可见 http://help.eclipse.org/luna/index.jsp?topic%2Forg.ec…...

7.JS里表达式,if条件判断,三元运算符,switch语句,断点调试

表达式和语句的区别 表达式就是可以被求值的代码比如什么a 1 语句就是一段可以执行的代码比如什么if else 直接给B站的黑马程序员的老师引流一波总结的真好 分支语句 就是基本上所有的语言都会有的if else 语句就是满足不同的条件执行不同的代码&#xff0c;让计算机有条件…...

RK3568平台开发系列讲解(存储篇)文件句柄与文件描述符介绍

🚀返回专栏总目录 文章目录 一、什么是文件句柄二、什么是文件描述符2.1、files_struct 结构体2.2、fdtable 结构体三、数据结构关系图沉淀、分享、成长,让自己和他人都能有所收获!😄 一、什么是文件句柄 用户空间的进程通过open系统调用打开一个文件之后,内核返回的就是…...

【C++】类和对象(五)友元、内部类、匿名对象

前言&#xff1a;前面我们说到类和对象是一个十分漫长的荆棘地&#xff0c;今天我们将走到终点&#xff0c;也就是说我们对于&#xff23;算是正式的入门了。 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f449; 专栏分类:高质量&#xff23;学习 &…...

攻防世界 CTF Web方向 引导模式-难度1 —— 1-10题 wp精讲

目录 view_source robots backup cookie disabled_button get_post weak_auth simple_php Training-WWW-Robots view_source 题目描述: X老师让小宁同学查看一个网页的源代码&#xff0c;但小宁同学发现鼠标右键好像不管用了。 不能按右键&#xff0c;按F12 robots …...

Docker之MongoDB安装、创建用户及登录认证

Docker之MongoDB安装、创建用户及登录认证 文章目录 Docker之MongoDB安装、创建用户及登录认证1. 拉取镜像2. 创建宿主机容器数据卷3. 运行mongodb容器1. 运行容器2. 创建用户3. 创建数据库并设置密码 1. 拉取镜像 docker pull mongo:4.2.212. 创建宿主机容器数据卷 运行docke…...

紫微斗数双星组合:天机天梁在辰戌

文章目录 前言内容总结 前言 紫微斗数双星组合&#xff1a;天机天梁在辰戌 内容 紫微斗数双星组合&#xff1a;天机天梁在辰戌 性格分析 在紫微斗数命盘中&#xff0c;天梁星是一颗“荫星”&#xff0c;能够遇难呈祥&#xff0c;化解凶危&#xff0c;主寿&#xff0c;主贵。…...

N-144基于微信小程序在线订餐系统

开发工具&#xff1a;IDEA、微信小程序 服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8 项目构建&#xff1a;maven 数据库&#xff1a;mysql5.7 前端技术&#xff1a;vue、ElementUI、 Vant Weapp 服务端技术&#xff1a;springbootmybatisredis 本系统分微信小程序和…...

[UI5 常用控件] 09.IconTabBar,IconTabHeader,TabContainer

文章目录 前言1. IconTabBar1.1 简介1.2 基本结构1.3 用法1.3.1 颜色&#xff0c;拖放&#xff0c;溢出1.3.2 Icons Only , Inner Contents1.3.3 showAll,Count,key,IconTabSeparator 1.3.4 Only Text1.3.5 headerMode-Inline1.3.6 design,IconTabSeparator-icon1.3.7 DensityM…...

CCF编程能力等级认证GESP—C++5级—20231209

CCF编程能力等级认证GESP—C5级—20231209 单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09;判断题&#xff08;每题 2 分&#xff0c;共 20 分&#xff09;编程题 (每题 25 分&#xff0c;共 50 分)小杨的幸运数烹饪问题 答案及解析单选题判断题编程题1编程题2 单…...

uniapp 对接腾讯云IM群组成员管理(增删改查)

UniApp 实战&#xff1a;腾讯云IM群组成员管理&#xff08;增删改查&#xff09; 一、前言 在社交类App开发中&#xff0c;群组成员管理是核心功能之一。本文将基于UniApp框架&#xff0c;结合腾讯云IM SDK&#xff0c;详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】

微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来&#xff0c;Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

电脑插入多块移动硬盘后经常出现卡顿和蓝屏

当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时&#xff0c;可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案&#xff1a; 1. 检查电源供电问题 问题原因&#xff1a;多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

2021-03-15 iview一些问题

1.iview 在使用tree组件时&#xff0c;发现没有set类的方法&#xff0c;只有get&#xff0c;那么要改变tree值&#xff0c;只能遍历treeData&#xff0c;递归修改treeData的checked&#xff0c;发现无法更改&#xff0c;原因在于check模式下&#xff0c;子元素的勾选状态跟父节…...