【OpenGauss源码学习 —— (RowToVec)算子】

news2024/11/25 20:26:51

VecToRow 算子

  • 概述
  • ExecInitRowToVec 函数
  • ExecRowToVec 函数
    • VectorizeOneTuple 函数
  • ExecEndRowToVec 函数
  • 总结

声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 OpenGauss1.1.0 的开源代码和《OpenGauss数据库源码解析》和《PostgresSQL数据库内核分析》一书

概述

  OpenGaussPortalRun 函数中会实际执行相关的 DML 查询,对数据进行计算和处理。在执行过程中,所有执行算子分为两大类行存储算子向量化算子。这两类算子分别对应行存储执行引擎向量化执行引擎行存储执行引擎的上层入口是 ExecutePlan 函数,向量化执行引擎的上层人口是 ExecuteVectorizedPlan 函数。其中向量化引擎是针对列存储表的执行引擎。如果存在行存储表和列存储表的混合计算,那么行存储执行引擎和向量化执行引擎直接可以通过 VecToRowRowToVec 算子进行相互转换行存储算子执行入口函数的命名规则一般为 “Exec + 算子名” 的形式,向量化算子执行入口函数的命名规则一般为 “ExecVee十算子名” 的形式,通过这样的命名规则,可以快速地找到对应算子的函数入口。
  在【OpenGauss源码学习 —— (VecToRow)算子】一文中,我们学习了VecToRow 算子的执行流程,本文则来补充学习一下 RowToVec 算子的执行流程。
  RowToVec 算子的主要功能和作用是将行存储表中的数据按列存储格式重新组织和转换,以便能够有效地交给向量化执行引擎处理。它负责将行数据逐行提取并转化成列向量,使得数据库系统能够在混合计算环境中高效地处理包括行存储表和列存储表的数据,从而实现更快速、更优化的查询和数据处理操作。这个过程是在混合存储环境中实现无缝数据转换的关键部分,有助于提高数据库查询性能和整体系统效率。
  RowToVec 算子的执行流程通常包括以下步骤:首先,RowToVec 算子从行存储表中获取一批行数据,这些行数据以行的形式存储在内存中。然后,它会逐行地将这些数据转换为列存储格式,这意味着将每列的数据分别提取出来,组成列向量。这个转换过程包括数据的重新排列和重新组织,以适应列存储的数据结构。一旦所有行数据都被转换为列向量,RowToVec 算子就会将这些向量传递给向量化执行引擎,以便后续的处理。这种转换操作允许在混合计算将行存储表和列存储表的数据进行有效整合,以便进行更高效的向量化计算。这样的执行流程可以在混合存储环境中实现数据的无缝转换和处理,以提高查询性能和效率。下面我们来详细的看一看相关函数源码一遍更好地理解和学习吧。

ExecInitRowToVec 函数

  ExecInitRowToVec 函数的主要功能是为 RowToVec 算子创建执行状态,并初始化该状态以准备执行操作。它包括创建状态结构设置计划节点初始化元组表分配向量缓冲初始化子节点等步骤。这个过程确保了 RowToVec 算子能够正确地将行数据转换为列向量,并在混合计算中无缝运行,以提高数据库查询性能。
  ExecInitRowToVec 函数的执行流程可以简要描述如下:

  1. 创建 RowToVecState 结构:首先,函数创建了一个 RowToVecState 结构,该结构用于存储 RowToVec 算子的执行状态信息。
  2. 设置基本属性:函数设置 RowToVecState 中的一些基本属性,如关联的计划节点执行状态、以及标记为向量化执行
  3. 检查数据类型支持:函数检查 RowToVec 算子是否支持所需的数据类型,以确保正确的数据处理。
  4. 初始化元组表:函数初始化用于存储结果的元组表,并分配所需的内存。
  5. 初始化子节点:函数初始化 RowToVec 算子的子节点,确保子节点能够正常执行。
  6. 创建表达式上下文:函数为 RowToVecState 创建表达式上下文,以便在执行期间进行表达式计算。
  7. 初始化元组类型:函数设置 RowToVecState元组类型,以匹配子节点的输出类型。
  8. 分配向量缓冲:函数为列向量数据分配内存,以便存储转换后的数据。
  9. 返回状态结构:最后,函数返回初始化完成的 RowToVecState 结构,以供后续的执行操作使用。

  ExecInitRowToVec 函数源码如下所示:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vecrowtovector.cpp

