<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tony Bai &#187; container</title>
	<atom:link href="http://tonybai.com/tag/container/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Sun, 12 Apr 2026 22:30:28 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Kelsey Hightower 退休后的冷思考：为什么 10 年过去了，我们还在谈论容器？</title>
		<link>https://tonybai.com/2026/01/22/why-are-we-still-talking-about-containers-in-ai-age/</link>
		<comments>https://tonybai.com/2026/01/22/why-are-we-still-talking-about-containers-in-ai-age/#comments</comments>
		<pubDate>Thu, 22 Jan 2026 00:23:51 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[apple/container]]></category>
		<category><![CDATA[ChasingHype]]></category>
		<category><![CDATA[cloudnative]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[Context]]></category>
		<category><![CDATA[Craftsmanship]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[FinishingWork]]></category>
		<category><![CDATA[FreeBSDServiceJails]]></category>
		<category><![CDATA[InvisibleTechnology]]></category>
		<category><![CDATA[KelseyHightower]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[LargeLanguageModel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[MichaelCrosby]]></category>
		<category><![CDATA[NativeIntegration]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[OS]]></category>
		<category><![CDATA[PromptEngineer]]></category>
		<category><![CDATA[Standardization]]></category>
		<category><![CDATA[TsunamiCycle]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[上下文]]></category>
		<category><![CDATA[云原生]]></category>
		<category><![CDATA[原生集成]]></category>
		<category><![CDATA[完成工作]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[工匠精神]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[提示词工程师]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[标准化]]></category>
		<category><![CDATA[海啸循环]]></category>
		<category><![CDATA[烂尾工程]]></category>
		<category><![CDATA[追逐热点]]></category>
		<category><![CDATA[隐形化]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5760</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/01/22/why-are-we-still-talking-about-containers-in-ai-age 大家好，我是Tony Bai。 “如果你在 2014 年告诉我，十年后我们还在讨论容器，我会觉得你疯了。但现在是 2025 年，我们依然在这里，谈论着同一个话题。” 在去年中旬举行的 ContainerDays Hamburg 2025 上，早已宣布“退休”的云原生传奇人物 Kelsey Hightower 发表了一场发人深省的主题演讲。在这个 AI 狂热席卷全球的时刻，他没有随波逐流地去谈论大模型，而是回过头来，向所有技术人抛出了一个灵魂拷问： 为什么我们总是在追逐下一个热点，却从来没有真正完成过手头的工作？ 烂尾工程的诅咒——技术圈的“海啸”循环 Kelsey 首先回顾了他职业生涯中经历的三次技术浪潮：Linux 取代 Unix(AIX、Solaris等)、DevOps 的兴起、以及 Docker/Kubernetes 的容器革命。 他敏锐地指出，技术圈似乎陷入了一个无休止的“海啸循环”： 热点爆发：一个新的技术（如 Docker）出现，VC 资金涌入，所有人都在谈论它。 疯狂追逐：为了抢占市场，大家都只做“足够发布”的工作，追求速度而非完美。 未竟而散：还没等这项技术真正成熟、稳定、标准化，下一个热点（如 AI）就来了。于是，半数工程师跳船去追新热点，留下一地鸡毛。 “我们就像一群踢足球的孩子，看到球滚到哪里，所有人就一窝蜂地冲过去，连守门员都离开了球门。结果是，球门大开，后方空虚。” 这就是为什么 10 年过去了，我们还在谈论容器。因为我们当年并没有真正“完成”它。我们留下了无数的复杂性、不兼容和“企业级发行版”，却忘了初衷。 Apple 的“非性感”工作——这才是未来 在演讲中，Kelsey 分享了他最近的一个惊人发现：Apple 正在 macOS 中原生集成容器运行时。 这不是 Docker Desktop，也不是虚拟机套娃，而是操作系统级别的原生支持。这就是 GitHub 上的一个名为 apple/container 的 Apple [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/why-are-we-still-talking-about-containers-in-ai-age-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/01/22/why-are-we-still-talking-about-containers-in-ai-age">本文永久链接</a> &#8211; https://tonybai.com/2026/01/22/why-are-we-still-talking-about-containers-in-ai-age</p>
<p>大家好，我是Tony Bai。</p>
<p>“如果你在 2014 年告诉我，十年后我们还在讨论容器，我会觉得你疯了。但现在是 2025 年，我们依然在这里，谈论着同一个话题。”</p>
<p>在去年中旬举行的 ContainerDays Hamburg 2025 上，早已宣布“退休”的云原生传奇人物 <strong>Kelsey Hightower</strong> 发表了<a href="https://www.youtube.com/watch?v=x1t2GPChhX8">一场发人深省的主题演讲</a>。在这个 AI 狂热席卷全球的时刻，他没有随波逐流地去谈论大模型，而是回过头来，向所有技术人抛出了一个灵魂拷问：</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/why-are-we-still-talking-about-containers-in-ai-age-2.png" alt="" /></p>
<p><strong>为什么我们总是在追逐下一个热点，却从来没有真正完成过手头的工作？</strong></p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/distributed-system-guide-qr.png" alt="" /></p>
<h2>烂尾工程的诅咒——技术圈的“海啸”循环</h2>
<p>Kelsey 首先回顾了他职业生涯中经历的三次技术浪潮：Linux 取代 Unix(AIX、Solaris等)、DevOps 的兴起、以及 Docker/Kubernetes 的容器革命。</p>
<p>他敏锐地指出，技术圈似乎陷入了一个无休止的“海啸循环”：</p>
<ol>
<li><strong>热点爆发</strong>：一个新的技术（如 Docker）出现，VC 资金涌入，所有人都在谈论它。</li>
<li><strong>疯狂追逐</strong>：为了抢占市场，大家都只做“足够发布”的工作，追求速度而非完美。</li>
<li><strong>未竟而散</strong>：还没等这项技术真正成熟、稳定、标准化，下一个热点（如 AI）就来了。于是，半数工程师跳船去追新热点，留下一地鸡毛。</li>
</ol>
<blockquote>
<p>“我们就像一群踢足球的孩子，看到球滚到哪里，所有人就一窝蜂地冲过去，连守门员都离开了球门。结果是，球门大开，后方空虚。”</p>
</blockquote>
<p>这就是为什么 10 年过去了，我们还在谈论容器。因为我们当年并没有真正“完成”它。我们留下了无数的复杂性、不兼容和“企业级发行版”，却忘了初衷。</p>
<h2>Apple 的“非性感”工作——这才是未来</h2>
<p>在演讲中，Kelsey 分享了他最近的一个惊人发现：<strong>Apple 正在 macOS 中原生集成容器运行时。</strong></p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/why-are-we-still-talking-about-containers-in-ai-age-3.png" alt="" /></p>
<p>这不是 Docker Desktop，也不是虚拟机套娃，而是操作系统级别的原生支持。这就是 GitHub 上的一个名为 <strong>apple/container</strong> 的 Apple 开源项目：</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/why-are-we-still-talking-about-containers-in-ai-age-4.png" alt="" /></p>
<p>Kelsey 提到 contributors 中有 Docker 元老 Michael Crosby ，Michael Crosby 正在 Apple 做着这件“不性感”但极其重要的事情。</p>
<p>Kelsey 认为，这才是容器技术的<strong>终局</strong>：</p>
<ul>
<li><strong>标准化</strong>：容器运行时将成为像 TCP/IP 协议栈一样的操作系统标配，无论你是 Linux、macOS 还是 Windows。</li>
<li><strong>隐形化</strong>：你不再需要安装 Docker，不再需要关心运行时。它就在那里，像水和电一样自然。</li>
<li><strong>应用商店的重构</strong>：未来，App Store 分发的可能就是容器镜像，彻底解决依赖冲突和安全沙箱问题。</li>
</ul>
<p>这正是那些没有去追逐 AI 热点，而是选择留在“球门”前的人，正在默默完成的伟大工程。</p>
<h2>关于 AI——不要做“盲目的复制者”</h2>
<p>作为 Google 前员工，Kelsey 对 AI 并不陌生。但他对当前的 LLM 热潮保持着清醒的警惕。</p>
<p>他现场演示了一个有趣的实验：询问一个本地运行的 LLM “FreeBSD Service Jails 需要什么版本？”<br />
*   <strong>AI 的回答</strong>：FreeBSD 13（一本正经的胡说八道）。<br />
*   <strong>真相</strong>：FreeBSD 15（尚未发布）。</p>
<p>Kelsey 指出，现在的 AI 就像一个热心但糊涂的路人，它不懂装懂，只想取悦你。</p>
<p><strong>他的建议是</strong>：</p>
<ol>
<li><strong>不要迷信生成</strong>：不要因为 AI 生成了代码就直接用，就像你不会盲目复制 Stack Overflow 的代码一样。</li>
<li><strong>上下文为王</strong>：AI 不是魔法，它只是一个强大的搜索引擎。如果你想得到正确答案，你必须先给它提供正确的<strong>上下文（Context）</strong>。</li>
<li><strong>先训练自己，再训练模型</strong>：在成为“提示词工程师”之前，先成为一名合格的工程师。只有当你自己深刻理解了问题，你才能判断 AI 的回答是天才还是垃圾。</li>
</ol>
<h2>给技术人的最后忠告</h2>
<p>演讲的最后，Kelsey 回答了关于开源、职业发展和未来的提问。他的几条忠告，值得每一位技术人铭记：</p>
<ul>
<li><strong>关于职业</strong>：“你的职业生涯不应该是一场马拉松，而应该是一场<strong>接力赛</strong>。当你到达巅峰时，想的应该是如何把接力棒交给下一个人，而不是霸占着位置直到倒下。”</li>
<li><strong>关于开源</strong>：“不要被商业公司的许可证游戏迷惑。如果代码是公开的，你可以 fork，可以学习。真正的开源精神在于分享和协作，而不在于谁拥有控制权。”</li>
<li><strong>关于专注</strong>：像那家只做钳子的德国公司（Knipex）一样，专注做好一件事。技术圈不缺追风者，缺的是能够沉下心来，把一项技术打磨到极致、直到它变得“无聊”和“隐形”的工匠。</li>
</ul>
<h2>小结</h2>
<p>Kelsey Hightower 的这场演讲，是对当前浮躁技术圈的一剂清醒剂。</p>
<p>他提醒我们，技术的真正价值，不在于它有多新、多热，而在于它是否真正解决了问题，是否被<strong>完整地</strong>交付了。在所有人都在谈论 AI 的今天，或许我们更应该关注那些被遗忘的“球门”，去完成那些尚未完成的伟大工程。</p>
<p>资料链接：https://www.youtube.com/watch?v=x1t2GPChhX8</p>
<hr />
<p><strong>你的“烂尾”故事</strong></p>
<p>Kelsey 的“海啸循环”论断让人深思。在你的职业生涯中，是否也经历过这种“还没做完旧技术，就被迫去追新热点”的无奈？你认为在这个 AI 时代，我们该如何保持“工匠精神”？</p>
<p>欢迎在评论区分享你的经历或思考！让我们一起在喧嚣中寻找内心的宁静。</p>
<p>如果这篇文章让你停下来思考了片刻，别忘了点个【赞】和【在看】，并转发给那些还在焦虑中奔跑的同行！</p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/01/22/why-are-we-still-talking-about-containers-in-ai-age/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Go 1.25中值得关注的几个变化</title>
		<link>https://tonybai.com/2025/08/15/some-changes-in-go-1-25/</link>
		<comments>https://tonybai.com/2025/08/15/some-changes-in-go-1-25/#comments</comments>
		<pubDate>Fri, 15 Aug 2025 00:21:19 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Cgroup]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[CoreType]]></category>
		<category><![CDATA[CPU]]></category>
		<category><![CDATA[DWARF5]]></category>
		<category><![CDATA[encoding]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go-import]]></category>
		<category><![CDATA[go.mod]]></category>
		<category><![CDATA[Go1]]></category>
		<category><![CDATA[go1.24]]></category>
		<category><![CDATA[godoc]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GOMAXPROCS]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[govet]]></category>
		<category><![CDATA[GPU]]></category>
		<category><![CDATA[ignore]]></category>
		<category><![CDATA[json]]></category>
		<category><![CDATA[jsonv2]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[limit]]></category>
		<category><![CDATA[map]]></category>
		<category><![CDATA[marshal]]></category>
		<category><![CDATA[monorepo]]></category>
		<category><![CDATA[nil]]></category>
		<category><![CDATA[pprof]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[spec]]></category>
		<category><![CDATA[subdir]]></category>
		<category><![CDATA[swisstable]]></category>
		<category><![CDATA[sync]]></category>
		<category><![CDATA[synctest]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[toolchain]]></category>
		<category><![CDATA[unmarshal]]></category>
		<category><![CDATA[vanity-import]]></category>
		<category><![CDATA[waitgroup]]></category>
		<category><![CDATA[兼容性]]></category>
		<category><![CDATA[单一仓库]]></category>
		<category><![CDATA[垃圾回收]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[工具链]]></category>
		<category><![CDATA[指针]]></category>
		<category><![CDATA[标准库]]></category>
		<category><![CDATA[泛型]]></category>
		<category><![CDATA[流式]]></category>
		<category><![CDATA[特性]]></category>
		<category><![CDATA[空指针]]></category>
		<category><![CDATA[编解码]]></category>
		<category><![CDATA[规范]]></category>
		<category><![CDATA[语法]]></category>
		<category><![CDATA[运行时]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5037</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/08/15/some-changes-in-go-1-25 大家好，我是Tony Bai。 北京时间2025年8月13日，Go 团队如期发布了 Go 语言的最新大版本——Go 1.25。按照惯例，每次 Go 大版本发布时，我都会撰写一篇“Go 1.x 中值得关注的几个变化”的文章。自 2014 年的 Go 1.4 版本起，这一系列文章已经伴随大家走过了十一个年头。 不过，随着我在版本冻结前推出的“Go 1.x 新特性前瞻”系列，以及对该大版本可能加入特性的一些独立的解读文章，本系列文章的形式也在不断演变。本文将不再对每个特性进行细致入微的分析，因为这些深度内容大多已在之前的《Go 1.25 新特性前瞻》一文中详细讨论过。本文将更聚焦于提炼核心亮点，并分享一些我的思考。 好了，言归正传，我们来看看Go 1.25带来了哪些惊喜！ 语言变化：兼容性基石上的精雕细琢 正如 Go 一贯所做的，新版 Go 1.25 继续遵循 Go1 的兼容性规范。最令 Gopher 们安心的一点是：Go 1.25 没有引入任何影响现有 Go 程序的语言级变更。 There are no languages changes that affect Go programs in Go 1.25. 这种对稳定性的极致追求，是 Go [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/some-changes-in-go-1-25-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/08/15/some-changes-in-go-1-25">本文永久链接</a> &#8211; https://tonybai.com/2025/08/15/some-changes-in-go-1-25</p>
<p>大家好，我是Tony Bai。</p>
<p>北京时间2025年8月13日，Go 团队如期发布了 Go 语言的最新大版本——<a href="https://go.dev/blog/go1.25">Go 1.25</a>。按照惯例，每次 Go 大版本发布时，我都会撰写一篇“Go 1.x 中值得关注的几个变化”的文章。自 2014 年的 <a href="https://tonybai.com/2014/11/04/some-changes-in-go-1-4">Go 1.4 版本</a>起，这一系列文章已经伴随大家走过了十一个年头。</p>
<p>不过，随着我在版本冻结前推出的“Go 1.x 新特性前瞻”系列，以及对该大版本可能加入特性的一些独立的解读文章，本系列文章的形式也在不断演变。本文将不再对每个特性进行细致入微的分析，因为这些深度内容大多已在之前的<a href="https://tonybai.com/2025/06/14/go-1-25-foresight">《Go 1.25 新特性前瞻》</a>一文中详细讨论过。本文将更聚焦于提炼核心亮点，并分享一些我的思考。</p>
<p>好了，言归正传，我们来看看Go 1.25带来了哪些惊喜！</p>
<h2>语言变化：兼容性基石上的精雕细琢</h2>
<p>正如 Go 一贯所做的，新版 Go 1.25 继续遵循 <a href="https://go.dev/doc/go1compat">Go1 的兼容性规范</a>。最令 Gopher 们安心的一点是：<strong>Go 1.25 没有引入任何影响现有 Go 程序的语言级变更</strong>。</p>
<blockquote>
<p>There are no languages changes that affect Go programs in Go 1.25.</p>
</blockquote>
<p>这种对稳定性的极致追求，是 Go 成为生产环境首选语言之一的重要原因。</p>
<p>尽管语法层面波澜不惊，但语言规范内部却进行了一次“大扫除”——<strong>移除了“core types”的概念</strong>。这一变化虽然对日常编码无直接影响，但它简化了语言规范，为未来泛型可能的演进铺平了道路，体现了 Go 团队在设计层面的严谨与远见。关于此变化的深度解读，可以回顾我之前的文章《<a href="https://tonybai.com/2025/03/27/remove-coretypes-from-go-spec/">Go 1.25 规范大扫除：移除“Core Types”，为更灵活的泛型铺路</a>》。</p>
<h2>编译器与运行时：看不见的性能飞跃</h2>
<p>如果说 <a href="https://tonybai.com/2025/02/16/some-changes-in-go-1-24">Go 1.24</a> 的运行时核心是<a href="https://tonybai.com/2024/11/14/go-map-use-swiss-table">优化 map</a>，那么 Go 1.25 的灵魂则在于让 Go 程序更“懂”其运行环境，并对 GC 进行了大刀阔斧的革新。</p>
<h3>容器感知型 GOMAXPROCS</h3>
<p>这无疑是 Go 1.25 最具影响力的变化之一。在容器化部署已成事实标准的今天，Go 1.25 的运行时终于具备了 <strong>cgroup 感知能力</strong>。在 Linux 系统上，它会默认根据容器的 CPU limit 来设置 GOMAXPROCS，并能动态适应 limit 的变化。</p>
<p>这意味着，只需升级到 Go 1.25，你的 Go 应用在 K8s 等环境中的 CPU 资源使用将变得更加智能和高效，告别了过去因 GOMAXPROCS 默认值不当而导致的资源浪费或性能瓶颈。更多细节，请参阅我的文章《<a href="https://tonybai.com/2025/04/09/gomaxprocs-defaults-add-cgroup-aware/">Go 1.25 新提案：GOMAXPROCS 默认值将迎 Cgroup 感知能力，终结容器性能噩梦？</a>》。</p>
<h3>实验性的 Green Tea GC</h3>
<p>Go 1.25 迈出了 GC 优化的重要一步，引入了一个新的实验性垃圾收集器。通过设置 GOEXPERIMENT=greenteagc 即可在构建时启用。</p>
<blockquote>
<p>A new garbage collector is now available as an experiment. This garbage collector’s design improves the performance of marking and scanning small objects through better locality and CPU scalability.</p>
</blockquote>
<p>据官方透露，这个新 GC 有望为真实世界的程序带来 <strong>10%—40% 的 GC 开销降低</strong>。知名go开发者Josh Baker(@tidwall)在Go 1.25发布正式版后，在X上分享了自己使用go 1.25新gc（绿茶）后的结果，他开源的实时地理空间和地理围栏项目tile38的GC开销下降35%：</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/some-changes-in-go-1-25-2.png" alt="" /></p>
<p>这是一个巨大的性能红利，尤其对于重度依赖GC的内存密集型应用。虽然它仍在实验阶段，但其展现的潜力已足够令人兴奋。对 Green Tea GC 设计原理感兴趣的朋友，可以阅读我的文章《<a href="https://tonybai.com/2025/05/03/go-green-tea-garbage-collector/">Go 新垃圾回收器登场：Green Tea GC 如何通过内存感知显著降低 CPU 开销？</a>》。</p>
<p>此外，Go 1.25 还修复了一个存在于 Go 1.21 至 1.24 版本中可能导致 <strong>nil pointer 检查被错误延迟的编译器 bug</strong>，并默认启用了 <strong>DWARFv5 调试信息</strong>，进一步缩小了二进制文件体积并加快了链接速度，对DWARFv5感兴趣的小伙伴儿可以重温一下我之前的《<a href="https://tonybai.com/2025/05/08/go-dwarf5/">Go 1.25链接器提速、执行文件瘦身：DWARF 5调试信息格式升级终落地</a>》一文，了解详情。</p>
<h2>工具链：效率与可靠性的双重提升</h2>
<p>强大的工具链是 Go 生产力的核心保障。Go 1.25 在此基础上继续添砖加瓦。</p>
<h3>go.mod 新增 ignore 指令</h3>
<p>对于大型 Monorepo 项目，go.mod 新增的 ignore 指令是一个福音。它允许你指定 Go 命令在匹配包模式时应忽略的目录，从而在不影响模块依赖的前提下，有效提升大型、混合语言仓库中的构建与扫描效率。关于此特性的详细用法，请见《<a href="https://tonybai.com/2025/05/22/go-mod-ignore-directive/">Go 工具链进化：go.mod 新增 ignore 指令，破解混合项目构建难题</a>》。</p>
<h3>支持仓库子目录作为模块根路径</h3>
<p>一个长期困扰 Monorepo 管理者和自定义 vanity import 用户的难题在 Go 1.25 中也得到了解决。Go 命令现在支持在解析 go-import meta 标签时，通过新增的 subdir 字段，将 Git 仓库中的子目录指定为模块的根。</p>
<p>这意味着，你可以轻松地将 github.com/my-org/my-repo/foo/bar 目录映射为模块路径 my.domain/bar，而无需复杂的代理或目录结构调整。这个看似微小但备受期待的改进，极大地提升了 Go 模块在复杂项目结构中的灵活性。想了解其来龙去脉和具体配置方法，可以参考我的文章《<a href="https://tonybai.com/2025/06/07/allow-serving-module-under-subdir">千呼万唤始出来？Go 1.25解决Git仓库子目录作为模块根路径难题</a>》。</p>
<h3>go doc -http：即开即用的本地文档</h3>
<p>这是一个虽小但美的改进。新的 go doc -http 选项可以快速启动一个本地文档服务器，并在浏览器中直接打开指定对象的文档。对于习惯于离线工作的开发者来说，这极大地提升了查阅文档的便捷性。详细介绍见《<a href="https://tonybai.com/2024/09/06/go-doc-add-http-support/">重拾精髓：go doc -http 让离线包文档浏览更便捷</a>》。</p>
<h3>go vet 新增分析器</h3>
<p>go vet 变得更加智能，新增了两个实用的分析器：</p>
<ul>
<li><strong>waitgroup</strong>：检查 sync.WaitGroup.Add 的调用位置是否错误（例如在 goroutine 内部调用）。</li>
<li><strong>hostport</strong>：诊断不兼容 IPv6 的地址拼接方式 fmt.Sprintf(“%s:%d”, host, port)，并建议使用 net.JoinHostPort。</li>
</ul>
<p>这些静态检查能帮助我们在编码阶段就扼杀掉一批常见的并发和网络编程错误。</p>
<h2>标准库：功能毕业与实验探索</h2>
<p>标准库的演进是每个 Go 版本的重要看点。</p>
<h3>testing/synctest 正式毕业</h3>
<p>在 Go 1.24 中以实验特性登场的 testing/synctest 包，在 Go 1.25 中正式毕业，成为标准库的一员。它为并发代码测试提供了前所未有的利器，通过虚拟化时间和调度，让编写可靠、无 flakiness 的并发测试成为可能。我曾撰写过一个<strong>“<a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzIyNzM0MDk0Mg==&amp;action=getalbum&amp;album_id=4017357519222882315#wechat_redirect">征服 Go 并发测试</a>”</strong>的微专栏，系统地介绍了该包的设计与实践，欢迎大家订阅学习。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/go-concurrent-test-qr.png" alt="" /></p>
<h3>encoding/json/v2 开启实验</h3>
<p>这是 Go 1.25 最受关注的实验性特性之一！通过 GOEXPERIMENT=jsonv2 环境变量，我们可以启用一个全新的、高性能的 JSON 实现。</p>
<blockquote>
<p>Go 1.25 includes a new, experimental JSON implementation&#8230; The new implementation performs substantially better than the existing one under many scenarios.</p>
</blockquote>
<p>根据官方说明，json/v2 在解码性能上相较于 v1 有了“巨大”的提升。这是 Go 社区多年来对 encoding/json 包性能诟病的一次正面回应。虽然其 API 仍在演进中，但它预示着 Go 的 JSON 处理能力未来将达到新的高度。对 v2 的初探，可以参考我的文章《<a href="https://tonybai.com/2025/05/15/go-json-v2/">手把手带你玩转 GOEXPERIMENT=jsonv2：Go 下一代 JSON 库初探</a>》。jsonv2支持真流式编解码的方法，也可以参考《<a href="https://tonybai.com/2025/08/09/true-streaming-support-in-jsonv2/">Go json/v2实战：告别内存爆炸，掌握真流式Marshal和Unmarshal</a>》这篇文章。</p>
<h3>sync.WaitGroup.Go：并发模式更便捷</h3>
<p>Go 语言的并发编程哲学之一就是让事情保持简单。Go 1.25 在 sync.WaitGroup 上新增的 Go 方法，正是这一哲学的体现。</p>
<p>这个新方法旨在消除 wg.Add(1) 和 defer wg.Done() 这一对经典的样板代码。现在，你可以直接调用 wg.Go(func() { &#8230; }) 来启动一个被 WaitGroup 追踪的 goroutine，Add 和 Done 的调用由 Go 方法在内部自动处理。这不仅让代码更简洁，也从根本上避免了因忘记调用 Add 或 Done 而导致的常见并发错误。</p>
<p>关于这个便捷方法的来龙去脉和设计思考，可以回顾我之前的文章《<a href="https://tonybai.com/2025/04/03/waitgroup-go-proposal/">WaitGroup.Go 要来了？Go 官方提案或让你告别 Add 和 Done 样板代码</a>》。</p>
<h2>其他：Trace Flight Recorder</h2>
<p>最后，我想特别提一下 runtime/trace 包新增的 <strong>Flight Recorder</strong> API。传统的运行时 trace 功能强大但开销巨大，不适合在生产环境中持续开启。</p>
<p>trace.FlightRecorder 提供了一种轻量级的解决方案：它将 trace 数据持续记录到一个内存中的环形缓冲区。当程序中发生某个重要事件（如一次罕见的错误）时，我们可以调用 FlightRecorder.WriteTo 将最近一段时间的 trace 数据快照保存到文件。这种“事后捕获”的模式，使得在生产环境中调试偶发、疑难的性能或调度问题成为可能，是 Go 诊断能力的一次重大升级。更多详情可以参阅《<a href="https://tonybai.com/2025/07/11/net-http-pprof-v2/">Go pprof 迎来重大革新：v2 提案详解，告别默认注册，拥抱飞行记录器</a>》。</p>
<h2>小结</h2>
<p>Go 1.25 的发布，再次彰显了 Go 语言务实求进的核心哲学。它没有追求华而不实的语法糖，而是将精力聚焦于那些能为广大开发者带来“无形收益”的领域：<strong>更智能的运行时、更快的 GC、更可靠的编译器、更高效的工具链</strong>。</p>
<p>这些看似底层的改进，正是 Go 作为一门“生产力语言”的价值所在。它让开发者可以专注于业务逻辑，而将复杂的系统优化和环境适配，放心地交给 Go 语言自身。</p>
<p>我鼓励大家尽快将 Go 1.25 应用到自己的项目中，亲自感受这些变化带来的提升。Go 的旅程，仍在继续，让我们共同期待它在未来创造更多的可能。</p>
<p><strong>感谢阅读！</strong></p>
<p>如果这篇文章让你对 Go 1.25 新特性有了新的认识，请帮忙 <strong>点赞</strong>和<strong>分享</strong>，让更多朋友一起学习和进步！</p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/08/15/some-changes-in-go-1-25/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go 1.25新特性前瞻：GC提速，容器更“懂”Go，json有v2了！</title>
		<link>https://tonybai.com/2025/06/14/go-1-25-foresight/</link>
		<comments>https://tonybai.com/2025/06/14/go-1-25-foresight/#comments</comments>
		<pubDate>Sat, 14 Jun 2025 00:06:39 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Cgroup]]></category>
		<category><![CDATA[Compiler]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[CoreType]]></category>
		<category><![CDATA[CPU]]></category>
		<category><![CDATA[crypto]]></category>
		<category><![CDATA[DWARF5]]></category>
		<category><![CDATA[encoding]]></category>
		<category><![CDATA[fmt]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go.mod]]></category>
		<category><![CDATA[Go1]]></category>
		<category><![CDATA[go1.25]]></category>
		<category><![CDATA[gobuild]]></category>
		<category><![CDATA[godoc]]></category>
		<category><![CDATA[GOEXPERIMENT]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GOMAXPROCS]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[govet]]></category>
		<category><![CDATA[ignore]]></category>
		<category><![CDATA[json]]></category>
		<category><![CDATA[jsonv2]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[nil]]></category>
		<category><![CDATA[panic]]></category>
		<category><![CDATA[Pointer]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[Sprintf]]></category>
		<category><![CDATA[sync]]></category>
		<category><![CDATA[synctest]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[TLS]]></category>
		<category><![CDATA[waitgroup]]></category>
		<category><![CDATA[兼容性]]></category>
		<category><![CDATA[向后兼容性]]></category>
		<category><![CDATA[垃圾回收]]></category>
		<category><![CDATA[垃圾收集器]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[工具链]]></category>
		<category><![CDATA[指针]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[运行时]]></category>
		<category><![CDATA[链接器]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4817</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/06/14/go-1-25-foresight 大家好，我是Tony Bai。 每年，Go 语言都会以其严谨而高效的节奏，带来两次版本更新。每一次迭代，Go 团队都在底层、工具链和标准库上持续深耕，为我们开发者提供更稳健、更高效、更安全的开发体验。虽然 Go 1.25 的正式版预计在 2025 年 8 月发布，但随着近期Go 1.25RC1版本的推出，我们基于其非最终版的 Release Notes，已经能一窥其核心亮点了。并且，和之前的版本一样，Go 1.25 带来的许多改进，都如同“无形之手”，你可能无需修改一行代码，甚至无需刻意感知，只需简单升级，便能享受到性能的飞跃、诊断能力的提升以及潜藏错误的暴露。这正是 Go 团队践行其核心原则的极致体现。 今天，就让我们一起“未雨绸缪”，聚焦 Go 1.25 中的核心特性，看看它将如何让 Go 语言变得更加强大。 语言层面：兼容至上，细微进化 Go语言对向后兼容性的承诺，是其最受开发者赞誉的特性之一。Go 1.25 再次延续了这一传统：它没有引入任何影响现有 Go 程序的语言语法变更！ 这意味着你可以放心地升级到 Go 1.25，而无需担忧已有的代码库会因此“崩溃”。 尽管如此，语言规范层面仍有细微的整理和优化，例如移除了“core type”的概念，代之以更详细的描述。这些更多是内部设计文档的完善，对日常 Go 程序的编写并无直接影响，但体现了 Go 语言设计本身的严谨性和持续迭代。兼容性，依然是 Go 坚不可摧的基石。 更详细地说明可以参考我之前的文章《Go 1.25规范大扫除：移除“Core Types”，为更灵活的泛型铺路》。 运行时与编译器：性能与可靠性的“幕后推手” 这一部分是 Go 1.25 带来诸多“无形”强大之处的集中体现，它们直接影响着 Go 程序的运行效率和稳定性。 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/go-1-25-foresight-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/06/14/go-1-25-foresight">本文永久链接</a> &#8211; https://tonybai.com/2025/06/14/go-1-25-foresight</p>
<p>大家好，我是Tony Bai。</p>
<p>每年，Go 语言都会以其严谨而高效的节奏，带来两次版本更新。每一次迭代，Go 团队都在底层、工具链和标准库上持续深耕，为我们开发者提供更稳健、更高效、更安全的开发体验。虽然 Go 1.25 的正式版预计在 2025 年 8 月发布，但随着近期Go 1.25RC1版本的推出，我们基于其非最终版的 Release Notes，已经能一窥其核心亮点了。并且，和之前的版本一样，Go 1.25 带来的许多改进，都如同“无形之手”，你可能无需修改一行代码，甚至无需刻意感知，只需简单升级，便能享受到性能的飞跃、诊断能力的提升以及潜藏错误的暴露。这正是 Go 团队践行其核心原则的极致体现。</p>
<p>今天，就让我们一起“未雨绸缪”，聚焦 Go 1.25 中的核心特性，看看它将如何让 Go 语言变得更加强大。</p>
<h2>语言层面：兼容至上，细微进化</h2>
<p>Go语言对<strong>向后兼容性</strong>的承诺，是其最受开发者赞誉的特性之一。Go 1.25 再次延续了这一传统：<strong>它没有引入任何影响现有 Go 程序的语言语法变更！</strong> 这意味着你可以放心地升级到 Go 1.25，而无需担忧已有的代码库会因此“崩溃”。</p>
<p>尽管如此，语言规范层面仍有细微的整理和优化，例如<a href="https://tonybai.com/2025/03/27/remove-coretypes-from-go-spec">移除了“core type”的概念</a>，代之以更详细的描述。这些更多是内部设计文档的完善，对日常 Go 程序的编写并无直接影响，但体现了 Go 语言设计本身的严谨性和持续迭代。兼容性，依然是 Go 坚不可摧的基石。</p>
<blockquote>
<p>更详细地说明可以参考我之前的文章《<a href="https://tonybai.com/2025/03/27/remove-coretypes-from-go-spec/">Go 1.25规范大扫除：移除“Core Types”，为更灵活的泛型铺路</a>》。</p>
</blockquote>
<h2>运行时与编译器：性能与可靠性的“幕后推手”</h2>
<p>这一部分是 Go 1.25 带来诸多“无形”强大之处的集中体现，它们直接影响着 Go 程序的运行效率和稳定性。</p>
<h3>容器感知型 GOMAXPROCS：更懂容器的 CPU 脾气</h3>
<p>在容器化部署日益普及的今天，Go 程序在 Kubernetes 等环境中运行，常常会遇到一个问题：GOMAXPROCS（控制 Go 运行时使用的最大 CPU 核心数）默认值是宿主机逻辑 CPU 数，而非容器实际被分配的 CPU 限制。这可能导致 CPU 资源浪费，或程序试图抢占过多资源，进而引发调度问题。</p>
<p>Go 1.25 带来了重大改进：在 Linux 系统上，Go 运行时现在会<strong>默认考虑 cgroup 的 CPU 限制（即容器的 CPU limit）</strong> 来设置 GOMAXPROCS 的默认值。如果 CPU limit 低于宿主机核心数，GOMAXPROCS 将自动降到这个更低的限制。此外，Go 运行时还会<strong>定期更新 GOMAXPROCS</strong>，以适应 cgroup 限制的动态变化。这一改进，直接解决了 Go 应用在容器环境中可能存在的资源配置不当问题，使得 Go 程序在 K8s 等云原生环境中运行时更加高效和“智能”，真正做到“物尽其用”。</p>
<blockquote>
<p>更详细地说明可以参考我之前的文章《<a href="https://tonybai.com/2025/04/09/gomaxprocs-defaults-add-cgroup-aware/">Go 1.25新提案：GOMAXPROCS默认值将迎Cgroup感知能力，终结容器性能噩梦？</a>》。</p>
</blockquote>
<h3>新的实验性垃圾收集器：GC开销有望显著降低</h3>
<p>Go 1.25 引入了一个<strong>新的实验性垃圾收集器</strong>，可以通过设置 GOEXPERIMENT=greenteagc 在构建时启用。这个新 GC 的设计旨在改进小对象的标记和扫描性能，并提升 CPU 可扩展性。</p>
<p>根据官方的基准测试，在实际应用中，垃圾回收的开销有望减少 <strong>10% 到 40%</strong>！如果这一实验性优化最终成熟并默认启用，将显著降低 Go 程序的 GC 停顿和整体资源消耗，对于所有 Go 应用（尤其是内存密集型应用）来说，这无疑是巨大的性能红利。</p>
<blockquote>
<p>更详细地说明可以参考我之前的文章《<a href="https://tonybai.com/2025/05/03/go-green-tea-garbage-collector/">Go新垃圾回收器登场：Green Tea GC如何通过内存感知显著降低CPU开销？</a>》。</p>
</blockquote>
<h3>更精准的 Nil Pointer Panic：让隐藏的 Bug 无所遁形</h3>
<p>这是一个虽然可能“打破”一些旧代码，但从长远来看极为重要的改进。Go 1.21 到 1.24 版本之间曾存在一个编译器 bug，导致某些在 os.Open 返回 nil 错误时，仍能“幸运地”继续运行并访问 nil 指针，而没有立即 panic。</p>
<pre><code class="go">// Go 1.21-1.24 曾因编译器bug可能不panic的示例
package main
import "os"
func main() {
    f, err := os.Open("nonExistentFile") // err != nil, f 是 nil
    name := f.Name() // 这里访问了 nil.Name()，但可能不panic
    if err != nil {
        return
    }
    println(name)
}
</code></pre>
<p>在 Go 1.25 中，这个编译器 bug 已经被修复，确保 nil 指针检查会及时且准确地执行。这意味着，上述示例中的代码在 Go 1.25 中将明确引发 nil 指针 panic。</p>
<p>这一变化提高了 Go 程序的运行时可靠性，让那些原本被编译器“侥幸放过”的隐藏 Bug 得以暴露。如果你的代码中存在类似问题，升级后可能需要进行修正，将非 nil 错误检查提前到使用变量之前。</p>
<h3>DWARF版本5 支持：更小更快，调试无忧</h3>
<p>Go 1.25 的编译器和链接器现在默认生成 <strong>DWARFv5 调试信息</strong>。这种更新的调试信息格式，可以有效减少 Go 二进制文件中调试信息所需的空间，并缩短程序的链接时间，对于构建大型 Go 应用程序尤其有利，有助于提升开发效率和 CI/CD 流程的速度。</p>
<blockquote>
<p>更详细地说明可以参考我之前的文章《<a href="https://tonybai.com/2025/05/08/go-dwarf5/">Go 1.25链接器提速、执行文件瘦身：DWARF 5调试信息格式升级终落地</a>》。</p>
</blockquote>
<h2>工具链：武装开发者，提升效率</h2>
<p>Go 语言强大的工具链是其生产力的重要保障。Go 1.25 在此基础上进一步发力，带来多项实用改进。</p>
<ul>
<li><strong>go build -asan 默认内存泄漏检测：Cgo 混合编程更安全</strong></li>
</ul>
<p>对于涉及到 Go 与 C/C++ 代码混合编程的场景，内存泄漏诊断一直是个挑战。Go 1.25 中，go build -asan 选项现在默认在程序退出时进行<strong>内存泄漏检测</strong>，能够报告 C 语言分配但未释放的内存。这大大增强了 Go 混合编程时的内存安全性，有助于发现原生代码中的隐蔽内存问题。</p>
<ul>
<li><strong>go.mod ignore directive：灵活管理超大型仓库</strong></li>
</ul>
<p>go.mod 文件新增了 ignore directive，允许你指定 Go 命令在匹配包模式（如 all 或 ./&#8230;）时应忽略的目录。这些目录下的文件不会被 Go 命令扫描和处理。这对于管理包含大量非 Go 代码、文档、或子模块的超大型代码仓库（Monorepo）非常有用，可以减少构建和扫描时间，提高 Go Modules 的灵活性。</p>
<blockquote>
<p>更详细地说明可以参考我之前的文章《<a href="https://tonybai.com/2025/05/22/go-mod-ignore-directive/">Go工具链进化：go.mod新增ignore指令，破解混合项目构建难题</a>》。</p>
</blockquote>
<ul>
<li><strong>go doc -http：本地文档，即开即用</strong></li>
</ul>
<p>一个看似小巧但能极大提升开发体验的改进。新的 go doc -http 选项，可以启动一个本地文档服务器，显示指定 Go 对象的文档，并自动在浏览器中打开。从此，查阅 Go 文档变得更加便捷、直观。</p>
<blockquote>
<p>更详细地说明可以参考我之前的文章《<a href="https://tonybai.com/2024/09/06/go-doc-add-http-support/">重拾精髓：go doc -http让离线包文档浏览更便捷</a>》。</p>
</blockquote>
<ul>
<li><strong>Vet 工具新分析器：提前发现常见 Bug</strong></li>
</ul>
<p>go vet 工具新增了两个实用的分析器。一个是waitgroup，能报告 sync.WaitGroup.Add 的不正确调用位置（例如在 go 协程内部调用）。另外一个是hostport，能检测并建议修正 fmt.Sprintf(“%s:%d”, host, port) 这种不兼容 IPv6 的地址构造方式，推荐使用 net.JoinHostPort。</p>
<p>这些分析器能帮助开发者在编码阶段就避免常见的并发和网络编程陷阱，进一步提升代码质量和可靠性。</p>
<h2>标准库：功能增强与实验性探索</h2>
<p>标准库的不断演进是 Go 保持活力的重要源泉。Go 1.25 在此也带来了多项关键变化。</p>
<h3>testing/synctest：并发测试的新利器</h3>
<p>Go 1.25 引入了全新的 testing/synctest 包，为并发代码的测试提供了原生支持。它允许你在一个隔离的“气泡”（bubble）中运行测试函数，并且能够控制测试环境中时间（使用伪造时钟）和协程的阻塞/恢复。这极大地方便了并发代码的调试和测试，尤其是那些依赖时间或 Goroutine 调度顺序的复杂场景，提高了测试的可靠性和可控性。</p>
<p>关于该特性，我曾编写过一个“<a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzIyNzM0MDk0Mg==&amp;action=getalbum&amp;album_id=1509674724631609344#wechat_redirect">征服Go并发测试</a>”的微专栏，欢迎大家扫描订阅，了解关于synctest的设计、实现以及实践方式。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/go-concurrent-test-qr.png" alt="" /></p>
<h3>encoding/json/v2 实验性版本：高性能 JSON 编解码展望</h3>
<p>Go 1.25 引入了一个<strong>新的、实验性的 encoding/json/v2 包</strong>，可以通过设置 GOEXPERIMENT=jsonv2 环境变量在构建时启用。这是对 Go 核心 encoding/json 包的一次重大修订，旨在提升性能和提供更灵活的配置选项。根据初步测试，新实现<strong>在解码性能上显著优于现有版本</strong>，并提供了更多配置 marshaler 和 unmarshaler 的选项。</p>
<p>这是一个令人兴奋的实验性功能，预示着 Go 的 JSON 编解码能力未来将更上一层楼。但作为实验性特性，Go 团队鼓励开发者积极测试自己的程序，并向社区提供反馈，帮助其持续演进。</p>
<blockquote>
<p>关于jsonv2使用的更详细地介绍可以参考我之前的文章《<a href="https://tonybai.com/2025/05/15/go-json-v2/">手把手带你玩转GOEXPERIMENT=jsonv2：Go下一代JSON库初探</a>》。</p>
</blockquote>
<h3>crypto/tls 持续增强：安全与隐私不放松</h3>
<p>Go 在密码学领域的投入从未停止。Go 1.25 中的 crypto/tls 包获得了多项改进：</p>
<ul>
<li>新增 Config.GetEncryptedClientHelloKeys 回调，支持 <strong>Encrypted Client Hello (ECH)</strong> 扩展，进一步提升 TLS 客户端的连接隐私。</li>
<li>默认禁用 TLS 1.2 握手中的 SHA-1 签名算法（但可以通过 tlssha1=1 的 GODEBUG 选项重新启用）。</li>
<li>在<a href="https://tonybai.com/2025/05/21/go-crypto-audit/"> FIPS 140-3 模式</a>下，允许使用更现代的 Ed25519 和 X25519MLKEM768 密钥交换算法。</li>
</ul>
<p>这些改进持续强化了 Go TLS 的安全性、隐私保护和合规性，为迎接未来的量子安全和更严格的安全标准做准备。</p>
<h3>unique 包改进：内存优化再进一步</h3>
<p>unique 包现在能更积极、高效地回收内部化值，有效减少在处理大量重复值时可能出现的内存膨胀问题。这对于 Go 编译器、LSP (Language Server Protocol) 等会大量使用 unique 包的场景，将带来显著的内存和性能优化。</p>
<h3>sync.WaitGroup.Go：并发模式更便捷</h3>
<p>sync.WaitGroup 新增了 Go 方法，为创建和计数 goroutine 提供了一个更便捷的封装，进一步简化了 Go 中常见的并发模式的写法。在之前的文章《<a href="https://tonybai.com/2025/04/03/waitgroup-go-proposal/">WaitGroup.Go要来了？Go官方提案或让你告别Add和Done样板代码</a>》有对这一特性来龙去脉的纤细说明。</p>
<h2>小结</h2>
<p>Go 1.25 的预发布版本，清晰地展现了 Go 语言在性能、可靠性、安全性和开发者体验上的全面提升。这些变化，无论是底层运行时的“无形”优化，还是工具链的智能辅助，都紧密围绕着 Go“生产力”和“生产就绪”的核心原则。</p>
<p>作为 Go 开发者，我们能从中获得的益处是巨大的：你不需要成为系统底层的专家，便能享受到 Go 团队带来的最新技术红利。这种“升级即获益”的模式，正是 Go 语言独特魅力的体现。</p>
<p>Go 语言的旅程永不停歇，它在不断地进化和完善。我鼓励所有 Go 开发者，积极尝试 Go 1.25 RC1 版本，将其应用到你的开发、测试环境中，并向 Go 团队提供宝贵的反馈。你的参与，将是对Go 团队最大的帮助。</p>
<hr />
<p><strong>精进有道，更上层楼</strong></p>
<p><a href="https://mp.weixin.qq.com/s/GWGWTfCRCsOJ_4Pk-pxpHA">极客时间《Go语言进阶课》上架刚好一个月</a>，受到了各位读者的热烈欢迎和反馈。在这里感谢大家的支持。目前我们已经完成了课程模块一『语法强化篇』的 13 讲，为你系统突破 Go 语言的语法认知瓶颈，打下坚实基础。</p>
<p>现在，我们即将进入模块二『设计先行篇』，这不仅包括 API 设计，更涵盖了项目布局、包设计、并发设计、接口设计、错误处理设计等构建高质量 Go 代码的关键要素。</p>
<p>这门进阶课程，是我多年 Go 实战经验和深度思考的结晶，旨在帮助你突破瓶颈，从“会用 Go”迈向“精通 Go”，真正驾驭 Go 语言，编写出更优雅、更高效、更可靠的生产级代码！</p>
<p>扫描下方二维码，立即开启你的 Go 语言进阶之旅！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<p><strong>感谢阅读！</strong></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/06/14/go-1-25-foresight/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go应用的K8s“最佳拍档”：何时以及如何用好多容器Pod模式</title>
		<link>https://tonybai.com/2025/04/24/multiple-containers-pod-pattern/</link>
		<comments>https://tonybai.com/2025/04/24/multiple-containers-pod-pattern/#comments</comments>
		<pubDate>Thu, 24 Apr 2025 00:19:56 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Adapter]]></category>
		<category><![CDATA[Ambassador]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[envoy]]></category>
		<category><![CDATA[Fluentd]]></category>
		<category><![CDATA[GA]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gRPC]]></category>
		<category><![CDATA[helper]]></category>
		<category><![CDATA[init-container]]></category>
		<category><![CDATA[istio]]></category>
		<category><![CDATA[job]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[linkerd]]></category>
		<category><![CDATA[Otel]]></category>
		<category><![CDATA[REST]]></category>
		<category><![CDATA[restartPolicy]]></category>
		<category><![CDATA[sidecar]]></category>
		<category><![CDATA[云原生]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[指标]]></category>
		<category><![CDATA[数据库]]></category>
		<category><![CDATA[日志]]></category>
		<category><![CDATA[服务网格]]></category>
		<category><![CDATA[模式]]></category>
		<category><![CDATA[配置]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4610</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/04/24/multiple-containers-pod-pattern 大家好，我是Tony Bai。 将Go应用部署到Kubernetes已经是许多团队的标配。在这个强大的容器编排平台上，除了运行我们的核心Go服务容器，Kubernetes还提供了一种灵活的设计模式——多容器Pod。通过在同一个Pod内运行多个容器，我们可以实现诸如初始化、功能扩展、适配转换等多种辅助功能，其中最知名的就是Sidecar模式。 这些“辅助容器”就像我们Go应用的“最佳拍档”，在某些场景下能发挥奇效。然而，正如 Kubernetes官方文档和社区讨论一直强调的那样，引入额外的容器并非没有成本。每一个额外的容器都会增加复杂度、资源消耗和潜在的运维开销。 因此，关键在于策略性地使用这些模式。我们不应将其视为默认选项，而应是解决特定架构挑战的精密工具。今天，我们就来聊聊Kubernetes中几种合理且常用的多容器Pod模式，探讨何时应该为我们的Go应用引入这些“拍档”，以及如何更好地利用Kubernetes v1.33中已正式稳定（GA）的原生Sidecar支持来实现它们。 图K8s v1.33发布 首先：警惕复杂性！优先考虑更简单的替代方案 在深入探讨具体模式之前，务必牢记一个核心原则：非必要，勿增实体。 对于Go这种拥有强大标准库和丰富生态的语言来说，许多常见的横切关注点（如日志记录、指标收集、配置加载、基本的HTTP客户端逻辑等）往往可以通过引入高质量的Go库在应用内部更轻量、更高效地解决。 只有当以下情况出现时，才应认真考虑引入多容器模式： 需要扩展或修改无法触碰源代码的应用（如第三方应用或遗留系统）。 需要将与语言无关的通用功能（如网络代理、安全策略）从主应用中解耦出来。 需要独立于主应用进行更新或扩展的辅助功能。 特定的初始化或适配需求无法在应用内部优雅处理。 切忌为了“看起来很酷”或“遵循某种时髦架构”而盲目添加容器。 下面我们看看常见的一些多容器模式以及对应的应用场景。 四种推荐的多容器模式及其Go应用场景 Kubernetes生态中已经沉淀出了几种非常实用且目标明确的多容器模式，我们逐一来看一下。 Init Container (初始化容器) Init Container是K8s最早支持的一种“sidecar”(那时候还不这么叫)，它一般用在主应用容器启动之前，执行一次性的关键设置任务。它会运行至完成然后终止。 它常用于以下场景： 运行数据库Schema迁移。 预加载配置或密钥。 检查依赖服务就绪。 准备共享数据卷。 下面是官方的一个init containers的示例： apiVersion: v1 kind: Pod metadata: name: myapp-pod labels: app.kubernetes.io/name: MyApp spec: containers: - name: myapp-container image: busybox:1.28 command: ['sh', [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/multiple-containers-pod-pattern-1.jpg" alt="" /></p>
<p><a href="https://tonybai.com/2025/04/24/multiple-containers-pod-pattern">本文永久链接</a> &#8211; https://tonybai.com/2025/04/24/multiple-containers-pod-pattern</p>
<p>大家好，我是Tony Bai。</p>
<p>将Go应用部署到Kubernetes已经是许多团队的标配。在这个强大的容器编排平台上，除了运行我们的核心Go服务容器，Kubernetes还提供了一种灵活的设计模式——<strong>多容器Pod</strong>。通过在同一个Pod内运行多个容器，我们可以实现诸如初始化、功能扩展、适配转换等多种辅助功能，其中最知名的就是<strong>Sidecar</strong>模式。</p>
<p>这些“辅助容器”就像我们Go应用的“最佳拍档”，在某些场景下能发挥奇效。然而，正如 Kubernetes官方文档和社区讨论一直强调的那样，<strong>引入额外的容器并非没有成本</strong>。每一个额外的容器都会增加复杂度、资源消耗和潜在的运维开销。</p>
<p>因此，关键在于策略性地使用这些模式。我们不应将其视为默认选项，而应是解决特定架构挑战的精密工具。今天，我们就来聊聊Kubernetes中几种合理且常用的多容器Pod模式，探讨何时应该为我们的Go应用引入这些“拍档”，以及如何更好地利用Kubernetes v1.33中已正式稳定（GA）的原生Sidecar支持来实现它们。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/multiple-containers-pod-pattern-2.jpg" alt="" /><br />
<center>图K8s v1.33发布</center></p>
<h2>首先：警惕复杂性！优先考虑更简单的替代方案</h2>
<p>在深入探讨具体模式之前，务必牢记一个核心原则：<strong>非必要，勿增实体</strong>。</p>
<p>对于Go这种拥有强大标准库和丰富生态的语言来说，许多常见的横切关注点（如日志记录、指标收集、配置加载、基本的HTTP客户端逻辑等）往往可以通过引入高质量的Go库在应用内部更轻量、更高效地解决。</p>
<p>只有当以下情况出现时，才应认真考虑引入多容器模式：</p>
<ul>
<li>需要扩展或修改无法触碰源代码的应用（如第三方应用或遗留系统）。</li>
<li>需要将与语言无关的通用功能（如网络代理、安全策略）从主应用中解耦出来。</li>
<li>需要独立于主应用进行更新或扩展的辅助功能。</li>
<li>特定的初始化或适配需求无法在应用内部优雅处理。</li>
</ul>
<p>切忌为了“看起来很酷”或“遵循某种时髦架构”而盲目添加容器。</p>
<p>下面我们看看常见的一些多容器模式以及对应的应用场景。</p>
<h2>四种推荐的多容器模式及其Go应用场景</h2>
<p>Kubernetes生态中已经沉淀出了几种非常实用且目标明确的多容器模式，我们逐一来看一下。</p>
<h3>Init Container (初始化容器)</h3>
<p>Init Container是K8s最早支持的一种“sidecar”(那时候还不这么叫)，它一般用在主应用容器启动之前，执行一次性的关键设置任务。它会运行至完成然后终止。</p>
<p>它常用于以下场景：</p>
<ul>
<li>运行数据库Schema迁移。</li>
<li>预加载配置或密钥。</li>
<li>检查依赖服务就绪。</li>
<li>准备共享数据卷。</li>
</ul>
<p>下面是官方的一个init containers的示例：</p>
<pre><code>apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app.kubernetes.io/name: MyApp
spec:
  containers:
  - name: myapp-container
    image: busybox:1.28
    command: ['sh', '-c', 'echo The app is running! &amp;&amp; sleep 3600']
  initContainers:
  - name: init-myservice
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]
  - name: init-mydb
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mydb; sleep 2; done"]
</code></pre>
<p>此示例定义了一个包含两个init容器的简单Pod。第一个init容器(init-myservice)等待myservice运行，第二个init容器(init-mydb)等待mydb运行。两个init容器完成后，Pod将从其spec部分运行app容器(myapp-container)。</p>
<h3>Ambassador (大使容器)</h3>
<p>Ambassador Container主要是用于扮演主应用容器的“网络大使”，简化其与外部服务的交互，它常用在下面一些场景里：</p>
<ul>
<li>服务发现与负载均衡代理。</li>
<li>请求重试与熔断。</li>
<li>身份验证与授权代理。</li>
<li>mTLS 加密通信。</li>
</ul>
<p>Ambassador通常作为Pod内的一个长期运行的容器。如果需要确保它在主应用之后停止（例如处理完最后的请求转发），Kubernetes原生Sidecar是实现Ambassador容器的理想选择。</p>
<h3>Configuration Helper (配置助手)</h3>
<p>配置助手也是一种最常使用的辅助容器模式，它主要用于动态地为正在运行的主应用提供或更新配置，比如监控ConfigMap/Secret变化并热加载、从配置中心拉取配置等。</p>
<p>它通常也是一个长期运行的容器。由于可能需要在主应用启动前提供初始配置，并在主应用停止后同步最后状态，使用原生Sidecar提供的精确生命周期管理非常有价值，可以使用Sidecar实现这种模式的容器。</p>
<h3>Adapter (适配器容器)</h3>
<p>Adapter容器负责在主应用和外部世界之间进行数据格式、协议或API的转换，常用于下面一些场景：</p>
<ul>
<li>统一监控指标格式。</li>
<li>协议转换（如 gRPC 转 REST）。</li>
<li>标准化日志输出。</li>
<li>兼容遗留系统接口。</li>
</ul>
<p>我们可以根据是否需要精确的生命周期协调来选择普通容器或原生Sidecar来实现这类长期运行的适配器容器。</p>
<p>可见，K8s原生的Sidecar是实现上述四种辅助容器的可靠实现，下面来简单介绍一下K8s原生Sidecar。</p>
<h2>K8s原生Sidecar：可靠实现辅助容器的关键</h2>
<p>现在，我们重点关注Kubernetes v1.33中正式稳定（GA）的原生Sidecar 功能。</p>
<p><strong>它是如何实现的呢？</strong></p>
<p>官方推荐的方式是：在Pod的spec.initContainers数组中定义你的Sidecar容器，并显式地将其restartPolicy设置为Always。下面是一个示例：</p>
<pre><code>spec:
  initContainers:
    - name: my-sidecar # 例如日志收集或网络代理
      image: my-sidecar-image:latest
      restartPolicy: Always # &lt;--- 关键：标记为原生Sidecar
      # ... 其他配置 ...
  containers:
    - name: my-go-app
      image: my-golang-app:latest
      # ...
</code></pre>
<p>虽然将长期运行的容器放在initContainers里初看起来可能有些“反直觉”，但这正是Kubernetes团队为了复用Init Container已有的启动顺序保证，并赋予其特殊生命周期管理能力而精心设计的稳定机制。</p>
<p><strong>原生Sidecar具有如下的核心优势：</strong></p>
<ul>
<li>可靠的启动行为： 所有非Sidecar的 Init Containers (restartPolicy 不是 Always) 会按顺序执行且必须成功完成。随后，主应用容器 (spec.containers) 和所有原生 Sidecar 并发启动。</li>
<li>优雅的关闭顺序保证：这是最大的改进！当 Pod 终止时，主应用容器先收到SIGTERM 并等待其完全停止（或超时），然后Sidecar容器才会收到 SIGTERM 开始关闭。</li>
<li>与Job 的良好协作： 对于设置了 restartPolicy: OnFailure或Never的Job，原生Sidecar不会因为自身持续运行而阻止Job的成功完成。</li>
</ul>
<p><strong>这对我们的Go应用意味着什么？</strong></p>
<p>当你的Go应用确实需要一个长期运行的辅助容器，并且<strong>需要精确的生命周期协调</strong>时，原生Sidecar提供了实实在在的好处：</p>
<ul>
<li>服务网格代理 (Ambassador 变种): Envoy, Linkerd proxy 等可以确保在 Go 应用处理完最后请求后才关闭，极大提升可靠性。</li>
<li>日志/监控收集 (Adapter/Helper 变种): Fluentd, Vector, OTel Collector 等可以确保捕获到 Go 应用停止前的最后状态信息。</li>
<li>需要与主应用生命周期紧密配合的其他辅助服务: 任何需要在主应用运行期间持续提供服务，并在主应用结束后才停止的场景。</li>
</ul>
<p>因此，原生Sidecar不是一个全新的模式，而是当我们需要实现上述这些需要精确生命周期管理的Sidecar模式时，Kubernetes v1.33 提供的稳定、可靠且官方推荐的实现方式。</p>
<h2>小结</h2>
<p>Kubernetes的多容器Pod模式为我们提供了强大的工具箱，但也伴随着额外的复杂性。对于Go开发者而言：</p>
<ul>
<li><strong>始终将简单性放在首位：</strong> 优先考虑使用 Go 语言自身的库和能力来解决问题。</li>
<li><strong>审慎评估必要性：</strong> 只有当明确的应用场景（如 Init, Ambassador, Config Helper, Adapter）带来的好处大于其引入的复杂度和资源开销时，才考虑使用多容器模式。</li>
<li><strong>理解模式目的：</strong> 清晰地知道你引入的每个辅助容器是为了解决什么特定问题。</li>
<li><strong>拥抱原生 Sidecar (GA):</strong> 当你确定需要一个长期运行且需要可靠生命周期管理的辅助容器时，<strong>利用 Kubernetes v1.33 及以后版本中稳定提供的原生 Sidecar 支持</strong>，是提升部署健壮性的最佳实践。</li>
</ul>
<p>多容器 Pod 是 Kubernetes 生态中的“精密武器”，理解何时拔剑、如何出鞘，并善用平台提供的稳定特性，才能真正发挥其威力，为我们的 Go 应用保驾护航。</p>
<p><strong>你通常在什么场景下为你的 Go 应用添加辅助容器？你对 K8s 原生 Sidecar 功能的稳定有何看法？欢迎在评论区分享你的实践经验和见解！</strong> 如果觉得这篇文章对你有启发，也请不吝点个【赞】和【在看】！</p>
<h2>参考资料</h2>
<ul>
<li><a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/">Init Containers</a> &#8211; https://kubernetes.io/docs/concepts/workloads/pods/init-containers/</li>
<li><a href="https://kubernetes.io/docs/tutorials/configuration/pod-sidecar-containers/">Pod Sidecar Containers</a> &#8211; https://kubernetes.io/docs/tutorials/configuration/pod-sidecar-containers/</li>
<li><a href="https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers">Sidecar Containers</a> &#8211; https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/</li>
<li><a href="https://kubernetes.io/blog/2025/04/23/kubernetes-v1-33-release">Kubernetes v1.33: Octarine</a> &#8211; https://kubernetes.io/blog/2025/04/23/kubernetes-v1-33-release/</li>
<li><a href="https://github.com/kubernetes/enhancements/issues/753">Sidecar Containers</a> &#8211; https://github.com/kubernetes/enhancements/issues/753</li>
</ul>
<hr />
<p><strong>拓展阅读与实践：抓住 K8s 学习与星球优惠的最后机会！</strong></p>
<p>聊完K8s的多容器Pod模式，想不想更系统地掌握K8s核心原理与实践？</p>
<p>我正打算将多年前深受好评的<strong>慕课网Kubernetes实战课</strong>(https://coding.imooc.com/class/284.html)内容（覆盖集群探索、网络、安全、存储、诊断、Operator等核心知识点）进行精选和更新，并逐步放入我的知识星球<strong>「Go &amp; AI 精进营」</strong> 的<strong>【Kubernetes进阶】</strong> 专栏。这对于理解K8s底层、打好云原生基础价值依旧。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/multiple-containers-pod-pattern-3.png" alt="" /><br />
<center>当初的课程核心内容(后会有调整)</center></p>
<p><strong>特别提醒：</strong> 「Go &amp; AI 精进营」将于<strong>5月1日起涨价至 388 元/年</strong>！现在是<strong>涨价前的最后一周</strong>，以当前价格加入，即可锁定未来一年的高质量Go 进阶、AI 应用实战以及这个即将更新的 K8s 实战专栏！</p>
<p>如果你想深入K8s原理，并抓住星球涨价前的<strong>最后优惠窗口</strong>，现在就加入我们吧！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-and-ai-tribe-zsxq-small-card.jpg" alt="img{512x368}" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/04/24/multiple-containers-pod-pattern/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>自定义Hash终迎标准化？Go提案maphash.Hasher接口设计解读</title>
		<link>https://tonybai.com/2025/04/17/standardize-the-hash-function/</link>
		<comments>https://tonybai.com/2025/04/17/standardize-the-hash-function/#comments</comments>
		<pubDate>Wed, 16 Apr 2025 23:15:31 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[bucket]]></category>
		<category><![CDATA[comparable]]></category>
		<category><![CDATA[Compiler]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[CPU]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.18]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[hash]]></category>
		<category><![CDATA[Hasher]]></category>
		<category><![CDATA[HashFloodingDoS]]></category>
		<category><![CDATA[Hash洪水攻击]]></category>
		<category><![CDATA[map]]></category>
		<category><![CDATA[maphash]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[seed]]></category>
		<category><![CDATA[swisstable]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[哈希表]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[性能]]></category>
		<category><![CDATA[接口]]></category>
		<category><![CDATA[标准库]]></category>
		<category><![CDATA[桶]]></category>
		<category><![CDATA[泛型]]></category>
		<category><![CDATA[泛型容器]]></category>
		<category><![CDATA[泛型类型]]></category>
		<category><![CDATA[种子]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[集合]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4579</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/04/17/standardize-the-hash-function 大家好，我是Tony Bai。 随着Go泛型的落地和社区对高性能自定义容器需求的增长，如何为用户自定义类型提供一套标准、安全且高效的Hash计算与相等性判断机制，成为了Go核心团队面临的重要议题。近日，经过Go核心开发者多轮深入探讨，编号为#70471 的提案”hash: standardize the hash function”最终收敛并被接受，为Go生态引入了全新的maphash.Hasher[T] 接口，旨在统一自定义类型的Hash实现方式。 这个旨在统一自定义类型Hash实现的提案令人期待，但我们首先需要理解，究竟是什么背景和痛点，促使Go社区必须着手解决自定义 Hash 的标准化问题呢？ 1. 背景：为何需要标准化的Hash接口？ 在Go 1.18泛型发布之前，为自定义类型（尤其是非comparable类型）实现Hash往往需要开发者自行设计方案，缺乏统一标准。随着泛型的普及，开发者可以创建自定义的哈希表、集合等泛型数据结构，此时，一个标准的、能与这些泛型容器解耦的Hash和相等性判断机制变得至关重要。 更关键的是安全性。一个简单的func(T) uint64类型的Hash函数看似直观和易实现，但极易受到Hash 洪水攻击 (Hash Flooding DoS) 的威胁。 什么是Hash洪水攻击呢？ 简单来说，哈希表通过Hash函数将键（Key）分散到不同的“桶”（Bucket）中，理想情况下可以实现快速的O(1)平均查找、插入和删除。但如果Hash函数的设计存在缺陷或过于简单（例如，不使用随机种子），攻击者就可以精心构造大量具有相同Hash值的不同键。当这些键被插入到同一个哈希表中时，它们会集中在少数几个甚至一个“桶”里，导致这个桶形成一个长链表。此时，对这个桶的操作（如查找或插入）性能会从O(1)急剧退化到O(n)，消耗大量CPU时间。攻击者通过发送大量这样的冲突键，就能耗尽服务器资源，导致服务缓慢甚至完全不可用。 Go内建的map类型通过为每个map实例使用内部随机化的 Seed（种子）来初始化其Hash函数，使得攻击者无法预测哪些键会产生冲突，从而有效防御了此类攻击。hash/maphash包也提供了基于maphash.Seed的安全Hash计算方式。因此，任何标准化的自定义Hash接口都必须将基于Seed的随机化纳入核心设计，以避免开发者在不知情的情况下引入安全漏洞。 明确了标准化Hash接口的必要性，尤其是出于安全性的考量之后，Go核心团队又是如何一步步探索、权衡，最终从多种可能性中确定接口的设计方向的呢？其间的思考过程同样值得我们关注。 2. 设计演进：从简单函数到maphash.Hasher 围绕如何设计这个标准接口，Go 团队进行了广泛的讨论（相关issue: #69420, #69559, #70471）。 最初，开发者们提出的 func(T) uint64 由于无法有效防御 Hash 洪水攻击而被迅速否定。 随后，大家一致认为需要引入Seed，讨论的焦点则转向Seed的传递和使用方式：是作为函数参数（func(Seed, T) uint64）还是封装在接口或结构体中。对此，Ian Lance Taylor提出了Hasher[T]接口的雏形，包含Hash(T) uint64和Equal(T, T) bool方法，并通过工厂函数（如 MakeSeededHasher）来管理 Seed。 然而，这引发了关于Seed作用域（per-process [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/standardize-the-hash-function-1.jpg" alt="" /></p>
<p><a href="https://tonybai.com/2025/04/17/standardize-the-hash-function">本文永久链接</a> &#8211; https://tonybai.com/2025/04/17/standardize-the-hash-function</p>
<p>大家好，我是Tony Bai。</p>
<p>随着Go泛型的落地和社区对高性能自定义容器需求的增长，如何为用户自定义类型提供一套标准、安全且高效的Hash计算与相等性判断机制，成为了Go核心团队面临的重要议题。近日，经过Go核心开发者多轮深入探讨，编号为<a href="https://github.com/golang/go/issues/70471">#70471 的提案”hash: standardize the hash function”</a>最终收敛并被接受，为Go生态引入了全新的maphash.Hasher[T] 接口，旨在统一自定义类型的Hash实现方式。</p>
<p>这个旨在统一自定义类型Hash实现的提案令人期待，但我们首先需要理解，究竟是什么背景和痛点，促使Go社区必须着手解决自定义 Hash 的标准化问题呢？</p>
<h2>1. 背景：为何需要标准化的Hash接口？</h2>
<p>在<a href="https://tonybai.com/2022/04/20/some-changes-in-go-1-18">Go 1.18泛型发布</a>之前，为自定义类型（尤其是非comparable类型）实现Hash往往需要开发者自行设计方案，缺乏统一标准。随着泛型的普及，开发者可以创建自定义的哈希表、集合等泛型数据结构，此时，一个标准的、能与这些泛型容器解耦的Hash和相等性判断机制变得至关重要。</p>
<p>更关键的是<strong>安全性</strong>。一个简单的func(T) uint64类型的Hash函数看似直观和易实现，但极易受到<strong>Hash 洪水攻击 (Hash Flooding DoS)</strong> 的威胁。</p>
<p><strong>什么是Hash洪水攻击呢？</strong> 简单来说，哈希表通过Hash函数将键（Key）分散到不同的“桶”（Bucket）中，理想情况下可以实现快速的O(1)平均查找、插入和删除。但如果Hash函数的设计存在缺陷或过于简单（例如，不使用随机种子），攻击者就可以精心构造大量<strong>具有相同Hash值的不同键</strong>。当这些键被插入到同一个哈希表中时，它们会集中在少数几个甚至一个“桶”里，导致这个桶形成一个长链表。此时，对这个桶的操作（如查找或插入）性能会从O(1)急剧退化到O(n)，消耗大量CPU时间。攻击者通过发送大量这样的冲突键，就能耗尽服务器资源，导致服务缓慢甚至完全不可用。</p>
<p><a href="https://tonybai.com/2024/11/14/go-map-use-swiss-table">Go内建的map类型</a>通过为每个map实例使用内部随机化的 Seed（种子）来初始化其Hash函数，使得攻击者无法预测哪些键会产生冲突，从而有效防御了此类攻击。hash/maphash包也提供了基于maphash.Seed的安全Hash计算方式。因此，任何标准化的自定义Hash接口都必须将<strong>基于Seed的随机化</strong>纳入核心设计，以避免开发者在不知情的情况下引入安全漏洞。</p>
<p>明确了标准化Hash接口的必要性，尤其是出于安全性的考量之后，Go核心团队又是如何一步步探索、权衡，最终从多种可能性中确定接口的设计方向的呢？其间的思考过程同样值得我们关注。</p>
<h2>2. 设计演进：从简单函数到maphash.Hasher</h2>
<p>围绕如何设计这个标准接口，Go 团队进行了广泛的讨论（相关issue: <a href="https://github.com/golang/go/issues/69420">#69420</a>, <a href="https://github.com/golang/go/issues/69559">#69559</a>, <a href="https://github.com/golang/go/issues/70471">#70471</a>）。</p>
<p>最初，开发者们提出的 func(T) uint64 由于无法有效防御 Hash 洪水攻击而被迅速否定。</p>
<p>随后，大家一致认为需要引入Seed，讨论的焦点则转向Seed的传递和使用方式：是作为函数参数（func(Seed, T) uint64）还是封装在接口或结构体中。对此，Ian Lance Taylor提出了Hasher[T]接口的雏形，包含Hash(T) uint64和Equal(T, T) bool方法，并通过工厂函数（如 MakeSeededHasher）来管理 Seed。 然而，这引发了关于Seed作用域（per-process vs per-table）和状态管理（stateless vs stateful）的进一步讨论。</p>
<p>Austin Clements 提出了多种接口变体，并深入分析了不同设计的利弊，包括API 简洁性、性能（间接调用 vs 直接调用）、类型推断的限制以及易用性（是否容易误用导致不安全）。</p>
<p>最终，为了更好地支持递归Hash（例如，一个结构体的Hash需要依赖其成员的Hash），讨论聚焦于将*maphash.Hash对象直接传递给Hash方法。maphash.Hash内部封装了Seed和Hash状态，能够方便地在递归调用中传递，简化了实现过程。</p>
<p>经历了对不同方案的深入探讨和关键决策（例如引入 *maphash.Hash），最终被接受并写入提案的maphash.Hasher[T] 接口究竟长什么样？它的核心设计理念又是什么呢？接下来，让我们来详细解读。</p>
<h2>3. 最终方案：maphash.Hasher[T]接口</h2>
<p>经过审慎评估和实际代码验证（见<a href="https://go.dev/cl/657296">CL 657296</a>和<a href="https://go.dev/cl/657297">CL 657297</a>），Go团队最终接受了以下maphash.Hasher[T]接口定义：</p>
<pre><code>package maphash

// A Hasher is a type that implements hashing and equality for type T.
//
// A Hasher must be stateless. Hence, typically, a Hasher will be an empty struct.
type Hasher[T any] interface {
    // Hash updates hash to reflect the contents of value.
    //
    // If two values are [Equal], they must also Hash the same.
    // Specifically, if Equal(a, b) is true, then Hash(h, a) and Hash(h, b)
    // must write identical streams to h.
    Hash(hash *Hash, value T) // 注意：这里的 hash 是 *maphash.Hash 类型
    Equal(a, b T) bool
}
</code></pre>
<p>该接口的核心设计理念可以归纳为如下几点：</p>
<ul>
<li><strong>Stateless Hasher:</strong> Hasher[T] 的实现本身应该是无状态的（通常是空结构体），所有状态（包括 Seed）都由传入的 *maphash.Hash 对象管理。</li>
<li><strong>安全保障:</strong> 通过强制使用maphash.Hash，确保了 Hash 计算过程与 Go 内建的、经过安全加固的Hash算法（如 runtime.memhash）保持一致，并天然集成了Seed 机制。</li>
<li><strong>递归友好:</strong> 在计算复杂类型的 Hash 时，可以直接将 *maphash.Hash 对象传递给成员类型的 Hasher，使得递归实现简洁高效。</li>
<li><strong>关注点分离:</strong> 将 Hash 计算 (Hash) 和相等性判断 (Equal) 分离，并与类型 T 本身解耦，提供了更大的灵活性（类似于 sort.Interface 的设计哲学）。</li>
</ul>
<p>下面是一个maphash.Hasher的使用示例：</p>
<pre><code>package main

import (
    "hash/maphash"
    "slices"
)

// 自定义类型
type Strings []string

// 为 Strings 类型实现 Hasher
type StringsHasher struct{} // 无状态

func (StringsHasher) Hash(mh *maphash.Hash, val Strings) {
    // 使用 maphash.Hash 的方法写入数据
    maphash.WriteComparable(mh, len(val)) // 先写入长度
    for _, s := range val {
        mh.WriteString(s)
    }
}

func (StringsHasher) Equal(a, b Strings) bool {
    return slices.Equal(a, b)
}

// 另一个包含自定义类型的结构体
type Thing struct {
    ss Strings
    i  int
}

// 为 Thing 类型实现 Hasher (递归调用 StringsHasher)
type ThingHasher struct{} // 无状态

func (ThingHasher) Hash(mh *maphash.Hash, val Thing) {
    // 调用成员类型的 Hasher
    StringsHasher{}.Hash(mh, val.ss)
    // 为基础类型写入 Hash
    maphash.WriteComparable(mh, val.i)
}

func (ThingHasher) Equal(a, b Thing) bool {
    // 优先比较简单字段
    if a.i != b.i {
        return false
    }
    // 调用成员类型的 Equal
    return StringsHasher{}.Equal(a.ss, b.ss)
}

// 假设有一个自定义的泛型 Set
type Set[T any, H Hasher[T]] struct {
    hash H // Hasher 实例 (通常是零值)
    seed maphash.Seed
    // ... 其他字段，如存储数据的 bucket ...
}

// Set 的 Get 方法示例
func (s *Set[T, H]) Has(val T) bool {
    var mh maphash.Hash
    mh.SetSeed(s.seed) // 使用 Set 实例的 Seed 初始化 maphash.Hash

    // 使用 Hasher 计算 Hash
    s.hash.Hash(&amp;mh, val)
    hashValue := mh.Sum64()

    // ... 在 bucket 中根据 hashValue 查找 ...
    // ... 找到潜在匹配项 potentialMatch 后，使用 Hasher 的 Equal 判断 ...
    // if s.hash.Equal(val, potentialMatch) {
    //     return true
    // }
    // ...

    // 简化示例，仅展示调用
    _ = hashValue // 避免编译错误

    return false // 假设未找到
}

func main() {
    // 创建 Set 实例时，需要提供具体的类型和对应的 Hasher 类型
    var s Set[Thing, ThingHasher]
    s.seed = maphash.MakeSeed() // 初始化 Seed

    // ... 使用 s ...
    found := s.Has(Thing{ss: Strings{"a", "b"}, i: 1})
    println(found)
}
</code></pre>
<p>这个精心设计的 maphash.Hasher[T] 接口及其使用范例展示了其潜力和优雅之处。然而，任何技术方案在落地过程中都难免遇到挑战，这个新接口也不例外。它目前还面临哪些已知的问题，未来又有哪些值得期待的发展方向呢？</p>
<h2>4. 挑战与展望</h2>
<p>尽管 maphash.Hasher 接口设计优雅且解决了核心问题，但也存在一些已知挑战：</p>
<ul>
<li><strong>编译器优化:</strong> 当前 Go 编译器（截至讨论时）在处理接口方法调用时，可能会导致传入的 *maphash.Hash 对象逃逸到堆上，影响性能。这是 Go 泛型和编译器优化（<a href="https://github.com/golang/go/issues/48849">#48849</a>）需要持续改进的地方，但核心团队认为不应因此牺牲接口设计的合理性。</li>
<li><strong>易用性:</strong> maphash.Hash 目前主要提供 Write, WriteString, WriteByte 以及泛型的 WriteComparable。对于其他基础类型（如各种宽度的整数、浮点数），可能需要更多便捷的 WriteXxx 方法来提升开发体验。</li>
<li><strong>生态整合:</strong> 未来 Go 标准库或扩展库中的泛型容器（如可能出现的 container/set 或 container/map 的变体）有望基于此接口构建，从而允许用户无缝接入自定义类型的 Hash 支持。</li>
</ul>
<p>综合来看，尽管存在一些挑战需要克服，但maphash.Hasher[T]接口的提出无疑是Go泛型生态发展中的一个重要里程碑。现在，让我们对它的意义和影响做一个简要的总结。</p>
<h2>5. 小结</h2>
<p>maphash.Hasher[T]接口的接受是Go在泛型时代标准化核心机制的重要一步。它不仅为开发者提供了一种统一、安全的方式来为自定义类型实现 Hash 和相等性判断，也为 Go 生态中高性能泛型容器的发展奠定了坚实的基础。虽然还存在一些编译器优化和 API 便利性方面的挑战，但其核心设计的合理性和前瞻性预示着 Go 在类型系统和泛型支持上的持续进步。我们期待看到这个接口在未来Go版本中的落地，以及它为Go开发者带来的便利。</p>
<p><strong>更多信息:</strong></p>
<ul>
<li><strong>GitHub Issue:</strong> <a href="https://github.com/golang/go/issues/70471">https://github.com/golang/go/issues/70471</a></li>
<li><strong>相关 CL (maphash):</strong> <a href="https://go.dev/cl/657296">https://go.dev/cl/657296</a></li>
<li><strong>相关 CL (go/types):</strong> <a href="https://go.dev/cl/657297">https://go.dev/cl/657297</a> (展示了该接口在 go/types 包中的应用)</li>
</ul>
<p>对于这个备受关注的 maphash.Hasher 接口提案，你怎么看？它是否满足了你对自定义类型 Hash 标准化的期待？或者你认为还有哪些挑战或改进空间？</p>
<p>非常期待在评论区看到你的真知灼见！</p>
<hr />
<p><strong>原「Gopher部落」已重装升级为「Go &amp; AI 精进营」知识星球，快来加入星球，开启你的技术跃迁之旅吧！</strong></p>
<p>我们致力于打造一个高品质的 <strong>Go 语言深度学习</strong> 与 <strong>AI 应用探索</strong> 平台。在这里，你将获得：</p>
<ul>
<li><strong>体系化 Go 核心进阶内容:</strong> 深入「Go原理课」、「Go进阶课」、「Go避坑课」等独家深度专栏，夯实你的 Go 内功。</li>
<li><strong>前沿 Go+AI 实战赋能:</strong> 紧跟时代步伐，学习「Go+AI应用实战」、「Agent开发实战课」，掌握 AI 时代新技能。</li>
<li><strong>星主 Tony Bai 亲自答疑:</strong> 遇到难题？星主第一时间为你深度解析，扫清学习障碍。</li>
<li><strong>高活跃 Gopher 交流圈:</strong> 与众多优秀 Gopher 分享心得、讨论技术，碰撞思想火花。</li>
<li><strong>独家资源与内容首发:</strong> 技术文章、课程更新、精选资源，第一时间触达。</li>
</ul>
<p>衷心希望「Go &amp; AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚，享受技术精进的快乐！欢迎你的加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-and-ai-tribe-zsxq-small-card.jpg" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /></p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格6$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻) &#8211; https://gopherdaily.tonybai.com</p>
<p>我的联系方式：</p>
<ul>
<li>微博(暂不可用)：https://weibo.com/bigwhite20xx</li>
<li>微博2：https://weibo.com/u/6484441286</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>Gopher Daily归档 &#8211; https://github.com/bigwhite/gopherdaily</li>
<li>Gopher Daily Feed订阅 &#8211; https://gopherdaily.tonybai.com/feed</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/04/17/standardize-the-hash-function/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>都2024年了，当初那个“Go，互联网时代的C语言”的预言成真了吗？</title>
		<link>https://tonybai.com/2024/08/17/go-the-c-language-of-the-internet-era-come-true/</link>
		<comments>https://tonybai.com/2024/08/17/go-the-c-language-of-the-internet-era-come-true/#comments</comments>
		<pubDate>Fri, 16 Aug 2024 22:17:02 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[caddy]]></category>
		<category><![CDATA[calico]]></category>
		<category><![CDATA[cilium]]></category>
		<category><![CDATA[cloudnative]]></category>
		<category><![CDATA[cockroachdb]]></category>
		<category><![CDATA[consul]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[containerd]]></category>
		<category><![CDATA[CoreDNS]]></category>
		<category><![CDATA[cortex]]></category>
		<category><![CDATA[dagger]]></category>
		<category><![CDATA[Dgraph]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[dragonfly]]></category>
		<category><![CDATA[etcd]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[gohugo]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[goplus]]></category>
		<category><![CDATA[harbor]]></category>
		<category><![CDATA[InfluxDB]]></category>
		<category><![CDATA[istio]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[juicefs]]></category>
		<category><![CDATA[junodb]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[linkerd2]]></category>
		<category><![CDATA[longhorn]]></category>
		<category><![CDATA[mattermost]]></category>
		<category><![CDATA[Milvus]]></category>
		<category><![CDATA[minio]]></category>
		<category><![CDATA[nats]]></category>
		<category><![CDATA[nsq]]></category>
		<category><![CDATA[ollama]]></category>
		<category><![CDATA[opentelemetry]]></category>
		<category><![CDATA[opentofu]]></category>
		<category><![CDATA[Otel]]></category>
		<category><![CDATA[podman]]></category>
		<category><![CDATA[prometheus]]></category>
		<category><![CDATA[rook]]></category>
		<category><![CDATA[terraform]]></category>
		<category><![CDATA[TIOBE]]></category>
		<category><![CDATA[traefik]]></category>
		<category><![CDATA[VictoriaMetrics]]></category>
		<category><![CDATA[vitess]]></category>
		<category><![CDATA[七牛云]]></category>
		<category><![CDATA[中间件]]></category>
		<category><![CDATA[云原生]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[系统编程语言]]></category>
		<category><![CDATA[驱动]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4248</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2024/08/17/go-the-c-language-of-the-internet-era-come-true 本文最初发表于我个人的微信公众号(iamtonybai)，但鉴于图片消息的篇幅受限(&#60;=1000字)，一些内容没能如愿展开，这里在博客上重新发布一下，也顺道丰富一下文章的内容。 2012年，七牛云创始人、goplus语言之父许式伟在一次演讲中给出一个大胆的预言：“Go，互联网时代的C语言”。 十余年过去了，我们不禁要问：当初的那个预言是否已经成真？ 在讨论这个预言之前，我们先来看在同一份演讲稿中，老许给出的另外三个预判： 它们是： Java语言份额继续下滑，最终被C和Go语言超越； C语言将长居编程榜第二的位置，有望在Go取代Java前重获第一的宝座； Go语言最终会取代Java位居编程榜榜首。 编程语言排行榜有很多，我们就以名气最大的TIOBE刚刚发布的2024年8月排行榜为例，看看这些预判是否成真。 很遗憾，一个也没命中。 在这份最新榜单中，C位列第三、Java位列第四，Go位列第九，相对于前两个月的第七还下降了两位。不过不得不说，老许对C语言的预判还是相对准确的。 那这是否意味着老许最初的那个预言也Miss了呢？个人觉得：并没有。因为这要看从哪个角度来审视。 传统观点认为，C语言被视为系统编程语言的杰出代表，因其卓越的底层操作能力和极致性能而广受推崇。它允许开发者直接与硬件交互，提供了高效的资源管理和快速的执行速度。如果从这样的视角去看待那则预言，那显然Go与“互联网时代C语言”这个评价和地位是不相称的。虽然Go最初的定位也是一门系统编程语言。 但如果我们跳出以“低级操作和性能”为中心的比较框架，而是从不同时代软件技术栈的层次与构建来看，Go与C语言的地位又极其的相似。 在互联网时代到来之前，C语言已经是整个软件技术栈的基石：从操作系统内核、设备驱动程序、中间件到应用程序，C语言凭借卓越的性能、无以伦比的生态，在技术栈的各个层次都有着广泛且核心的应用。 当时针指向云原生时代时，Go语言在云原生技术栈的构建中，发挥了与C语言相似的作用： 云原生“操作系统”：Kubernetes； 云原生“驱动程序”：容器运行时（docker、containerd、podman）、网络插件(Calico、cilium、CoreDNS等)、存储插件（Rook、longhorn等）； 云原生“中间件”：数据库(CockroachDB、Vitess、InfluxDB(2.x)、VictoriaMetrics、Dgraph、milvus等)、消息队列(NATS、nsq等)、服务网格(Istio、linkerd2)、API网关/代理(Traefik、emissary等)、镜像仓库/加速器(harbor、Dragonfly)、key-value存储(Etcd、consul、junodb)、安全相关(falco、OPA、vault)、可观测组件(OpenTelemetry、Prometheus、Thanos、Cortex等)、基础设施管理(terraform、dagger)、分布式存储(minio、SeaweedFS、juicefs)、AI大模型运维(ollama)。 应用层：Caddy、gohugo、mattermost等。 我们用一张示意图来横向对比一下： 听我讲到这里，你是不是觉得老许的那个预言好像命中了呢！ 当然，从狭义的角度来看，Go与C还有一些地方是很像的，比如：语法简单、跨平台可移植性好等。并且两者还“沾亲带故”：Unix之父Ken Thompson当年和Dennis Ritchie一起发明了C语言，又和Rob Pike等一起设计了Go语言！ 最后，回顾许式伟2012年的预言，我们不得不惊叹于其洞察力。Go语言确实在很大程度上成为了”互联网时代的C语言”，但不是通过传统的性能优势，而是通过重新构建了云原生技术栈，从这个角度看，Go语言也不失为云原生时代的”系统语言” —— 它不仅能够优雅地处理分布式系统的复杂性，它还使得构建和维护大规模、高可靠性的分布式系统变得更为简单，是云原生时代的思维方式和解决方案的集大成者，某种程度上还可以说定义了云原生时代的软件开发范式。 Gopher部落知识星球在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时，我们也会加强代码质量和最佳实践的分享，包括如何编写简洁、可读、可测试的Go代码。此外，我们还会加强星友之间的交流和互动。欢迎大家踊跃提问，分享心得，讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落，享受coding的快乐! 欢迎大家踊跃加入！ 著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个链接地址：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。 Gopher Daily(Gopher每日新闻) &#8211; https://gopherdaily.tonybai.com 我的联系方式： 微博(暂不可用)：https://weibo.com/bigwhite20xx 微博2：https://weibo.com/u/6484441286 博客：tonybai.com github: https://github.com/bigwhite Gopher Daily归档 &#8211; https://github.com/bigwhite/gopherdaily Gopher Daily Feed订阅 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/go-the-c-language-of-the-internet-era-come-true-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2024/08/17/go-the-c-language-of-the-internet-era-come-true">本文永久链接</a> &#8211; https://tonybai.com/2024/08/17/go-the-c-language-of-the-internet-era-come-true</p>
<p><a href="https://mp.weixin.qq.com/s/GTXSNoPTmJ-mprMKAY8esw">本文最初发表于我个人的微信公众号(iamtonybai)</a>，但鉴于图片消息的篇幅受限(&lt;=1000字)，一些内容没能如愿展开，这里在博客上重新发布一下，也顺道丰富一下文章的内容。</p>
<hr />
<p>2012年，<a href="https://www.qiniu.com/">七牛云</a>创始人、<a href="https://github.com/goplus/gop">goplus语言</a>之父<a href="https://github.com/xushiwei">许式伟</a>在一次演讲中给出一个大胆的预言：“<strong>Go，互联网时代的C语言</strong>”。</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-the-c-language-of-the-internet-era-come-true-2.png" alt="" /></p>
<p>十余年过去了，我们不禁要问：当初的那个预言是否已经成真？</p>
<p>在讨论这个预言之前，我们先来看在同一份演讲稿中，老许给出的另外三个预判：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-the-c-language-of-the-internet-era-come-true-3.png" alt="" /></p>
<p>它们是：</p>
<ul>
<li>Java语言份额继续下滑，最终被C和Go语言超越；</li>
<li>C语言将长居编程榜第二的位置，有望在Go取代Java前重获第一的宝座；</li>
<li>Go语言最终会取代Java位居编程榜榜首。</li>
</ul>
<p>编程语言排行榜有很多，我们就以名气最大的<a href="https://www.tiobe.com/tiobe-index/">TIOBE</a>刚刚发布的2024年8月排行榜为例，看看这些预判是否成真。</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-the-c-language-of-the-internet-era-come-true-4.png" alt="" /></p>
<p>很遗憾，<strong>一个也没命中</strong>。</p>
<p>在这份最新榜单中，C位列第三、Java位列第四，Go位列第九，<a href="https://mp.weixin.qq.com/s?__biz=MzIyNzM0MDk0Mg==&amp;mid=2247497403&amp;idx=1&amp;sn=03bc972e38163e1539da765249d46586&amp;chksm=e860115adf17984cfe47f9680d8c0fb6370987ad45415ff2d38233d05fe6b315210ce6ada385#rd">相对于前两个月的第七</a>还下降了两位。不过不得不说，老许对C语言的预判还是相对准确的。</p>
<p>那这是否意味着老许最初的那个预言也Miss了呢？个人觉得：<strong>并没有</strong>。因为这要看从哪个角度来审视。</p>
<p>传统观点认为，C语言被视为系统编程语言的杰出代表，因其卓越的底层操作能力和极致性能而广受推崇。它允许开发者直接与硬件交互，提供了高效的资源管理和快速的执行速度。如果从这样的视角去看待那则预言，那显然Go与“互联网时代C语言”这个评价和地位是不相称的。虽然<a href="https://go.dev/talks/2012/splash.article">Go最初的定位也是一门系统编程语言</a>。</p>
<p>但如果我们跳出以“低级操作和性能”为中心的比较框架，而是<strong>从不同时代软件技术栈的层次与构建来看，Go与C语言的地位又极其的相似</strong>。</p>
<p>在互联网时代到来之前，C语言已经是整个软件技术栈的基石：从操作系统内核、设备驱动程序、中间件到应用程序，C语言凭借卓越的性能、无以伦比的生态，在技术栈的各个层次都有着广泛且核心的应用。</p>
<p>当时针指向云原生时代时，<strong>Go语言在云原生技术栈的构建中，发挥了与C语言相似的作用</strong>：</p>
<ul>
<li>云原生“操作系统”：<a href="https://mp.weixin.qq.com/s/paOduv0t1CtBCUoUBfJ7rQ">Kubernetes</a>；</li>
<li>云原生“驱动程序”：容器运行时（<a href="https://tonybai.com/tag/docker">docker</a>、<a href="https://github.com/containerd/containerd">containerd</a>、<a href="https://github.com/containers/podman">podman</a>）、网络插件(<a href="https://github.com/projectcalico/calico">Calico</a>、<a href="https://github.com/cilium/cilium">cilium</a>、<a href="https://github.com/coredns/coredns">CoreDNS</a>等)、存储插件（<a href="https://github.com/rook/rook">Rook</a>、<a href="https://github.com/longhorn/longhorn?tab=readme-ov-file">longhorn</a>等）；</li>
<li>云原生“中间件”：数据库(<a href="https://github.com/cockroachdb/cockroach">CockroachDB</a>、<a href="https://github.com/vitessio/vitess">Vitess</a>、<a href="https://github.com/influxdata/influxdb/tree/main-2.x">InfluxDB(2.x)</a>、<a href="https://github.com/VictoriaMetrics/VictoriaMetrics">VictoriaMetrics</a>、<a href="https://github.com/dgraph-io/dgraph">Dgraph</a>、<a href="https://github.com/milvus-io/milvus">milvus</a>等)、消息队列(<a href="https://github.com/nats-io/nats-server">NATS</a>、<a href="https://github.com/nsqio/nsq">nsq</a>等)、服务网格(<a href="https://tonybai.com/2018/01/03/an-intro-of-microservices-governance-by-istio">Istio</a>、<a href="https://github.com/linkerd/linkerd2">linkerd2</a>)、API网关/代理(<a href="https://github.com/traefik/traefik">Traefik</a>、<a href="https://github.com/emissary-ingress/emissary">emissary</a>等)、镜像仓库/加速器(<a href="https://tonybai.com/2017/10/23/the-speech-script-practice-on-deploying-a-ha-harbor-cluster-for-osc-shenyang-2017/">harbor</a>、<a href="https://github.com/dragonflyoss/Dragonfly2">Dragonfly</a>)、key-value存储(<a href="https://github.com/etcd-io/etcd">Etcd</a>、<a href="https://github.com/hashicorp/consul">consul</a>、<a href="https://github.com/paypal/junodb">junodb</a>)、安全相关(<a href="https://github.com/falcosecurity/falco">falco</a>、<a href="https://github.com/open-policy-agent/opa">OPA</a>、<a href="https://github.com/hashicorp/vault">vault</a>)、可观测组件(<a href="https://github.com/open-telemetry/community">OpenTelemetry</a>、<a href="https://github.com/prometheus/prometheus">Prometheus</a>、<a href="https://github.com/thanos-io/thanos">Thanos</a>、<a href="https://github.com/cortexproject/cortex">Cortex</a>等)、基础设施管理(<a href="https://github.com/hashicorp/terraform">terraform</a>、<a href="https://github.com/dagger/dagger">dagger</a>)、分布式存储(<a href="https://github.com/minio/">minio</a>、<a href="https://github.com/seaweedfs/seaweedfs">SeaweedFS</a>、<a href="https://github.com/juicedata/juicefs">juicefs</a>)、AI大模型运维(<a href="https://github.com/ollama/ollama">ollama</a>)。</li>
<li>应用层：<a href="https://github.com/caddyserver/caddy">Caddy</a>、<a href="https://github.com/gohugoio/hugo">gohugo</a>、<a href="https://github.com/mattermost/mattermost">mattermost</a>等。</li>
</ul>
<p>我们用一张示意图来横向对比一下：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-the-c-language-of-the-internet-era-come-true-5.png" alt="" /></p>
<p>听我讲到这里，你是不是觉得老许的那个预言好像命中了呢！</p>
<p>当然，从狭义的角度来看，Go与C还有一些地方是很像的，比如：语法简单、跨平台可移植性好等。并且两者还“沾亲带故”：Unix之父Ken Thompson当年和Dennis Ritchie一起发明了C语言，又和Rob Pike等一起设计了Go语言！</p>
<p>最后，回顾许式伟2012年的预言，我们不得不惊叹于其洞察力。Go语言确实在很大程度上成为了”互联网时代的C语言”，但不是通过传统的性能优势，而是通过<strong>重新构建了云原生技术栈</strong>，从这个角度看，Go语言也不失为云原生时代的”系统语言” —— 它不仅能够优雅地处理分布式系统的复杂性，它还使得构建和维护大规模、高可靠性的分布式系统变得更为简单，是云原生时代的思维方式和解决方案的集大成者，某种程度上还可以说定义了云原生时代的软件开发范式。</p>
<hr />
<p><a href="https://public.zsxq.com/groups/51284458844544">Gopher部落知识星球</a>在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时，我们也会加强代码质量和最佳实践的分享，包括如何编写简洁、可读、可测试的Go代码。此外，我们还会加强星友之间的交流和互动。欢迎大家踊跃提问，分享心得，讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落，享受coding的快乐! 欢迎大家踊跃加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻) &#8211; https://gopherdaily.tonybai.com</p>
<p>我的联系方式：</p>
<ul>
<li>微博(暂不可用)：https://weibo.com/bigwhite20xx</li>
<li>微博2：https://weibo.com/u/6484441286</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>Gopher Daily归档 &#8211; https://github.com/bigwhite/gopherdaily</li>
<li>Gopher Daily Feed订阅 &#8211; https://gopherdaily.tonybai.com/feed</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2024, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2024/08/17/go-the-c-language-of-the-internet-era-come-true/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用Ollama和OpenWebUI在CPU上玩转Meta Llama3-8B</title>
		<link>https://tonybai.com/2024/04/23/playing-with-meta-llama3-8b-on-cpu-using-ollama-and-openwebui/</link>
		<comments>https://tonybai.com/2024/04/23/playing-with-meta-llama3-8b-on-cpu-using-ollama-and-openwebui/#comments</comments>
		<pubDate>Tue, 23 Apr 2024 13:49:11 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[ChatGPT]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[CPU]]></category>
		<category><![CDATA[curl]]></category>
		<category><![CDATA[DBRX]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[falcon]]></category>
		<category><![CDATA[fine-tune]]></category>
		<category><![CDATA[Gemma]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[GPT]]></category>
		<category><![CDATA[GPU]]></category>
		<category><![CDATA[HuggingFace]]></category>
		<category><![CDATA[llama]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[meta]]></category>
		<category><![CDATA[Mistral]]></category>
		<category><![CDATA[ollama]]></category>
		<category><![CDATA[OpenWebUI]]></category>
		<category><![CDATA[Qwen]]></category>
		<category><![CDATA[RAG]]></category>
		<category><![CDATA[systemctl]]></category>
		<category><![CDATA[systemd]]></category>
		<category><![CDATA[vicuna]]></category>
		<category><![CDATA[yi]]></category>
		<category><![CDATA[大模型]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[指令微调]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4143</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2024/04/23/playing-with-meta-llama3-8b-on-cpu-using-ollama-and-openwebui 2024年4月18日，meta开源了Llama 3大模型，虽然只有8B和70B两个版本，但Llama 3表现出来的强大能力还是让AI大模型界为之震撼了一番，本人亲测Llama3-70B版本的推理能力十分接近于OpenAI的GPT-4，何况还有一个400B的超大模型还在路上，据说再过几个月能发布。 Github上人气巨火的本地大模型部署和运行工具项目Ollama也在第一时间宣布了对Llama3的支持： 近期除了学习Rust，还有就在研究如何将LLM应用于产品中。以前走微调的路径行不通，最近的RAG(Retrieval-Augmented Generation)和Agent路径则让我看到一丝曙光。不过实施这两个路径的前提是一个强大的LLM，而开源的meta Llama系列LLM则是不二之选。 在这篇文章中，我就先来体验一下如何基于Ollama安装和运行Meta Llama3-8B大模型，并通过兼容Ollama API的OpenWebUI建立对大模型的Web图形化访问方式。 1. 安装Ollama Ollama是一个由Go实现的、可以在本地丝滑地安装和运行各种开源大模型的工具，支持目前国内外很多主流的开源大模型，比如Llama、Mistral、Gemma、DBRX、Qwen、phi、vicuna、yi、falcon等。其支持的全量模型列表可以在Ollama library查看。 Ollama的安装采用了“curl &#124; sh”，我们可以一键将其下载并安装到本地： $curl -fsSL https://ollama.com/install.sh &#124; sh &#62;&#62;&#62; Downloading ollama... ######################################################################## 100.0% &#62;&#62;&#62; Installing ollama to /usr/local/bin... &#62;&#62;&#62; Creating ollama user... &#62;&#62;&#62; Adding ollama user to video group... &#62;&#62;&#62; Adding current user to ollama group... &#62;&#62;&#62; Creating [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/playing-with-meta-llama3-8b-on-cpu-using-ollama-and-openwebui-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2024/04/23/playing-with-meta-llama3-8b-on-cpu-using-ollama-and-openwebui">本文永久链接</a> &#8211; https://tonybai.com/2024/04/23/playing-with-meta-llama3-8b-on-cpu-using-ollama-and-openwebui</p>
<p>2024年4月18日，<a href="https://ai.meta.com/blog/meta-llama-3/">meta开源了Llama 3大模型</a>，虽然只有<a href="https://huggingface.co/meta-llama/Meta-Llama-3-8B">8B</a>和<a href="https://huggingface.co/meta-llama/Meta-Llama-3-70B">70B</a>两个版本，但Llama 3表现出来的强大能力还是让AI大模型界为之震撼了一番，本人亲测Llama3-70B版本的推理能力十分接近于<a href="https://openai.com/research/gpt-4">OpenAI的GPT-4</a>，何况还有一个400B的超大模型还在路上，据说再过几个月能发布。</p>
<p>Github上人气巨火的本地大模型部署和运行工具项目<a href="https://github.com/ollama/ollama">Ollama</a>也在<a href="https://ollama.com/blog/llama3">第一时间宣布了对Llama3的支持</a>：</p>
<p><img src="https://tonybai.com/wp-content/uploads/playing-with-meta-llama3-8b-on-cpu-using-ollama-and-openwebui-2.png" alt="" /></p>
<p>近期除了<a href="https://tonybai.com/2024/04/22/gopher-rust-first-lesson-all-about-rust/">学习Rust</a>，还有就在研究如何将LLM应用于产品中。以前走微调的路径行不通，最近的RAG(Retrieval-Augmented Generation)和Agent路径则让我看到一丝曙光。不过实施这两个路径的前提是一个强大的LLM，而开源的meta Llama系列LLM则是不二之选。</p>
<p>在这篇文章中，我就先来体验一下如何基于Ollama安装和运行Meta Llama3-8B大模型，并通过兼容Ollama API的<a href="https://github.com/open-webui/open-webui">OpenWebUI</a>建立对大模型的Web图形化访问方式。</p>
<h2>1. 安装Ollama</h2>
<p>Ollama是一个由Go实现的、可以<strong>在本地丝滑地安装和运行各种开源大模型的工具</strong>，支持目前国内外很多主流的开源大模型，比如Llama、<a href="https://mistral.ai/">Mistral</a>、<a href="https://ai.google.dev/gemma">Gemma</a>、<a href="https://www.databricks.com/blog/introducing-dbrx-new-state-art-open-llm">DBRX</a>、<a href="https://github.com/QwenLM/Qwen">Qwen</a>、<a href="https://huggingface.co/microsoft/phi-2">phi</a>、<a href="https://lmsys.org/blog/2023-03-30-vicuna/">vicuna</a>、<a href="https://github.com/01-ai/Yi">yi</a>、<a href="https://huggingface.co/blog/falcon">falcon</a>等。其支持的全量模型列表可以在<a href="https://ollama.com/library">Ollama library</a>查看。</p>
<p>Ollama的安装采用了“curl | sh”，我们可以一键将其下载并安装到本地：</p>
<pre><code>$curl -fsSL https://ollama.com/install.sh | sh
&gt;&gt;&gt; Downloading ollama...
######################################################################## 100.0%
&gt;&gt;&gt; Installing ollama to /usr/local/bin...
&gt;&gt;&gt; Creating ollama user...
&gt;&gt;&gt; Adding ollama user to video group...
&gt;&gt;&gt; Adding current user to ollama group...
&gt;&gt;&gt; Creating ollama systemd service...
&gt;&gt;&gt; Enabling and starting ollama service...
Created symlink from /etc/systemd/system/default.target.wants/ollama.service to /etc/systemd/system/ollama.service.
&gt;&gt;&gt; The Ollama API is now available at 127.0.0.1:11434.
&gt;&gt;&gt; Install complete. Run "ollama" from the command line.
WARNING: No NVIDIA/AMD GPU detected. Ollama will run in CPU-only mode.
</code></pre>
<p>我们看到Ollama下载后启动了一个ollama systemd service，这个服务就是Ollama的核心API服务，它常驻内存。通过systemctl可以确认一下该服务的运行状态：</p>
<pre><code>$systemctl status ollama
● ollama.service - Ollama Service
   Loaded: loaded (/etc/systemd/system/ollama.service; enabled; vendor preset: disabled)
   Active: active (running) since 一 2024-04-22 17:51:18 CST; 11h ago
 Main PID: 9576 (ollama)
    Tasks: 22
   Memory: 463.5M
   CGroup: /system.slice/ollama.service
           └─9576 /usr/local/bin/ollama serve

</code></pre>
<p>另外我对Ollama的systemd unit文件做了一些改动，我修改了一下Environment的值，增加了”OLLAMA_HOST=0.0.0.0&#8243;，这样便于后续在容器中运行的OpenWebUI可以访问到Ollama API服务：</p>
<pre><code># cat /etc/systemd/system/ollama.service
[Unit]
Description=Ollama Service
After=network-online.target

[Service]
ExecStart=/usr/local/bin/ollama serve
User=ollama
Group=ollama
Restart=always
RestartSec=3
Environment="PATH=/root/.cargo/bin:/usr/local/cmake/bin:/usr/local/bin:.:/root/.bin/go1.21.4/bin:/root/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin" "OLLAMA_HOST=0.0.0.0"

[Install]
WantedBy=default.target
</code></pre>
<p>修改后执行下面命令使之生效：</p>
<pre><code>$systemctl daemon-reload
$systemctl restart ollama
</code></pre>
<h2>2. 下载和运行大模型</h2>
<p>Ollama支持一键下载和运行模型。我手里有一个16/32G的云虚机，但没有GPU，因此这里我使用的是<a href="https://ollama.com/library/llama3">Llama3-8B指令微调后的用于chat/diaglogue的模型</a>，我们只需要通过下面命令便可以快速下载并运行该模型(4bit量化的)：</p>
<pre><code>$ollama run llama3
pulling manifest
pulling 00e1317cbf74...   0% ▕                ▏    0 B/4.7 GB
pulling 00e1317cbf74...   7% ▕█               ▏ 331 MB/4.7 GB   34 MB/s    2m3s^C
pulling manifest
pulling manifest
pulling manifest
pulling manifest
pulling 00e1317cbf74...  61% ▕█████████       ▏ 2.8 GB/4.7 GB   21 MB/s   1m23s^C
... ...
</code></pre>
<p>下载和执行成功后，该命令行便会等待你的问题输入，我们随便输入一个关于Go的问题，下面是输出结果：</p>
<pre><code>$ollama run llama3
&gt;&gt;&gt; could you tell me something about golang language?
Go!

Here are some interesting facts and features about the Go programming language:

**What is Go?**

Go, also known as Golang, is a statically typed, compiled, and designed to be concurrent and garbage-collected language. It
was developed by Google in 2009.

**Key Features:**

1. **Concurrency**: Go has built-in concurrency support through goroutines (lightweight threads) and channels (communication
mechanisms). This makes it easy to write concurrent programs.
2. **Garbage Collection**: Go has a automatic garbage collector, which frees developers from worrying about memory
management.
3. **Static Typing**: Go is statically typed, meaning that the type system checks the types of variables at compile time,
preventing type-related errors at runtime.
4. **Simple Syntax**: Go's syntax is designed to be simple and easy to read. It has a minimalistic approach to programming
language design.
... ...
</code></pre>
<p>推理速度大约在5~6个token吧，尚可接受，但这个过程是相当耗CPU：</p>
<p><img src="https://tonybai.com/wp-content/uploads/playing-with-meta-llama3-8b-on-cpu-using-ollama-and-openwebui-3.png" alt="" /></p>
<p>除了通过命令行方式与Ollama API服务交互之外，我们还可以用Ollama的restful API：</p>
<pre><code>$curl http://localhost:11434/api/generate -d '{
&gt;   "model": "llama3",
&gt;   "prompt":"Why is the sky blue?"
&gt; }'
{"model":"llama3","created_at":"2024-04-22T07:02:36.394785618Z","response":"The","done":false}
{"model":"llama3","created_at":"2024-04-22T07:02:36.564938841Z","response":" color","done":false}
{"model":"llama3","created_at":"2024-04-22T07:02:36.745215652Z","response":" of","done":false}
{"model":"llama3","created_at":"2024-04-22T07:02:36.926111842Z","response":" the","done":false}
{"model":"llama3","created_at":"2024-04-22T07:02:37.107460031Z","response":" sky","done":false}
{"model":"llama3","created_at":"2024-04-22T07:02:37.287201658Z","response":" can","done":false}
{"model":"llama3","created_at":"2024-04-22T07:02:37.468517901Z","response":" vary","done":false}
{"model":"llama3","created_at":"2024-04-22T07:02:37.649011829Z","response":" depending","done":false}
{"model":"llama3","created_at":"2024-04-22T07:02:37.789353456Z","response":" on","done":false}
{"model":"llama3","created_at":"2024-04-22T07:02:37.969236546Z","response":" the","done":false}
{"model":"llama3","created_at":"2024-04-22T07:02:38.15172159Z","response":" time","done":false}
{"model":"llama3","created_at":"2024-04-22T07:02:38.333323271Z","response":" of","done":false}
{"model":"llama3","created_at":"2024-04-22T07:02:38.514564929Z","response":" day","done":false}
{"model":"llama3","created_at":"2024-04-22T07:02:38.693824676Z","response":",","done":false}
... ...
</code></pre>
<p>不过我日常使用大模型最为广泛的方式还是通过Web UI进行交互。目前有很多支持Ollama API的Web &amp; Desktop项目，这里我们选取<a href="https://github.com/open-webui/open-webui">Open WebUI</a>，它的前身就是Ollama WebUI。</p>
<h2>3. 安装和使用Open WebUI与大模型交互</h2>
<p>最快体验Open WebUI的方式当然是使用容器安装，不过官方镜像站点ghcr.io/open-webui/open-webui:main下载太慢，我找了一个位于Docker Hub上的个人mirror镜像，下面是在本地安装Open WebUI的命令：</p>
<pre><code>$docker run -d -p 13000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data -e OLLAMA_BASE_URL=http://host.docker.internal:11434  --name open-webui --restart always dyrnq/open-webui:main
</code></pre>
<p>容器启动后，我们在host上访问13000端口即可打开Open WebUI页面：</p>
<p><img src="https://tonybai.com/wp-content/uploads/playing-with-meta-llama3-8b-on-cpu-using-ollama-and-openwebui-4.png" alt="" /></p>
<p>首个注册的用户，将会被Open WebUI认为是admin用户！注册登录后，我们就可以进入首页：</p>
<p><img src="https://tonybai.com/wp-content/uploads/playing-with-meta-llama3-8b-on-cpu-using-ollama-and-openwebui-5.png" alt="" /></p>
<p>选择model后，我们便可以输入问题，并与Ollama部署的Llama3模型对话了：</p>
<p><img src="https://tonybai.com/wp-content/uploads/playing-with-meta-llama3-8b-on-cpu-using-ollama-and-openwebui-6.png" alt="" /></p>
<blockquote>
<p>注：如果Open WebUI运行不正常，可以通过查看openwebui的容器日志来辅助诊断问题。</p>
</blockquote>
<p>Open WebUI的功能还有很多，大家可以自行慢慢挖掘:)。</p>
<h2>4. 小结</h2>
<p>在本文中，我介绍了Meta开源的Llama 3大模型以及Ollama和OpenWebUI的使用。Llama 3是一个强大的AI大模型，实测接近于OpenAI的GPT-4，并且还有一个更强大的400B模型即将发布。Ollama是一个用于本地部署和运行大模型的工具，支持多个国内外开源模型，包括Llama在内。我详细介绍了如何安装和运行Ollama，并使用Ollama下载和运行Llama3-8B模型。展示了通过命令行和REST API与Ollama进行交互，以及模型的推理速度和CPU消耗。此外，我还提到了OpenWebUI，一种兼容Ollama API的Web图形化访问方式。通过Ollama和OpenWebUI，大家可以方便地在CPU上使用Meta Llama3-8B大模型进行推理任务，并获得满意的结果。</p>
<p>后续，我将进一步研究如何将Llama3应用于产品中，并探索RAG（Retrieval-Augmented Generation）和Agent技术的潜力。这两种路径可以为基于Llama3的大模型应用开发带来新的可能性。</p>
<hr />
<p><a href="https://public.zsxq.com/groups/51284458844544">Gopher部落知识星球</a>在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时，我们也会加强代码质量和最佳实践的分享，包括如何编写简洁、可读、可测试的Go代码。此外，我们还会加强星友之间的交流和互动。欢迎大家踊跃提问，分享心得，讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落，享受coding的快乐! 欢迎大家踊跃加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻) &#8211; https://gopherdaily.tonybai.com</p>
<p>我的联系方式：</p>
<ul>
<li>微博(暂不可用)：https://weibo.com/bigwhite20xx</li>
<li>微博2：https://weibo.com/u/6484441286</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>Gopher Daily归档 &#8211; https://github.com/bigwhite/gopherdaily</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2024, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2024/04/23/playing-with-meta-llama3-8b-on-cpu-using-ollama-and-openwebui/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>有效表达软件架构的最小图集</title>
		<link>https://tonybai.com/2023/12/06/a-minimum-set-of-diagrams-for-expressing-software-architecture/</link>
		<comments>https://tonybai.com/2023/12/06/a-minimum-set-of-diagrams-for-expressing-software-architecture/#comments</comments>
		<pubDate>Wed, 06 Dec 2023 13:29:08 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[ADL]]></category>
		<category><![CDATA[API网关]]></category>
		<category><![CDATA[Arc42]]></category>
		<category><![CDATA[Architect]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[C4]]></category>
		<category><![CDATA[C4Model]]></category>
		<category><![CDATA[component]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[Context]]></category>
		<category><![CDATA[diagram]]></category>
		<category><![CDATA[DSL]]></category>
		<category><![CDATA[gRPC]]></category>
		<category><![CDATA[json]]></category>
		<category><![CDATA[mermaid]]></category>
		<category><![CDATA[MVC]]></category>
		<category><![CDATA[PlantUML]]></category>
		<category><![CDATA[RUP]]></category>
		<category><![CDATA[Structurizr]]></category>
		<category><![CDATA[Swagger]]></category>
		<category><![CDATA[tradeoff]]></category>
		<category><![CDATA[UML]]></category>
		<category><![CDATA[View]]></category>
		<category><![CDATA[websocket]]></category>
		<category><![CDATA[代码视图]]></category>
		<category><![CDATA[团队协作]]></category>
		<category><![CDATA[图形化]]></category>
		<category><![CDATA[场景视图]]></category>
		<category><![CDATA[容器视图]]></category>
		<category><![CDATA[序列图]]></category>
		<category><![CDATA[开发视图]]></category>
		<category><![CDATA[形式化]]></category>
		<category><![CDATA[控制流图]]></category>
		<category><![CDATA[数据流图]]></category>
		<category><![CDATA[架构]]></category>
		<category><![CDATA[架构师]]></category>
		<category><![CDATA[架构设计]]></category>
		<category><![CDATA[流程图]]></category>
		<category><![CDATA[消费者]]></category>
		<category><![CDATA[物理视图]]></category>
		<category><![CDATA[生产者]]></category>
		<category><![CDATA[用例图]]></category>
		<category><![CDATA[类图]]></category>
		<category><![CDATA[组件视图]]></category>
		<category><![CDATA[结构化]]></category>
		<category><![CDATA[统一建模语言]]></category>
		<category><![CDATA[自然语言]]></category>
		<category><![CDATA[草图]]></category>
		<category><![CDATA[语境图]]></category>
		<category><![CDATA[进程视图]]></category>
		<category><![CDATA[逻辑视图]]></category>
		<category><![CDATA[金字塔写作]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4065</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2023/12/06/a-minimum-set-of-diagrams-for-expressing-software-architecture 无论你是专职的软件架构师，还是在团队内兼职充当软件架构师角色的开发人员，一旦你处在软件架构师这个位置上，你自然就会遇到软件架构设计的三个困惑： 如何更深刻地理解业务； 如何更正确地取舍(包括技术性和业务性的)； 如何更有效地表达软件架构。 以上每个困惑展开来写都够写一本书的。而在这篇文章中，我仅聚焦最后一个困惑，聊聊我心目中表达软件架构的有效方式 &#8212; 最小图集(Minimum Diagram Set)。 1. 为什么软件架构需要有效表达 众所周知，软件架构承载着系统关键的技术决策和业务约束，指导着复杂软件的构建与演进，是实现软件系统的蓝图。但并不是说有了好的软件架构就一定可以做出好的软件系统，软件系统最终还是要经由开发人员来实现。 如果说架构师是软件架构的生产者，那么开发人员可以理解为是软件架构的消费者。但和一件普通商品一样，往往消费者很难Get到产品设计者的全部idea，产品越复杂，消费者Get到的比例越低，于是商品的生产者就会绞尽脑汁地制作产品说明书、功能演示视频等，目的就是想从不同角度更多、更有效的表达自己的商品的特性。对于普通商品而言，消费者Get程度低顶多是少用几个功能特性；但对于架构师生产的“产品”：架构设计成果而言，如果其消费者开发人员Get的程度低，那影响就会很严重，甚至可能会导致软件系统的开发彻底失败。 并且更不幸的是：我们的软件系统都是“复杂产品”。这样，如何表达和解读软件架构，弥合生产者与消费者之间的Gap，让开发者更多更深刻的理解软件架构这件“产品”便成为了架构师的困惑，日常架构设计工作中的难题，也是业界探索的重要课题。 架构设计是架构师与开发者之间的协议，只有有效的、充分的表达，协议才能被共识理解和忠实执行。业界在有效表达软件架构这条路上摸索了很多年，下面简单说说架构设计表达的演进历程。 2. 软件架构表达方式演进简史 软件架构表达的目的就是要直观地传达架构设计人员的思想和意图，使开发团队可以达成对架构设计的一致理解，促进各个团队协作，并作为开发人员编写代码以及管理人员推进项目的重要指导与参考。 2.1 自然语言描述 在软件工程的早期阶段，软件架构设计通常使用自然语言（如英语）进行描述。架构师会使用文档、规范和书面记录来表达架构设计的概念、原则、结构、组件和交互。然而，自然语言描述存在歧义性、解释性不足、理解起来较慢的问题，可能导致误解和沟通障碍。 2.2 图形化表达 人类大脑中传输的信息90%是视觉信息，其处理图形的速度要比处理文字的速度快上万倍。于是随着软件架构的复杂性增加，人们开始采用更直观、更易理解的图形化方法来描述架构设计(并辅以自然语言的文字描述)。 提到图形化表达，最简单的方法就是使用一支笔+一张白纸，基于自己“创造”的符号绘制草图(Sketch，以下草图来自c4model.com)： 这种非规范的框线草图虽然提供了灵活性，但付出的代价却是一致性，因为大家都在创造自己的制图符号，而不是使用统一的标准。 2.3 结构化的图形表达 结构化图是在设计表达迈向标准化方面走出的重要一步。结构化图包括数据流图、控制流图、层次图、组件图等，用于可视化表示系统的组件、模块、依赖关系和交互流程等(下图中元素来自维基百科)。 作为一种可以直观可视化描述与沟通架构设计的方式，结构化图形成为了表达架构设计的常见方法之一。不过，早期结构化表达的类型有限，无法涵盖所有环节，有的也没有形成标准，为了提高标准化程度，满足架构设计表达的全部需求，人们在二十世纪末推出了大一统的图形化建模语言UML。 2.4 统一建模语言(UML) 统一建模语言（Unified Modeling Language，UML）是一种通用的标准化、图形化建模语言，广泛用于软件架构和设计的表示，在软件架构表达方法方面具有里程碑意义： UML第一次在规范层面对图形表示进行了标准化，它提供了一组规范化的图形符号，用于描述系统的结构、行为和交互。在那个Rational统一过程（RUP）以及面向对象设计方法如日中天的时代，人们每每进行设计时，言必称使用UML。UML在图形化、标准化表达设计图方面走到了至今为止都无人企及的高峰。 但是，20多年后的今天，UML并没有成为当时标准出品方期望的那个样子，没能成为表达软件系统设计的主流符号系统。也许是它的复杂性阻碍了有效沟通，让人们看到它的spec后就“望而却步”了。不过UML并没有死掉，它依然活着，UML规范中的一些图(Diagram)依然被大家常用，比如：序列图(Sequence Diagram)、用例图(Use Case Diagram)、类图(Class Diagram)等。 2.5 形式化表达 业界在寻求图形化表达标准化的同时，也有一个分支在寻求用自然语言的“标准化”表达方法，这就是软件架构设计的形式化表达，在这个领域形成的语言被称为架构描述语言(ADL)。ADL提供了一组特定的语法和语义规则，用于定义系统的组件、接口、依赖关系、行为和性能特征。ADL使架构师能够使用精确的语言来表达和分析架构设计，支持自动化的验证和分析工具，在学术研究这个小众领域还是很有受众的。不过，显然在大多数工程化淋雨，形式化表达门槛太高，对于软件架构在团队内快速有效建立共识起不到什么作用。 下面是一些ADL的实现，感兴趣的童鞋可以了解一下： xArch/xADL ACME AADL 2.6 多视角的表达 有了UML这个前车之鉴后，人们似乎也放弃了在图记号“标准化”之路上的继续探索了，而是回归问题本源：怎么有效，就怎么来。 在工程实践中，人们认清了一个事实：很难在一张大图(Diagram)中进行软件架构设计的有效表达。于是大家开始采用“盲人摸象”的策略，将一个架构按不同视角表达为不同的图(Diagram)，这样当开发人员将多个视角形成的图都理解后，也就理解了整个架构设计。 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/a-minimum-set-of-diagrams-for-expressing-software-architecture-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2023/12/06/a-minimum-set-of-diagrams-for-expressing-software-architecture">本文永久链接</a> &#8211; https://tonybai.com/2023/12/06/a-minimum-set-of-diagrams-for-expressing-software-architecture</p>
<p>无论你是专职的软件架构师，还是在团队内兼职充当软件架构师角色的开发人员，一旦你处在软件架构师这个位置上，你自然就会遇到软件架构设计的三个困惑：</p>
<ul>
<li>如何更深刻地理解业务；</li>
<li>如何更正确地取舍(包括技术性和业务性的)；</li>
<li>如何更有效地表达软件架构。</li>
</ul>
<p>以上每个困惑展开来写都够写一本书的。而在这篇文章中，我仅聚焦最后一个困惑，聊聊我心目中表达软件架构的有效方式 &#8212; 最小图集(Minimum Diagram Set)。</p>
<h2>1. 为什么软件架构需要有效表达</h2>
<p>众所周知，软件架构承载着系统关键的技术决策和业务约束，指导着复杂软件的构建与演进，是实现软件系统的蓝图。但并不是说有了好的软件架构就一定可以做出好的软件系统，<strong>软件系统最终还是要经由开发人员来实现</strong>。</p>
<p>如果说架构师是软件架构的生产者，那么开发人员可以理解为是软件架构的消费者。但和一件普通商品一样，往往消费者很难Get到产品设计者的全部idea，产品越复杂，消费者Get到的比例越低，于是商品的生产者就会绞尽脑汁地制作产品说明书、功能演示视频等，目的就是想从不同角度更多、更有效的表达自己的商品的特性。对于普通商品而言，消费者Get程度低顶多是少用几个功能特性；但对于架构师生产的“产品”：架构设计成果而言，如果其消费者开发人员Get的程度低，那影响就会很严重，甚至可能会导致软件系统的开发彻底失败。</p>
<p>并且更不幸的是：我们的软件系统都是“复杂产品”。这样，如何表达和解读软件架构，弥合生产者与消费者之间的Gap，让开发者更多更深刻的理解软件架构这件“产品”便成为了架构师的困惑，日常架构设计工作中的难题，也是业界探索的重要课题。</p>
<p>架构设计是架构师与开发者之间的协议，只有有效的、充分的表达，协议才能被共识理解和忠实执行。业界在有效表达软件架构这条路上摸索了很多年，下面简单说说架构设计表达的演进历程。</p>
<h2>2. 软件架构表达方式演进简史</h2>
<p>软件架构表达的目的就是要直观地传达架构设计人员的思想和意图，使开发团队可以达成对架构设计的一致理解，促进各个团队协作，并作为开发人员编写代码以及管理人员推进项目的重要指导与参考。</p>
<h3>2.1 自然语言描述</h3>
<p>在软件工程的早期阶段，软件架构设计通常使用自然语言（如英语）进行描述。架构师会使用文档、规范和书面记录来表达架构设计的概念、原则、结构、组件和交互。然而，自然语言描述存在歧义性、解释性不足、理解起来较慢的问题，可能导致误解和沟通障碍。</p>
<h3>2.2 图形化表达</h3>
<p>人类大脑中传输的信息90%是视觉信息，其处理图形的速度要比处理文字的速度快上万倍。于是随着软件架构的复杂性增加，人们开始采用更直观、更易理解的图形化方法来描述架构设计(并辅以自然语言的文字描述)。</p>
<p>提到图形化表达，最简单的方法就是使用<strong>一支笔+一张白纸</strong>，基于自己“创造”的符号绘制草图(Sketch，以下草图来自c4model.com)：</p>
<p><img src="https://tonybai.com/wp-content/uploads/a-minimum-set-of-diagrams-for-expressing-software-architecture-2.png" alt="" /></p>
<p>这种非规范的框线草图虽然提供了灵活性，但付出的代价却是一致性，因为大家都在创造自己的制图符号，而不是使用统一的标准。</p>
<h3>2.3 结构化的图形表达</h3>
<p>结构化图是在设计表达迈向标准化方面走出的重要一步。结构化图包括数据流图、控制流图、层次图、组件图等，用于可视化表示系统的组件、模块、依赖关系和交互流程等(下图中元素来自维基百科)。</p>
<p><img src="https://tonybai.com/wp-content/uploads/a-minimum-set-of-diagrams-for-expressing-software-architecture-3.png" alt="" /></p>
<p>作为一种可以直观可视化描述与沟通架构设计的方式，结构化图形成为了表达架构设计的常见方法之一。不过，早期结构化表达的类型有限，无法涵盖所有环节，有的也没有形成标准，为了提高标准化程度，满足架构设计表达的全部需求，人们在二十世纪末推出了大一统的图形化建模语言UML。</p>
<h3>2.4 <a href="https://www.uml.org">统一建模语言(UML)</a></h3>
<p>统一建模语言（Unified Modeling Language，UML）是一种通用的标准化、图形化建模语言，广泛用于软件架构和设计的表示，在软件架构表达方法方面具有里程碑意义：</p>
<p><img src="https://tonybai.com/wp-content/uploads/a-minimum-set-of-diagrams-for-expressing-software-architecture-4.jpeg" alt="" /></p>
<p>UML第一次在规范层面对图形表示进行了标准化，它提供了一组规范化的图形符号，用于描述系统的结构、行为和交互。在那个Rational统一过程（RUP）以及面向对象设计方法如日中天的时代，人们每每进行设计时，言必称使用UML。UML在图形化、标准化表达设计图方面走到了至今为止都无人企及的高峰。</p>
<p><img src="https://tonybai.com/wp-content/uploads/a-minimum-set-of-diagrams-for-expressing-software-architecture-5.png" alt="" /></p>
<p>但是，20多年后的今天，UML并没有成为当时标准出品方期望的那个样子，没能成为表达软件系统设计的主流符号系统。也许是<strong>它的复杂性阻碍了有效沟通</strong>，让人们看到它的spec后就“望而却步”了。不过UML并没有死掉，它依然活着，UML规范中的一些图(Diagram)依然被大家常用，比如：<a href="https://www.uml-diagrams.org/sequence-diagrams.html">序列图(Sequence Diagram)</a>、<a href="https://www.uml-diagrams.org/use-case-diagrams.html">用例图(Use Case Diagram)</a>、<a href="https://www.uml-diagrams.org/class-diagrams-overview.html">类图(Class Diagram)</a>等。</p>
<p><img src="https://tonybai.com/wp-content/uploads/a-minimum-set-of-diagrams-for-expressing-software-architecture-6.png" alt="" /></p>
<h3>2.5 形式化表达</h3>
<p>业界在寻求图形化表达标准化的同时，也有一个分支在寻求用自然语言的“标准化”表达方法，这就是软件架构设计的形式化表达，在这个领域形成的语言被称为<a href="https://en.wikipedia.org/wiki/Architecture_description_language">架构描述语言(ADL)</a>。ADL提供了一组特定的语法和语义规则，用于定义系统的组件、接口、依赖关系、行为和性能特征。ADL使架构师能够使用精确的语言来表达和分析架构设计，支持自动化的验证和分析工具，在学术研究这个小众领域还是很有受众的。不过，显然在大多数工程化淋雨，形式化表达门槛太高，对于软件架构在团队内快速有效建立共识起不到什么作用。</p>
<p>下面是一些ADL的实现，感兴趣的童鞋可以了解一下：</p>
<ul>
<li><a href="http://isr.uci.edu/projects/xarchuci/">xArch/xADL</a> </li>
<li><a href="http://www.cs.cmu.edu/~able/">ACME</a></li>
<li><a href="http://www.aadl.info/">AADL</a></li>
</ul>
<h3>2.6 多视角的表达</h3>
<p>有了UML这个前车之鉴后，人们似乎也放弃了在图记号“标准化”之路上的继续探索了，而是回归问题本源：<strong>怎么有效，就怎么来</strong>。</p>
<p>在工程实践中，人们认清了一个事实：<strong>很难在一张大图(Diagram)中进行软件架构设计的有效表达</strong>。于是大家开始采用“盲人摸象”的策略，将一个架构按不同视角表达为不同的图(Diagram)，这样<strong>当开发人员将多个视角形成的图都理解后，也就理解了整个架构设计</strong>。</p>
<p>按照这个多视角表达的思路(也被称为是一种<strong>软件架构建模</strong>思路)，业界先后出现了：</p>
<ul>
<li><a href="https://arxiv.org/abs/2006.04975">Kruchten 4+1 views</a></li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/a-minimum-set-of-diagrams-for-expressing-software-architecture-7.png" alt="" /></p>
<p>逻辑视图（Logical View）关注系统的功能和功能模块，描述系统中各个模块之间的关系、接口和行为。它展示了系统的静态结构和动态行为，以及模块之间的通信和信息流。</p>
<p>进程视图（Process View）描述系统的并发和分布式特性，关注系统中的进程、线程、任务以及它们之间的关系和通信。该视图展示了系统的并发性、性能、可伸缩性等方面。</p>
<p>物理视图（Physical View）描述系统在硬件和软件环境中的部署和分布情况，包括物理设备、网络拓扑、软件组件的部署位置等。它关注系统的部署架构、可靠性、安全性等方面。</p>
<p>开发视图（Development View）关注系统的软件开发过程和组织结构，描述软件模块的组织、构建、测试和部署过程。它展示了软件开发团队的组织结构、开发工具、版本控制等方面。</p>
<p>场景视图（Scenario View）描述系统在特定使用情境下的行为和交互，以用户场景、用例或故事来说明系统的功能和行为。它帮助验证和验证系统架构的正确性和适应性。</p>
<ul>
<li><a href="https://c4model.com">C4 model</a></li>
</ul>
<p>C4模型是一种简洁、易于理解的软件架构建模方法，由Simon Brown提出。它通过四个层次的视图来描述软件系统的不同方面，包括语境视图(Context Diagram，这里借鉴了《<a href="https://book.douban.com/subject/26248182/">程序员必读之软件架构</a>》)一书中对Context的翻译)、容器视图(Container Diagram)、组件视图(Component Diagram)和代码视图(Code Diagram)，如下图所示：</p>
<p><img src="https://tonybai.com/wp-content/uploads/a-minimum-set-of-diagrams-for-expressing-software-architecture-8.png" alt="" /></p>
<p>语境视图是最高层级的视图，用于描述软件系统与外部实体之间的关系和交互。它展示了系统所处的环境和与外部实体（如用户、其他系统、第三方服务等）的关系，以及它们之间的交互方式。</p>
<p>容器视图关注系统内部的软件容器及其之间的关系和交互。容器可以是物理的、虚拟的或逻辑的，它们承载着系统中的组件或服务。容器可以是应用程序、数据库、消息队列、Web服务等。容器视图描述了系统的主要部件，以及它们之间的依赖关系和通信方式。</p>
<p>组件视图进一步展开容器视图中的组件，描述系统内部的组件及其之间的关系和交互。组件视图展示了系统的模块、类、库或其他可重用的软件单元，并显示它们之间的依赖关系、接口和通信方式。</p>
<p>代码视图是最底层的视图，关注具体的代码实现细节。它用于描述系统中的类、函数、方法等代码单元的结构、关系和实现细节。代码视图可以是面向对象的类图、模块图或其他代码组织结构的表示方式，用于帮助开发人员理解和浏览源代码。</p>
<p>下面示意图可以更直观的展示出语境、容器、组件以及代码之间这种逐渐“展开”的层次关系：</p>
<p><img src="https://tonybai.com/wp-content/uploads/a-minimum-set-of-diagrams-for-expressing-software-architecture-9.png" alt="" /></p>
<p>通过C4模型的这四个层次的视图，架构师可以逐渐深入地描述和表达软件系统的不同层次和组成部分，从整体到细节，帮助团队成员和利益相关者更好地理解和沟通软件架构。</p>
<ul>
<li><a href="https://arc42.org/">Arc42</a> </li>
</ul>
<p>Arc42是一种用于软件架构文档化的模板和方法，它提供了一套规范和指导原则来描述软件系统的架构。下面是Arc42的全景图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/a-minimum-set-of-diagrams-for-expressing-software-architecture-10.png" alt="" /></p>
<p>我们看到：Arc42模板也包含了多个视图，每个视图都关注系统架构的不同方面，包括Context、Building Block View、Runtime View以及Deployment View等。</p>
<p>Context View：描述系统与其外部环境之间的关系和交互，强调边界的概念，分为技术Context与业务Context。</p>
<p>部署视图（Deployment View）描述了系统的部署架构和环境，包括物理设备、服务器、网络拓扑以及协议等信息。</p>
<p>构件视图（Building Block View）描述了系统内部的组件、模块、子系统、包等，并展示它们之间的关系和依赖。构件视图是源码结构的概览。</p>
<p>运行时视图（Runtime View）描述了系统在运行时的行为和交互以及具体场景下对其他构件的运行时依赖。使用序列图、状态图等方式可展示系统的运行时行为。</p>
<h3>2.7 Diagrams As Code</h3>
<p>架构设计不是一成不变的，需要不断演进，因此架构视图也需要“与时俱进”的更新。但直接更新图片格式似乎很不方便，也无法在形式上很好的达成一致，于是一些基于<a href="https://tonybai.com/2022/05/10/introduction-of-implement-dsl-using-antlr-and-go">DSL</a>语法生成架构设计图(Diagram)的工具便涌现了出来，比如：<a href="http://plantuml.com/">PlantUML</a>、<a href="https://structurizr.com/dsl">Structurizr</a>、<a href="https://mermaid.live/">Mermaid</a>等。有了这些工具，架构师便可以使用文本编辑器来“画图”，支持“所见即所得”。并且由于Diagrams As Code(代码即图)，我们可以将架构设计图与版本控制系统很好地集成。</p>
<p>到这里，我们知道了基于多视角+“Diagrams As Code”是目前的主流的架构设计表达和实践方法，那么我们在软件架构表达实践中，究竟选择哪几个视角来表达呢？这个目前没有统一标准。调研了4+1 Views、C4 model以及Arc42后，我这里说说自己日常做架构表达时使用的最小视图集。</p>
<h2>3. 最小图集</h2>
<p>很多读者可能听说或学习过或实践过<a href="https://book.douban.com/subject/33391219/">金字塔写作</a>，金字塔写作原理是一种用于新闻报道和科技写作的写作方法，它的核心思想是将最重要的信息放在文章的开头，然后逐渐向下展开，提供更多的细节和背景信息。</p>
<p>金字塔写作的优势在于：</p>
<ul>
<li>它可以迅速吸引读者的注意力，让读者在最短时间内了解文章的核心内容；</li>
<li>它还可确保信息传递：将最重要的信息放在开头，可以避免读者在阅读过程中错过关键信息或迷失在细枝末节中，确保信息有效地传达给读者；</li>
<li>它还具备灵活性和可定制性，不要求严格按照一个固定的结构来组织文章，而是提供了一种基本的思路和原则，可以根据具体情况进行调整和定制，以适应不同的写作需求和读者群体。</li>
</ul>
<p>我理解，金字塔写作方法之所以能够成功，其本质是站在了读者的角度去思考问题，想读者之所想，做读者之所需。</p>
<p>软件架构表达的目的也是让开发人员快速深入的理解架构，与设计人员达成共识，指导后续软件系统的实现。所以要想形成有效表达，我们就需要像金字塔写作那样<strong>站在开发人员的角度</strong>来考虑架构表达，借鉴金字塔原理，自上而下，先表达最重要的信息，然后逐渐向下展开，避免开发人员在理解过程中错过关键信息或迷失在细枝末节当中。</p>
<p>综合前面介绍的多种Views的方法，我们觉得软件架构表达的起点，即第一个图必须是语境图(Context Diagram)。</p>
<h3>3.1 语境图(Context Diagram)</h3>
<p>语境图表达的是系统最高的抽象层次，是最高视角，全局视角。通过语境图，可以解决开发人员在内心中提出的下面问题：</p>
<ul>
<li>我们构建的（或已经构建的）软件系统是什么(What)？</li>
<li>谁会用它？</li>
<li>如何融入已有的IT环境？</li>
<li>系统的边界是什么？（业务的，技术的)</li>
</ul>
<p>语境图不会也不应该展示太多细节，它是软件系统设计图的起点。后续的图都是用“放大镜”将我们的系统放大后的细节的表达。当牵涉到理解系统间接口的问题时，语境图还可以为你识别可能需要沟通的人提供了一个起点。</p>
<p>语境图向开发者展现的重点在于软件系统的范围以及与外部的交互行为（用户&lt; &#8211; >系统、系统&lt; &#8211; >系统等等）。下面是使用<a href="https://structurizr.com/dsl">structurizr</a>绘制的一个语境图的实例：</p>
<p><img src="https://tonybai.com/wp-content/uploads/a-minimum-set-of-diagrams-for-expressing-software-architecture-11.png" alt="" /></p>
<p>语境图中心蓝色的矩形框代表的是我们的软件系统，上方的user、role、actor是我们的软件系统的用户；client是与我们的软件系统交互的系统，是系统到系统交互的一个代表；在我们的软件系统、Inner System1和Inner System2之外有一个虚线框，代表了企业范围；而Inner System1和Inner System2是我们的软件系统在企业内部依赖的系统；同时，我们的软件系统还依赖企业外部的Outer System1和Outer System2。</p>
<p>上述语境图对应的structurizr dsl代码如下：</p>
<pre><code>// system context diagrams

