【Flink】详解Flink任务提交流程

news2025/1/20 2:13:28

启动一个任务

通常我们会使用 bin/flink run -t yarn-per-job -c com.xxx.xxx.WordCount/WordCount.jar 方式启动任务;我们看一下 flink文件中到底做了什么,以下是其部分源码

# Convert relative path to absolute path
bin=`dirname "$target"`

# get flink config
. "$bin"/config.sh

if [ "$FLINK_IDENT_STRING" = "" ]; then
        FLINK_IDENT_STRING="$USER"
fi

CC_CLASSPATH=`constructFlinkClassPath`

log=$FLINK_LOG_DIR/flink-$FLINK_IDENT_STRING-client-$HOSTNAME.log
log_setting=(-Dlog.file="$log" -Dlog4j.configuration=file:"$FLINK_CONF_DIR"/log4j-cli.properties -Dlog4j.configurationFile=file:"$FLINK_CONF_DIR"/log4j-cli.properties -Dlogback.configurationFile=file:"$FLINK_CONF_DIR"/logback.xml)

# Add Client-specific JVM options
FLINK_ENV_JAVA_OPTS="${FLINK_ENV_JAVA_OPTS} ${FLINK_ENV_JAVA_OPTS_CLI}"

# Add HADOOP_CLASSPATH to allow the usage of Hadoop file systems
exec "${JAVA_RUN}" $JVM_ARGS $FLINK_ENV_JAVA_OPTS "${log_setting[@]}" -classpath "`manglePathList "$CC_CLASSPATH:$INTERNAL_HADOOP_CLASSPATHS"`" org.apache.flink.client.cli.CliFrontend "$@"

可以看到,第一步将相对地址转换成绝对地址;第二步获取 Flink 配置信息,这个信息放在 bin 目录下的 config. sh中;第三步获取 JVM 配置信息;最后一步就是程序真正的入口 org.apache.flink.client.cli.CliFrontend,这里有几点需要注意的:

  1. Linux- exec 命令用于调用并执行指令;
  2. 在配置文件中 JAVA_RUN="$JAVA_HOME"/bin/java,因此这里运行的是 Java 程序;
  3. FLINK_ENV_JAVA_OPTS:是上文获取的关于 JVM 的相关配置;
  4. java -cp 和 -classpath 一样,是指定类运行所依赖其他类的路径;
  5. INTERNAL_HADOOP_CLASSPATHS="${HADOOP_CLASSPATH}:${HADOOP_CONF_DIR}:${YARN_CONF_DIR}"config. sh文件指定了 Hadoop 的相关路径;
  6. "$@" 表示所有参数以以 "$1" " $2" … "$ n" 的形式输出,例如 bin/flink run -t yarn-per-job -c com.xxx.xxx.WordCount/WordCount.jar -p 11 "$@" 会解析成 run -t yarn-per-job -c com.xxx.xxx.WordCount/WordCount.jar -p 11

小结一下,我们任务的提交流程是输入启动命令→读取配置信息→ java -cp 开启虚拟机→开启 CliFrontend→运行 CliFrontend. run

CliFrontend 详解

class CliFrontend{
	protected void run(String[] args) throws Exception {
        LOG.info("Running 'run' command.");
		// TODO 获取默认的运行时参数
        final Options commandOptions = CliFrontendParser.getRunCommandOptions();
        // 解析参数返回CommandLine对象
        // 解析见-> DefaultParser详解,核心逻辑是使用CliFrontendParser进行解析->使用DefaultParser进行解析
        final CommandLine commandLine = getCommandLine(commandOptions, args, true);

        // evaluate help flag
        if (commandLine.hasOption(HELP_OPTION.getOpt())) {
            CliFrontendParser.printHelpForRun(customCommandLines);
            return;
        }
		
        // TODO 按照Generic、Yarn、Default顺序判断是否活跃
        // 详解见 -> sub1 
        final CustomCommandLine activeCommandLine =
                validateAndGetActiveCommandLine(checkNotNull(commandLine)); 
        
        // TODO 根据输入参数封装了一个ProgramOptions
        // 详解见 -> ProgramOptions
        final ProgramOptions programOptions = ProgramOptions.create(commandLine);
		
        // TODO 获取任务Jar包和依赖项;
        // 详解见 -> sub2
        final List<URL> jobJars = getJobJarAndDependencies(programOptions);
		
        // TODO 获取有效配置 -> 这里的逻辑最终是调用每一个CLI的toConfiguration方法
        final Configuration effectiveConfiguration =
                getEffectiveConfiguration(activeCommandLine, commandLine, programOptions, jobJars);
		
        // TODO 打印日志信息,
        LOG.debug("Effective executor configuration: {}", effectiveConfiguration);
	
        // TODO 将programOptions(程序)和effectiveConfiguration(有效配置信息)再封装成一个PackagedProgram对象
        try (PackagedProgram program = getPackagedProgram(programOptions, effectiveConfiguration)) {
            // TODO 执行封装对象
            executeProgram(effectiveConfiguration, program);
        }
    }
    