// 初始化 RowToVecState 结构
// 这段代码的作用是初始化 RowToVecState 结构,为 RowToVec 算子的执行创建执行状态。
// 具体步骤包括创建状态结构、设置计划节点、初始化元组表、分配向量缓冲、初始化子节点等。
RowToVecState* ExecInitRowToVec(RowToVec* node, EState* estate, int eflags)
{
    RowToVecState* state = NULL;

    /*
     * 创建状态结构
     */
    state = makeNode(RowToVecState);
    state->ps.plan = (Plan*)node;
    state->ps.state = estate;
    state->ps.vectorized = true;

    // 检查 RowToVec 算子是否支持所需数据类型
    CheckTypeSupportRowToVec(node->plan.targetlist);

    /*
     * 初始化元组表
     *
     * 排序节点只从其排序关系中返回扫描元组。
     */
    ExecInitResultTupleSlot(estate, &state->ps);

    /* 分配向量缓冲 */
    state->m_fNoMoreRows = false;

    /*
     * 初始化子节点
     *
     * 我们屏蔽了子节点对支持 REWIND、BACKWARD 或 MARK/RESTORE 的需求。
     */
    outerPlanState(state) = ExecInitNode(outerPlan(node), estate, eflags);

    /*
     * 杂项初始化
     *
     * 为节点创建表达式上下文
     */
    ExecAssignExprContext(estate, &state->ps);

    /*
     * 初始化元组类型。不需要初始化投影信息,因为此节点不进行投影。
     */
    ExecAssignResultTypeFromTL(
            &state->ps,
            ExecGetResultType(outerPlanState(state))->tdTableAmType);

    TupleDesc res_desc = state->ps.ps_ResultTupleSlot->tts_tupleDescriptor;
    state->m_pCurrentBatch = New(CurrentMemoryContext) VectorBatch(CurrentMemoryContext, res_desc);
    state->ps.ps_ProjInfo = NULL;

    return state;
}

ExecRowToVec 函数

  ExecRowToVec 函数的主要功能是将行数据转换为向量批次,用于向量化处理。它通过不断从外部计划节点获取行数据,然后将每个行数据向量化,直到外部计划的所有行都处理完毕。函数中的 VectorizeOneTuple 函数用于将单个行元组转换为向量,并使用适当的内存管理。一旦向量批次已满或外部计划的行耗尽,函数将返回当前的向量批次。
  ExecRowToVec 函数的的执行流程可以简要描述如下:

  1. 准备必要的变量和数据结构,包括初始化向量批次(VectorBatch)和执行上下文(ExprContext)。
  2. 重置执行上下文的内存,以确保在处理每个行数据时不会出现内存混淆。
  3. 获取外部计划节点的状态信息,准备从外部计划获取行数据。
  4. 如果已经没有更多的行数据需要处理(由 m_fNoMoreRows 标志控制),则直接跳到完成步骤。
  5. 通过循环迭代处理外部计划的每个行数据,每次获取一个外部计划的元组。
  6. 对每个获取的行数据执行向量化处理,将行数据转换为向量格式,并使用适当的内存进行管理。
  7. 如果向量批次已满,或者外部计划的所有行都已处理完毕,则结束循环。
  8. 在完成步骤中,将向量批次中的每列的行数设置为相同的值,以标记批次中的有效数据行数
  9. 最后,返回向量批次,其中包含转换后的向量化数据。

  ExecRowToVec 函数的源码如下:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vecrowtovector.cpp

/*
 * @Description: 向量化运算符--将行数据转换为向量批次。
 *
 * @IN state: RowToVecState 结构,用于管理 RowToVec 算子的执行状态。
 * @return: 返回包含行表数据的向量批次,如果没有数据可转换则返回 NULL。
 */
VectorBatch* ExecRowToVec(RowToVecState* state)
{
    int i;
    PlanState* outer_plan = NULL;
    TupleTableSlot* outer_slot = NULL;
    VectorBatch* batch = state->m_pCurrentBatch;

    /* 重置当前的 ecxt_per_tuple_memory 上下文 */
    ExprContext* econtext = state->ps.ps_ExprContext;
    ResetExprContext(econtext);

    /* 从节点获取状态信息 */
    outer_plan = outerPlanState(state);
    batch->Reset();

    /*
     * 如果在返回 NULL 后再次调用 ExecProcNode(),它可能会重新启动,
     * 因此我们需要自行进行保护。
     */
    if (state->m_fNoMoreRows) {
        goto done;
    }

    /*
     * 处理每个外部计划元组,然后获取下一个元组,直到外部计划耗尽。
     */
    for (;;) {
        outer_slot = ExecProcNode(outer_plan);
        if (TupIsNull(outer_slot)) {
            state->m_fNoMoreRows = true;
            break;
        }

        /*
         * 向量化一个元组并切换到 exprcontext 的 ecxt_per_tuple_memory。
         */
        if (VectorizeOneTuple(batch, outer_slot, econtext->ecxt_per_tuple_memory)) {
            /* 批次已满,现在返回当前批次 */
            break;
        }
    }

done:
    for (i = 0; i < batch->m_cols; i++) {
        batch->m_arr[i].m_rows = batch->m_rows;
    }

    return batch;
}

