设计一个简易版本的分布式任务调度系统

news2024/11/27 22:37:14
  • 👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家
  • 📕系列专栏:Spring源码、JUC源码、Kafka原理、分布式技术原理
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:源码溯源,一探究竟
  • 📝联系方式:nhs19990716,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬👀

文章目录

  • 简易版分布式任务调度实现
    • 需求分析
    • 设计思路
    • 实现原理
      • 自定义注解
      • 初始化服务
        • 容器上下文初始化
        • 扫描自定义注解
        • 监听容器刷新事件
          • 初始化配置
          • 初始化服务
          • 启动服务
          • 挂载节点
      • 节点监听
      • 并行任务执行
      • 可扩展自定义AOP
    • 最终效果
      • 测试
      • 管理后台
      • 效果
    • 总结
    • 项目地址

简易版分布式任务调度实现

需求分析

事实上,市面上有很多分布式任务调度框架,比如大名鼎鼎的xxl-job,以及各个大厂自研的分布式任务调度框架等,但是说到自研,为什么自研呢?

  • 定制化需求:因为可能有特定的业务需求,需要定制化的分布式任务调度框架来满足自身的业务场景和特定需求。
  • 技术积累:可能在分布式系统和任务调度领域有丰富的技术积累和经验,他们希望通过自研框架来进一步提升技术实力和竞争力。
  • 控制权:自研框架可以让大厂更好地掌控自身的技术发展和业务发展,降低对外部框架的依赖和风险。

针对上述一些特点,那么了解一个框架,或者说自研一个框架,需要怎么做?自研一个,自己做一个简易版,只实现核心功能即可!!!

那么对于实现分布式任务调度来说,除了实现核心功能外,还需要轻便型,最好是,简单配置后就能立马使用,要多简单就多简单,所以简便性也是设计简易版分布式任务调度的核心思路。

设计思路

前面说到了,想要简便性,那么该怎么办?是都在配置文件中配置好?还是怎么样?

其实为了达到简便性,我们可以使用注解的方式,也就是说,我们把想要定时控制的方法上面加个注解,然后它就可以定时执行了,这样最简便了。当了解到了这一点,接下来开始介绍核心架构。

在这里插入图片描述

一个容易理解的设计思路一般都是先从图解开始的。

首先扫描其中所有带有 某种注解 的方法,将其注册到注册中心(Zk)中,然后我们的管理后台扫描这些任务在后台页面中显示,最终在利用Zk中watcher的特性,监听某个节点的话,通过其变化,动态的控制任务的启动,修改配置参数等功能。

在这里插入图片描述

实现原理

自定义注解

/**
 这段代码定义了一个自定义注解 DcsScheduled,它可以用来标记方法,并指定该方法作为一个 Dcs 调度任务。
 */

@Retention(RetentionPolicy.RUNTIME) // 指定该注解在运行时保留,因此可以通过反射来访问该注解的信息。
@Target(ElementType.METHOD) // 指定该注解只能应用在方法上。
public @interface DcsScheduled {

    String desc() default "缺省"; // 用于描述调度任务的说明,默认取值为"缺省"。

    String cron() default ""; // 指定调度任务的 cron 表达式,用于设置任务的执行时间规则。

    boolean autoStartup() default true; // 指定是否自动启动调度任务,默认为 true。

}
/**
 这段代码定义了一个自定义注解 EnableDcsScheduling,它可以用来在Spring Boot应用中启用Dcs调度功能。
 */
@Target({ElementType.TYPE}) // 指定该注解只能应用在类上。
@Retention(RetentionPolicy.RUNTIME) // 指定该注解在运行时保留,因此可以通过反射来访问该注解的信息。
@Import({DcsSchedulingConfiguration.class}) // 指定在应用中导入 DcsSchedulingConfiguration 类的配置。
@ImportAutoConfiguration({SchedulingConfig.class, CronTaskRegister.class, DoJoinPoint.class})
//指定在应用中自动导入 SchedulingConfig、CronTaskRegister 和 DoJoinPoint 类的配置。
@ComponentScan("cn.nhs.*") // 指定扫描并加载 cn.nhs 包及其子包下的所有组件。
public @interface EnableDcsScheduling {

}

