StarRocks分布式元数据源码解析

1. 支持元数据表

https://github.com/StarRocks/starrocks/pull/44276/files

核心类:LogicalIcebergMetadataTable,Iceberg元数据表,将元数据的各个字段做成表的列,后期可以通过sql操作从元数据获取字段,这个表的组成字段是DataFile相关的字段

public static LogicalIcebergMetadataTable create(String catalogName, String originDb, String originTable) {return new LogicalIcebergMetadataTable(catalogName,ConnectorTableId.CONNECTOR_ID_GENERATOR.getNextId().asInt(),ICEBERG_LOGICAL_METADATA_TABLE_NAME,Table.TableType.METADATA,builder().columns(PLACEHOLDER_COLUMNS).column("content", ScalarType.createType(PrimitiveType.INT)).column("file_path", ScalarType.createVarcharType()).column("file_format", ScalarType.createVarcharType()).column("spec_id", ScalarType.createType(PrimitiveType.INT)).column("partition_data", ScalarType.createType(PrimitiveType.VARBINARY)).column("record_count", ScalarType.createType(PrimitiveType.BIGINT)).column("file_size_in_bytes", ScalarType.createType(PrimitiveType.BIGINT)).column("split_offsets", ARRAY_BIGINT).column("sort_id", ScalarType.createType(PrimitiveType.INT)).column("equality_ids", ARRAY_INT).column("file_sequence_number", ScalarType.createType(PrimitiveType.BIGINT)).column("data_sequence_number", ScalarType.createType(PrimitiveType.BIGINT)).column("column_stats", ScalarType.createType(PrimitiveType.VARBINARY)).column("key_metadata", ScalarType.createType(PrimitiveType.VARBINARY)).build(),originDb,originTable,MetadataTableType.LOGICAL_ICEBERG_METADATA);
}

2. Iceberg表扫描

https://github.com/StarRocks/starrocks/pull/44313

核心类:StarRocksIcebergTableScan,扫描Iceberg表的实现类,基于Iceberg的上层接口实现,类似Iceberg默认提供的DataTableScan,doPlanFiles中定义实际的元数据文件扫描逻辑

这一块应当属于数据上层扫描逻辑

protected CloseableIterable<FileScanTask> doPlanFiles() {List<ManifestFile> dataManifests = findMatchingDataManifests(snapshot());List<ManifestFile> deleteManifests = findMatchingDeleteManifests(snapshot());boolean mayHaveEqualityDeletes = !deleteManifests.isEmpty() && mayHaveEqualityDeletes(snapshot());boolean loadColumnStats = mayHaveEqualityDeletes || shouldReturnColumnStats();if (shouldPlanLocally(dataManifests, loadColumnStats)) {return planFileTasksLocally(dataManifests, deleteManifests);} else {return planFileTasksRemotely(dataManifests, deleteManifests);}
}

3. Iceberg元数据信息接口

[Feature] Introduce meta spec interface by stephen-shelby · Pull Request #44527 · StarRocks/starrocks · GitHub

核心类:IcebergMetaSpec,Iceberg元数据描述,核心是RemoteMetaSplit的一个List,代表了元数据文件的列表,基于这个做分布式解析

这一块应当属于元数据文件的切片逻辑

public List<RemoteMetaSplit> getSplits() {return splits;
}

4. Iceberg元数据扫描节点

https://github.com/StarRocks/starrocks/pull/44581

核心类:IcebergMetadataScanNode,Iceberg元数据的扫描节点,袭乘自PlanNode类,主要是把上节的RemoteMetaSplit放到StarRocks的执行结构当中

这一块属于Iceberg逻辑向StarRocks逻辑的中间转换层

