StarRocks分布式元数据源码解析

news2024/9/22 9:49:04

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 scheduler
    hdfsScanRange.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 scheduler
    hdfsScanRange.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.coloradmin.cn/o/1911185.html

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

相关文章

Linux--线程的控制

目录 0.前言 1.pthread库 2.关于控制线程的接口 2.1.创建线程&#xff08;pthread_create&#xff09; 2.2.线程等待&#xff08;pthread_join&#xff09; 代码示例1&#xff1a; ​编辑 ***一些问题*** 2. 3.创建多线程 3.线程的终止 &#xff08;pthread_exit /…

python—读写csv文件

目录 csv库方法参数 读取数据 csv.reader方法 文件指定行或列数据读取操作 txt文件的readlines、read方法 csv.DictReader方法 写入数据 txt文件的write&#xff0c;writelines csv.writer方法 csv.DictWriter方法 读写联合(修改及插入数据) 读写csv 文件时&#xf…

语义言语流畅性的功能连接和有效连接

摘要 语义言语流畅性(SVF)受损在多种神经系统疾病中都存在。虽然已经报道了SVF相关区域的激活情况&#xff0c;但这些区域如何相互连接以及它们在脑网络中的功能作用仍存在分歧。本研究使用功能磁共振成像评估了健康被试SVF静态和动态功能连接(FC)以及有效连接。观察到额下回(…

c++初阶学习----入门(上)

大家好啊。最近学习了一点关于c的知识。这不就迫不及待的来与大家分享了嘛。但我这也是现学现卖所以咧。有很多遗落甚至不对的地方希望大家可以在评论区里面指出来。这样也可以增加大家对知识的巩固。 c语言与c的联系 不知道大家看到c会不会不由自主的联想到C语言啊。毕竟都是…

TVBox的Json配置接口编写指南,模板格式说明(如何打造一个专属于自己的TVBox配置文件)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 配置解析 📒📝 JSON基础📝 配置文件结构/参数说明📝 编写步骤📝 注意事项🎈 接口分享⚓️ 相关链接 ⚓️📖 介绍 📖 TVBox 是一款备受欢迎的电视盒子应用(免费影视必备),它以其高度自定义的特性深受用户喜爱…

Pearson 相关系数的可视化辅助判断和怎么用

Pearson 相关系数的可视化辅助判断和怎么用 flyfish Pearson 相关系数 是一种用于衡量两个连续型变量之间线性相关程度的统计量。其定义为两个变量协方差与标准差的乘积的比值。公式如下&#xff1a; r ∑ ( x i − x ˉ ) ( y i − y ˉ ) ∑ ( x i − x ˉ ) 2 ∑ ( y i −…

jitsi 使用JWT验证用户身份

前言 Jitsi Meet是一个很棒的会议系统,但是默认他运行所有人创建会议,这样在某种程度上,我们会觉得他不安全,下面我们就来介绍下使用JWT来验证用户身份 方案 卸载旧的lua依赖性sudo apt-get purge lua5.1 liblua5.1-0 liblua5.1-dev luarocks添加ubuntu的依赖源,有则不需…

AI时代算法面试:揭秘高频算法问题与解答策略

三种决策树算法的特点和区别 ID3算法&#xff1a;基本的决策树算法&#xff0c;适用于简单的分类问题C4.5算法&#xff1a;改进了ID3算法&#xff0c;适用于更复杂的分类问题&#xff0c;可以处理连续型数据和缺失值CART算法&#xff1a;更加通用的决策树算法&#xff0c;适用于…

住宅代理、移动代理和数据中心代理之间的区别

如果您是一名认真的互联网用户&#xff0c;可能需要反复访问某个网站或服务器&#xff0c;可能是为了数据抓取、价格比较、SEO 监控等用例&#xff0c;而不会被 IP 列入黑名单或被 CAPTCHA 阻止。 代理的工作原理是将所有传出数据发送到代理服务器&#xff0c;然后代理服务器将…

用LangGraph、 Ollama,构建个人的 AI Agent

