KubeVela核心控制器原理浅析

news2024/11/25 22:46:19

前言

在学习 KubeVela 的核心控制器之前,我们先简单了解一下 KubeVela 的相关知识。

KubeVela 本身是一个应用交付与管理控制平面,它架在 Kubernetes 集群、云平台等基础设施之上,通过开放应用模型来对组件、云服务、运维能力、交付工作流进行统一的编排和交付。

具体来说,KubeVela 本身主要由如下几个部分组成:

核心控制器 为整个系统提供核心控制逻辑,完成诸如编排应用和工作流、修订版本快照、垃圾回收等等基础逻辑。

模块化能力控制器 负责对 X-Definitions 对象进行注册和管理。

Cluster Gateway 控制器 为操作多集群提供了统一的访问接口。

插件体系 负责注册和管理 KubeVela 的扩展功能,包括 CRD 控制器和相关模块定义。比如 VelaUX、FluxCD、Workflow 等插件。

UI 控制台 和 CLI 分别为用户提供了图形化界面和命令行界面操作 API。

本文主要介绍 KubeVela 的核心控制逻辑,如未做特殊说明,以下内容均基于 KubeVela 社区版本 v1.8.2 阐述。

OAM 应用模型

在介绍 KubeVela 的核心控制逻辑之前,我们先了解一下核心控制器作用对象,OAM 应用模型(如图 1),开放应用模型允许用户把一个现代微服务应用部署所需的所有组件和各项运维动作,描述为一个统一的、与基础设施无关的"部署计划",进而实现在混合环境中进行标准化和高效率的应用交付。

图片

图1

具体来说:

  • • 通过一个叫做应用部署计划(Application)的对象来声明一个微服务应用的完整交付流程,这其中包含了待交付组件、关联的运维动作、交付流水线等内容。

  • • 所有的待交付组件、运维动作和流水线中的每一个步骤,都遵循 OAM 规范设计为独立的可插拔模块,允许用户按照自己的需求进行组合或者定制。

  • • OAM 模型也会负责规范各个模块之间的协作接口。

应用部署计划

Application 对象是 KubeVela 核心控制器作用的最小单元,也是用户唯一需要了解的 API,它表达了一个微服务应用的部署计划。遵循 OAM 规范,一个应用部署计划(Application)由"待部署组件(Component)"、"运维动作(Trait)"、"应用的执行策略(Policy)",以及"部署工作流(Workflow)"这四部分概念组成。

组件

组件(Component)是构成微服务应用的基本单元,比如一个 Bookinfo 应用可以包含 Ratings、Reviews、Details 等多个组件。

运维特征

运维特征(Trait)负责定义组件可以关联的通用运维行为,比如服务发布、访问、治理、弹性、可观测性、灰度发布等。在 OAM 规范中,一个组件可以绑定任意个运维特征。

应用的执行策略

应用的执行策略(Policy)负责定义应用级别的部署特征,比如健康检查规则、安全组、防火墙、SLO、检验等模块。

部署执行工作流

部署执行工作流(Workflow)定义了从部署开始到达到部署终态的一条完整路径,KubeVela 会按这个流水线执行工作流中定义的各个步骤来完成整个应用交付。

KubeVela 不仅内置了多种类型组件、运维特征、策略以及工作流 Step,还支持用户自定义组件、运维特征、策略及工作流 Step,帮助用户在混合环境中进行标准化和高效率的应用交付。在实际使用时,用户通过上述 Application 对象来引用预置的组件、运维特征、应用策略、以及工作流节点模块,填写这些模块暴露的用户参数即可完成一次对应用交付的建模。

核心控制器工作原理

KubeVela vela-core 的代码结构相对来说比较简单,因为它是基于 controller-runtime 框架开发实现的控制器管理器。其中 componentdefinition、traitdefinition、policydefinition、workflowstepdefinition 控制器分别用于调谐管理组件、运维特征、策略、工作流步骤类型及其版本状态信息,而 application 控制器则用于调谐应用部署计划。

