Java | 一分钟掌握定时任务 | 7 - ElasticJob分布式定时任务

news2024/11/19 20:32:28

作者:Mars酱

声明:本文章由Mars酱编写,部分内容来源于网络,如有疑问请联系本人。

转载:欢迎转载,转载前先请联系我!

前言

ElasticJob 是面向互联网生态和海量任务的分布式调度解决方案。 它通过弹性调度、资源管控、以及任务治理的功能,打造一个适用于互联网场景的分布式调度解决方案,并通过开放的架构设计,提供多元化的任务生态。 它的各个产品使用统一的任务 API,开发者仅需一次开发,即可随意部署。

架构

elasticjob由两个相互独立的子项目 ElasticJob-Lite 和 ElasticJob-Cloud 组成组成,这是ElasticJob-Lite 的架构图:

从架构图可以看到,左上角App1和App2两个业务模块中的Elastic-Job往zk中注册了信息,右边的Elastic-Job-Lite是监听了zk的,因此,整个任务的调度是由zk来完成的。下面的console通过Rest API去获取zk中的信息,得到调度数据和日志,并存盘。

这是ElasticJob-Cloud的架构图:

ElasticJob-Cloud的调度是依赖Mesos的,从架构图的理解,Mesos和zk结合做好任务调度,再分发给Mesos的代理并执行。

功能和特性

以下是ElasticJob的特性优点

  • 支持任务在分布式场景下的分片和高可用
  • 能够水平扩展任务的吞吐量和执行效率
  • 任务处理能力随资源配备弹性伸缩
  • 优化任务和资源调度
  • 相同任务聚合至相同的执行器统一处理
  • 动态调配追加资源至新分配的任务
  • 失效转移
  • 错过任务重新执行
  • 分布式环境下任务自动诊断和修复
  • 基于有向无环图 (DAG) 的任务依赖
  • 基于有向无环图 (DAG) 的任务项目依赖
  • 可扩展的任务类型统一接口
  • 支持丰富的任务类型库–包括数据流、脚本、HTTP、文件、大数据
  • 易于对接业务任务–兼容 Spring IOC
  • 任务管控端
  • 任务事件追踪
  • 注册中心管理

入门角色

既然这么多优点,我们就入门试试吧。入门elasticjob-lite也继承了Quartz框架,同样的很简单,只要三个角色:

SimpleJob:任务主体。如果用过Quartz,那么应该能够理解这个,基本上和Quartz的Job接口类似,只要实现一个execute方法就行了,入门用这个就行;

JobConfiguration:任务配置。同样的可以理解为类似Quartz框架中的Trigger,最重要的就是配置任务的执行频率;

ScheduleJobBootstrap:调度主体。这个一样,参考Quartz框架中的Scheduler对象,它把任务和配置结合起来,任务按照配置中的频率执行。

写个例子

我们创建这三种角色,首先创建任务主体:

import org.apache.shardingsphere.elasticjob.api.ShardingContext;
import org.apache.shardingsphere.elasticjob.simple.job.SimpleJob;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

/**
 * (这个类的说明)
 *
 * @author mars酱
 */

public class MarsSimpleJob implements SimpleJob {
    
    @Override
    public void execute(final ShardingContext shardingContext) {
        System.out.printf("Item: %s | Time: %s | Thread: %s | %s%n",
               			  shardingContext.getShardingItem(), 
                          new SimpleDateFormat("HH:mm:ss").format(new Date()), 
                          Thread.currentThread().getId(), 
                          "就是这么简单~");
    }
}

再创建任务配置:

import org.apache.shardingsphere.elasticjob.api.JobConfiguration;
import org.apache.shardingsphere.elasticjob.tracing.api.TracingConfiguration;

import javax.sql.DataSource;
import java.util.Objects;

/**
 * (这个类的说明)
 *
 * @author mars酱
 */

public class JobConfigurationBuilder {
    public static JobConfiguration buildJobConfiguration(String jobName, String cronExpression, TracingConfiguration<DataSource> tracingConfig) {

        JobConfiguration.Builder builder = JobConfiguration.newBuilder(jobName, 3)
                .cron(cronExpression)
                .shardingItemParameters("0=a,1=b,2=c");
        if (Objects.nonNull(tracingConfig)) {
            builder.addExtraConfigurations(tracingConfig);
        }
        return builder.build();
    }
}