初始化服务

容器上下文初始化
// 获取上下文将其注入全局上下文中
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Constants.Global.applicationContext = applicationContext;
    }
扫描自定义注解
/**
     postProcessAfterInitialization 方法是在 Spring 容器实例化 Bean 并完成初始化后立即调用的。
     具体来说,它是在 Bean 初始化完成之后、即将返回给调用者之前被调用的。

     相当于Spring生命周期中的钩子函数
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 获取到对应的bean,如果之前已经存在在 set集合中了,相当于被处理过了,那么就直接从set集合中返回。
        Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
        if (this.nonAnnotatedClasses.contains(targetClass)) return bean;

        // 遍历bean中所有的方法
        Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass());

        for (Method method : methods) {
            // 去找使用了 @DcsScheduled 注解的方法
            DcsScheduled dcsScheduled = AnnotationUtils.findAnnotation(method, DcsScheduled.class);
            if (null == dcsScheduled || 0 == method.getDeclaredAnnotations().length) continue;
            // 当指定的键 beanName 在 Map 中不存在时,计算一个新的值并将其插入到 Map 中。如果指定的键存在,则直接返回对应的值。
            List<ExecOrder> execOrderList = Constants.execOrderMap.computeIfAbsent(beanName, k -> new ArrayList<>());
            ExecOrder execOrder = new ExecOrder();
            execOrder.setBean(bean);
            execOrder.setBeanName(beanName);
            execOrder.setMethodName(method.getName());
            execOrder.setDesc(dcsScheduled.desc());
            execOrder.setCron(dcsScheduled.cron());
            execOrder.setAutoStartup(dcsScheduled.autoStartup());
            execOrderList.add(execOrder);
            this.nonAnnotatedClasses.add(targetClass);
        }
        return bean;
    }
监听容器刷新事件
/**
     实现 ApplicationListener<ContextRefreshedEvent> 接口可以监听 Spring 容器的刷新事件。
     当 Spring 容器启动或刷新时,会触发 ContextRefreshedEvent 事件,从而调用 onApplicationEvent 方法。

     在 onApplicationEvent 方法中,可以编写自己的逻辑来处理容器刷新事件。
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        try {
            ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
            //1. 初始化配置
            init_config(applicationContext);
            //2. 初始化服务
            init_server(applicationContext);
            //3. 启动任务
            init_task(applicationContext);
            //4. 挂载节点
            init_node();
            //5. 心跳监听
            HeartbeatService.getInstance().startFlushScheduleStatus();

            logger.info("schedule init config、server、task、node、heart done!");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
初始化配置
//1. 初始化配置
    private void init_config(ApplicationContext applicationContext) {
        try {
            StarterServiceProperties properties = applicationContext.getBean("nhs-schedule-starterAutoConfig", StarterAutoConfig.class).getProperties();
            Constants.Global.zkAddress = properties.getZkAddress();
            Constants.Global.schedulerServerId = properties.getSchedulerServerId();
            Constants.Global.schedulerServerName = properties.getSchedulerServerName();
            InetAddress id = InetAddress.getLocalHost();
            Constants.Global.ip = id.getHostAddress();
        } catch (Exception e) {
            logger.error("middleware schedule init config error!", e);
            throw new RuntimeException(e);
        }
    }
初始化服务
//2. 初始化服务
    private void init_server(ApplicationContext applicationContext) {
        try {
            //获取zk连接
            CuratorFramework client = ZkCuratorServer.getClient(Constants.Global.zkAddress);
            //节点组装

            // /cn/nhs/schedule/server/schedule-spring-boot-starter-test
            path_root_server = StrUtil.joinStr(path_root, LINE, "server", LINE, schedulerServerId);
            // /cn/nhs/schedule/server/schedule-spring-boot-starter-test/ip/本机ip地址
            path_root_server_ip = StrUtil.joinStr(path_root_server, LINE, "ip", LINE, Constants.Global.ip);

            //创建节点&递归删除本服务IP下的旧内容
            ZkCuratorServer.deletingChildrenIfNeeded(client, path_root_server_ip);
            ZkCuratorServer.createNode(client, path_root_server_ip);
            ZkCuratorServer.setData(client, path_root_server, schedulerServerName);

            //添加节点&监听
            //  /cn/nhs/schedule/exec
            ZkCuratorServer.createNodeSimple(client, Constants.Global.path_root_exec);
            ZkCuratorServer.addTreeCacheListener(applicationContext, client, Constants.Global.path_root_exec);
        } catch (Exception e) {
            logger.error("schedule init server error!", e);
            throw new RuntimeException(e);
        }
    }
启动服务
//3. 启动任务
    private void init_task(ApplicationContext applicationContext) {
        CronTaskRegister cronTaskRegistrar = applicationContext.getBean("nhs-schedule-cronTaskRegister", CronTaskRegister.class);
        Set<String> beanNames = Constants.execOrderMap.keySet();
        for (String beanName : beanNames) {
            List<ExecOrder> execOrderList = Constants.execOrderMap.get(beanName);
            for (ExecOrder execOrder : execOrderList) {
                if (!execOrder.getAutoStartup()) continue;
                SchedulingRunnable task = new SchedulingRunnable(execOrder.getBean(), execOrder.getBeanName(), execOrder.getMethodName());
                cronTaskRegistrar.addCronTask(task, execOrder.getCron());
            }
        }
    }
挂载节点
private void init_node() throws Exception {
        Set<String> beanNames = Constants.execOrderMap.keySet();
        for (String beanName : beanNames) {
            List<ExecOrder> execOrderList = Constants.execOrderMap.get(beanName);
            for (ExecOrder execOrder : execOrderList) {
                String path_root_server_ip_clazz = StrUtil.joinStr(path_root_server_ip, LINE, "clazz", LINE, execOrder.getBeanName());
                String path_root_server_ip_clazz_method = StrUtil.joinStr(path_root_server_ip_clazz, LINE, "method", LINE, execOrder.getMethodName());
                String path_root_server_ip_clazz_method_status = StrUtil.joinStr(path_root_server_ip_clazz, LINE, "method", LINE, execOrder.getMethodName(), "/status");
                //添加节点
                ZkCuratorServer.createNodeSimple(client, path_root_server_ip_clazz);
                ZkCuratorServer.createNodeSimple(client, path_root_server_ip_clazz_method);
                ZkCuratorServer.createNodeSimple(client, path_root_server_ip_clazz_method_status);
                //添加节点数据[临时]
                ZkCuratorServer.appendPersistentData(client, path_root_server_ip_clazz_method + "/value", JSON.toJSONString(execOrder));
                //添加节点数据[永久]
                ZkCuratorServer.setData(client, path_root_server_ip_clazz_method_status, execOrder.getAutoStartup() ? "1" : "0");
            }
        }
    }

节点监听

//所有子节点监听
    public static void addTreeCacheListener(final ApplicationContext applicationContext, final CuratorFramework client, String path) throws Exception {
        /**
         具体而言,TreeCache是ZooKeeper的一个监听器,可以监控指定节点及其子节点的变化。
         通过创建TreeCache实例并启动它,可以在ZooKeeper中的指定节点上设置监听器,以便在节点发生变化时触发相应的事件。
         启动TreeCache后,它会从指定节点开始递归地缓存其下所有的子节点和数据,并且持续监控这些节点的状态变化。
         */
        TreeCache treeCache = new TreeCache(client, path);
        treeCache.start();
        // 为 treeCache 添加一个监听器,当节点发生变化时,会调用对应的回调函数进行处理。
        treeCache.getListenable().addListener((curatorFramework, event) -> {

            // 这段代码的作用是从 ZooKeeper 的监听事件中解析出一个 Instruct 对象。
            /*
            具体来说,代码首先判断事件中是否包含有效数据,如果没有则直接返回。然后将事件中的数据转换为字节数组,
            再将字节数组转换为字符串,并根据一些条件进行判断,确保该字符串是一个合法的 JSON 格式。

            如果判断失败,则直接返回。若判断成功,则利用 JSON.parseObject() 方法将该 JSON
            字符串解析成一个 Instruct 对象,并返回该对象。
            */
            if (null == event.getData()) return;
            byte[] eventData = event.getData().getData();
            if (null == eventData || eventData.length < 1) return;
            String json = new String(eventData, Constants.Global.CHARSET_NAME);
            if ("".equals(json) || json.indexOf("{") != 0 || json.lastIndexOf("}") + 1 != json.length()) return;
            Instruct instruct = JSON.parseObject(new String(event.getData().getData(), Constants.Global.CHARSET_NAME), Instruct.class);
            // 在回调函数中,判断事件的类型,如果是节点新增或更新,则根据节点中存储的信息以及一些条件进行相应的业务逻辑处理。
            switch (event.getType()) {
                case NODE_ADDED:
                case NODE_UPDATED:
                    // 如果当前本机的ip 与 schedulerServerId 都与 回调返回的相等。
                    if (Constants.Global.ip.equals(instruct.getIp()) &&
                            Constants.Global.schedulerServerId.equals(instruct.getSchedulerServerId())) {

                        //获取对象
                        CronTaskRegister cronTaskRegistrar = applicationContext.getBean("nhs-schedule-cronTaskRegister", CronTaskRegister.class);
                        boolean isExist = applicationContext.containsBean(instruct.getBeanName());
                        if (!isExist) return;
                        Object scheduleBean = applicationContext.getBean(instruct.getBeanName());
                        // /cn/nhs/schedule/server/schedule-spring-boot-starter-test/ip/机器ip/clazz/类对象名称/method/方法名称/status
                        String path_root_server_ip_clazz_method_status = StrUtil.joinStr(path_root, Constants.Global.LINE, "server", Constants.Global.LINE, instruct.getSchedulerServerId(), Constants.Global.LINE, "ip", LINE, instruct.getIp(), LINE, "clazz", LINE, instruct.getBeanName(), LINE, "method", LINE, instruct.getMethodName(), "/status");
                        //执行命令 0关闭、1启动、2更新
                        Integer status = instruct.getStatus();
                        switch (status) {
                            case 0: // 关闭
                                cronTaskRegistrar.removeCronTask(instruct.getBeanName() + "_" + instruct.getMethodName());
                                // 重新将状态设置回去
                                setData(client, path_root_server_ip_clazz_method_status, "0");
                                logger.info("schedule task stop {} {}", instruct.getBeanName(), instruct.getMethodName());
                                break;
                            case 1: // 启动

                                cronTaskRegistrar.addCronTask(new SchedulingRunnable(scheduleBean, instruct.getBeanName(), instruct.getMethodName()), instruct.getCron());
                                setData(client, path_root_server_ip_clazz_method_status, "1");
                                logger.info("schedule task start {} {}", instruct.getBeanName(), instruct.getMethodName());
                                break;
                            case 2: // 更新
                                cronTaskRegistrar.removeCronTask(instruct.getBeanName() + "_" + instruct.getMethodName());
                                cronTaskRegistrar.addCronTask(new SchedulingRunnable(scheduleBean, instruct.getBeanName(), instruct.getMethodName()), instruct.getCron());
                                setData(client, path_root_server_ip_clazz_method_status, "1");
                                logger.info("schedule task refresh {} {}", instruct.getBeanName(), instruct.getMethodName());
                                break;
                        }
                    }
                    break;
                case NODE_REMOVED:
                    break;
                default:
                    break;
            }
        });
    }