private void addSplitScanRangeLocations(RemoteMetaSplit split) {TScanRangeLocations scanRangeLocations = new TScanRangeLocations();THdfsScanRange hdfsScanRange = new THdfsScanRange();hdfsScanRange.setUse_iceberg_jni_metadata_reader(true);hdfsScanRange.setSerialized_split(split.getSerializeSplit());hdfsScanRange.setFile_length(split.length());hdfsScanRange.setLength(split.length());// for distributed schedulerhdfsScanRange.setFull_path(split.path());hdfsScanRange.setOffset(0);TScanRange scanRange = new TScanRange();scanRange.setHdfs_scan_range(hdfsScanRange);scanRangeLocations.setScan_range(scanRange);TScanRangeLocation scanRangeLocation = new TScanRangeLocation(new TNetworkAddress("-1", -1));scanRangeLocations.addToLocations(scanRangeLocation);result.add(scanRangeLocations);
}

5. Iceberg元数据读取

https://github.com/StarRocks/starrocks/pull/44632

核心类:IcebergMetadataScanner,这个应该是Iceberg元数据的实际读取类,实现自StarRocks的ConnectorScanner

ConnectorScanner是StarRocks的设计的介于C++-based的BE和Java-based的大数据组件之间的JNI抽象中间层,可以直接复用Java SDK,规避了对BE代码的侵入以及使用C++访问大数据存储的诸多不便

这一块属于时实际元数据文件读取的Java侧代码

image.png

public int getNext() throws IOException {try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) {int numRows = 0;for (; numRows < getTableSize(); numRows++) {if (!reader.hasNext()) {break;}ContentFile<?> file = reader.next();for (int i = 0; i < requiredFields.length; i++) {Object fieldData = get(requiredFields[i], file);if (fieldData == null) {appendData(i, null);} else {ColumnValue fieldValue = new IcebergMetadataColumnValue(fieldData);appendData(i, fieldValue);}}}return numRows;} catch (Exception e) {close();LOG.error("Failed to get the next off-heap table chunk of iceberg metadata.", e);throw new IOException("Failed to get the next off-heap table chunk of iceberg metadata.", e);}
}

    这一块目前没有找到Java侧的上层调用,应该在C++中调用,如下,其构造类是在C++中的

// ---------------iceberg metadata jni scanner------------------
std::unique_ptr<JniScanner> create_iceberg_metadata_jni_scanner(const JniScanner::CreateOptions& options) {const auto& scan_range = *(options.scan_range);;const auto* hdfs_table = dynamic_cast<const IcebergMetadataTableDescriptor*>(options.hive_table);std::map<std::string, std::string> jni_scanner_params;jni_scanner_params["required_fields"] = hdfs_table->get_hive_column_names();jni_scanner_params["metadata_column_types"] = hdfs_table->get_hive_column_types();jni_scanner_params["serialized_predicate"] = options.scan_node->serialized_predicate;jni_scanner_params["serialized_table"] = options.scan_node->serialized_table;jni_scanner_params["split_info"] = scan_range.serialized_split;jni_scanner_params["load_column_stats"] = options.scan_node->load_column_stats ? "true" : "false";const std::string scanner_factory_class = "com/starrocks/connector/iceberg/IcebergMetadataScannerFactory";return std::make_unique<JniScanner>(scanner_factory_class, jni_scanner_params);
}

6. 元数据收集任务

https://github.com/StarRocks/starrocks/pull/44679/files

核心类:IcebergMetadataCollectJob,Iceberg元数据的收集类,实现自MetadataCollectJob,目前看就是通过执行SQL语句,从前文的LogicalIcebergMetadataTable表当中获取数据

这一块属于最终的元数据收集

private static final String ICEBERG_METADATA_TEMPLATE = "SELECT content" + // INTEGER", file_path" + // VARCHAR", file_format" + // VARCHAR", spec_id" + // INTEGER", partition_data" + // BINARY", record_count" + // BIGINT", file_size_in_bytes" + // BIGINT", split_offsets" + // ARRAY<BIGINT>", sort_id" + // INTEGER", equality_ids" + // ARRAY<INTEGER>", file_sequence_number" + // BIGINT", data_sequence_number " + // BIGINT", column_stats " + // BINARY", key_metadata " + // BINARY"FROM `$catalogName`.`$dbName`.`$tableName$logical_iceberg_metadata` " +"FOR VERSION AS OF $snapshotId " +"WHERE $predicate'";

7. 流程梳理

image.png