最后创建调度器,并执行:

import org.apache.shardingsphere.elasticjob.lite.api.bootstrap.impl.ScheduleJobBootstrap;
import org.apache.shardingsphere.elasticjob.lite.example.job.simple.JavaSimpleJob;
import org.apache.shardingsphere.elasticjob.reg.base.CoordinatorRegistryCenter;
import org.apache.shardingsphere.elasticjob.reg.zookeeper.ZookeeperConfiguration;
import org.apache.shardingsphere.elasticjob.reg.zookeeper.ZookeeperRegistryCenter;
import org.apache.shardingsphere.elasticjob.tracing.api.TracingConfiguration;

import javax.sql.DataSource;


/**
 * (这个类的说明)
 *
 * @author mars酱
 */

public final class SchedulerMain {

    private static final int EMBED_ZOOKEEPER_PORT = 4181;

    private static final String ZOOKEEPER_CONNECTION_STRING = "localhost:" + EMBED_ZOOKEEPER_PORT;

    private static final String JOB_NAMESPACE = "elasticjob-marsz-lite-java";

    // CHECKSTYLE:OFF
    public static void main(final String[] args) {
        // 内嵌zk服务
        EmbedZookeeperServer.start(EMBED_ZOOKEEPER_PORT);
        CoordinatorRegistryCenter regCenter = setUpRegistryCenter();
        // 简单作业
        setUpSimpleJob(regCenter, null);
    }

    private static CoordinatorRegistryCenter setUpRegistryCenter() {
        ZookeeperConfiguration zkConfig = new ZookeeperConfiguration(ZOOKEEPER_CONNECTION_STRING, JOB_NAMESPACE);
        CoordinatorRegistryCenter result = new ZookeeperRegistryCenter(zkConfig);
        result.init();
        return result;
    }

    private static void setUpSimpleJob(final CoordinatorRegistryCenter regCenter, final TracingConfiguration<DataSource> tracingConfig) {
        new ScheduleJobBootstrap(regCenter,
                new MarsSimpleJob(),
                JobConfigurationBuilder.buildJobConfiguration("marsSimpleJob", "0/5 * * * * ?", tracingConfig)).schedule();

    }

}

运行的效果:

截图中Item是处理的分片项,Thread是当前线程的id,看到了Quartz框架的影子…。

任务执行流程

既然能成功运行,我们看看内部的处理逻辑吧。Mars酱本机并没有安装zk,所以copy了官方的例子,在程序运行前先启用了一个内嵌的zk服务:

EmbedZookeeperServer.start(EMBED_ZOOKEEPER_PORT);

这个只能在模拟的时候使用,千万不能拿去放生产环境。接下来就是注册中心的配置了,我们需要的是CoordinatorRegistryCenter对象:

private static CoordinatorRegistryCenter setUpRegistryCenter() {
	ZookeeperConfiguration zkConfig = new ZookeeperConfiguration(ZOOKEEPER_CONNECTION_STRING, JOB_NAMESPACE);
    CoordinatorRegistryCenter result = new ZookeeperRegistryCenter(zkConfig);
    result.init();
    return result;
}

好了,zk的部分处理完成,下面就是直接SchedulerJobBootstrap的部分了。

ScheduleJobBootstrap初始化

ScheduleJobBootstrap的初始化在例子中需要三个参数:

CoordinatorRegistryCenter:这个是协调用的注册中心。是一个接口类,它的实现在ElasticJob里面只有一个ZookeeperRegisterCenter对象,未来是不是会支持其他的注册中心呢?

ElasticJob: Mars酱理解为任务对象。但是ElasticJob这个对象本身是个空接口,有两个子接口SimpleJobDataflowJob,前者Mars酱的理解是和Quartz中的Job对象类似,只要实现execute函数就行,后者有需要实现两个接口,一个fetchData获取数据,一个processData处理数据。所以,ElasticJob这个接口留空,是为了还有其他扩展吧?