并行任务执行

// 添加任务 & 启动任务
    public void addCronTask(SchedulingRunnable task, String cronExpression) {
        // 首先判断是否已经存在这个任务了,如果存在,那么先移除这个任务
        if (null != Constants.scheduledTasks.get(task.taskId())) {
            removeCronTask(task.taskId());
        }
        // 然后再启动
        CronTask cronTask = new CronTask(task, cronExpression);
        Constants.scheduledTasks.put(task.taskId(), scheduleCronTask(cronTask));
    }

    private ScheduledTask scheduleCronTask(CronTask cronTask) {
        ScheduledTask scheduledTask = new ScheduledTask();
        // 线程池去执行任务,然后使用 scheduledTask.future 同步接收
        scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
        return scheduledTask;
    }

    // 移除任务
    public void removeCronTask(String taskId) {
        ScheduledTask scheduledTask = Constants.scheduledTasks.remove(taskId);
        if (scheduledTask == null) return;
        // 其内部调用 ScheduledFuture 的 cancel 方法
        scheduledTask.cancel();
    }

可扩展自定义AOP

@Aspect
@Component("nhs-schedule")
public class DoJoinPoint {

    private Logger logger = LoggerFactory.getLogger(DoJoinPoint.class);

    @Pointcut("@annotation(cn.nhs.schedule.annotation.DcsScheduled)")
    public void aopPoint() {
    }