1. IcebergMetadataCollectJob的调用

    IcebergMetadataCollectJob -> StarRocksIcebergTableScan.planFileTasksRemotely -> StarRocksIcebergTableScan.doPlanFiles -> 由Iceberg定义的TableScan流程触发

2. StarRocksIcebergTableScan的构建

    StarRocksIcebergTableScan -> IcebergCatalog.getTableScan -> IcebergMetadata.collectTableStatisticsAndCacheIcebergSplit -> prepareMetadata()和triggerIcebergPlanFilesIfNeeded()

    prepareMetadata()线路由PrepareCollectMetaTask任务触发,其执行逻辑中调用了prepareMetadata()接口。PrepareCollectMetaTask是OptimizerTask的子类,属于StarRocks优化器的一环,在Optimizer类执行优化的时候会。这一块属于CBO优化,默认是false,没找到设置成true的地方,目前应该没有启用

    triggerIcebergPlanFilesIfNeeded()路线有几个调用的地方,主路线应该是getRemoteFileInfos(),其他两个看内容属于统计信息之类的信息收集

    IcebergMetadata.getRemoteFileInfos -> IcebergScanNode.setupScanRangeLocations -> PlanFragmentBuilder.visitPhysicalIcebergScan -> PhysicalIcebergScanOperator

    这一条调用链最终源头到PhysicalIcebergScanOperator,这个应当是IcebergScanNode经过SQL计划转换后的实际执行节点类

3. 元数据扫描

    IcebergMetaSpec -> IcebergMetadata.getSerializedMetaSpec -> MetadataMgr.getSerializedMetaSpec -> IcebergMetadataScanNode.setupScanRangeLocations -> PlanFragmentBuilder.visitPhysicalIcebergMetadataScan -> PhysicalIcebergMetadataScanOperator

    元数据扫描这一块源头最终走到PhysicalIcebergMetadataScanOperator,也就是IcebergMetadataScanNode对应的执行类

4. 元数据扫描和数据扫描的逻辑关系

    目前整体流程在最上层就差PhysicalIcebergMetadataScanOperator和PhysicalIcebergScanOperator的逻辑关系,这个逻辑在StarRocks的SQL到执行计划的转换过程当中

    往上追踪到BackendSelectorFactory,注意这里有两个扫描节点的分配策略:LocalFragmentAssignmentStrategy、RemoteFragmentAssignmentStrategy。根据类的说明,最左节点为scanNode的时候,使用LocalFragmentAssignmentStrategy,它首先将扫描范围分配给 worker,然后将分配给每个 worker 的扫描范围分派给片段实例

    在LocalFragmentAssignmentStrategy的assignFragmentToWorker当中可以看到入参包含很多scanNode,追踪上层到CoordinatorPreprocessor,scanNode的来源是StarRocks的DAG图。这之后的源头就涉及到任务解析和DAG图的顺序构建,应当是先扫描元数据再扫描数据这样构建

for (ExecutionFragment execFragment : executionDAG.getFragmentsInPostorder()) {fragmentAssignmentStrategyFactory.create(execFragment, workerProvider).assignFragmentToWorker(execFragment);
}

8. 代码解析

1. 元数据扫描

  • LogicalIcebergMetadataTable

    首先从PhysicalIcebergMetadataScanOperator出发,访问者模式调用接口accept,走到PlanFragmentBuilder.visitPhysicalIcebergMetadataScan

    这里首先跟LogicalIcebergMetadataTable关联了起来,这里PhysicalIcebergMetadataScanOperator里包含的表是LogicalIcebergMetadataTable表

    LogicalIcebergMetadataTable的初始创建根据调用链追踪应当由CatalogMgr.createCatalog触发

PhysicalIcebergMetadataScanOperator node = (PhysicalIcebergMetadataScanOperator) optExpression.getOp();LogicalIcebergMetadataTable table = (LogicalIcebergMetadataTable) node.getTable();
  • IcebergMetadataScanNode

    中间经历一些列的设置,之后构建了IcebergMetadataScanNode