JobConfiguration:弹性任务配置项。构建这个对象不能直接设置,只能用buider的方式构建。需要配置的属性很多,但是核心属性大致就是几个:任务名称、分片数、执行频率、分片参数。JobConfiguration的所有属性如下:

属性名说明
String jobName任务名称
String croncron表达式
String timeZone任务运行的时区
int shardingTotalCount任务分片总数
String shardingItemParameters分片序号和参数,多个键值对之间用逗号分隔,从0开始,但是不能大于或等于任务分片的总数
String jobParameter任务自定义任务参数
boolean monitorExecution是否监听执行
boolean failover是否启用故障转移。开启表示如果任务在一次任务执行中途宕机,允许将该次未完成的任务在另一任务节点上补偿执行
boolean misfire不发火。哈哈,其实是是否开启错过任务重新执行
int maxTimeDiffSeconds最大时差
int reconcileIntervalMinutes间隔时长
String jobShardingStrategyType任务分片策略类型,总共三种
String jobExecutorServiceHandlerType任务执行程序服务处理程序类型
String jobErrorHandlerType任务错误处理类型
Collection jobListenerTypes任务监听类型
Collection extraConfigurations附加配置信息
String description任务描述
Properties props扩展用属性值
boolean disabled是否禁用
boolean overwrite是否覆盖
String label标签
boolean staticSharding是否支持静态分片

ScheduleJobBootstrap执行

同样的,例子中的MarsSimpleJob的execute函数,最终会被ElasticJob框架调用,我们按照被执行的反向顺序往上找。MarsSimpleJob是继承SimpleJob的, 而SimpleJob的execute函数是被SimpleJobExecutor所调用:

/**
 * Simple job executor.
 */
public final class SimpleJobExecutor implements ClassedJobItemExecutor<SimpleJob> {
    
    @Override
    public void process(final SimpleJob elasticJob, final JobConfiguration jobConfig, final JobFacade jobFacade, final ShardingContext shardingContext) {
        // 这里调用execute函数
        elasticJob.execute(shardingContext);
    }
    
    @Override
    public Class<SimpleJob> getElasticJobClass() {
        return SimpleJob.class;
    }
}

再继续往上找,process的核心流程就是在ElasticJobExecutor里面了,调用process的部分在ElasticJobExcutor中几个重载的process方法调用的,两个process函数完成不同的功能,调用SimpleExecutor的process部分是这样:

@SuppressWarnings("unchecked")
private void process(final JobConfiguration jobConfig, final ShardingContexts shardingContexts, final int item, final JobExecutionEvent startEvent) {
        jobFacade.postJobExecutionEvent(startEvent);
        log.trace("Job '{}' executing, item is: '{}'.", jobConfig.getJobName(), item);
        JobExecutionEvent completeEvent;
        try {
            // 这里调用SimpleJobExecutor的process
            jobItemExecutor.process(elasticJob, jobConfig, jobFacade, shardingContexts.createShardingContext(item));
            completeEvent = startEvent.executionSuccess();
            log.trace("Job '{}' executed, item is: '{}'.", jobConfig.getJobName(), item);
            jobFacade.postJobExecutionEvent(completeEvent);
            // CHECKSTYLE:OFF
        } catch (final Throwable cause) {
            // CHECKSTYLE:ON
            completeEvent = startEvent.executionFailure(ExceptionUtils.transform(cause));
            jobFacade.postJobExecutionEvent(completeEvent);
            itemErrorMessages.put(item, ExceptionUtils.transform(cause));
            JobErrorHandler jobErrorHandler = executorContext.get(JobErrorHandler.class);
            jobErrorHandler.handleException(jobConfig.getJobName(), cause);
        }
}

上面这个process负责最终任务的执行部分,由JobItemExecutor对象调用,SimpleJobExecutor被JobItemExecutor接口定义。整个这个proces由guava包的EventBus处理消息事件,执行之前有startEvent,执行完成有completeEvent,异常也有对应的失败event,方面架构图中存盘事件日志、ELK日志收集动作。

调用这个process的部分,由另一个process完成,长这样的:

