SpringBoot 动态加载jar包,动态配置

一、概述

1、背景

目前数据治理服务中有众多治理任务,当其中任一治理任务有改动需要升级或新增一个治理任务时,都需要将数据治理服务重启,会影响其他治理任务的正常运行。

2、目标
  • 能够动态启动、停止任一治理任务

  • 能够动态升级、添加治理任务

  • 启动、停止治理任务或升级、添加治理任务不能影响其他任务

3、方案
  • 为了支持业务代码尽量的解耦,把部分业务功能通过动态加载的方式加载到主程序中,以满足可插拔式的加载、组合式的部署。

  • 配合xxl-job任务调度框架,将数据治理任务做成xxl-job任务的方式注册到xxl-job中,方便统一管理。

二、动态加载

1、自定义类加载器

URLClassLoader 是一种特殊的类加载器,可以从指定的 URL 中加载类和资源。它的主要作用是动态加载外部的 JAR 包或者类文件,从而实现动态扩展应用程序的功。为了便于管理动态加载的jar包,自定义类加载器继承URLClassloader。

package cn.jy.sjzl.util;import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** 自定义类加载器** @author lijianyu* @date 2023/04/03 17:54**/
public class MyClassLoader extends URLClassLoader {private Map<String, Class<?>> loadedClasses = new ConcurrentHashMap<>();public Map<String, Class<?>> getLoadedClasses() {return loadedClasses;}public MyClassLoader(URL[] urls, ClassLoader parent) {super(urls, parent);}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {// 从已加载的类集合中获取指定名称的类Class<?> clazz = loadedClasses.get(name);if (clazz != null) {return clazz;}try {// 调用父类的findClass方法加载指定名称的类clazz = super.findClass(name);// 将加载的类添加到已加载的类集合中loadedClasses.put(name, clazz);return clazz;} catch (ClassNotFoundException e) {e.printStackTrace();return null;}}public void unload() {try {for (Map.Entry<String, Class<?>> entry : loadedClasses.entrySet()) {// 从已加载的类集合中移除该类String className = entry.getKey();loadedClasses.remove(className);try{// 调用该类的destory方法,回收资源Class<?> clazz = entry.getValue();Method destory = clazz.getDeclaredMethod("destory");destory.invoke(clazz);} catch (Exception e ) {// 表明该类没有destory方法}}// 从其父类加载器的加载器层次结构中移除该类加载器close();} catch (Exception e) {e.printStackTrace();}}
}
  • 自定义类加载器中,为了方便类的卸载,定义一个map保存已加载的类信息。key为这个类的ClassName,value为这个类的类信息。

  • 同时定义了类加载器的卸载方法,卸载方法中,将已加载的类的集合中移除该类。由于此类可能使用系统资源或调用线程,为了避免资源未回收引起的内存溢出,通过反射调用这个类中的destroy方法,回收资源。

  • 最后调用close方法。

2、动态加载

由于此项目使用spring框架,以及xxl-job任务的机制调用动态加载的代码,因此要完成以下内容

  • 将动态加载的jar包读到内存中

  • 将有spring注解的类,通过注解扫描的方式,扫描并手动添加到spring容器中。

  • 将@XxlJob注解的方法,通过注解扫描的方式,手动添加到xxljob执行器中。

package com.jy.dynamicLoad;import com.jy.annotation.XxlJobCron;
import com.jy.classLoader.MyClassLoader;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.xxl.job.core.handler.impl.MethodJobHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;/*** @author lijianyu* @date 2023/04/29 13:18**/
@Component
public class DynamicLoad {private static Logger logger = LoggerFactory.getLogger(DynamicLoad.class);@Autowiredprivate ApplicationContext applicationContext;private Map<String, MyClassLoader> myClassLoaderCenter = new ConcurrentHashMap<>();@Value("${dynamicLoad.path}")private String path;/*** 动态加载指定路径下指定jar包* @param path* @param fileName* @param isRegistXxlJob  是否需要注册xxljob执行器,项目首次启动不需要注册执行器* @return map<jobHander, Cron> 创建xxljob任务时需要的参数配置*/public void loadJar(String path, String fileName, Boolean isRegistXxlJob) throws ClassNotFoundException, InstantiationException, IllegalAccessException {File file = new File(path +"/" + fileName);Map<String, String> jobPar = new HashMap<>();// 获取beanFactoryDefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();// 获取当前项目的执行器try {// URLClassloader加载jar包规范必须这么写URL url = new URL("jar:file:" + file.getAbsolutePath() + "!/");URLConnection urlConnection = url.openConnection();JarURLConnection jarURLConnection = (JarURLConnection)urlConnection;// 获取jar文件JarFile jarFile = jarURLConnection.getJarFile();Enumeration<JarEntry> entries = jarFile.entries();// 创建自定义类加载器,并加到map中方便管理MyClassLoader myClassloader = new MyClassLoader(new URL[] { url }, ClassLoader.getSystemClassLoader());myClassLoaderCenter.put(fileName, myClassloader);Set<Class> initBeanClass = new HashSet<>(jarFile.size());// 遍历文件while (entries.hasMoreElements()) {JarEntry jarEntry = entries.nextElement();if (jarEntry.getName().endsWith(".class")) {// 1. 加载类到jvm中// 获取类的全路径名String className = jarEntry.getName().replace('/', '.').substring(0, jarEntry.getName().length() - 6);// 1.1进行反射获取myClassloader.loadClass(className);}}Map<String, Class<?>> loadedClasses = myClassloader.getLoadedClasses();XxlJobSpringExecutor xxlJobExecutor = new XxlJobSpringExecutor();for(Map.Entry<String, Class<?>> entry : loadedClasses.entrySet()){String className = entry.getKey();Class<?> clazz = entry.getValue();// 2. 将有@spring注解的类交给spring管理// 2.1 判断是否注入springBoolean flag = SpringAnnotationUtils.hasSpringAnnotation(clazz);if(flag){// 2.2交给spring管理BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();// 此处beanName使用全路径名是为了防止beanName重复String packageName = className.substring(0, className.lastIndexOf(".") + 1);String beanName = className.substring(className.lastIndexOf(".") + 1);beanName = packageName + beanName.substring(0, 1).toLowerCase() + beanName.substring(1);// 2.3注册到spring的beanFactory中beanFactory.registerBeanDefinition(beanName, beanDefinition);// 2.4允许注入和反向注入beanFactory.autowireBean(clazz);beanFactory.initializeBean(clazz, beanName);/*if(Arrays.stream(clazz.getInterfaces()).collect(Collectors.toSet()).contains(InitializingBean.class)){initBeanClass.add(clazz);}*/initBeanClass.add(clazz);}// 3. 带有XxlJob注解的方法注册任务// 3.1 过滤方法Map<Method, XxlJob> annotatedMethods = null;try {annotatedMethods = MethodIntrospector.selectMethods(clazz,new MethodIntrospector.MetadataLookup<XxlJob>() {@Overridepublic XxlJob inspect(Method method) {return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);}});} catch (Throwable ex) {}// 3.2 生成并注册方法的JobHanderfor (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {Method executeMethod = methodXxlJobEntry.getKey();// 获取jobHander和CronXxlJobCron xxlJobCron = executeMethod.getAnnotation(XxlJobCron.class);if(xxlJobCron == null){throw new CustomException("500", executeMethod.getName() + "(),没有添加@XxlJobCron注解配置定时策略");}if (!CronExpression.isValidExpression(xxlJobCron.value())) {throw new CustomException("500", executeMethod.getName() + "(),@XxlJobCron参数内容错误");}XxlJob xxlJob = methodXxlJobEntry.getValue();jobPar.put(xxlJob.value(), xxlJobCron.value());if (isRegistXxlJob) {executeMethod.setAccessible(true);// registMethod initMethod = null;Method destroyMethod = null;xxlJobExecutor.registJobHandler(xxlJob.value(), new CustomerMethodJobHandler(clazz, executeMethod, initMethod, destroyMethod));}}}// spring bean实际注册initBeanClass.forEach(beanFactory::getBean);} catch (IOException e) {logger.error("读取{} 文件异常", fileName);e.printStackTrace();throw new RuntimeException("读取jar文件异常: " + fileName);}}
}

以下是判断该类是否有spring注解的工具类

apublic class SpringAnnotationUtils {private static Logger logger = LoggerFactory.getLogger(SpringAnnotationUtils.class);/*** 判断一个类是否有 Spring 核心注解** @param clazz 要检查的类* @return true 如果该类上添加了相应的 Spring 注解;否则返回 false*/public static boolean hasSpringAnnotation(Class<?> clazz) {if (clazz == null) {return false;}//是否是接口if (clazz.isInterface()) {return false;}//是否是抽象类if (Modifier.isAbstract(clazz.getModifiers())) {return false;}try {if (clazz.getAnnotation(Component.class) != null ||clazz.getAnnotation(Repository.class) != null ||clazz.getAnnotation(Service.class) != null ||clazz.getAnnotation(Controller.class) != null ||clazz.getAnnotation(Configuration.class) != null) {return true;}}catch (Exception e){logger.error("出现异常:{}",e.getMessage());}return false;}
}

注册xxljob执行器的操作是仿照的xxljob中的XxlJobSpringExecutor的注册方法。

3、动态卸载

动态卸载的过程,就是将动态加载的代码,从内存,spring以及xxljob中移除。

代码如下:

/*** 动态卸载指定路径下指定jar包* @param fileName* @return map<jobHander, Cron> 创建xxljob任务时需要的参数配置*/
public void unloadJar(String fileName) throws IllegalAccessException, NoSuchFieldException {// 获取加载当前jar的类加载器MyClassLoader myClassLoader = myClassLoaderCenter.get(fileName);// 获取jobHandlerRepository私有属性,为了卸载xxljob任务Field privateField = XxlJobExecutor.class.getDeclaredField("jobHandlerRepository");// 设置私有属性可访问privateField.setAccessible(true);// 获取私有属性的值jobHandlerRepositoryXxlJobExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();Map<String, IJobHandler> jobHandlerRepository = (ConcurrentHashMap<String, IJobHandler>) privateField.get(xxlJobSpringExecutor);// 获取beanFactory,准备从spring中卸载DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();Map<String, Class<?>> loadedClasses = myClassLoader.getLoadedClasses();Set<String> beanNames = new HashSet<>();for (Map.Entry<String, Class<?>> entry: loadedClasses.entrySet()) {// 1. 将xxljob任务从xxljob执行器中移除// 1.1 截取beanNameString key = entry.getKey();String packageName = key.substring(0, key.lastIndexOf(".") + 1);String beanName = key.substring(key.lastIndexOf(".") + 1);beanName = packageName + beanName.substring(0, 1).toLowerCase() + beanName.substring(1);// 获取bean,如果获取失败,表名这个类没有加到spring容器中,则跳出本次循环Object bean = null;try{bean = applicationContext.getBean(beanName);}catch (Exception e){// 异常说明spring中没有这个beancontinue;}// 1.2 过滤方法Map<Method, XxlJob> annotatedMethods = null;try {annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),new MethodIntrospector.MetadataLookup<XxlJob>() {@Overridepublic XxlJob inspect(Method method) {return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);}});} catch (Throwable ex) {}// 1.3 将job从执行器中移除for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {XxlJob xxlJob = methodXxlJobEntry.getValue();jobHandlerRepository.remove(xxlJob.value());}// 2.0从spring中移除,这里的移除是仅仅移除的bean,并未移除bean定义beanNames.add(beanName);beanFactory.destroyBean(beanName, bean);}// 移除bean定义Field mergedBeanDefinitions = beanFactory.getClass().getSuperclass().getSuperclass().getDeclaredField("mergedBeanDefinitions");mergedBeanDefinitions.setAccessible(true);Map<String, RootBeanDefinition> rootBeanDefinitionMap = ((Map<String, RootBeanDefinition>) mergedBeanDefinitions.get(beanFactory));for (String beanName : beanNames) {beanFactory.removeBeanDefinition(beanName);// 父类bean定义去除rootBeanDefinitionMap.remove(beanName);}// 卸载父任务,子任务已经在循环中卸载jobHandlerRepository.remove(fileName);// 3.2 从类加载中移除try {// 从类加载器底层的classes中移除连接Field field = ClassLoader.class.getDeclaredField("classes");field.setAccessible(true);Vector<Class<?>> classes = (Vector<Class<?>>) field.get(myClassLoader);classes.removeAllElements();// 移除类加载器的引用myClassLoaderCenter.remove(fileName);// 卸载类加载器myClassLoader.unload();} catch (NoSuchFieldException e) {logger.error("动态卸载的类,从类加载器中卸载失败");e.printStackTrace();} catch (IllegalAccessException e) {logger.error("动态卸载的类,从类加载器中卸载失败");e.printStackTrace();}logger.error("{} 动态卸载成功", fileName);}
4、动态配置

使用动态加载时,为了避免服务重新启动后丢失已加载的任务包,使用动态配置的方式,加载后动态更新初始化加载配置。

以下提供了两种自己实际操作过的配置方式。

4.1 动态修改本地yml

动态修改本地yml配置文件,需要添加snakeyaml的依赖

4.1.1 依赖引入

<dependency><groupId>org.yaml</groupId><artifactId>snakeyaml</artifactId><version>1.29</version>
</dependency>

4.1.2 工具类

读取指定路径下的配置文件,并进行修改。

package com.jy.util;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;/*** 用于动态修改bootstrap.yml配置文件* @author lijianyu* @date 2023/04/18 17:57**/
@Component
public class ConfigUpdater {public void updateLoadJars(List<String> jarNames) throws IOException {// 读取bootstrap.ymlYaml yaml = new Yaml();InputStream inputStream = new FileInputStream(new File("src/main/resources/bootstrap.yml"));Map<String, Object> obj = yaml.load(inputStream);inputStream.close();obj.put("loadjars", jarNames);// 修改FileWriter writer = new FileWriter(new File("src/main/resources/bootstrap.yml"));DumperOptions options = new DumperOptions();options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);options.setPrettyFlow(true);Yaml yamlWriter = new Yaml(options);yamlWriter.dump(obj, writer);}
}
4.2 动态修改nacos配置

Spring Cloud Alibaba Nacos组件完全支持在运行时通过代码动态修改配置,还提供了一些API供开发者在代码里面实现动态修改配置。在每次动态加载或卸载数据治理任务jar包时,执行成功后都会进行动态更新nacos配置。

package cn.jy.sjzl.config;import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;import java.util.Properties;@Configuration
public class NacosConfig {@Value("${spring.cloud.nacos.server-addr}")private String serverAddr;@Value("${spring.cloud.nacos.config.namespace}")private String namespace;public ConfigService configService() throws NacosException {Properties properties = new Properties();properties.put("serverAddr", serverAddr);properties.put("namespace", namespace);return NacosFactory.createConfigService(properties);}
}
package cn.jy.sjzl.util;import cn.jy.sjzl.config.NacosConfig;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.config.ConfigService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;/*** nacos配置中,修改sjzl-loadjars.yml** @author lijianyu* @date 2023/04/19 17:59**/
@Component
public class NacosConfigUtil {private static Logger logger = LoggerFactory.getLogger(NacosConfigUtil.class);@Autowiredprivate NacosConfig nacosConfig;private String dataId = "sjzl-loadjars.yml";@Value("${spring.cloud.nacos.config.group}")private String group;/*** 从nacos配置文件中,添加初始化jar包配置* @param jarName 要移除的jar包名* @throws Exception*/public void addJarName(String jarName) throws Exception {ConfigService configService = nacosConfig.configService();String content = configService.getConfig(dataId, group, 5000);// 修改配置文件内容YAMLMapper yamlMapper = new YAMLMapper();ObjectMapper jsonMapper = new ObjectMapper();Object yamlObject = yamlMapper.readValue(content, Object.class);String jsonString = jsonMapper.writeValueAsString(yamlObject);JSONObject jsonObject = JSONObject.parseObject(jsonString);List<String> loadjars;if (jsonObject.containsKey("loadjars")) {loadjars = (List<String>) jsonObject.get("loadjars");}else{loadjars = new ArrayList<>();}if (!loadjars.contains(jarName)) {loadjars.add(jarName);}jsonObject.put("loadjars" , loadjars);Object yaml = yamlMapper.readValue(jsonMapper.writeValueAsString(jsonObject), Object.class);String newYamlString = yamlMapper.writeValueAsString(yaml);boolean b = configService.publishConfig(dataId, group, newYamlString);if(b){logger.info("nacos配置更新成功");}else{logger.info("nacos配置更新失败");}}
}

三、分离打包

分离打包时,根据实际情况在pom.xml中修改以下配置

<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.2.4</version><executions><execution><phase>package</phase><goals><goal>shade</goal></goals><configuration><filters><filter><artifact>*:*</artifact><includes><include>com/jy/job/demo/**</include></includes></filter></filters><finalName>demoJob</finalName></configuration></execution></executions></plugin></plugins>
</build>

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

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

相关文章

文献速递:肿瘤分割---- ALA-Net:用于3D结直肠肿瘤分割的自适应病变感知注意力网络

文献速递&#xff1a;肿瘤分割---- ALA-Net&#xff1a;用于3D结直肠肿瘤分割的自适应病变感知注意力网络 01 文献速递介绍 结直肠癌&#xff08;CRC&#xff09;在全球范围内与高发病率和死亡率相关&#xff0c;。肿瘤的预后高度依赖于诊断时疾病的阶段。准确检测和分割肿瘤…

不需英文基础也可以轻松学编程,中文编程开发工具免费版下载,编程工具构件箱之扩展控制面板构件用法

不需英文基础也可以轻松学编程&#xff0c;中文编程开发工具免费版下载&#xff0c;编程工具构件箱之扩展控制面板构件用法 一、前言 编程入门视频教程链接 https://edu.csdn.net/course/detail/39036 编程工具及实例源码文件下载可以点击最下方官网卡片——软件下载——常…

1-3 动手学深度学习v2-线性回归的从零开始实现-笔记

手动创建训练数据集 根据带有噪声的线性模型构造一个人造数据集。我们使用线性模型参数 w [ 2 , − 3.4 ] T \pmb{w} [2,-3.4]^{T} w[2,−3.4]T、 b 4.2 b 4.2 b4.2和噪声项 ϵ \epsilon ϵ生成数据集及其标签&#xff1a; y X w b ϵ \pmb{y} \pmb{Xw}b\epsilon yXw…

【Mysql】用户权限操作、处理重复数据

用户权限操作 登陆进入数据库 输入用户名和密码 #mysql -uroot -p;创建一个名字为“mei_db” 编码为utf-8的数据库 create database if not exists mei_db default character set utf8 collate utf8_general_ci;# 创建用户&#xff0c;用户可以通过特定ip的客户端访问数据库…

2024年【化工自动化控制仪表】考试题及化工自动化控制仪表最新解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年化工自动化控制仪表考试题为正在备考化工自动化控制仪表操作证的学员准备的理论考试专题&#xff0c;每个月更新的化工自动化控制仪表最新解析祝您顺利通过化工自动化控制仪表考试。 1、【单选题】()指用多个相…

Flask 入门5 :过滤器

1. 自定义过滤器 过滤器本质上是 Python 的函数&#xff0c;他会把被过滤的值当作第一个参数传给这个函数&#xff0c;函数经过一些逻辑处理后&#xff0c;再返回新的值。在过滤器函数写好后&#xff0c;可以通过app.template_filter 装饰器或者是app.add_template_filter函数…

spring-security authentication persistence

翻译版本【spring-security 6.2.1】persistence Persisting Authentication 用户第一次请求受保护的资源时&#xff0c;系统会提示他们输入凭据。提示输入凭据的最常见方法之一是将用户重定向到登录页面。未经身份验证的用户请求受保护的资源的HTTP交换可能如下所示: 例1。未…

关于Linux和消息队列常见的十道面试题

实际工作中如何排查CPU飙升问题&#xff1f; 在实际工作中&#xff0c;我们可以通过以下步骤来排查CPU飙升的问题&#xff1a; 使用系统监控工具&#xff1a;首先&#xff0c;我们可以使用系统监控工具&#xff0c;如top命令&#xff0c;来查看所有进程占系统CPU的排序。这样可…

编程笔记 html5cssjs 079 JavaScript 循环语句

编程笔记 html5&css&js 079 JavaScript 循环语句 循环语句1. for 循环2. while 循环3. do...while 循环4. for...in 循环5. for...of 循环 小结 在JavaScript中&#xff0c;有几种不同的循环语句用于执行重复的代码块。 循环语句 以下是一些主要的循环类型及其详解与示…

PHP三级分类数据处理

一、原始数据 function getDatas(){return [[component > system,redict > /system,path > /system,hidden > 1,name > 系统管理,children > [[component > user,redict > /user,path > /user,hidden > 1,name > 用户管理,children > [[c…

倒计时63天

e/f/j e:&#xff0c;&#xff0c;&#xff0c;一道e题卡我2个多h&#xff0c;幸好之后去写i题了&#xff0c;不然就完了╥﹏╥... E-Tokitsukaze and Eliminate (easy)_2024牛客寒假算法基础集训营2 (nowcoder.com) 我的思路&#xff1a;倒过来看不同个数再考虑一下1212这样…

UML---用例图,类图

用例图 用例图&#xff08;Use Case Diagram&#xff09;主要描述系统的功能需求和参与者与系统之间的交互。它是用户与系统交互的最简表示形式&#xff0c;展现了用户和与他相关的用例之间的关系。用例图被视为系统的蓝图&#xff0c;通过它&#xff0c;人们可以获知系统不同种…

mysql:事务的特性ACID、并发事务(脏读、不可重复读、幻读、如何解决、隔离级别)、undo log和redo log的区别、相关面试题和答案

事务是一组操作的集合&#xff0c;它会把所有的操作作为一个整体一起向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 事务的特性&#xff08;ACID&#xff09; 原子性&#xff08;Atomicity&#xff09;&#xff1a;事务是不可分割的…

Flink-1.18.1环境搭建

下载 下载flink安装包 Index of /dist/flink/flink-1.18.1 下载flink-cdc安装包 Release Release 3.0.0 ververica/flink-cdc-connectors GitHub 安装 添加环境变量 vi ~/.bash_profile export FLINK_HOME=/home/postgres/flink/flink-1.18.1 export PATH=$PATH:$FL…

idea开发工具的简单使用与常见问题

1、配置git 选择左上角目录file->setting 打开&#xff0c;Version Control 目录下Git&#xff0c;选择git安装目录下的git.exe文件&#xff1b; 点击test&#xff0c;出现git版本&#xff0c;则表示git识别成功&#xff0c;点击右下角确认即可生效。 2、配置node.js 选…

ubuntu开机报错/dev/nume0n1p2:clean

本来是开机卡在这个界面&#xff0c;经过以下操作&#xff0c;变成这种了 现在的问题变成linux卡在 failed to start NVIDIA Persistence Daemon 按照下面的操作方法&#xff0c;可以有开机界面了。但是输入密码后&#xff0c;一直在登录界面 1.方式一&#xff1a;重新安装显…

Prometheus主机相关指标表达式整理

问题描述 需要通过Prometheus采集监控的主机节点的cpu使用率、内存使用率、磁盘使用率等指标数据&#xff0c;并根据topk排序筛选出前topN的主机。对过程进行一下记录。 表达式 //磁盘读速率top10private final static String TOP10_DISK_READ "topk(10,sum(irate(node…

.gitlab-ci.yml文件参数

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

TypeScript 学习笔记(Day4)

「写在前面」 本文为 b 站黑马程序员 TypeScript 教程的学习笔记。本着自己学习、分享他人的态度&#xff0c;分享学习笔记&#xff0c;希望能对大家有所帮助。推荐先按顺序阅读往期内容&#xff1a; 1. TypeScript 学习笔记&#xff08;Day1&#xff09; 2. TypeScript 学习笔…

【计算机网络】Socket的SO_TIMEOUT与连接超时时间

SO_TIMEOUT选项是Socket的一个选项&#xff0c;用于设置读取数据的超时时间。它指定了在读取数据时等待的最长时间&#xff0c;如果在指定的时间内没有数据可读取&#xff0c;将抛出SocketTimeoutException异常。 SO_TIMEOUT的设置 默认情况下&#xff0c;SO_TIMEOUT选项的值…