VectorizeOneTuple 函数

  VectorizeOneTuple 函数的作用是将一个数据库查询结果中的单个元组(Tuple)转换为向量化数据结构(VectorBatch),以便进行向量化处理。它会逐一提取元组中的每列数据,并根据列的数据类型长度将其合适地存储在向量批次中的相应列中。此功能是在向量化执行引擎中关键的一步,通过高效地将行数据转换为列存储的向量形式,有助于加速数据库查询的处理速度和效率。此外,该函数还会检查向量批次是否已满,以便控制向量处理的大小和内存使用
  VectorizeOneTuple 函数的执行流程可以简要描述如下:

  1. 初始化必要的变量和标志,包括一个标志变量 may_more 用于表示是否可能还有更多的行需要处理,以及循环迭代中使用的计数器变量 i 和 j
  2. 切换当前内存上下文到传入的 transformContext 上下文,以确保内存管理的正确性。
  3. 确保输入的插槽 slot 不为空,并且具有有效的元组描述符
  4. 使用 tableam_tslot_getallattrs 函数从插槽中提取所有列的属性值
  5. 开始循环迭代插槽中的每个列数据处理每列的数据类型和长度
  6. 根据列的数据类型长度选择合适的处理方式,包括整数浮点数变长数据大整数TID行标识符)等。
  7. 如果列不为空(slot->tts_isnull[i] == false),则将列的值存储到向量批次的相应列中,并标记该列的数据为非 NULL
  8. 如果列为空(slot->tts_isnull[i] == true),则将向量批次中的相应列标记为 NULL
  9. 增加向量批次中的行数计数器 pBatch->m_rows
  10. 如果向量批次已满(行数达到 BatchMaxSize),则设置 may_more 标志为 true
  11. 切换回之前的内存上下文,以确保内存管理的正确性。
  12. 返回 may_more 标志,指示向量批次是否已满,以便在上层调用中做出相应的处理。

  VectorizeOneTuple 函数的源码如下:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vecrowtovector.cpp

/*
 * @Description: 将一个元组打包成向量批次。
 *
 * @IN pBatch: 目标向量化数据。
 * @IN slot:   一个插槽(TupleTableSlot)中的源数据。
 * @IN transformContext: 切换到此上下文以避免内存泄漏。
 * @return: 如果 pBatch 已满,则返回 true,否则返回 false。
 */