    // 定义了一个环绕通知(@Around("aopPoint()")),在目标方法执行前后进行拦截和处理
    // 在doRouter方法中,获取目标方法的执行时间,并在执行结束后记录日志
    @Around("aopPoint()")
    public Object doRouter(ProceedingJoinPoint jp) throws Throwable {
        long begin = System.currentTimeMillis();
        Method method = getMethod(jp);
        try {
            return jp.proceed();
        } finally {
            long end = System.currentTimeMillis();
            logger.info("\nschedule method:{}.{} take time(m):{}", jp.getTarget().getClass().getSimpleName(), method.getName(), (end - begin));
        }
    }

    // getMethod方法用于获取目标方法的Method对象
    private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
        Signature sig = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) sig;
        return getClass(jp).getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
    }

    // getClass方法用于获取目标对象的Class对象
    private Class<? extends Object> getClass(JoinPoint jp) throws NoSuchMethodException {
        return jp.getTarget().getClass();
    }

}

目前这里的功能并没有扩展,基本只是打印执行耗时,如果需要监听任务执行的详细信息,可以在这里控制。

最终效果

测试

@SpringBootApplication
@EnableDcsScheduling
public class ApiTestApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApiTestApplication.class, args);
    }

}
@Component("demoTaskOne")
public class DemoTaskOne {