workspace {

    model {
        u = person "User"
        r = person "Role"
        a = person "Actor"
        c = softwareSystem "Client Software System" {
            tags "client"
        }

        enterprise = group "Enterprise A" {
            s = softwareSystem "Our Software System" {
                tags "server"
            }

            d1 = softwareSystem "Inner System1" {
                tags "dep"
            }

            d2 = softwareSystem "Inner System2" {
                tags "dep"
            }
        }
        d3 = softwareSystem "Outer System1" {
            tags "dep"
        }

        d4 = softwareSystem "Outer System2" {
            tags "dep"
        }

        u -&gt; s "Uses"
        r -&gt; s "Uses"
        a -&gt; s "Uses"
        c -&gt; s "Call"
        s -&gt; d1 "Uses"
        s -&gt; d2 "Uses"
        s -&gt; d3 "Uses"
        s -&gt; d4 "Uses"
    }

    views {
        systemContext s {
            include *
            autoLayout
        }

        styles {
            element "server" {
                background #1168bd
                color #ffffff
            }

            element "dep" {
                background #e5e4e2
                color #000000
            }

            element "client" {
                background #e5e4e2
                color #000000
            }

            element "Person" {
                shape person
                background #08427b
                color #ffffff
            }
        }

    }

}
</code></pre>
<p>基于语境图，就好比我们站在万米高空一览Our Software System。不过对于架构设计表达来说，这还不够，现在是时候下降高度让视野进入到系统内部去挖掘一些细节了。</p>
<h3>3.2 容器图(Container Diagram)</h3>
<p>在从万米高空的系统全局视角了解了我们的软件系统是什么后，我们将第一次进入到系统内部。我们现在所处的高度是100米，在这个高度上，可以清晰地看到软件系统的整体形态、内部脉络、技术选择、职责分布以及各个部分之间是如何交流的。我们将每个部分称为一个容器(container)。一个容器通常可以表示一个应用/服务或数据存储，如果你的软件系统采用了微服务架构，那么将每个服务作为一个容器通常是可行的。</p>
<p>针对每个容器，我们可以设置它的属性：名字(如Web App、API网关、关系数据库存储、订阅服务等）、实现技术(如mvc等)以及功能性的描述。在容器间的联系上我们可以附加上通信方式(json over http、gRPC、websocket等)。</p>
<p>下面是上面语境图中的My Software System的容器图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/a-minimum-set-of-diagrams-for-expressing-software-architecture-12.png" alt="" /></p>
<p>在这个容器图中，我们看到了系统支持通过Web app和mobile app访问和使用；系统的入口使用了API网关；系统内部分为业务服务和基础服务，基础服务封装了到关系数据库、对象存储(oss)的接口(关系数据库和oss都是技术选择)；业务服务可以调用企业内部服务，亦可调用企业外部服务，并且明确了调用方式。</p>
<p>下面是生成上述容器图的structurizr的代码：</p>
<pre><code>// container diagrams

workspace {

    model {
        u = person "User"

        enterprise = group "Enterprise A" {
            s = softwareSystem "Our Software System" {
                tags "server"

                mobileApp = container "Mobile App" {
                    tags "container"
                }

                webApp = container "Web App" {
                     tags "container"
                }

                apiGw = container "API Gateway" {
                     tags "container"
                }

                biz1 = container "Business Service 1" {
                     tags "container"
                }

                biz2 = container "Business Service 2" {
                     tags "container"
                }

                biz3 = container "Business Service 3" {
                     tags "container"
                }

                base1 = container "Base Service 1" {
                     tags "container"
                }

                base2 = container "Base Service 2" {
                     tags "container"
                }

                base3 = container "Base Service 3" {
                     tags "container"
                }

                rds = container "Relational Database system" {
                     tags "container"
                }

                oss = container "Object Storage Service" {
                     tags "container"
                }
            }

            d1 = softwareSystem "Inner System1" {
                tags "dep"
            }

            d2 = softwareSystem "Inner System2" {
                tags "dep"
            }
        }

        d3 = softwareSystem "Outer System1" {
            tags "dep"
        }

        d4 = softwareSystem "Outer System2" {
            tags "dep"
        }

        u -&gt; mobileApp "Uses"
        u -&gt; webApp "Uses"
        mobileApp -&gt; apiGw "Makes API calls to" "JSON/HTTPS"
        WEBApp -&gt; apiGw "Makes API calls to" "JSON/HTTPS"
        apiGw -&gt; biz1 "Route API calls to" "gRPC"
        apiGw -&gt; biz2 "Route API calls to" "gRPC"
        apiGw -&gt; biz3 "Route API calls to" "gRPC"
        biz1 -&gt; base1 "Inner API calls to" "gRPC"
        biz1 -&gt; base2 "Inner API calls to" "gRPC"
        biz2 -&gt; base2 "Inner API calls to" "gRPC"
        biz2 -&gt; base3 "Inner API calls to" "gRPC"
        biz3 -&gt; base3 "Inner API calls to" "gRPC"
        base1 -&gt; rds "Reads from and writes to" "Raw SQL"
        base1 -&gt; oss "Reads from and writes to" "HTTPS"
        base2 -&gt; rds "Reads from and writes to" "Raw SQL"
        base3 -&gt; oss "Reads from and writes to" "HTTPS"
        biz1 -&gt; d1 "Make API calls to" "HTTP"
        biz2 -&gt; d3 "Make API calls to" "HTTP"
        biz3 -&gt; d2 "Make API calls to" "HTTP"
        biz3 -&gt; d4 "Make API calls to" "HTTP"
    }

    views {
        container s {
            include *
            autoLayout
        }

        styles {
            element "server" {
                background #1168bd
                color #ffffff
            }

            element "container" {
                background #1168bd
                color #ffffff
            }

            element "dep" {
                background #e5e4e2
                color #000000
            }

            element "Person" {
                shape person
                background #08427b
                color #ffffff
            }
        }

    }

}
</code></pre>
<blockquote>
<p>注：在容器图这个层次上，group关键字没有起作用，导致企业内部服务与外部服务放在一起了。</p>
</blockquote>
<p>按照C4 model的思路，接下来我们会再下降高度，来到10米的高空，进入到某个容器的内部。但容器内部的设计在我看来属于详细设计范畴，如果采用的是微服务架构，那么容器内部的设计就相当于某个服务的设计。所以这里，我并未将这部分作为架构表达的必需之图。</p>
<h3>3.3 序列图(Sequence Diagram)</h3>
<p>无论是语境图，还是容器图，从大类来看，都属于静态的结构图。但做过软件系统设计和研发的童鞋都知道，仅有静态的表达还是不够的，不足以传达软件系统的所有信息，我们还需要对动态行为的表达。这就是为什么我将序列图作为软件表达最小图集一份子的原因。</p>
<p>可能有些人将序列图作为需求分析阶段的产物，其实，序列图既可以在需求阶段产生，也可以在架构设计阶段产生。它在不同阶段有不同的应用和目的。</p>
<p>在需求阶段，序列图被用于描述系统的功能需求和行为。它可以帮助分析和定义系统的用例或用户故事，以及系统与外部实体（如用户、其他系统、服务等）之间的交互过程。通过序列图，需求分析人员和开发团队可以更清晰地理解系统的功能需求，并就用户与系统之间的交互进行沟通和确认。</p>
<p>在架构设计阶段，序列图被用于描述系统的结构和组件之间的交互。在这个阶段，序列图通常用于展示系统的运行时行为、组件之间的消息传递和调用关系。架构师使用序列图来验证系统的设计方案，确保系统的各个组件按预期互相协作，并满足功能和性能要求。</p>
<p>这里的序列图，可以对应前面的Arc42的Runtime View，以及C4 model的<a href="https://c4model.com/#DynamicDiagram">Dynamic Diagram</a>。</p>
<p>序列图也是UML语言中最常被使用的一种Diagram，即便是在UML不那么被提及的今天，我个人也推荐使用<a href="https://www.uml-diagrams.org/sequence-diagrams.html">UML的序列图</a>来表达，而不推荐用structurizr来画了，structurizr在序列图方面的表达能力还是弱了许多。</p>
<p>你可以用你最喜欢的画图工具来绘制UML序列图（比如我经常用的<a href="https://www.drawio.com">drawio</a>），也可以选择<a href="https://plantuml.com/zh/sequence-diagram">plantuml</a>这种基于DSL语法生成序列图的方式来绘制。plantuml对序列图的支持还是非常好的，支持了序列图的大多数元素，可以绘制出非常复杂的图来(下图来自plantuml官网)：</p>
<p><img src="https://tonybai.com/wp-content/uploads/a-minimum-set-of-diagrams-for-expressing-software-architecture-13.png" alt="" /></p>
<p>针对一个复杂的软件系统，我们可能需要针对不同的Container(或更进一步的组件)绘制较多的序列图，至少要覆盖到软件系统各个Container的核心交互流程。</p>
<h3>3.4 部署图(Deployment Diagram)</h3>
<p>无论是C4模型，还是arc42，亦或是UML语言，都包含部署图。在软件架构表达时，准确表达部署设计，对开发人员后续的实现具有很好的指导作用。通过部署图，架构设计人员可以说明静态图中的软件系统和/或容器实例是如何部署到给定部署环境（如生产、暂存、开发等）中的基础设施上的，比如下面这个部署示意图(来自c4model.com)：</p>
<p><img src="https://tonybai.com/wp-content/uploads/a-minimum-set-of-diagrams-for-expressing-software-architecture-14.png" alt="" /></p>
<p>我们看到部署图中的核心角色是部署节点(Node)，它代表了软件系统/容器实例运行的位置；可能是物理基础设施（如物理服务器或设备）、虚拟化基础设施（如IaaS、PaaS、虚拟机）、容器化基础设施（如Docker容器）、执行环境（如数据库服务器、Java EE Web/应用服务器、Microsoft IIS）等，并且部署节点还可以嵌套。此外，右下角的”x N”表示需要多少个部署节点。</p>
<p>通过部署图还可以表达云基础架构的情况(下图来自c4model.com)，可以包含DNS、负载均衡器以及防火墙等部署的基础设施的节点：</p>
<p><img src="https://tonybai.com/wp-content/uploads/a-minimum-set-of-diagrams-for-expressing-software-architecture-15.png" alt="" /></p>
<p>structurizr对于部署图支持的还不错，还可以像上图那样使用不同公有云提供商特色的Theme来绘制部署图。</p>
<p>到这里，我们已经“凑齐”了表达软件系统架构的最小图集：语境图、容器图、序列图和部署图。我们要学会灵活使用这些图。在软件系统十分复杂的情况下，我们可以将语境图分为<a href="https://c4model.com/#SystemLandscapeDiagram">System Landscape diagram</a>和多个sub system的语境图，之后以此类推，对于每个sub system做容器图等。</p>
<h2>4. 最小图集之外的图(可选)</h2>
<p>有些公司或组织会将架构设计阶段延伸到container内部，这样对软件系统架构的表达就要延伸到详细设计，甚至是编码阶段时，我们就要考虑下面两个类型的Diagram了：组件图和代码图。</p>
<h3>4.1 组件图(Component Diagram)</h3>
<p>如果容器图阶段，你所在的高度是100米，那么组件图阶段，你将位于高度为10米的空中，这足以让你看清容器中每个组件(Component)的细节。</p>
<p>组件图就是容器内部的设计，它涉及到容器内部各个逻辑组件的结构与组件间的交互。在这个层次，你可以使用你擅长的面向对象设计方法，或者面向契约/接口的设计模式，你也可以使用一些成熟的企业应用设计模式，比如MVC等。</p>
<p>下面是一张组件图示例(来自c4model.com)：</p>
<p><img src="https://tonybai.com/wp-content/uploads/a-minimum-set-of-diagrams-for-expressing-software-architecture-16.png" alt="" /></p>
<p>我们看到中间的部分就是API Application这个容器内部的逻辑组件结构与交互情况。有些时候在组件图这一层面，我们甚至可以对照初对应项目中的代码布局结构。</p>
<p>对于组件图中关键组件间的复杂交互流程，可辅以序列图的方式来表达。</p>
<p>此外，组件图可以使用structurizr绘制，语法和语境图、容器图十分相似。</p>
<h3>4.2 代码图(Code Diagram)</h3>
<p>再下降，我们来到离地面1米的高度，我们几乎要躬身入局，参与编码了。通常架构设计不会到达这个阶段，架构师们在100米或10米高度完成任务后，就可以去休息了。</p>
<p>但如果包含这个阶段，我们要给出的便是代码图(Code Diagram)，再直白些，就是UML类图、E-R关系图等，下面是一个示意图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/a-minimum-set-of-diagrams-for-expressing-software-architecture-17.png" alt="" /></p>
<p>这是一个直面开发人员的图，你可以看到编程语言中的那些机制：接口、继承、实现等等，开发人员甚至可以通过工具将这样的uml class图直接转换为项目的骨架代码。</p>
<h2>4. 小结</h2>
<p>本文首先介绍了为什么软件架构需要有效表达，以便开发者更好地理解架构设计。然后回顾了软件架构表达方式的演进历史，从自然语言描述到图形化表达，再到结构化图形表达、UML、形式化表达，最终发展到现在的多视角表达方式。</p>
<p>文章结合笔者实践经验，借鉴多个多视角软件架构模型，提出了最小图集的概念，笔者认为有效表达软件架构最关键的视角有四个，分别是:</p>
<ol>
<li>语境图：描述系统的整体位置和边界 </li>
<li>容器图：展示系统内部的容器及其关系</li>
<li>序列图：呈现容器内组件以及组件之间的交互行为</li>
<li>部署图：阐明系统在实际环境中的部署情况</li>
</ol>
<p>此外，我认为还可根据需要补充组件图和代码图等更细节的视图。这套最小图集能较全面地表达软件系统的静态结构和动态行为，帮助开发者理解架构设计。</p>
<p>总的来说，该文章从工程实践的视角出发，提出了一套行之有效的软件架构表达方法，对于架构设计的团队沟通及实现具有很好的指导意义。</p>
<p>btw，在容器图或组件图设计阶段，如果要完善工程设计，还可以结合具体的接口文档予以表达，比如基于Swagger的API设计文档等。</p>
<h2>5. 参考资料</h2>
<ul>
<li>《<a href="https://book.douban.com/subject/26248182/">Software Architecture for Developers</a>》- https://book.douban.com/subject/26248182/</li>
<li><a href="https://c4model.com">The C4 model for visualising software architecture</a> &#8211; https://c4model.com</li>
<li><a href="https://www.omg.org/spec/UML">Unified Modeling Language Specification</a> &#8211; https://www.omg.org/spec/UML</li>
<li><a href="https://www.uml-diagrams.org">The Unified Modeling Language</a> &#8211; https://www.uml-diagrams.org</li>
<li><a href="https://arquisoft.github.io/slides/course2021/EN.ASW.TE03_Documentation.pdf">Communicating Software Architecture</a> &#8211; https://arquisoft.github.io/slides/course2021/EN.ASW.TE03_Documentation.pdf</li>
<li><a href="https://arc42.org">arc42</a> &#8211; https://arc42.org</li>
</ul>
<hr />
<p><a href="https://public.zsxq.com/groups/51284458844544">“Gopher部落”知识星球</a>旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！2023年，Gopher部落将进一步聚焦于如何编写雅、地道、可读、可测试的Go代码，关注代码质量并深入理解Go核心技术，并继续加强与星友的互动。欢迎大家加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻) &#8211; https://gopherdaily.tonybai.com</p>
<p>我的联系方式：</p>
<ul>
<li>微博(暂不可用)：https://weibo.com/bigwhite20xx</li>
<li>微博2：https://weibo.com/u/6484441286</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>Gopher Daily归档 &#8211; https://github.com/bigwhite/gopherdaily</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2023, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2023/12/06/a-minimum-set-of-diagrams-for-expressing-software-architecture/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>将Roaring Bitmap序列化为JSON</title>
		<link>https://tonybai.com/2023/02/01/serialize-roaring-bitmap-to-json/</link>
		<comments>https://tonybai.com/2023/02/01/serialize-roaring-bitmap-to-json/#comments</comments>
		<pubDate>Wed, 01 Feb 2023 14:24:48 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Array]]></category>
		<category><![CDATA[benchmark]]></category>
		<category><![CDATA[bit]]></category>
		<category><![CDATA[bitarray]]></category>
		<category><![CDATA[bitmap]]></category>
		<category><![CDATA[bitvector]]></category>
		<category><![CDATA[bytedance]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[encoding]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[json]]></category>
		<category><![CDATA[marshal]]></category>
		<category><![CDATA[RLE]]></category>
		<category><![CDATA[roaring]]></category>
		<category><![CDATA[SET]]></category>
		<category><![CDATA[sonic]]></category>
		<category><![CDATA[Test]]></category>
		<category><![CDATA[unmarshal]]></category>
		<category><![CDATA[二进制]]></category>
		<category><![CDATA[位图]]></category>
		<category><![CDATA[位图索引]]></category>
		<category><![CDATA[压缩位图]]></category>
		<category><![CDATA[字节跳动]]></category>
		<category><![CDATA[序列化]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[性能基准测试]]></category>
		<category><![CDATA[数组]]></category>
		<category><![CDATA[映射]]></category>
		<category><![CDATA[标准库]]></category>
		<category><![CDATA[比特位]]></category>
		<category><![CDATA[汇编]]></category>
		<category><![CDATA[测试]]></category>
		<category><![CDATA[集合]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3789</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2023/02/01/serialize-roaring-bitmap-to-json 近期在实现一个数据结构时使用到了位图索引(bitmap index)，本文就来粗浅聊聊位图(bitmap)。 一. 什么是bitmap 位图索引使用位数组(bit array，也有叫bitset的，通常被称为位图(bitmap)，以下均使用bitmap这个名称)实现。一个bitmap是一个从某个域（通常是一个整数范围）到集合{0，1}中的值的映射： 映射：f(x) -&#62; {0, 1}， x是[0, n)的集合中的元素。 以n=8的集合{1, 2, 5}为例： f(0) = 0 f(1) = 1 f(2) = 1 f(3) = 0 f(4) = 0 f(5) = 1 f(6) = 0 f(7) = 0 如果用bit来表示映射后得到的值，我们将得到一个二进制数0b00100110(最右侧的bit位上的值指示集合中数值0的存在性)，这样我们就可以用一个字节大小的数值0b00100110来表示{1, 2, 5}这个集合中各个位置的数值的存在性了。 我们看到相比于使用一个byte数组来表示{1, 2, 5}这个集合(即便是8个数值，也至少要8x8=64个字节)，bitmap无疑具有更高的空间利用率。同时，通过bitmap的与、或、异或等操作，我们可以很容易且高性能地得到集合的交、并、Top-K等集合操作的结果。 不过，传统的bitmap并不总能带来空间上的节省，比如我们要表示{1, 2, 10, 50000000}这样一个集合，那么使用传统bitmap将带来很大的空间开销。对于这样的具有稀疏元素特性的集合，传统位图实现就失去了其优势，而压缩位图(compressed bitmap)则成为了更佳的选择。 二. 压缩位图(compressed [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/serialize-roaring-bitmap-to-json-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2023/02/01/serialize-roaring-bitmap-to-json">本文永久链接</a> &#8211; https://tonybai.com/2023/02/01/serialize-roaring-bitmap-to-json</p>
<p>近期在实现一个数据结构时使用到了<a href="https://en.wikipedia.org/wiki/Bitmap_index">位图索引(bitmap index)</a>，本文就来粗浅聊聊位图(bitmap)。</p>
<h3>一. 什么是bitmap</h3>
<p>位图索引使用位数组(bit array，也有叫bitset的，通常被称为位图(bitmap)，以下均使用bitmap这个名称)实现。一个bitmap是一个从某个域（通常是一个整数范围）到集合{0，1}中的值的映射：</p>
<pre><code>映射：f(x) -&gt; {0, 1}， x是[0, n)的集合中的元素。
</code></pre>
<p>以n=8的集合{1, 2, 5}为例：</p>
<pre><code>f(0) = 0
f(1) = 1
f(2) = 1
f(3) = 0
f(4) = 0
f(5) = 1
f(6) = 0
f(7) = 0
</code></pre>
<p>如果用bit来表示映射后得到的值，我们将得到一个二进制数0b00100110(最右侧的bit位上的值指示集合中数值0的存在性)，这样我们就可以用<strong>一个字节大小</strong>的数值0b00100110来表示{1, 2, 5}这个集合中各个位置的数值的存在性了。</p>
<p>我们看到相比于使用一个byte数组来表示{1, 2, 5}这个集合(即便是8个数值，也至少要8x8=64个字节)，bitmap无疑具有更高的空间利用率。同时，通过bitmap的与、或、异或等操作，我们可以很容易且高性能地得到集合的交、并、Top-K等集合操作的结果。</p>
<p>不过，传统的bitmap并不总能带来空间上的节省，比如我们要表示{1, 2, 10, 50000000}这样一个集合，那么使用传统bitmap将带来很大的空间开销。对于这样的具有稀疏元素特性的集合，传统位图实现就失去了其优势，而<a href="https://en.wikipedia.org/wiki/Bitmap_index#Compression">压缩位图(compressed bitmap)</a>则成为了更佳的选择。</p>
<h3>二. 压缩位图(compressed bitmap)</h3>
<p>压缩位图既可以很好的支持稀疏集合，又保留了传统位图的空间和高性能的集合操作优势。最常见的压缩位图的方案是RLE(run-length encoding)，对这种方案的粗浅理解是对连续的0和1进行分别计数，比如下面这bitmap就可以压缩编码为<strong>n个0和m和1</strong>：</p>
<pre><code>0b0000....00001111...111
</code></pre>
<p>RLE方案(以及其变体)具有很好的压缩比并且编解码也很高效。不过其不足是很难随机访问某个bit，每次访问特定的bit都要从头进行解压缩。如果你想将两个大的bitmap进行交集操作，你必须解压缩整个大bitmap。</p>
<p>一种名为roaring bitmap的压缩位图方案可以解决上述的问题。</p>
<h3>三. roaring bitmap工作原理简介</h3>
<p>roaring bitmap 的工作方式是这样的：它将32位整型所能表示的整型数[0, 4294967296)划分为2^16个chunk（例如，[0，2^16)，[2^16，2x2^16)，...）。当向roaring bitmap加入一个数或从roaring bitmap获取一个数的存在性时，roaring bitmap通过这个数的前16位决定该数在哪个trunk中。一旦确定trunk后，便可以通过与该trunk关联的container指针找到真正存储该数后16位值的container，在container中通过查找算法定位：</p>
<p><img src="https://tonybai.com/wp-content/uploads/serialize-roaring-bitmap-to-json-2.png" alt="" /></p>
<p>如上图所示：roaring bitmap的trunk关联的container类型不止有一种：</p>
<ul>
<li>array container：这是一个有序的16bit整型数组，也是默认的container type，最多存储4096个数值。当超出这个数量时，会考虑用bitset container存储；</li>
<li>bitset container：就是一个非压缩的bitmap，有2^16个bit位；</li>
<li>run container：这是一个采用RLE压缩的、适合存储连续数值的container type，从上面图中也可以看出，这个container中存储的是一个个数对&lt;s,l>，表示的数值范围为[s, s + l]。</li>
</ul>
<p>roaring bitmap会根据trunk中的数的特征选择适当的container类型，并且这种选择是动态的，以尽量减少内存使用为目标。当我们向roaring bitmap添加或删除值时，对应trunk的container type都可能会改变。不过从整体视角看，无论使用哪种container，roaring bitmap都支持对某个bit的快速随机访问。同时roaring bitmap在实现层面也更容易利用现代cpu提供的高性能指令，并且是缓存友好的。</p>
<h3>四. roaring bitmap的效果</h3>
<p>roaring bitmap官方提供了多种主流语言的实现，其中Go语言的实现是<a href="https://github.com/RoaringBitmap/roaring">roaring包</a>。roaring包的使用十分简单，下面就是一个简单的示例：</p>
<pre><code>package main

import (
    "fmt"

    "github.com/RoaringBitmap/roaring"
)

func main() {
    rb := roaring.NewBitmap()
    rb.Add(1)
    rb.Add(100000000)
    fmt.Println(rb.String())
    fmt.Println(rb.Contains(1))
    fmt.Println(rb.Contains(2))
    fmt.Println(rb.Contains(100000000))

    fmt.Println("cardinality:", rb.GetCardinality())
    fmt.Println("rb size=", rb.GetSizeInBytes())
}
</code></pre>
<p>运行示例得到如下结果：</p>
<pre><code>{1,100000000}
true
false
true
cardinality: 2
rb size= 16
</code></pre>
<p>我们看到{1, 100000000}的稀疏集合映射到roaring bitmap仅占用了16个字节的空间(和非压缩bitmap对比)。</p>
<p>下面是一个由3000w以内的随机整数构成的集合到roaring bitmap的映射示例：</p>
<pre><code>func main() {
    rb := roaring.NewBitmap()

    for i := 0; i &lt; 30000000; i++ {
        rb.Add(uint32(rand.Int31n(30000000)))
    }

    fmt.Println("cardinality:", rb.GetCardinality())
    fmt.Println("rb size=", rb.GetSizeInBytes())
}
</code></pre>
<p>下面是其执行结果：</p>
<pre><code>cardinality: 18961805
rb size= 3752860
</code></pre>
<p>我们看到集合中一共加入近1900w个数，roaring bitmap总共占用了3.6MB的内存空间，这个和非压缩bitmap没有拉开差距。</p>
<p>下面是一个连续的3000w数字的集合到roaring bitmap的映射示例：</p>
<pre><code>func main() {
    rb := roaring.NewBitmap()

    for i := 0; i &lt; 30000000; i++ {
        rb.Add(uint32(i))
    }

    fmt.Println("cardinality:", rb.GetCardinality())
    fmt.Println("rb size=", rb.GetSizeInBytes())
}
</code></pre>
<p>其执行结果如下：</p>
<pre><code>cardinality: 30000000
rb size= 21912
</code></pre>
<p>显然针对这样的连续数字集合，roaring bitmap的空间效率体现的十分明显。</p>
<h3>五. roaring bitmap的序列化</h3>
<p>以上是对roaring bitmap的粗浅入门介绍，如果对roaring bitmap感兴趣，可以去其官方站点或开源项目主页做深入了解和学习。不过这里我要说的是roaring bitmap的序列化问题(序列化后便可以传输和持久化存储了)，以序列化为JSON和从JSON反序列化为例。</p>
<p>考虑到性能问题，json序列化我选择的是<a href="https://github.com/bytedance/sonic">字节开源的sonic项目</a>。sonic虽然说是一个Go开源项目，但由于其对JSON解析的极致优化的要求，目前该项目中Go代码的占比仅有30%不到，60%多都是<strong>汇编代码</strong>。sonic提供与Go标准库json包兼容的函数接口，并且sonic还支持streaming I/O模式，支持将特定类型对象序列化到io.Writer或从io.Reader中反序列化数据为一个特定类型对象，这个也是标准库json包所不支持的。当遇到超大JSON时，streaming I/O模式十分惯用，io.Writer和Reader可以让你的Go应用不至于瞬间分配大量内存，甚至被oom killed掉。</p>
<p>不过roaring bitmap并没有原生提供序列化(marshal)到JSON(或反向序列化)的函数/方法，那么我们如何将一个roaring bitmap序列化为一个JSON文本呢？Go标准库json包提供了Marshaler和Unmarshaler接口，凡是实现了这两个接口的自定义类型，json包都可以支持该自定义类型的序列化和反序列化。在这方面，<strong>sonic项目与Go标准库json包保持兼容</strong>。</p>
<p>不过roaring.Bitmap类型并没有实现Marshaler和Unmarshaler接口，roaring.Bitmap的序列化和反序列化需要我们自己来完成。</p>
<p>那么，我们首先想到的就是基于roaring.Bitmap自定义一个新类型，比如MyRB：</p>
<pre><code>// https://github.com/bigwhite/experiments/blob/master/roaring-bitmap-examples/bitmap_json.go
type MyRB struct {
    RB *roaring.Bitmap
}
</code></pre>
<p>然后，我们给出MyRB的MarshalJSON和UnmarshalJSON方法的实现以满足Marshaler和Unmarshaler接口的要求：</p>
<pre><code>// https://github.com/bigwhite/experiments/blob/master/roaring-bitmap-examples/bitmap_json.go
func (rb *MyRB) MarshalJSON() ([]byte, error) {
    s, err := rb.RB.ToBase64()
    if err != nil {
        return nil, err
    }

    r := fmt.Sprintf(`{"rb":"%s"}`, s)
    return []byte(r), nil
}

func (rb *MyRB) UnmarshalJSON(data []byte) error {
    // data =&gt; {"rb":"OjAAAAEAAAAAAB4AEAAAAAAAAQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4A"}

    _, err := rb.RB.FromBase64(string(data[7 : len(data)-2]))
    if err != nil {
        return err
    }

    return nil
}
</code></pre>
<p>我们利用roaring.Bitmap提供的ToBase64方法将roaring bitmap转换为一个base64字符串，然后再序列化为JSON；反序列化则是利用FromBase64对JSON数据进行解码。下面我们测试一下MyRB类型与JSON间的相互转换：</p>
<pre><code>// https://github.com/bigwhite/experiments/blob/master/roaring-bitmap-examples/bitmap_json.go

func main() {
    var myrb = MyRB{
        RB: roaring.NewBitmap(),
    }

    for i := 0; i &lt; 31; i++ {
        myrb.RB.Add(uint32(i))
    }
    fmt.Printf("the cardinality of origin bitmap = %d\n", myrb.RB.GetCardinality())

    buf, err := sonic.Marshal(&amp;myrb)
    if err != nil {
        panic(err)
    }

    fmt.Printf("bitmap2json: %s\n", string(buf))

    var myrb1 = MyRB{
        RB: roaring.NewBitmap(),
    }
    err = sonic.Unmarshal(buf, &amp;myrb1)
    if err != nil {
        panic(err)
    }

    fmt.Printf("after json2bitmap, the cardinality of new bitmap = %d\n", myrb1.RB.GetCardinality())
}
</code></pre>
<p>运行该示例：</p>
<pre><code>the cardinality of origin bitmap = 31
bitmap2json: {"rb":"OjAAAAEAAAAAAB4AEAAAAAAAAQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaABsAHAAdAB4A"}
after json2bitmap, the cardinality of new bitmap = 31
</code></pre>
<p>输出结果符合预期。</p>
<p>基于支持序列化的MyRB，顺便我们再看一下sonic和标准库json的benchmark对比，我们编写一个简单的对比测试用例：</p>
<pre><code>// https://github.com/bigwhite/experiments/blob/master/roaring-bitmap-examples/benchmark_test.go

type Foo struct {
    N    int    `json:"num"`
    Name string `json:"name"`
    Addr string `json:"addr"`
    Age  string `json:"age"`
    RB   MyRB   `json:"myrb"`
}

func BenchmarkSonicJsonEncode(b *testing.B) {
    var f = Foo{
        N: 5,
        RB: MyRB{
            RB: roaring.NewBitmap(),
        },
    }

    for i := 0; i &lt; 3000; i++ {
        f.RB.RB.Add(uint32(i))
    }

    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i &lt; b.N; i++ {
        _, err := sonic.Marshal(&amp;f)
        if err != nil {
            panic(err)
        }
    }
}

func BenchmarkSonicJsonDecode(b *testing.B) {
    var f = Foo{
        N: 5,
        RB: MyRB{
            RB: roaring.NewBitmap(),
        },
    }

    for i := 0; i &lt; 3000; i++ {
        f.RB.RB.Add(uint32(i))
    }

    buf, err := sonic.Marshal(&amp;f)
    if err != nil {
        panic(err)
    }
    var f1 = Foo{
        RB: MyRB{
            RB: roaring.NewBitmap(),
        },
    }

    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i &lt; b.N; i++ {
        err = sonic.Unmarshal(buf, &amp;f1)
        if err != nil {
            panic(err)
        }
    }
}

func BenchmarkStdJsonEncode(b *testing.B) {
    var f = Foo{
        N: 5,
        RB: MyRB{
            RB: roaring.NewBitmap(),
        },
    }

    for i := 0; i &lt; 3000; i++ {
        f.RB.RB.Add(uint32(i))
    }

    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i &lt; b.N; i++ {
        _, err := json.Marshal(&amp;f)
        if err != nil {
            panic(err)
        }
    }
}

func BenchmarkStdJsonDecode(b *testing.B) {
    var f = Foo{
        N: 5,
        RB: MyRB{
            RB: roaring.NewBitmap(),
        },
    }

    for i := 0; i &lt; 3000; i++ {
        f.RB.RB.Add(uint32(i))
    }

    buf, err := json.Marshal(&amp;f)
    if err != nil {
        panic(err)
    }
    var f1 = Foo{
        RB: MyRB{
            RB: roaring.NewBitmap(),
        },
    }

    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i &lt; b.N; i++ {
        err = json.Unmarshal(buf, &amp;f1)
        if err != nil {
            panic(err)
        }
    }
}
</code></pre>
<p>执行这个benchmark：</p>
<pre><code>$go test -bench .
goos: darwin
goarch: amd64
pkg: demo
... ...
BenchmarkSonicJsonEncode-8         71176         16331 ns/op       49218 B/op         13 allocs/op
BenchmarkSonicJsonDecode-8         85080         13710 ns/op       37236 B/op         11 allocs/op
BenchmarkStdJsonEncode-8           24490         49345 ns/op       47409 B/op         10 allocs/op
BenchmarkStdJsonDecode-8           20083         59593 ns/op       29000 B/op         15 allocs/op
PASS
ok      demo    6.166s
</code></pre>
<p>从我们这个benchmark结果可以看到，sonic要比标准库json包快3-4倍。</p>
<p>本文中代码可以到<a href="https://github.com/bigwhite/experiments/blob/master/roaring-bitmap-examples">这里</a>下载。</p>
<h3>六. 参考资料</h3>
<ul>
<li>Roaring Bitmap : June 2015 report - https://es.slideshare.net/lemire/roaringprezi-49478534</li>
<li>Roaring Bitmap官网 - https://roaringbitmap.org/</li>
<li>Roaring Bitmap Spec - https://github.com/RoaringBitmap/RoaringFormatSpec</li>
<li>Roaring Bitmap Go实现 - https://github.com/RoaringBitmap/roaring</li>
<li>字节跳动的sonic项目 - https://github.com/bytedance/sonic</li>
<li>paper: Consistently faster and smaller compressed bitmaps with Roaring - https://arxiv.org/pdf/1603.06549.pdf</li>
<li>基于Bitmap的精确去重和用户行为分析 - http://ai.baidu.com/forum/topic/show/987701</li>
<li>paper: Roaring Bitmaps: Implementation of an Optimized Software Library - https://arxiv.org/pdf/1709.07821.pdf</li>
</ul>
<hr />
<p><a href="https://wx.zsxq.com/dweb2/index/group/51284458844544">“Gopher部落”知识星球</a>旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！2023年，Gopher部落将进一步聚焦于如何编写雅、地道、可读、可测试的Go代码，关注代码质量并深入理解Go核心技术，并继续加强与星友的互动。欢迎大家加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 - https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<ul>
<li>微博(暂不可用)：https://weibo.com/bigwhite20xx</li>
<li>微博2：https://weibo.com/u/6484441286</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2023, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2023/02/01/serialize-roaring-bitmap-to-json/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用Go开发Kubernetes Operator：基本结构</title>
		<link>https://tonybai.com/2022/08/15/developing-kubernetes-operators-in-go-part1/</link>
		<comments>https://tonybai.com/2022/08/15/developing-kubernetes-operators-in-go-part1/#comments</comments>
		<pubDate>Mon, 15 Aug 2022 14:47:40 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[builder]]></category>
		<category><![CDATA[cluster]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[controller]]></category>
		<category><![CDATA[coreos]]></category>
		<category><![CDATA[CR]]></category>
		<category><![CDATA[CRD]]></category>
		<category><![CDATA[CustomResourceDefinition]]></category>
		<category><![CDATA[DaemonSet]]></category>
		<category><![CDATA[deployment]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[etcd]]></category>
		<category><![CDATA[framework]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[image]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[kubebuilder]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Make]]></category>
		<category><![CDATA[Makefile]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[operator]]></category>
		<category><![CDATA[operator-framework]]></category>
		<category><![CDATA[operator-sdk]]></category>
		<category><![CDATA[pod]]></category>
		<category><![CDATA[prometheus]]></category>
		<category><![CDATA[reconcile]]></category>
		<category><![CDATA[reconciliation]]></category>
		<category><![CDATA[Redhat]]></category>
		<category><![CDATA[replicas]]></category>
		<category><![CDATA[ReplicaSet]]></category>
		<category><![CDATA[resource]]></category>
		<category><![CDATA[role]]></category>
		<category><![CDATA[role-binding]]></category>
		<category><![CDATA[scale]]></category>
		<category><![CDATA[SDK]]></category>
		<category><![CDATA[Service]]></category>
		<category><![CDATA[service-account]]></category>
		<category><![CDATA[spec]]></category>
		<category><![CDATA[TPR]]></category>
		<category><![CDATA[webserver]]></category>
		<category><![CDATA[伸缩]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3638</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2022/08/15/developing-kubernetes-operators-in-go-part1 注：文章首图基于《Kubernetes Operators Explained》修改 几年前，我还称Kubernetes为服务编排和容器调度领域的事实标准，如今K8s已经是这个领域的“霸主”，地位无可撼动。不过，虽然Kubernetes发展演化到今天已经变得非常复杂，但是Kubernetes最初的数据模型、应用模式与扩展方式却依然有效。并且像Operator这样的应用模式和扩展方式日益受到开发者与运维者的欢迎。 我们的平台内部存在有状态(stateful)的后端服务，对有状态的服务的部署和运维是k8s operator的拿手好戏，是时候来研究一下operator了。 一. Operator的优点 kubernetes operator的概念最初来自CoreOS &#8211; 一家被红帽(redhat)收购的容器技术公司。 CoreOS在引入Operator概念的同时，也给出了Operator的第一批参考实现：etcd operator和prometheus operator。 注：etcd于2013年由CoreOS以开源形式发布；prometheus作为首款面向云原生服务的时序数据存储与监控系统，由SoundCloud公司于2012年以开源的形式发布。 下面是CoreOS对Operator这一概念的诠释：Operator在软件中代表了人类的运维操作知识，通过它可以可靠地管理一个应用程序。 图：CoreOS对operator的诠释(截图来自CoreOS官方博客归档) Operator出现的初衷就是用来解放运维人员的，如今Operator也越来越受到云原生运维开发人员的青睐。 那么operator好处究竟在哪里呢？下面示意图对使用Operator和不使用Operator进行了对比： 通过这张图，即便对operator不甚了解，你也能大致感受到operator的优点吧。 我们看到在使用operator的情况下，对有状态应用的伸缩操作(这里以伸缩操作为例，也可以是其他诸如版本升级等对于有状态应用来说的“复杂”操作)，运维人员仅需一个简单的命令即可，运维人员也无需知道k8s内部对有状态应用的伸缩操作的原理是什么。 在没有使用operator的情况下，运维人员需要对有状态应用的伸缩的操作步骤有深刻的认知，并按顺序逐个执行一个命令序列中的命令并检查命令响应，遇到失败的情况时还需要进行重试，直到伸缩成功。 我们看到operator就好比一个内置于k8s中的经验丰富运维人员，时刻监控目标对象的状态，把复杂性留给自己，给运维人员一个简洁的交互接口，同时operator也能降低运维人员因个人原因导致的操作失误的概率。 不过，operator虽好，但开发门槛却不低。开发门槛至少体现在如下几个方面： 对operator概念的理解是基于对k8s的理解的基础之上的，而k8s自从2014年开源以来，变的日益复杂，理解起来需要一定时间投入； 从头手撸operator很verbose，几乎无人这么做，大多数开发者都会去学习相应的开发框架与工具，比如：kubebuilder、operator framework sdk等； operator的能力也有高低之分，operator framework就提出了一个包含五个等级的operator能力模型(CAPABILITY MODEL)，见下图。使用Go开发高能力等级的operator需要对client-go这个kubernetes官方go client库中的API有深入的了解。 图：operator能力模型(截图来自operator framework官网) 当然在这些门槛当中，对operator概念的理解既是基础也是前提，而理解operator的前提又是对kubernetes的诸多概念要有深入理解，尤其是resource、resource type、API、controller以及它们之间的关系。接下来我们就来快速介绍一下这些概念。 二. Kubernetes resource、resource type、API和controller介绍 Kubernetes发展到今天，其本质已经显现： Kubernetes就是一个“数据库”(数据实际持久存储在etcd中)； 其API就是“sql语句”； API设计采用基于resource的Restful风格, resource type是API的端点(endpoint)； 每一类resource(即Resource Type)是一张“表”，Resource Type的spec对应“表结构”信息(schema)； 每张“表”里的一行记录就是一个resource，即该表对应的Resource Type的一个实例(instance)； [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/developing-kubernetes-operators-in-go-part1-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2022/08/15/developing-kubernetes-operators-in-go-part1">本文永久链接</a> &#8211; https://tonybai.com/2022/08/15/developing-kubernetes-operators-in-go-part1</p>
<blockquote>
<p>注：文章首图基于《Kubernetes Operators Explained》修改</p>
</blockquote>
<p><a href="https://tonybai.com/2018/10/17/imooc-course-kubernetes-practice-go-online/">几年前，我还称Kubernetes为服务编排和容器调度领域的事实标准</a>，如今K8s已经是这个领域的“霸主”，地位无可撼动。不过，虽然Kubernetes发展演化到今天已经变得非常复杂，但是Kubernetes最初的数据模型、应用模式与扩展方式却依然有效。并且像<a href="https://kubernetes.io/docs/concepts/extend-kubernetes/operator/">Operator这样的应用模式和扩展方式</a>日益受到开发者与运维者的欢迎。</p>
<p>我们的平台内部存在有状态(stateful)的后端服务，对有状态的服务的部署和运维是k8s operator的<strong>拿手好戏</strong>，是时候来研究一下operator了。</p>
<h3>一. Operator的优点</h3>
<p><a href="https://web.archive.org/web/20170129131616/https://coreos.com/blog/introducing-operators.html">kubernetes operator的概念最初来自CoreOS</a> &#8211; 一家被红帽(redhat)收购的容器技术公司。</p>
<p>CoreOS在引入Operator概念的同时，也给出了Operator的第一批参考实现：<a href="https://web.archive.org/web/20170224100544/https://coreos.com/blog/introducing-the-etcd-operator.html">etcd operator</a>和<a href="https://web.archive.org/web/20170224101137/https://coreos.com/blog/the-prometheus-operator.html">prometheus operator</a>。</p>
<blockquote>
<p>注：<a href="https://etcd.io">etcd</a>于2013年由CoreOS以开源形式发布；<a href="https://prometheus.io">prometheus</a>作为首款面向云原生服务的时序数据存储与监控系统，由SoundCloud公司于2012年以开源的形式发布。</p>
</blockquote>
<p>下面是CoreOS对Operator这一概念的诠释：<strong>Operator在软件中代表了人类的运维操作知识，通过它可以可靠地管理一个应用程序</strong>。</p>
<p><img src="https://tonybai.com/wp-content/uploads/developing-kubernetes-operators-in-go-part1-4.png" alt="" /><br />
<center>图：CoreOS对operator的诠释(截图来自CoreOS官方博客归档)</center></p>
<p>Operator出现的初衷就是用来解放运维人员的，如今Operator也越来越受到云原生运维开发人员的青睐。</p>
<p>那么operator好处究竟在哪里呢？下面示意图对使用Operator和不使用Operator进行了对比：</p>
<p><img src="https://tonybai.com/wp-content/uploads/developing-kubernetes-operators-in-go-part1-2.png" alt="" /></p>
<p>通过这张图，即便对operator不甚了解，你也能大致感受到operator的优点吧。</p>
<p>我们看到在使用operator的情况下，对有状态应用的伸缩操作(这里以伸缩操作为例，也可以是其他诸如版本升级等对于有状态应用来说的“复杂”操作)，运维人员仅需一个简单的命令即可，运维人员也无需知道k8s内部对有状态应用的伸缩操作的原理是什么。</p>
<p>在没有使用operator的情况下，运维人员需要对有状态应用的伸缩的操作步骤有深刻的认知，并按顺序逐个执行一个命令序列中的命令并检查命令响应，遇到失败的情况时还需要进行重试，直到伸缩成功。</p>
<p>我们看到operator就好比一个内置于k8s中的经验丰富运维人员，时刻监控目标对象的状态，把复杂性留给自己，给运维人员一个简洁的交互接口，同时operator也能降低运维人员因个人原因导致的操作失误的概率。</p>
<p>不过，operator虽好，但开发门槛却不低。开发门槛至少体现在如下几个方面：</p>
<ul>
<li>对operator概念的理解是基于对k8s的理解的基础之上的，而k8s自从2014年开源以来，变的日益复杂，理解起来需要一定时间投入；</li>
<li>从头手撸operator很verbose，几乎无人这么做，大多数开发者都会去学习相应的开发框架与工具，比如：<a href="https://github.com/kubernetes-sigs/kubebuilder">kubebuilder</a>、<a href="https://sdk.operatorframework.io">operator framework sdk</a>等；</li>
<li>operator的能力也有高低之分，operator framework就提出了一个包含<strong>五个等级的operator能力模型(CAPABILITY MODEL)</strong>，见下图。使用Go开发高能力等级的operator需要对<a href="https://github.com/kubernetes/client-go">client-go</a>这个kubernetes官方go client库中的API有深入的了解。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/developing-kubernetes-operators-in-go-part1-3.png" alt="" /><br />
<center>图：operator能力模型(截图来自operator framework官网)</center></p>
<p>当然在这些门槛当中，对operator概念的理解既是基础也是前提，而理解operator的前提又是对kubernetes的诸多概念要有深入理解，尤其是resource、resource type、API、controller以及它们之间的关系。接下来我们就来快速介绍一下这些概念。</p>
<h3>二. Kubernetes resource、resource type、API和controller介绍</h3>
<p>Kubernetes发展到今天，其本质已经显现：</p>
<ul>
<li>Kubernetes就是一个“数据库”(数据实际持久存储在etcd中)；</li>
<li>其API就是“sql语句”；</li>
<li>API设计采用基于resource的Restful风格, resource type是API的端点(endpoint)；</li>
<li>每一类resource(即Resource Type)是一张“表”，Resource Type的spec对应“表结构”信息(schema)；</li>
<li>每张“表”里的一行记录就是一个resource，即该表对应的Resource Type的一个实例(instance)；</li>
<li>Kubernetes这个“数据库”内置了很多“表”，比如Pod、Deployment、DaemonSet、ReplicaSet等；</li>
</ul>
<p>下面是一个Kubernetes API与resource关系的示意图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/developing-kubernetes-operators-in-go-part1-5.png" alt="" /></p>
<p>我们看到resource type有两类，一类的namespace相关的(namespace-scoped)，我们通过下面形式的API操作这类resource type的实例：</p>
<pre><code>VERB /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE - 操作某特定namespace下面的resouce type中的resource实例集合
VERB /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME - 操作某特定namespace下面的resource type中的某个具体的resource实例
</code></pre>
<p>另外一类则是namespace无关，即cluster范围(cluster-scoped)的，我们通过下面形式的API对这类resource type的实例进行操作：</p>
<pre><code>VERB /apis/GROUP/VERSION/RESOURCETYPE - 操作resouce type中的resource实例集合
VERB /apis/GROUP/VERSION/RESOURCETYPE/NAME - 操作resource type中的某个具体的resource实例
</code></pre>
<p>我们知道Kubernetes并非真的只是一个“数据库”，它是服务编排和容器调度的平台标准，它的基本调度单元是Pod(也是一个resource type)，即一组容器的集合。那么Pod又是如何被创建、更新和删除的呢？这就离不开控制器(controller)了。<strong>每一类resource type都有自己对应的控制器(controller)</strong>。以pod这个resource type为例，它的controller为ReplicasSet的实例。</p>
<p>控制器的运行逻辑如下图所示：</p>
<p><img src="https://tonybai.com/wp-content/uploads/developing-kubernetes-operators-in-go-part1-6.png" alt="" /><br />
<center>图：控制器运行逻辑(引自《Kubernetes Operators Explained》一文)</center></p>
<p>控制器一旦启动，将尝试获得resource的当前状态(current state)，并与存储在k8s中的resource的期望状态（desired state，即spec)做比对，如果不一致，controller就会调用相应API进行调整，尽力使得current state与期望状态达成一致。这个达成一致的过程被称为<strong>协调(reconciliation)</strong>，协调过程的伪代码逻辑如下：</p>
<pre><code>for {
    desired := getDesiredState()
    current := getCurrentState()
    makeChanges(desired, current)
}
</code></pre>
<blockquote>
<p>注：k8s中有一个object的概念？那么object是什么呢？它类似于Java Object基类或Ruby中的Object超类。不仅resource type的实例resource是一个(is-a)object，resource type本身也是一个object，它是kubernetes concept的实例。</p>
</blockquote>
<p>有了上面对k8s这些概念的初步理解，我们下面就来理解一下Operator究竟是什么！</p>
<h3>三. Operator模式 = 操作对象(CRD) + 控制逻辑(controller)</h3>
<p>如果让运维人员直面这些内置的resource type(如deployment、pod等)，也就是前面“使用operator vs. 不使用operator”对比图中的第二种情况, 运维人员面临的情况将会很复杂，且操作易错。</p>
<p>那么如果不直面内置的resource type，那么我们如何自定义resource type呢, Kubernetes提供了Custom Resource Definition，CRD(在coreos刚提出operator概念的时候，crd的前身是Third Party Resource, TPR)可以用于自定义resource type。</p>
<p>根据前面我们对resource type理解，定义CRD相当于建立新“表”(resource type)，一旦CRD建立，k8s会为我们自动生成对应CRD的API endpoint，我们就可以通过yaml或API来操作这个“表”。我们可以向“表”中“插入”数据，即基于CRD创建Custom Resource(CR)，这就好比我们创建Deployment实例，向Deployment“表”中插入数据一样。</p>
<p>和原生内置的resource type一样，光有存储对象状态的CR还不够，原生resource type有对应controller负责协调(reconciliation)实例的创建、伸缩与删除，CR也需要这样的“协调者”，即我们也需要定义一个controller来负责监听CR状态并管理CR创建、伸缩、删除以及保持期望状态(spec)与当前状态(current state)的一致。这个controller不再是面向原生Resource type的实例，而是<strong>面向CRD的实例CR的controller</strong>。</p>
<p>有了自定义的操作对象类型(CRD)，有了面向操作对象类型实例的controller，我们将其打包为一个概念：“Operator模式”，operator模式中的controller也被称为operator，它是在集群中对CR进行维护操作的主体。</p>
<h3>四. 使用kubebuilder开发webserver operator</h3>
<blockquote>
<p>假设：此时你的本地开发环境已经具备访问实验用k8s环境的一切配置，通过kubectl工具可以任意操作k8s。</p>
</blockquote>
<p><strong>再深入浅出的概念讲解都不如一次实战对理解概念更有帮助</strong>，下面我们就来开发一个简单的Operator。</p>
<p>前面提过operator开发非常verbose，因此社区提供了开发工具和框架来帮助开发人员简化开发过程，目前主流的包括operator framework sdk和kubebuilder，前者是redhat开源并维护的一套工具，支持使用go、ansible、helm进行operator开发(其中只有go可以开发到能力级别5的operator，其他两种则不行)；而kubebuilder则是kubernetes官方的一个sig(特别兴趣小组)维护的operator开发工具。目前基于operator framework sdk和go进行operator开发时，operator sdk底层使用的也是kubebuilder，所以这里我们就直接使用kubebuilder来开发operator。</p>
<p>按照operator能力模型，我们这个operator差不多处于2级这个层次，我们定义一个Webserver的resource type，它代表的是一个基于nginx的webserver集群，我们的operator支持创建webserver示例(一个nginx集群)，支持nginx集群伸缩，支持集群中nginx的版本升级。</p>
<p>下面我们就用kubebuilder来实现这个operator！</p>
<h4>1. 安装kubebuilder</h4>
<p>这里我们采用源码构建方式安装，步骤如下：</p>
<pre><code>$git clone git@github.com:kubernetes-sigs/kubebuilder.git
$cd kubebuilder
$make
$cd bin
$./kubebuilder version
Version: main.version{KubeBuilderVersion:"v3.5.0-101-g5c949c2e",
KubernetesVendor:"unknown",
GitCommit:"5c949c2e50ca8eec80d64878b88e1b2ee30bf0bc",
BuildDate:"2022-08-06T09:12:50Z", GoOs:"linux", GoArch:"amd64"}
</code></pre>
<p>然后将bin/kubebuilder拷贝到你的PATH环境变量中的某个路径下即可。</p>
<h4>2. 创建webserver-operator工程</h4>
<p>接下来，我们就可以使用kubebuilder创建webserver-operator工程了：</p>
<pre><code>$mkdir webserver-operator
$cd webserver-operator
$kubebuilder init  --repo github.com/bigwhite/webserver-operator --project-name webserver-operator

