ArgoWorkflow教程(四)---Workflow 日志归档

news2025/1/12 8:42:51

argoworkflow-4-artifacts-archive.png

上一篇我们分析了argo-workflow 中的 artifact,包括 artifact-repository 配置以及 Workflow 中如何使用 artifact。本篇主要分析流水线 GC 以及归档,防止无限占用集群中 etcd 的空间。


**【ArgoWorkflow 系列】**持续更新中,搜索公众号【探索云原生】订阅,文章。


1. 概述

因为 ArgoWorkflow 是用 CRD 方式实现的,不需要外部存储服务也可以正常运行:

  • 运行记录使用 Workflow CR 对象存储
  • 运行日志则存放在 Pod 中,通过 kubectl logs 方式查看
    • 因此需要保证 Pod 不被删除,否则就无法查看了

但是也正因为所有数据都存放在集群中,当数据量大之后 etcd 存储压力会很大,最终影响到集群稳定性

为了解决该问题 ArgoWorkflow 提供了归档功能,将历史数据归档到外部存储,以降低 etcd 的存储压力。

具体实现为:

  • 1)将 Workflow 对象会存储到 Postgres(或 MySQL)
  • 2)将 Pod 对应的日志会存储到 S3,因为日志数据量可能会比较大,因此没有直接存 PostgresQL。

为了提供归档功能,需要依赖两个存储服务:

  • Postgres:外部数据库,用于存储归档后的工作流记录
  • minio:提供 S3 存储,用于存储 Workflow 中生成的 artifact 以及已归档工作流的 Pod 日志

因此,如果不需要存储太多 Workflow 记录及日志查看需求的话,就不需要使用归档功能,定时清理集群中的数据即可。

2.Workflow GC

Argo Workflows 有个工作流执行记录(Workflow)的清理机制,也就是 Garbage Collect(GC)。GC 机制可以避免有太多的执行记录, 防止 Kubernetes 的后端存储 Etcd 过载。

开启

我们可以在 ConfigMap 中配置期望保留的工作执行记录数量,这里支持为不同状态的执行记录设定不同的保留数量。

首先查看 argo-server 启动命令中指定的是哪个 Configmap

# kubectl -n argo get deploy argo-workflows-server -oyaml|grep args -A 5
      - args:
        - server
        - --configmap=argo-workflows-workflow-controller-configmap
        - --auth-mode=server
        - --secure=false
        - --loglevel

可以看到,这里是用的argo-workflows-workflow-controller-configmap,那么修改这个即可。

配置如下:

apiVersion: v1
data:
  retentionPolicy: |
    completed: 3
    failed: 3
    errored: 3
kind: ConfigMap
metadata:
  name: argo-workflows-workflow-controller-configmap
  namespace: argo

需要注意的是,这里的清理机制会将多余的 Workflow 资源从 Kubernetes 中删除。如果希望能更多历史记录的话,建议启用并配置好归档功能。

然后重启 argo-workflow-controller 和 argo-server

kubectl -n argo rollout restart deploy argo-workflows-server
kubectl -n argo rollout restart deploy argo-workflows-workflow-controller

测试

运行多个流水线,看下是否会自动清理

for ((i=1; i<=10; i++)); do
cat <<EOF | kubectl create -f -
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: hello-world-
spec:
  entrypoint: whalesay
  templates:
  - name: whalesay
    container:
      image: docker/whalesay
      command: [cowsay]
      args: ["hello world $i"]
EOF
done

创建了 10 个 Workflow,看一下运行完成后会不会自动清理掉

[root@lixd-argo archive]# k get wf
NAME                STATUS      AGE   MESSAGE
hello-world-6hgb2   Succeeded   74s
hello-world-6pl5w   Succeeded   37m
hello-world-9fdmv   Running     21s
hello-world-f464p   Running     18s
hello-world-kqwk4   Running     16s
hello-world-kxbtk   Running     18s
hello-world-p88vd   Running     19s
hello-world-q7xbk   Running     22s
hello-world-qvv7d   Succeeded   10m
hello-world-t94pb   Running     23s
hello-world-w79q6   Running     15s
hello-world-wl4vl   Running     23s
hello-world-znw7w   Running     23s

过一会再看

[root@lixd-argo archive]# k get wf
NAME                STATUS      AGE    MESSAGE
hello-world-f464p   Succeeded   102s
hello-world-kqwk4   Succeeded   100s
hello-world-w79q6   Succeeded   99s