application_controller 是 KubeVela 的核心控制器,它会解析应用部署计划,生成当前应用的修订版本,并与最近一次修订版本进行比较根据是否有差异来判断修订版本状态是否需要更新,然后解析工作流 Steps 中引用的外部策略生成 manifest 并执行 apply,再然后根据应用工作流 Steps 生成工作流 Instance 和待执行的任务 Runners,最后创建工作流 Executor 并执行任务 Runners。

图片

KubeVela 核心控制器的工作原理跟 K8s Controller 是一样的,都是通过 API Server 提供的 List & Watch 接口来实时监控集群中 Application 资源对象的状态变化,当资源对象的状态变化时,控制器会尝试将其状态调谐为期望的状态。接下来笔者将对 application_controller 的调谐逻辑进行详细说明,主要包括解析应用部署计划、创建应用修订版本、应用外部策略、解析执行工作流任务等四个过程。

1、解析应用部署计划

解析应用部署计划的过程实际上就是解析应用部署计划中的组件、运维特征、策略和工作流,然后生成应用描述文件 AppFile,在生成 AppFile 之前会先检查最新的应用修订版与应用是否具有相同的 publishVersion,如果 publishVersion 相同则直接根据最新的应用修订版生成 AppFile,否则根据应用应用部署计划生成 AppFile。AppFile 作为后续调谐逻辑的基准数据模型,不仅包含了应用部署计划中的信息,还记录了调谐过程中所必须的 PolicyWorkload 等中间产物。

func (p *Parser) GenerateAppFile(ctx context.Context, app *v1beta1.Application) (*Appfile, error) {
   if ctx, ok := ctx.(monitorContext.Context); ok {
      subCtx := ctx.Fork("generate-app-file", monitorContext.DurationMetric(func(v float64) {
         metrics.AppReconcileStageDurationHistogram.WithLabelValues("generate-appfile").Observe(v)
      }))
      defer subCtx.Commit("finish generate appFile")
   }
   if isLatest, appRev, err := p.isLatestPublishVersion(ctx, app); err != nil {
      return nil, err
   } else if isLatest {
      app.Spec = appRev.Spec.Application.Spec
      return p.GenerateAppFileFromRevision(appRev)
   }
   return p.GenerateAppFileFromApp(ctx, app)
}

2、创建应用修订版本

创建应用修订版本的过程实际上就是保存应用版本,如果是更新应用的操作,会获取应用最近一次的版本信息,并与应用当前版本比较确认是否需要更新应用状态中的最近一次版本信息。

if err := handler.PrepareCurrentAppRevision(logCtx, appFile); err != nil {
        logCtx.Error(err, "Failed to prepare app revision")
        r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedRevision, err))
        return r.endWithNegativeCondition(logCtx, app, condition.ErrorCondition("Revision", err), common.ApplicationRendering)
    }
    if err := handler.FinalizeAndApplyAppRevision(logCtx); err != nil {
        logCtx.Error(err, "Failed to apply app revision")
        r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedRevision, err))
        return r.endWithNegativeCondition(logCtx, app, condition.ErrorCondition("Revision", err), common.ApplicationRendering)
    }
    logCtx.Info("Successfully prepare current app revision", "revisionName", handler.currentAppRev.Name,
        "revisionHash", handler.currentRevHash, "isNewRevision", handler.isNewRevision)
    app.Status.SetConditions(condition.ReadyCondition("Revision"))
    r.Recorder.Event(app, event.Normal(velatypes.ReasonRevisoned, velatypes.MessageRevisioned))

    if err := handler.UpdateAppLatestRevisionStatus(logCtx); err != nil {
        logCtx.Error(err, "Failed to update application status")
        return r.endWithNegativeCondition(logCtx, app, condition.ReconcileError(err), common.ApplicationRendering)
    }
    logCtx.Info("Successfully apply application revision")

3、应用外部策略

外部策略指的是未在内部策略中声明的且在工作流步骤中使用的策略,这些策略是在应用的命名空间下声明的策略。AppFile 中的策略负载 PolicyWorkload 是通过加载工作流的外部策略得到的,应用外部策略会将 AppFile 中的 PolicyWorkloads 渲染成 manifest 后分发到集群。