	// ****************************** sub1 ******************************************** //
    public CustomCommandLine validateAndGetActiveCommandLine(CommandLine commandLine) {
            LOG.debug("Custom commandlines: {}", customCommandLines);
            for (CustomCommandLine cli : customCommandLines) {
                LOG.debug(
                        "Checking custom commandline {}, isActive: {}", cli, cli.isActive(commandLine));
                // 按照Generic、Yarn、Default顺序判断是否活跃,具体分析见下文 
                if (cli.isActive(commandLine)) {
                    return cli;
                }
            }
            throw new IllegalStateException("No valid command-line found.");
    }
    // ************************************************************************** //

	// ****************************** sub2 ******************************************** //
	private List<URL> getJobJarAndDependencies(ProgramOptions programOptions)
            throws CliArgsException {
        // TODO 获取入口类
        String entryPointClass = programOptions.getEntryPointClassName();
        // TODO 获取JAR包路径
        String jarFilePath = programOptions.getJarFilePath();

        try {
            File jarFile = jarFilePath != null ? getJarFile(jarFilePath) : null;
            return PackagedProgram.getJobJarAndDependencies(jarFile, entryPointClass);
        } catch (FileNotFoundException | ProgramInvocationException e) {
            throw new CliArgsException(
                    "Could not get job jar and dependencies from JAR file: " + e.getMessage(), e);
        }
    }
    // ************************************************************************** //
}

c

class DefaultParser{

}


/*
CustomCommandLine 是一个接口,GenericCLI、AbstractYarnCli、DefaultCLI
继承关系见下图
*/
class GenericCLI {
	/* 每一个Option都是这样的解析逻辑,有一个短的opt参数进行判断,同时有一个longOpt参数进行辅助判断,
	以executorOption为例,就是命令行中有-e 或者 -executor 就会返回True
	*/
	private final Option executorOption =
            new Option(
                    "e",
                    "executor",
                    true,
                    "DEPRECATED: Please use the -t option instead which is also available with the \"Application Mode\".\n"
                            + "The name of the executor to be used for executing the given job, which is equivalent "
                            + "to the \""
                            + DeploymentOptions.TARGET.key()
                            + "\" config option. The "
                            + "currently available executors are: "
                            + getExecutorFactoryNames()
                            + ".");

	 /*
    1. 获取验证配置项是否存在
    2. 获取验证执行器参数,即-e, executor
    3. 获取验证目标参数是否存在,即-t target
    */
	public boolean isActive(CommandLine commandLine) {  
		// configuration是配置文件
		return configuration.getOptional(DeploymentOptions.TARGET).isPresent()  
		|| commandLine.hasOption(executorOption.getOpt())  
		|| commandLine.hasOption(targetOption.getOpt());  
	}
}

class FlinkYarnSessionCli{
	public boolean isActive(CommandLine commandLine) {
		// 调用父类的的isActive方法
        if (!super.isActive(commandLine)) {
            return (isYarnPropertiesFileMode(commandLine)
                    && yarnApplicationIdFromYarnProperties != null);
        }
        return true;
    }
    