可以看到,只保留了 3 条记录,其他的都被清理了,说明 GC 功能 ok。

3. 流水线归档

https://argo-workflows.readthedocs.io/en/stable/workflow-archive/

开启 GC 功能之后,会自动清理 Workflow 以保证 etcd 不被占满,但是也无法查询之前的记录了。

ArgoWorkflow 也提供了流水线归档功能,来解决该问题。

通过将 Workflow 记录到外部 Postgres 数据库来实现持久化,从而满足查询历史记录的需求。

部署 Postgres

首先,简单使用 helm 部署一个 AIO 的Postgres

REGISTRY_NAME=registry-1.docker.io
REPOSITORY_NAME=bitnamicharts
storageClass="local-path"
# postgres 账号的密码
adminPassword="postgresadmin"

helm install pg-aio oci://$REGISTRY_NAME/$REPOSITORY_NAME/postgresql \
--set global.storageClass=$storageClass \
--set global.postgresql.auth.postgresPassword=$adminPassword \
--set global.postgresql.auth.database=argo

配置流水线归档

同样的,在 argo 配置文件中增加 persistence 相关配置即可:

persistence: 
  archive: true
  postgresql:
    host: pg-aio-postgresql.default.svc.cluster.local
    port: 5432
    database: postgres
    tableName: argo_workflows
    userNameSecret:
      name: argo-postgres-config
      key: username
    passwordSecret:
      name: argo-postgres-config
      key: password

argo-workflows-workflow-controller-configmap 完整内容如下:

apiVersion: v1
data:
  retentionPolicy: |
    completed: 3
    failed: 3
    errored: 3
  persistence: |
    archive: true
    archiveTTL: 180d
    postgresql:
      host: pg-aio-postgresql.default.svc.cluster.local
      port: 5432
      database: argo
      tableName: argo_workflows
      userNameSecret:
        name: argo-postgres-config
        key: username
      passwordSecret:
        name: argo-postgres-config
        key: password
kind: ConfigMap
metadata:
  name: argo-workflows-workflow-controller-configmap
  namespace: argo

然后还要创建一个 secret

kubectl create secret generic argo-postgres-config -n argo --from-literal=password=postgresadmin --from-literal=username=postgres

可能还需要给 rbac,否则 Controller 无法查询 secret

kubectl create clusterrolebinding argo-workflow-controller-admin --clusterrole=admin --serviceaccount=argo:argo-workflows-workflow-controller

然后重启 argo-workflow-controller 和 argo-server

kubectl -n argo rollout restart deploy argo-workflows-server
kubectl -n argo rollout restart deploy argo-workflows-workflow-controller

在启用存档的情况下启动工作流控制器时,将在数据库中创建以下表:

  • argo_workflows
  • argo_archived_workflows
  • argo_archived_workflows_labels
  • schema_history

归档记录 GC

配置文件中的 archiveTTL 用于指定压缩到 Postgres 中的 Workflow 记录存活时间,argo Controller 会根据该配置自动删除到期的记录,若不指定该值则不会删除。

具体如下:

func (r *workflowArchive) DeleteExpiredWorkflows(ttl time.Duration) error {
	rs, err := r.session.SQL().
		DeleteFrom(archiveTableName).
		Where(r.clusterManagedNamespaceAndInstanceID()).
		And(fmt.Sprintf("finishedat < current_timestamp - interval '%d' second", int(ttl.Seconds()))).
		Exec()
	if err != nil {
		return err
	}
	rowsAffected, err := rs.RowsAffected()
	if err != nil {
		return err
	}
	log.WithFields(log.Fields{"rowsAffected": rowsAffected}).Info("Deleted archived workflows")
	return nil
}

不过删除任务默认每天执行一次,因此就算配置为 1m 分钟也不会立即删除。

