标签 docker 下的文章

Go项目该拥抱Monorepo吗?Google经验、etcd模式及白盒交付场景下的深度剖析

本文永久链接 – https://tonybai.com/2025/06/06/go-monorepo

大家好,我是Tony Bai。

在Go语言的生态系统中,我们绝大多数时候接触到的项目都是遵循“一个代码仓库(Repo),一个Go模块(Module)”的模式。这种清晰、独立的组织方式,在很多场景下都运作良好。然而,当我们放眼业界,特别是观察像Google这样的技术巨头,或者深入研究etcd这类成功的开源项目时,会发现另一种代码组织策略——Monorepo(单一代码仓库)——也在扮演着越来越重要的角色。

与此同时,Go语言的依赖管理从早期的GOPATH模式(其设计深受Google内部Monorepo实践的影响)演进到如今的Go Modules,我们不禁要问:在现代Go工程实践中,尤其是面对日益复杂的项目协作和特殊的交付需求(如国内甲方普遍要求的“白盒交付”),传统的Single Repo模式是否依然是唯一的最佳选择?Go项目是否也应该,或者在何种情况下,考虑拥抱Monorepo?

这篇文章,就让我们一起深入探讨Go与Monorepo的“前世今生”,解读不同形态的Go Monorepo实践(包括etcd模式),借鉴Google的经验,剖析其在现代软件工程,特别是白盒交付场景下的价值,并探讨相关的最佳实践与挑战。

Go Monorepo的形态解读:不仅仅是“大仓库”

首先,我们需要明确什么是Monorepo。它并不仅仅是简单地把所有代码都堆放在一个巨大的Git仓库里。一个真正意义上的Monorepo,通常还伴随着统一的构建系统、版本控制策略、代码共享机制以及与之配套的工具链支持,旨在促进大规模代码库的协同开发和管理。

在Go的世界里,Monorepo可以呈现出几种不同的形态:

形态1:单一仓库,单一主模块

这是我们最熟悉的一种“大型Go项目”组织方式。整个代码仓库的根目录下有一个go.mod文件,定义了一个主模块。项目内部通过Go的包(package)机制来组织不同的功能或子系统。

  • 优点: 依赖管理相对简单直接,所有代码共享同一套依赖版本。
  • 缺点: 对于逻辑上可以独立部署或版本化的多个应用/服务,这种方式可能会导致不必要的耦合。一个服务的变更可能需要整个大模块重新构建和测试,灵活性稍差。

形态2:单一仓库,多Go模块 —— 以etcd为例

这种形态更接近我们通常理解的“Go Monorepo”。etcd-io/etcd项目就是一个很好的例子。它的代码仓库顶层有一个go.mod文件,定义了etcd项目的主模块。但更值得关注的是,在其众多的子目录中(例如 client/v3, server/etcdserver/api, raft/raftpb 等),也包含了各自独立的go.mod文件,这些子目录本身也构成了独立的Go模块。

etcd为何采用这种模式?

  • 独立的版本演进与发布: 像client/v3这样的客户端库,其API稳定性和版本发布节奏可能与etcd服务器本身不同。将其作为独立模块,可以独立打版本标签(如client/v3.5.0),方便外部项目精确依赖特定版本的客户端。
  • 清晰的API边界与可引用性: 子模块化使得每个组件的公共API更加明确。外部项目可以直接go get etcd仓库中的某个子模块,而无需引入整个庞大的etcd主项目。
  • 更细粒度的依赖管理: 每个子模块只声明自己真正需要的依赖,避免了将所有依赖都集中在顶层go.mod中。

那么,一个Repo下有多个Go Module是Monorepo的一种形式吗? 答案是肯定的。这是一种更结构化、更显式地声明了内部模块边界和依赖关系的Monorepo形式(即便规模较小,内部的模块不多)。它们之间通常通过go.mod中的replace指令(尤其是在本地开发或特定构建场景)或Go 1.18引入的go.work工作区模式来协同工作。比如下面etcd/etcdutl这个子目录下的go.mod就是一个典型的使用replace指令的例子:

module go.etcd.io/etcd/etcdutl/v3

go 1.24

toolchain go1.24.3

replace (
    go.etcd.io/etcd/api/v3 => ../api
    go.etcd.io/etcd/client/pkg/v3 => ../client/pkg
    go.etcd.io/etcd/client/v3 => ../client/v3
    go.etcd.io/etcd/pkg/v3 => ../pkg
    go.etcd.io/etcd/server/v3 => ../server
)

// Bad imports are sometimes causing attempts to pull that code.
// This makes the error more explicit.
replace (
    go.etcd.io/etcd => ./FORBIDDEN_DEPENDENCY
    go.etcd.io/etcd/v3 => ./FORBIDDEN_DEPENDENCY
    go.etcd.io/tests/v3 => ./FORBIDDEN_DEPENDENCY
)