    @DcsScheduled(cron = "0/3 * * * * *", desc = "01定时任务执行测试:taskMethod01", autoStartup = false)
    public void taskMethod01() {
        System.out.println("测试定时任务1");
    }

    @DcsScheduled(cron = "0/3 * * * * *", desc = "01定时任务执行测试:taskMethod02", autoStartup = false)
    public void taskMethod02() {
        System.out.println("测试定时任务2");
    }

}

管理后台

@SpringBootApplication
public class ImcApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(ImcApplication.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(ImcApplication.class, args);
    }

}

效果

在这里插入图片描述

当我们启动时

在这里插入图片描述

可以看到,我们启动的任务间隔是3s一次

在这里插入图片描述

在测试端日志显示符合我们的规律。

同时也可以做到不修改代码的情况下进行修改计划时间。

在这里插入图片描述

在这里插入图片描述

也可以实现不关闭服务的前提下关闭任务

在这里插入图片描述

在这里插入图片描述

总结

其实核心在于zk的watcher进行监听指定节点的变化,而通过管理后台的每次启动或者暂停,都将要改变节点的信息赋值到正在监听的节点,然后正在监听的节点发现指定节点发生变化,进行回调然后执行后续的全部动作,这也就是简易版本的分布式任务调度框架的时间了。

