GitOps实践指南:GitOps能为我们带来什么?
Git,作为开发过程中的核心工具,提供了强大的版本控制功能。即便在写代码的时候稍微手抖一下,我们也能通过 Git 的差异对比(diff)轻松追踪到庞大工程中的问题,确保代码的准确与可靠。这种无与伦比的自省能力,不仅提高了工作效率,也带来了极大的安全感,让一切都有迹可循。
如果上面的这些能力在运维上也能实现,是不是稳定性会特别好,变更时候想出故障都难?任何非预期的变化都能通过diff对比出来?是的。GitOps就是基于这些触发点而产生的。2017年Weaveworks的CEO Alexis Richardson 提出了这个概念:通过一个模型抽象使得整个系统的操作变得自动化,使用Git来承载这个模型。

不过这么多年过去了,GitOps相关的产品和平台层出不穷,除了WeaveWorks,似乎大家对GitOps各有各的理解和实践:似乎解决了一些问题,但似乎又没完全解决。那么,问题到底出在哪里?GitOps到底能为我们带来些什么?本文尝试展开讲讲这个问题。
首先,我们先从日常的工作开始思考,我们到底希望GitOps帮我们实现哪些目标?我先列了三个目标,大家看看是不是也有类似的想法:
- 变更自动化:只要把代码提交到git中,就进行自动构建、自动测试、自动部署,不要让我到处点按钮。
- 变更代码化:变更到底会变哪些东西,影响哪些基础设施,能否直接在代码中就能看出?不要在一句“风险可控”背后藏着一个没人能理得清的操作流程。
- 变更透明化:变更信息中包含所有的源码变化,不要包含一些无法展开的引用关系:比如容器镜像tag变化需要有对应的构建源码的变化。
带着这三个目标,我们先去找找业界对于GitOps的定义,看看是否能找到一些线索:
一、GitOps是什么
云原生基金会(CNCF)在2021年成立了一个OpenGitOps工作组,旨在推广GitOps的最佳实践。在这个工作组的官网首页放着四条GitOps的原则:
- Declarative:声明式
- Versioned and Immutable:版本化且不可变
- Pulled Automatically:自动拉取
- Continuously Reconciled:持续调和
这四条原则基本是WeaveWorks的GitOps定义的一个深化,而且较多地推荐使用Flux进行GitOps实践,有关这块介绍,我们会在第三章展开。鉴于这个工作组中能得到的有效信息实在太少,我们继续寻找业界有关GitOps的介绍。
GitLab出版过一本电子书叫《A beginners guide to GitOps》(下载地址在参考材料中),在书中提出了一个概念公式:GitOps = IaC + MRs + CI/CD。这个概念就比前面的GitOps四原则更具体一些了:
- IaC (Infrastructure as Code): 指出GitOps管理模型就应该是IaC,这比原则中的Declarative更进了一步:这不仅仅是关于声明性配置,而是关于将整个基础设施的管理嵌入到代码之中,从而实现更精确和可重复的部署过程。
- MRs (Merge Requests):明确指出,在 GitOps 中,管理动作不是通过直接提交代码来完成,而是通过一系列的合并请求(Merge Request)。这种方法允许进行更深入的风险评估和审计,在代码合并到生产环境之前确保质量和安全。
- CI/CD (Continuous Integration/Continuous Delivery):GitOps 的最终目标是实现代码的自动化集成和持续交付:既确保严格的测试验证,又能加速开发周期,保持高质量和稳定性。
在第四章,我们会展开说说如何基于这个公式去进行GitOps实践。
本来我的觉得GitOps差不多概念也就这样了,基本上几大厂商的观点都看过了。没想到又在亚马逊上看到了一本书《Repeatability, Reliability, and Scalability through GitOps》(购买链接在参考材料中),阅读之后对于GitOps的认知又拓宽了不少,作者在书中提出了三种类型的GitOps:
- The Original GitOps: 原始GitOps,即k8s做IaC,然后落地GitOps的方案。
- The Purist GitOps: 纯粹GitOps,不一定基于k8s,但是基于某种IaC+GitOps进行落地的方案,使用终态的方式来进行管理。
- The Verified GitOps: 验证型GitOps,也不一定有终态管理,只要确保整个过程中有git diff能验证即可。
说实话,第三种类型的GitOps让我豁然开朗,这种GitOps确实更贴近实际落地:IaC的改造推广常常会有较大的成本,而在没有改造完之前,就不能享受GitOps带来的好处了吗?作者告诉我们一种选择,这种以验证为目的GitOps就行。