	public Configuration toConfiguration(CommandLine commandLine) throws FlinkException {
        // we ignore the addressOption because it can only contain "yarn-cluster"
        final Configuration effectiveConfiguration = new Configuration();

        applyDescriptorOptionToConfig(commandLine, effectiveConfiguration);
		
        // TODO 获取APPID,查看是否启动了集群
        final ApplicationId applicationId = getApplicationId(commandLine);
        if (applicationId != null) {
            final String zooKeeperNamespace;
            if (commandLine.hasOption(zookeeperNamespace.getOpt())) {
                zooKeeperNamespace = commandLine.getOptionValue(zookeeperNamespace.getOpt());
            } else {
                zooKeeperNamespace =
                        effectiveConfiguration.getString(HA_CLUSTER_ID, applicationId.toString());
            }
			
            // TODO 设置高可用
            effectiveConfiguration.setString(HA_CLUSTER_ID, zooKeeperNamespace);
            // TODO 设置YARN的应用ID
            effectiveConfiguration.setString(
                    YarnConfigOptions.APPLICATION_ID, applicationId.toString());
            // 执行器,已经启动了集群就是Session模式
            effectiveConfiguration.setString(
                    DeploymentOptions.TARGET, YarnSessionClusterExecutor.NAME);
        } else {
            // 没启动集群就是per-JOB 模式
            effectiveConfiguration.setString(DeploymentOptions.TARGET, YarnJobClusterExecutor.NAME);
        }

	    // TODO 如果有JM内存设置,则进行设置
        if (commandLine.hasOption(jmMemory.getOpt())) {
            String jmMemoryVal = commandLine.getOptionValue(jmMemory.getOpt());
            if (!MemorySize.MemoryUnit.hasUnit(jmMemoryVal)) {
                jmMemoryVal += "m";
            }
            // TODO 添加JM内存设置
            effectiveConfiguration.set(
                    JobManagerOptions.TOTAL_PROCESS_MEMORY, MemorySize.parse(jmMemoryVal));
        }

		// TODO 如果有TM总内存设置,则进行设置
        if (commandLine.hasOption(tmMemory.getOpt())) {
            String tmMemoryVal = commandLine.getOptionValue(tmMemory.getOpt());
            if (!MemorySize.MemoryUnit.hasUnit(tmMemoryVal)) {
                tmMemoryVal += "m";
            }
            //  TODO 添加TM的总内存;
            effectiveConfiguration.set(
                    TaskManagerOptions.TOTAL_PROCESS_MEMORY, MemorySize.parse(tmMemoryVal));
        }

		// TODO 如果有SLOT数设置,则进行设置
        if (commandLine.hasOption(slots.getOpt())) {
            // 每个TM的SLOT数
            effectiveConfiguration.setInteger(
                    TaskManagerOptions.NUM_TASK_SLOTS,
                    Integer.parseInt(commandLine.getOptionValue(slots.getOpt())));
        }

		// TODO 设置动态属性
        dynamicPropertiesEncoded = encodeDynamicProperties(commandLine);
        if (!dynamicPropertiesEncoded.isEmpty()) {
            Map<String, String> dynProperties = getDynamicProperties(dynamicPropertiesEncoded);
            for (Map.Entry<String, String> dynProperty : dynProperties.entrySet()) {
                effectiveConfiguration.setString(dynProperty.getKey(), dynProperty.getValue());
            }
        }

		
        if (isYarnPropertiesFileMode(commandLine)) {
            return applyYarnProperties(effectiveConfiguration);
        } else {
            return effectiveConfiguration;
        }
    }
}

// FlinkYarnSessionCli的父类
class AbstractYarnCli{
	public boolean isActive(CommandLine commandLine) {
            // TODO 获取JM地址
            final String jobManagerOption = commandLine.getOptionValue(addressOption.getOpt(), null);
            // TODO ID = “yarn-cluster”, 
            final boolean yarnJobManager = ID.equals(jobManagerOption);
            // TODO 如果Yarn启动,则会有APPID,判断是否有APPID;1. 看输入命令中是否有APPID;2.从配置文件中读取APPID;
            final boolean hasYarnAppId =
                    commandLine.hasOption(applicationId.getOpt())
                            || configuration.getOptional(YarnConfigOptions.APPLICATION_ID).isPresent();
            // TODO 使用“yarn-session”或者“yarn-per-job”进行比较
            final boolean hasYarnExecutor =
                    YarnSessionClusterExecutor.NAME.equalsIgnoreCase(
                                    configuration.get(DeploymentOptions.TARGET))
                            || YarnJobClusterExecutor.NAME.equalsIgnoreCase(
                                    configuration.get(DeploymentOptions.TARGET));
            // -m yarn-cluster || yarn 有APPID、命令行制定了 || 执行器是YARN的
            return hasYarnExecutor || yarnJobManager || hasYarnAppId;
    }
}

