<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>工程实践 on Zampo Blog</title><link>https://blog.cpdd.fyi/tags/%E5%B7%A5%E7%A8%8B%E5%AE%9E%E8%B7%B5/</link><description>Recent content in 工程实践 on Zampo Blog</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Thu, 23 Apr 2026 20:00:00 +0800</lastBuildDate><atom:link href="https://blog.cpdd.fyi/tags/%E5%B7%A5%E7%A8%8B%E5%AE%9E%E8%B7%B5/index.xml" rel="self" type="application/rss+xml"/><item><title>你的 Docker 镜像为什么有 2GB：从 Namespaces 到 Dockerfile 最佳实践</title><link>https://blog.cpdd.fyi/posts/docker-deep-dive-namespaces-cgroups/</link><pubDate>Thu, 23 Apr 2026 20:00:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/docker-deep-dive-namespaces-cgroups/</guid><description>&lt;p&gt;凌晨三点，线上容器启动要三分钟。&lt;/p&gt;
&lt;p&gt;你登上服务器，&lt;code&gt;docker images&lt;/code&gt; 一看：2.1GB。PM 问为什么这么慢，你说&amp;quot;容器已经很快了&amp;quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;但容器不是虚拟机。你把 Docker 当 VM 用，它当然慢。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;问题不在 Docker，在你没理解它到底是什么。&lt;/p&gt;
&lt;p&gt;很多人以为 Docker 只是把代码和依赖打包进一个盒子，但依然不知道为什么上了生产就崩。你可能正在给一个简单的 Node.js 应用构建 2GB 的镜像，硬编码环境变量，容器启动要三分钟。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;容器不是虚拟机。&lt;/strong&gt; 它不需要 hypervisor，不需要臃肿的 guest OS。它是一个与宿主机内核直接共享的进程。&lt;/p&gt;
&lt;p&gt;看完这篇，你能写出高效 Dockerfile，不再把容器当虚拟机用。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一容器-vs-vm为什么你的镜像这么大"&gt;一、容器 vs VM：为什么你的镜像这么大&lt;/h2&gt;
&lt;p&gt;虚拟机通过 hypervisor 模拟物理硬件。每个 VM 运行独立的操作系统和应用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;VM 有三大痛点：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;痛点&lt;/th&gt;
 &lt;th&gt;表现&lt;/th&gt;
 &lt;th&gt;后果&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;资源税&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;每个 VM 都带着完整的内核&lt;/td&gt;
 &lt;td&gt;10 个 VM 就是 10 份 Linux 内核，内存和 CPU 大量浪费&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;启动延迟&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;启动一个完整操作系统需要几分钟&lt;/td&gt;
 &lt;td&gt;微服务时代根本等不起&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;体积庞大&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;VM 镜像通常几个 GB&lt;/td&gt;
 &lt;td&gt;存储和传输都很慢&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Docker 虚拟化的是操作系统，不是硬件。容器共享宿主机内核，只隔离用户空间的进程、库和依赖。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;容器优势：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;快：通常秒级启动&lt;/li&gt;