项目地址

schedule-springboot-starter-main: 实现分布式任务调度中间件,能够动态的开启、关闭任务,并且可以动态的修改参数 (gitee.com)

schedule-springboot-starter-test:实现分布式任务调度中间件的测试 (gitee.com)

schedule-springboot-controller:实现分布式任务调度中间件的后台管理 (gitee.com)

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

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

相关文章

【Math】高斯分布的乘积 Product of Guassian Distribution【附带Python实现】

【Math】高斯分布的乘积 Product of Guassian Distribution【附带Python实现】 文章目录 【Math】高斯分布的乘积 Product of Guassian Distribution【附带Python实现】1.推导2. CodeReference 结果先放在前面 1.推导 在学习PEARL算法的时候&#xff0c;encoder的设计涉及到了…

读书笔记-《数据结构与算法》-摘要3[选择排序]

选择排序 核心&#xff1a;不断地选择剩余元素中的最小者。 找到数组中最小元素并将其和数组第一个元素交换位置。在剩下的元素中找到最小元素并将其与数组第二个元素交换&#xff0c;直至整个数组排序。 性质&#xff1a; 比较次数(N-1)(N-2)(N-3)…21~N^2/2交换次数N运行…

jquery 判断是手机端还是电脑端

判断为手机端&#xff1a; var sUserAgent navigator.userAgent.toLowerCase(); var bIsIpad sUserAgent.match(/ipad/i) "ipad"; var bIsIphoneOs sUserAgent.match(/iphone os/i) "iphone os"; var bIsMidp sUserAgent.match(/midp/i) "mid…

Navicat在分辨率不同的屏幕窗口显示大小不一致问题解决

1.主屏幕为2560*1600分辨率&#xff0c;能够显示较多数据连接 2.在第二屏幕分辨率低&#xff0c;字体变大&#xff0c;显示内容变少 解决办法&#xff1a; 1.右击navicat图标-属性 2.选择【兼容性】-在兼容性页面中选择**“更改高DPI设置”** 3…勾选“高DPI缩放替代”&a…

chown和chmod

chown和chmod都是在Linux和Unix系统中用于设置文件和文件夹权限的命令&#xff0c;但它们的功能和用途有所不同。 功能&#xff1a;chown主要用于修改文件或文件夹的所有者和所属组&#xff0c;而chmod则主要用于修改文件或文件夹的读写执行权限。用途&#xff1a;如果想要授权…

GD32F103*固件库移植FreeRTOS详细教程与解析

GD32F103*固件库移植FreeRTOS详细教程与解析 GD32F103*移植μCOS-Ⅲ详细教程与解析&#xff0c;欢迎指正 文章目录 GD32F103*固件库移植FreeRTOS详细教程与解析前言一、移植前的准备二、移植步骤1.文件结构2.添加代码3.编译与配置 三、注意事项总结 前言 FreeRTOS是一个可以基…

力扣973. 最接近原点的 K 个点(java 排序法,大顶堆法)

Problem: 973. 最接近原点的 K 个点 文章目录 题目描述思路解题方法复杂度Code 题目描述 给定一个数组 points &#xff0c;其中 points[i] [xi, yi] 表示 X-Y 平面上的一个点&#xff0c;并且是一个整数 k &#xff0c;返回离原点 (0,0) 最近的 k 个点。 这里&#xff0c;平面…

.NET微信网页开发相关文章教程

前言 今天我们主要总结一下.NET微信网页开发的相关文章教程。 微信网页开发详细文档可以看微信官方文档&#xff1a;背景 | 微信开放文档 全面的.NET微信网页开发之JS-SDK使用步骤、配置信息和接口请求签名生成详解 微信官方文档对于accessToken和jsapi_ticket生成的示例代码…