IcebergMetadataScanNode metadataScanNode =new IcebergMetadataScanNode(context.getNextNodeId(), tupleDescriptor,"IcebergMetadataScanNode", node.getTemporalClause());

    构建之后调用了setupScanRangeLocations,走到了IcebergMetadataScanNode的类逻辑,首先获取元数据文件的分片信息

IcebergMetaSpec serializedMetaSpec = GlobalStateMgr.getCurrentState().getMetadataMgr().getSerializedMetaSpec(catalogName, originDbName, originTableName, snapshotId, icebergPredicate).cast();
  • IcebergMetadata

    这段逻辑跟IcebergMetadata关联了起来,调用其getSerializedMetaSpec接口,接口中就是获取Iceberg的元数据文件,中间经历了一定的过滤

List<ManifestFile> dataManifests = snapshot.dataManifests(nativeTable.io());List<ManifestFile> matchingDataManifests = filterManifests(dataManifests, nativeTable, predicate);
for (ManifestFile file : matchingDataManifests) {remoteMetaSplits.add(IcebergMetaSplit.from(file));
}

    获取分片之后就是按StarRocks的扫描结构组装TScanRangeLocations,最终在实际执行时分布式分配解析

private void addSplitScanRangeLocations(RemoteMetaSplit split) {TScanRangeLocations scanRangeLocations = new TScanRangeLocations();THdfsScanRange hdfsScanRange = new THdfsScanRange();hdfsScanRange.setUse_iceberg_jni_metadata_reader(true);hdfsScanRange.setSerialized_split(split.getSerializeSplit());hdfsScanRange.setFile_length(split.length());hdfsScanRange.setLength(split.length());// for distributed schedulerhdfsScanRange.setFull_path(split.path());hdfsScanRange.setOffset(0);TScanRange scanRange = new TScanRange();scanRange.setHdfs_scan_range(hdfsScanRange);scanRangeLocations.setScan_range(scanRange);TScanRangeLocation scanRangeLocation = new TScanRangeLocation(new TNetworkAddress("-1", -1));scanRangeLocations.addToLocations(scanRangeLocation);result.add(scanRangeLocations);
}
  • PlanFragment 

    visitPhysicalIcebergMetadataScan接口最终组装的是一个PlanFragment,这大体类似于Spark的stage,是物理执行计划的计划块

PlanFragment fragment =new PlanFragment(context.getNextFragmentId(), metadataScanNode, DataPartition.RANDOM);
context.getFragments().add(fragment);
return fragment
  • IcebergMetadataScanner

    IcebergMetadataScanner由于其调用逻辑来自于C++的代码,暂未梳理其逻辑,但是假定其执行了,可以看其效果,主要在getNext()接口中读取数据

    可以看到其读取后的数据结构是ContentFile,是Iceberg中DataFile的上层父类

ContentFile<?> file = reader.next();
for (int i = 0; i < requiredFields.length; i++) {Object fieldData = get(requiredFields[i], file);if (fieldData == null) {appendData(i, null);} else {ColumnValue fieldValue = new IcebergMetadataColumnValue(fieldData);appendData(i, fieldValue);}
}

    主要在appendData接口当中,向表添加数据,可以看到这里设置了一个offHeapTable

    offHeapTable是 StarRocks 中的一个特殊表类型,简单来说就是在堆外内存中建立一个表结构,将数据对应存储到堆外内存,之后可以以表形式去访问

protected void appendData(int index, ColumnValue value) {offHeapTable.appendData(index, value);
}

2. 数据扫描中的元数据解析

    首先同样到PlanFragmentBuilder.visitPhysicalIcebergScan,流程与visitPhysicalIcebergMetadataScan类似

    首先是这里的表是数据表

Table referenceTable = node.getTable();
context.getDescTbl().addReferencedTable(referenceTable);
TupleDescriptor tupleDescriptor = context.getDescTbl().createTupleDescriptor();
tupleDescriptor.setTable(referenceTable);// set slot
prepareContextSlots(node, context, tupleDescriptor);

    之后是IcebergScanNode

IcebergScanNode icebergScanNode =new IcebergScanNode(context.getNextNodeId(), tupleDescriptor, "IcebergScanNode",equalityDeleteTupleDesc);

    IcebergScanNode这里核心是调用setupScanRangeLocations