func LoadExternalPoliciesForWorkflow(ctx context.Context, cli client.Client, appNs string, steps []workflowv1alpha1.WorkflowStep, internalPolicies []v1beta1.AppPolicy) ([]v1beta1.AppPolicy, error) {
   policies := internalPolicies
   policyMap := map[string]struct{}{}
   for _, policy := range policies {
      policyMap[policy.Name] = struct{}{}
   }
   // Load extra used policies declared in the workflow step
   for _, _step := range steps {
      if _step.Type == DeployWorkflowStep && _step.Properties != nil {
         props := DeployWorkflowStepSpec{}
         if err := utils.StrictUnmarshal(_step.Properties.Raw, &props); err != nil {
            return nil, errors.Wrapf(err, "invalid WorkflowStep %s", _step.Name)
         }
         for _, policyName := range props.Policies {
            if _, found := policyMap[policyName]; !found {
               po := &v1alpha1.Policy{}
               if err := cli.Get(ctx, types2.NamespacedName{Namespace: appNs, Name: policyName}, po); err != nil {
                  if kerrors.IsNotFound(err) {
                     return nil, errors.Errorf("external policy %s not found", policyName)
                  }
                  return nil, errors.Wrapf(err, "failed to load external policy %s in namespace %s", policyName, appNs)
               }
               policies = append(policies, v1beta1.AppPolicy{Name: policyName, Type: po.Type, Properties: po.Properties})
               policyMap[policyName] = struct{}{}
            }
         }
      }
   }
   return policies, nil
}


func (h *AppHandler) ApplyPolicies(ctx context.Context, af *appfile.Appfile) error {
   if ctx, ok := ctx.(monitorContext.Context); ok {
      subCtx := ctx.Fork("apply-policies", monitorContext.DurationMetric(func(v float64) {
         metrics.AppReconcileStageDurationHistogram.WithLabelValues("apply-policies").Observe(v)
      }))
      defer subCtx.Commit("finish apply policies")
   }
   policyManifests, err := af.GeneratePolicyManifests(ctx)
   if err != nil {
      return errors.Wrapf(err, "failed to render policy manifests")
   }
   if len(policyManifests) > 0 {
      for _, policyManifest := range policyManifests {
         util.AddLabels(policyManifest, map[string]string{
            oam.LabelAppName:      h.app.GetName(),
            oam.LabelAppNamespace: h.app.GetNamespace(),
         })
      }
      if err = h.Dispatch(ctx, "", common.PolicyResourceCreator, policyManifests...); err != nil {
         return errors.Wrapf(err, "failed to dispatch policy manifests")
      }
   }
   return nil
}

4、解析执行工作流任务

核心控制器中最重要的也是最难理解的部分应该就属工作流任务的解析执行了,因为该部分使用了大量的函数式编程和异步编程,对于一个 go 语言的初学者来说,捋清楚这段代码并不是一件易事。接下来笔者将从解析和执行两个方面来介绍工作流任务。

解析工作流任务的过程主要包含三个步骤,第一步是注册 handlers 到 providers 中,每个 provider 中都包含了多个 handlers 处理程序,这些 handlers 主要供后续执行工作流任务使用,不管是内置的还是自定义工作流步骤,在声明 Step 的过程中可以调用特定 provider 中的 handler 来完成工作任务的执行。第二步是初始化工作流实例(执行工作流任务的阶段也会根据这个工作流实例创建工作流的执行器用于执行工作流中任务 Runners)。第三步是根据工作流实例和已注册的 handlers providers 去遍历解析工作流步骤 Step 生成工作流任务运行程序 Runners。