private void process(final JobConfiguration jobConfig, final ShardingContexts shardingContexts, final ExecutionSource executionSource) {
        Collection<Integer> items = shardingContexts.getShardingItemParameters().keySet();
        if (1 == items.size()) {
            int item = shardingContexts.getShardingItemParameters().keySet().iterator().next();
            JobExecutionEvent jobExecutionEvent = new JobExecutionEvent(IpUtils.getHostName(), IpUtils.getIp(), shardingContexts.getTaskId(), jobConfig.getJobName(), executionSource, item);
            process(jobConfig, shardingContexts, item, jobExecutionEvent);
            return;
        }
        CountDownLatch latch = new CountDownLatch(items.size());
        for (int each : items) {
            JobExecutionEvent jobExecutionEvent = new JobExecutionEvent(IpUtils.getHostName(), IpUtils.getIp(), shardingContexts.getTaskId(), jobConfig.getJobName(), executionSource, each);
            ExecutorService executorService = executorContext.get(ExecutorService.class);
            if (executorService.isShutdown()) {
                return;
            }
            // 提交给线程池执行
            executorService.submit(() -> {
                try {
                    process(jobConfig, shardingContexts, each, jobExecutionEvent);
                } finally {
                    latch.countDown();
                }
            });
        }
        try {
            latch.await();
        } catch (final InterruptedException ex) {
            Thread.currentThread().interrupt();
        }
}

上面这个process负责把分片参数依次组装好,设置好JobExecutionEvent中的ip、主机名等参数,然后放入线程池中去执行。再往上,看现在这个process被调用的部分:

private void execute(final JobConfiguration jobConfig, final ShardingContexts shardingContexts, final ExecutionSource executionSource) {
	if (shardingContexts.getShardingItemParameters().isEmpty()) {
		jobFacade.postJobStatusTraceEvent(shardingContexts.getTaskId(), State.TASK_FINISHED, String.format("Sharding item for job '%s' is empty.", jobConfig.getJobName()));
        return;
    }
    // 往注册中心注册ShardingContexts信息
    jobFacade.registerJobBegin(shardingContexts);
    String taskId = shardingContexts.getTaskId();
    // 发送跟踪日志,标记任务正在运行
    jobFacade.postJobStatusTraceEvent(taskId, State.TASK_RUNNING, "");
    try {
        // 调用process
        process(jobConfig, shardingContexts, executionSource);
    } finally {
        // TODO Consider increasing the status of job failure, and how to handle the overall loop of job failure
        // 告知注册中心任务完成
        jobFacade.registerJobCompleted(shardingContexts);
        if (itemErrorMessages.isEmpty()) {
            // 没有失败信息,通知任务完成
            jobFacade.postJobStatusTraceEvent(taskId, State.TASK_FINISHED, "");
        } else {
            // 否则通知失败
            jobFacade.postJobStatusTraceEvent(taskId, State.TASK_ERROR, itemErrorMessages.toString());
            itemErrorMessages.clear();
        }
    }
}

方法execute从注册中心注册ShardingContext信息,并发送跟踪日志事件,然后调用process,最后发送跟踪消息标记任务完成。再有一个重载的execute方法调用上面这个execute方法,如下:

public void execute() {
    // job的配置信息
    JobConfiguration jobConfig = jobFacade.loadJobConfiguration(true);
    executorContext.reloadIfNecessary(jobConfig);
    JobErrorHandler jobErrorHandler = executorContext.get(JobErrorHandler.class);
    try {
        jobFacade.checkJobExecutionEnvironment();
    } catch (final JobExecutionEnvironmentException cause) {
        jobErrorHandler.handleException(jobConfig.getJobName(), cause);
    }
    // 这里有玄机
    ShardingContexts shardingContexts = jobFacade.getShardingContexts();
    // 发送时间消息总线
    jobFacade.postJobStatusTraceEvent(shardingContexts.getTaskId(), State.TASK_STAGING, String.format("Job '%s' execute begin.", jobConfig.getJobName()));
    if (jobFacade.misfireIfRunning(shardingContexts.getShardingItemParameters().keySet())) {
        jobFacade.postJobStatusTraceEvent(shardingContexts.getTaskId(), 
                                          State.TASK_FINISHED, 
                                          String.format(
                "Previous job '%s' - shardingItems '%s' is still running, misfired job will start after previous job completed.", 
                                              jobConfig.getJobName(),
                                              shardingContexts.getShardingItemParameters().keySet()));
            return;
    }
    try {
        // 任务执行的前置流程
        jobFacade.beforeJobExecuted(shardingContexts);
        //CHECKSTYLE:OFF
    } catch (final Throwable cause) {
        //CHECKSTYLE:ON
        jobErrorHandler.handleException(jobConfig.getJobName(), cause);
    }
    // 调用上面的execute方法
    execute(jobConfig, shardingContexts, ExecutionSource.NORMAL_TRIGGER);
    while (jobFacade.isExecuteMisfired(shardingContexts.getShardingItemParameters().keySet())) {
        jobFacade.clearMisfire(shardingContexts.getShardingItemParameters().keySet());
        execute(jobConfig, shardingContexts, ExecutionSource.MISFIRE);
    }
    // 故障转移
    jobFacade.failoverIfNecessary();
    try {
        // 任务执行的后置流程
        jobFacade.afterJobExecuted(shardingContexts);
        //CHECKSTYLE:OFF
    } catch (final Throwable cause) {
        //CHECKSTYLE:ON
        jobErrorHandler.handleException(jobConfig.getJobName(), cause);
    }
}

这个execute就由Quartz的JobRunShell调用了,Quartz的调用的过程在 Java | 一分钟掌握定时任务 | 6 - Quartz定时任务 - 掘金 (juejin.cn) 里面还好Mars酱分析过了。

执行流程总结

那么,追踪完源代码,大致的流程就应该是如下:

1.组装基本参数(任务、频率等) -> 2. ScheduleJobBootstrap初始化 -> 3.配置任务属性 -> 4.设置各种facade -> 5.初始化ElasticJobExecutor -> 6.调用scheduler执行任务 -> 7.获取任务执行器(SimpleJobExecutor) -> 8.各种校验逻辑 -> 9. 处理分片参数 -> 10. 设置任务为运行状态 -> 11. 提交任务到线程池 -> 12.执行任务 -> 13.处理任务后续逻辑

任务的调度过程由zk完成,取决于zk的任务调度策略吧?如果一台机器的定时运行时挂了,zk会转移到另一台运行中的机器中去。-- Mars酱

分片的策略

任务的分片策略,用于将任务在分布式环境下分解成为任务使用。

SPI 名称详细说明
JobShardingStrategy作业分片策略接口
已知实现类详细说明
AverageAllocationJobShardingStrategy根据分片项平均分片
OdevitySortByNameJobShardingStrategy根据任务名称哈希值的奇偶数决定按照任务服务器 IP 升序或是降序的方式分片
RoundRobinByNameJobShardingStrategy根据任务名称轮询分片

那么任务的分片策略在哪里使用的呢?就在代码中注释的“这里有玄机”那行。在getShardingContexts的方法中会调用ShardingService,它会去获取JobConfiguration中配置的分片策略方式:

public void shardingIfNecessary() {
    List<JobInstance> availableJobInstances = instanceService.getAvailableJobInstances();
    if (!isNeedSharding() || availableJobInstances.isEmpty()) {
        return;
    }
    if (!leaderService.isLeaderUntilBlock()) {
        blockUntilShardingCompleted();
        return;
    }
    waitingOtherShardingItemCompleted();
    JobConfiguration jobConfig = configService.load(false);
    int shardingTotalCount = jobConfig.getShardingTotalCount();
    log.debug("Job '{}' sharding begin.", jobName);
    jobNodeStorage.fillEphemeralJobNode(ShardingNode.PROCESSING, "");
    resetShardingInfo(shardingTotalCount);
    // 获取任务分片策略
    JobShardingStrategy jobShardingStrategy = JobShardingStrategyFactory.getStrategy(jobConfig.getJobShardingStrategyType());
    jobNodeStorage.executeInTransaction(getShardingResultTransactionOperations(jobShardingStrategy.sharding(availableJobInstances, jobName, shardingTotalCount)));
    log.debug("Job '{}' sharding complete.", jobName);
}