bool VectorizeOneTuple(_in_ VectorBatch* pBatch, _in_ TupleTableSlot* slot, _in_ MemoryContext transformContext)
{
    bool may_more = false;  // 标志是否可能还有更多行需要处理
    int i, j;

    /* 切换到当前的转换上下文 */
    MemoryContext old_context = MemoryContextSwitchTo(transformContext);

    /*
     * 提取旧元组的所有值。
     */
    Assert(slot != NULL && slot->tts_tupleDescriptor != NULL);

    tableam_tslot_getallattrs(slot);  // 从插槽中获取所有属性值

    j = pBatch->m_rows;
    for (i = 0; i < slot->tts_nvalid; i++) {  // 遍历元组的每一列
        int type_len;
        Form_pg_attribute attr = slot->tts_tupleDescriptor->attrs[i];  // 获取列的属性信息

        pBatch->m_arr[i].m_desc.typeId = attr->atttypid;  // 设置向量批次中列的数据类型

        if (slot->tts_isnull[i] == false) {  // 检查列是否为 NULL
            type_len = attr->attlen;  // 获取列的数据长度
            switch (type_len) {  // 根据数据长度选择合适的处理方式
                case sizeof(char):
                case sizeof(int16):
                case sizeof(int32):
                case sizeof(Datum):
                    pBatch->m_arr[i].m_vals[j] = slot->tts_values[i];  // 处理整数和浮点数
                    break;
                case 12:
                case 16:
                case 64:
                case -2:
                    pBatch->m_arr[i].AddVar(slot->tts_values[i], j);  // 处理变长数据
                    break;
                case -1: {
                    Datum v = PointerGetDatum(PG_DETOAST_DATUM(slot->tts_values[i]));  // 解压缩数据
                    /* 如果是 numeric 列,尝试将 numeric 转换为 big integer */
                    if (attr->atttypid == NUMERICOID) {
                        v = try_convert_numeric_normal_to_fast(v);
                    }
                    pBatch->m_arr[i].AddVar(v, j);  // 处理大整数和压缩数据
                    /* 由于可能创建了新的内存,因此我们必须及时检查和释放。 */
                    if (DatumGetPointer(slot->tts_values[i]) != DatumGetPointer(v)) {
                        pfree(DatumGetPointer(v));  // 释放临时内存
                    }
                    break;
                }
                case 6:
                    if (attr->atttypid == TIDOID && attr->attbyval == false) {  // 处理 TID 数据
                        pBatch->m_arr[i].m_vals[j] = 0;
                        ItemPointer dest_tid = (ItemPointer)(pBatch->m_arr[i].m_vals + j);
                        ItemPointer src_tid = (ItemPointer)DatumGetPointer(slot->tts_values[i]);
                        *dest_tid = *src_tid;
                    } else {
                        pBatch->m_arr[i].AddVar(slot->tts_values[i], j);  // 处理其他复合类型数据
                    }
                    break;
                default:
                    ereport(ERROR, (errcode(ERRCODE_INDETERMINATE_DATATYPE), errmsg("不支持的数据类型分支")));  // 不支持的数据类型分支,抛出错误
            }

            SET_NOTNULL(pBatch->m_arr[i].m_flag[j]);  // 标记列中的数据为非 NULL
        } else {
            SET_NULL(pBatch->m_arr[i].m_flag[j]);  // 标记列中的数据为 NULL
        }
    }

    pBatch->m_rows++;  // 增加向量批次中的行数
    if (pBatch->m_rows == BatchMaxSize) {  // 如果向量批次已满
        may_more = true;  // 设置 may_more 标志为 true
    }

    /* 切换回旧上下文 */
    (void)MemoryContextSwitchTo(old_context);  // 切换回之前的内存上下文

    return may_more;  // 返回 may_more 标志,指示向量批次是否已满
}

ExecEndRowToVec 函数

  ExecEndRowToVec 函数的主要作用是用于清理和释放 RowToVec 算子执行时所分配的资源和状态,包括向量批次数据执行上下文结果元组槽以及关闭外部计划节点。首先,它将向量批次数据的指针设置为 NULL,以防止内存泄漏。然后,通过 ExecFreeExprContext 函数取消与计划节点的输出上下文的链接,但不实际释放内存,这是因为内存释放可能由上层节点负责。接下来,它清空结果元组槽的数据,确保不会有残留数据。最后,它关闭外部计划节点释放与之相关的资源。这个函数用于 RowToVec 算子的执行结束时,进行资源清理和释放,以确保系统资源的有效利用。
  ExecEndRowToVec 函数的源码如下:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vecrowtovector.cpp

void ExecEndRowToVec(RowToVecState* node)
{
    node->m_pCurrentBatch = NULL;  // 清空向量批次数据,防止内存泄漏

    /*
     * 我们不实际释放任何 ExprContexts(参见 ExecFreeExprContext 中的注释),
     * 只需从计划节点中取消链接输出上下文即可。
     */
    ExecFreeExprContext(&node->ps);  // 取消与计划节点的输出上下文的链接,不实际释放内存

    /*
     * 清空元组表
     */
    (void)ExecClearTuple(node->ps.ps_ResultTupleSlot);  // 清空结果元组槽的数据

    /*
     * 关闭子计划节点
     */
    ExecEndNode(outerPlanState(node));  // 关闭外部计划节点
}

总结

  RowToVec 算子是用于将行存储数据转换为向量化数据的操作符,它通过 ExecInitRowToVec 函数进行初始化将行数据逐行处理并转换为向量批次,这个过程由 ExecRowToVecVectorizeOneTuple 函数完成,最后,ExecEndRowToVec 函数用于清理和释放资源,协同工作,使得在处理混合行存储和列存储数据时,能够高效地进行向量化计算,提高数据库查询性能。
  这里用 AI 尝试性的生成了一张描述 RowToVec 算子的图,感觉很有意思。
