【故障排查】10分钟解决Quartz重复调度的疑难杂症

我司使用Apache DolphinScheduler作为调度框架很久了,感兴趣的小伙伴可以看看这些干货文章:

因为之前监控到会出现重复的调度的问题,所以此文记录排查重复调度问题的全过程,希望对社区其他的小伙伴能够起到抛砖引玉的作用!

注:本文使用的DolphinScheduler 3.1.1的版本。

数据库使用的Tidb数据库,版本6.5.0,数据库的隔离级别是默认的RR(Read Committed)。

Quartz版本为2.3.2,存储模式为JDBC。使用的CronTrigger,设置了org.quartz.jobStore.acquireTriggersWithinLock=true

先说结论。Tidb和Mysql在RR隔离级别下,开启事务后,创建Read view的时机不同,导致Tidb会出现Quartz重复调度的问题。

一、问题发生

前一段时间,客户给我们报了重复调度的问题。

可见下图,同名同调度时间的任务被执行了两次,id相差1。还不是单个偶发情况,基本每一到两天都会出现。对于调度框架,出现重复调度的问题是不可接受的。

file

二、问题定位

第一步就是定位问题,Apache DolphinScheduler的底层调度框架是Quartz。首先要明确问题的原因,是Quartz的问题还是DolphinScheduler的问题。

通过排查日志,可以看到是Quartz这边的问题。

file

那问题很明确了,Quartz这边出现了重复调度,当时就觉得很奇怪,因为这个框架非常稳定了,上一次发版本还是在Oct 24, 2019。

很多调度框架的底层使用的都是Quartz,比如XXL-Job等。但是日志这边确实很清晰的看出来确实Quartz重复触发了同一个任务。

网上关于Quartz重复调度的问题的解决方案都是加锁

设置 org.quartz.jobStore.acquireTriggersWithinLock=true

但是DolphinScheduler这边默认在配置文件中就已经设置了这个参数。

比较棘手,不太好排查。网上也没有太多相关的资料。

当时怀疑是不是数据库的问题,比如锁失效,多个线程拿到了同一把锁,说实话感觉这个可能性不大,但是确实没想到其他的可能。

于是就先看Quartz的源码和调度逻辑,这种问题一般需要了解更多的底层知识才能发现问题。在看源码的时候也看到了这么一篇博客,写的非常清晰,有非常好的参考价值,非常感谢作者。

我们首先讲解一下Quartz里面一些关于调度的概念,后面会用到。

  1. Quartz核心调度主要是QRTZ_TRIGGERS这张表。Quartz把一次调度称之为一次Fire。QRTZ_TRIGGERS这张表里有几个比较核心的字段,TRIGGER_NAME(调度任务名)、PREV_FIRE_TIME(上次调度时间)、NEXT_FIRE_TIME(下次调度时间)、TRIGGER_STATE(当前状态)。
  2. QRTZ_FIRED_TRIGGERS,这张表存储的正在触发的调度信息。

状态正常的Tigger状态变更流程图: file

Quartz调度过程简化

  1. 加锁
  2. 获取将要fire的triggers列表,这里有一个概念,比如当前时间是9.59,他可能会获取下次调度时间为9.57-10.02之间的trrigers,防止漏调度的问题
  3. 更新QRTZ_TRIGGERS的状态从WAITING->ACQUAIRED
  4. 将trigger信息insert进QRTZ_FIRED_TRIGGERS表,状态为ACQUAIRED
  5. 释放锁

等到真正开始调度

  1. 加锁
  2. 检查QRTZ_TRIGGERS状态是否为ACQUAIRED
  3. 如果是,QRTZ_FIRED_TRIGGERS状态更改为EXECUTING
  4. 更新QRTZ_TRIGGERS的next_fire_time
  5. 将QRTZ_TRIGGERS状态更新为WAITING
  6. 释放锁

1、开启MVCC日志

在看源码的同时,也联系DBA是否可以保持DB这边所有的sql以供排查。