func (wfc *WorkflowController) archivedWorkflowGarbageCollector(stopCh <-chan struct{}) {
	defer runtimeutil.HandleCrash(runtimeutil.PanicHandlers...)

	periodicity := env.LookupEnvDurationOr("ARCHIVED_WORKFLOW_GC_PERIOD", 24*time.Hour)
	if wfc.Config.Persistence == nil {
		log.Info("Persistence disabled - so archived workflow GC disabled - you must restart the controller if you enable this")
		return
	}
	if !wfc.Config.Persistence.Archive {
		log.Info("Archive disabled - so archived workflow GC disabled - you must restart the controller if you enable this")
		return
	}
	ttl := wfc.Config.Persistence.ArchiveTTL
	if ttl == config.TTL(0) {
		log.Info("Archived workflows TTL zero - so archived workflow GC disabled - you must restart the controller if you enable this")
		return
	}
	log.WithFields(log.Fields{"ttl": ttl, "periodicity": periodicity}).Info("Performing archived workflow GC")
	ticker := time.NewTicker(periodicity)
	defer ticker.Stop()
	for {
		select {
		case <-stopCh:
			return
		case <-ticker.C:
			log.Info("Performing archived workflow GC")
			err := wfc.wfArchive.DeleteExpiredWorkflows(time.Duration(ttl))
			if err != nil {
				log.WithField("err", err).Error("Failed to delete archived workflows")
			}
		}
	}
}

需要设置环境变量 ARCHIVED_WORKFLOW_GC_PERIOD 来调整该值,修改 argo-workflows-workflow-controller 增加 env,就像这样:

        env:
        - name: ARCHIVED_WORKFLOW_GC_PERIOD
          value: 1m

测试

接下来创建 Workflow 看下是否测试

for ((i=1; i<=10; i++)); do
cat <<EOF | kubectl create -f -
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: hello-world-
spec:
  entrypoint: whalesay
  templates:
  - name: whalesay
    container:
      image: docker/whalesay
      command: [cowsay]
      args: ["hello world $i"]
EOF
done

查看下是 postgres 中是否生成归档记录

export POSTGRES_PASSWORD=postgresadmin

kubectl run postgresql-dev-client --rm --tty -i --restart='Never' --namespace default --image docker.io/bitnami/postgresql:14.1.0-debian-10-r80 --env="PGPASSWORD=$POSTGRES_PASSWORD" --command -- psql --host pg-aio-postgresql -U postgres -d argo -p 5432

按 Enter 进入 Pod 后直接查询即可

# 查询表
argo-# \dt
                     List of relations
 Schema |              Name              | Type  |  Owner
--------+--------------------------------+-------+----------
 public | argo_archived_workflows        | table | postgres
 public | argo_archived_workflows_labels | table | postgres
 public | argo_workflows                 | table | postgres
 public | schema_history                 | table | postgres
(4 rows)

# 查询记录
argo=# select name,phase from argo_archived_workflows;
       name        |   phase
-------------------+-----------
 hello-world-s8v4f | Succeeded
 hello-world-6pl5w | Succeeded
 hello-world-qvv7d | Succeeded
 hello-world-vgjqr | Succeeded
 hello-world-g2s8f | Succeeded
 hello-world-jghdm | Succeeded
 hello-world-fxtvk | Succeeded
 hello-world-tlv9k | Succeeded
 hello-world-bxcg2 | Succeeded
 hello-world-f6mdw | Succeeded
 hello-world-dmvj6 | Succeeded
 hello-world-btknm | Succeeded
(12 rows)

# \q 退出
argo=# \q

可以看到,Postgres 中已经存储好了归档的 Workflow,这样需要查询历史记录时到 Postgres 查询即可。

将 archiveTTL 修改为 1 分钟,然后重启 argo,等待 1 至2 分钟后,再次查看

argo=#  select name,phase from argo_archived_workflows;
 name | phase
------+-------
(0 rows)

argo=#

可以看到,所有记录都因为 TTL 被清理了,这样也能保证外部 Postgres 中的数据不会越累积越多。

4. Pod 日志归档

https://argo-workflows.readthedocs.io/en/stable/configure-archive-logs/

流水线归档实现了流水线持久化,即使把集群中的 Workflow 对象删除了,也可以从 Postgres 中查询到记录以及状态等信息。

但是流水线执行的日志却分散在对应 Pod 中的,如果 Pod 被删除了,日志就无法查看了,因此我们还需要做日志归档。

配置 Pod 归档

全局配置

在 argo 配置文件中开启 Pod 日志归档并配置好 S3 信息。

具体配置如下:

和第三篇配置的 artifact 一样,只是多了一个 archiveLogs: true

artifactRepository:
  archiveLogs: true
  s3:
    endpoint: minio.default.svc:9000
    bucket: argo
    insecure: true
    accessKeySecret:
      name: my-s3-secret
      key: accessKey
    secretKeySecret:
      name: my-s3-secret
      key: secretKey