func (h *AppHandler) GenerateApplicationSteps(ctx monitorContext.Context,
   app *v1beta1.Application,
   appParser *appfile.Parser,
   af *appfile.Appfile) (*wfTypes.WorkflowInstance, []wfTypes.TaskRunner, error) {
   appRev := h.currentAppRev
   t := time.Now()
   defer func() {
      metrics.AppReconcileStageDurationHistogram.WithLabelValues("generate-app-steps").Observe(time.Since(t).Seconds())
   }()
   appLabels := map[string]string{
      oam.LabelAppName:      app.Name,
      oam.LabelAppNamespace: app.Namespace,
   }
   handlerProviders := providers.NewProviders()
   kube.Install(handlerProviders, h.r.Client, appLabels, &kube.Handlers{
      Apply:  h.Dispatch,
      Delete: h.Delete,
   })
   configprovider.Install(handlerProviders, h.r.Client, func(ctx context.Context, resources []*unstructured.Unstructured, applyOptions []apply.ApplyOption) error {
      for _, res := range resources {
         res.SetLabels(util.MergeMapOverrideWithDst(res.GetLabels(), appLabels))
      }
      return h.resourceKeeper.Dispatch(ctx, resources, applyOptions)
   })
   oamProvider.Install(handlerProviders, app, af, h.r.Client, h.applyComponentFunc(
      appParser, appRev, af), h.renderComponentFunc(appParser, appRev, af))
   pCtx := velaprocess.NewContext(generateContextDataFromApp(app, appRev.Name))
   renderer := func(ctx context.Context, comp common.ApplicationComponent) (*appfile.Workload, error) {
      return appParser.ParseWorkloadFromRevisionAndClient(ctx, comp, appRev)
   }
   multiclusterProvider.Install(handlerProviders, h.r.Client, app, af,
      h.applyComponentFunc(appParser, appRev, af),
      h.checkComponentHealth(appParser, appRev, af),
      renderer)
   terraformProvider.Install(handlerProviders, app, renderer)
   query.Install(handlerProviders, h.r.Client, nil)

   instance := generateWorkflowInstance(af, app)
   executor.InitializeWorkflowInstance(instance)
   runners, err := generator.GenerateRunners(ctx, instance, wfTypes.StepGeneratorOptions{
      Providers:       handlerProviders,
      PackageDiscover: h.r.pd,
      ProcessCtx:      pCtx,
      TemplateLoader:  template.NewWorkflowStepTemplateRevisionLoader(appRev, h.r.dm),
      Client:          h.r.Client,
      StepConvertor: map[string]func(step workflowv1alpha1.WorkflowStep) (workflowv1alpha1.WorkflowStep, error){
         wfTypes.WorkflowStepTypeApplyComponent: func(lstep workflowv1alpha1.WorkflowStep) (workflowv1alpha1.WorkflowStep, error) {
            copierStep := lstep.DeepCopy()
            if err := convertStepProperties(copierStep, app); err != nil {
               return lstep, errors.WithMessage(err, "convert [apply-component]")
            }
            copierStep.Type = wfTypes.WorkflowStepTypeBuiltinApplyComponent
            return *copierStep, nil
         },
      },
   })
   if err != nil {
      return nil, nil, err
   }
   return instance, runners, nil
}

生成任务运行程序的过程是根据应用部署计划中配置的工作流,遍历工作流步骤 Step 参数及类型,加载 Step 类型模板,然后根据 Step 参数和模板编译生成 task。taskRunner 包含三个参数,第一个参数是工作流步骤名称 wfstepName,用于标识任务名称;第二个参数是 checkPending 函数,用于检查是否挂起任务运行;第三个参数是 run 函数,也就是任务的实际运行程序,加载 Step 模板并接收 Step 配置参数完成任务编译的过程就是在这个 run 函数中完成的。

func GenerateRunners(ctx monitorContext.Context, instance *types.WorkflowInstance, options types.StepGeneratorOptions) ([]types.TaskRunner, error) {
   ctx.V(options.LogLevel)
   subCtx := ctx.Fork("generate-task-runners", monitorContext.DurationMetric(func(v float64) {
      metrics.GenerateTaskRunnersDurationHistogram.WithLabelValues("workflowrun").Observe(v)
   }))
   defer subCtx.Commit("finish generate task runners")
   options = initStepGeneratorOptions(ctx, instance, options)
   taskDiscover := tasks.NewTaskDiscover(ctx, options)
   var tasks []types.TaskRunner
   for _, step := range instance.Steps {
      opt := &types.TaskGeneratorOptions{
         ID:              generateStepID(instance.Status, step.Name),
         PackageDiscover: options.PackageDiscover,
         ProcessContext:  options.ProcessCtx,
      }
      for typ, convertor := range options.StepConvertor {
         if step.Type == typ {
            opt.StepConvertor = convertor
         }
      }
      task, err := generateTaskRunner(ctx, instance, step, taskDiscover, opt, options)
      if err != nil {
         return nil, err
      }
      tasks = append(tasks, task)
   }
   return tasks, nil
}

