分布式之任务调度Elastic-Job学习二

4 Spring 集成与分片详解

ejob-springboot 工程

4.1 pom 依赖

<properties><elastic-job.version>2.1.5</elastic-job.version>
</properties>
<dependency><groupId>com.dangdang</groupId><artifactId>elastic-job-lite-core</artifactId><version>${elastic-job.version}</version>
</dependency>
<!-- elastic-job-lite-spring -->
<dependency><groupId>com.dangdang</groupId><artifactId>elastic-job-lite-spring</artifactId><version>${elastic-job.version}</version>
</dependency>

4.2 application.properties

定义配置类和任务类中要用到的参数
server.port=${random.int[10000,19999]}
regCenter.serverList = localhost:2181
regCenter.namespace = gupao-ejob-springboot
gupaoJob.cron = 0/3 * * * * ?
gupaoJob.shardingTotalCount = 2
gupaoJob.shardingItemParameters = 0=0,1=

4.3 创建任务

创建任务类,加上@Component 注解

@Component
public class SimpleJobDemo implements SimpleJob {public void execute(ShardingContext shardingContext) {System.out.println(String.format("------Thread ID: %s, %s,任务总片数: %s, " +"当前分片项: %s.当前参数: %s," +"当前任务名称: %s.当前任务参数 %s", Thread.currentThread().getId()new SimpleDateFormat("HH:mm:ss").format(new Date()), shardingContext.getShardingTotalCount(), shardingContext.getShardingItem(), shardingContext.getShardingParameter(), shardingContext.getJobName(), shardingContext.getJobParameter()));}
}

4.4 注册中心配置

Bean 的 initMethod 属性用来指定 Bean 初始化完成之后要执行的方法,用来替代继承 InitializingBean 接口,以便在容器启动的时候创建注册中心。
@Configuration
public class ElasticRegCenterConfig {@Bean(initMethod = "init")public ZookeeperRegistryCenter regCenter(@Value("${regCenter.serverList}") final String serverList, @Value("${regCenter.namespace}") final String namespace) {return new ZookeeperRegistryCenter(new ZookeeperConfiguration(serverList, namespace));}
}

4.5 作业三级配置