icebergScanNode.setupScanRangeLocations(context.getDescTbl());

    最终同样封装成PlanFragment

PlanFragment fragment =new PlanFragment(context.getNextFragmentId(), icebergScanNode, DataPartition.RANDOM);
context.getFragments().add(fragment);
return fragment;
  • IcebergScanNode

    在setupScanRangeLocations当中,有一个操作是getRemoteFileInfos,这个就是获取数据文件信息,因此内部包含了元数据解析的部分

List<RemoteFileInfo> splits = GlobalStateMgr.getCurrentState().getMetadataMgr().getRemoteFileInfos(catalogName, icebergTable, null, snapshotId, predicate, null, -1);
  • IcebergMetadata

    getRemoteFileInfos是在IcebergMetadata当中,会调用triggerIcebergPlanFilesIfNeeded,看接口名字可以明确这是用来触发Iceberg的元数据解析的,最终走到了collectTableStatisticsAndCacheIcebergSplit

private void triggerIcebergPlanFilesIfNeeded(IcebergFilter key, IcebergTable table, ScalarOperator predicate,long limit, Tracers tracers, ConnectContext connectContext) {if (!scannedTables.contains(key)) {tracers = tracers == null ? Tracers.get() : tracers;try (Timer ignored = Tracers.watchScope(tracers, EXTERNAL, "ICEBERG.processSplit." + key)) {collectTableStatisticsAndCacheIcebergSplit(table, predicate, limit, tracers, connectContext);}}
}

    collectTableStatisticsAndCacheIcebergSplit当中获取了TableScan,这里的Scan就是StarRocksIcebergTableScan

TableScan scan = icebergCatalog.getTableScan(nativeTbl, new StarRocksIcebergTableScanContext(catalogName, dbName, tableName, planMode(connectContext), connectContext)).useSnapshot(snapshotId).metricsReporter(metricsReporter).planWith(jobPlanningExecutor);
  • StarRocksIcebergTableScan

    之后走scan.planFiles(),这个中间会基于Iceberg的逻辑进行调用

CloseableIterable<FileScanTask> fileScanTaskIterable = TableScanUtil.splitFiles(scan.planFiles(), scan.targetSplitSize());

    Icberg的逻辑中planFiles最终会调用TableScan的doPlanFiles,这里调用的就是StarRocksIcebergTableScan的实现接口,根据场景有本地和远程的调用方式

if (shouldPlanLocally(dataManifests, loadColumnStats)) {return planFileTasksLocally(dataManifests, deleteManifests);
} else {return planFileTasksRemotely(dataManifests, deleteManifests);
}

    Iceberg应当是使用的planFileTasksRemotely,内部会构建IcebergMetadataCollectJob

MetadataCollectJob metadataCollectJob = new IcebergMetadataCollectJob(catalogName, dbName, tableName, TResultSinkType.METADATA_ICEBERG, snapshotId(), icebergSerializedPredicate);metadataCollectJob.init(connectContext.getSessionVariable());long currentTimestamp = System.currentTimeMillis();
String threadNamePrefix = String.format("%s-%s-%s-%d", catalogName, dbName, tableName, currentTimestamp);
executeInNewThread(threadNamePrefix + "-fetch_result", metadataCollectJob::asyncCollectMetadata);
  • MetadataExecutor执行

    IcebergMetadataCollectJob的执行在MetadataExecutor当中,就是基本的SQL执行,这里是异步的

public void asyncExecuteSQL(MetadataCollectJob job) {ConnectContext context = job.getContext();context.setThreadLocalInfo();String sql = job.getSql();ExecPlan execPlan;StatementBase parsedStmt;try {parsedStmt = SqlParser.parseOneWithStarRocksDialect(sql, context.getSessionVariable());execPlan = StatementPlanner.plan(parsedStmt, context, job.getSinkType());} catch (Exception e) {context.getState().setError(e.getMessage());return;}this.executor = new StmtExecutor(context, parsedStmt);context.setExecutor(executor);context.setQueryId(UUIDUtil.genUUID());context.getSessionVariable().setEnableMaterializedViewRewrite(false);LOG.info("Start to execute metadata collect job on {}.{}.{}", job.getCatalogName(), job.getDbName(), job.getTableName());executor.executeStmtWithResultQueue(context, execPlan, job.getResultQueue());
}

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

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