完整配置如下:

apiVersion: v1
data:
  retentionPolicy: |
    completed: 3
    failed: 3
    errored: 3
  persistence: |
    archive: true
    postgresql:
      host: pg-aio-postgresql.default.svc.cluster.local
      port: 5432
      database: argo
      tableName: argo_workflows
      userNameSecret:
        name: argo-postgres-config
        key: username
      passwordSecret:
        name: argo-postgres-config
        key: password
  artifactRepository: |
    archiveLogs: true
    s3:
      endpoint: minio.default.svc:9000
      bucket: argo
      insecure: true
      accessKeySecret:
        name: my-s3-secret
        key: accessKey
      secretKeySecret:
        name: my-s3-secret
        key: secretKey
kind: ConfigMap
metadata:
  name: argo-workflows-workflow-controller-configmap
  namespace: argo

注意:根据第三篇分析 artifact,argo 中关于 artifactRepository 的信息包括三种配置方式:

  • 1)全局配置
  • 2)命名空间默认配置
  • 3)Workflow 中指定配置

这里是用的全局配置方式,如果 Namespace 级别或者 Workflow 级别也配置了 artifactRepository 并指定了不开启日志归档,那么也不会归档的。

然后重启 argo

kubectl -n argo rollout restart deploy argo-workflows-server
kubectl -n argo rollout restart deploy argo-workflows-workflow-controller
在 Workflow & template 中配置

配置整个工作流都需要归档

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: archive-location-
spec:
  archiveLogs: true
  entrypoint: whalesay
  templates:
  - name: whalesay
    container:
      image: docker/whalesay:latest
      command: [cowsay]
      args: ["hello world"]

配置工作流中的某一个 template 需要归档。

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: archive-location-
spec:
  entrypoint: whalesay
  templates:
  - name: whalesay
    container:
      image: docker/whalesay:latest
      command: [cowsay]
      args: ["hello world"]
    archiveLocation:
      archiveLogs: true

小结

3 个地方都可以配置是否归档,就还挺麻烦的,根据官方文档,各个配置优先级如下:

workflow-controller config (on) > workflow spec (on/off) > template (on/off)

Controller Config MapWorkflow SpecTemplateare we archiving logs?
truetruetruetrue
truetruefalsetrue
truefalsetruetrue
truefalsefalsetrue
falsetruetruetrue
falsetruefalsefalse
falsefalsetruetrue
falsefalsefalsefalse

对应的代码实现:

// IsArchiveLogs determines if container should archive logs
// priorities: controller(on) > template > workflow > controller(off)
func (woc *wfOperationCtx) IsArchiveLogs(tmpl *wfv1.Template) bool {
	archiveLogs := woc.artifactRepository.IsArchiveLogs()
	if !archiveLogs {
		if woc.execWf.Spec.ArchiveLogs != nil {
			archiveLogs = *woc.execWf.Spec.ArchiveLogs
		}
		if tmpl.ArchiveLocation != nil && tmpl.ArchiveLocation.ArchiveLogs != nil {
			archiveLogs = *tmpl.ArchiveLocation.ArchiveLogs
		}
	}
	return archiveLogs
}

建议配置全局的就行了。

测试

接下来创建 Workflow 看下是否测试

cat <<EOF | kubectl create -f -
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: hello-world-
spec:
  entrypoint: whalesay
  templates:
  - name: whalesay
    container:
      image: docker/whalesay
      command: [cowsay]
      args: ["hello world"]
EOF

等待 Workflow 运行完成

# k get po
NAME                     READY   STATUS      RESTARTS   AGE
hello-world-6pl5w        0/2     Completed   0          53s
# k get wf
NAME                STATUS      AGE   MESSAGE
hello-world-6pl5w   Succeeded   55s

到 S3 查看是否有日志归档文件

argo-archive-log.png

可以看到,在指定 bucket 里已经存储了一个日志文件,以$bucket/$workflowName/$stepName 格式命名。

正常一个 Workflow 都会有多个 Step,每一个 step 分一个目录存储

内容就是 Pod 日志,具体如下:

 _____________ 