执行工作流任务的过程也包含三个步骤,第一步是根据工作流实例创建工作流执行器。第二步是调用执行器的 ExecuteRunners 方法按顺序执行工作流任务运行程序。第三步则是根据工作流任务运行程序的执行结果(即工作流执行状态)和工作流实例状态的 EndTime 来调谐应用状态或 gc ResourceTrackers,其中 ResourceTrackers 主要是用来跟踪和维护应用管理的资源,会在转发应用管理的资源清单之前在 HubCluster 中进行创建,可以确保在删除应用程序时能真正删除所有托管的资源。

func (w *workflowExecutor) ExecuteRunners(ctx monitorContext.Context, taskRunners []types.TaskRunner) (v1alpha1.WorkflowRunPhase, error) {
   InitializeWorkflowInstance(w.instance)
   status := &w.instance.Status
   dagMode := status.Mode.Steps == v1alpha1.WorkflowModeDAG
   cacheKey := fmt.Sprintf("%s-%s", w.instance.Name, w.instance.Namespace)

   allRunnersDone, allRunnersSucceeded := checkRunners(taskRunners, w.instance.Status)
   if status.Finished {
      StepStatusCache.Delete(cacheKey)
   }
   if checkWorkflowTerminated(status, allRunnersDone) {
      if isTerminatedManually(status) {
         return v1alpha1.WorkflowStateTerminated, nil
      }
      return v1alpha1.WorkflowStateFailed, nil
   }
   if checkWorkflowSuspended(status) {
      return v1alpha1.WorkflowStateSuspending, nil
   }
   if allRunnersSucceeded {
      return v1alpha1.WorkflowStateSucceeded, nil
   }

   wfCtx, err := w.makeContext(ctx, w.instance.Name)
   if err != nil {
      ctx.Error(err, "make context")
      return v1alpha1.WorkflowStateExecuting, err
   }
   w.wfCtx = wfCtx

   if cacheValue, ok := StepStatusCache.Load(cacheKey); ok {
      // handle cache resource
      if len(status.Steps) < cacheValue.(int) {
         return v1alpha1.WorkflowStateSkipped, nil
      }
   }

   e := newEngine(ctx, wfCtx, w, status, taskRunners)

   err = e.Run(ctx, taskRunners, dagMode)
   if err != nil {
      ctx.Error(err, "run steps")
      StepStatusCache.Store(cacheKey, len(status.Steps))
      return v1alpha1.WorkflowStateExecuting, err
   }

   StepStatusCache.Store(cacheKey, len(status.Steps))
   if feature.DefaultMutableFeatureGate.Enabled(features.EnablePatchStatusAtOnce) {
      return e.status.Phase, nil
   }
   return e.checkWorkflowPhase(), nil
}

执行工作流任务,首先会创建工作流的执行引擎,然后调用引擎的 Run 方法顺序执行或并行执行 taskRunner,默认 steps 以 StepByStep 顺序执行,subSteps 以 DAG 并行执行。顺序执行会遍历 taskRunners,并依次调用 taskRunner 的 run 方法,run 方法的内容就是上文提到的生成 taskRunner 时的 run 函数,根据 Step 参数配置和加载的 Step 模板完成工作流步骤任务编译后,会执行 Step CUE Template 中调用的 provider handlers(即上文提到的在解析工作流任务阶段注册的各类型 providers handlers),从而完成 taskRunner 执行。应用部署计划中用到最多的工作流步骤类型是 deploy,deploy 是一个的功能强大的组件部署步骤,使用策略进行多集群交付。另外使用最多的应用策略是 topology,topology 描述了组件应该部署到的集群和命名空间。 

deploy.cue

import (
 "vela/op"
)

"deploy": {
 type: "workflow-step"
 annotations: {
  "category": "Application Delivery"
 }
 labels: {
  "scope": "Application"
 }
 description: "A powerful and unified deploy step for components multi-cluster delivery with policies."
}
template: {
 deploy: op.#Deploy & {
  policies:                 parameter.policies
  parallelism:              parameter.parallelism
  ignoreTerraformComponent: parameter.ignoreTerraformComponent
 }
 parameter: {
  //+usage=If set to false, the workflow will suspend automatically before this step, default to be true.
  auto: *true | bool
  //+usage=Declare the policies that used for this deployment. If not specified, the components will be deployed to the hub cluster.
  policies: *[] | [...string]
  //+usage=Maximum number of concurrent delivered components.
  parallelism: *5 | int
  //+usage=If set false, this step will apply the components with the terraform workload.
  ignoreTerraformComponent: *true | bool
 }
}

总结

本文主要介绍了 KubeVela 核心控制器的工作原理,包括核心控制逻辑中解析应用部署计划、创建应用修订版本、应用外部策略、解析执行工作流任务等四个部分,本篇作为综述帮助大家初步了解 KubeVela 核心控制器的技术要点和运行机制,后续我们将分别从上述四个部分进行详细解读。

参考文献

应用管理平台 kubevela:https://qiankunli.github.io/2022/10/23/kubevela.html

kubevela 源码分析:https://qiankunli.github.io/2022/11/06/kubevela_source.html

KubeVela 源码仓库:https://github.com/kubevela/kubevela

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

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

相关文章

王者荣耀,,,,,

第一步是创建项目 项目名自拟 第二部创建个包名 来规范class 然后是创建类 GameFrame 运行类 package com.sxt; import java.awt.Graphics; import java.awt.Image; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener;…

Linux中vi常用命令-批量替换

在日常服务器日志查看中常用到的命令有grep、tail等&#xff0c;有时想查看详细日志&#xff0c;用到vi命令&#xff0c;记录下来&#xff0c;方便查看。 操作文件&#xff1a;test.properites 一、查看与编辑 查看命令&#xff1a;vi 文件名 编辑命令&#xff1a;按键 i&…

Go 语言 Printf 函数和格式化动词详解

Printf() 函数可以使用多种格式化动词对输出进行格式化。下面是可以与所有数据类型一起使用的一些通用格式化动词&#xff1a; 通用格式化动词&#xff1a; 以下动词适用于所有数据类型&#xff1a; 动词描述%v以默认格式打印值%#v以 Go 语法格式打印值%T打印值的类型%%打印百…

帮管客CRM 文件上传漏洞复现

0x01 产品简介 帮管客CRM是一款集客户档案、销售记录、业务往来等功能于一体的客户管理系统。帮管客CRM客户管理系统&#xff0c;客户管理&#xff0c;从未如此简单&#xff0c;一个平台满足企业全方位的销售跟进、智能化服务管理、高效的沟通协同、图表化数据分析帮管客颠覆传…

(三) Windows 下 Sublime Text 3 配置Python环境和Anaconda代码提示

一&#xff1a;新建一个 Python3.7 编译环境。 1 Tools--Build System--New Build System... 修改前&#xff1a; 修改后&#xff1a; 内容&#xff1a; {"cmd":["C:\\Python\\Python37-32\\python.exe","-u","$file"],"file_r…

JAVA小游戏简易版王者荣耀

第一步是创建项目 项目名自拟 第二部创建个包名 来规范class 然后是创建类 GameFrame 运行类 package com.sxt; import java.awt.Graphics; import java.awt.Image; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener;…

2023年初中生古诗文大会复选最后6天备考策略和更新的在线模拟题

今天是2023年11月26日&#xff0c;星期日&#xff0c;距离2023年第八届上海市中学生古诗文大会复选&#xff08;复赛&#xff09;还有六天&#xff08;2023年12月2日上午举办&#xff09;&#xff0c;相信各位晋级的小学霸们正在繁忙的学业之余抓紧备考。 为了帮助孩子们更有效…

GLP-1 , GLP-1R

-- 6VCB_GLP-1R G_protein, GLP-1 peptidea positive allosteric modulator

UE5人物残影学习(材质实现)

学习视频 UE4简单的材质球残影人教学&#xff0c;你学会了吗&#xff01;_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1rY411q7Yb/?spm_id_from333.788.top_right_bar_window_history.content.click 结果预览 1.创建残值&#xff0c;混合模式勾选半透明 “混合模…