相关文章

数据结构——顺序表【C】

顺序表 1. 顺序表的概念以及结构1.1概念1.2静态顺序表和动态顺序表 2. 顺序表接口模拟实现接口总览2.1 初始化数据和销毁容器 2.2 顺序表的尾插和尾删2.3 头插和头删2.4 任意位置插入和删除数据2.5 查找数据 3. 顺序表的问题 &#xff1a; 1. 顺序表的概念以及结构 1.1概念 顺…

贴脸细看Mixtral 8x7B- 稀疏混合专家模型(MoE)的创新与推动

贴脸细看Mixtral 8x7B- 稀疏混合专家模型&#xff08;MoE&#xff09;的创新与推动 原创 一路到底孟子敬 上堵吟 2024年01月15日 20:05 美国 I. 引言 A. Mixtral 8x7B的背景和目的 • 背景&#xff1a;随着大型语言模型在自然语言处理&#xff08;NLP&#xff09;领域的广泛…

社交论坛圈子系统APP开发社交圈子小程序系统源码开源,带语音派对聊天室/圈子社交论坛及时聊天

功能// 首页左右滑动切换分类 使用资讯类app常见的滑动切换分类&#xff0c;让用户使用更方便。 2信息卡片流展示 每条信息都是一个卡片&#xff0c;头像展示会员标签&#xff0c;单图自动宽度&#xff0c;多图九宫格展示&#xff0c;底部展示信息发布地址&#xff0c;阅读量、…

Camera Raw:常规工具

在 Camera Raw 窗口右下角提供了四个常用的工具&#xff0c;它们分别是&#xff1a;缩放工具、抓手工具、切换取样器叠加以及切换网格叠加工具。 ◆ ◆ ◆ 缩放工具 Zoom Tool 用于放大或缩小预览图像&#xff0c;便于查看和编辑细节。 快捷键&#xff1a;Z 1、双击“缩放工具…

10-windows自带的磁盘上传配额限制?提示这个错误:XXX用户上空间不足,需要XXMB来复制此项目,请删除或移动文件来获得足够的空间如何解决?

1.配置缘由&#xff1a; Windows自带的功能&#xff1a;限制某个磁盘登录的用户上传到这块磁盘的文件容量大小。 2.配置磁盘配额步骤: 右键整块磁盘--属性--配额 3.提示这个错误&#xff1a;XXX用户上空间不足&#xff0c;需要XXMB来复制此项目&#xff0c;请删除或移动文件来…

普中51单片机:中断系统与寄存器解析(六)

文章目录 引言中断流程图中断优先级下降沿中断结构图中断相关寄存器IE中断允许寄存器&#xff08;可位寻址&#xff09;XICON辅助中断控制寄存器&#xff08;可位寻址&#xff09;TCON标志控制寄存器SCON串行口控制寄存器 中断号中断响应条件中断函数代码模板电路图开发板IO连接…

局域网远程共享桌面如何实现

在局域网内实现远程共享桌面&#xff0c;可以通过以下几种方法&#xff1a; 一、使用Windows自带的远程桌面功能&#xff1a; 首先&#xff0c;在需要被控制的电脑上右键点击“此电脑”&#xff0c;选择“属性”。 进入计算机属性界面后&#xff0c;点击“高级系统设置”&am…

DNS--

1.DNS作用 -互联网中的114查号台/导航员 全球13台DNS根服务器分布&#xff1a; 美国VeriSign公司 2台 网络管理组织IANA(Internet Assigned Number Authority) 1台 欧洲网络管理组织RIPE-NCC(Resource IP Europeens Network Coordination Centre) 1台 美国PSINet公司 1台…

suricata7 rule加载(三)加载options