require (
    github.com/coreos/go-semver v0.3.1
    github.com/dustin/go-humanize v1.0.1
    github.com/olekukonko/tablewriter v1.0.7
    github.com/spf13/cobra v1.9.1
    github.com/stretchr/testify v1.10.0
    go.etcd.io/bbolt v1.4.0
    go.etcd.io/etcd/api/v3 v3.6.0-alpha.0
    go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0
    go.etcd.io/etcd/client/v3 v3.6.0-alpha.0
    go.etcd.io/etcd/pkg/v3 v3.6.0-alpha.0
    go.etcd.io/etcd/server/v3 v3.6.0-alpha.0
    go.etcd.io/raft/v3 v3.6.0
    go.uber.org/zap v1.27.0
)
//... ...

形态3:Google规模的Monorepo (The Google Way)

Google内部的超大规模Monorepo是业界典范,正如Rachel Potvin和Josh Levenberg在其经典论文《Why Google Stores Billions of Lines of Code in a Single Repository》中所述,这个单一仓库承载了Google绝大多数的软件资产——截至2015年1月,已包含约10亿个文件,900万个源文件,20亿行代码,3500万次提交,总计86TB的数据,被全球95%的Google软件开发者使用。

其核心特点包括:

  • 统一版本控制系统Piper: Google自研的Piper系统,专为支撑如此规模的代码库而设计,提供分布式存储和高效访问。
  • 强大的构建系统Blaze/Bazel: 能够高效地构建和测试这个庞大代码库中的任何目标,并精确管理依赖关系。
  • 单一事实来源 (Single Source of Truth): 所有代码都在一个地方,所有开发者都工作在主干的最新版本(Trunk-Based Development),避免了多版本依赖的困扰(如“菱形依赖问题”)。
  • 原子化变更与大规模重构: 开发者可以进行跨越数千个文件甚至整个代码库的原子化修改和重构,构建系统确保所有受影响的依赖都能同步更新。
  • 广泛的代码共享与可见性: 促进了代码复用和跨团队协作,但也需要工具(如CodeSearch)和机制(如API可见性控制)来管理复杂性。

Go语言的许多设计哲学,如包路径的全局唯一性、internal包的可见性控制、甚至早期的GOPATH模式(它强制所有Go代码在一个统一的src目录下,模拟了Monorepo的开发体验),都在不同程度上受到了Google内部这种开发环境的影响。

Google Monorepo的智慧:版本、分支与依赖管理的启示

虽然我们无法完全复制Google内部的庞大基础设施和自研工具链,但其在超大规模Monorepo管理上积累的经验,依然能为我们带来宝贵的启示:

  1. Trunk-Based Development (主干开发): Google绝大多数开发者工作在主干的最新版本。新功能通过条件标志(feature flags)控制,而非长时间存在的特性分支,这极大地避免了传统多分支开发模式下痛苦的合并过程。发布时,从主干切出发布分支,Bug修复在主干完成后,择优(cherry-pick)到发布分支。
  2. 统一版本与依赖管理: Monorepo的核心优势在于“单一事实来源”。所有内部依赖都是源码级的,不存在不同项目依赖同一内部库不同版本的问题。对于第三方开源依赖,Google有专门的流程进行统一引入、审查和版本管理,确保整个代码库中只有一个版本存在。这从根本上解决了“菱形依赖”等版本冲突问题。
  3. 强大的自动化工具链是基石:
    • 构建系统 (Bazel): 能够进行精确的依赖分析、增量构建和并行测试,是Monorepo高效运作的核心。
    • 代码审查 (Critique): Google文化高度重视代码审查,所有代码提交前都必须经过Review。
    • 静态分析与大规模重构工具 (Tricorder, Rosie): 自动化工具用于代码质量检查、发现潜在问题,并支持跨整个代码库的大规模、安全的自动化重构。
    • 预提交检查与持续集成: 强大的自动化测试基础设施,在代码提交前运行所有受影响的测试,确保主干的健康。

对我们的启示:

  • “单一事实来源”的价值: 即使不采用Google规模的Monorepo,在团队或组织内部,尽可能统一核心共享库的版本,减少不必要的依赖分歧,是非常有益的。
  • 自动化的力量: 投入自动化测试、CI/CD、代码质量检查和依赖管理工具,是管理任何规模代码库(尤其是Monorepo)的必要投资。
  • 主干开发与特性标志: 对于需要快速迭代和持续集成的项目,主干开发结合特性标志,可能比复杂的多分支策略更敏捷。
  • 对依赖的审慎态度: Google对第三方依赖的严格管控值得借鉴。任何外部依赖的引入都应经过评估。

企业级Go Monorepo的最佳实践:从理念到落地

当我们的组织或项目发展到一定阶段,特别是当多个Go服务/库之间存在紧密耦合、需要频繁协同变更,或者希望统一工程标准时,Monorepo可能成为一个有吸引力的选项。