Writing kustomize manifests for you to edit...
Writing scaffold for you to edit...
Get controller runtime:
$ go get sigs.k8s.io/controller-runtime@v0.12.2
go: downloading k8s.io/client-go v0.24.2
go: downloading k8s.io/component-base v0.24.2
Update dependencies:
$ go mod tidy
Next: define a resource with:
kubebuilder create api
</code></pre>
<blockquote>
<p>注：&#8211;repo指定go.mod中的module root path，你可以定义你自己的module root path。</p>
</blockquote>
<h4>3. 创建API，生成初始CRD</h4>
<p>Operator包括CRD和controller，这里我们就来建立自己的CRD，即自定义的resource type，也就是API的endpoint，我们使用下面kubebuilder create命令来完成这个步骤：</p>
<pre><code>$kubebuilder create api --version v1 --kind WebServer
Create Resource [y/n]
y
Create Controller [y/n]
y
Writing kustomize manifests for you to edit...
Writing scaffold for you to edit...
api/v1/webserver_types.go
controllers/webserver_controller.go
Update dependencies:
$ go mod tidy
Running make:
$ make generate
mkdir -p /home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin
test -s /home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin/controller-gen || GOBIN=/home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.9.2
/home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
Next: implement your new API and generate the manifests (e.g. CRDs,CRs) with:
$ make manifests
</code></pre>
<p>之后，我们执行make manifests来生成最终CRD对应的yaml文件：</p>
<pre><code>$make manifests
/home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
</code></pre>
<p>此刻，整个工程的目录文件布局如下：</p>
<pre><code>$tree -F .
.
├── api/
│   └── v1/
│       ├── groupversion_info.go
│       ├── webserver_types.go
│       └── zz_generated.deepcopy.go
├── bin/
│   └── controller-gen*
├── config/
│   ├── crd/
│   │   ├── bases/
│   │   │   └── my.domain_webservers.yaml
│   │   ├── kustomization.yaml
│   │   ├── kustomizeconfig.yaml
│   │   └── patches/
│   │       ├── cainjection_in_webservers.yaml
│   │       └── webhook_in_webservers.yaml
│   ├── default/
│   │   ├── kustomization.yaml
│   │   ├── manager_auth_proxy_patch.yaml
│   │   └── manager_config_patch.yaml
│   ├── manager/
│   │   ├── controller_manager_config.yaml
│   │   ├── kustomization.yaml
│   │   └── manager.yaml
│   ├── prometheus/
│   │   ├── kustomization.yaml
│   │   └── monitor.yaml
│   ├── rbac/
│   │   ├── auth_proxy_client_clusterrole.yaml
│   │   ├── auth_proxy_role_binding.yaml
│   │   ├── auth_proxy_role.yaml
│   │   ├── auth_proxy_service.yaml
│   │   ├── kustomization.yaml
│   │   ├── leader_election_role_binding.yaml
│   │   ├── leader_election_role.yaml
│   │   ├── role_binding.yaml
│   │   ├── role.yaml
│   │   ├── service_account.yaml
│   │   ├── webserver_editor_role.yaml
│   │   └── webserver_viewer_role.yaml
│   └── samples/
│       └── _v1_webserver.yaml
├── controllers/
│   ├── suite_test.go
│   └── webserver_controller.go
├── Dockerfile
├── go.mod
├── go.sum
├── hack/
│   └── boilerplate.go.txt
├── main.go
├── Makefile
├── PROJECT
└── README.md