&lt;li&gt;轻：不需要独立 OS，内存和 CPU 占用小&lt;/li&gt;
&lt;li&gt;可移植：应用和依赖打包在一起，任意环境一致运行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/docker-deep-dive/vm-vs-container.svg" alt="容器 vs VM 架构对比"&gt;&lt;/p&gt;</description></item><item><title>我用 PostgreSQL 替换了整个技术栈，省了 6 个微服务</title><link>https://blog.cpdd.fyi/posts/replaced-entire-stack-with-postgresql/</link><pubDate>Fri, 17 Apr 2026 20:00:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/replaced-entire-stack-with-postgresql/</guid><description>&lt;p&gt;现代软件工程已经基本变成了&amp;quot;订阅管理模拟器&amp;quot;。&lt;/p&gt;
&lt;p&gt;我们被云厂商洗脑了，以为即使构建一个基础应用，也需要拼凑一个脆弱的分布式网络：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Redis 做缓存&lt;/li&gt;
&lt;li&gt;Confluent Kafka 集群做后台任务&lt;/li&gt;
&lt;li&gt;Elasticsearch 只是为了一个简单的搜索框&lt;/li&gt;
&lt;li&gt;再搞个专用向量数据库为了那个临时加上的 AI 功能&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;等你终于把应用部署给你那&amp;quot;要求极高&amp;quot;的用户群（你自己和你妈）时，你已经欠了十几家 Y Combinator 支持的 SaaS 初创公司的钱，就为了让灯亮着。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这是一个过度工程化、价格离谱的陷阱。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;但如果我告诉你，你可以把那些闪亮的云依赖全部扔进焚化炉，用一个经过 30 年验证的开源软件替换它们呢？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;科技行业不想让你知道的秘密：一个久经考验的工具可以吞噬你的整个架构。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我刚刚用 PostgreSQL 替换了整个技术栈。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一postgresql-为什么能吞噬整个栈"&gt;一、PostgreSQL 为什么能吞噬整个栈&lt;/h2&gt;
&lt;p&gt;PostgreSQL 是一个开源对象关系数据库系统，已经活跃开发了 30 多年。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;开箱即用的能力：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;坚如磐石的 ACID 合规性——当你的廉价云服务器崩溃时，用户数据不会损坏&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可扩展性&lt;/strong&gt;——这才是它能吞噬整个栈的真正原因&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;PostgreSQL 不只是行列存储。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;它还能存储：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JSONB（半结构化数据）&lt;/li&gt;
&lt;li&gt;向量（AI 嵌入）&lt;/li&gt;
&lt;li&gt;全文搜索索引&lt;/li&gt;
&lt;li&gt;地理空间数据（PostGIS）&lt;/li&gt;
&lt;li&gt;图数据（关系遍历）&lt;/li&gt;
&lt;li&gt;时间序列数据&lt;/li&gt;
&lt;li&gt;键值对（作为 Redis 替代）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;PostgreSQL 不是数据库，是一个数据平台。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二实战替换redis缓存--键值存储"&gt;二、实战替换：Redis（缓存 + 键值存储）&lt;/h2&gt;
&lt;h3 id="传统架构"&gt;传统架构&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;应用 → Redis（缓存） → PostgreSQL（持久化）
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;问题：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;两个服务要运维&lt;/li&gt;
&lt;li&gt;缓存和数据库数据可能不一致&lt;/li&gt;
&lt;li&gt;Redis 挂了要处理降级&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="postgresql-方案"&gt;PostgreSQL 方案&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;使用 UNLOGGED 表做高速缓存：&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>99% 的开发者不懂堆内存，你真的懂吗？</title><link>https://blog.cpdd.fyi/posts/heap-memory-deep-dive/</link><pubDate>Fri, 17 Apr 2026 18:00:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/heap-memory-deep-dive/</guid><description>&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;example&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;问你一个问题：这个 &lt;code&gt;x&lt;/code&gt; 是在堆上还是在栈上？&lt;/p&gt;
&lt;p&gt;如果你答&amp;quot;栈上&amp;quot;，你错了——至少在这段代码里，Go 编译器会把 &lt;code&gt;x&lt;/code&gt; 分配到堆上。&lt;/p&gt;
&lt;p&gt;为什么？因为它的地址逃逸了函数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;99% 的开发者不懂堆内存&lt;/strong&gt;——不是不懂 &lt;code&gt;malloc&lt;/code&gt; 和 &lt;code&gt;free&lt;/code&gt;，是不懂分配器、碎片化、逃逸分析、GC 代价这些让堆变得迷人的底层细节。&lt;/p&gt;
&lt;p&gt;你可能觉得内存管理就是&amp;quot;分配&amp;quot;和&amp;quot;释放&amp;quot;这么简单，也可能不在意这些底层细节。但如果你不懂我刚才说的那些术语，或者想深入了解堆内存，这篇就是为你写的。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一堆-vs-栈核心差异在哪"&gt;一、堆 vs 栈：核心差异在哪&lt;/h2&gt;
&lt;p&gt;先说结论。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;栈内存&lt;/strong&gt;是程序内存中用于存储局部变量的区域，&lt;strong&gt;堆内存&lt;/strong&gt;是用于动态分配的区域。&lt;/p&gt;
&lt;p&gt;听起来简单，但 99% 的开发者忽略了两者的本质差异。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;差异点&lt;/th&gt;
 &lt;th&gt;栈（Stack）&lt;/th&gt;
 &lt;th&gt;堆（Heap）&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;管理方式&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;后进先出（LIFO），编译器自动管理&lt;/td&gt;
 &lt;td&gt;运行时管理，分配器或 GC 管理&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;分配速度&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;极快，编译器确切知道值存储在哪里、存活多久&lt;/td&gt;
 &lt;td&gt;慢，涉及元数据、可能的线程同步、GC&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;生命周期&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;函数结束时自动释放&lt;/td&gt;
 &lt;td&gt;可以比创建它的函数活得更久&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;大小限制&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;相对较小，通常几 MB&lt;/td&gt;
 &lt;td&gt;更大、更灵活，可向 OS 请求更多&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;碎片化&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;无&lt;/td&gt;
 &lt;td&gt;有，频繁分配释放会产生碎片&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;栈的工作方式：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;每次函数调用都会压入一个帧（frame），包含局部变量，返回时弹出。这个过程是严格的后继先出，所以极快。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;堆的工作方式：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;堆是一个更灵活但代价更高的空间。当程序运行时需要按需分配内存——比如那些可能比当前函数存活更久的对象，或者大小可以增长的集合——堆就是这些内存的来源。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/heap-memory-deep-dive/stack-vs-heap.svg" alt="堆栈结构对比图"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键区别在于生命周期：&lt;/strong&gt; 栈变量在函数结束时消失，但堆对象可以比创建它的函数活得更久。&lt;/p&gt;</description></item><item><title>你每天都在用 Git，但你可能不懂 rebase</title><link>https://blog.cpdd.fyi/posts/git-rebase-explained/</link><pubDate>Tue, 14 Apr 2026 16:10:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/git-rebase-explained/</guid><description>&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/git-rebase-explained/cover.svg" alt="Git rebase vs merge 对比"&gt;&lt;/p&gt;
&lt;p&gt;你在 feature 分支上写了三个提交，准备提 PR。&lt;/p&gt;
&lt;p&gt;同事说：先 rebase 一下 main，把历史整理干净。&lt;/p&gt;
&lt;p&gt;你照做了，但心里没底：rebase 到底改了什么？会不会出问题？&lt;/p&gt;
&lt;p&gt;很多开发者对 rebase 的理解，停留在&amp;quot;会敲命令，不知道它到底做了什么&amp;quot;。&lt;/p&gt;</description></item></channel></rss>