// DefaultCLI用来部署Standalone模式
class DefaultCLI{
	public boolean isActive(CommandLine commandLine) {
        // 永远返回True
        return true;
    }
}

class ProgramOptions {
    public static ProgramOptions create(CommandLine line) throws CliArgsException {
        
        if (isPythonEntryPoint(line) || containsPythonDependencyOptions(line)) {
            // Python相关的类都是调用这个方法
            return createPythonProgramOptions(line);
        } else {
            // 其他都是调用这个方法
            return new ProgramOptions(line);
        }
    }
    
	protected ProgramOptions(CommandLine line) throws CliArgsException {
        super(line);
		
        // TODO 获取入口类
        this.entryPointClass =
                line.hasOption(CLASS_OPTION.getOpt())
                        ? line.getOptionValue(CLASS_OPTION.getOpt())
                        : null;
		
        // TODO 获取JAR包
        this.jarFilePath =
                line.hasOption(JAR_OPTION.getOpt())
                        ? line.getOptionValue(JAR_OPTION.getOpt())
                        : null;
		
        this.programArgs = extractProgramArgs(line);

		// TODO 路径解析并添加到一个ArrayList中
        List<URL> classpaths = new ArrayList<URL>();
        if (line.hasOption(CLASSPATH_OPTION.getOpt())) {
            for (String path : line.getOptionValues(CLASSPATH_OPTION.getOpt())) {
                try {
                    classpaths.add(new URL(path));
                } catch (MalformedURLException e) {
                    throw new CliArgsException("Bad syntax for classpath: " + path);
                }
            }
        }
        this.classpaths = classpaths;

		// TODO 先检查是否有并行度设置,如果有则解析并行度并进行值检查,如果没有则使用默认并行度
        if (line.hasOption(PARALLELISM_OPTION.getOpt())) {
            String parString = line.getOptionValue(PARALLELISM_OPTION.getOpt());
            try {
                parallelism = Integer.parseInt(parString);
                if (parallelism <= 0) {
                    throw new NumberFormatException();
                }
            } catch (NumberFormatException e) {
                throw new CliArgsException(
                        "The parallelism must be a positive number: " + parString);
            }
        } else {
            parallelism = ExecutionConfig.PARALLELISM_DEFAULT;
        }

		// TODO 分发模式,这个和上文介绍的executorOption判断逻辑是一致的。
        detachedMode =
                line.hasOption(DETACHED_OPTION.getOpt())
                        || line.hasOption(YARN_DETACHED_OPTION.getOpt());
        shutdownOnAttachedExit = line.hasOption(SHUTDOWN_IF_ATTACHED_OPTION.getOpt());

        this.savepointSettings = CliFrontendParser.createSavepointRestoreSettings(line);
    }
}

class JobManagerOptions{
    public static final ConfigOption<MemorySize> TOTAL_PROCESS_MEMORY =
		    // 这类配置信息都是这种链式调用
            key("jobmanager.memory.process.size")
                    .memoryType()
                    .noDefaultValue()
                    .withDescription(
                            "Total Process Memory size for the JobManager. This includes all the memory that a "
                                    + "JobManager JVM process consumes, consisting of Total Flink Memory, JVM Metaspace, and JVM Overhead. "
                                    + "In containerized setups, this should be set to the container memory. See also "
                                    + "'jobmanager.memory.flink.size' for Total Flink Memory size configuration.");
}

CustomCommandLine 继承关系:

![[【Flink】详解Flink任务提交流程_image_1.png]]

参数解析

class CliFrontendParser{
    // 选项列表
    // 解析和以executorOption等的逻辑都是一样的
    static final Option SAVEPOINT_DISPOSE_OPTION =
            new Option("d", "dispose", true, "Path of savepoint to dispose.");
    
    static final Option HELP_OPTION =
            new Option(
                    "h",
                    "help",
                    false,
                    "Show the help message for the CLI Frontend or the action.");
    ...
}

class DefaultParser{
    public CommandLine parse(Options options, String[] arguments, Properties properties, boolean stopAtNonOption) throws ParseException {
        ...if (arguments != null) {
            String[] var9 = arguments;
            int var10 = arguments.length;

            for(int var7 = 0; var7 < var10; ++var7) {
                String argument = var9[var7];
                // 核心解析逻辑
                this.handleToken(argument);
            }
        }
        ...
    }
    