14 directories, 40 files
</code></pre>
<h4>4. webserver-operator的基本结构</h4>
<p>忽略我们此次不关心的诸如leader election、auth_proxy等，我将这个operator例子的主要部分整理到下面这张图中：</p>
<p><img src="https://tonybai.com/wp-content/uploads/developing-kubernetes-operators-in-go-part1-7.png" alt="" /></p>
<p>图中的各个部分就是使用kubebuilder生成的<strong>operator的基本结构</strong>。</p>
<p>webserver operator主要由CRD和controller组成：</p>
<ul>
<li>CRD</li>
</ul>
<p>图中的左下角的框框就是上面生成的CRD yaml文件：config/crd/bases/my.domain_webservers.yaml。CRD与api/v1/webserver_types.go密切相关。我们在api/v1/webserver_types.go中为CRD定义spec相关字段，之后make manifests命令可以解析webserver_types.go中的变化并更新CRD的yaml文件。</p>
<ul>
<li>controller</li>
</ul>
<p>从图的右侧部分可以看出，controller自身就是作为一个deployment部署在k8s集群中运行的，它监视CRD的实例CR的运行状态，并在Reconcile方法中检查预期状态与当前状态是否一致，如果不一致，则执行相关操作。</p>
<ul>
<li>其它</li>
</ul>
<p>图中左上角是有关controller的权限的设置，controller通过serviceaccount访问k8s API server，通过role.yaml和role_binding.yaml设置controller的角色和权限。</p>
<h4>5. 为CRD spec添加字段(field)</h4>
<p>为了实现Webserver operator的功能目标，我们需要为CRD spec添加一些状态字段。前面说过，CRD与api中的webserver_types.go文件是同步的，我们只需修改webserver_types.go文件即可。我们在WebServerSpec结构体中增加Replicas和Image两个字段，它们分别用于表示webserver实例的副本数量以及使用的容器镜像：</p>
<pre><code>// api/v1/webserver_types.go