DBA这边反馈保持所有的sql会占据大量的磁盘空间,但是可以先开启MVCC日志,也就是可以切换到不同的时间点,去查看当时数据库里面的状态。也很有用。

file

进过排查可以看见,针对于同一条调度,QRTZ_FIRED_TRIGGERS出现了两条记录,更加印证了重复调度是由于Quartz引起的。

但是拿不到具体执行了哪些sql,单纯看MVCC没有太多的价值。

2、开启全量日志

再次跟DBA沟通后,给我们搭建了一个新集群,仅供测试使用,可以开启全量日志。

有了全量日志就非常方便排查了。

[2024/02/28 18:45:20.141 +00:00] [INFO] [session.go:3749] [GENERAL_LOG] [conn=2452605821289251623] [user=udp@10.249.34.78] [schemaVersion=421] [txnStartTS=448042348020498438] [forUpdateTS=448042348020498438] [isReadConsistency=false] [currentDB=udp] [isPessimistic=true] [sessionTxnMode=PESSIMISTIC] [sql="INSERT INTO t_ds_process_instance  ( process_definition_code, process_definition_version, state, state_history, recovery, start_time,  run_times, name, host, command_type, command_param, task_depend_type, max_try_times, failure_strategy, warning_type, warning_group_id, schedule_time, command_start_time,  executor_id, is_sub_process, history_cmd, process_instance_priority, worker_group, environment_code, timeout, tenant_id,  next_process_instance_id, dry_run, restart_time )  VALUES  ( 12316168402080, 1, 1, '[{\"time\":\"2024-02-28 18:45:20\",\"state\":\"RUNNING_EXECUTION\",\"desc\":\"init running\"},{\"time\":\"2024-02-28 18:45:20\",\"state\":\"RUNNING_EXECUTION\",\"desc\":\"start a new process from scheduler\"}]', 0, '2024-02-28 18:45:20.007',  1, 'shell-1-20240228184520007', 'udp-service-dev-aiad-master-1.udp-service-dev-aiad-master-headless.wap-udp-dev.svc.aiadgen-int-1:5678', 6, '{\"schedule_timezone\":\"Asia/Shanghai\"}', 2, 0, 1, 0, 0, '2024-02-28 18:43:08.0', '2024-02-28 18:45:17.0',  810004, 0, 'SCHEDULER', 2, 'default', -1, 0, -1,  0, 0, '2024-02-28 18:45:20.007' )"]

[2024/02/28 18:45:26.185 +00:00] [INFO] [session.go:3749] [GENERAL_LOG] [conn=2452605821289251629] [user=udp@10.249.34.78] [schemaVersion=421] [txnStartTS=448042349606469635] [forUpdateTS=448042349606469635] [isReadConsistency=false] [currentDB=udp] [isPessimistic=true] [sessionTxnMode=PESSIMISTIC] [sql="INSERT INTO t_ds_process_instance  ( process_definition_code, process_definition_version, state, state_history, recovery, start_time,  run_times, name, host, command_type, command_param, task_depend_type, max_try_times, failure_strategy, warning_type, warning_group_id, schedule_time, command_start_time,  executor_id, is_sub_process, history_cmd, process_instance_priority, worker_group, environment_code, timeout, tenant_id,  next_process_instance_id, dry_run, restart_time )  VALUES  ( 12316168402080, 1, 1, '[{\"time\":\"2024-02-28 18:45:26\",\"state\":\"RUNNING_EXECUTION\",\"desc\":\"init running\"},{\"time\":\"2024-02-28 18:45:26\",\"state\":\"RUNNING_EXECUTION\",\"desc\":\"start a new process from scheduler\"}]', 0, '2024-02-28 18:45:26.05',  1, 'shell-1-20240228184526050', 'udp-service-dev-aiad-master-0.udp-service-dev-aiad-master-headless.wap-udp-dev.svc.aiadgen-int-1:5678', 6, '{\"schedule_timezone\":\"Asia/Shanghai\"}', 2, 0, 1, 0, 0, '2024-02-28 18:43:08.0', '2024-02-28 18:45:24.0',  810004, 0, 'SCHEDULER', 2, 'default', -1, 0, -1,  0, 0, '2024-02-28 18:45:26.05' )"]