【hacker送书第3期】OpenCV轻松入门:面向Python(第2版)

第3期图书推荐 内容简介作者简介图书目录专家推荐参与方式 内容简介 本书基于面向 Python 的 OpenCV(OpenCV for Python)&#xff0c;介绍了图像处理的方方面面。本书以 OpenCV 官方文档的知识脉络为主线&#xff0c;并对细节进行补充和说明。书中不仅介绍了 OpenCV 函数的使用…

基于51单片机的智能垃圾桶硬件设计

**单片机设计介绍&#xff0c; 基于51单片机的智能垃圾桶硬件设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于51单片机的智能垃圾桶设计旨在通过传感器和控制电路实现智能化的垃圾桶功能。下面是一个简要的硬件设计介绍&…

如何在Ubuntu系统上安装Git

简单介绍 Git是一个开源的分布式版本控制系统&#xff0c;用于敏捷高效地处理任何或小或大的项目。Git是Linus Torvalds为了帮助管理Linux内核开发而开发的一个开放源码的版本控制软件。Git 与常用的版本控制工具CVS&#xff0c;Subversion 等不同&#xff0c;它采用了分布式版…

QT网络协议知识体系(一)

//获取主机的名称和ip地址 //获取主机的所有信息

Java | The last packet sent successfully to the server was xxx milliseconds ago

最近在部署代码后&#xff0c;后端总是会遇到这个问题&#xff0c;设备通道在访问数据库时经常会报错&#xff0c;在搜集大量资料后我以为是配置问题&#xff0c;首先要保证&#xff1a; &#xff08;1&#xff09;首先确定jdbc.url地址是正确的 &#xff08;2&#xf…

设计一个算法,将链表中所有结点的链接方向“原地”逆转,即要求仅利用原表的存储空间,换句话说,要求算法的空间复杂度为O(1)

设计一个算法&#xff0c;将链表中所有结点的链接方向“原地”逆转&#xff0c;即要求仅利用原表的存储空间&#xff0c;换句话说&#xff0c;要求算法的空间复杂度为O&#xff08;1&#xff09; 代码思路&#xff1a; 这里要求不用额外空间&#xff0c;那么就要考虑链表自身的…

路径规划之D*算法

系列文章目录 路径规划之Dijkstra算法 路径规划之Best-First Search算法 路径规划之A*算法 路径规划之D *算法 路径规划之D*算法 系列文章目录前言一、D*算法1.1 起源1.2 思想1.3 阶段1.4 个人理解1.5 应用 前言 之前说过A是目前应用最广泛的寻路算法&#xff0c;但是A算法存…

第 373 场 LeetCode 周赛题解

A 循环移位后的矩阵相似检查 模拟 class Solution { public:bool areSimilar(vector<vector<int>> &mat, int k) {int m mat.size(), n mat[0].size();k % n;auto g mat;for (int i 0; i < m; i)if (i & 1)rotate(mat[i].begin(), mat[i].begin() …

林曦的小世界:不在担心与顾虑中蹉跎时间,Just Do It

内容来自林曦的小世界      先提问&#xff1a;你有没有过这样的经历&#xff0c;一项很想学的技艺&#xff0c;一件想做许久的事情&#xff0c;却始终下不了决心&#xff0c;担心左&#xff0c;担心右&#xff0c;彷徨犹豫间&#xff0c;时间过去了&#xff0c;这件事仍未…

苹果的未来:分析其成长策略和 2 兆美元以上的野心

Apple正在蕴育新的创新增长。作为世界上最有价值的公司&#xff0c;苹果公司拥有超过2万亿美元的市值和超过1000亿美元的年利润&#xff0c;并成功用十几年实践去打造和培育了一个硬件、软件和服务“三位一体”的商业生态&#xff0c;始终坚持以用户体验为先&#xff0c;创新极…

有关HarmonyOS-ArkTS的Http通信请求

一、Http简介 HTTP&#xff08;Hypertext Transfer Protocol&#xff09;是一种用于在Web应用程序之间进行通信的协议&#xff0c;通过运输层的TCP协议建立连接、传输数据。Http通信数据以报文的形式进行传输。Http的一次事务包括一个请求和一个响应。 Http通信是基于客户端-服…