    private void handleToken(String token) throws ParseException {
        this.currentToken = token;
        if (this.skipParsing) {
            this.cmd.addArg(token);
        } else if ("--".equals(token)) {
            this.skipParsing = true;
        } else if (this.currentOption != null && this.currentOption.acceptsArg() && this.isArgument(token)) {
        // 添加参数值
        this.currentOption.addValueForProcessing(
                this.stripLeadingAndTrailingQuotesDefaultOn(token));
        } else if (token.startsWith("--")) {
            // TODO 解析 -- 形式参数;
            this.handleLongOption(token);
        } else if (token.startsWith("-") && !"-".equals(token)) {
            // TODO 解析 - 形式参数
            this.handleShortAndLongOption(token);
        } else {
            // TODO 解析未知参数
            this.handleUnknownToken(token);
        }

        if (this.currentOption != null && !this.currentOption.acceptsArg()) {
            this.currentOption = null;
        }

    }
    
    private void handleLongOption(String token) throws ParseException {
        if (token.indexOf(61) == -1) {
            // TODO 解析的是不包含=号的,例如-L、--l
            this.handleLongOptionWithoutEqual(token);
        } else {
            // TODO 解析包含=号的,例如--L=V
            this.handleLongOptionWithEqual(token);
        }

    }
    
    // 解析逻辑都是去除前缀,然后校验参数
    private void handleLongOptionWithoutEqual(String token) throws ParseException {
        List<String> matchingOpts = this.getMatchingLongOptions(token);
        if (matchingOpts.isEmpty()) {
            this.handleUnknownToken(this.currentToken);
        } else {
            if (matchingOpts.size() > 1 && !this.options.hasLongOption(token)) {
                throw new AmbiguousOptionException(token, matchingOpts);
            }

            String key = this.options.hasLongOption(token) ? token : (String)matchingOpts.get(0);
            // 参数添加到执行命令
            this.handleOption(this.options.getOption(key));
        }

    }
    
    private List<String> getMatchingLongOptions(String token) {
        if (this.allowPartialMatching) {
            // 部分匹配走这里
            return this.options.getMatchingOptions(token);
        } else {
            List<String> matches = new ArrayList(1);
            if (this.options.hasLongOption(token)) {
                // 获取参数
                Option option = this.options.getOption(token);
                // 添加参数
                matches.add(option.getLongOpt());
            }

            return matches;
        }
    }
    
    public Option getOption(String opt) {
        // 先去前缀
        opt = Util.stripLeadingHyphens(opt);
        // 去完前缀后看短字符有没有包含,有返回短参数否则返回长参数
        return this.shortOpts.containsKey(opt) ? (Option)this.shortOpts.get(opt) : (Option)this.longOpts.get(opt);
    }
    
    private void handleOption(Option option) throws ParseException {
        // 添加前先检查起哪一个参数
        this.checkRequiredArgs();
        option = (Option)option.clone();
        this.updateRequiredOptions(option);
        // 添加参数
        this.cmd.addOption(option);
        // 设置当前参数,跟checkRequiredArgs 这个方法相配合
        if (option.hasArg()) {
            this.currentOption = option;
        } else {
            this.currentOption = null;
        }
    }
}

小结

  1. 程序的入口命令:run -t yarn-per-job /opt/module...ratget.jar --port 9999
  2. 入口类是:org.apache.flink.client.cli.CliFrontend;环境信息:Config.sh
  3. Run方法中
    1. validateAndGetActiveCommandLine:按照Generic、Yarn、Default顺序判断是否活跃,创建对应的客户端;
      1. Generic的isActive方法中
        1. 获取验证配置项是否存在
        2. 获取验证执行器参数是否存在,即 -e, executor
        3. 获取验证目标参数是否存在,即-t target
      2. FlinkYarnSessionCliisActive方法
        1. -m yarn-cluster:判断是否是per-job模式;
        2. 根据 JM 地址→是否有 APPID,看是否启动了 Yarn(从命令行和配置信息中检查)
        3. 使用“yarn-session”或者“yarn-per-job”进行比较,看exector是否是这两个;
        4. 上述三种情况任意一种成立都会使用 Yarn 模式;
      3. Default的isActive方法永远返回True;
    2. 根据输入参数封装了一个ProgramOptions对象,该对象包括 Java 的主要获取入口类、JAR 包路径、程序参数、并行度等;
    3. 获取并设置有效配置(高可用、JM 和 TM 内存、TM-SLOT 数),简而言之就是将多个配置信息(activeCommandLine, commandLine,programOptions, jobJars)变成一个Configuration对象;
    4. 将程序+配置信息封装成一个PackagedProgram对象然后调用其 execute 方法执行任务;