以下是一些在企业环境中实施Go Monorepo的最佳实践:

  1. 明确采用Monorepo的驱动力与目标: 是为了代码共享?原子化重构?统一CI/CD?还是像我们接下来要讨论的“白盒交付”需求?清晰的目标有助于后续的设计决策。

  2. 项目布局与模块划分的艺术:

    • 清晰的顶层目录结构: 例如,使用cmd/存放所有应用入口,pkg/存放可在Monorepo内部跨项目共享的库,services/或components/用于组织逻辑上独立的服务或组件(每个服务/组件可以是一个独立的Go模块),internal/用于存放整个仓库共享但不对外暴露的内部实现。
    • 推荐策略:为每个可独立部署的服务或可独立发布的库建立自己的go.mod文件。 这提供了明确的依赖边界和独立的版本控制能力。
    • 使用go.work提升本地开发体验: 在Monorepo根目录创建go.work文件,将所有相关的Go模块加入工作区,简化本地开发时的模块间引用和构建测试。
  3. 依赖管理的黄金法则:

    • 服务级go.mod中的replace指令: 对于Monorepo内部模块之间的依赖,务必在依赖方的go.mod中使用replace指令将其指向本地文件系统路径。这是确保模块在Monorepo内部能正确解析和构建的关键,尤其是在没有go.work的CI环境或交付给客户时。
      // In my-org/monorepo/services/service-api/go.mod
      module my-org/monorepo/services/service-api
      go 1.xx
      require (
      my-org/monorepo/pkg/common-utils v0.1.0 // 依赖内部共享库
      )
      replace my-org/monorepo/pkg/common-utils => ../../pkg/common-utils // 指向本地
    • 谨慎管理第三方依赖: 定期使用go list -m all、go mod graph分析依赖树,使用go mod tidy清理,关注go.sum的完整性。使用govulncheck进行漏洞扫描。
  4. 版本控制与发布的规范:

    • 为每个独立发布的服务/库打上带路径前缀的Git Tag: 例如,为services/appA模块的v1.2.3版本打上services/appA/v1.2.3的Tag。这样,外部可以通过go get my-org/monorepo/services/appA@services/appA/v1.2.3来精确获取。
    • 维护清晰的Changelog: 无论是整个Monorepo的(如果适用),还是每个独立发布单元的,都需要有详细的变更记录。
  5. 分支策略的适配:

    • 可以考虑简化的Gitflow(主分支、开发分支、特性分支、发布分支、修复分支)或更轻量的GitHub Flow / GitLab Flow。关键是确保主分支(如main或master)始终保持可发布或接近可发布的状态。
    • 特性开发在独立分支进行,通过Merge Request / Pull Request进行代码审查后合入主开发分支。
  6. CI/CD的智能化与效率:

    • 按需构建与测试: CI/CD流水线应能识别出每次提交所影响的模块/服务,仅对受影响的部分进行构建和测试,避免不必要的全量操作。
    • 并行化: 利用Monorepo的结构,并行执行多个独立模块/服务的构建和测试任务。
    • 统一构建环境: 使用Docker等技术确保CI/CD环境与开发环境的一致性。

Go Monorepo与白盒交付:相得益彰的“黄金搭档”

现在,让我们回到一个非常具体的、尤其在国内甲方项目中常见的需求——白盒交付。白盒交付通常意味着乙方需要将项目的完整源码(包括所有依赖的内部库)、构建脚本、详细文档等一并提供给甲方,并确保甲方能在其环境中独立、可复现地构建出与乙方交付版本完全一致的二进制产物,同时甲方也可能需要在此基础上进行二次开发或长期维护。

在这种场景下,如果乙方的原始项目是分散在多个Repo中(特别是还依赖了乙方内部无法直接暴露给甲方的私有库),那么采用为客户定制一个整合的Monorepo进行交付的策略,往往能带来诸多益处:

  1. 解决内部私有库的访问与依赖问题:
    我们可以将乙方原先的内部私有库代码,作为模块完整地复制到交付给客户的这个Monorepo的特定目录下(例如libs/或internal_libs/)。然后,在这个Monorepo内部,所有原先依赖这些私有库的服务模块,在其各自的go.mod文件中通过replace指令,将依赖路径指向Monorepo内部的本地副本。这样,客户在构建时就完全不需要访问乙方原始的、可能无法从客户环境访问的私有库地址了。

  2. 提升可复现构建的成功率:

    • 集中的依赖管理: 所有交付代码及其内部依赖都在一个统一的Monorepo中,通过服务级的go.mod和replace指令明确了版本和本地路径,极大降低了因依赖版本不一致或依赖源不可达导致的构建失败。
    • 统一构建环境易于实现: 针对单一Monorepo提供标准化的构建脚本和Dockerfile(如果使用容器构建),比为多个分散Repo分别提供和维护要简单得多。
    • 结合-trimpath、版本信息注入等技巧,更容易在客户环境中构建出与乙方环境内容一致的二进制文件。
  3. 简化后续的协同维护与Patch交付:

    • 集中的代码基: 即使后续乙方仅以Patch形式向甲方提供Bug修复或功能升级,这些Patch也是针对这个统一Monorepo的特定路径的变更。甲方应用Patch、进行代码审查和版本追溯都更为集中和方便。
    • 清晰的项目布局与版本管理: 在Monorepo内部,通过良好的目录组织和为每个独立服务打上带路径前缀的版本标签,使得甲乙双方对代码结构、版本演进和变更范围都有清晰的认知。
  4. 便于客户搭建统一的CI/CD与生成SBOM:

    • 甲方可以在这个统一的Monorepo基础上,更容易地搭建自己的CI/CD流水线,并实现按需构建。
    • 为Monorepo中的每个独立服务生成其专属的软件物料清单(SBOM)也更为规范和便捷。