如果你还记得今年的 Google I/O大会&#xff0c;你肯定注意到了他们今年发布的 Astra&#xff0c;一个人工智能体&#xff08;AI Agent&#xff09;。事实上&#xff0c;目前最新的 GPT-4o 也是个 AI Agent。 现在各大科技公司正在投入巨额资金来创建人工智能体&#xff08;AI …

VBA实现Excel的数据透视表

前言 本节会介绍通过VBA的PivotCaches.Create方法实现Excel创建新的数据透视表、修改原有的数据透视表的数据源以及刷新数据透视表内容。 本节测试内容以下表信息为例 1、创建数据透视表 语法&#xff1a;PivotCaches.Create(SourceType, [SourceData], [Version]) 说明&am…

面对数据不一致性的解决方案:

polarDB是读写分离和计算存储分离的分布式数据库&#xff0c;并且副本的log replicate是基于Parallel-Raft协议来实现的。所以在瞬时进行写和读的操作时&#xff0c;是不可避免会存在数据一致性问题&#xff0c;导致这个数据一致性问题的原因不是事务&#xff0c;而是多副本日志…

【考研数学】李林《880题》25版听说大改版?和和24版差别大吗?

25版和24版总体差别不大&#xff0c;只有小部分内容有所变动&#xff01; 拓展题部分的更新&#xff1a;25版在拓展题部分进行了一些更新&#xff0c;从李林的模拟题中挑选了大约40道题目添加到新版中。 高等数学&#xff1a;变动主要集中在前三章&#xff0c;但具体的题目变…

【C++】开源:坐标转换和大地测量GeographicLib库配置使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍坐标转换和大地测量GeographicLib库配置使用。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关…

Facebook社交平台的未来发展趋势分析

随着科技和社交需求的不断演变&#xff0c;Facebook作为全球最大的社交平台之一&#xff0c;其未来发展的趋势备受关注。从技术创新到社会影响&#xff0c;Facebook正在经历着前所未有的变化和挑战。本文将探讨Facebook未来发展的几个关键趋势&#xff0c;并分析其可能的影响和…

SpringBoot 实现视频分段播放(通过进度条来加载视频)

需求&#xff1a;现在我本地电脑中有一个文件夹&#xff0c;文件夹中都是视频&#xff0c;需要实现视频播放的功能。 问题&#xff1a;如果通过类似 SpringBoot static 文件夹的方式来实现&#xff0c;客户端要下载好完整的视频之后才可以播放&#xff0c;并且服务端也会占用大…

Androidstudio开发,天气预报APP

1.项目功能思维导图 2. 项目涉及到的技术点 数据来源&#xff1a;和风天气API使用okhttp网络请求框架获取api数据使用gson库解析json数据使用RecyclerViewadapter实现未来7天列表展示和天气指数使用PopupMenu 实现弹出选项框使用动画定时器实现欢迎页倒计时和logo动画使用Text…

用Vue3和Plotly.js绘制交互式3D散点图

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 使用 Plotly.js 创建 2D 密度图 应用场景介绍 密度图是一种可视化数据分布的图表&#xff0c;它显示了数据点的密度在不同区域的变化情况。在许多科学和工程领域中&#xff0c;密度图被广泛用于探索和分析数据…

java项目总结数据库

1.什么是数据库 用于存储和管理数据的仓库 2.数据库的特点 1.持久化存储数据。确实数据库就是一个文件系统。 2.便于存储和管理数据 3.使用统一的方式操作数据库 --SQL 3.MqSql服务启动 4.登录和退出 这里的ip值IP地址 5.客户端与服务器关系 6.目录结构 7.SQL 1.什么是SQL&…

AI赋能OFFICE 智能化办公利器!

ONLYOFFICE在线编辑器的最新版本8.1已经发布&#xff0c;整个套件带来了30多个新功能和432个bug修复。这个文档编辑器无疑成为了办公软件中的翘楚。它不仅支持处理文本文档、电子表格、演示文稿、可填写的表单和PDF&#xff0c;还允许多人在线协作&#xff0c;并支持AI集成&…