往期回顾

  1. 【Flink】Flink时间语义详解
  2. 【Flink】详解JobGraph
  3. 【Flink】详解StreamGraph
  4. 【Flink】浅谈Flink架构和调度
  5. 【Flink】详解Flink的八种分区
  6. 【Flink】浅谈Flink背压问题(1)
  7. 【分布式】浅谈CAP、BASE理论(1)

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

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

相关文章

快速搭建机器学习demo: gradio教程

1. Intro gradio是一个能够快速建立机器学习demo web应用的工具&#xff0c;仅需简单的几行代码就能构建机器学习模型的可视化交互demo&#xff0c;并分享给你的朋友使用。 与gradio相同功能的竞品有Streamlit&#xff0c;相比Gradio&#xff0c;Streamlit相对复杂&#xff0…

热门二叉树面试题

606. 根据二叉树创建字符串 - 力扣&#xff08;LeetCode&#xff09; 给你二叉树的根节点 root &#xff0c;请你采用前序遍历的方式&#xff0c;将二叉树转化为一个由括号和整数组成的字符串&#xff0c;返回构造出的字符串。 空节点使用一对空括号对 "()" 表示&a…

Baichuan-13B:130亿参数的开源语言模型,引领中文和英文benchmark

Baichuan-13B: 一个强大的开源大规模语言模型 标题&#xff1a;Baichuan-13B&#xff1a;130亿参数的开源语言模型&#xff0c;引领中文和英文benchmark Baichuan-13B是由百川智能开发的一个开源大规模语言模型项目&#xff0c;包含了130亿参数。该模型在中文和英文的权威ben…

基于Nginx深入浅出亿级流量架构设计(更新至2023.7.18)

基于Nginx深入浅出亿级流量架构设计 环境准备/安装部署Nginx四个发行版本简单介绍Nginx的安装 Nginx的目录结构与基本运行原理及其最小配置解析Nginx虚拟主机与域名配置ServerName匹配规则反向代理在系统结构中的应用场景Nginx的反向代理配置基于反向代理的负载均衡器 环境准备…

自洽性改善语言模型中的思维链推理

自洽性改善语言模型中的思维链推理 摘要介绍对多样化路径的自洽实验实验设置主要结果当CoT影响效率时候&#xff0c;SC会有所帮助与现有方法进行比较附加研究 相关工作总结 原文&#xff1a; 摘要 本篇论文提出了一种新的编码策略——自洽性&#xff0c;来替换思维链中使用的…

【STM32】SPI屏幕刷图总结:GPIO模拟,硬件SPI,DMA+硬件SPI

文章目录 GPIO模拟SPI硬件SPI外设DMA硬件SPI外设总结 代码工程&#xff1a;https://github.com/liefyuan/stm32-spi-st7789-tft.git 前言 我的屏幕的分辨率是&#xff1a;240*320 驱动是&#xff1a;ST7789V 线驱动方式&#xff1a;四线SPI&#xff08;CS&#xff0c;DC&#…

206. 反转链表

给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1]示例 3&#xff1a; 输入&am…

PageObject+Python+Appium

目录 前言&#xff1a; 简介 功能 常用目录 配置 实例 - 第一次启动 app 实例 - 登录 代码入口实例 结果展示 前言&#xff1a; Page Object模式是一种常用的设计模式&#xff0c;用于组织和管理自动化测试脚本中的页面对象。它将页面的元素和操作封装在一个独立的类…

数据质量相关问题系列

数据质量问题是什么&#xff1f; 数据质量&#xff0c;是指在业务环境下&#xff0c;数据符合数据消费者的使用目的&#xff0c;能满足业务场景具体需求的程度。而数据质量问题是指数据集中存在无法容忍的缺陷&#xff0c;从而降低该数据的可靠性和可信度。简单理解就是数据很脏…

【JAVA】云HIS系统功能菜单知识(一)