可见,对于复杂的、涉及多服务和内部依赖的Go项目白盒交付场景,精心设计的客户侧Monorepo策略,可以显著提升交付的透明度、可控性、可维护性和客户满意度。**

小结

Monorepo并非没有代价。正如Google的论文中所指出的,它对工具链(特别是构建系统)、版本控制实践(如分支管理、Code Review)、以及团队的协作模式都提出了更高的要求。仓库体积的膨胀、潜在的构建时间增加(如果CI/CD优化不当)、以及更细致的权限管理需求,都是采用Monorepo时需要认真评估和应对的挑战。Google为其Monorepo投入了巨大的工程资源来构建和维护支撑系统,这对大多数组织来说是难以复制的。

然而,在特定场景下——例如拥有多个紧密关联的Go服务、希望促进代码共享与原子化重构、或者面临像白盒交付这样的特殊工程需求时——Monorepo展现出的优势,如“单一事实来源”、简化的依赖管理、原子化变更能力等,是难以替代的。

Go语言本身的设计,从早期的GOPATH到如今Go Modules对工作区(go.work)和子目录模块版本标签的支持,都在逐步提升其在Monorepo环境下的开发体验。虽然Go不像Bazel那样提供一个“大一统”的官方Monorepo构建解决方案,但其工具链的灵活性和社区的实践,已经为我们探索和实施Go Monorepo提供了坚实的基础。

最终,Go项目是否应该拥抱Monorepo,并没有一刀切的答案。 它取决于项目的具体需求、团队的规模与成熟度、以及愿意为之投入的工程成本。但毫无疑问,理解Monorepo的理念、借鉴Google等先行者的经验(既要看到其优势,也要理解其巨大投入)、掌握etcd等项目的实践模式,并思考其在如白盒交付等现代工程场景下的应用价值,将极大地拓展我们作为Go开发者的视野,并为我们的技术选型和架构设计提供宝贵的参考。

Go的生态在持续进化,我们对更优代码组织和工程实践的探索也永无止境。


聊聊你的Monorepo实践与困惑

Go语言项目,是坚守传统的“一Repo一Module”,还是拥抱Monorepo的集中管理?你在实践中是如何权衡的?特别是面对etcd这样的多模块仓库,或者类似Google的超大规模Monorepo理念,你有哪些自己的思考和经验?在白盒交付场景下,Monorepo又为你带来了哪些便利或新的挑战?

Google I/O 2025 Go 语言进展:生产力、生产就绪与 AI 赋能

本文永久链接 – https://tonybai.com/2025/05/25/go-at-googleio-2025

大家好,我是Tony Bai。

在Google I/O 2025大会上,Go 产品负责人 Cameron Balahan 和开发者关系负责人 Marc Dougherty 详细阐述了 Go 语言在生产力、生产就绪度和开发者体验方面的最新进展及未来规划。演讲强调了 Go 语言以规模化为核心的设计理念及其三大指导原则:生产力、超越语言的完整体验和生产就绪。重点介绍了Go 1.23Go 1.24版本在生产力方面的革新,包括引入迭代器简化循环、gopls 的智能现代化能力以及通过 go get 管理 Go 工具链;在生产就绪性方面,突出了 WebAssembly 支持的增强、安全体系的持续深化(特别是后量子密码学的透明集成和 FIPS-140 支持的便捷启用)以及核心性能的显著提升(如全新的 map 实现)。此外,演讲还强调了 Go 语言在 AI 基础设施构建中的核心地位,并展望了 Go 1.25+ 在 SIMD 支持、多核硬件优化等方向的探索,同时重申了 Go 1.0 的兼容性承诺。

这里是基于演讲视频,借助AI整理的文字稿,我做了简单校对和格式调整,供大家参考。