< hello world >
 ------------- 
    \
     \
      \     
                    ##        .            
              ## ## ##       ==            
           ## ## ## ##      ===            
       /""""""""""""""""___/ ===        
  ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~   
       \______ o          __/            
        \    \        __/             
          \____\______/   

5. 小结


**【ArgoWorkflow 系列】**持续更新中,搜索公众号【探索云原生】订阅,文章。


总结一下,本文主要分析了以下 3 部分内容:

  • 1)开启 GC,自动清理运行完成的 Workflow 记录,避免占用 etcd 空间
  • 2)开启流水线归档,将 Workflow 记录存储到外部 Postgres,便于查询历史记录
  • 3)开启 Pod 日志归档,将流水线每一步 Pod 日志记录到 S3,便于查询,否则 Pod 删除就无法查询了

生产使用,一般都建议开启相关的清理和归档功能,如果全存储到 etcd,难免会影响到集群性能和稳定性。

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

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

相关文章

nlohmann::json中有中文时调用dump转string抛出异常的问题

问题描述 Winodows下C开发想使用一个json库&#xff0c;使用的nlohmann::json&#xff0c;但是遇到json中使用中文时&#xff0c;转成string&#xff0c;会抛出异常。 nlohmann::json contentJson;contentJson["chinese"] "哈哈哈";std::string test con…

从卫星和飞机等不同传感器方面由QGIS 遥感分析

在地理信息科学 (GIS) 中,遥感是指从远处获取有关地球表面特征信息的行为。遥感数据是从许多不同的平台获取而来,包括卫星、飞机和具有许多不同传感器的固定仪器,包括光谱图像(相机)和激光雷达。最常见的遥感数据形式是卫星和航空图像。 为了充分实现这些照片的价值,需要…

搜狗翻译+3款工具安利,让语言不再是加班路上的绊脚石

现在不管是开跨国会议、搞国际项目合作&#xff0c;还是平时发个邮件啥的&#xff0c;语言问题常常是让人加班的主要由头。不过还算幸运&#xff0c;随着科技不断发展&#xff0c;出现了一堆既高效又智能的翻译工具。这些工具老厉害了&#xff0c;不但能大大提升我们的工作效率…

线程--线程同步

这里写目录标题 同步概念线程同步概念数据混乱原因 互斥量原理锁的注意事项1、cpu时间轮片2、建议锁总结 使用锁来管理线程同步问题产生主要函数init、destorylock、unlock代码注意事项 条件变量二级目录二级目录二级目录 信号量二级目录二级目录二级目录 一级目录二级目录二级…

算法:数字化系统的智慧核心

在当今快速发展的数字化时代&#xff0c;算法在数字化系统中扮演着至关重要的角色。从数字孪生系统到物联网应用&#xff0c;算法不仅是技术进步的推动力&#xff0c;更是实现智能化、自动化的核心。本文将探讨数字化系统中算法的重要性&#xff0c;以及它们是如何被实现和集成…

spring容器创建bean过程中使用到的几个factory

文章目录 前述BeanFactoryFactoryBeanObjectFactory 前述 spring我们可以理解为一个帮我们管理bean的容器&#xff0c;使用spring框架之前创建bean都是通过new的方式&#xff0c;使用spring框架之后&#xff0c; 我们只需要告诉spring框架我们有那些bean&#xff0c;它会帮我们…

Linux:命令行参数

目录 一、命令行参数是什么&#xff1f; 二、命令行参数作用 三、命令行参数如何传递给main函数&#xff1f; 一、命令行参数是什么&#xff1f; C语言中的main函数&#xff0c;我们发现既可以带参数&#xff0c;也可以不带参数。带参数的main函数如下&#xff1a; 参数为一…

Matplotlib - Statistical Distribution作图

1. 前言 在数据分析和统计学中&#xff0c;绘制统计分布图是非常重要的&#xff0c;因为它帮助我们直观地理解数据的特性&#xff0c;并为进一步的分析提供基础。统计分布图能够揭示数据集的结构、趋势、集中趋势和离散程度等信息&#xff0c;从而使我们更容易做出合理的假设、…

监控系统添加vcenter上的esxi主机

监控系统的软件选择&#xff1a; 监控系统要求 快速搭建 能快捷地添加vcenter上的主机&#xff08;esxi&#xff09; 能实现动态添加主机监控 可供选择的监控软件 Prometheus vmware_exporter添加 vcenter及esxi监控&#xff0c;报奇怪的错误&#xff0c;解决时间比较长&a…

高阶数据结构之哈希表基础讲解与模拟实现

程序猿的读书历程&#xff1a;x语言入门—>x语言应用实践—>x语言高阶编程—>x语言的科学与艺术—>编程之美—>编程之道—>编程之禅—>颈椎病康复指南。 前言&#xff1a; 哈希表&#xff08;Hash Table&#xff09;是一种高效的键值对存储数据结构&…

C++(进阶) ─── 继承

目录 1.继承的概念及定义 1.1继承的概念 1.2 继承定义 1.2.1定义格式 1.2.2继承关系和访问限定符 1.2.3继承基类成员访问方式的变化 2.基类和派生类对象赋值转换 3.继承中的作用域 4.派生类的默认成员函数 5.继承与友元 6. 继承与静态成员 7.复杂的菱形继承及菱形虚拟继承 8.继…

ARCGIS PRO DSK MapTool

MapTool用于自定义地图操作工具&#xff0c;使用户能够在ArcGIS Pro中执行特定的地图交互操作。添加 打开MapTool1.vb文件&#xff0c;可以看到系统已经放出MapTool1类&#xff1a; Public Sub New()将 IsSketchTool 设置为 true 以使此属性生效IsSketchTool TrueSketchTyp…

秋招测评为什么有行测题型?有没有训练题库?

为什么有行测题型&#xff0c;那这就得看看行测题型的作用了。 1、行测题可以比较全面评估应聘者的基本素质&#xff0c;包括数学能力、语言能力、逻辑思维能力等。这些能力是从事各类职业所必需的基本能力&#xff0c;对于判断应聘者的学习潜力和工作效率具有重要意义。 2、…

MySQL基于GTID同步模式搭建主从复制

系列文章目录 rpmbuild构建mysql5.7.42版本的rpm包 文章目录 系列文章目录一、mysql-5.7.42RPM包构建二、同步模式分类介绍1.异步同步模式2.半同步模式2.1.实现半同步操作流程2.2.半同步问题总结2.3.半同步一致性2.4.异步与半同步对比 3.GTID同步 三、GTID同步介绍1.gtid介绍2…

如何准备多台虚拟机并配置集群化软件

在搭建集群化软件的过程中&#xff0c;首先需要准备好多台Linux服务器。本文将详细介绍如何使用VMware提供的克隆功能来准备多台虚拟机&#xff0c;并进行必要的配置以实现集群化软件的部署。 1. 准备多台虚拟机 安装集群化软件&#xff0c;首要条件就是要有多台Linux服务器可…

nvm无法下载npm的问题

1、问题 执行 nvm install 14.21.3 命令&#xff0c;node可以正常下载成功&#xff0c;npm下载失败 2、nvm配置信息 …/nvm/settings.txt root: D:\soft\nvm path: D:\soft\nodejs node_mirror: npmmirror.com/mirrors/node/ npm_mirror: registry.npmmirror.com/mirrors/…

Java面试篇基础部分-Java内部类介绍

首先需要了解什么是内部类,内部类就是定义在类的内部的类称为内部类,内部类可以根据不同的定义方式分为静态内部类、成员内部类、局部内部类和匿名内部类。 静态内部类 定义在类体内部的通过static关键字修饰的类,被称为静态内部类。静态内部类可以访问外部类的静态变量和…

BEV学习---LSS-3--(体素坐标系及各种坐标系的理解)

1、体素坐标系 如下两个链接&#xff0c;详细介绍了对体素坐标系的理解&#xff1a; 体素坐标(voxel_coors)在mmdetection3d中的理解_体素坐标系-CSDN博客 3D目标检测中坐标系详解_点云用的什么坐标系-CSDN博客 2、自动驾驶中各种坐标系的定义及相互转换 【KnowledgeBase】…

lamp和nginx的搭建

lamp搭建 下载需要用到的 yum install php yum install php-mysql yum install php-mbstring 进入到html路径下&#xff0c;将文件复制到该路径 解压文件 将文件夹里的内容都复制到html下 在配置文件中添加页面index.php 此时打开网页提示需添加可写权限 但因为直接添加不…

Java实现发送邮件如何配置SMTP和认证信息?

Java实现发送邮件的关键要点&#xff1f;Java怎么实现邮件发送&#xff1f; Java作为一种强大的编程语言&#xff0c;提供了丰富的库和工具来实现邮件发送功能。AokSend将详细介绍如何在Java中配置SMTP服务器和认证信息&#xff0c;以实现邮件发送功能。 Java实现发送邮件&am…