一、云HIS特色 云HIS滚动消息栏&#xff1a;质控消息、住院时长、药库结转、患者入院、医嘱停止、新开医嘱、门诊用药不良、出院审核、药品调拨、排班提醒、药品库存、药品过期、药品临期等帮助医生、护士和相关管理人员实时接收院内消息并作出处理。 二、云HIS功能菜单 【预约…

【MySQL】MySQL事务保姆级教程(适合MySQL初学者学习)

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;啥技术都喜欢捣鼓捣鼓&#xff0c;喜欢分享技术、经验、生活。 &#x1f60e;人生感悟&#xff1a;尝尽人生百味&#xff0c;方知世间冷暖。 &#x1f4d6;所属专栏&#xff1a;重…

桥接模式——连接抽象维度和实现维度

桥接模式 bridge pattern 一 定义 桥接模式&#xff0c;是将抽象部分与它的具体实现部分分离&#xff0c;使它们都可以独立地变化。它是将两个不同的维度建立联系。这两个维度通常是指&#xff1a;抽象维度和实现维度。 使用场景 在抽象和具体实现之间需要增加更多的灵活性…

找不到dll的问题解决,loadlibrary 126错误

症状&#xff1a;loadlibrary时&#xff0c;getlasterror返回126&#xff0c;表示是找不到模块错误。来到目录下看到依赖所需的dll均存在 解决办法&#xff1a; 1.下载ProcmonProcmom-监视进程所有的动作资源-CSDN文库https://download.csdn.net/download/gergul/880597622.运…

RocketMQ第四节(部署模式、监控面板等)

1&#xff1a;mq的部署模式 部署方式 | RocketMQ 参考官网。 单机模式&#xff1a;抗风险能力差&#xff0c;单机挂机没服务&#xff0c;单机硬盘损坏&#xff0c;丢失数据 多机&#xff08;多master没有Slave副本&#xff09;: 多个master采用RAID10磁盘&#xff0c;不会丢…

[java安全]类加载器CommonsCollections3

文章目录 【java安全】类加载器&CommonsCollections3前言java类加载器URLClassLoader利用ClassLoader#defineClass()直接加载字节码 使用TemplatesImpl加载字节码TemplatesImpl中字节码实现AbstractTranslet类 构造未完成POCTrAXFilter类调用newTransformer()InstantiateTr…

【AI绘画Stable Diffusion】高清图像放大+面部修复+一键抠图,谈谈你可能不知道的优化技巧!

一提起后期处理&#xff0c;我猜你可能立马想到的就是图像放大。 今天&#xff0c;我要向你展示的后期处理手法&#xff0c;以及优化技巧。 图片放大算法 如果你常用的是秋叶大佬的整合包&#xff0c;那么你对"R-ESRGAN 4x"和"R-ESRGAN 4x Anime6B"应该…

FPGA单端口RAM——IP核

文章目录 前言一、RAM简介1、随机存储器IP核分类1、RAM IP核2、ROM IP核 2、RAM IP核 二、IP核配置步骤三、源码1、ram_rw驱动文件2、ip_1port_ram顶层文件3、仿真文件4、仿真波形 四、SignalTap II在线调试五、总结六、参考资料 前言 环境&#xff1a; 1、Quartus18.1 2、vsco…

Leetcode-每日一题【1019.链表中的下一个更大结点】

题目 给定一个长度为 n 的链表 head 对于列表中的每个节点&#xff0c;查找下一个 更大节点 的值。也就是说&#xff0c;对于每个节点&#xff0c;找到它旁边的第一个节点的值&#xff0c;这个节点的值 严格大于 它的值。 返回一个整数数组 answer &#xff0c;其中 answer[…

Servlet的监听器

Servlet常用的监听器 ServletContextAttributeListener 用来感知ServlerContext对象属性变化&#xff0c;比如添加或删除属性变化 ServletContextListener 用来感知ServlerContext对象的创建和销毁的 ServletRequestListener 可以用来监听感知ServletRequest对象的创建和销毁的…

大语言模型(LLM)评估综述

论文地址&#xff1a;https://arxiv.org/abs/2307.03109 Github地址&#xff1a;https://github.com/MLGroupJLU/LLM-eval-survey 一 、背景 随着ChatGPT和GPT-4的推出&#xff0c;大型语言模型&#xff08;LLM&#xff09;在学术界和工业界的热度日益升高&#xff0c;这主要…