通过日志我们可以看到出现了重复调度,预计调度时间是 2024-02-28 18:43:08.0

我们需要找出跟调度相关的日志,QRTZ_FIRED_TRIGGERS和QRTZ_TRIGGERS。

第一次调度相关日志:

[2024/02/28 18:45:08.250 +00:00] [INFO] [session.go:3749] [GENERAL_LOG] [conn=2452605821289251625] [user=udp@10.249.34.78] [schemaVersion=421] [txnStartTS=448042343682015234] [forUpdateTS=448042344638840833] [isReadConsistency=false] [currentDB=udp] [isPessimistic=true] [sessionTxnMode=PESSIMISTIC] [sql="UPDATE QRTZ_TRIGGERS SET JOB_NAME = 'job_1201640', JOB_GROUP = 'jobgroup_1200004', DESCRIPTION = null, NEXT_FIRE_TIME = 1709145788000, PREV_FIRE_TIME = 1709145784000, TRIGGER_STATE = 'WAITING', TRIGGER_TYPE = 'CRON', START_TIME = 1709114081000, END_TIME = 4861267200000, CALENDAR_NAME = null, MISFIRE_INSTR = 1, PRIORITY = 5 WHERE SCHED_NAME = 'DolphinScheduler' AND TRIGGER_NAME = 'job_1201640' AND TRIGGER_GROUP = 'jobgroup_1200004'"]

[2024/02/28 18:45:13.348 +00:00] [INFO] [session.go:3749] [GENERAL_LOG] [conn=2452605821289251625] [user=udp@10.249.34.78] [schemaVersion=421] [txnStartTS=448042345307308035] [forUpdateTS=448042345897132034] [isReadConsistency=false] [currentDB=udp] [isPessimistic=true] [sessionTxnMode=PESSIMISTIC] [sql="INSERT INTO QRTZ_FIRED_TRIGGERS (SCHED_NAME, ENTRY_ID, TRIGGER_NAME, TRIGGER_GROUP, INSTANCE_NAME, FIRED_TIME, SCHED_TIME, STATE, JOB_NAME, JOB_GROUP, IS_NONCONCURRENT, REQUESTS_RECOVERY, PRIORITY) VALUES('DolphinScheduler', 'udp-service-dev-aiad-master-017091137217721709113723285', 'job_1201640', 'jobgroup_1200004', 'udp-service-dev-aiad-master-01709113721772', 1709145913303, 1709145788000, 'ACQUIRED', null, null, 0, 0, 5)"]

[2024/02/28 18:45:20.141 +00:00] [INFO] [session.go:3749] [GENERAL_LOG] [conn=2452605821289251623] [user=udp@10.249.34.78] [schemaVersion=421] [txnStartTS=448042348020498438] [forUpdateTS=448042348020498438] [isReadConsistency=false] [currentDB=udp] [isPessimistic=true] [sessionTxnMode=PESSIMISTIC] [sql="INSERT INTO t_ds_process_instance  ( process_definition_code, process_definition_version, state, state_history, recovery, start_time,  run_times, name, host, command_type, command_param, task_depend_type, max_try_times, failure_strategy, warning_type, warning_group_id, schedule_time, command_start_time,  executor_id, is_sub_process, history_cmd, process_instance_priority, worker_group, environment_code, timeout, tenant_id,  next_process_instance_id, dry_run, restart_time )  VALUES  ( 12316168402080, 1, 1, '[{\"time\":\"2024-02-28 18:45:20\",\"state\":\"RUNNING_EXECUTION\",\"desc\":\"init running\"},{\"time\":\"2024-02-28 18:45:20\",\"state\":\"RUNNING_EXECUTION\",\"desc\":\"start a new process from scheduler\"}]', 0, '2024-02-28 18:45:20.007',  1, 'shell-1-20240228184520007', 'udp-service-dev-aiad-master-1.udp-service-dev-aiad-master-headless.wap-udp-dev.svc.aiadgen-int-1:5678', 6, '{\"schedule_timezone\":\"Asia/Shanghai\"}', 2, 0, 1, 0, 0, '2024-02-28 18:43:08.0', '2024-02-28 18:45:17.0',  810004, 0, 'SCHEDULER', 2, 'default', -1, 0, -1,  0, 0, '2024-02-28 18:45:20.007' )"]


[2024/02/28 18:45:16.730 +00:00] [INFO] [session.go:3749] [GENERAL_LOG] [conn=2452605821289251625] [user=udp@10.249.34.78] [schemaVersion=421] [txnStartTS=448042346552492033] [forUpdateTS=448042346853957637] [isReadConsistency=false] [currentDB=udp] [isPessimistic=true] [sessionTxnMode=PESSIMISTIC] [sql="UPDATE QRTZ_TRIGGERS SET JOB_NAME = 'job_1201640', JOB_GROUP = 'jobgroup_1200004', DESCRIPTION = null, NEXT_FIRE_TIME = 1709145792000, PREV_FIRE_TIME = 1709145788000, TRIGGER_STATE = 'WAITING', TRIGGER_TYPE = 'CRON', START_TIME = 1709114081000, END_TIME = 4861267200000, CALENDAR_NAME = null, MISFIRE_INSTR = 1, PRIORITY = 5 WHERE SCHED_NAME = 'DolphinScheduler' AND TRIGGER_NAME = 'job_1201640' AND TRIGGER_GROUP = 'jobgroup_1200004'"]

第二次调度相关日志:

[2024/02/28 18:45:18.454 +00:00] [INFO] [session.go:3749] [GENERAL_LOG] [conn=2452605821289251605] [user=udp@10.249.34.78] [schemaVersion=421] [txnStartTS=448042345936453636] [forUpdateTS=448042347509317637] [isReadConsistency=false] [currentDB=udp] [isPessimistic=true] [sessionTxnMode=PESSIMISTIC] [sql="SELECT TRIGGER_NAME, TRIGGER_GROUP, NEXT_FIRE_TIME, PRIORITY FROM QRTZ_TRIGGERS WHERE SCHED_NAME = 'DolphinScheduler' AND TRIGGER_STATE = 'WAITING' AND NEXT_FIRE_TIME <= 1709145941638 AND (MISFIRE_INSTR = -1 OR (MISFIRE_INSTR != -1 AND NEXT_FIRE_TIME >= 1709145618319)) ORDER BY NEXT_FIRE_TIME ASC, PRIORITY DESC"]

[2024/02/28 18:45:19.281 +00:00] [INFO] [session.go:3749] [GENERAL_LOG] [conn=2452605821289251605] [user=udp@10.249.34.78] [schemaVersion=421] [txnStartTS=448042345936453636] [forUpdateTS=448042347509317637] [isReadConsistency=false] [currentDB=udp] [isPessimistic=true] [sessionTxnMode=PESSIMISTIC] [sql="UPDATE QRTZ_TRIGGERS SET TRIGGER_STATE = 'ACQUIRED' WHERE SCHED_NAME = 'DolphinScheduler' AND TRIGGER_NAME = 'job_1201640' AND TRIGGER_GROUP = 'jobgroup_1200004' AND TRIGGER_STATE = 'WAITING'"]

[2024/02/28 18:45:19.497 +00:00] [INFO] [session.go:3749] [GENERAL_LOG] [conn=2452605821289251605] [user=udp@10.249.34.78] [schemaVersion=421] [txnStartTS=448042345936453636] [forUpdateTS=448042347509317637] [isReadConsistency=false] [currentDB=udp] [isPessimistic=true] [sessionTxnMode=PESSIMISTIC] [sql="INSERT INTO QRTZ_FIRED_TRIGGERS (SCHED_NAME, ENTRY_ID, TRIGGER_NAME, TRIGGER_GROUP, INSTANCE_NAME, FIRED_TIME, SCHED_TIME, STATE, JOB_NAME, JOB_GROUP, IS_NONCONCURRENT, REQUESTS_RECOVERY, PRIORITY) VALUES('DolphinScheduler', 'udp-service-dev-aiad-master-117091136300541709113631821', 'job_1201640', 'jobgroup_1200004', 'udp-service-dev-aiad-master-11709113630054', 1709145919453, 1709145788000, 'ACQUIRED', null, null, 0, 0, 5)"]

[2024/02/28 18:45:26.185 +00:00] [INFO] [session.go:3749] [GENERAL_LOG] [conn=2452605821289251629] [user=udp@10.249.34.78] [schemaVersion=421] [txnStartTS=448042349606469635] [forUpdateTS=448042349606469635] [isReadConsistency=false] [currentDB=udp] [isPessimistic=true] [sessionTxnMode=PESSIMISTIC] [sql="INSERT INTO t_ds_process_instance  ( process_definition_code, process_definition_version, state, state_history, recovery, start_time,  run_times, name, host, command_type, command_param, task_depend_type, max_try_times, failure_strategy, warning_type, warning_group_id, schedule_time, command_start_time,  executor_id, is_sub_process, history_cmd, process_instance_priority, worker_group, environment_code, timeout, tenant_id,  next_process_instance_id, dry_run, restart_time )  VALUES  ( 12316168402080, 1, 1, '[{\"time\":\"2024-02-28 18:45:26\",\"state\":\"RUNNING_EXECUTION\",\"desc\":\"init running\"},{\"time\":\"2024-02-28 18:45:26\",\"state\":\"RUNNING_EXECUTION\",\"desc\":\"start a new process from scheduler\"}]', 0, '2024-02-28 18:45:26.05',  1, 'shell-1-20240228184526050', 'udp-service-dev-aiad-master-0.udp-service-dev-aiad-master-headless.wap-udp-dev.svc.aiadgen-int-1:5678', 6, '{\"schedule_timezone\":\"Asia/Shanghai\"}', 2, 0, 1, 0, 0, '2024-02-28 18:43:08.0', '2024-02-28 18:45:24.0',  810004, 0, 'SCHEDULER', 2, 'default', -1, 0, -1,  0, 0, '2024-02-28 18:45:26.05' )"]

可以看出呈现线程关系,也就是第一次调度彻底结束之后,出现了第二次调度。

比较疑惑的点在于第一次调度后已经更新了QRTZ_TRIGGERS的next fire time,但是第二次调度在select 之后触发的调度跟第一次调度的调度时间相同。

我们拿不到sql的执行结果,但是通过日志分析,第二次调度在执行select 获取需要调度的任务时,返回的结果跟第一次调度时返回的结果相同。

非常奇怪。当时怀疑是不是数据库的主从同步有问题,导致第二次调度获取的数据是更新前的数据。

但是通过mvcc查看:

file

可以明显的看出来,第二次调度的时候,数据库的值更新过的,不存在主从同步的问题。

并且从日志来看,是串行执行的,也就是获取锁没问题,说到获取锁,那么看看加锁释放锁是怎么个流程。

3、查看锁记录

conn可以简单理解为进程号。结果出乎意料,第二次调度的进程在45分11秒就尝试获取锁了,45分18秒才获取到锁。中间等待了7秒。

[2024/02/28 18:45:11.772 +00:00] [INFO] [session.go:3749] [GENERAL_LOG] [conn=2452605821289251605] [user=udp@10.249.34.78] [schemaVersion=421] [txnStartTS=0] [forUpdateTS=0] [isReadConsistency=false] [currentDB=udp] [isPessimistic=true] [sessionTxnMode=PESSIMISTIC] [sql="SELECT * FROM QRTZ_LOCKS WHERE SCHED_NAME = 'DolphinScheduler' AND LOCK_NAME = 'TRIGGER_ACCESS' FOR UPDATE"]

[2024/02/28 18:45:18.544 +00:00] [INFO] [session.go:3749] [GENERAL_LOG] [conn=2452605821289251605] [user=udp@10.249.34.78] [schemaVersion=421] [txnStartTS=448042345936453636] [forUpdateTS=448042347509317637] [isReadConsistency=false] [currentDB=udp] [isPessimistic=true] [sessionTxnMode=PESSIMISTIC] [sql="SET SQL_SELECT_LIMIT=DEFAULT"]

那第一次调度进程什么拿到锁的,于是梳理了一下两个进程申请释放锁的关系。

第一次调度的进程号简称为625,第二次调度的进程号简称为605。

18:45:09.427 625 申请锁

18:45:11.772 605 申请锁 -> 阻塞

18:45:12.210 625拿到锁

625 执行调度逻辑

625 18:45:16.730 执行完,更新triggers 表

18:45:17.287 625释放锁

18:45:17.928 625申请锁

18:45:18.363 605拿到锁

605 执行调度逻辑

看到这里,就有个猜想,605在拿到锁的时候查询QRTZ_TRIGGERS的结果跟625是相同的。

4、复现问题

那我们可以复现这个流程。

首先创建表。