作者为了让我们更能深入地理解这个验证型GitOps,还画了一张流程示意图:红色部分为人工,绿色部分为自动。从图中可以看到,自动的流程在右侧自动流转,左侧分别是development、devops、sre三种角色在根据git diff进行验证审计。
二、Infrastructure as Code要怎么做
通过前面一些概念,我们能看到声明式&IaC在gitops中占据非常重要的部分。可以这样说,如果IaC无法承载所有的运维编排能力,运维编排需求可以会外溢到其他系统或平台上,那么这部分外溢的逻辑就无法使用Git来追踪了:所以这个IaC的声明式的编排能力会变得尤为重要。
在分析IaC方案之前,我们先来看看声明式编程(Declarative)和命令式编程(Imperative)的区别:
- 常见的声明式编程语言有SQL和HTML,我们来写个常见的例子:
# 使用SQL查询数据库中的年龄超过30岁的用户
SELECT name, age FROM users WHERE age > 30;
- 常见的命令式编程包括过程式编程(Procedural Programming)和面向对象编程(Object-Oriented Programming),还是上面的这个查询年龄超过30岁的用户的例子,使用Python写则会变成这样
users = [{"name": "Alice", "age": 25}, {"name": "Bob", "age": 35}, {"name": "Carol", "age": 32}]
users_over_30 = []
for user in users:if user["age"] > 30:users_over_30.append(user)
通过这两个例子大家可以明显地感觉到声明式编程的可读性更强,描述结果并且只关注结果;与之相对应的是,过程式编程需要理解整个过程,才能知道这段代码在干什么。
我们来列一下市面上比较流行的IaC方案:
- Terraform:HashiCorp公司于2014年创建,提供人们使用HashiCorp Configuration Language(HCL)来声明式编写基础设施的能力。Terraform的社区生态非常活跃,其provider支持几乎所有的云厂商。
- Crossplane:Crossplane 是在 2018 年由 Upbound 公司推出,旨在通过扩展 Kubernetes 的能力,提供统一的多云和混合云环境下的基础设施即服务(IaaS)管理解决方案。
- Pulumi: Pulumi是在2017年由几位前微软员工成立的Pulumi Corp 创立的。与Terraform等工具不同,Pulumi允许使用常用的编程语言(如Python、TypeScript、JavaScript等)来定义和部署基础设施,从而使得基础设施代码更容易理解和维护。
光这样讲会有些抽象,我们围绕着一个例子来比较一下这几种IaC方案,如何来创建一个阿里云ECS:

从上面的例子可以看到,Terraform作为早期的IaC方案,这样声明确实大大简化基础设施的使用成本,只要几行代码就能申请出一台虚拟机。Crossplane是扩展k8s的能力,所以每种基础设施都是一个CRD。但标准的YAML也带来一个问题就是遇到一些复杂的条件渲染,就无能为力了,它不像Terraform的HCL能够在语言中增加一些模板类的控制语句,它需要通过自定义控制器来解决,这无疑增加了IaC交付的难度。
Pulumi明显吸收了Terraform的经验:既然IaC中的模板等控制流无法避免,与其想方设法设计包含过程函数的声明式编程语言,不如我干脆就还是用普通编程语言,只是在SDK中引导用户声明式编程,保留用户使用控制语句的权利。
虽然各种IaC提供了非常方便的基础设施交付方案,但事实上如果真正进行工程化的使用,就会遇到一个状态存储的问题。每个IaC都必须将状态存储下来,才是一份完整的幂等声明,如下图所示:

如果没有这份状态文件,反复执行同一份IaC声明,就会不断地创建资源。对于GitOps而言,所有的变化都必须可以被git commit追踪:这份状态文件同样至关重要,已经创建过的资源不能再创建,否则可能就会产生生产故障。
由于状态文件维护的复杂性,但有些用户又垂涎IaC声明式交付的便利,就会衍生出这样的半吊子的使用场景:只用IaC进行资源创建,后续维护依然使用原有的模式,这样就不用管理创建时产生的状态文件。 事物存在即合理,也不能说这样的方案有什么问题,但确实从另外一个侧面反映出一个问题:IaC的声明代码无法表达全部终态,必须要加上状态文件才构成幂等完整的终态。这个问题也带给我们了一个提醒:GitOps需要将状态也纳入进去,否则无法达成前面提到的“变更透明化”这个目标。
三、GitOps相关产品分析
分析完了IaC方案,我们继续来看GitOps相关产品,我们来分析一下:由于GitOps是在k8s社区中发展起来,所以现在市面上的GitOps基本都是围绕着k8s容器编排展开,如我们第一章所提到 The Original GitOps(原始GitOps)。
我们首先来看看最老牌的CI/CD工具Jenkins在k8s下的演进:Jenkins X。

通过这张架构图我们可以看出Jenkins X在k8s场景下,放弃了原有的JenkinsFile的构建编排能力,转而使用Tekton来进行构建流水线的编排。Jenkins整体的侧重CI的,在Jenkins X中依然如此,流水线主要是为CI服务,在流水线的末端会有几个CD节点将实例部署到目标环境。这种CI/CD串联的方案适合小型工程,对于大一些的工程,其CD能力则会有些捉襟见肘,因为毕竟只是几个shell命令的组合。
从整体上看,Jenkins X能够实现“变更自动化”,但其他方面则稍显不足。
然后我们来看一下ArgoCD,这是在GitOps领域的当红炸子鸡,它通过一个Application的CRD实现了k8s的YAML的控制权的转移。需要操作k8s集群的需求,转变成了在git中提交git commit。下面是ArgoCD的GitOps架构图:

ArgoCD和Jenkins X这类方案比起来少了很多功能,但架不住它简单好用:只需要把Helm包推到git上,他就会被自动部署到k8s环境之上。ArgoCD的这种纯CD的方案有个自动化的短板就是:在CI环节构建完的容器镜像,需要手工把他们拷贝到Helm包中,然后提交git。虽然说这种镜像地址的人肉拷贝也是个常见的行为,但是毕竟我们当前是在调研完整的GitOps方案,如果从源头上就不支持,那只能说他在“变更自动化”这块存在不足。
最后,我们来看一下FluxCD,这是GitOps概念提出者WeaveWorks公司的开源产品。这个FluxCD确实属于对GitOps的践行产物,在ArgoCD中仅用Application盖住的概念,在这里被分成了Source、Helm、Image Automation 等,分别有对应的控制器来负责干活:

不得不说,FluxCD中的概念还是非常完整的,同时它也有ImageUpdateAutomation这样一个对象,专门用来进行镜像的自动更新、如果在镜像仓库中出现新镜像,它就会自动提交一个git commit把对应的镜像字段更新,这个机制完美地解决了ArgoCD中人手工拷贝的问题:不过度介入CI环节,但对于CI环节的制品能自动感知,自动部署。
四、GitOps动手实践
通过前三章节的介绍,我们对GitOps的构成基本清晰了,复用一下GitLab中的那个公式基本就是 GitOps = IaC + MRs + CI/CD,不过从IaC开始做存在较大挑战,毕竟需要从源头改变整个链路,我们可以先从CI/CD开始。
我们先尝试解决第一个问题“变更透明化”:增强对于构建环节的制品采集能力,将每个制品的sha256、git commit以及名称全部采集到数据库。尽量不改变原有的CI流程,在CI最终出制品的节点后将信息采回。

有了制品信息之后,我们就能对每次复杂的变更进行源码下探,遇到镜像变更,就制品仓库中找到对应的源码进一步下探(inspect),使得原本两眼一抹黑的镜像变化,也变得清晰透明。
事实上,从The Verified GitOps(验证型GitOps)的角度看,基于制品做变更透视方案已经将GitOps达成了。不过我们一开始定的目标还有两个没有完全达成:变更自动化、变更代码化。自动化这块其实反倒是简单,在CI的最后一个节点接上变更平台的接口即可。变更代码化这块则需要我们设计一套声明式的方案来编排整个变更链路:这里最大的挑战是对于已有流程和平台的整合。
IaC的语法设计是个苦差事,这么多大牛前仆后继,市面的IaC语言似乎总无法完全用声明的方式,解决基础设施编排的问题。Pulumi这套方案给了我们很大的启发,既然这样,为何不直接用现有的编程语言?
于是我们借鉴Pulumi设计了一套SRE Stack方案:

- 我们提供sre_stack供用户在熟悉的编程环境(以Python为例)下编排他的变更过程。
- IaC代码 + 状态文件构成了完整的变更描述,弥补了terraform这类的IaC工具在状态管理中的不足。
- 状态文件本质上也是一张数据快照,对于包含外部系统的数据的一个快照,所以可以友好地实现跨平台的数据整合。
有人可能就会问,如果有人不在sre_stack.py中维护终态,转而在外部系统中维护数据是不是也是可以的。是的,本身这个方案就考虑到了对原有系统的整合,所以这种情况是允许的,但我们能结构化地检查和审计出来:如果sre_stack.py文件没有变化,而status文件变化了,意味着这是一个非IaC驱动的终态变化。如果我们能接受这次变化(毕竟某些低频的复杂操作可能确实没那么适合在IaC中表达),只要审批过了,下次就不会再出现。整个流程如下图所示:

针对开分支,开发者提交了MergeRequest之后,会自动触发SRE Stack的状态渲染,将status文件补全进代码中,因此在审批环节就能完整地看到变化,在本章的开头部分,我们已经对于制品管理以及透视机制有了介绍,因此在通过IaC下的产生的变更透视树就变成了下面的样子:

于是,通过SRE Stack+制品管理,我们基本实现了这三个目标:
- 变更自动化:只要修改stack文件,发起MergeRequest,就进行自动渲染、自动部署,如果将代码构建的最后一个节点变成修改stack文件,就能实现CI到CD的无缝衔接。
- 变更代码化:stack文件中,使用声明的方式描述了变更的目标和行为,使得我们只要查看stack文件和状态文件,就能知道这次变更的内容是什么。
- 变更透明化:通过CI环节的制品信息采集,在最终的变更审批环节,我们能够透视出包含源码的完整的变更栈,不再有无法展开的引用关系。
五、总结
当前GitOps体系已经在内部逐步使用推广,有相应需求的公司可以联系SREWorks进行开源共建,我们可以将其作为一款SREWorks运维应用逐步对外推出。
参考材料
- weaveworks blog: The History of GitOps The History of GitOps
- OpenGitOps工作组官网 Home | OpenGitOps
- 《A beginners guide to GitOps》GitLab
- 《Repeatability, Reliability, and Scalability through GitOps》Amazon.com
- 《Pulumi 到底比 Terraform 强在哪》Pulumi 到底比 Terraform 强在哪
- 《Crossplane 是下一代 IaC 么》Crossplane 是下一代 IaC 么
- 《规模化环境Terraform状态管理技巧》规模化环境Terraform状态管理技巧
- 《FluxCD, ArgoCD or Jenkins X: Which Is the Right GitOps Tool for You?》FluxCD, ArgoCD or Jenkins X: Which Is the Right GitOps Tool for You?
- 《Tekton入门介绍》Tekton入门介绍-腾讯云开发者社区-腾讯云
- 《Automatic image update in Git with FluxCD》Automatic image update in Git with FluxCD
相关文章:
GitOps实践指南:GitOps能为我们带来什么?
Git,作为开发过程中的核心工具,提供了强大的版本控制功能。即便在写代码的时候稍微手抖一下,我们也能通过 Git 的差异对比(diff)轻松追踪到庞大工程中的问题,确保代码的准确与可靠。这种无与伦比的自省能力…...
D3485国产芯片+5V工作电压, 内置失效保护电路采用SOP8封装
D3485是一款5V供电、半双工的RS-485收发器,芯片内部包含一路驱动器和路接收器。D3485使用限摆率驱动器,能显著减小EMI和由于不恰当的终端匹配电缆所引起的反射,并实现高达10Mbps的无差错数据传输。D3485内置失效保护电路,保证接收…...
devops使用
官方文档 使用 Git 进行代码 - Azure DevOps | Microsoft Learn...
AI训练师常用的ChatGPT通用提示词模板
AI模型选择:如何选择合适的AI模型? 数据集准备:如何准备用于训练的数据集? 数据预处理:如何对待训练数据进行预处理? 特征工程:如何进行特征选择和特征工程? 超参数调整…...
Java加密算法工具类(AES、DES、MD5、RSA)
整理了有关加密算法工具类,结合了几个博客以及自己改良后可直接使用,主要介绍以下四种加密方式:AES、DES、MD5、RSA,详细介绍都在注释里面有讲。 一、AES import com.alibaba.fastjson.JSONObject; import java.nio.charset.Sta…...
探索Go语言的魅力:一门简洁高效的编程语言
介绍Go语言: Go,也被称为Golang,是由Google开发的一门开源编程语言。它结合了现代编程语言的优点,拥有高效的并发支持和简洁的语法,使其成为构建可伸缩、高性能应用的理想选择。 Go语言的特性: 并发编程…...
【用unity实现100个游戏之19】制作一个3D传送门游戏,实现类似鬼打墙,迷宫,镜子,任意门效果
最终效果 文章目录 最终效果素材第一人称人物移动开门效果显示原理渲染相机跟着我们视角移动门的摄像机跟着我们旋转近裁剪面设置传送配置代码实现传送效果结束完结素材 https://assetstore.unity.com/packages/3d/props/interior/door-free-pack-aferar-148411...
DRF(Django Rest Framework)框架基于restAPI协议规范的知识点总结
Django Rest Framework学习 一、初识DRF组件 1.1 FBV和CBV FBV:function based view from django.shortcuts import render, HttpResponse # 这种是基于FBV模式 函数模式实现Views函数 def login(request):if request.method GET:return HttpResponse("Get…...
Linux磁盘与文件系统管理
目录 在linux系统中使用硬盘 磁盘的数据结构 磁盘接口类型 字母含义 MBR磁盘分区 分区类型 分区的缺点 文件系统的 文件系统有什么作用 文件系统的修复 检测并确认新磁盘 参看磁盘信息 查看磁盘信息 添加磁盘 查看添加磁盘情况:sda系统磁盘ÿ…...
数字魔法AI绘画的艺术奇迹-用Stable Diffusion挑战无限可能【文末送书-12】
文章目录 前言一. 技术原理1.1 发展历程 二.对艺术领域的影响三. 挑战与机遇四.AI魔法绘画:用Stable Diffusion挑战无限可能【文末送书-12】4.1 粉丝福利:文末推荐与福利免费包邮送书! 前言 随着人工智能技术的迅猛发展,AI在各个…...
【docker实战】02 用docker安装mysql
本示例采用bitnami的镜像进行安装MySQL 一、镜像搜索 先搜索一下mysql有哪些镜像 [rootlocalhost ~]# docker search mysql NAME DESCRIPTION STARS OFFICIAL AUTOMATED mysql …...
循环渲染ForEach
目录 1、接口说明 2、键值生成规则 3、组件创建规则 3.1、首次渲染 3.2、非首次渲染 4、使用场景 4.1、数据源不变 4.2、数据源组项发生变化 4.3、数据源数组项子属性变化 5、反例 5.1、渲染结果非预期 5.2、渲染性能降低 Android开发中我们有ListView组件、GridVi…...
纷享销客华为云:如何让企业多一个选择?
纷享销客携手华为云推出多项联合解决方案,为企业的数字化提供了一个新选择。12月12日,纷享销客&华为云联合解决方案发布会在北京举办。本次发布会以“「CRM云」让企业多一个选择”为主题,来自行业头部企业的CEO、CIO、业务负责人等&#…...
前端实现断点续传文件
公司要求实现的功能,大概思路是将上传的文件通过jsZip压缩后,进行切片,留一下总切片,当前片,并把这些数据给后端,至于前端的校验,是由Md5完成的,验证文件唯一性,这样下次…...
复试 || 就业day01(2023.12.27)算法篇
文章目录 前言两数之和存在重复元素 II好数对的数目总持续时间可被 60 整除的歌曲 前言 💫你好,我是辰chen,本文旨在准备考研复试或就业 💫文章题目大多来自于 leetcode,当然也可能来自洛谷或其他刷题平台 Ὂ…...
JavaWeb——JQuery
文章目录 JQuery 是什么?jQuery 的原理示意图JQuery 基本开发步骤jQuery 对象和 DOM 对象将dom对象转为JQuery对象jQuery 对象转成 DOM 对象jQuery 选择器基本选择器基础过滤选择器JQuery 是什么? 基本介绍 jQuery 是一个快速的,简洁的 javaScript 库,使用户能更方便地处理…...
Python教程:查询Py模块的版本号,有哪些方法?
1.查看模块内部的版本信息:如果你已经导入了该模块,可以查看模块内部的__version__属性来获取版本信息。例如,对于pandas模块,可以运行import pandas和print(pandas.version)来获取版本信息。 import pandas print(pandas.__vers…...
第一节 初始化项目
系列文章目录 第一节 初始化项目 文章目录 操作步骤 总结 操作步骤 打开cmd 输入 vue ui 在打开的网页中点击“创建”,复制文件夹路径并粘贴点击“在此创建新项目” 输入项目名称 点击下一步选择手动配置 选择babel、router、vuex、css pre-processors、 linter建…...
idea提示unable to import maven project
问题描述: idea导入maven依赖时提示unable to import maven project 打开log日志如下: 问题原因以及解决方案: maven版本与idea版本不兼容,切换maven版本即可...
【Spring】SpringBoot日志
文章目录 什么是日志日志的用途日志的使用如何打印日志日志级别日志框架门面模式(外观模式)日志级别的使用配置日志级别日志持久化配置日志的存储目录配置日志文件名配置日志文件分割 更简单的日志输出 什么是日志 在计算机领域,日志是一个记…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...
LabVIEW双光子成像系统技术
双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制,展现出显著的技术优势: 深层组织穿透能力:适用于活体组织深度成像 高分辨率观测性能:满足微观结构的精细研究需求 低光毒性特点:减少对样本的损伤…...
毫米波雷达基础理论(3D+4D)
3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文: 一文入门汽车毫米波雷达基本原理 :https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...
【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅!
【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅! 🌱 前言:一棵树的浪漫,从数组开始说起 程序员的世界里,数组是最常见的基本结构之一,几乎每种语言、每种算法都少不了它。可你有没有想过,一组看似“线性排列”的有序数组,竟然可以**“长”成一棵平衡的二…...