在这里插入图片描述
  这张图解释了数据库系统中 RowToVec 算子的功能,展示了数据从行存储格式转换到列存储格式的过程。下面是对每个部分的详细解释:

顶部部分 - 行存储表:
    这部分代表数据库中常见的行存储表。
    在行存储中,数据按行组织,这里以水平线的形式表示。
    每条水平线象征一个数据行,包含顺序存储的各种字段。

中间部分 - RowToVec算子转换:
    中间部分展示了RowToVec算子。
    它作为一个转换机制,将数据从行格式转换为列格式。
    这个过程对于在混合计算环境中操作的数据库非常关键,这些数据库使用行存储和列存储格式。
    算子将行数据逐行提取并转化为列向量,这一过程在图中以从水平线到垂直线的转换形式展示。

底部部分 - 列存储格式:
    图的底部展示了转换后的数据,现在以列存储格式呈现。
    在列存储中,数据按列组织,这里以垂直线的形式表示。
    每条垂直线代表一列数据,显示了数据在转换后的新结构。

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

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

相关文章

使用AWS Glue与AWS Kinesis构建的流式ETL作业(二)——数据处理

大纲 2 数据处理2.1 架构2.2 AWS Glue连接和创建2.2.1 创建AWS RedShift连接2.2.2 创建AWS RDS连接&#xff08;以PG为例&#xff09; 2.3 创建AWS Glue Job2.4 编写脚本2.4.1 以AWS RedShift为例2.4.2 以PG为例 2.5 运行脚本 2 数据处理 2.1 架构 2.2 AWS Glue连接和创建 下…

DSSS技术和OFDM技术

本内容为学习笔记&#xff0c;内容不一定正确&#xff0c;请多处参考进行理解 https://zhuanlan.zhihu.com/p/636853588 https://baike.baidu.com/item/OFDM/5790826?frge_ala https://zhuanlan.zhihu.com/p/515701960?utm_id0 一、 DSSS技术 信号替代&#xff1a;DSSS技术为…

谈一谈内存池

文章目录 一&#xff0c;什么是内存池二&#xff0c;进程地址空间中是如何解决内存碎片问题的三&#xff0c;malloc的实现原理四&#xff0c;STL中空间配置器的实现原理五&#xff0c;高并发内存池该内存池的优势在哪里内存池的设计框架内存申请流程ThreadCache层CentreCache层…

论文阅读:一种通过降低噪声和增强判别信息实现细粒度分类的视觉转换器

论文标题&#xff1a; A vision transformer for fine-grained classification by reducing noise and enhancing discriminative information 翻译&#xff1a; 一种通过降低噪声和增强判别信息实现细粒度分类的视觉转换器 摘要 最近&#xff0c;已经提出了几种基于Vision T…

Linux 中用户与权限

1.添加用户 useradd 1&#xff09;创建用户 useradd 用户名 2&#xff09;设置用户密码 passwd 用户名 设置密码是便于连接用户时使用到&#xff0c;如我使用物理机链接该用户 ssh 用户名 ip 用户需要更改密码的话&#xff0c;使用 passwd 指令即可 3)查看用户信息 id 用…

【数据结构(六)】希尔排序、快速排序、归并排序、基数排序的代码实现(3)

文章目录 1. 希尔排序1.1. 简单插入排序存在的问题1.2. 相关概念1.3. 应用实例1.3.1. 交换法1.3.1.1. 逐步推导实现方式1.3.1.2. 通用实现方式1.3.1.3. 计算时间复杂度 1.3.2. 移动法 2. 快速排序2.1. 相关概念2.2. 实例应用2.2.1. 思路分析2.2.2. 代码实现 2.3. 计算快速排序的…

Spatial Data Analysis(三):点模式分析

Spatial Data Analysis&#xff08;三&#xff09;&#xff1a;点模式分析 ---- 1853年伦敦霍乱爆发 在此示例中&#xff0c;我将演示如何使用 John Snow 博士的经典霍乱地图在 Python 中执行 KDE 分析和距离函数。 感谢 Robin Wilson 将所有数据数字化并将其转换为友好的 G…

(04730)电路分析基础之电阻、电容及电感元件

04730电子技术基础 语雀&#xff08;完全笔记&#xff09; 电阻元件、电感元件和电容元件的概念、伏安关系&#xff0c;以及功率分析是我们以后分析电 路的基础知识。 电阻元件 电阻及其与温度的关系 电阻 电阻元件是对电流呈现阻碍作用的耗能元件&#xff0c;例如灯泡、…