如果不设置,默认使用的是平均分片策略。

总结

这,大抵就是ElasticJob的工作原理了吧。

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

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

相关文章

pointNet、pointNet++算法学习笔记

算法结构 原理解析 已知&#xff1a; N个点&#xff0c;每个点的信息x,y,z。 MLP:全连接网络&#xff0c;输入层&#xff0c;隐藏层&#xff0c;输出层。 原理&#xff1a; &#xff08;1&#xff09;第一步 MLP 对每个点进行MLP操作&#xff0c;即&#xff1a;3–>MLP(3,C…

Spring MVC文件上传处理详解

Spring MVC文件上传处理详解 Spring MVC是Java Web开发中非常常用的框架之一&#xff0c;它提供了许多方便的功能。其中&#xff0c;文件上传是Web开发中常用的功能之一&#xff0c;本文将介绍如何使用Spring MVC处理文件上传以及相关代码实现。 文件上传的基本原理 在Web开发…

HTML-iconfont动态图标SVG效果--阿里巴巴图标矢量库

给北大打工&#xff0c;实现官网首页动态图标效果_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Ys4y1c7oh/?spm_id_from333.1007.top_right_bar_window_default_collection.content.click&vd_source924f5dad6f2dcb0a3e5dca4604287ecd&#xff08;本篇笔记操作方法…

Java 基础语法学习笔记

目录 一、Java语言概述 1.1 Java 的出现 1.2 Java的主要特性 1.3 Java语言的特点 1.4 Java语言的核心机制 1.5 Java语言的环境搭建 二、第一个Java程序 2.1 需要注意的问题 2.2 注释&#xff08;comment) 2.3 注意点&#xff1a; 2.4 Java API 的文档 2.5 第一个 Jav…

python+django智慧办公hr招聘辅助管理系统vue

随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;智慧办公hr招聘辅助管理系统当然也不能排除在外。智慧办公hr招聘辅助管理系统是以实际运用为开发背景&#xff0c;运用软件工程…

深度学习框架-Keras:特点、架构、应用和未来发展趋势

引言 深度学习是一种新兴的技术&#xff0c;已经在许多领域中得到广泛的应用&#xff0c;如计算机视觉、自然语言处理、语音识别等。在深度学习中&#xff0c;深度学习框架扮演着重要的角色。Keras是一种广泛使用的深度学习框架&#xff0c;它在许多方面都有所改进&#xff0c…

云计算基础——云计算与移动互联网、物联网

8.1 云计算与移动互联网 8.1.1 移动互联网的发展概况 移动互联网的发展概况 移动互联网是指以宽带IP为技术核心&#xff0c;可同时提供语音、数据、多媒体等业务服务的开什么是移动互联网?放式基础电信网络&#xff0c;从用户行为角度来看&#xff0c;移动互联网广义上是指用…

openldap介绍以及使用

参考文献&#xff1a;openldap介绍和使用 基本概念 官网&#xff1a;https://www.openldap.org 官方文档&#xff1a;https://www.openldap.org/doc LDAP是一个开放的&#xff0c;中立的&#xff0c;工业标准的应用协议&#xff0c;通过IP协议提供访问控制和维护分布式信息的…

目标检测 YOLOv5 开源代码项目调试与讲解实战土堆 课程笔记

P4 P5 主要讲解了 detect.py 中的参数的使用 如何利用 YOLOv5 进行预测&#xff08;一&#xff09;_哔哩哔哩_bilibili 如何利用YOLOv5进行预测&#xff08;二&#xff09;_哔哩哔哩_bilibili &#xff08;一&#xff09;weight&#xff1a;代码如下 parser.add_argument(--w…

VTKmimics Calculate Parts

前言&#xff1a;本博文主要研究mimics中Calculate Parts所采用的方法以及VTK中三维重建的方法&#xff0c;希望对各位小伙伴有所帮助&#xff0c;谢谢&#xff01; mimics-Calculate parts - Interpolation Gray Interpolation 灰度值插值是一种真正的3D插值&#xff0c;它考…

【C++学习第十一讲】C++数据类型

文章目录 一、编程语言中的数据类型1.1 整型&#xff08;Integer&#xff09;1.2 浮点型&#xff08;Floating-Point&#xff09;1.3 字符型&#xff08;Character&#xff09;1.4 布尔型&#xff08;Boolean&#xff09;1.5 数组&#xff08;Array&#xff09;1.6 字符串&…

TitanIDE:环境安装部署教程

随着市场需求的迅速增长和技术的不断发展&#xff0c;云原生不仅仅是一种技术&#xff0c;更是一种思想。它通过容器化、微服务化、自动化等技术手段&#xff0c;推动了应用程序设计和交付的转变&#xff0c;使应用程序的开发、测试、部署和管理变得更加高效和灵活。 随着市场需…

【多线程】| 线程冲突解决方案

目录 &#x1f981; 线程同步1.什么是线程冲突&#xff1f;2.什么是线程同步&#xff1f;3.解决线程同步的方案3.1语法结构3.2synchronized使用 &#x1f981; synchronized详细用法1. 使用this作为线程锁对象1.1 语法结构&#xff1a;1.2 使用说明 2. 使用字符串作为线程对象锁…

lwIP更新记04:TCP 初始序列号

从 lwIP-2.0.0 开始&#xff0c;可以自定义 TCP 报文段的初始序列号。 TCP 报文段首部有一个序列号字段&#xff0c;它是一个32位的计数器&#xff0c;从 0 到 4294967295&#xff0c;它的值为当前报文段中第一个数据的字节序号。TCP 在建立连接的时候需要初始序列号&#xff…

JVM系列-第11章-垃圾回收相关概念

垃圾回收相关概念 System.gc() 的理解 在默认情况下&#xff0c;通过System.gc()者Runtime.getRuntime().gc() 的调用&#xff0c;会显式触发Full GC&#xff0c;同时对老年代和新生代进行回收&#xff0c;尝试释放被丢弃对象占用的内存。 然而System.gc()调用附带一个免责声…

小航助学2022年NOC初赛图形化(小高组)(含题库答题软件账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSDN博客 单选题3.0分 删除编辑 答案:C 第1题如果要控制所有角色一起朝舞台区右侧移动&#xff0c;下面哪个积木块是不需要的&#xff1f; A…

微签助力中融基金电子文件安全高效签章

中融基金重安全&#xff0c;炼丹炉里炼微签 这次讲一个微签在炼丹炉里炼出了火眼金睛的故事。 先看一个数字。 金融隐私泄露事件大约以每年35&#xff05;的数据在增长。 数字来自《中国银行保险报》与亚信网络安全产业技术研究院发布的《金融行业网络安全白皮书》。 大数据时…

「车型分析」控制系统典型应用车型 —— 辊筒AGV

辊筒AGV (Roller conveyor ) 是一种常见的AGV机器人类型&#xff0c;它利用辊筒和轮子在巷道中实现货物的搬运和运输&#xff0c;可实现托盘物品的卸载和运输等功能, 具有更高的灵活性、适应性和效率。本文将基于这款市场上常见的AGV进行一次简单的介绍。 1 车型介绍: 辊筒AGV…

深度学习中必备的算法:神经网络、卷积神经网络、循环神经网络

深度学习是一种新兴的技术&#xff0c;已经在许多领域中得到广泛的应用&#xff0c;如计算机视觉、自然语言处理、语音识别等。在深度学习中&#xff0c;算法是实现任务的核心&#xff0c;因此深度学习必备算法的学习和理解是非常重要的。 本文将详细介绍深度学习中必备的算法…

2023彩虹易支付最新原版安装教程(内附源码)

此源码已通过检查&#xff0c;确认无后门&#xff0c;且所有代码开源&#xff0c;无加密文件。 测试日期 2023年5月21日 源码已扫描无后门&#xff0c;不放心的也可以自己再去扫描一遍 2023年5月22日 各个功能接口测试完毕&#xff0c;均可用 选中下方可查看下载链接 http…