// WebServerSpec defines the desired state of WebServer
type WebServerSpec struct {
    // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
    // Important: Run "make" to regenerate code after modifying this file

    // The number of replicas that the webserver should have
    Replicas int `json:"replicas,omitempty"`

    // The container image of the webserver
    Image string `json:"image,omitempty"`

    // Foo is an example field of WebServer. Edit webserver_types.go to remove/update
    Foo string `json:"foo,omitempty"`
}
</code></pre>
<p>保存修改后，<strong>执行make manifests</strong>重新生成config/crd/bases/my.domain_webservers.yaml</p>
<pre><code>$cat my.domain_webservers.yaml
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  annotations:
    controller-gen.kubebuilder.io/version: v0.9.2
  creationTimestamp: null
  name: webservers.my.domain
spec:
  group: my.domain
  names:
    kind: WebServer
    listKind: WebServerList
    plural: webservers
    singular: webserver
  scope: Namespaced
  versions:
  - name: v1
    schema:
      openAPIV3Schema:
        description: WebServer is the Schema for the webservers API
        properties:
          apiVersion:
            description: 'APIVersion defines the versioned schema of this representation
              of an object. Servers should convert recognized schemas to the latest
              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
            type: string
          kind:
            description: 'Kind is a string value representing the REST resource this
              object represents. Servers may infer this from the endpoint the client
              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
            type: string
          metadata:
            type: object
          spec:
            description: WebServerSpec defines the desired state of WebServer
            properties:
              foo:
                description: Foo is an example field of WebServer. Edit webserver_types.go
                  to remove/update
                type: string
              image:
                description: The container image of the webserver
                type: string
              replicas:
                description: The number of replicas that the webserver should have
                type: integer
            type: object
          status:
            description: WebServerStatus defines the observed state of WebServer
            type: object
        type: object
    served: true
    storage: true
    subresources:
      status: {}