基于STM32驱动的压力传感器实时监测系统

本文介绍了如何使用STM32驱动压力传感器进行实时监测。首先&#xff0c;我们会介绍压力传感器的工作原理和常见类型。然后&#xff0c;我们将介绍如何选择合适的STM32单片机和压力传感器组合。接下来&#xff0c;我们会详细讲解如何使用STM32驱动压力传感器进行数据采集和实时监…

根文件系统软件运行测试

一. 简介 前面几篇文章学习了制作一个可以在开发板上运行的&#xff0c;简单的根文件系统。 本文在上一篇文章学习的基础上进行的&#xff0c;文章地址如下&#xff1a; 完善根文件系统-CSDN博客 本文对根文件系统软件运行进行测试。 我们使用 Linux 的目的就是运行我们自…

vue3 setup语法糖 多条件搜索(带时间范围)

目录 前言&#xff1a; setup介绍&#xff1a; setup用法&#xff1a; 介绍&#xff1a; 前言&#xff1a; 不管哪个后台管理中都会用到对条件搜索带有时间范围的也不少见接下来就跟着我步入vue的多条件搜索&#xff08;带时间范围&#xff09; 在 Vue 3 中&#xff0c;你…

[JavaScript前端开发及实例教程]计算器井字棋游戏的实现

计算器&#xff08;网页内实现效果&#xff09; HTML部分 <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>My Calculator&l…

Unity3D对CSV文件操作(创建、读取、写入、修改)

系列文章目录 Unity工具 文章目录 系列文章目录前言一、Csv是什么&#xff1f;二、创建csv文件2-1、构建表数据2-2、创建表方法2-3、完整的脚本&#xff08;第一种方式&#xff09;2-4、运行结果2-5、完整的脚本&#xff08;第二种方式&#xff09;2-6、运行结果2-7、想用哪种…

基于STM32 HAL库的光电传感器驱动程序实例

本文将使用STM32 HAL库编写一个光电传感器的驱动程序示例。首先&#xff0c;我们会介绍光电传感器的工作原理和应用场景。然后&#xff0c;我们将讲解如何选择合适的STM32芯片和光电传感器组合。接下来&#xff0c;我们会详细介绍使用STM32 HAL库编写光电传感器驱动程序的基本步…

AVFormatContext封装层:理论与实战

文章目录 前言一、封装格式简介1、FFmpeg 中的封装格式2、查看 FFmpeg 支持的封装格式 二、API 介绍三、 实战 1&#xff1a;解封装1、原理讲解2、示例源码 13、运行结果 14、示例源码 25、运行结果 2 三、 实战 2&#xff1a;转封装1、原理讲解2、示例源码3、运行结果 前言 A…

Docker中部署ElasticSearch 和Kibana,用脚本实现对数据库资源的未授权访问

图未保存&#xff0c;不过文章当中的某一步骤可能会帮助到您&#xff0c;那么&#xff1a;感恩&#xff01; 1、docker中拉取镜像 #拉取镜像 docker pull elasticsearch:7.7.0#启动镜像 docker run --name elasticsearch -d -e ES_JAVA_OPTS"-Xms512m -Xmx512m" -e…

删除误提交的 git commit

背景描述 某次的意外 commit 中误将密码写到代码中并且 push 到了 remote repo 里面, 本文将围绕这个场景讨论如何弥补. 模拟误提交操作 在 Gitee 创建一个新的 Repo, clone 到本地 git clone https://gitee.com/lpwm/myrepo.git创建两个文件, commit 后 push 到 remote 作…

JSON 语法详解:轻松掌握数据结构(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

CSS中 设置文字下划线 的几种方法

在网页设计和开发中&#xff0c;我们经常需要对文字进行样式设置&#xff0c;包括字体,颜色&#xff0c;大小等&#xff0c;其中&#xff0c;设置文字下划线是一种常见需求 一 、CSS种使用 text-decoration 属性来设置文字的装饰效果&#xff0c;包括下划线。 常用的取值&…

JFrog----基于Docker方式部署JFrog

文章目录 1 下载镜像2 创建数据挂载目录3 启动 JFrog服务4 浏览器登录5 重置密码6 设置 license7 设置 Base URL8 设置代理9 选择仓库类型10 预览11 查看结果 1 下载镜像 免费版 docker pull docker.bintray.io/jfrog/artifactory-oss体验版&#xff1a; docker pull releas…