原视频链接:https://www.youtube.com/watch?v=kj80m-umOxs 建议大家也都看一下。


我是 Cameron,我是 Google Go 编程语言的产品负责人。我是 Marc,我负责 Go 的开发者关系。

对于那些刚接触我们项目的人来说,Go 是一个由 Google 支持的开源编程语言,它能让开发者和软件工程团队快速构建更安全、可靠和可扩展的生产系统。

Google 在 15 年前将 Go 作为一个开源项目发布,在此之前两年,Google 为了应对自身在构建和维护大规模、关键任务系统方面面临的挑战而启动了这个项目。使用现有的工具,我们不得不在动态解释性语言的生产力和强类型编译语言的生产就绪性之间做出选择。但我们两者都想要,所以我们构建了Go

Go 的核心前提是开发工具从一开始就应该优先考虑可扩展性,这意味着要考虑到现代软件的架构方式、现代工作负载运行的环境,以及最重要的,编写、操作和维护这一切的团队。因此,考虑到这一点,我们围绕三个原则构建了Go,这些原则至今仍在指导着我们。

首先,Go 是高效的。它易于学习,易于维护,可读性强,并且能够很好地适应不同的团队、工作负载和用例。

其次,Go 不仅仅是一门语言,它是一个完整的开发者体验。从 IDE 到生产环境,我们提供端到端的解决方案,涵盖整个软件开发生命周期的所有接触点。我们提供所有这一切,开箱即用,并带有合理的、可自动调整的默认设置。

第三,Go 是生产就绪的。它可靠、高效、稳定且安全,这使得它非常适合从简单应用到企业系统和关键基础设施的各种场景。

多年来,Go 已经成为现代云计算的核心,并由此延伸到现代网络。世界上许多最知名的云技术都是用 Go 编写的,包括 Kubernetes、Docker、Terraform 等等。各种规模的公司,从个人到初创企业再到大型企业,都已采用 Go,尤其是在其基于云的工作负载方面。这在很大程度上是因为 Go 是为云计算而专门构建的。Go 所支持的库、集成和架构是为云而生的,而不是后来才为云进行改造的。这意味着你可以比使用其他语言更快、更容易地实现云计算的优势。

但你不必相信我的话。Go 用户一直给予我们非凡的反馈和客户满意度(注:93%)——这种水平在行业内几乎闻所未闻。使用情况也证明了这一点。如今,Go 比以往任何时候都更受欢迎,拥有数百万开发者,并且仍在快速增长。事实上,根据去年的NewStack的一项调查,Go 是仅有的两种增长速度超过开发者本身增长速度的语言之一。另一种是 Rust,我们认为它与 Go 配合得非常好,但这是另一个话题了。这样的迹象随处可见。Go 一直在 Stack Overflow 上被评为最受欢迎的技术之一。去年,Cloudflare 报告称,Go 是互联网上支持 API 调用的第一大语言

因此,无论你是个人开发者、企业,还是介于两者之间的组织,Go 都能让你快速、更可靠地构建和扩展你的项目。你可能会很高兴你这样做了。接下来,Marc 将深入探讨 Go 的所有最新进展。交给你了,Marc。

谢谢,Cameron。Go 每年发布两次新的主版本,分别在八月和二月。在过去的一年里,我们在 1.23 和 1.24 版本中发布了许多令人兴奋的新功能,以帮助你和你的团队提高工作效率。

在1.23 版本中,我们引入了带有 seq 和 seq2 类型的迭代器。相较于经典的 Go 风格,迭代器不仅仅是标准库中的一个新类型。它们是一种优雅的方式,可以使用已经熟悉的 for range 表达式来简化循环,并将迭代的机制与循环体分开。在迭代器出现之前,有几种不同的方法来遍历数据。一些方法会返回一个包含所有结果的切片,这对于大型集合来说可能效率低下。另一种方法是创建自己的迭代器对象,就像这段代码一样,它使用了 Google Cloud Storage 库。注意这里的复杂性。我们的循环中有流程控制和错误检查。并且该错误检查需要在每个循环中重复。使用迭代器,你可以使用熟悉的 for range 语法来执行循环。复杂的流程控制则保留在迭代器内部。这使得我们的循环体可以专注于处理文件或错误,而无需担心流程控制。

从 1.24 版本开始,标准库在 strings、slices 和 maps 包中包含了一系列迭代器。因为迭代器只是一个函数,所以你可以定义自己的迭代器,包括为其他地方定义的集合类型定义迭代器。这是我为 Cloud Storage 示例定义的迭代器。声明看起来有点复杂,但你可以看到这里的流程控制与之前具有相同的效果。这个迭代器让我们能够将流程控制处理从循环中分离出来,并使它们更具可读性。

