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

  • 👏作者简介:大家好,我是爱吃芝士的土豆倪,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)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

96基于matlab的GMDH神经网络对YPML120 时间序列进行预测

基于matlab的GMDH神经网络对YPML120 时间序列进行预测&#xff0c;输出训练数据和测试数据的结果&#xff0c;及预测均方根误差结果和正态分布。数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。 96matlabGMDH神经网络 (xiaohongshu.com)

MQTT框架和使用

目录 MQTT框架 1. MQTT概述 1.1 形象地理解三个角色 1.2 消息的传递 2. 在Windows上体验MQTT 2.1 安装APP 2.2 启动服务器 2.3 使用MQTTX 2.3.1 建立连接 2.3.2 订阅主题 2.3.3 发布主题 2.4 使用mosquitto 2.4.1 发布消息 2.4.2 订阅消息 3. kawaii-mqtt源码分析…

音乐律动效果

先上图 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>音乐律动效果</title><style>* {margin: 0;padding: 0;}li {list-style: none;}.container .img {width: 200px;height: 200…

k8s之镜像拉取时使用secret

k8s之secret使用 一、说明二、secret使用2.1 secret类型2.2 创建secret2.3 配置secret 一、说明 从公司搭建的网站镜像仓库&#xff0c;使用k8s部署服务时拉取镜像失败&#xff0c;显示未授权&#xff1a; 需要在拉取镜像时添加认证信息. 关于secret信息,参考: https://www.…

微信小程序之猜数字和猜拳小游戏

目录 效果图 app.json 一、首页&#xff08;index3&#xff09;的代码 wxml代码 wxss代码 二、猜数字页面&#xff08;index&#xff09;代码 wxml代码 wxss代码 js代码 三.游戏规则页面&#xff08;logs&#xff09;代码 wxml代码 wxss代码 四.猜拳页面&#xff…

pthread学习遇到的问题

1.pthread_t 是个类型&#xff0c;指的是线程ID。pthread_create&#xff08;&#xff09;的时候穿地址进去&#xff0c;线程创建好后就会成为线程ID&#xff08;即输出型参数&#xff09; 2.pthread_self() pthread_self()获得是调用这个函数的线程ID &#xff08;我以为是…

随着互联网的快速发展,日常网站监测工具显得越发重要

德迅云安全-领先云安全服务与解决方案提供商 如今&#xff0c;随着互联网的快速发展&#xff0c;网站安全问题日益凸显。恶意攻击、数据泄露、黑客入侵等威胁不断涌现&#xff0c;给企业和个人的信息安全带来了巨大风险。因此&#xff0c;选择一个强大的网站安全监测工具成为了…

Django 开发 web 后端,好用过 SpringBoot ?

基础语法 Django&#xff08;Python&#xff09;&#xff1a;以简洁和直观著称。它允许更快的开发速度&#xff0c;特别适合快速迭代的项目。例如&#xff0c;一个简单的视图函数&#xff1a; from django.http import HttpResponsedef hello_world(request):return HttpRespon…

26、pytest使用allure解读

官方实例 # content of pytest_quick_start_test.py import allurepytestmark [allure.epic("My first epic"), allure.feature("Quick start feature")]allure.id(1) allure.story("Simple story") allure.title("test_allure_simple_te…

Mybatis XML 配置文件

我们刚开始就有说Mybatis 的开发有两种方式: 1.注释 2.XML 注解和 XML 的方式是可以共存的 我们前面说的都是注释的方式,接下来是XML方式 XML的方式分为三步 : 1.配置数据库(配在 application.yml 里面) 这个跟注释的配置是一样的,username应该都是一样的,password记得写…

使用消息队列遇到的问题—kafka

目录 1 分区2 消费者3 Kafka 如何保证消息的消费顺序&#xff1f;3.1 方案一3.2 方案二 在项目中使用kafka作为消息队列&#xff0c;核心工作是创建生产者—包装数据&#xff1b;创建消费者----包装数据。 欠缺一些思考&#xff0c;特此梳理项目中使用kafka遇到的一些问题和解决…

【4】密评-网络和通信安全测评

0x01 依据 GB/T 39786 -2021《 信息安全技术 信息系统密码应用基本要求》针对等保三级系统要求&#xff1a; 网络和通信层面&#xff1a; a&#xff09;应采用密码技术对通信实体进行身份鉴别&#xff0c;保证通信实体身份的真实性&#xff1b; b&#xff09;宜采用密码…

Linux gtest单元测试

1 安装git sudo apt-get install git2 下载googletest git clone https://github.com/google/googletest.git3 安装googletest 注意1: 如果在 make 过程中报错,可在 CMakeLists.txt 中增加如下行,再执行下面的命令: SET(CMAKE_CXX_FLAGS “-std=c++11”) 注意2: CMakeLists…

【GIT】.gitignore 在忽略目录中放开某目录

示例&#xff1a;忽略build下面的所有目录&#xff0c;只放开build/ast2500-default/workspace/recipes-phosphor/ 目录 .gitignore 实现文件代码 # 忽略 build 目录下的所有目录 # 并放开build/ast2500-default/workspace/recipes-phosphor/ build/* !build/ast2500-defaul…

【从零开始学习JVM | 第一篇】快速了解JVM

前言&#xff1a; 在探索现代软件开发的丰富生态系统时&#xff0c;我们不可避免地会遇到一个强大而神秘的存在——Java虚拟机&#xff08;JVM&#xff09;。作为Java语言最核心的组成之一&#xff0c;JVM已经超越了其最初的设计目标&#xff0c;成为一个多语言的运行平台&…

微前端 无界基本用法

目录 无界方案​ 应用加载机制和 js 沙箱机制​ 路由同步机制​ iframe 连接机制和 css 沙箱机制​ 通信机制​ 优势​ 无界入门 无界方案​ 在乾坤的issue中一个议题非常有意思&#xff0c;有个开发者提出能否利用iframe来实现js沙箱能力&#xff0c;这个idea启发了无…

EPWM初学笔记

时钟 PCLKCR0 PCLKCR1 EPWM总体预览 三部分就可以简单的使用EPWM 时基模块&#xff0c;比较模块&#xff0c;动作限定模块 时基模块 TBCTL时基控制寄存器 TBCTR计数寄存器 TBPHS相位寄存器 TBPRD周期寄存器 比较模块 CMPCTL比较控制寄存器 影子模式&#xff0c;加载模式 CMP…

nodejs+vue+elementui校园演出赞助艺术资源管理系统

系统主要分为系统管理员和学生、校外人员三个部分&#xff0c;系统管理员主要功能包括&#xff1a;首页、个人中心、学生管理、校外人员管理、社团信息管理、校内演出管理、校外商演管理、系统管理&#xff1b;基本上实现了整个基于vue的校园艺术资源管理系统的设计与实现信息管…

Linix服务器添加dns解析

Linix开通互联网域名地址出现&#xff0c;如下错误&#xff1a; 需要访问的服务器上添加dns解析 vim /etc/sysconfig/network-scripts/ifcfg-ens192 添加如下配置&#xff1a; DNS1202.96.134.13 重启网卡&#xff1a; systemctl restart network 注意如果是docker服务部署…

STM32(PWM、ADC)

1、PWM 定义 PWM&#xff0c;全称为脉冲宽度调制&#xff08;Pulse Width Modulation&#xff09;&#xff0c;它通过改变信号的高电平和低电平的持续时间比例来控制输出信号的平均功率或电压。 PWM&#xff0c;全称为脉冲宽度调制&#xff08;Pulse Width Modulation&#xff…