suricata7.0.5 加载options (msg:“HTTP Request Example”; flow:established,to_server; http.method; content:“POST”; http.uri; content:“query.php”; bsize:>9; http.protocol; content:“HTTP/1.1”; bsize:8; http.host; content:“360”; bsize:>3; class…

代理详解之静态代理、动态代理、SpringAOP实现

1、代理介绍 代理是指一个对象A通过持有另一个对象B&#xff0c;可以具有B同样的行为的模式。为了对外开放协议&#xff0c;B往往实现了一个接口&#xff0c;A也会去实现接口。但是B是“真正”实现类&#xff0c;A则比较“虚”&#xff0c;他借用了B的方法去实现接口的方法。A…

vue3 + element-plus 表格行内编辑,如何实现表单校验?

问题描述&#xff1a; 当使用table实现行内编辑时&#xff0c;往往需要对必填项增加校验以及错误高度&#xff0c; 预期实现效果如下&#xff1a; 实现思路&#xff1a; 使用el-form表单自身的校验功能&#xff1a;通过el-from绑定对应表格行的prop&#xff0c; 实现校验 页面…

数据结构基础--------【二叉树题型】

1、前提(待补充) 1.**DFS&#xff08;Depth First Search&#xff09;&#x1f617;*递归法得到最终的数组&#xff08;深度优先算法&#xff09; 其过程简要来说是对每一个可能的分支路径深入到不能再深入为止&#xff0c;如果遇到死路就往回退&#xff0c;回退过程中如果遇…

OAuth2.0登录的四种方式

OAuth登录的四种方式 1. 授权码 授权码&#xff08;authorization code&#xff09;方式&#xff0c;指的是第三方应用先申请一个授权码&#xff0c;然后再用该码获取令牌。 这种方式是最常用的流程&#xff0c;安全性也最高&#xff0c;它适用于那些有后端的 Web 应用。授权…

点亿点计划Web3.0广告平台即将发射Clicks科力币

点亿点计划Web3.0广告平台即将发射Clicks科力币 我们很高兴地宣布&#xff0c;点亿点计划Web3.0广告平台即将发射Clicks科力币&#xff01;科力币&#xff08;Clicks&#xff09;是Clicks X Web3.0多功能应用的治理代币&#xff0c;未来将为代币持有人带来巨大的广告收入。 …

计算机的错误计算(二十六)

摘要 结合计算机的错误计算&#xff08;二十四&#xff09;中的 Maple 环境下的计算过程&#xff0c;&#xff08;二十五&#xff09;讨论了&#xff08;不&#xff09;停机问题。事实上&#xff0c;其它数学软件比如 Mathematica 也存在该问题。 &#xff08;不&#xff09;停…

《植物大战僵尸杂交版》2.2版本:全新内容与下载指南

《植物大战僵尸杂交版》2.2版本已经火热更新&#xff0c;带来了一系列令人兴奋的新玩法和调整&#xff0c;为这款经典的塔防游戏注入了新的活力。如果你是《植物大战僵尸》系列的忠实粉丝&#xff0c;那么这个版本绝对值得你一探究竟。 2.2版本更新亮点 新增看星星玩法 这个新…

Linux学习——Linux中无法使用ifconfg命令

Linux学习——Linux中无法使用ifconfg命令&#xff1f; &#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅…

【Python基础篇】条件判断和循环判断

文章目录 1. 条件判断1.1 单分支1.2 双分支1.3 多分支 2. 循环判断2.1 while2.2 for2.3 break2.4 continue 1. 条件判断 1.1 单分支 前面学习了打印&#xff0c;但是有时候我们在打印时会面临选择&#xff0c;例如&#xff1a;一个网吧&#xff0c;未满18&#xff0c;禁止进入…

力扣喜刷刷--day1

1.无重复字符的最长子串 知识点&#xff1a;滑动窗口 基本概念 窗口&#xff1a;窗口是一个连续的子序列&#xff0c;可以是固定长度或可变长度。滑动&#xff1a;窗口在数据序列上移动&#xff0c;可以是向左或向右。边界&#xff1a;窗口的起始和结束位置。 应用场景 字符…

OpenAI与Thrive Global推出Thrive AI Health:AI驱动的健康教练应用

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…