CREATE TABLE `QRTZ_LOCKS` (
  `SCHED_NAME` varchar(120) NOT NULL,
  `LOCK_NAME` varchar(40) NOT NULL,
  PRIMARY KEY (`SCHED_NAME`,`LOCK_NAME`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

CREATE TABLE `t_ds_version` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `version` varchar(200) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `version_UNIQUE` (`version`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='version';

INSERT INTO udp.t_ds_version (version) VALUES
     ('1');

随后开启两个会话,按照如下顺序测试

Tidb

Process1Process2
start TRANSACTION;
start TRANSACTION;
SELECT * FROM QRTZ_LOCKS WHERE SCHED_NAME = 'DolphinScheduler' AND LOCK_NAME = 'TRIGGER_ACCESS' FOR UPDATE; 当前读
SELECT * FROM QRTZ_LOCKS WHERE SCHED_NAME = 'DolphinScheduler' AND LOCK_NAME = 'TRIGGER_ACCESS' FOR UPDATE; 当前读
UPDATE t_ds_version set version = '2' where 1 = 1;
commit;
select * from t_ds_version; `快照读1
version = 1

Process2的select * from t_ds_version;读取到的version=1。

而同样的操作在MySQL上执行时,Process2读取到的却是version=2。

该问题是因为MySQL和Tidb开启事务时创建Read view的时机不同导致的。

Mysql 在RR隔离级别下,同一个事务中的第一个快照读才会创建Read View,之后的快照读读取的都是同一个Read View。

没有搜到Tidb对于这块的相关文档,但是通过现象来看则是在开启事务的同时就创建了Read View。 关于Mysql的相关文档可以参考MySQL InnoDB的MVCC实现机制。

那说明应该就是这个差别导致了重复调度的问题。

5、问题复盘

我们假设一种情况,详见下图。

file

两个服务器一先一后开启事务,然后抢占锁,拿到锁后执行调度的逻辑。

如同所示,就出现了重复调度的情况。只看红框里面的逻辑,就跟最上面我们模拟在Tidb和Mysql中执行的一样,服务器2在Tidb数据库的情况下,拿到锁之后读取的是9.59时间下的Read View,所以也正常触发了应该10.00调度的任务。出现了重复的调度的情况。甚至可能出现重复调度三次四次的情况,如果在极其巧合的情况下。

三、解决方案

切换Mysql数据库,或者Tidb数据库更改到RC隔离级别。

关于为什么Tidb和Mysql在RR隔离级别下有区别,可以Track

本文由 白鲸开源科技 提供发布支持!

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

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

相关文章

1.中医学习-总论

目录 1.为什么要学中医 2.什么是中医 介绍 中医例子1&#xff1a; 中医例子2: 中医最高境界“大道至简” 中医讲究的是本质 中医核心&#xff1a;阴阳、表里、寒热、虚实 ​编辑医不叩门 3.阴阳 1.一天中的阴阳 2.一年中的阴阳 3.阴阳之间的关系 4.阴阳四季的变化 …

JetBrains学生包续期

Pycharm进入显示your license has expired 这个意思是你的许可过期了&#xff0c;重新输入最新的激活码就可以了。 1. 说明我的JetBrains学生包需要进行续期了。首先登录JetBrains官网打开个人主页&#xff0c;点击Renew my Education Pack 2. 然后填写一系列信息&#xff0c;…

户用光伏OA管理系统——光伏企业管理的新利器

随着全球对可再生能源的重视和推广&#xff0c;光伏行业得到了快速的发展&#xff0c;为人们提供了清洁、可再生能源。户用光伏作为光伏行业中应用最为广泛的发电系统&#xff0c;可以安装在居民住宅等地。越来越多的人们对户用光伏的发展给予支持&#xff0c;给光伏企业管理带…

PHP+MySQL开发组合:多端多商户DIY商城源码系统 带完整的搭建教程以及安装代码包

近年来&#xff0c;电商行业的迅猛发展&#xff0c;越来越多的商户开始寻求搭建自己的在线商城。然而&#xff0c;传统的商城系统往往功能单一&#xff0c;无法满足商户个性化、多样化的需求。同时&#xff0c;搭建一个功能完善的商城系统需要专业的技术团队和大量的时间成本&a…

OpenCV4.9.0在windows系统下的安装

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一章&#xff1a;将 OpenCV 与 Eclipse 结合使用&#xff08;插件 CDT&#xff09; 下一章&#xff1a;未发表 警告&#xff1a; 本教程可以包含过时的信息. 此处的描述已在 Windows 7 SP1 上…

FMQL45 XADC

通过PL自带的XADC可以读写温度&#xff0c;电压等&#xff0c;但是总是读的不对。开始查找FM_QL_bsp里面少了一个部分就是pl&#xff01;添加&#xff0c;在ewp已经修改了&#xff0c;但是文件不存在&#xff0c;现在给补上了&#xff0c;有点奇怪&#xff0c;有的项目会自动生…

ConKI: Contrastive Knowledge Injection for Multimodal Sentiment Analysis

文章目录 ConKI&#xff1a;用于多模态情感分析的对比知识注入文章信息研究目的研究内容研究方法1.总体结构2.Encoding with Knowledge Injection2.1 Pan-knowledge representations2.2 Knowledge-specific representations 3.Hierarchical Contrastive Learning4.损失函数5.训…

发布镜像到阿里云仓库

发布上一篇Dockerfile实战-自定义的centos镜像。 1、登录阿里云 2、找到容器镜像服务 3、创建命令空间 4、创建镜像仓库 5、点击进入这个镜像仓库&#xff0c;可以看到所有的信息 6、根据操作指南测试推送发布 6.1登录阿里云 [rootzhoujunru home]# docker login --usernam…

【问题记录】自定义Prometheus exporter收集数据,Prometheus显示收集到数据,grafana未显示数据出来

问题背景&#xff1a; 使用golang编写Prometheus exporter&#xff0c;获取指定API Url返回值中的data值&#xff0c;把它做为自定义指标。 1、exporter 500ms自动更新一次data值 2、Prometheus的Graph界面输入自定义指标可以查询到值的变化 3、自定义指标最小时间是ms级别&…

es 集群安全认证

参考文档&#xff1a;Configure security for the Elastic Stack | Elasticsearch Guide [7.17] | Elastic ES敏感信息泄露的原因 Elasticsearch在默认安装后&#xff0c;不提供任何形式的安全防护不合理的配置导致公网可以访问ES集群。比如在elasticsearch.yml文件中,server…

数据在内存中的的存储

1.数据类型介绍 内置类型 char , short , int , long , float , double , long long (C99) long 类型的大小是 4 / 8 个字节&#xff0c;元素C语言规定 sizeof&#xff08;long&#xff09;> sizeof&#xff08;int&#xff09;就行。在32位平台上&#xff0c;long为4个字…

完整指南:如何使用 Stable Diffusion API

Stable Diffusion 是一个先进的深度学习模型&#xff0c;用于创造和修改图像。这个模型能够基于文本描述来生成图像&#xff0c;让机器理解和实现用户的创意。使用这项技术的关键在于掌握其 API&#xff0c;通过编程来操控图像生成的过程。 在探索 Stable Diffusion API 的世界…

循环链表的用法

7.设 数 组 data[m] 作 为 循 环 队 列 SQ 的 存 储 空 间 &#xff0c;front 为 队 头 指 针 &#xff0c;rear 为 队 尾 指 针 &#xff0c;则 执 行 出 队 操 作 后 其 头 指 针 front 值 为 &#xff08; &#xff09; A&#xff0e;frontfront1 B&#xff0e;front(front1…

上位机图像处理和嵌入式模块部署(qmacvisual自己编写算法插件)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们说过&#xff0c;qmacvisual本身支持52个控件&#xff0c;但是指望这52个控件可以cover所有的应用场景&#xff0c;这也不太现实。另外&am…

【DBC专题】-11-使用Cantools将CAN/CANFD DBC自动生成C语言代码

目录 1 安装Python和Cantools 1.1 查看Python已安装的Package包 1.2 在Python中安装Cantools插件包 1.3 获取更多Cantools工具的更新动态 2 经典CAN/CANFD DBC自动生成C语言代码 2.1 批处理文件CAN_DBC_To_C.bat内容说明 2.2 经典CAN/CANFD DBC文件要求 2.3 如何使用生…

腾讯云k8s容器服务

1、新建一个集群 这个网址&#xff1a; 登录登录 - 腾讯云 2、选择第一个 3、名字随便起一个&#xff0c;然后基本默认就行 4、 组件配置直接跳过&#xff0c;信息确认&#xff0c;等待集群初始化&#xff0c;等10分钟左右&#xff08;容器服务需要充点钱才行&#xff09; 5…

瑞_JVM虚拟机_类的生命周期_初始化阶段 <clinit>

文章目录 1 JVM虚拟机概述2 类的生命周期2.1 加载阶段2.2 连接阶段2.3 初始化阶段\<client> ★★★★★2.3.1 案例一2.3.1.1 案例描述2.3.1.2 解析字节码指令 2.3.2 案例二2.3.3 小结2.3.4 代码中触发类的初始化的方式2.3.4.0 设置打印出加载并初始化的类2.3.4.1 方式一2…

three.js 鼠标左右拖动改变玩家视角

这里主要用到了 一个方法 obj.getWorldDirection(); obj.getWorldDirection()表示的获取obj对象自身z轴正方向在世界坐标空间中的方向。 按下 W键前进运动&#xff1b; <template><div><el-container><el-main><div class"box-card-left…

redis学习-List类型相关命令以及特殊情况分析

目录 1. lpush key value1 value2 ... 2. lrange key start end 3. lpop key num 4. rpush key value1 value2 ... 5. rpop key num 6. lindex key index 7. llen key 8. lrem key num value 9. rpoplpush key1 key2 10. lset key index value 11. linsert key before/after…

【STL】List容器介绍+相关练习题详细版本

List 1.List介绍2.使用注意3.list与vector的对比4.练习题 1.List介绍 list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;…