</code></pre>
<p>一旦定义完CRD，我们就可以将其安装到k8s中：</p>
<pre><code>$make install
/home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
test -s /home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin/kustomize || { curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash -s -- 3.8.7 /home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin; }
{Version:kustomize/v3.8.7 GitCommit:ad092cc7a91c07fdf63a2e4b7f13fa588a39af4f BuildDate:2020-11-11T23:14:14Z GoOs:linux GoArch:amd64}
kustomize installed to /home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin/kustomize
/home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin/kustomize build config/crd | kubectl apply -f -
customresourcedefinition.apiextensions.k8s.io/webservers.my.domain created
</code></pre>
<p>检查安装情况：</p>
<pre><code>$kubectl get crd|grep webservers
webservers.my.domain                                             2022-08-06T21:55:45Z
</code></pre>
<h4>6. 修改role.yaml</h4>
<p>在开始controller开发之前，我们先来为controller后续的运行“铺平道路”，即设置好相应权限。</p>
<p>我们在controller中会为CRD实例创建对应deployment和service，这样就要求controller有操作deployments和services的权限，这样就需要我们修改role.yaml，增加service account:  controller-manager 操作deployments和services的权限：</p>
<pre><code>// config/rbac/role.yaml
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  creationTimestamp: null
  name: manager-role
rules:
- apiGroups:
  - my.domain
  resources:
  - webservers
  verbs:
  - create
  - delete
  - get
  - list
  - patch
  - update
  - watch
- apiGroups:
  - my.domain
  resources:
  - webservers/finalizers
  verbs:
  - update
- apiGroups:
  - my.domain
  resources:
  - webservers/status
  verbs:
  - get
  - patch
  - update
- apiGroups:
  - apps
  resources:
  - deployments
  verbs:
  - create
  - delete
  - get
  - list
  - patch
  - update
  - watch
- apiGroups:
  - apps
  - ""
  resources:
  - services
  verbs:
  - create
  - delete
  - get
  - list
  - patch
  - update
  - watch
</code></pre>
<p>修改后的role.yaml先放在这里，后续与controller一并部署到k8s上。</p>
<h4>7. 实现controller的Reconcile(协调)逻辑</h4>
<p>kubebuilder为我们搭好了controller的代码架子，我们只需要在controllers/webserver_controller.go中实现WebServerReconciler的Reconcile方法即可。下面是Reconcile的一个简易流程图，结合这幅图理解代码就容易的多了：</p>
<p><img src="https://tonybai.com/wp-content/uploads/developing-kubernetes-operators-in-go-part1-8.png" alt="" /></p>
<p>下面是对应的Reconcile方法的代码：</p>
<pre><code>// controllers/webserver_controller.go

func (r *WebServerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    log := r.Log.WithValues("Webserver", req.NamespacedName)

    instance := &amp;mydomainv1.WebServer{}
    err := r.Get(ctx, req.NamespacedName, instance)
    if err != nil {
        if errors.IsNotFound(err) {
            // Request object not found, could have been deleted after reconcile request.
            // Return and don't requeue
            log.Info("Webserver resource not found. Ignoring since object must be deleted")
            return ctrl.Result{}, nil
        }

        // Error reading the object - requeue the request.
        log.Error(err, "Failed to get Webserver")
        return ctrl.Result{RequeueAfter: time.Second * 5}, err
    }

    // Check if the webserver deployment already exists, if not, create a new one
    found := &amp;appsv1.Deployment{}
    err = r.Get(ctx, types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace}, found)
    if err != nil &amp;&amp; errors.IsNotFound(err) {
        // Define a new deployment
        dep := r.deploymentForWebserver(instance)
        log.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
        err = r.Create(ctx, dep)
        if err != nil {
            log.Error(err, "Failed to create new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
            return ctrl.Result{RequeueAfter: time.Second * 5}, err
        }
        // Deployment created successfully - return and requeue
        return ctrl.Result{Requeue: true}, nil
    } else if err != nil {
        log.Error(err, "Failed to get Deployment")
        return ctrl.Result{RequeueAfter: time.Second * 5}, err
    }

    // Ensure the deployment replicas and image are the same as the spec
    var replicas int32 = int32(instance.Spec.Replicas)
    image := instance.Spec.Image

    var needUpd bool
    if *found.Spec.Replicas != replicas {
        log.Info("Deployment spec.replicas change", "from", *found.Spec.Replicas, "to", replicas)
        found.Spec.Replicas = &amp;replicas
        needUpd = true
    }

    if (*found).Spec.Template.Spec.Containers[0].Image != image {
        log.Info("Deployment spec.template.spec.container[0].image change", "from", (*found).Spec.Template.Spec.Containers[0].Image, "to", image)
        found.Spec.Template.Spec.Containers[0].Image = image
        needUpd = true
    }

    if needUpd {
        err = r.Update(ctx, found)
        if err != nil {
            log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name)
            return ctrl.Result{RequeueAfter: time.Second * 5}, err
        }
        // Spec updated - return and requeue
        return ctrl.Result{Requeue: true}, nil
    }

    // Check if the webserver service already exists, if not, create a new one
    foundService := &amp;corev1.Service{}
    err = r.Get(ctx, types.NamespacedName{Name: instance.Name + "-service", Namespace: instance.Namespace}, foundService)
    if err != nil &amp;&amp; errors.IsNotFound(err) {
        // Define a new service
        srv := r.serviceForWebserver(instance)
        log.Info("Creating a new Service", "Service.Namespace", srv.Namespace, "Service.Name", srv.Name)
        err = r.Create(ctx, srv)
        if err != nil {
            log.Error(err, "Failed to create new Servie", "Service.Namespace", srv.Namespace, "Service.Name", srv.Name)
            return ctrl.Result{RequeueAfter: time.Second * 5}, err
        }
        // Service created successfully - return and requeue
        return ctrl.Result{Requeue: true}, nil
    } else if err != nil {
        log.Error(err, "Failed to get Service")
        return ctrl.Result{RequeueAfter: time.Second * 5}, err
    }

    // Tbd: Ensure the service state is the same as the spec, your homework

    // reconcile webserver operator in again 10 seconds
    return ctrl.Result{RequeueAfter: time.Second * 10}, nil
}
</code></pre>
<p>这里大家可能发现了：<strong>原来CRD的controller最终还是将CR翻译为k8s原生Resource，比如service、deployment等。CR的状态变化(比如这里的replicas、image等)最终都转换成了deployment等原生resource的update操作</strong>，这就是operator的精髓！理解到这一层，operator对大家来说就不再是什么密不可及的概念了。</p>
<p>有些朋友可能也会发现，上面流程图中似乎没有考虑CR实例被删除时对deployment、service的操作，的确如此。不过对于一个7&#215;24小时运行于后台的服务来说，我们更多关注的是其变更、伸缩、升级等操作，删除是优先级最低的需求。</p>
<h4>8. 构建controller image</h4>
<p>controller代码写完后，我们就来构建controller的image。通过前文我们知道，这个controller其实就是运行在k8s中的一个deployment下的pod。我们需要构建其image并通过deployment部署到k8s中。</p>
<p>kubebuilder创建的operator工程中包含了Makefile，通过make docker-build即可构建controller image。docker-build使用golang builder image来构建controller源码，不过如果不对Dockerfile稍作修改，你很难编译过去，因为默认GOPROXY在国内无法访问。这里最简单的改造方式是使用vendor构建，下面是改造后的Dockerfile：</p>
<pre><code># Build the manager binary
FROM golang:1.18 as builder

ENV GOPROXY https://goproxy.cn
WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
COPY vendor/ vendor/
# cache deps before building and copying source so that we don't need to re-download as much
# and so that source changes don't invalidate our downloaded layer
#RUN go mod download

# Copy the go source
COPY main.go main.go
COPY api/ api/
COPY controllers/ controllers/

# Build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -mod=vendor -a -o manager main.go

# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
#FROM gcr.io/distroless/static:nonroot
FROM katanomi/distroless-static:nonroot
WORKDIR /
COPY --from=builder /workspace/manager .
USER 65532:65532

ENTRYPOINT ["/manager"]
</code></pre>
<p>下面是构建的步骤：</p>
<pre><code>$go mod vendor
$make docker-build

test -s /home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin/controller-gen || GOBIN=/home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.9.2
/home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
/home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
KUBEBUILDER_ASSETS="/home/tonybai/.local/share/kubebuilder-envtest/k8s/1.24.2-linux-amd64" go test ./... -coverprofile cover.out
?       github.com/bigwhite/webserver-operator    [no test files]
?       github.com/bigwhite/webserver-operator/api/v1    [no test files]
ok      github.com/bigwhite/webserver-operator/controllers    4.530s    coverage: 0.0% of statements
docker build -t bigwhite/webserver-controller:latest .
Sending build context to Docker daemon  47.51MB
Step 1/15 : FROM golang:1.18 as builder
 ---&gt; 2d952adaec1e
Step 2/15 : ENV GOPROXY https://goproxy.cn
 ---&gt; Using cache
 ---&gt; db2b06a078e3
Step 3/15 : WORKDIR /workspace
 ---&gt; Using cache
 ---&gt; cc3c613c19c6
Step 4/15 : COPY go.mod go.mod
 ---&gt; Using cache
 ---&gt; 5fa5c0d89350
Step 5/15 : COPY go.sum go.sum
 ---&gt; Using cache
 ---&gt; 71669cd0fe8e
Step 6/15 : COPY vendor/ vendor/
 ---&gt; Using cache
 ---&gt; 502b280a0e67
Step 7/15 : COPY main.go main.go
 ---&gt; Using cache
 ---&gt; 0c59a69091bb
Step 8/15 : COPY api/ api/
 ---&gt; Using cache
 ---&gt; 2b81131c681f
Step 9/15 : COPY controllers/ controllers/
 ---&gt; Using cache
 ---&gt; e3fd48c88ccb
Step 10/15 : RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -mod=vendor -a -o manager main.go
 ---&gt; Using cache
 ---&gt; 548ac10321a2
Step 11/15 : FROM katanomi/distroless-static:nonroot
 ---&gt; 421f180b71d8
Step 12/15 : WORKDIR /
 ---&gt; Running in ea7cb03027c0
Removing intermediate container ea7cb03027c0
 ---&gt; 9d3c0ea19c3b
Step 13/15 : COPY --from=builder /workspace/manager .
 ---&gt; a4387fe33ab7
Step 14/15 : USER 65532:65532
 ---&gt; Running in 739a32d251b6
Removing intermediate container 739a32d251b6
 ---&gt; 52ae8742f9c5
Step 15/15 : ENTRYPOINT ["/manager"]
 ---&gt; Running in 897893b0c9df
Removing intermediate container 897893b0c9df
 ---&gt; e375cc2adb08
Successfully built e375cc2adb08
Successfully tagged bigwhite/webserver-controller:latest
</code></pre>
<blockquote>
<p>注：执行make命令之前，先将Makefile中的IMG变量初值改为IMG ?= bigwhite/webserver-controller:latest</p>
</blockquote>
<p>构建成功后，执行make docker-push将image推送到镜像仓库中(这里使用了docker公司提供的公共仓库)。</p>
<h4>9. 部署controller</h4>
<p>之前我们已经通过make install将CRD安装到k8s中了，接下来再把controller部署到k8s上，我们的operator就算部署完毕了。执行make deploy即可实现部署：</p>
<pre><code>$make deploy
test -s /home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin/controller-gen || GOBIN=/home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.9.2
/home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
test -s /home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin/kustomize || { curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash -s -- 3.8.7 /home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin; }
cd config/manager &amp;&amp; /home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin/kustomize edit set image controller=bigwhite/webserver-controller:latest
/home/tonybai/test/go/operator/kubebuilder/webserver-operator/bin/kustomize build config/default | kubectl apply -f -
namespace/webserver-operator-system created
customresourcedefinition.apiextensions.k8s.io/webservers.my.domain unchanged
serviceaccount/webserver-operator-controller-manager created
role.rbac.authorization.k8s.io/webserver-operator-leader-election-role created
clusterrole.rbac.authorization.k8s.io/webserver-operator-manager-role created
clusterrole.rbac.authorization.k8s.io/webserver-operator-metrics-reader created
clusterrole.rbac.authorization.k8s.io/webserver-operator-proxy-role created
rolebinding.rbac.authorization.k8s.io/webserver-operator-leader-election-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/webserver-operator-manager-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/webserver-operator-proxy-rolebinding created
configmap/webserver-operator-manager-config created
service/webserver-operator-controller-manager-metrics-service created
deployment.apps/webserver-operator-controller-manager created
</code></pre>
<p>我们看到deploy不仅会安装controller、serviceaccount、role、rolebinding，它还会创建namespace，也会将crd安装一遍。也就是说deploy是一个完整的operator安装命令。</p>
<blockquote>
<p>注：使用make undeploy可以完整卸载operator相关resource。</p>
</blockquote>
<p>我们用kubectl logs查看一下controller的运行日志：</p>
<pre><code>$kubectl logs -f deployment.apps/webserver-operator-controller-manager -n webserver-operator-system
1.6600280818476188e+09    INFO    controller-runtime.metrics    Metrics server is starting to listen    {"addr": "127.0.0.1:8080"}
1.6600280818478029e+09    INFO    setup    starting manager
1.6600280818480284e+09    INFO    Starting server    {"path": "/metrics", "kind": "metrics", "addr": "127.0.0.1:8080"}
1.660028081848097e+09    INFO    Starting server    {"kind": "health probe", "addr": "[::]:8081"}
I0809 06:54:41.848093       1 leaderelection.go:248] attempting to acquire leader lease webserver-operator-system/63e5a746.my.domain...
I0809 06:54:57.072336       1 leaderelection.go:258] successfully acquired lease webserver-operator-system/63e5a746.my.domain
1.6600280970724037e+09    DEBUG    events    Normal    {"object": {"kind":"Lease","namespace":"webserver-operator-system","name":"63e5a746.my.domain","uid":"e05aaeb5-4a3a-4272-b036-80d61f0b6788","apiVersion":"coordination.k8s.io/v1","resourceVersion":"5238800"}, "reason": "LeaderElection", "message": "webserver-operator-controller-manager-6f45bc88f7-ptxlc_0e960015-9fbe-466d-a6b1-ff31af63a797 became leader"}
1.6600280970724993e+09    INFO    Starting EventSource    {"controller": "webserver", "controllerGroup": "my.domain", "controllerKind": "WebServer", "source": "kind source: *v1.WebServer"}
1.6600280970725305e+09    INFO    Starting Controller    {"controller": "webserver", "controllerGroup": "my.domain", "controllerKind": "WebServer"}
1.660028097173026e+09    INFO    Starting workers    {"controller": "webserver", "controllerGroup": "my.domain", "controllerKind": "WebServer", "worker count": 1}
</code></pre>
<p>可以看到，controller已经成功启动，正在等待一个WebServer CR的相关事件(比如创建)！下面我们就来创建一个WebServer CR!</p>
<h4>10. 创建WebServer CR</h4>
<p>webserver-operator项目中有一个CR sample，位于config/samples下面，我们对其进行改造，添加我们在spec中加入的字段：</p>
<pre><code>// config/samples/_v1_webserver.yaml 

apiVersion: my.domain/v1
kind: WebServer
metadata:
  name: webserver-sample
spec:
  # TODO(user): Add fields here
  image: nginx:1.23.1
  replicas: 3
</code></pre>
<p>我们通过kubectl创建该WebServer CR：</p>
<pre><code>$cd config/samples
$kubectl apply -f _v1_webserver.yaml
webserver.my.domain/webserver-sample created
</code></pre>
<p>观察controller的日志：</p>
<pre><code>1.6602084232243123e+09  INFO    controllers.WebServer   Creating a new Deployment   {"Webserver": "default/webserver-sample", "Deployment.Namespace": "default", "Deployment.Name": "webserver-sample"}
1.6602084233446114e+09  INFO    controllers.WebServer   Creating a new Service  {"Webserver": "default/webserver-sample", "Service.Namespace": "default", "Service.Name": "webserver-sample-service"}
</code></pre>
<p>我们看到当CR被创建后，controller监听到相关事件，创建了对应的Deployment和service，我们查看一下为CR创建的Deployment、三个Pod以及service：</p>
<pre><code>$kubectl get service
NAME                       TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes                 ClusterIP   172.26.0.1     &lt;none&gt;        443/TCP        22d
webserver-sample-service   NodePort    172.26.173.0   &lt;none&gt;        80:30010/TCP   2m58s

$kubectl get deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
webserver-sample   3/3     3            3           4m44s

$kubectl get pods
NAME                               READY   STATUS    RESTARTS   AGE
webserver-sample-bc698b9fb-8gq2h   1/1     Running   0          4m52s
webserver-sample-bc698b9fb-vk6gw   1/1     Running   0          4m52s
webserver-sample-bc698b9fb-xgrgb   1/1     Running   0          4m52s
</code></pre>
<p>我们访问一下该服务：</p>
<pre><code>$curl http://192.168.10.182:30010
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Welcome to nginx!&lt;/title&gt;
&lt;style&gt;
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Welcome to nginx!&lt;/h1&gt;
&lt;p&gt;If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.&lt;/p&gt;

&lt;p&gt;For online documentation and support please refer to
&lt;a href="http://nginx.org/"&gt;nginx.org&lt;/a&gt;.&lt;br/&gt;
Commercial support is available at
&lt;a href="http://nginx.com/"&gt;nginx.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thank you for using nginx.&lt;/em&gt;&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>服务如预期返回响应！</p>
<h4>11. 伸缩、变更版本和Service自愈</h4>
<p>接下来我们来对CR做一些常见的运维操作。</p>
<ul>
<li>副本数由3变为4</li>
</ul>
<p>我们将CR的replicas由3改为4，对容器实例做一次扩展操作：</p>
<pre><code>// config/samples/_v1_webserver.yaml 

apiVersion: my.domain/v1
kind: WebServer
metadata:
  name: webserver-sample
spec:
  # TODO(user): Add fields here
  image: nginx:1.23.1
  replicas: 4
</code></pre>
<p>然后通过kubectl apply使之生效：</p>
<pre><code>$kubectl apply -f _v1_webserver.yaml
webserver.my.domain/webserver-sample configured
</code></pre>
<p>上述命令执行后，我们观察到operator的controller日志如下：</p>
<pre><code>1.660208962767797e+09   INFO    controllers.WebServer   Deployment spec.replicas change {"Webserver": "default/webserver-sample", "from": 3, "to": 4}
</code></pre>
<p>稍后，查看pod数量：</p>
<pre><code>$kubectl get pods
NAME                               READY   STATUS    RESTARTS   AGE
webserver-sample-bc698b9fb-8gq2h   1/1     Running   0          9m41s
webserver-sample-bc698b9fb-v9gvg   1/1     Running   0          42s
webserver-sample-bc698b9fb-vk6gw   1/1     Running   0          9m41s
webserver-sample-bc698b9fb-xgrgb   1/1     Running   0          9m41s
</code></pre>
<p>webserver pod副本数量成功从3扩为4。</p>
<ul>
<li>变更webserver image版本</li>
</ul>
<p>我们将CR的image的版本从nginx:1.23.1改为nginx:1.23.0，然后执行kubectl apply使之生效。</p>
<p>我们查看controller的响应日志如下：</p>
<pre><code>1.6602090494113188e+09  INFO    controllers.WebServer   Deployment spec.template.spec.container[0].image change {"Webserver": "default/webserver-sample", "from": "nginx:1.23.1", "to": "nginx:1.23.0"}
</code></pre>
<p>controller会更新deployment，导致所辖pod进行滚动升级：</p>
<pre><code>$kubectl get pods
NAME                               READY   STATUS              RESTARTS   AGE
webserver-sample-bc698b9fb-8gq2h   1/1     Running             0          10m
webserver-sample-bc698b9fb-vk6gw   1/1     Running             0          10m
webserver-sample-bc698b9fb-xgrgb   1/1     Running             0          10m
webserver-sample-ffcf549ff-g6whk   0/1     ContainerCreating   0          12s
webserver-sample-ffcf549ff-ngjz6   0/1     ContainerCreating   0          12s
</code></pre>
<p>耐心等一小会儿，最终的pod列表为：</p>
<pre><code>$kubectl get pods
NAME                               READY   STATUS    RESTARTS   AGE
webserver-sample-ffcf549ff-g6whk   1/1     Running   0          6m22s
webserver-sample-ffcf549ff-m6z24   1/1     Running   0          3m12s
webserver-sample-ffcf549ff-ngjz6   1/1     Running   0          6m22s
webserver-sample-ffcf549ff-t7gvc   1/1     Running   0          4m16s
</code></pre>
<ul>
<li>service自愈：恢复被无删除的Service</li>
</ul>
<p>我们来一次“误操作”，将webserver-sample-service删除，看看controller能否帮助service自愈：</p>
<pre><code>$kubectl delete service/webserver-sample-service
service "webserver-sample-service" deleted
</code></pre>
<p>查看controller日志：</p>
<pre><code>1.6602096994710526e+09  INFO    controllers.WebServer   Creating a new Service  {"Webserver": "default/webserver-sample", "Service.Namespace": "default", "Service.Name": "webserver-sample-service"}
</code></pre>
<p>我们看到controller检测到了service被删除的状态，并重建了一个新service！</p>
<p>访问新建的service：</p>
<pre><code>$curl http://192.168.10.182:30010
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Welcome to nginx!&lt;/title&gt;
&lt;style&gt;
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Welcome to nginx!&lt;/h1&gt;
&lt;p&gt;If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.&lt;/p&gt;

&lt;p&gt;For online documentation and support please refer to
&lt;a href="http://nginx.org/"&gt;nginx.org&lt;/a&gt;.&lt;br/&gt;
Commercial support is available at
&lt;a href="http://nginx.com/"&gt;nginx.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thank you for using nginx.&lt;/em&gt;&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>可以看到service在controller的帮助下完成了自愈！</p>
<h3>五. 小结</h3>
<p>本文对Kubernetes Operator的概念以及优点做了初步的介绍，并基于kubebuilder这个工具开发了一个具有2级能力的operator。当然这个operator离完善还有很远的距离，其主要目的还是帮助大家理解operator的概念以及实现套路。</p>
<p>相信你阅读完本文后，对operator，尤其是其基本结构会有一个较为清晰的了解，并具备开发简单operator的能力！</p>
<p>文中涉及的源码可以在<a href="https://github.com/bigwhite/experiments/tree/master/webserver-operator">这里</a>下载 &#8211; https://github.com/bigwhite/experiments/tree/master/webserver-operator。</p>
<h3>六. 参考资料</h3>
<ul>
<li>kubernetes operator 101, Part 1: Overview and key features &#8211; https://developers.redhat.com/articles/2021/06/11/kubernetes-operators-101-part-1-overview-and-key-features</li>
<li>Kubernetes Operators 101, Part 2: How operators work &#8211; https://developers.redhat.com/articles/2021/06/22/kubernetes-operators-101-part-2-how-operators-work</li>
<li>Operator SDK: Build Kubernetes Operators &#8211; https://developers.redhat.com/blog/2020/04/28/operator-sdk-build-kubernetes-operators-and-deploy-them-on-openshift</li>
<li>kubernetes doc: Custom Resources &#8211; https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/</li>
<li>kubernetes doc: Operator pattern &#8211; https://kubernetes.io/docs/concepts/extend-kubernetes/operator/</li>
<li>kubernetes doc: API concepts &#8211; https://kubernetes.io/docs/reference/using-api/api-concepts/</li>
<li>Introducing Operators: Putting Operational Knowledge into Software 第一篇有关operator的文章 by coreos &#8211; https://web.archive.org/web/20170129131616/https://coreos.com/blog/introducing-operators.html</li>
<li>CNCF Operator白皮书v1.0 &#8211; https://github.com/cncf/tag-app-delivery/blob/main/operator-whitepaper/v1/Operator-WhitePaper_v1-0.md</li>
<li>Best practices for building Kubernetes Operators and stateful apps &#8211; https://cloud.google.com/blog/products/containers-kubernetes/best-practices-for-building-kubernetes-operators-and-stateful-apps</li>
<li>A deep dive into Kubernetes controllers &#8211;  https://docs.bitnami.com/tutorials/a-deep-dive-into-kubernetes-controllers</li>
<li>Kubernetes Operators Explained &#8211; https://blog.container-solutions.com/kubernetes-operators-explained</li>
<li>书籍《Kubernetes Operator》 &#8211; https://book.douban.com/subject/34796009/</li>
<li>书籍《Programming Kubernetes》 &#8211; https://book.douban.com/subject/35498478/</li>
<li>Operator SDK Reaches v1.0 &#8211; https://cloud.redhat.com/blog/operator-sdk-reaches-v1.0</li>
<li>What is the difference between kubebuilder and operator-sdk &#8211; https://github.com/operator-framework/operator-sdk/issues/1758</li>
<li>Kubernetes Operators in Depth &#8211; https://www.infoq.com/articles/kubernetes-operators-in-depth/</li>
<li>Get started using Kubernetes Operators &#8211; https://developer.ibm.com/learningpaths/kubernetes-operators/ </li>
<li>Use Kubernetes operators to extend Kubernetes’ functionality &#8211; https://developer.ibm.com/learningpaths/kubernetes-operators/operators-extend-kubernetes/</li>
<li>memcached operator &#8211; https://github.com/operator-framework/operator-sdk-samples/tree/master/go/memcached-operator</li>
</ul>
<hr />
<p><a href="https://wx.zsxq.com/dweb2/index/group/51284458844544">“Gopher部落”知识星球</a>旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！2022年，Gopher部落全面改版，将持续分享Go语言与Go应用领域的知识、技巧与实践，并增加诸多互动形式。欢迎大家加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/。smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<ul>
<li>微博：https://weibo.com/bigwhite20xx</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2022, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2022/08/15/developing-kubernetes-operators-in-go-part1/feed/</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
	</channel>
</rss>