随着像迭代器这样的新概念的引入,Go 的垂直集成工具可帮助你的代码库与最新的模式和习惯用法保持同步。Go 的语言服务器 gopls 可以与你的 IDE 集成,既可以通过大多数 IDE 中的语言服务器支持,也可以通过插件(如 VS Code Go 扩展)实现。Gopls 在常规的语言服务器功能方面提供帮助,例如类型检查、函数签名和引用。但 gopls 的功能远不止于此。还记得那个复杂的迭代器定义吗?由于 gopls 从第一天起就知道新功能,因此它可以帮助你在编写时避免错误。在这里,它注意到了一个错误,即我们的迭代器可能会在应该停止后调用我们的 yield 函数。gopls 包含一套现代化功能,这些常见模式后来已作为语言特性或标准库新增功能得到解决。虽然你可以在整个代码库上运行现代化工具,但 gopls 可以在你编辑的任何地方内联建议它们。这里有一些旧模式的例子在左边,以及它们现代化的替代方案在右边。

最后一个现代化工具展示了 JSON 解析器的一个新特性,称为omitzero。JSON 包从 Go 1.0 开始就是 Go 的一部分。它通过简化 Go 结构体的序列化,实现了 API 客户端和服务器的人性化开发。omitzero 选项的添加解决了一些在处理 Go 的零值(如空结构体和未初始化的 time.Time 对象)时常见的错误和令人意外的行为。这些新增功能让你能够更好地控制对象如何序列化为 JSON,并避免可能的错误和混淆来源。

你是否需要更新你的 Go 运行时以利用新功能?从 1.23 版本开始,你可以使用 go get 来管理 Go 工具链,就像管理任何其他依赖项一样。Go 会根据需要下载更新的工具链,让你的团队可以使用最新的功能,而无需停下来手动更新工具链。这也适用于依赖项。如果你依赖了需要 1.24 版本的代码,Go 会更新你模块的 go 指令以要求 1.24 版本,并自动获取 1.24 运行时。Go 语言和 Go 工具不断寻找新的方法来帮助你保持代码库的可读性和现代化,并让你的团队保持专注和高效。

Marc 刚刚向你介绍了让你更高效的一些新功能。但请记住,Go 关注的是生产力和生产就绪性。那么,让我们来谈谈 Go 1.23 和 1.24 中那些让你的应用程序更健壮、更安全、性能更高的最新功能。

正如我之前所说,Go 的创始原则部分集中在其可移植性和对现代工作负载运行的现代环境的关注上。这些环境在不断发展。随着它们的发展,我们希望确保 Go 能够跟上步伐。我们做到这一点的一种方式是在 Go 1.24 中显著改进了 Go 对 WebAssembly 的支持。WebAssembly,或称 Wasm,是一种二进制指令格式和沙盒化运行时环境,它开启了许多新的有趣用例,尤其是在云端。包括 Go 在内的几种语言都能够编译 Wasm 模块,这些模块包含可在所有 Wasm 主机上运行的可移植的、与体系结构无关的字节码。同一个 Wasm 主机应用程序可以调用来自多个不同 Wasm 模块的方法,这些模块可以根据需要用一种语言或多种语言混合编写。这些 Wasm 模块是可热加载的,并在内存安全的沙盒化运行时中运行,具有结构化的控制流和验证。任何系统调用都通过 Wasm 运行时进行路由,这提供了一个额外的安全层,有点像一个极其轻量级的容器。尽管存在这一层抽象,但 Wasm 应用程序效率极高,能够在主机上实现接近本机的性能。这使得它们特别适用于高性能、低延迟的用例,例如边缘计算。例如,你可以在 Google Cloud 服务扩展上运行你的 Wasm 代码,它在 200 多个国家的 200 多个边缘位置提供边缘计算。

Go 在 Go 1.11 版本中通过 JS Wasm 移植首次引入了对 Wasm 的支持。Wasm 本身最初是为浏览器设计的。JS Wasm 移植通过允许你通过 JavaScript 主机定位网页,从而启用了此用例。Go 开发者利用这个功能制作了一些非常有趣的东西,尤其是游戏。甚至还有一些利用 JS Wasm 移植的 Go 开源游戏引擎。Go 开发者可以使用这些项目轻松开发在浏览器中运行的令人印象深刻的 2D 游戏。随着 Wasm 的发展,Go 也在发展。在 Go 1.21 中,我们引入了对 WebAssembly 系统接口(WASI)预览版 1 的支持。WASI 提供了一个 POSIX 风格的接口,用于与系统资源进行交互,例如文件系统、系统时钟、数据实用程序等等。在这个例子中,你可以看到一个简单的“Hello, world!”程序,我们通过开头的编译标志将其编译为 Wasm。然后我们可以使用众多免费开源的 Wasm 运行时和库之一来运行该程序。在这种情况下,我们使用的是 wazero,一个用 Go 实现的开源项目。从 Go 1.21 开始,Go 开发者可以将 Wasm 模块构建为可执行文件,在 Wasm 运行时中启动它,并运行至完成。