Core——Type——Lite
return LiteJobConfiguration.newBuilder(new SimpleJobConfiguration(JobCoreConfiguration.newBuilder()
@Configuration
public class ElasticJobConfig {@Autowiredprivate ZookeeperRegistryCenter regCenter;@Bean(initMethod = "init")public JobScheduler simpleJobScheduler(final SimpleJobDemo simpleJob, @Value("${gupaoJob.cron}") final String cron, @Value("${gupaoJob.shardingTotalCount}") final int shardingTotalCount,@Value("${gupaoJob.shardingItemParameters}") final StringshardingItemParameters) {return new SpringJobScheduler(simpleJob, regCenter, getLiteJobConfiguration(simpleJob.getClass(), cron, shardingTotalCount, shardingItemParameters));}private LiteJobConfiguration getLiteJobConfiguration(final Class<? extends SimpleJob> jobClass, final String cron, final int shardingTotalCount, final String shardingItemParameters) {return LiteJobConfiguration.newBuilder(new SimpleJobConfiguration(JobCoreConfiguration.newBuilder(jobClass.getName(), cron, shardingTotalCount).shardingItemParameters(shardingItemParameters).build(), jobClass.getCanonicalName())).overwrite(true).build();}
}

4.6 作业运行

先把 application.properties 中的分片数全部改成 1
启动 com.gupaoedu.EjobApp 的 main 方法

4.7 分片策略

4.7.1 分片项与分片参数
任务分片,是为了实现把一个任务拆分成多个子任务,在不同的 ejob 示例上执行。
例如 100W 条数据,在配置文件中指定分成 10 个子任务(分片项),这 10 个子任务再按照一定的规则分配到 5 个实际运行的服务器上执行。除了直接用分片项 ShardingItem获取分片任务之外,还可以用 item 对应的 parameter 获取任务。
standalone 工程:simple.SimpleJobTest
JobCoreConfiguration coreConfig = JobCoreConfiguration.newBuilder("MySimpleJob", "0/2 * * * * ?", 4).shardingItemParameters("0=RDP, 1=CORE, 2=SIMS, 3=ECIF").build();
springboot 工程,在 application.properties 中定义。
定义几个分片项,一个任务就会有几个线程去运行它。
注意:分片个数和分片参数要一一对应。通常把分片项设置得比 E-Job 服务器个数大一些,比如 3 台服务器,分成 9 片,这样如果有服务器宕机,分片还可以相对均匀。
4.7.2 分片验证
为避免运行的任务太多看不清楚运行结果,可以注释在 ElasticJobConfig 中注释DataFlowJob 和 ScriptJob。SimpleJob 的分片项改成 2。
直接运行 com.gupaoedu.EjobApp。
或者打成 jar 包:mvn package -Dmaven.test.skip=true
Jar 包路径:ejob-springboot\target\ejob-springboot-0.0.1-SNAPSHOT.jar
修改名称为 ejob.jar 放到 D 盘下
多实例运行(单机):
java –jar ejob.jar
1、 多运行一个点,任务不会重跑(两个节点各获得一个分片项)
2、 关闭一个节点,任务不会漏

在这里插入图片描述

4.7.3 分片策略
http://elasticjob.io/docs/elastic-job-lite/02-guide/job-sharding-strategy/  分片项如何分配到服务器?这个跟分片策略有关。
策略类描述具体规则
AverageAllocationJobShardingStrategy基于平均分配算法的分片策略,也是默认的分片策略。如果分片不能整除,则不能整除的多余分片将依次追加到序号小的服务器。如: 如果有 3 台服务器,分成 9 片,则每台服务器 分 到 的 分 片 是 : 1=[0,1,2], 2=[3,4,5], 3=[6,7,8] 如果有 3 台服务器,分成 8 片,则每台服务器分到的分片是:1=[0,1,6], 2=[2,3,7], 3=[4,5] 如果有 3 台服务器,分成 10 片,则每台服务器 分 到 的 分 片 是 : 1=[0,1,2,9], 2=[3,4,5], 3=[6,7,8]
OdevitySortByNameJobShardingStrategy根据作业名的哈希值奇偶数决定 IP 升降序算法的分片策略。根据作业名的哈希值奇偶数决定 IP 升降序算法的分片策略。 作业名的哈希值为奇数则 IP 升序。 作业名的哈希值为偶数则 IP 降序。用于不同的作业平均分配负载至不同的服务器。
RotateServerByNameJobShardingStrategy根据作业名的哈希值对服务器列表进行轮转的分片策略。
自定义分片策略实现 JobShardingStrategy 接口并实现 sharding 方法,接口方法参数为作业服务器 IP 列表和分片策略选项,分片策略选项包括作业名称,分片总数以及分片序列号和个性化参数对照表,可以根据需求定制化自己的分片策略。
AverageAllocationJobShardingStrategy 的缺点是,一旦分片数小于作业服务器数,作业将永远分配至 IP 地址靠前的服务

器,导致 IP 地址靠后的服务器空闲。而 OdevitySortByNameJobShardingStrategy 则可以根据作业名称重新分配服务器负
载。如:
如果有 3 台服务器,分成 2 片,作业名称的哈希值为奇数,则每台服务器分到的分片是:1=[0], 2=[1], 3=[]
如果有 3 台服务器,分成 2 片,作业名称的哈希值为偶数,则每台服务器分到的分片是:3=[0],
在 Lite 配置中指定分片策略:

String jobShardingStrategyClass = AverageAllocationJobShardingStrategy.class.getCanonicalName();
LiteJobConfiguration simpleJobRootConfig = LiteJobConfiguration.newBuilder(simpleJobConfig).jobShardingStrategyClass(jobShardingStrategyClass).build();
4.7.4 分片方案
获取到分片项 shardingItem 之后,怎么对数据进行分片嗯?
1、对业务主键进行取模,获取余数等于分片项的数据
举例:获取到的 sharding item 是 0,1
在 SQL 中加入过滤条件:where mod(id, 4) in (1, 2)。
这种方式的缺点:会导致索引失效,查询数据时会全表扫描。
解决方案:在查询条件中在增加一个索引条件进行过滤。
2、在表中增加一个字段,根据分片数生成一个 mod 值。取模的基数要大于机器数。否则在增加机器后,会导致机器空闲。例如取模基数是 2,而服务器有 5 台,那么有三台服务器永远空闲。而取模基数是 10,生成 10 个 shardingItem,可以分配到 5 台服务器。当然,取模基数也可以调整。
3、如果从业务层面,可以用 ShardingParamter 进行分片。例如 0=RDP, 1=CORE, 2=SIMS, 3=ECIF List<users> = SELECT * FROM user WHERE status = 0 AND SYSTEM_ID = 'RDP' limit 0, 10
在 Spring Boot 中要 Elastic-Job 要配置的内容太多了,有没有更简单的添加任务的方法呢?比如在类上添加一个注解?这个时候我们就要用到 starter 了。

4.8 e-job starter

Git 上有一个现成的实现
https://github.com/TFdream/elasticjob-spring-boot-starter
工程:elasticjob-spring-boot-starter
需求(一个 starter 应该有什么样子):
需求实现作用
可以在启动类上使用@Enable 功能开启 E-Job 任务调度注解@EnableElasticJob在自动配置类上用@ConditionalOnBean决定是否自动配置
可以在 properties 或 yml 中识别配置内容配置类 RegCenterProperties.java支 持 在 properties 文 件 中 使 用elasticjob.regCenter 前缀,配置注册中心参数
在类上加上注解,直接创建任务注解 @JobScheduled配置任务参数,包括定分片项、分片参数等等
不用创建 ZK 注册中心自动配置类 RegCentreAutoConfiguration.java注入从 RegCenterProperties.java 读取到的参数,自动创 ZookeeperConfiguration
不用创建三级(Core、Type、Lite)配置自动配置类 JobAutoConfiguration.java读 取 注 解 的 参 数 , 创 建JobCoreConfiguration 、
JobTypeConfiguration 、LiteJobConfiguration 在注册中心创建之后再创建
Spring Boot 启动时自动配置创 建Resource/META-INF/spring.factories指定两个自动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
io.dreamstudio.elasticjob.autoconfigure.RegCentreAutoConfiguration,\
io.dreamstudio.elasticjob.autoconfigure.JobAutoConfiguration
打包 starter 的工程,引入 starter 的依赖,即可在项目中使用注解开启任务调度功能。

5 E-Job 原理

5.1 启动

standalone 工程
new JobScheduler(regCenter, simpleJobRootConfig).init();

init 方法

public void init() {LiteJobConfiguration liteJobConfigFromRegCenter = schedulerFacade.updateJobConfiguration(liteJobConfig);// 设置分片数JobRegistry.getInstance().setCurrentShardingTotalCount(liteJobConfigFromRegCenter.getJobName(), liteJobConfigFromRegCenter.getTypeConfig().getCoreConfig().getShardingTotalCount());// 构建任务,创建调度器JobScheduleController jobScheduleController = new JobScheduleController(createScheduler(),createJobDetail(liteJobConfigFromRegCenter.getTypeConfig().getJobClass()), liteJobConfigFromRegCenter.getJobName());// 在 ZK 上注册任务JobRegistry.getInstance().registerJob(liteJobConfigFromRegCenter.getJobName(), jobScheduleController, regCenter);// 添加任务信息并进行节点选举schedulerFacade.registerStartUpInfo(!liteJobConfigFromRegCenter.isDisabled());// 启动调度器jobScheduleController.scheduleJob(liteJobConfigFromRegCenter.getTypeConfig().getCoreConfig().getCron());
}

registerStartUpInfo 方法

public void registerStartUpInfo(final boolean enabled) {// 启动所有的监听器listenerManager.startAllListeners();// 节点选举leaderService.electLeader();// 服务信息持久化(写到 ZK)serverService.persistOnline(enabled);// 实例信息持久化(写到 ZK)instanceService.persistOnline();// 重新分片shardingService.setReshardingFlag();// 监控信息监听器monitorService.listen();// 自诊断修复,使本地节点与 ZK 数据一致if (!reconcileService.isRunning()) {reconcileService.startAsync();}
}
监听器用于监听 ZK 节点信息的变化。
启动的时候进行主节点选举
/**
* 选举主节点. */
public void electLeader() {log.debug("Elect a new leader now.");jobNodeStorage.executeInLeader(LeaderNode.LATCH, new LeaderElectionExecutionCallback());log.debug("Leader election completed.");
}

Latch 是一个分布式锁,选举成功后在 instance 写入服务器信息
在这里插入图片描述
// 服务信息持久化(写到 ZK servers 节点)

serverService.persistOnline(enabled);

以下是单机运行多个实例:
在这里插入图片描述
// 实例信息持久化(写到 ZK instances 节点)

instanceService.persistOnline();

运行了两个实例:
在这里插入图片描述

5.2 任务执行与分片原理

关注两个问题:
1、LiteJob 是怎么被执行的?
2、分片项是怎么分配给不同的服务实例的?
在创建 Job 的时候(createJobDetail),创建的是实现了 Quartz 的 Job 接口的LiteJob 类,LiteJob 类实现了 Quartz 的 Job 接口。
在 LiteJob 的 execute 方法中获取对应类型的执行器,调用 execute()方法。
public static AbstractElasticJobExecutor getJobExecutor(final ElasticJob elasticJob, final JobFacade jobFacade) {if (null == elasticJob) {return new ScriptJobExecutor(jobFacade);}if (elasticJob instanceof SimpleJob) {return new SimpleJobExecutor((SimpleJob) elasticJob, jobFacade);}if (elasticJob instanceof DataflowJob) {return new DataflowJobExecutor((DataflowJob) elasticJob, jobFacade);}throw new JobConfigurationException("Cannot support job type '%s'", elasticJob.getClass().getCanonicalName());
}
EJOB 提供管理任务执行器的抽象类 AbstractElasticJobExecutor,核心动作在execute()方法中执行。
public final void execute() {}

调用了另一个 execute()方法,122 行:

execute(shardingContexts, JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER);
private void execute(final ShardingContexts shardingContexts, final JobExecutionEvent.ExecutionSource
executionSource) {}

在这个 execute 方法中又调用了 process()方法,150 行

private void process(final ShardingContexts shardingContexts, final JobExecutionEvent.ExecutionSource
executionSource) {Collection<Integer> items = shardingContexts.getShardingItemParameters().keySet();// 只有一个分片项时,直接执行if (1 == items.size()) {int item = shardingContexts.getShardingItemParameters().keySet().iterator().next();JobExecutionEvent jobExecutionEvent = new JobExecutionEvent(shardingContexts.getTaskId(), jobName, executionSource, item);process(shardingContexts, item, jobExecutionEvent);return;}final CountDownLatch latch = new CountDownLatch(items.size());// 本节点遍历执行相应的分片信息for (final int each : items) {final JobExecutionEvent jobExecutionEvent = new JobExecutionEvent(shardingContexts.getTaskId(), jobName, executionSource, each);if (executorService.isShutdown()) {return;}executorService.submit(new Runnable() {@Overridepublic void run() {try {process(shardingContexts, each, jobExecutionEvent);} finally {latch.countDown();}}});}try {// 等待所有的分片项任务执行完毕latch.await();} catch (final InterruptedException ex) {Thread.currentThread().interrupt();}
}

又调用了另一个 process()方法,206 行

protected abstract void process(ShardingContext shardingContext);
交给具体的实现类(SimpleJobExecutor、DataflowJobExecutor、ScriptJobExecutor)去处理。

在这里插入图片描述
最终调用到任务类

@Override
protected void process(final ShardingContext shardingContext) {simpleJob.execute(shardingContext);
}

5.3 失效转移

所谓失效转移,就是在执行任务的过程中发生异常时,这个分片任务可以在其他节点再次执行。
simple.SimpleJobTest,failover 方法:
// 设置失效转移
JobCoreConfiguration coreConfig = JobCoreConfiguration.newBuilder("MySimpleJob", "0/2 * * * * ?", 4).shardingItemParameters("0=RDP, 1=CORE, 2=SIMS, 3=ECIF").failover(true).build()
FailoverListenerManager 监听的是 zk 的 instance 节点删除事件。如果任务配置了 failover 等于 true,其中某个 instance 与 zk 失去联系或被删除,并且失效的节点又不是本身,就会触发失效转移逻辑。
Job 的失效转移监听来源于 FailoverListenerManager 中内部类JobCrashedJobListener 的 dataChanged 方法。
当节点任务失效时会调用 JobCrashedJobListener 监听器,此监听器会根据实例 id获取所有的分片,然后调用 FailoverService 的 setCrashedFailoverFlag 方法,将每个分片 id 写到/jobName/leader/failover/items 下,例如原来的实例负责 1、2 分片项,那么 items 节点就会写入 1、2,代表这两个分片项需要失效转移。
protected void dataChanged(final String path, final Type eventType, final String data) {if (isFailoverEnabled() && Type.NODE_REMOVED == eventType && instanceNode.isInstancePath(path)) {String jobInstanceId = path.substring(instanceNode.getInstanceFullPath().length() + 1);if (jobInstanceId.equals(JobRegistry.getInstance().getJobInstance(jobName).getJobInstanceId())) {return;}List<Integer> failoverItems = failoverService.getFailoverItems(jobInstanceId);if (!failoverItems.isEmpty()) {for (int each : failoverItems) {// 设置失效的分片项标记failoverService.setCrashedFailoverFlag(each);failoverService.failoverIfNecessary();}} else {for (int each : shardingService.getShardingItems(jobInstanceId)) {failoverService.setCrashedFailoverFlag(each);failoverService.failoverIfNecessary();}}}
}
然后接下来调用 FailoverService 的 failoverIfNessary 方法,首先判断是否需要失败转移,如果可以需要则只需作业失败转移。
public void failoverIfNecessary() {if (needFailover()) {jobNodeStorage.executeInLeader(FailoverNode.LATCH, new FailoverLeaderExecutionCallback());}
}
条件一:${JOB_NAME}/leader/failover/items/${ITEM_ID} 有失效转移的作业分片项。
条件二:当前作业不在运行中。
private boolean needFailover() {return jobNodeStorage.isJobNodeExisted(FailoverNode.ITEMS_ROOT)&& !jobNodeStorage.getJobNodeChildrenKeys(FailoverNode.ITEMS_ROOT).isEmpty()&& !JobRegistry.getInstance().isJobRunning(jobName);
}
在主节点执行操作
public void executeInLeader(final String latchNode, final LeaderExecutionCallback callback) {try (LeaderLatch latch = new LeaderLatch(getClient(), jobNodePath.getFullPath(latchNode))) {latch.start();latch.await();callback.execute();//CHECKSTYLE:OFF} catch (final Exception ex) {//CHECKSTYLE:ONhandleException(ex);}
}
1、再次判断是否需要失效转移;
2、从注册中心获得一个 `${JOB_NAME}/leader/failover/items/${ITEM_ID}` 作业分片项;
3、在注册中心节点`${JOB_NAME}/sharding/${ITEM_ID}/failover` 注册作业分片项为当前作业节点;
4、然后移除任务转移分片项;
5、最后调用执行,提交任务。
class FailoverLeaderExecutionCallback implements LeaderExecutionCallback {
@Override
public void execute() {// 判断是否需要失效转移if (JobRegistry.getInstance().isShutdown(jobName) || !needFailover()) {return;}// 从${JOB_NAME}/leader/failover/items/${ITEM_ID}获得一个分片项int crashedItem =Integer.parseInt(jobNodeStorage.getJobNodeChildrenKeys(FailoverNode.ITEMS_ROOT).get(0));log.debug("Failover job '{}' begin, crashed item '{}'", jobName, crashedItem);// 在注册中心节点`${JOB_NAME}/sharding/${ITEM_ID}/failover`注册作业分片项为当前作业节点jobNodeStorage.fillEphemeralJobNode(FailoverNode.getExecutionFailoverNode(crashedItem), JobRegistry.getInstance().getJobInstance(jobName).getJobInstanceId());// 移除任务转移分片项jobNodeStorage.removeJobNodeIfExisted(FailoverNode.getItemsNode(crashedItem));JobScheduleController jobScheduleController = JobRegistry.getInstance().getJobScheduleController(jobName);if (null != jobScheduleController) {// 提交任务jobScheduleController.triggerJob();}}
}
这里仅仅是触发作业,而不是立即执行。

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

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

相关文章

安全cdn有哪些优势

1. 免备案&#xff1a;在中国大陆地区&#xff0c;进行网站建设需要先进行备案手续&#xff0c;而安全cdn可以避免这一繁琐的步骤&#xff0c;节省时间和精力。 2. 精品线路&#xff1a;安全cdn使用的是覆盖范围更广、速度更快的香港CN2 GIA优化线路。 3. 高速稳定&#xff1a…

在vue3中使用Cesium保姆篇

1.首先新建一个vue项目 Vue.js - 渐进式 JavaScript 框架 | Vue.js 可以直接到管网中查看命令通过npm来创建一个vue3的项目 然后通过命令下载1.99的版本的cesium和plugin npm i cesium1.99 vite-plugin-cesium 下载完了以后 2.引入cesium 首先找到vue的vite.config.js …

[LitCTF 2023]这是什么?SQL !注一下 !

[LitCTF 2023]这是什么&#xff1f;SQL &#xff01;注一下 &#xff01; wp 题目描述&#xff1a;为了安全起见多带了几个套罢了o(▽)q 页面内容&#xff08;往下滑&#xff09;&#xff1a; SQL 语句已给出&#xff0c;无非是更换了闭合方式。 先输个 1 试试&#xff1a; …

【unity】基于Obi的绳/杆蓝图、绳杆区别及其创建方法

绳索 是通过使用距离和弯曲约束将粒子连接起来而形成的。由于规则粒子没有方向(只有位置)&#xff0c;因此无法模拟扭转效应(维基百科)&#xff0c;绳子也无法保持其静止形状。然而&#xff0c;与杆不同的是&#xff0c;绳索可以被撕裂/劈开&#xff0c;并且可以在运行时改变其…

轻量对象存储 LighthouseCOS 用户实践征文

产品使用攻略、上云技术实践&#xff0c;有奖征集&#xff0c;多重好礼等您带回家&#xff5e; 存储桶一键挂载轻量应用服务器&#xff0c;简单易用&#xff0c;腾讯云轻量对象存储用户实践征文活动特惠&#xff1a;腾讯云轻量云专场特惠活动。 投稿说明 1. 注册/登录腾讯云账…

【EI会议征稿通知】第三届智能电网与绿色能源国际学术会议(ICSGGE 2024)

第三届智能电网与绿色能源国际学术会议&#xff08;ICSGGE 2024&#xff09; 2024 3rd International Conference on Smart Grid and Green Energy 2024年第三届智能电网与绿色能源国际学术会议&#xff08;ICSGGE 2024&#xff09;将于2024年4月19-21日在中国成都举行。会议…

探索架构之美 | 小米分享架构师的方法论

大家好&#xff0c;我是小米&#xff01;今天我们要聊的话题可是相当精彩——“架构师的方法论”&#xff01;作为一名热爱技术的小伙伴&#xff0c;我深知在软件开发领域&#xff0c;拥有一套科学的方法论是多么的重要。所以&#xff0c;不废话&#xff0c;让我们一起踏上探索…

SpringBoot集成 Websocket 实现服务与客户端进行消息发送和接收

介绍 WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。 效果 客户端效果 服务端日志 pom依赖 <!-- websocket --> <dependency><groupId>org.springfram…

vue3+Ts+Hook的方式实现商城核心功能sku选择器

前言 Hooks是React等函数式编程框架中非常受欢迎的工具&#xff0c;随着VUE3 Composition API 函数式编程风格的推出&#xff0c;现在也受到越来越多VUE3开发者的青睐&#xff0c;它让开发者的代码具有更高的复用度且更加清晰、易于维护。 本文将通过CRMEB商城商品详情sku选择…

iOS苹果和Android安卓测试APP应用程序的差异

Hello大家好呀&#xff0c;我是咕噜铁蛋&#xff01;我们经常需要关注移动应用程序的测试和优化&#xff0c;以提供更好的用户体验。在移动应用开发领域&#xff0c;iOS和Android是两个主要的操作系统平台。本文铁蛋讲给各位小伙伴们详细介绍在App测试中iOS和Android的差异&…

Flyweight享元/共享模式(对象性能)

Flyweight 链接&#xff1a;享元模式实例代码 解析 目的 在软件系统采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中&#xff0c;从而带来很高的运行时代价——主要指内存需求方面的代价。如何在避免大量细粒度对象问题的同时&#xff0c;让外部客户程序仍然…

快速、准确地检测和分类病毒序列分析工具 ViralCC的介绍和详细使用方法, 附带应用脚本

介绍 viralcc是一个基因组病毒分析工具&#xff0c;可以用于快速、准确地检测和分类病毒序列。 github&#xff1a;dyxstat/ViralCC: ViralCC: leveraging metagenomic proximity-ligation to retrieve complete viral genomes (github.com) Instruction of reproducing resul…

java基于ssm框架的滁艺咖啡在线销售系统+vue论文

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装滁艺咖啡在线销售系统软件来发挥其高效地信息处理的作用&am…

客服系统接入FastGPT

接入FastGPT 点击【应用】【外部使用】【API访问】【新建】新建一个KEY&#xff0c;同时也可以看到我们的API根地址 这个根地址和Key可以填入任何支持OpenAI接口的应用里&#xff0c;这个接口是兼容OpenAI格式。 在客服系统【知识库AI配置】里填上接口地址和接口密钥。这样我…

更新!又10本期刊被踢,Scopus期刊目录-第九版(附下载)

Scopus概况 Scopus是Elsevier创立于2004年的摘要和引文数据库&#xff0c;同时也是全世界最大的摘要和引文数据库&#xff0c;涵盖了丛书、期刊和行业期刊这三种资源类型。 截止到2023年8月&#xff0c;Scopus期刊目录中共包含期刊44049本。 Scopus与SCIE或SSCI一样&#xf…

第十四章 14.2案例:使用KVM命令集管理虚拟机

查看命令帮助 [rootLinux01 ~]# virsh -h—————————————————————————————————————————— 查看KVM的配置文件存放目录〈test01 , xml是虚拟机系统实例的配置文件) [rootLinux01 ~]# ls /etc/libvirt/qemu —————————————…

Git 常用命令详解及如何在IDEA中操作

文章目录 前言发现宝藏一、初识Git1.Git概述2. Git的功能3. Git运行图示 二、Git下载安装三、Git 代码托管服务1.常用的 Git 代码托管服务2.使用码云代码托管服务 四、Git 常用命令1.Git 全局设置2.获取Git 仓库3.工作区、暂存区、版本库 概念4.Git 工作区中文件的两种状态5.本…

第一课:Transformer

第一课&#xff1a;Transformer 文章目录 第一课&#xff1a;Transformer1、学习总结&#xff1a;什么是语言模型&#xff1f;大语言模型&#xff08;LLM&#xff09;技术演变史注意力机制Transformer结构课程ppt及代码地址 2、学习心得&#xff1a;3、经验分享&#xff1a;4、…

【VSCode】CMake Language Support 总是下载 .NET 超时,但又不想升级dotnet

错误信息 Error: Could not resolve dotnet path!An error occurred while installing .NET (6.0): .NET Acquisition Failed: Installation failed: Error: .NET installation timed out. You may need to change the timeout time if you have a slow connection. Please se…

【基础篇】十三、强软弱虚引用、终结器引用

文章目录 0、相关&#x1f58a;1、强引用2、软引用3、弱引用4、虚引用5、终结引用 关于对象能否被回收&#xff1a; 计数器可达性分析 还可以根据引用的类型&#xff0c;不同的引用类型&#xff0c;对应对象的不同GC回收规则。 0、相关&#x1f58a; &#x1f4d5;【强软弱虚…