KubeVela核心控制器原理浅析
前言
在学习 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.Specreturn 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 := internalPoliciespolicyMap := map[string]struct{}{}for _, policy := range policies {policyMap[policy.Name] = struct{}{}}// Load extra used policies declared in the workflow stepfor _, _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.currentAppRevt := 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.WorkflowStepTypeBuiltinApplyComponentreturn *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.TaskRunnerfor _, 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.StatusdagMode := status.Mode.Steps == v1alpha1.WorkflowModeDAGcacheKey := 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 = wfCtxif cacheValue, ok := StepStatusCache.Load(cacheKey); ok {// handle cache resourceif 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.policiesparallelism: parameter.parallelismignoreTerraformComponent: 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
相关文章:

KubeVela核心控制器原理浅析
前言 在学习 KubeVela 的核心控制器之前,我们先简单了解一下 KubeVela 的相关知识。 KubeVela 本身是一个应用交付与管理控制平面,它架在 Kubernetes 集群、云平台等基础设施之上,通过开放应用模型来对组件、云服务、运维能力、交付工作流进…...

迎接“全全闪”时代 XSKY星辰天合发布星海架构和星飞产品
11 月 17 日消息,北京市星辰天合科技股份有限公司(简称:XSKY星辰天合)在北京首钢园举办了主题为“星星之火”的 XSKY 星海全闪架构暨星飞存储发布会。 (图注:XSKY星辰天合 CEO 胥昕) XSKY星辰天…...
[架构相关]基础架构设计原则
基础架构设计原则 文章目录 基础架构设计原则一、可用性(Availability)1.1、引入冗余1.2、负载均衡1.3、故障转移1.4、备份和恢复策略 二、可扩展性(Scalability)2.1 水平扩展2.2 垂直扩展2.3 弹性扩展 三、可靠性(Rel…...
测试在 Oracle 下直接 rm dbf 数据文件并重启数据库
创建一个新的表空间并创建新的用户,指定新表空间为新用户的默认表空间 create tablespace zzw datafile /oradata/cesdb/zzw01.dbf size 10m;zzw用户已经创建过,这里修改其默认表空间 alter user zzw quota unlimited on zzw; alter user zzw default …...

【开源】基于JAVA的计算机机房作业管理系统
项目编号: S 017 ,文末获取源码。 \color{red}{项目编号:S017,文末获取源码。} 项目编号:S017,文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 登录注册模块2.2 课程管理模块2.3 课…...
Ubuntu 配置静态 IP
Ubuntu 18 开始可以使用netplan配置网络。配置文件位于/etc/netplan/xxx.yaml中,netplan默认是使用NetworkManager来配置网卡信息的。 修改配置文件: 1、打开文件编辑:sudo vi 01-network-manager-all.yaml原文件内容如下:netwo…...

Spring Cloud实战 |分布式系统的流量控制、熔断降级组件Sentinel如何使用
专栏集锦,大佬们可以收藏以备不时之需 Spring Cloud实战专栏:https://blog.csdn.net/superdangbo/category_9270827.html Python 实战专栏:https://blog.csdn.net/superdangbo/category_9271194.html Logback 详解专栏:https:/…...

第六届 传智杯初赛B组
文章目录 A. 字符串拼接🍻 AC code B. 最小差值🍻 AC code C. 红色和紫色🍻 AC code D. abb🍻 AC code E. kotori和素因子🍻 AC code F. 红和蓝🍻 AC code 🥰 Tips:AI可以把代码从 j…...

文档向量化工具(二):text2vec介绍
目录 前言 text2vec开源项目 核心能力 文本向量表示模型 本地试用 安装依赖 下载模型到本地(如果你的网络能直接从huggingface上拉取文件,可跳过) 运行试验代码 前言 在上一篇文章中介绍了,如何从不同格式的文件里提取…...
vscode中pylance无法显示outline无法跳转
当打开的workspce中有较多的文件时,pylance需要分析的文件太多,导致卡住,无法分析到对应的python文件 常见的情况是,当我们在workspace中包含了data文件夹(通常是通过软连接方式把数据集链接过来)…...
番外篇之通讯录
前言:用到的知识点有枚举、结构体、数组,快速排序(用的名字排序) 下面是测试函数: test.c #define _CRT_SECURE_NO_WARNINGS 1 #include"contact.h" void menu() {printf("*************************…...

学生信息管理系统程序Python
系统主界面 在该界面中可以选择要使用功能对应的菜单进行不同的操作。在选择功能菜单时,有两种方法, 一种是输入1,另一种是按下键盘上的↑或↓方向键进行选择。这两种方法的结果是一样的,所以使用哪种方法都可以。 (…...
[js] for forEach for of 循环里await关键字的用法
1、for:循环中使用await的写法(生效) async function loop(){for( let i0; i<array.length; i ){let datas await getDatas()break} }2、forEach:循环中使用await的写法(不生效): array.f…...

Linux面试题(二)
目录 17、怎么使一个命令在后台运行? 18、利用 ps 怎么显示所有的进程? 怎么利用 ps 查看指定进程的信息? 19、哪个命令专门用来查看后台任务? 20、把后台任务调到前台执行使用什么命令?把停下的后台任务在后台执行起来用什么命令? 21、终止进程用什么命令…...

电源控制系统架构(PCSA)之系统控制处理器组件
目录 6.4 系统控制处理器 6.4.1 SCP组件 SCP处理器Core SCP处理器Core选择 SCP处理器核内存 系统计数器和通用计时器 看门狗 电压调节器控制 时钟控制 系统控制 信息接口 电源策略单元 传感器控制 外设访问 系统访问 6.4 系统控制处理器 系统控制处理器(SCP)是…...

《已解决: ImportError: Keras requires TensorFlow 2.2 or higher 问题》
🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页: 🐅🐾猫头虎的博客🎐《面试题大全专栏》 🦕 文章图文并茂🦖…...

在 CentOS 7 上安装 MySQL 8
在 CentOS 7 上安装 MySQL 8 步骤 1: 添加 MySQL Yum 存储库 首先,我们需要添加 MySQL Yum 存储库。打开终端并执行以下命令: sudo yum install -y https://repo.mysql.com/mysql80-community-release-el7-3.noarch.rpm步骤 2: 导入 MySQL GPG 公钥 …...
从二极管到linux服务器
软件设计: os: 批处理系统: 轮询系统:单片机裸机开发 实时系统:ucosii,rtos,rt-thread、风和系统、liteos(主要是海思系列soc在用)等 非实时系统:linux 对os任务切换时寄存器的功能有理解。 对ipc机制有理解。 bsp: 需要对寄存器、单片机内部总线、iic、spi、uart、c…...

设计模式-16-Spring源码中的设计模式
1-Spring之观察者模式 Java、Google Guava都提供了观察者模式的实现框架。Java提供的框架比较简单,只包含java.util.Observable和java.util.Observer两个类。Google Guava提供的框架功能比较完善和强大:通过EventBus事件总线来实现观察者模式。实际上&am…...
Leetcode 2948. Make Lexicographically Smallest Array by Swapping Elements
Leetcode 2948. Make Lexicographically Smallest Array by Swapping Elements 1. 解题思路2. 代码实现 题目链接:2948. Make Lexicographically Smallest Array by Swapping Elements 1. 解题思路 这一题其实思路上就是分组排序,显然,对于…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...

3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...

深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用
文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么?1.1.2 感知机的工作原理 1.2 感知机的简单应用:基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...

vulnyx Blogger writeup
信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面,gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress,说明目标所使用的cms是wordpress,访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...
go 里面的指针
指针 在 Go 中,指针(pointer)是一个变量的内存地址,就像 C 语言那样: a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10,通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...