这就引出了今天的内容。在 Go 1.24 中,我们通过两种主要方式扩展了 Go 的 Wasm 功能。首先,Go 1.24 允许你使用 go:wasmexport 编译器指令将 Go 函数导出到 Wasm 主机。当我们将这样的代码编译成 Wasm 模块时,我们可以在 Wasm 主机中导入它,Wasm 主机可以直接调用模块导出的函数。其次,Go 1.24 添加了对构建 WASI 反应器 (reactor) 的支持。当你使用此功能以 Reactor 模式构建 Wasm 模块时,即使模块执行完毕,它也可以保持初始化状态。这对于你希望无限期可用的长时间运行的插件或扩展非常有用。初始化一次,让它保持运行,它可以继续响应调用,包括通过维护状态。在这个例子中,我们使用 wazero 的库来创建一个 Wasm 主机,它将调用我们在上一个例子中导出的 add 函数。不过,这次我们将使用高亮显示的构建标志以反应器模式构建 Wasm 模块。现在,我们可以多次运行 add 函数而无需重新初始化它。

接下来,我们来谈谈 Go 如何让你的应用程序更安全。Go 一直在安全特性和功能方面处于领先地位。在 Go 1.13 中,我们引入了模块代理和校验和数据库,它们缓存并记录 Go 生态系统中所有依赖项的哈希值,保护你免受中间人攻击和其他对依赖项的篡改。然后,在 Go 1.18 中,我们引入了内置的模糊测试 (fuzz testing),这是第一个将原生模糊测试内置并集成到其标准工具链中的主流编程语言。你可以将模糊测试视为一种自动化测试形式,它智能地操纵程序的输入以找出错误,尤其是安全漏洞。2022 年,我们推出了 Go 的端到端漏洞管理系统,它可以在任何地方(从 IDE 到运行时)发现依赖项中的已知漏洞。通过分析从你的代码到依赖项的调用图,Go 的漏洞管理工具能够检测你是否实际调用了易受攻击的代码,从而消除了绝大多数的误报。

基于我们对安全的关注,在 Go 1.24 中,我们引入了对后量子密码学的支持,所有这些都在幕后透明地实现。我们还改进了对 FIPS-140 的支持,这是一项美国政府合规制度,其中包括用于加密应用的已批准算法。你可以在不更改任何代码的情况下启用 FIPS 模式,既可以在运行时使用高亮显示的调试标志,也可以在构建时使用高亮显示的构建 flag。

最后,我们继续专注于使 Go 更快、更高效。我们做到这一点的一个重要方式是引入了一个全新的内置 map 类型实现,它基于一种名为 Swiss Tables 的新哈希表设计。从 Go 1.24 开始,map 透明地使用新的 Swiss Table 实现。在微基准测试中,使用新实现的 map 操作比 Go 1.23 快了高达 60%,尤其是在处理大型 map 时。这一切都无缝集成在 Go 的内置 map 中。无需调整你的代码。只需升级即可。

还有更多,包括 Go 1.23 和 1.24 中许多新的底层工具,用于提高效率。例如,在 Go 1.23 中,我们引入了 Unique Package,可以高效地对值进行去重和比较。在 Go 1.24 中,我们引入了 weak.Pointers,它允许你安全地指向一个对象而不会阻止它被垃圾回收,以及 AddCleanup 函数,这是一种更灵活、更高效且更不容易出错的终结机制。还有更多,包括改进的内存分配速度和整体速度提升。所有这些都延续了我们保持 Go 既高效又生产就绪的重点。

接下来,让我们把话筒转回给 Marc,让他快速介绍一下 Go 在生成式 AI 中的最新应用。

正如你刚才听到的,Go 拥有许多特性,使其成为构建生产系统的绝佳语言。像高效的网络库和集成的结构体标签这样的特性,使其非常适合构建分布式系统。这也是 Go 在云基础设施和服务中如此普遍的重要原因。同样的这些原因也使得 Go 成为当今构建 AI 基础设施和服务的绝佳选择。流行的生成式 AI 工具和库,如 Ollama、Local AI、LangChain Go、Genkit 等等,都是用 Go 编写的。就像之前的主要基础设施项目一样,这些工具和库利用 Go 的生产力和生产就绪性来创建高度可扩展且更可靠的关键任务服务,数百万来自不同语言生态系统的开发者依赖这些服务来支持其 AI 驱动的工作负载。

事实上,云和 AI 系统之间的共同点比你想象的要多。由于 LLM 通常需要专用的、专门的计算资源,因此它们通常作为通过 API 调用的网络服务运行。让我们以 Go 博客最近一篇文章中概述的检索增强生成 (RAG) 系统为例。我们的 RAG 系统使用向量数据库来存储相关文档,以便在回答用户问题时提供给我们的 LLM。向量数据库依赖于专门的嵌入模型,因此我们可以高效地查询与用户问题相似的文档。我们将研究三种不同的框架,用于将这些服务连接在一起。

