datax源码分析
文章目录
- 前言
- 一、加载配置文件
- 二、根据加载的配置文件进行调度
- 三、根据配置文件执行读取写入任务
- 总结
前言
在上一篇文章当中我们已经了解了datax的启动原理,以及datax的最基础的配置,datax底层java启动类的入口及关键参数。
接下来我将进行启动类执行的源码分析,设计到datax当中的一些关键的配置文件的初始化,各种组件之间的加载原理及运行原理,带领大家深度理解datax的底层运行逻辑。
一、加载配置文件
在上一篇我们已经知道了,datax的调用的是Engine类的mian方法,代码当中只有一行关键代码Engine.entry(args)。
public static void main(String[] args) throws Exception {//我们人为设置的启动参数args = new String[]{"-mode", "standalone","-jobid", "-1","-job", "D:\\chrom下载\\datax\\datax\\bin\\mysql2mysql.json"};//设置datax的解压目录System.setProperty("datax.home", "D:\\chrom下载\\datax\\datax");int exitCode = 0;try {//所有的逻辑都在这类方法里面Engine.entry(args);} catch (Throwable e) {//异常处理省略。。。}System.exit(exitCode);}
- Engine.entry(args)核心入口方法,该方法最核心内容主要是**解析datax运行所需的所有的参数,将其封装成Configuration,后续调用engine.start(configuration)**进行所有的业务逻辑的处理。我们需要着重的关注的方法只有一个就是
Configuration configuration = ConfigParser.parse(jobPath)
public static void entry(final String[] args) throws Throwable {//省略不重要代码。。。//核心的解析逻辑,我们需要特别关注的代码逻辑Configuration configuration = ConfigParser.parse(jobPath);//省略不重要代码。。。ConfigurationValidate.doValidate(configuration);Engine engine = new Engine();engine.start(configuration);}
- 解析我们需要的配置文件,job任务的json配置文件mysql2mysql.json,{you datax path}/conf/core.json文件,遍历{you datax patch}/plugin/reader/下的所有reade和{you datax patch}/plugin/writer/下的所有writer,加载符合的reader/writer的配置的plugin.json文件,reader/writer配置就是在mysql2mysql.json当中配置的,需要注意我们配置的reader/writer名称必须和reader/writer目录下的plugin_job_template.json下的name属性完全一致。处理还会加载前置处理plugin和后置的plugin,博主在源码debug时并未配置这两项的plugin。
public static Configuration parse(final String jobPath) {//加载我们配置job的json配置文件,我这里是mysql2mysql.json配置文件,配置文件//可以参考我上一篇文章当中的配置Configuration configuration = ConfigParser.parseJobConfig(jobPath);//将我们配置mysql2mysql.json文件的属性和core.json文件的属性合并。configuration.merge(//加载{you datax path}/conf/core.json文件ConfigParser.parseCoreConfig(CoreConstant.DATAX_CONF_PATH),false);//根据配置文件读取reader配置String readerPluginName = configuration.getString(CoreConstant.DATAX_JOB_CONTENT_READER_NAME);//根据配置文件读取writer配置String writerPluginName = configuration.getString(CoreConstant.DATAX_JOB_CONTENT_WRITER_NAME);//读取前置处理pluginString preHandlerName = configuration.getString(CoreConstant.DATAX_JOB_PREHANDLER_PLUGINNAME);//读取后置处理pluginString postHandlerName = configuration.getString(CoreConstant.DATAX_JOB_POSTHANDLER_PLUGINNAME);Set<String> pluginList = new HashSet<String>();pluginList.add(readerPluginName);pluginList.add(writerPluginName);if(StringUtils.isNotEmpty(preHandlerName)) {pluginList.add(preHandlerName);}if(StringUtils.isNotEmpty(postHandlerName)) {pluginList.add(postHandlerName);}try {//加载所有的插件配置文件,我们暂时只有reader和writer插件,即mysqlReader和mysqlWriter,将配置文件合并configuration.merge(parsePluginConfig(new ArrayList<String>(pluginList)), false);}catch (Exception e){//吞掉异常,保持log干净。这里message足够。LOG.warn(String.format("插件[%s,%s]加载失败,1s后重试... Exception:%s ", readerPluginName, writerPluginName, e.getMessage()));try {Thread.sleep(1000);} catch (InterruptedException e1) {//}configuration.merge(parsePluginConfig(new ArrayList<String>(pluginList)), false);}return configuration;}
到此为止我们的前期最关键的一个步骤就完成了,解析所有需要的配置项json
,最终我们加载出来的配置文件格式如下:
{"internal": {"common": {"column": {"dateFormat": "yyyy-MM-dd","datetimeFormat": "yyyy-MM-dd HH:mm:ss","encoding": "utf-8","extraFormats": ["yyyyMMdd"],"timeFormat": "HH:mm:ss","timeZone": "GMT+8"}},"core": {"container": {"job": {"id": -1,"mode": "standalone","reportInterval": 10000},"taskGroup": {"channel": 5},"trace": {"enable": "false"}},"dataXServer": {"address": "http://localhost:7001/api","reportDataxLog": false,"reportPerfLog": false,"timeout": 10000},"statistics": {"collector": {"plugin": {"maxDirtyNumber": 10,"taskClass": "com.alibaba.datax.core.statistics.plugin.task.StdoutPluginCollector"}}},"transport": {"channel": {"byteCapacity": 67108864,"capacity": 512,"class": "com.alibaba.datax.core.transport.channel.memory.MemoryChannel","flowControlInterval": 20,"speed": {"byte": -1,"record": -1}},"exchanger": {"bufferSize": 32,"class": "com.alibaba.datax.core.plugin.BufferedRecordExchanger"}}},"entry": {"jvm": "-Xms1G -Xmx1G"},//mysql2mysql.json当中的配置都在此项当中"job": {"content": [{"reader": {"name": "mysqlreader","parameter": {"column": ["id","name","age"],"connection": [{"jdbcUrl": ["jdbc:mysql://localhost:3306/database?useSSL=false"],"table": ["stu_copy1"]}],"password": "123456","username": "root"}},"writer": {"name": "mysqlwriter","parameter": {"column": ["id","name","age"],"connection": [{"jdbcUrl": "jdbc:mysql://localhost:3306/database?useSSL=false&useUnicode=true&characterEncoding=utf8","table": ["stu_copy2"]}],"password": "123456","username": "root","writeMode": "replace"}}}],"setting": {"speed": {"channel": 5}}},//reader/writer配置都在此项当中。"plugin": {"reader": {"mysqlreader": {"class": "com.alibaba.datax.plugin.reader.mysqlreader.MysqlReader","description": "useScene: prod. mechanism: Jdbc connection using the database, execute select sql, retrieve data from the ResultSet. warn: The more you know about the database, the less problems you encounter.","developer": "alibaba","name": "mysqlreader","path": "D:\\chrom下载\\datax\\datax\\plugin\\reader\\mysqlreader"}},"writer": {"mysqlwriter": {"class": "com.alibaba.datax.plugin.writer.mysqlwriter.MysqlWriter","description": "useScene: prod. mechanism: Jdbc connection using the database, execute insert sql. warn: The more you know about the database, the less problems you encounter.","developer": "alibaba","name": "mysqlwriter","path": "D:\\chrom下载\\datax\\datax\\plugin\\writer\\mysqlwriter"}}}}
}
二、根据加载的配置文件进行调度
- 默认使用JobContainer进行任务调度,AbstractContainer 实现类有两个,一个是JobContainer。另外一个是TaskGroupContainer。对于它们两个的关系,JobContainer: Job执行器,负责Job全局拆分、调度、前置语句和后置语句等工作的工作单元。类似Yarn中的JobTracker
TaskGroupContainer: TaskGroup执行器,负责执行一组Task的工作单元,类似Yarn中的TaskTracker。简单理解就是JobContainer负责任务调度,TaskGroupContainer负责执行。在源代码当中也有体现,在使用JobContainer调度后最终还是使用TaskGroupContainer进行执行。
public void start(Configuration allConf) {// 绑定column转换信息ColumnCast.bind(allConf);/*** 初始化PluginLoader,可以获取各种插件配置*/LoadUtil.bind(allConf);//我们位配置任务组的配置信息,isJob为tureboolean isJob = !("taskGroup".equalsIgnoreCase(allConf.getString(CoreConstant.DATAX_CORE_CONTAINER_MODEL)));//JobContainer会在schedule后再行进行设置和调整值int channelNumber =0;AbstractContainer container;long instanceId;int taskGroupId = -1;if (isJob) {//创建一个JobContainer,将配置信息都设置到JobContainer中,//后续使用container执行allConf.set(CoreConstant.DATAX_CORE_CONTAINER_JOB_MODE, RUNTIME_MODE);container = new JobContainer(allConf);instanceId = allConf.getLong(CoreConstant.DATAX_CORE_CONTAINER_JOB_ID, 0);} else {container = new TaskGroupContainer(allConf);instanceId = allConf.getLong(CoreConstant.DATAX_CORE_CONTAINER_JOB_ID);taskGroupId = allConf.getInt(CoreConstant.DATAX_CORE_CONTAINER_TASKGROUP_ID);channelNumber = allConf.getInt(CoreConstant.DATAX_CORE_CONTAINER_TASKGROUP_CHANNEL);}//缺省打开perfTraceboolean traceEnable = allConf.getBool(CoreConstant.DATAX_CORE_CONTAINER_TRACE_ENABLE, true);boolean perfReportEnable = allConf.getBool(CoreConstant.DATAX_CORE_REPORT_DATAX_PERFLOG, true);//standalone模式的 datax shell任务不进行汇报if(instanceId == -1){perfReportEnable = false;}int priority = 0;try {priority = Integer.parseInt(System.getenv("SKYNET_PRIORITY"));}catch (NumberFormatException e){LOG.warn("prioriy set to 0, because NumberFormatException, the value is: "+System.getProperty("PROIORY"));}Configuration jobInfoConfig = allConf.getConfiguration(CoreConstant.DATAX_JOB_JOBINFO);//初始化PerfTracePerfTrace perfTrace = PerfTrace.getInstance(isJob, instanceId, taskGroupId, priority, traceEnable);perfTrace.setJobInfo(jobInfoConfig,perfReportEnable,channelNumber);//最终的实际执行的逻辑container.start();}
- 执行任务的调度,JobContainer.start(),最终会通过JobContainer.schedule()进行任务的调度处理。
public void start() {LOG.info("DataX jobContainer starts job.");boolean hasException = false;boolean isDryRun = false;try {this.startTimeStamp = System.currentTimeMillis();isDryRun = configuration.getBool(CoreConstant.DATAX_JOB_SETTING_DRYRUN, false);//模拟运行,不进行实际的数据的读取和写出。只是进行数据的预校验。该配置在属性job当中,//即我们配置mysql2mysql.json当中,默认我们未配置isDryRun为false。if(isDryRun) {LOG.info("jobContainer starts to do preCheck ...");this.preCheck();} else {userConf = configuration.clone();LOG.debug("jobContainer starts to do preHandle ...");//前置处理preHandler,实现AbstractPlugin接口,//最终调用AbstractPlugin.preHandler()方法this.preHandle();LOG.debug("jobContainer starts to do init ...");//初始化JobContainer的reader/writer属性,调用实际引用的//Reader.Job.init()和Writer.Job.init()方法this.init();LOG.info("jobContainer starts to do prepare ...");//调用对应的reader/writer的prepare前置处理方法this.prepare();LOG.info("jobContainer starts to do split ...");//重新设置reader/writer的配置,当我们配置了多个任务时,即//job.content设置多项时会出现,博主测下来只有第一个任务可以执行//后续的不会执行,就算每个配置项的reader/writer配置相同,不清楚是不是bug。this.totalStage = this.split();LOG.info("jobContainer starts to do schedule ...");//实际调用调度处理的逻辑,该逻辑是代码当中最核心的逻辑this.schedule();LOG.debug("jobContainer starts to do post ...");//调用对应的reader/writer的post后置处理方法this.post();LOG.debug("jobContainer starts to do postHandle ...");//后置处理postHandle,实现AbstractPlugin接口,//最终调用AbstractPlugin.postHandler()方法this.postHandle();LOG.info("DataX jobId [{}] completed successfully.", this.jobId);//调用外部hookthis.invokeHooks();}} catch (Throwable e) {//异常信息处理,此处省略。。。} finally {//如果实际执行调度,则打印统计信息if(!isDryRun) {this.destroy();this.endTimeStamp = System.currentTimeMillis();if (!hasException) {//最后打印cpu的平均消耗,GC的统计VMInfo vmInfo = VMInfo.getVmInfo();if (vmInfo != null) {vmInfo.getDelta(false);LOG.info(vmInfo.totalString());}LOG.info(PerfTrace.getInstance().summarizeNoException());this.logStatistics();}}}}
- 任务调度处理JobContainer.schedule(),起内部调用了StandAloneScheduler.schedule(taskGroupConfigs)进行实际的调度处理。
public void schedule(List<Configuration> configurations) {//省略部分代码。。。//关键点,根据配置信息执行任务调度分配startAllTaskGroup(configurations);//省略部分代码。。。}
- 通过线程池进行任务的调度处理,ProcessInnerScheduler.startAllTaskGroup。在实际调用当中我们可以发现最终使用的是TaskGroupContainer进行的处理,在job执行的配置文件,我们可以选择通过TaskGroupContainer直接执行还是通过JobContainer进行调度执行,
如果core.container.model为taskGroup就直接通过TaskGroupContainer执行,不会进行前置插件后置插件等逻辑的处理。
public void startAllTaskGroup(List<Configuration> configurations) {this.taskGroupContainerExecutorService = Executors.newFixedThreadPool(configurations.size());for (Configuration taskGroupConfiguration : configurations) {//TaskGroupContainer taskGroupContainer = new // TaskGroupContainer(configuration);//TaskGroupContainer就是实际执行的调用的执行组件,最开始我们执行的JobContainer//我们可以根据mysql2mysql.json的配置直接执行,也可以通过JobContainer进行调度。return new TaskGroupContainerRunner(taskGroupContainer);TaskGroupContainerRunner taskGroupContainerRunner = newTaskGroupContainerRunner(taskGroupConfiguration);//使用线程池进行并行处理调用this.taskGroupContainerExecutorService.execute(taskGroupContainerRunner);}this.taskGroupContainerExecutorService.shutdown();}
三、根据配置文件执行读取写入任务
- TaskGroupContainerRunner封装了TaskGroupContainer,TaskGroupContainerRunner实现了Runnable接口,当被线程池调用时内部实际调用了TaskGroupContainer.start()方法进行处理。
public void start() {try {//省略一部分代码。。。while (true) {//1.判断task状态,省略。。。// 2.发现该taskGroup下taskExecutor的总状态失败则汇报错误,省略代码。。。 //3.有任务未执行,且正在运行的任务数小于最大通道限制Iterator<Configuration> iterator = taskQueue.iterator();while(iterator.hasNext() && runTasks.size() < channelNumber){//省略部分代码。。。Configuration taskConfigForRun = taskMaxRetryTimes > 1 ? taskConfig.clone() : taskConfig;TaskExecutor taskExecutor = new TaskExecutor(taskConfigForRun, attemptCount);taskStartTimeMap.put(taskId, System.currentTimeMillis());//代码最关键的地方。此处就是实际调用reader和writer的入口,//我们着重分析这个代码。taskExecutor.doStart();iterator.remove();runTasks.add(taskExecutor);//上面,增加task到runTasks列表,因此在monitor里注册。taskMonitor.registerTask(taskId, this.containerCommunicator.getCommunication(taskId));taskFailedExecutorMap.remove(taskId);LOG.info("taskGroup[{}] taskId[{}] attemptCount[{}] is started",this.taskGroupId, taskId, attemptCount);}//4.任务列表为空,executor已结束, 搜集状态为success--->成功//5.如果当前时间已经超出汇报时间的interval,那么我们需要马上汇报Thread.sleep(sleepIntervalInMillSec);}//6.最后还要汇报一次reportTaskGroupCommunication(lastTaskGroupContainerCommunication, taskCountInThisTaskGroup);} catch (Throwable e) {//省略。。。}}
- 通过taskExecutor.doStart()执行数据的同步,该类当中有几个和重要的属性,
taskConfig:job.content的内容、readerThread:读操作线程、writerThread:写操作线程、readerRunner:读操作的实际执行对象,它也为Runnable接口实现、writerRunner:写操作的实际执行对象,它也为Runnable接口实现。
public void doStart() {//1.先启动写线程,执行WriterRunner.run()方法this.writerThread.start();if (!this.writerThread.isAlive() || this.taskCommunication.getState() == State.FAILED) {throw DataXException.asDataXException(FrameworkErrorCode.RUNTIME_ERROR,this.taskCommunication.getThrowable());}//2.后启动读线程,执行ReaderRunner.run()方法this.readerThread.start();if (!this.readerThread.isAlive() && this.taskCommunication.getState() == State.FAILED) {throw DataXException.asDataXException(FrameworkErrorCode.RUNTIME_ERROR,this.taskCommunication.getThrowable());}}
- 读操作是通过执行读线程readerThread执行ReaderRunner.run方法,最终会调用到我们自定义的reader实现类,即MysqlReader执行startRead方法。
public void startRead(RecordSender recordSender) {int fetchSize = this.readerSliceConfig.getInt(Constant.FETCH_SIZE);//这里会执行真正的查询操作this.commonRdbmsReaderTask.startRead(this.readerSliceConfig, recordSender,super.getTaskPluginCollector(), fetchSize);}
- 读取操作最终对数据库进行读取操作时会调用CommonRdbmsReader.startRead方法。
public void startRead(Configuration readerSliceConfig,RecordSender recordSender,TaskPluginCollector taskPluginCollector, int fetchSize) {//需要读取的数据的sql String querySql = readerSliceConfig.getString(Key.QUERY_SQL);//需要读取的数据表String table = readerSliceConfig.getString(Key.TABLE);PerfTrace.getInstance().addTaskDetails(taskId, table + "," + basicMsg);LOG.info("Begin to read record by Sql: [{}\n] {}.",querySql, basicMsg);PerfRecord queryPerfRecord = new PerfRecord(taskGroupId,taskId, PerfRecord.PHASE.SQL_QUERY);queryPerfRecord.start();//获取数据库连接Connection conn = DBUtil.getConnection(this.dataBaseType, jdbcUrl,username, password);DBUtil.dealWithSessionConfig(conn, readerSliceConfig,this.dataBaseType, basicMsg);int columnNumber = 0;ResultSet rs = null;try {//获取所有的查询结果列表数据。rs = DBUtil.query(conn, querySql, fetchSize);queryPerfRecord.end();ResultSetMetaData metaData = rs.getMetaData();columnNumber = metaData.getColumnCount();PerfRecord allResultPerfRecord = new PerfRecord(taskGroupId, taskId, PerfRecord.PHASE.RESULT_NEXT_ALL);allResultPerfRecord.start();long rsNextUsedTime = 0;long lastTime = System.nanoTime();while (rs.next()) {rsNextUsedTime += (System.nanoTime() - lastTime);//将读取的到的所有的数据都通过recordSender传递给writer,默认使用的是//BufferedRecordExchanger,将读取到的数据写入到缓冲区当中。this.transportOneRecord(recordSender, rs,metaData, columnNumber, mandatoryEncoding, taskPluginCollector);lastTime = System.nanoTime();}allResultPerfRecord.end(rsNextUsedTime);LOG.info("Finished read record by Sql: [{}\n] {}.",querySql, basicMsg);}catch (Exception e) {throw RdbmsException.asQueryException(this.dataBaseType, e, querySql, table, username);} finally {DBUtil.closeDBResources(null, conn);}}
- WriterRunner通过读取到缓冲区BufferedRecordExchanger的数据后,交由具体的writer进行处理,我们示例使用的是Mysqlwriter,最终调用Mysqlwriter.startWrite()方法处理缓冲区的数据。
public void startWrite(RecordReceiver recordReceiver) {//调用CommonRdbmsWriter进行处理收到的缓冲区数据this.commonRdbmsWriterTask.startWrite(recordReceiver, this.writerSliceConfig,super.getTaskPluginCollector());}
- 通过CommonRdbmsWriter将读取到的缓冲区的数据进行处理写入到配置的数据库当中。
public void startWrite(RecordReceiver recordReceiver,Configuration writerSliceConfig,TaskPluginCollector taskPluginCollector) {//获取连接Connection connection = DBUtil.getConnection(this.dataBaseType,this.jdbcUrl, username, password);DBUtil.dealWithSessionConfig(connection, writerSliceConfig,this.dataBaseType, BASIC_MESSAGE);//处理数据startWriteWithConnection(recordReceiver, taskPluginCollector, connection);}
public void startWriteWithConnection(RecordReceiver recordReceiver, TaskPluginCollector taskPluginCollector, Connection connection) {this.taskPluginCollector = taskPluginCollector;// 用于写入数据的时候的类型根据目的表字段类型转换this.resultSetMetaData = DBUtil.getColumnMetaData(connection,this.table, StringUtils.join(this.columns, ","));// 写数据库的SQL语句calcWriteRecordSql();List<Record> writeBuffer = new ArrayList<Record>(this.batchSize);int bufferBytes = 0;try {Record record;while ((record = recordReceiver.getFromReader()) != null) {if (record.getColumnNumber() != this.columnNumber) {// 源头读取字段列数与目的表字段写入列数不相等,直接报错throw DataXException.asDataXException(DBUtilErrorCode.CONF_ERROR,String.format("列配置信息有错误. 因为您配置的任务中,源头读取字段数:%s 与 目的表要写入的字段数:%s 不相等. 请检查您的配置并作出修改.",record.getColumnNumber(),this.columnNumber));}writeBuffer.add(record);bufferBytes += record.getMemorySize();if (writeBuffer.size() >= batchSize || bufferBytes >= batchByteSize) {doBatchInsert(connection, writeBuffer);writeBuffer.clear();bufferBytes = 0;}}if (!writeBuffer.isEmpty()) {doBatchInsert(connection, writeBuffer);writeBuffer.clear();bufferBytes = 0;}} catch (Exception e) {throw DataXException.asDataXException(DBUtilErrorCode.WRITE_DATA_ERROR, e);} finally {writeBuffer.clear();bufferBytes = 0;DBUtil.closeDBResources(null, null, connection);}}
总结
datax的整体原理就是,根据我们配置的文件,加载不同的reader和writer,reader通过配置的单独的read线程将源数据库数据读取出来写入到缓冲区当中,writer通过线程将缓冲区的数据读取出来写入到目标数据库当中。
我们在实际的工作当中可能更需要的关注对于这个reader和writer的扩展,对于datax都已经提供了plugin生命周期的函数处理,前置处理后置处理等函数,如果我们需要自定义数据库的reader和writer逻辑时,就需要自定义Reader.Task的startRead接口,以及Writer.Task的startWrite接口去自定对于缓冲区数据的处理逻辑。
相关文章:
datax源码分析
文章目录 前言一、加载配置文件二、根据加载的配置文件进行调度三、根据配置文件执行读取写入任务总结 前言 在上一篇文章当中我们已经了解了datax的启动原理,以及datax的最基础的配置,datax底层java启动类的入口及关键参数。 接下来我将进行启动类执行…...
【HDLbits--分支预测器简单实现】
HDLbits--分支预测器简单实现 1 timer2.branche predicitors3.Branch history shift4.Branch direction predictor 以下是分支预测器的简单其实现; 1 timer 实现一个计时器,当load1’b1时,加载data进去,当load1’b0时进行倒计时&…...
优化Go错误码管理:构建清晰、优雅的HTTP和gRPC错误码规范
在系统开发过程中,如何优雅地管理错误信息一直是个棘手问题。传统的错误处理方式往往存在不统一、难以维护等缺点。而 errcode 模块通过对错误码进行规范化管理,为系统级和业务级错误提供了统一的编码标准。本文将带您深入了解 errcode 的设计原理、错误…...
批量压缩与优化 Excel 文档,减少 Excel 文档大小
当我们在 Excel 文档中插入图片资源的时候,如果我们插入的是原图,可能会导致 Excel 变得非常的大。这非常不利于我们传输或者共享。那么当我们的 Excel 文件非常大的时候,我们就需要对文档做一些压缩或者优化的处理。那有没有什么方法可以实现…...
MongoDB分页实现方式对比:PageRequest vs Skip/Limit
MongoDB分页实现方式对比:PageRequest vs Skip/Limit 一、基本概念1.1 PageRequest分页1.2 Skip/Limit分页 二、主要区别2.1 使用方式2.2 参数计算2.3 适用场景PageRequest适用场景:Skip/Limit适用场景: 三、性能考虑3.1 PageRequest的性能特…...
SAP Commerce(Hybris)营销模块(一):商城产品折扣配置
基于Hybris的Backoffice后台管理系统,创建一个基于模板的营销规则,并配置上对应的优惠活动。 架构设计 先从一张架构图说起 Hybris的促销模块,是基于Promotion引擎来实现的,可以通过Backoffice来进行配置。 通过上面的架构图又可…...
如何在 React 中实现错误边界?
在 React 中实现错误边界 错误边界是 React 提供的一种机制,用于捕获子组件树中的 JavaScript 错误,并展示回退 UI。它可以帮助开发者更好地处理错误,提升用户体验。本文将详细介绍如何在 React 中实现错误边界,包括其工作原理、…...
从头开始开发基于虹软SDK的人脸识别考勤系统(python+RTSP开源)(五)完整源码已上传!
本篇是对照之前代码剩余的部分代码做补充,分享给大家,便于对照运行测试。 完整版的全功能单文件版本已上传!https://download.csdn.net/download/xiaomage_cn/90484179 人脸识别抽象层,这个大家应该都知道,就是为了方…...
PySide(PyQT)的mouseMoveEvent()和hoverMoveEvent()的区别
在 PySide中,mouseMoveEvent 和 hoverMoveEvent 都是用于处理鼠标移动相关操作的事件,但它们之间存在明显的区别: 事件触发条件 • mouseMoveEvent: 当鼠标在对应的图形项(如 QGraphicsPixmapItem)…...
【通缩螺旋的深度解析与科技破局路径】
通缩螺旋的深度解析与科技破局路径 一、通缩螺旋的形成机制与恶性循环 通缩螺旋(Deflationary Spiral)是经济学中描述价格持续下跌与经济衰退相互强化的动态过程,其核心逻辑可拆解为以下链条: 需求端萎缩:居民消费信…...
【如何使用云服务器与API搭建专属聊天系统:宝塔面板 + Openwebui 完整教程】
文章目录 不挑电脑、不用技术,云服务器 API 轻松搭建专属聊天系统,对接 200 模型,数据全在自己服务器,安全超高一、前置准备:3 分钟快速上手指南云服务器准备相关账号注册 二、手把手部署教程(含代码块&a…...
Oracle数据库存储结构--逻辑存储结构
数据库存储结构:分为物理存储结构和逻辑存储结构。 物理存储结构:操作系统层面如何组织和管理数据 逻辑存储结构:Oracle数据库内部数据组织和管理数据,数据库管理系统层面如何组织和管理数据 Oracle逻辑存储结构 数据库的逻…...
C++ 左值(lvalue)和右值(rvalue)
在 C 中,左值(lvalue)和右值(rvalue)是指对象的不同类别,区分它们对于理解 C 中的表达式求值和资源管理非常重要,尤其在现代 C 中涉及到移动语义(Move Semantics)和完美转…...
《实战AI智能体》DeepSearcher 的架构设计
DeepSearcher 的架构设计 一个通往搜索AGI的Agentic RAG应该如何设计? 从架构上看,DeepSearcher 主要分为两大模块。 一个是数据接入模块,通过Milvus向量数据库来接入各种第三方的私有知识。这也是DeepSearcher相比OpenAI的原本DeepResearc…...
Kotlin 继承
Kotlin 继承 概述 Kotlin 是一种现代的编程语言,它具有简洁、安全、互操作性等特点。在面向对象编程中,继承是一种非常重要的特性,它允许我们创建具有共同属性和方法的类。本文将详细介绍 Kotlin 中的继承机制,包括继承的基本概…...
【6】树状数组学习笔记
前言 树状数组是我学的第一个高级数据结构,属于 log \log log 级数据结构。 其实现在一般不会单独考察数据结构,主要是其在其他算法(如贪心,DP)中起到优化作用。 长文警告:本文一共 995 995 995 行…...
【RISCV LAB】0x01-安装实验仿真辅助工具
安装实验辅助工具 实验环境搭建安装 Verilator编译依赖下载源码编译安装测试安装 安装 RISC-V 交叉编译工具链编译依赖下载源码编译安装编译并安装添加环境变量并测试 安装 GTKWave其他模拟器推荐RARSemulsiV FAQ 实验环境搭建 Verilator 是一款开源的支持 Verilog 和 SystemV…...
OSPF-2 邻接建立关系
上一期我们说了OSPF的邻居建立关系以及OSPF邻居关系建立中建立失败的因素以及相关实验案例 这一期我们来说说OSPF的邻接关系建立时需要交互哪些报文以及失败因素及原因和相关实验案例 一、概述 在运行了OSPF的网络当中为了交互链路状态信息和路由信息,互相之间需要建立邻接关…...
操作系统知识点29
1.当用户使用外部设备时,其控制设备的命令传递途径依次为用户应用层->设备独立层->设备驱动层->设备硬件 2.通常用于管理空闲物理内存的方法:空闲快链表法;位示图法;空闲页面表 3. 可用于文件的存取控制和保护的方法&a…...
【Java篇】行云流水,似风分岔:编程结构中的自然法则
文章目录 Java 程序逻辑控制:顺序、分支与循环结构全面解析一、顺序结构二、分支结构2.1 if 语句2.1.1 基本语法2.1.2 if-else 语句2.1.3 if-else if-else 语句 2.2 switch 语句 三、循环结构3.1 while 循环3.2 break 语句3.3 continue 语句3.4 for 循环 四、输入输…...
代码块与设计模式
文章目录 1.代码块1.1基本介绍基本语法 1.2代码块的好处和案例演示1.3代码块使用注意事项和细节讨论!!! 2.单例设计模式2.1什么是设计模式2.2什么是单例模式2.2.1饿汉式2.2.2懒汉式2.2.3比较 1.代码块 1.1基本介绍 代码化块又称为初始化块,属于类中的成员[即是类的一部分]&am…...
要登录的设备ip未知时的处理方法
目录 1 应用场景... 1 2 解决方法:... 1 2.1 wireshark设置... 1 2.2 获取网口mac地址,wireshark抓包前预过滤掉自身mac地址的影响。... 2 2.3 pc网口和设备对接... 3 2.3.1 情况1:... 3 2.3.2 情…...
CentOS 系统安装 docker 以及常用插件
博主用的的是WindTerm软件链接的服务器,因为好用 1.链接上服务器登入后,在/root/目录下 2.执行以下命令安装docker sudo yum install -y yum-utilssudo yum-config-manager \--add-repo \https://download.docker.com/linux/centos/docker-ce.reposudo…...
统计字符(字符串)(gets与fgets的区别)
统计字符 #include<stdio.h> #include<string.h> int main(){char str1[5],str2[80];while(gets(str1)){if(strcmp(str1,"#")0)break;gets(str2);for(int i0;i<strlen(str1);i){int sum0;for(int j0;j<strlen(str2);j){if(str1[i]str2[j])sum;}p…...
Node.js REPL 深入解析
Node.js REPL 深入解析 引言 Node.js 作为一种流行的 JavaScript 运行环境,在服务器端开发中扮演着重要角色。REPL(Read-Eval-Print Loop,读取-求值-打印循环)是 Node.js 的一个核心特性,它允许开发者在一个交互式环境中执行 JavaScript 代码。本文将深入探讨 Node.js R…...
【测试语言基础篇】Python基础之List列表
一、Python 列表(List) 序列是Python中最基本的数据结构。序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推。 Python有6个序列的内置类型,但最常见的是列表和元组。序列都可…...
中山六院团队发表可解释多模态融合模型Brim,可以在缺少分子数据时借助病理图像模拟生成伪基因组特征|顶刊解读·25-02-14
小罗碎碎念 在癌症诊疗领域,精准预测患者预后对临床决策意义重大。传统的癌症分期系统,如TNM分期,因无法充分考量肿瘤异质性,难以准确预测患者的临床结局。而基于人工智能的多模态融合模型虽有潜力,但在实际临床应用中…...
《基於Python的网络爬虫抓包技术研究与应用》
## 摘要 本文探讨了基于Python的网络爬虫抓包技术及其应用。随着互联网数据的快速增长,网络爬虫技术在数据采集和分析中扮演着越来越重要的角色。本研究首先介绍了网络爬虫的基本概念和Python在爬虫开发中的优势,然后深入分析了抓包技术的原理和常用工具…...
从零开始探索C++游戏开发:性能、控制与无限可能
一、为何选择C开发游戏? 在虚幻引擎5渲染的次世代画面背后,在《巫师3》的庞大开放世界中,在《毁灭战士》的丝滑60帧战斗里,C始终扮演着核心技术角色。这门诞生于1983年的语言,至今仍占据着游戏引擎开发语言使用率榜首…...
TypeScript 高级类型 vs JavaScript:用“杂交水稻”理解类型编程
如果把 JavaScript 比作乐高积木,TypeScript 就是一套智能积木系统。本文将用最生活化的比喻,带你理解 TypeScript 那些看似复杂的高级类型。 一、先看痛点:JavaScript 的“薛定谔类型” // 场景:用户信息处理 function getUserI…...
几款可用于绘制工艺原理图的开源框架
一、LogicFlow 由滴滴团队开发的开源流程图框架,支持高度定制的工艺原理图绘制。 • 核心特性: • 提供拖拽式界面和丰富的节点类型(矩形、圆形、多边形等),支持自定义节点形状、样式和交互逻辑。 • 支持插件扩展&am…...
STM32如何精准控制步进电机?
在工业自动化、机器人控制等场合,步进电机以其高精度、开环控制的特性得到了广泛应用。而在嵌入式系统中,使用STM32进行步进电机的精确控制,已成为开发者的首选方案之一。 本文将从嵌入式开发者的角度,深入探讨如何基于STM32 MCU…...
Go语言入门基础详解
一、语言历史背景 Go语言由Google工程师Robert Griesemer、Rob Pike和Ken Thompson于2007年设计,2009年正式开源。设计目标: 兼具Python的开发效率与C的执行性能内置并发支持(goroutine/channel)简洁的类型系统现代化的包管理跨…...
WPF窗口读取、显示、修改、另存excel文件——CAD c#二次开发
效果如下: using System.Data; using System.IO; using System.Windows; using Microsoft.Win32; using ExcelDataReader; using System.Text; using ClosedXML.Excel;namespace IfoxDemo {public partial class SimpleWindow : Window{public SimpleWindow(){Initi…...
Ubuntu 服务器安装 Python 环境 的详细指南
以下是 在 Ubuntu 上安装 Python 3.10 的详细步骤(兼容 Ubuntu 20.04/22.04): 方法一:通过 PPA 仓库安装(推荐) 1. 添加 deadsnakes PPA sudo apt update sudo apt install software-properties-common s…...
鸿蒙next 多行文字加图片后缀实现方案
需求 实现类似iOS的YYLabel之类的在文字后面加上图片作为后缀的样式,多行时文字使用…省略超出部分,但必须保证图片的展现。 系统方案 在当前鸿蒙next系统提供的文字排版方法基本没有合适使用的接口,包括imagespan和RichEditor,根据AI的回…...
STM32---FreeRTS队列集
一、简介 一个队列只允许任务间传递的消息为同一种数据类型,如果需要在任务间传递不同数据类型的消息时,那么就可以使用队列集 ! 作用:用于对多个队列或信号量进行“监听”,其中不管哪一个消息到来,都可让…...
oracle11.2.0.4 RAC 保姆级静默安装(二) DB数据库软件
1.响应文件配置 [rootdb11g1 software]# su - oracle [oracledb11g1 ~]$ cd /software/database/ [oracledb11g1 database]$ cd response/ [oracledb11g1 response]$ vi db_install.rsp oracle.install.optionINSTALL_DB_SWONLY ORACLE_HOSTNAMEdb11g1 UNIX_GROUP_NAME…...
用python代码将excel中的数据批量写入Json中的某个字段,生成新的Json文件
需求 需求: 1.将execl文件中的A列赋值给json中的TrackId,B列赋值给json中的OId 要求 execl的每一行,对应json中的每一个OId json 如下: {"List": [{"BatchNumber": "181-{{var}}",// "Bat…...
【每日学点HarmonyOS Next知识】状态变量、动画UI残留、Tab控件显示、ob前缀问题、文字背景拉伸
1、HarmonyOS 怎么用一个变量观察其他很多个变量的变化? 有一个提交按钮的颜色,需要很多个值非空才变为红色,否则变为灰色,可不可以用一个变量统一观察这很多个值,去判断按钮该显示什么颜色,比如Button().…...
【第五节】windows sdk编程:windows 控件基础
目录 一、控件概述 二、标准控件 三、通用控件 四、控件的创建 五、控件风格 六、控件相关的消息 6.1 控件控制消息 6.2 控件通知消息 一、控件概述 控件是 Windows 系统内置的窗口类,它们只能是某个窗口的子窗口。因此,创建控件时必须使用 WS_C…...
架构师论文《论云原生架构及其应用》
【摘要】 2022年3月,我作为系统架构师参与了某大型零售企业“智能化供应链管理平台”项目的设计与实施工作。该平台旨在整合企业分散在不同区域的仓储、物流、库存及订单系统,构建统一管理的云原生架构,以应对业务季节性峰值带来的弹性伸缩需…...
Centos 7 安装达梦数据库
一、环境准备 1. 确认操作系统的版本和数据库的版本是否一致 cat /etc/redhat-release 2. 关闭防火墙 查看防火墙状态 firewall-cmd --state 停止firewall systemctl stop firewalld.service 禁止firewall开机启动 systemctl disable firewalld.service 3. 修改文件l…...
46.全排列
46.全排列 力扣题目链接 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1: 输入:nums [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例 2:…...
RabbitMQ (Java)学习笔记
目录 一、概述 ①核心组件 ②工作原理 ③优势 ④应用场景 二、入门 1、docker 安装 MQ 2、Spring AMQP 3、代码实现 pom 依赖 配置RabbitMQ服务端信息 发送消息 接收消息 三、基础 work Queue 案例 消费者消息推送限制(解决消息堆积方案之一&#…...
2-002:MySQL 索引的最左前缀匹配原则是什么?
MySQL 索引的最左前缀匹配原则 最左前缀匹配原则(Leftmost Prefix Matching) 是指: 当 查询使用了复合索引(联合索引) 时,MySQL 会优先匹配索引的 最左列,然后逐步向右匹配,直到遇到…...
【Python 数据结构 15.哈希表】
目录 一、哈希表的基本概念 1.哈希表的概念 2.键值对的概念 3.哈希函数的概念 4.哈希冲突的概念 5.常用的哈希函数 Ⅰ、直接定址法 Ⅱ、平方取中法 Ⅲ、折叠法 Ⅳ、除留余数法 Ⅴ、位与法 6.哈希冲突的解决方案 Ⅰ、开放定址法 Ⅱ、链地址法 7.哈希表的初始化 8.哈希表的元素插…...
校园安全用电怎么保障?防触电装置来帮您
引言 随着教育设施的不断升级和校园用电需求的日益增长,校园电力系统的安全性和可靠性成为了学校管理的重要课题。三相智能安全配电装置作为一种电力管理设备,其在校园中的应用不仅能够提高电力系统的安全性,还能有效保障师生的用电安全&am…...
疗养院管理系统设计与实现(代码+数据库+LW)
摘 要 传统办法管理信息首先需要花费的时间比较多,其次数据出错率比较高,而且对错误的数据进行更改也比较困难,最后,检索数据费事费力。因此,在计算机上安装疗养院管理系统软件来发挥其高效地信息处理的作用…...
基于 Redis Stream 实现消息队列功能
好长时间没更新了。。。。。。 背景:举个例子在某个接口执行完成后只需要前半段返回结果,后半段可能是日志记录、下游系统调用等功能的情况下,将耗时的消息进行异步发送就显得很有必要,这时就有很多种选择,单体项目甚至…...