智能优化算法应用:基于类电磁机制算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于类电磁机制算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于类电磁机制算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.类电磁机制算法4.实验参数设定5.算法结果…

最全Web前端校招面试真题合集(附答案)

历时半年&#xff0c;我们整理了这份市面上最全面的前端校招面试题解析大全。 包含了腾讯、字节跳动、百度、阿里、滴滴、美团、58、拼多多、360、新浪、搜狐等一线互联网公司面试被问到的题目。希望对大家参加前端校招有所帮助吧&#xff01; HTML 浏览器页面有哪三层构成&…

sql面试题之累计消耗问题

sql中累计求和是我们比较经常遇到的问题&#xff0c;那么与之相反的累计消耗的问题不知你是否挑战过 –问题&#xff1a;在活动大促中&#xff0c;有玩游戏瓜分奖金环节。现有奖金池为3000元&#xff0c;代表奖金池中的初始额度 表中的数据代表每一个用户和其对应的得分&#…

vscode git管理

vscode添加了git管理 1、如下按钮&#xff0c;可以看到本次的修改部分 2、安装git history 就可以查看每次的不同部分了

Javafx实现浏览器

浏览器是一种计算机程序&#xff0c;主要用于显示互联网上的网页。通过浏览器&#xff0c;用户可以访问各种网站、搜索引擎、在线应用程序、社交媒体等。常见的浏览器包括Google Chrome、Mozilla Firefox、Safari、Microsoft Edge、Opera等。浏览器的功能不仅限于浏览网页&…

如何将微服务注册到eureka-server中

将需要注册到eureka-server的服务的maven的pom文件中添加eureka-client依赖 <!--eureka-client依赖--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>&l…

白盒测试法如何进行单元测试

摘要&#xff1a; 单元测试是软件测试的基础&#xff0c;本文详细的论述了单元测试的两个步骤人工静态检查法与动态执行法&#xff0c;所需执行的工作项目及相关的策略和方法。通过对这两个步骤的描述作者将多年的单元测试经验及测试理论注入于全文。 白盒测试法如何进行单元…

在外包待了6年,技术退步太明显......

先说情况&#xff0c;大专毕业&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近6年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试&#xf…

升降压型LED恒流驱动芯片,升降压LED恒流驱动IC

升降压型LED恒流驱动芯片AH1120 是一种高性能的LED驱动解决方案&#xff0c;它能够提供稳定、精确的电流输出&#xff0c;以满足各种LED照明应用的需求。这种芯片具有多种优点&#xff0c;包括高恒流精度、优异的母线和负载调整率、宽输入电压范围、高效率、工作频率可调、智能…

Web 应用程序性能测试核心步骤

通常大家做web 应用程序的时候会有哪些操作呢&#xff1f;今天就来看看常见的web 应用程序的常见操作。 Web 应用程序性能测试核心步骤 1&#xff1a;识别测试环境。确定物理测试环境和生产环境&#xff0c;以及测试团队可用的工具和资源。物理环境包括硬件、软件和网络配置。…

RT-Thread Studio文件消失不见或被排除构建

不得不说RT-Thread Studio里面配置真多&#xff0c;今天我同事的电脑发现根本没有被画斜杠的文件夹&#xff0c;导致我想移植f1的写内部flash这个&#xff08;可以看上一个文章&#xff09;时候不能直接点击属性排除构建&#xff0c;然后在网上查找的时候也没怎么找到说法&…

产品创新受赞誉,怿星荣获2023未来汽车(电子和软件)创新创业大赛一等奖

2023未来汽车&#xff08;电子和软件&#xff09;创新创业大赛 11月29日&#xff0c;上海临港&#xff0c;由中国汽车工程学会和中国&#xff08;上海&#xff09;自由贸易试验区临港新片区管理委员会联合举办的“2023未来汽车&#xff08;电子和软件&#xff09;创新创业大赛…