对于我们的第一个例子,我们将直接使用 Gemini 和 Weaviate 客户端库。这段代码来自用户查询处理程序。我们正在使用 Weaviate 的 GraphQL 接口来获取文档。查询本身有点长,所以我们使用了一个辅助函数。这种方法的一个缺点是,如果我们更改向量数据库,就必须重写辅助函数。

在这里,我们使用的是 LangChain Go,它为我们的 LLM 和向量数据库提供了接口抽象。如果我们替换这些组件,相似性搜索和从单个提示生成调用的代码将无需更改。

最后,我们来看看 Firebase Genkit for Go,目前处于测试阶段。它提供了与 LangChain Go 类似的抽象。Genkit 包含生产级功能,如提示管理和可观察性,这些功能可能在代码中不可见,但可以改善整体开发者体验。

随着你的 AI 系统的发展,Go 对简单性的强调意味着即使代码规模和复杂性增加,你的代码仍然保持可读性。Go 的特性,如对象嵌入和接口,使得在需求和技术发生变化时可以无缝迁移——而它们总是会发生变化。Go 在跟上快速变化方面的成熟能力使其在一些最知名的云基础设施组件中取得了成功。推动 Go 在云领域普及的相同特性,也使其成为我们构建未来 AI 基础设施的绝佳选择。

我希望我们已经在这个视频中证明了,Go 围绕生产力、开发者体验和生产就绪性的创始原则,仍然是我们今天优先考虑工作的依据。在结束之前,我想花几分钟时间让大家一窥 Go 1.25 及更高版本即将推出的内容。

首先,在 Marc 关于 AI 的讨论基础上,我们对围绕 SIMD 所做的工作感到非常兴奋。SIMD 使现代 CPU 能够执行向量化数组操作,并行运行某些类型的循环。这些功能对于许多类型的性能优化至关重要,包括某些类型的 AI 基础设施所需的优化。

在性能方面,我们在多核硬件方面有很多令人兴奋的机会,包括垃圾回收器和调度器的功能,这些功能可以更好地利用现代 CPU 架构中的非一致性内存访问。

切换到语言本身,在我们持续推动提高生产力方面,我们还有很多需要完善的地方,特别是在泛型操作的灵活性方面。有关该工作的更多信息,请查看我们在 GitHub 上 Go 项目的讨论。

在我们做所有这些以及更多事情的同时,你可以放心,我们现在和将来所做的任何更改都将继续履行 Go 的兼容性承诺。Go 仍然并将永远保持与 Go 1.0 的完全向后兼容。

在我们结束时,我们想花点时间感谢 Go 社区。我们,Go 团队,致力于在未来很长一段时间内保持 Go 的生产力和生产就绪性。但我们知道我们并不孤单。今天,我们的生态系统比以往任何时候都更大、更健全。我们继续看到许多非常高质量的工具和库涌现,尤其是在围绕生成式 AI 的新用例方面。我们看到世界各地成千上万的 Gopher 聚会、参加 Go 会议,并在网上协作,所有这些都是因为他们热爱 Go。所以,感谢 Go 社区。正是因为你们的贡献,Go 才得以发展,并且比以往任何时候都更具相关性。我们非常自豪能与你们一起参与这段旅程。

要开始使用,或获取有关本视频中讨论的任何内容的更多信息,请务必访问我们的主页 go.dev。感谢你参加今年的 Google I/O 大会。我们迫不及待地想看看你今年以及未来几年用 Go 构建的成果。


原「Gopher部落」已重装升级为「Go & AI 精进营」知识星球,快来加入星球,开启你的技术跃迁之旅吧!

我们致力于打造一个高品质的 Go 语言深度学习AI 应用探索 平台。在这里,你将获得:

  • 体系化 Go 核心进阶内容: 深入「Go原理课」、「Go进阶课」、「Go避坑课」等独家深度专栏,夯实你的 Go 内功。
  • 前沿 Go+AI 实战赋能: 紧跟时代步伐,学习「Go+AI应用实战」、「Agent开发实战课」,掌握 AI 时代新技能。
  • 星主 Tony Bai 亲自答疑: 遇到难题?星主第一时间为你深度解析,扫清学习障碍。
  • 高活跃 Gopher 交流圈: 与众多优秀 Gopher 分享心得、讨论技术,碰撞思想火花。
  • 独家资源与内容首发: 技术文章、课程更新、精选资源,第一时间触达。

衷心希望「Go & AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚,享受技术精进的快乐!欢迎你的加入!

img{512x368}

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言进阶课 AI原生开发工作流实战 Go语言精进之路1 Go语言精进之路2 Go语言第一课 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats