<?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/categories/%E6%8A%80%E6%9C%AF%E5%AE%9E%E6%88%98/</link><description>Recent content in 技术实战 on Zampo Blog</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Fri, 22 May 2026 16:40:00 +0800</lastBuildDate><atom:link href="https://blog.cpdd.fyi/categories/%E6%8A%80%E6%9C%AF%E5%AE%9E%E6%88%98/index.xml" rel="self" type="application/rss+xml"/><item><title>ready=true 不是发布：Go 内存模型真正要你保证什么</title><link>https://blog.cpdd.fyi/posts/go-memory-model/</link><pubDate>Fri, 22 May 2026 16:40:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-memory-model/</guid><description>&lt;p&gt;线上偶现一个很烦的 bug：配置已经加载了，日志也打印了“ready”，但另一个 goroutine 读到的还是旧值。&lt;/p&gt;
&lt;p&gt;代码看起来没什么毛病：先写数据，再把 &lt;code&gt;ready&lt;/code&gt; 置成 &lt;code&gt;true&lt;/code&gt;。读的一侧先等 &lt;code&gt;ready&lt;/code&gt;，再读数据。人脑看这段逻辑，很容易得出一个结论：既然 &lt;code&gt;ready&lt;/code&gt; 已经是 &lt;code&gt;true&lt;/code&gt;，前面的数据当然也该准备好了。&lt;/p&gt;
&lt;p&gt;问题就在这里。&lt;/p&gt;
&lt;p&gt;在 Go 里，&lt;code&gt;ready=true&lt;/code&gt; 不等于发布。普通变量上的“先写后读”，也不等于 goroutine 之间有可见性保证。&lt;/p&gt;
&lt;p&gt;并发安全不是让代码看起来有先后，而是让规范承认它有先后。&lt;/p&gt;
&lt;p&gt;这篇是 Go 内存模型系列的第一篇。它不打算把你拖进 CPU 指令、缓存一致性协议和汇编细节里。我们先解决一个更常见、更工程化的问题：goroutine A 写了变量，goroutine B 什么时候保证能看到？&lt;/p&gt;
&lt;p&gt;答案只有一个：两边要存在 happens-before 关系。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-memory-model/happens-before.svg" alt="happens-before 关系示意图"&gt;&lt;/p&gt;
&lt;h2 id="最危险的代码通常看起来最顺"&gt;最危险的代码，通常看起来最顺&lt;/h2&gt;
&lt;p&gt;先看这段很多人都写过的代码：&lt;/p&gt;
&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;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&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;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&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="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&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;data&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 class="s"&gt;&amp;#34;payload&amp;#34;&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;ready&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 class="kc"&gt;true&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;span class="line"&gt;&lt;span class="cl"&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="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ready&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="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="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;/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;data&lt;/code&gt; 先被赋值，&lt;code&gt;ready&lt;/code&gt; 后变成 &lt;code&gt;true&lt;/code&gt;。读的一侧又是看到 &lt;code&gt;ready&lt;/code&gt; 才打印 &lt;code&gt;data&lt;/code&gt;，似乎不会出错。&lt;/p&gt;</description></item><item><title>Go 服务被 OOMKilled，别先怪 GC</title><link>https://blog.cpdd.fyi/posts/go-gc-debugging/</link><pubDate>Fri, 22 May 2026 00:01:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-gc-debugging/</guid><description>&lt;p&gt;服务又被 OOMKilled 了。&lt;/p&gt;
&lt;p&gt;Pod 刚重启，群里已经有人开始翻 GC 日志。有人说 &lt;code&gt;GC CPU&lt;/code&gt; 到了 20%，有人说把 &lt;code&gt;GOGC&lt;/code&gt; 调低一点，还有人建议直接上 &lt;code&gt;GOMEMLIMIT&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这些动作不一定错，但顺序错了。&lt;/p&gt;
&lt;p&gt;OOMKilled 是结果，不是根因。它只说明容器被内核杀掉了，不说明是谁把内存吃掉了。可能是 Go heap 真的在涨，可能是 goroutine 泄漏把栈和引用一起拖大，可能是 &lt;code&gt;alloc_space&lt;/code&gt; 很高导致 GC 忙，可能是 RSS 里有 cgo、mmap、tmpfs，也可能只是容器 limit 给得太紧。&lt;/p&gt;
&lt;p&gt;Go 服务的内存问题，最怕一句话定性。&lt;/p&gt;
&lt;p&gt;一旦你把所有问题都叫“GC 问题”，排查就会变成调参赌博：今天调 &lt;code&gt;GOGC&lt;/code&gt;，明天加 limit，后天又开始怀疑 pprof 没抓准。真正稳定的做法，是先把几条线拆开：Go 堆、Go runtime 管理的内存、RSS、容器 limit，以及业务生命周期。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-gc-debugging/debug-flow.png" alt="Go GC 排查流程"&gt;&lt;/p&gt;
&lt;h2 id="第一步不是调参数是确认谁杀了你"&gt;第一步不是调参数，是确认谁杀了你&lt;/h2&gt;
&lt;p&gt;线上看到内存上涨，第一件事不是打开 pprof，也不是改 &lt;code&gt;GOGC&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;先确认事故事实。&lt;/p&gt;
&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;span class="lnt"&gt;5
&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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl describe pod &amp;lt;pod&amp;gt; -n &amp;lt;ns&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl logs &amp;lt;pod&amp;gt; -n &amp;lt;ns&amp;gt; --previous
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl top pod &amp;lt;pod&amp;gt; -n &amp;lt;ns&amp;gt; --containers
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get pod &amp;lt;pod&amp;gt; -n &amp;lt;ns&amp;gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{.status.containerStatuses[*].lastState.terminated.reason}&amp;#39;&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;/p&gt;</description></item><item><title>Go GC 占 CPU 5%，到底要不要调 GOGC？</title><link>https://blog.cpdd.fyi/posts/go-gc-tuning/</link><pubDate>Thu, 21 May 2026 23:30:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-gc-tuning/</guid><description>&lt;p&gt;服务 p99 每隔几十秒抖一下。&lt;/p&gt;
&lt;p&gt;监控里 GC CPU 大概 5%，不算夸张，但曲线很准：GC 一来，延迟就往上冒。群里很快有人提议：把 &lt;code&gt;GOGC&lt;/code&gt; 从 100 调到 200，先让 GC 少跑几次。&lt;/p&gt;
&lt;p&gt;这个动作看起来很合理。&lt;/p&gt;
&lt;p&gt;CPU 下来了，GC 日志没那么密了，p99 也安静了几天。然后另一个问题来了：容器内存开始逼近 limit，某个高峰期被 OOMKilled。大家又把 &lt;code&gt;GOGC&lt;/code&gt; 调回去，延迟抖动也跟着回来。&lt;/p&gt;
&lt;p&gt;这就是很多 Go GC 调优的真实状态：不是不会调参数，而是没想清楚自己到底在买什么。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;GOGC&lt;/code&gt; 调的是账期，不是魔法。&lt;/p&gt;
&lt;p&gt;调高它，相当于允许堆多长一段时间，用内存换 CPU；调低它，相当于让 GC 更勤快，用 CPU 换内存。至于 &lt;code&gt;GOMEMLIMIT&lt;/code&gt;，它不是另一个版本的 &lt;code&gt;GOGC&lt;/code&gt;，而是在告诉 runtime：这个进程由 Go 管的内存，最好别越过这条线。&lt;/p&gt;
&lt;p&gt;所以这篇不打算把 GC 参数逐个解释一遍。线上真正需要的是一条决策路径：要不要调，调什么，调多少，什么时候停手。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-gc-tuning/gogc-heap-curve.png" alt="GOGC 与堆目标关系"&gt;&lt;/p&gt;
&lt;h2 id="先别问-gogc-设多少先问谁在付账"&gt;先别问 GOGC 设多少，先问谁在付账&lt;/h2&gt;
&lt;p&gt;Go 官方 GC Guide 给过一个很关键的公式：&lt;/p&gt;
&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;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Target heap memory = Live heap + (Live heap + GC roots) × GOGC / 100
&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;/p&gt;</description></item><item><title>内存一直涨，不一定是泄漏：Go GC 真正在做什么</title><link>https://blog.cpdd.fyi/posts/go-gc-three-color-marking/</link><pubDate>Thu, 21 May 2026 20:25:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-gc-three-color-marking/</guid><description>&lt;p&gt;服务内存从 800MB 慢慢涨到 2.6GB。&lt;/p&gt;
&lt;p&gt;曲线不陡，不像雪崩。它只是每天往上爬一点，重启以后掉下来，过几天又回到老地方。告警响了，群里第一句话通常是：是不是内存泄漏？&lt;/p&gt;
&lt;p&gt;这个判断不算错，但太急。&lt;/p&gt;
&lt;p&gt;在 Go 服务里，内存持续上涨可能是真泄漏，也可能是 live heap 变大了，可能是 &lt;code&gt;GOGC&lt;/code&gt; 目标让下一轮回收来得更晚，也可能是短命对象太多，把 GC 拖进了频繁工作。还有一种更容易误判：heap 已经回收了，但 RSS 没有马上按你想象的方式降下来。&lt;/p&gt;
&lt;p&gt;内存涨，不等于内存泄漏。&lt;/p&gt;
&lt;p&gt;要把这件事查清楚，不能只盯 RSS。你得知道 Go GC 什么时候启动、怎么判断对象还活着、为什么需要写屏障、哪里会停顿，以及哪些参数是真的能调。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-gc-three-color-marking/gc-flow.svg" alt="Go GC 流程图"&gt;&lt;/p&gt;
&lt;h2 id="gc-不是保洁员是成本调度器"&gt;GC 不是保洁员，是成本调度器&lt;/h2&gt;
&lt;p&gt;很多人对 GC 的直觉是“内存不用了，运行时帮我扫掉”。这个说法只对了一半。&lt;/p&gt;
&lt;p&gt;真正重要的是另一半：GC 每扫一次，都要花 CPU；扫得太勤，CPU 被回收器吃掉，服务吞吐和延迟会受影响。扫得太晚，内存峰值又会涨上去，容器可能先把你杀掉。&lt;/p&gt;
&lt;p&gt;所以 Go GC 不是等地上脏了才来的保洁员。它更像一个拿着预算表的人：上一轮还有多少对象活着？再往后拖一点，内存能不能接受？现在开始扫，CPU 能不能接受？&lt;/p&gt;
&lt;p&gt;Go 官方 GC Guide 讲得很清楚：GC 主要消耗 CPU 和物理内存。想省 CPU，就得允许堆多长一会儿；想省内存，就得让 GC 更频繁地工作。&lt;/p&gt;
&lt;p&gt;这就是 &lt;code&gt;GOGC&lt;/code&gt; 的本质。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;GOGC=100&lt;/code&gt; 不是“内存达到 100MB 触发 GC”。它的意思更接近：在上一轮存活堆的基础上，允许新分配对象按约 100% 的比例增长，再触发下一轮 GC。Go 1.19 之后，目标堆计算还会把 GC roots 的成本纳入进来，可以简化理解为：&lt;/p&gt;
&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;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;下一轮目标 ≈ live heap + (live heap + GC roots) × GOGC / 100
&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;GOGC&lt;/code&gt; 调的是成本，不是情绪。&lt;/p&gt;</description></item><item><title>加 buffer 不是修 bug：Go channel 阻塞排查实战</title><link>https://blog.cpdd.fyi/posts/go-channel-practice-debugging/</link><pubDate>Thu, 21 May 2026 18:30:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-channel-practice-debugging/</guid><description>&lt;p&gt;goroutine 数量从 200 慢慢涨到 2 万。&lt;/p&gt;
&lt;p&gt;监控图上那条线，不陡，但一直在爬。就像水龙头没拧紧——不喷，但也不会停。&lt;/p&gt;
&lt;p&gt;你的第一反应是什么？&lt;/p&gt;
&lt;p&gt;加大 buffer。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;make(chan int, 100)&lt;/code&gt; 改成 &lt;code&gt;make(chan int, 1000)&lt;/code&gt;。重启。观察。好像好了。&lt;/p&gt;
&lt;p&gt;三天后，又涨回来了。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-channel-practice-debugging/cover.svg" alt="Go channel 阻塞排查封面"&gt;&lt;/p&gt;
&lt;p&gt;这不是运气差。这是 Go 服务最常见的线上问题之一：channel 阻塞导致 goroutine 泄漏。而&amp;quot;加 buffer&amp;quot;是社区里最流行、也最没用的解法。&lt;/p&gt;
&lt;p&gt;buffer 只是把崩溃推迟了。问题的根因——谁在等谁、谁该退出、谁该 close——一个都没解决。&lt;/p&gt;
&lt;p&gt;这篇文章做一件事：从&amp;quot;goroutine 涨了&amp;quot;这个信号出发，走一遍完整的排查路径，把 channel 的六个最常见陷阱逐个拆开。&lt;/p&gt;
&lt;p&gt;不是背 API。&lt;/p&gt;
&lt;p&gt;是下次线上出问题的时候，你知道先看什么。&lt;/p&gt;
&lt;h2 id="发现怎么知道-channel-卡住了"&gt;发现：怎么知道 channel 卡住了&lt;/h2&gt;
&lt;p&gt;排查 channel 阻塞的第一步不是看代码，是看数字。&lt;/p&gt;
&lt;h3 id="runtimenumgoroutine最简单的基线"&gt;runtime.NumGoroutine()：最简单的基线&lt;/h3&gt;
&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;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&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="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;runtime&amp;#34;&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&gt;&lt;/span&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;init&lt;/span&gt;&lt;span class="p"&gt;()&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="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&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;ticker&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="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewTicker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&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="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Second&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="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Stop&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="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;C&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;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;goroutines: %d&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NumGoroutine&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="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="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="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;这段代码放到任何 Go 服务的 &lt;code&gt;init()&lt;/code&gt; 里，每 30 秒打一行日志。goroutine 数量只升不降，大概率有泄漏。&lt;/p&gt;</description></item><item><title>Go channel 不是高级锁，它是一种组织并发的语言</title><link>https://blog.cpdd.fyi/posts/go-channel-csp-design-philosophy/</link><pubDate>Thu, 21 May 2026 16:02:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-channel-csp-design-philosophy/</guid><description>&lt;p&gt;有人为了写得“更 Go”，把一个简单 cache 包成了 channel 协议。&lt;/p&gt;
&lt;p&gt;每次 &lt;code&gt;Get&lt;/code&gt;，先构造 request，发给 owner goroutine，再等 response channel 返回。还要处理 context、close、退出顺序、goroutine 泄漏。&lt;/p&gt;
&lt;p&gt;最后代码确实没有显式 &lt;code&gt;sync.RWMutex&lt;/code&gt; 了。&lt;/p&gt;
&lt;p&gt;但问题也来了：本来一把锁能讲清楚的事，被写成了一套小型 RPC。&lt;/p&gt;
&lt;p&gt;这不是优雅。&lt;/p&gt;
&lt;p&gt;这是绕远。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-channel-csp-design-philosophy/cover.png" alt="Go channel 设计哲学封面"&gt;&lt;/p&gt;
&lt;p&gt;Go 社区有一句被反复引用的话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Do not communicate by sharing memory; instead, share memory by communicating.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;很多人把它理解成：Go 不喜欢锁，channel 更高级，能用 channel 就别用 mutex。&lt;/p&gt;
&lt;p&gt;这个理解太粗了。&lt;/p&gt;
&lt;p&gt;channel 的价值不是把锁藏起来，而是把通信显式写出来。mutex 也不是“不 Go”，它就是保护共享状态的直接工具。&lt;/p&gt;
&lt;p&gt;真正要问的不是：channel 和 mutex 谁更高级？&lt;/p&gt;
&lt;p&gt;真正要问的是：你现在到底是在传东西，还是在护东西？&lt;/p&gt;
&lt;h2 id="channel-首先讲的是结构不是性能"&gt;channel 首先讲的是结构，不是性能&lt;/h2&gt;
&lt;p&gt;Rob Pike 在那场很有名的演讲《Concurrency is not Parallelism》里，一直在压一个区分：concurrency 是 structure，parallelism 是 execution。&lt;/p&gt;
&lt;p&gt;并发不是“同时跑得更快”的同义词。&lt;/p&gt;
&lt;p&gt;并发先是一种组织程序的方式：把一件事拆成多个可以独立执行的单元，再让它们通过通信协调。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-channel-csp-design-philosophy/rob-pike.jpg" alt="Rob Pike"&gt;&lt;/p&gt;</description></item><item><title>Go channel 源码不是在讲队列，而是在讲 goroutine 怎么排队</title><link>https://blog.cpdd.fyi/posts/go-channel-hchan-runtime-source/</link><pubDate>Thu, 21 May 2026 15:17:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-channel-hchan-runtime-source/</guid><description>&lt;p&gt;goroutine dump 里满屏 &lt;code&gt;chan send&lt;/code&gt;，你知道它卡住了。&lt;/p&gt;
&lt;p&gt;但它到底卡在哪里？&lt;/p&gt;
&lt;p&gt;是缓冲区满了？没有 receiver？被 &lt;code&gt;select&lt;/code&gt; 挂进了等待队列？还是 channel 被 close 之后才醒过来，然后 panic？&lt;/p&gt;
&lt;p&gt;如果只停留在“channel 是 goroutine 之间通信的管道”，这些问题永远说不清。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ch &amp;lt;- v&lt;/code&gt; 这一行代码，在 Go runtime 里不是一句“把数据塞进队列”。它会走过 &lt;code&gt;hchan&lt;/code&gt;、环形缓冲区、&lt;code&gt;sendq&lt;/code&gt; / &lt;code&gt;recvq&lt;/code&gt;、&lt;code&gt;sudog&lt;/code&gt;、&lt;code&gt;gopark&lt;/code&gt;、&lt;code&gt;goready&lt;/code&gt;，最后决定一个 goroutine 是继续跑，还是挂起来等别人。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-channel-hchan-runtime-source/cover.png" alt="Go channel hchan 运行时封面"&gt;&lt;/p&gt;
&lt;p&gt;Go 的 channel 设计长期受 CSP 思想影响。Rob Pike 在 Go 早期设计和并发模型传播里反复强调：不要通过共享内存来通信，而要通过通信来共享内存。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-channel-hchan-runtime-source/rob-pike.jpg" alt="Rob Pike"&gt;&lt;/p&gt;
&lt;p&gt;这句话听起来像哲学，但到了 runtime 里，它会变成很具体的结构：谁在等谁，数据放在哪里，哪个 goroutine 应该被唤醒。&lt;/p&gt;
&lt;p&gt;这篇只做一件事：沿着 Go 1.25.4 的 &lt;code&gt;runtime/chan.go&lt;/code&gt; 和 &lt;code&gt;runtime/select.go&lt;/code&gt;，把 channel 的几条关键路径走一遍。&lt;/p&gt;
&lt;p&gt;不是为了背源码。&lt;/p&gt;
&lt;p&gt;是为了让你下次看到 &lt;code&gt;chan send&lt;/code&gt;、&lt;code&gt;chan receive&lt;/code&gt;、&lt;code&gt;close&lt;/code&gt;、&lt;code&gt;select&lt;/code&gt; 的时候，脑子里有一张清楚的运行时地图。&lt;/p&gt;
&lt;p&gt;channel 不是无锁队列，它是带调度语义的协作原语。&lt;/p&gt;
&lt;h2 id="hchanchannel-在运行时到底长什么样"&gt;hchan：channel 在运行时到底长什么样&lt;/h2&gt;
&lt;p&gt;Go 代码里你写的是：&lt;/p&gt;</description></item><item><title>Go 服务 goroutine 涨了，别先猜：按这条流程查 context 泄漏</title><link>https://blog.cpdd.fyi/posts/go-context-goroutine-leak-debugging/</link><pubDate>Thu, 21 May 2026 13:45:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-context-goroutine-leak-debugging/</guid><description>&lt;p&gt;线上 goroutine 数开始往上爬，最怕的不是它涨。&lt;/p&gt;
&lt;p&gt;最怕的是你盯着监控看了十分钟，然后开始猜：是不是 &lt;code&gt;context&lt;/code&gt; 泄漏了？是不是超时没生效？是不是某个 channel 卡住了？&lt;/p&gt;
&lt;p&gt;这些猜法都有可能对，也都有可能错。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;runtime.NumGoroutine()&lt;/code&gt; 只能告诉你“现在有多少 goroutine”。它不会告诉你这些 goroutine 停在哪里，也不会告诉你是谁创建的，更不会告诉你为什么没有退出。&lt;/p&gt;
&lt;p&gt;排查这类问题，第一步不是解释 &lt;code&gt;context&lt;/code&gt; 原理。&lt;/p&gt;
&lt;p&gt;第一步是采样。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-context-goroutine-leak-debugging/cover.png" alt="Go context goroutine 泄漏排查封面"&gt;&lt;/p&gt;
&lt;p&gt;这篇是 Go &lt;code&gt;context&lt;/code&gt; 系列第三篇。前两篇讲过 &lt;code&gt;context&lt;/code&gt; 的生命周期、&lt;code&gt;cancel&lt;/code&gt; 的边界，以及 Go 为什么坚持显式传 &lt;code&gt;ctx&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这一篇换一个角度：不从源码开始，从排查现场开始。&lt;/p&gt;
&lt;p&gt;一套最小流程就够了：&lt;/p&gt;
&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;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&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-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;goroutine 涨了
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;先采样，确认趋势
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;用 pprof 找等待点
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;用 go vet 查静态路径
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;按创建路径、取消路径、响应路径回到代码
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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;img src="https://blog.cpdd.fyi/images/go-context-goroutine-leak-debugging/inline-01.png" alt="Go context 泄漏最小排查流程"&gt;&lt;/p&gt;</description></item><item><title>Go 为什么宁可让你多传一个 ctx 参数</title><link>https://blog.cpdd.fyi/posts/go-context-explicit-cost/</link><pubDate>Wed, 20 May 2026 22:20:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-context-explicit-cost/</guid><description>&lt;p&gt;很多 Go 开发者第一次系统用 &lt;code&gt;context&lt;/code&gt;，都会嫌它啰嗦。&lt;/p&gt;
&lt;p&gt;一个请求从 handler 进来，service 要传 &lt;code&gt;ctx&lt;/code&gt;，repo 要传 &lt;code&gt;ctx&lt;/code&gt;，RPC client 要传 &lt;code&gt;ctx&lt;/code&gt;，连中间那些根本不关心超时和取消的函数，也要机械地把 &lt;code&gt;ctx&lt;/code&gt; 往下递。&lt;/p&gt;
&lt;p&gt;看多了以后，很自然会冒出一个问题：为什么不能像 Java、Node.js、Python 那样，把上下文放进运行时里？&lt;/p&gt;
&lt;p&gt;Java 有 &lt;code&gt;ThreadLocal&lt;/code&gt;。Node.js 有 &lt;code&gt;AsyncLocalStorage&lt;/code&gt;。Python 有 &lt;code&gt;contextvars&lt;/code&gt;。它们都能让深层函数不改签名，也拿到当前请求的 trace id、用户信息、日志上下文。&lt;/p&gt;
&lt;p&gt;Go 偏不。&lt;/p&gt;
&lt;p&gt;它宁可让你在函数签名里多写一个参数：&lt;/p&gt;
&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;/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;DoSomething&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&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="c1"&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="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;这不是因为 Go 不知道隐式上下文更省事。恰好相反，这正是 Go 的取舍：它愿意把“不优雅”摆在台面上，换调用链里的生命周期可见。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-context-explicit-cost/cover.png" alt="Go context 显式代价封面"&gt;&lt;/p&gt;
&lt;h2 id="显式传参最先付出的就是签名噪音"&gt;显式传参，最先付出的就是签名噪音&lt;/h2&gt;
&lt;p&gt;先承认代价。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ctx context.Context&lt;/code&gt; 作为首参，会污染函数签名。尤其在分层很深的服务里，中间层经常只是 pass-through：自己不用 &lt;code&gt;ctx&lt;/code&gt;，但必须接住它，再传给下一层。&lt;/p&gt;
&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;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CreateReq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&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="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&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="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&gt;&lt;/span&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CreateReq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&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="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UserID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Payload&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="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&gt;&lt;/span&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&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;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&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="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`insert into ...`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;payload&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="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&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;Handler&lt;/code&gt; 和 &lt;code&gt;Service&lt;/code&gt; 可能都没有真正“使用” &lt;code&gt;ctx&lt;/code&gt;。它们只是把一段调用生命周期交给更底层的数据库调用。&lt;/p&gt;</description></item><item><title>Go context 最容易被误用的地方：以为 cancel 会替你收拾现场</title><link>https://blog.cpdd.fyi/posts/go-context-timeout-source/</link><pubDate>Wed, 20 May 2026 17:50:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-context-timeout-source/</guid><description>&lt;p&gt;线上接口开始超时，goroutine 数一路往上涨。&lt;/p&gt;
&lt;p&gt;你第一反应可能是：不是已经传了 &lt;code&gt;context.WithTimeout&lt;/code&gt; 吗？超时到了，goroutine 不就该停了吗？&lt;/p&gt;
&lt;p&gt;这就是 Go &lt;code&gt;context&lt;/code&gt; 最容易被误用的地方。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;context&lt;/code&gt; 不负责替你停掉 goroutine。它只负责把“该停了”这句话传到门口。门里的人听不听、什么时候退场，要看你自己的代码。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-context-timeout-source/cover.png" alt="Go context 调用链生命周期封面"&gt;&lt;/p&gt;
&lt;p&gt;这篇是 Go &lt;code&gt;context&lt;/code&gt; 系列的第一篇。先不急着讲 HTTP、gRPC、database/sql 这些框架怎么接入，也不急着把所有 API 列一遍。&lt;/p&gt;
&lt;p&gt;我们先把最底下这层讲清楚：&lt;code&gt;context&lt;/code&gt; 到底是什么，&lt;code&gt;cancel&lt;/code&gt; 到底做了什么，为什么 &lt;code&gt;WithTimeout&lt;/code&gt; 后面那句 &lt;code&gt;defer cancel()&lt;/code&gt; 不是仪式感，而是资源释放动作。&lt;/p&gt;
&lt;h2 id="事故通常不是从源码开始的"&gt;事故通常不是从源码开始的&lt;/h2&gt;
&lt;p&gt;真实排查里，很少有人一上来就打开 &lt;code&gt;context.go&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;更常见的是这样的场景：&lt;/p&gt;
&lt;p&gt;一个接口偶发超时。压测一上来，P99 开始抖，错误日志里出现 &lt;code&gt;context deadline exceeded&lt;/code&gt;。你看监控，CPU 没打满，内存也没爆，但 goroutine 数不太对，一路往上爬。&lt;/p&gt;
&lt;p&gt;然后你打开 pprof：&lt;/p&gt;
&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;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -s &lt;span class="s1"&gt;&amp;#39;http://127.0.0.1:6060/debug/pprof/goroutine?debug=1&amp;#39;&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;采样里有一批 goroutine 卡在业务 worker 上，等某个 channel，或者等 &lt;code&gt;&amp;lt;-ctx.Done()&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这时候最危险的判断是：&lt;/p&gt;
&lt;p&gt;“是不是 Go 的 context 泄漏了？”&lt;/p&gt;
&lt;p&gt;大多数时候，不是。&lt;/p&gt;
&lt;p&gt;更可能是你的调用链里少了三件事之一：创建了 timeout context 但没及时 &lt;code&gt;cancel&lt;/code&gt;；启动了 goroutine 但没监听 &lt;code&gt;ctx.Done()&lt;/code&gt;；或者监听了 &lt;code&gt;Done&lt;/code&gt;，却没有确保取消路径真的跑到。&lt;/p&gt;</description></item><item><title>给 Cursor 一个小需求，怎样让它先计划、再修改、再验收</title><link>https://blog.cpdd.fyi/posts/cursor-small-task-workflow/</link><pubDate>Wed, 20 May 2026 10:39:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/cursor-small-task-workflow/</guid><description>&lt;p&gt;这篇不讲安装，也不讲一堆按钮。&lt;/p&gt;
&lt;p&gt;我们只做一件小事：给 Cursor 一个登录页，让它把页面视觉调得更像一个正式产品，但不允许它改业务逻辑。&lt;/p&gt;
&lt;p&gt;任务很小，正好适合练手。小到你能看完所有 diff；也真实到足够暴露问题。很多人用 Agent 翻车，不是让它做了多复杂的架构，而是从一句“帮我优化一下页面”开始，最后发现字段、校验、接口路径都被顺手动了。&lt;/p&gt;
&lt;p&gt;这一次不追求“让 Cursor 改得多快”。&lt;/p&gt;
&lt;p&gt;我们只看一件事：它能不能按你的节奏走，分析、计划、执行、验收。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/cursor-small-task-workflow/cover.png" alt="Cursor 小需求实操封面"&gt;&lt;/p&gt;
&lt;h2 id="准备一个小到能验收的项目"&gt;准备一个小到能验收的项目&lt;/h2&gt;
&lt;p&gt;我用的是一个最小登录页 demo，只有四个核心文件：&lt;/p&gt;
&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;span class="lnt"&gt;5
&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-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cursor-agent-demo/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── index.html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── style.css
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── login.js
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── tests/login.test.js
&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;/p&gt;
&lt;p&gt;表单的业务契约也很明确：&lt;/p&gt;
&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-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;loginForm&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/api/login&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;post&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;email&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;email&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;email&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="p"&gt;/&amp;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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;password&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;password&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;password&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="na"&gt;minlength&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;8&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;/&amp;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;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;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;/p&gt;
&lt;p&gt;不能改这些东西：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;表单字段名&lt;/li&gt;
&lt;li&gt;&lt;code&gt;form action&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;请求路径&lt;/li&gt;
&lt;li&gt;&lt;code&gt;required&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;minlength&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;提交流程&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你第一次练 Cursor Agent，我建议也从这种项目开始。不要一上来就让它改业务模块、重构页面、接新接口。项目越大，你越容易被一堆看起来很努力的 diff 淹没。&lt;/p&gt;
&lt;p&gt;Agent 不是不能做大任务。&lt;/p&gt;
&lt;p&gt;但你得先练会怎么管住小任务。&lt;/p&gt;
&lt;h2 id="第一步只分析不许改"&gt;第一步：只分析，不许改&lt;/h2&gt;
&lt;p&gt;很多人给 Cursor 的第一句话是：&lt;/p&gt;
&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;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&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;/p&gt;
&lt;p&gt;“优化”是什么？只改样式，还是改交互？能不能抽组件？能不能动校验？能不能顺手把 HTML 结构也重排？你没说清楚，Agent 就只能猜。&lt;/p&gt;</description></item><item><title>Cursor 真正难的不是安装，是别把 Agent 用成自动补全</title><link>https://blog.cpdd.fyi/posts/cursor-agent-workflow/</link><pubDate>Fri, 15 May 2026 18:45:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/cursor-agent-workflow/</guid><description>&lt;p&gt;你只是想让 Cursor 改一个登录页。&lt;/p&gt;
&lt;p&gt;它很积极。顺手改了组件结构、路由命名、校验逻辑，还帮你重排了几个文件。&lt;/p&gt;
&lt;p&gt;diff 很长，像是干了很多活。问题是你不敢合并。&lt;/p&gt;
&lt;p&gt;这就是很多人用 Cursor 的真实卡点。不是不会安装，不是不知道 Chat 在哪，也不是模型不够强。&lt;/p&gt;
&lt;p&gt;难的是：你得学会管住一个会主动行动的 Agent。&lt;/p&gt;
&lt;p&gt;Cursor 的价值不在“让 AI 多写几行代码”。它真正改变的是开发者和代码库之间的关系。过去你是在编辑器里写代码，现在你是在编辑器里指挥一个会读文件、改文件、跑命令、解释 diff 的执行者。&lt;/p&gt;
&lt;p&gt;如果还把它当成更聪明的自动补全用，结果通常只有两种：要么大材小用，要么放它乱跑。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/cursor-agent-workflow/cover.svg" alt="Cursor Agent 工作流封面"&gt;&lt;/p&gt;
&lt;h2 id="低门槛反而容易误判"&gt;低门槛，反而容易误判&lt;/h2&gt;
&lt;p&gt;Cursor 对 VS Code 用户很友好。&lt;/p&gt;
&lt;p&gt;打开项目，侧边栏聊天，选中文件提问，让它解释一段代码，或者直接让它改一个小功能。这些动作都很自然。Cursor 官方文档里也把 Agent 定义得很直接：它可以完成复杂编码任务，可以运行终端命令，也可以编辑代码。&lt;/p&gt;
&lt;p&gt;这很方便，但方便会制造一个错觉：装上就会用了。&lt;/p&gt;
&lt;p&gt;实际上，装上 Cursor 只是打开了入口。真正决定结果的，是你怎么给它上下文、怎么限制改动范围、怎么验收输出。&lt;/p&gt;
&lt;p&gt;很多人第一次用 Cursor，都会写这种 prompt：&lt;/p&gt;
&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;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&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;/p&gt;
&lt;p&gt;“优化”是什么意思？改视觉，还是改交互？能不能动接口请求？能不能改路由？能不能顺手抽组件？改完怎么判断对不对？&lt;/p&gt;
&lt;p&gt;你没说，Agent 就只能猜。&lt;/p&gt;
&lt;p&gt;Agent 的可怕之处不是它不会做事，而是它太愿意做事。你给一个含糊任务，它会补齐你的空白。补得对，是惊喜；补得错，就是事故。&lt;/p&gt;
&lt;h2 id="不要直接让它改先让它只分析"&gt;不要直接让它改，先让它只分析&lt;/h2&gt;
&lt;p&gt;我做了一个最小实测项目：一个纯 HTML/CSS/JS 登录页。&lt;/p&gt;
&lt;p&gt;项目很小，只有这几个文件：&lt;/p&gt;
&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;span class="lnt"&gt;5
&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-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cursor-agent-demo/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── index.html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── style.css
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── login.js
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── tests/login.test.js
&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;任务也很小：只调整登录页的视觉层，不改变字段、表单 action、请求路径和校验逻辑。&lt;/p&gt;</description></item><item><title>Hermes Kanban 实战：别再用聊天记录管理多 Agent 团队</title><link>https://blog.cpdd.fyi/posts/hermes-kanban-hardcore-guide/</link><pubDate>Wed, 13 May 2026 15:36:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/hermes-kanban-hardcore-guide/</guid><description>&lt;p&gt;你把任务卡创建好了，researcher、writer、coder 也都分配了。&lt;/p&gt;
&lt;p&gt;然后什么都没发生。&lt;/p&gt;
&lt;p&gt;这不是 bug。恰恰相反，这是 Hermes Kanban 最容易被误解、也最值得讲清楚的地方：&lt;strong&gt;创建 card，不等于启动 worker。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;很多人把多 Agent 系统想得太像“开几个聊天窗口”。一个负责调研，一个负责写稿，一个负责代码，大家各干各的。听起来很美，真跑起来就会发现：谁先干？谁等谁？谁审核？交付物放哪？失败了谁接？&lt;/p&gt;
&lt;p&gt;靠聊天记录和几个 Markdown 文件也能凑合跑。但只要任务链稍微长一点，协作就开始变脆。&lt;/p&gt;
&lt;p&gt;Hermes Kanban 解决的不是“让 Agent 更聪明”。它解决的是另一个更工程的问题：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;把 Agent 的工作，放进一条可追踪、可审核、可恢复的流水线里。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这篇不讲概念安利，只讲怎么用。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/hermes-kanban-hardcore-guide/cover.svg" alt="Hermes Kanban 看板与命令主视觉"&gt;&lt;/p&gt;
&lt;h2 id="先记住一句话kanban-是任务系统不是魔法按钮"&gt;先记住一句话：Kanban 是任务系统，不是魔法按钮&lt;/h2&gt;
&lt;p&gt;Hermes Kanban 是一块持久化的任务板。你可以用它创建任务、分配 assignee、声明 parent/child 依赖、绑定 workspace、记录 comment、阻塞等待审核、完成后给下游传 handoff。&lt;/p&gt;
&lt;p&gt;它适合管这种任务：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个任务需要多个 profile 接力，比如 researcher → writer → default；&lt;/li&gt;
&lt;li&gt;下游必须等上游交付后才能开始；&lt;/li&gt;
&lt;li&gt;交付物必须有路径、有验证、有审核记录；&lt;/li&gt;
&lt;li&gt;任务失败后不能靠人翻聊天记录猜状态；&lt;/li&gt;
&lt;li&gt;你希望 worker 不只是“答一句”，而是按岗位技能执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它不适合管这种任务：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你只是问一句命令怎么写；&lt;/li&gt;
&lt;li&gt;任务几分钟就能完成，没有上下游；&lt;/li&gt;
&lt;li&gt;没有明确交付物，也不需要审核；&lt;/li&gt;
&lt;li&gt;你还没把需求想清楚，只是随手丢一个念头。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这也是为什么 Hermes Kanban 的六列看板很重要。它不是装饰，它是任务生命周期。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/hermes-kanban-hardcore-guide/inline-01.svg" alt="Hermes Kanban 六列看板示意"&gt;&lt;/p&gt;
&lt;p&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;Triage&lt;/td&gt;
 &lt;td&gt;原始想法，还不是可执行任务&lt;/td&gt;
 &lt;td&gt;点 Specify，或用 &lt;code&gt;hermes kanban specify&lt;/code&gt; 补成任务简报&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Todo&lt;/td&gt;
 &lt;td&gt;任务已创建，但依赖未满足或还没分配&lt;/td&gt;
 &lt;td&gt;等 parent 完成，或补 assignee&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Ready&lt;/td&gt;
 &lt;td&gt;已经可执行，等 dispatcher 或人工 claim&lt;/td&gt;
 &lt;td&gt;检查 gateway/dispatcher 是否在跑&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;In Progress&lt;/td&gt;
 &lt;td&gt;worker 已 claim，正在执行&lt;/td&gt;
 &lt;td&gt;看 heartbeat、runs、log&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Blocked&lt;/td&gt;
 &lt;td&gt;等审核、等补料、或断路器触发&lt;/td&gt;
 &lt;td&gt;看 block reason，决定 unblock 还是打回&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Done&lt;/td&gt;
 &lt;td&gt;任务完成&lt;/td&gt;
 &lt;td&gt;看 summary 和 metadata，给下游消费&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;不要小看这六列。多 Agent 协作最大的问题，不是 Agent 不会做事，而是状态没人管。&lt;/p&gt;</description></item><item><title>Claude Code vs Cursor：不是选工具，是选入口</title><link>https://blog.cpdd.fyi/posts/claude-code-vs-cursor/</link><pubDate>Fri, 08 May 2026 11:30:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/claude-code-vs-cursor/</guid><description>&lt;p&gt;花 $200 买了 Cursor Ultra，发现请求数不够用。&lt;/p&gt;
&lt;p&gt;又花 $100 买了 Claude Code Max，结果两个工具各跑各的。Claude 改完代码，Cursor 不知道；Cursor 里聊了半天，Claude 从头开始。&lt;/p&gt;
&lt;p&gt;最后两边都浪费，你还得花精力在两个界面之间切换。&lt;/p&gt;
&lt;p&gt;这不是工具的问题。&lt;/p&gt;
&lt;p&gt;是你没想清楚一件事：&lt;strong&gt;你习惯在哪干活，就该在哪接 AI。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;很多人选 Claude Code 还是 Cursor，看的是功能对比表：谁上下文更大、谁模型更强、谁更便宜。&lt;/p&gt;
&lt;p&gt;这个思路从一开始就错了。&lt;/p&gt;
&lt;p&gt;工具选型不是比功能，是比入口。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/claude-code-vs-cursor/inline-01.png" alt="Claude Code vs Cursor 入口对比"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="先说结论两个都能用但入口不同"&gt;先说结论：两个都能用，但入口不同&lt;/h2&gt;
&lt;p&gt;Claude Code 和 Cursor 的核心差异，不是&amp;quot;谁更强&amp;quot;。&lt;/p&gt;
&lt;p&gt;是&lt;strong&gt;交互入口不同&lt;/strong&gt;。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;Claude Code&lt;/th&gt;
 &lt;th&gt;Cursor&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&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;/td&gt;
 &lt;td&gt;命令驱动，批量操作&lt;/td&gt;
 &lt;td&gt;对话驱动，可视化交互&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;上下文管理&lt;/td&gt;
 &lt;td&gt;需要手动 &lt;code&gt;/compact&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;最高 1M tokens&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;自动化&lt;/td&gt;
 &lt;td&gt;天然适合 CI/CD、脚本&lt;/td&gt;
 &lt;td&gt;更适合人工交互&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&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;Cursor 2.0 之后，已经明显偏向对话式开发环境。不要再把它简单理解成&amp;quot;看代码和调试&amp;quot;的辅助 IDE。&lt;/p&gt;
&lt;p&gt;Claude Code 把工作流压进终端，Cursor 把工作流放进对话环境。&lt;/p&gt;
&lt;p&gt;你选的不是工具。&lt;/p&gt;</description></item><item><title>别让 Claude Code 一路狂奔：高手都在盯这 5 个地方</title><link>https://blog.cpdd.fyi/posts/claude-code-best-practices/</link><pubDate>Fri, 08 May 2026 11:30:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/claude-code-best-practices/</guid><description>&lt;p&gt;Claude 修一个 bug，顺手改了 12 个文件。&lt;/p&gt;
&lt;p&gt;它删了旧组件，换了依赖，重写了一段状态管理。终端里日志飞快滚动，看起来很努力，也很专业。&lt;/p&gt;
&lt;p&gt;你隐约觉得不对，但没按 &lt;code&gt;Esc&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;十分钟后，测试挂了，页面也挂了。最麻烦的是，你已经看不清它到底从哪一步开始跑偏。&lt;/p&gt;
&lt;p&gt;很多人用 Claude Code 的进阶方式，恰好是错的：把权限开大，把确认关掉，把任务丢进去，然后等它“一路跑完”。这不是高手，这是把方向盘交出去。&lt;/p&gt;
&lt;p&gt;Claude Code 真正好用的地方，不是它能连续工作，而是你能在它工作时不断校准。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;真正的进阶，不是放手，而是会接管。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/claude-code-best-practices/inline-01.png" alt="Claude Code 控制回路"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="先理解它为什么会跑偏"&gt;先理解它为什么会跑偏&lt;/h2&gt;
&lt;p&gt;Anthropic 的 Cal 在官方最佳实践分享里提到一个很关键的心智模型：Claude Code 很像一个“只用终端的同事”。&lt;/p&gt;
&lt;p&gt;它不是靠提前索引整座代码库，然后从向量数据库里召回文件。按照官方分享里的说法，它更像一个新同事进入项目：用搜索、glob、grep、find 之类的方式，一点点探索代码库，看到结果后再决定下一步搜哪里。&lt;/p&gt;
&lt;p&gt;这套方式很灵活，但也有代价。&lt;/p&gt;
&lt;p&gt;它会不断读文件、跑命令、改代码，上下文越来越长，判断会受当前看到的信息影响。如果你一开始没给规则，中途不看计划，最后也不跑测试，它当然可能越走越偏。&lt;/p&gt;
&lt;p&gt;所以 Claude Code 的进阶，不是让它更自由。&lt;/p&gt;
&lt;p&gt;而是给它一条轨道。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="第一个地方claudemd不是备忘录是项目规则"&gt;第一个地方：CLAUDE.md，不是备忘录，是项目规则&lt;/h2&gt;
&lt;p&gt;很多人知道 &lt;code&gt;/init&lt;/code&gt; 可以生成 &lt;code&gt;CLAUDE.md&lt;/code&gt;，但写完就放在那里了。&lt;/p&gt;
&lt;p&gt;这很浪费。&lt;/p&gt;
&lt;p&gt;官方分享里说得很清楚：当你启动 Claude Code 时，如果当前工作目录里有 &lt;code&gt;CLAUDE.md&lt;/code&gt;，它会被放进上下文里，作为开发者留给 Claude 的重要指令。&lt;/p&gt;
&lt;p&gt;这不是“给 Claude 留一句备注”。&lt;/p&gt;
&lt;p&gt;这是你给它的项目规则。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CLAUDE.md&lt;/code&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;/td&gt;
 &lt;td&gt;模块在哪里，测试在哪里，入口在哪里&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;src/app&lt;/code&gt; 是路由，&lt;code&gt;src/lib&lt;/code&gt; 放业务逻辑&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;运行方式&lt;/td&gt;
 &lt;td&gt;怎么启动、测试、构建&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;npm test&lt;/code&gt;、&lt;code&gt;npm run typecheck&lt;/code&gt;、&lt;code&gt;npm run lint&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;工程约束&lt;/td&gt;
 &lt;td&gt;不能做什么，修改前要注意什么&lt;/td&gt;
 &lt;td&gt;不要引入新状态库；改 API 前先看兼容层&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一个实用模板：&lt;/p&gt;</description></item><item><title>装了 Claude Code，别上来就写 prompt</title><link>https://blog.cpdd.fyi/posts/claude-code-beginner-guide/</link><pubDate>Fri, 08 May 2026 10:00:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/claude-code-beginner-guide/</guid><description>&lt;p&gt;装好 Claude Code，打开终端，输入第一句话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;帮我做一个办公室排班应用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;五分钟后，Claude 生了一堆文件。&lt;code&gt;npm install&lt;/code&gt; 跑不起来，页面打不开，你盯着终端不知道该从哪开始修。&lt;/p&gt;
&lt;p&gt;问题不在 Claude Code，在你给 prompt 的方式。&lt;/p&gt;
&lt;p&gt;很多人装完 Claude Code 的第一反应是把它当聊天机器人用——扔一句话，等它干活。结果就是：代码能生成，但跑不起来；功能有，但结构乱；改一处，崩三处。&lt;/p&gt;
&lt;p&gt;这篇文章不罗列功能，不背参数。只讲一件事：&lt;strong&gt;怎么从装好 Claude Code 到做出第一个能跑的项目，中间不翻车。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="claude-code-是什么"&gt;Claude Code 是什么&lt;/h2&gt;
&lt;p&gt;一句话：Anthropic 出的终端 AI 编程工具。&lt;/p&gt;
&lt;p&gt;它不是 IDE，不是编辑器，是一个跑在终端里的 AI 代理。你能跟它对话，它能读你的代码、改你的文件、跑你的命令、提交你的 Git。&lt;/p&gt;
&lt;p&gt;Anthropic 团队里有个说法很准确：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Claude Code 就像那个只用终端的同事——从不碰 GUI，但什么都能搞定。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;它和 Cursor 的区别也很简单：Claude Code 是终端里的 AI，Cursor 2.0 之后更偏向对话式开发环境。两者不冲突，但分工不能再按老版本 Cursor 来理解——不要再把 Cursor 简单当成“看代码和调试”的辅助位。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="安装三分钟搞定"&gt;安装：三分钟搞定&lt;/h2&gt;
&lt;h3 id="前置条件"&gt;前置条件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;装了 Node.js（&lt;code&gt;node -v&lt;/code&gt; 能出版本就行）&lt;/li&gt;
&lt;li&gt;有 Claude 付费订阅（Pro / Max / Teams / Enterprise），或者有 API Key&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;2026 年 4 月 21 日 Anthropic 曾短暂从 Pro 计划中移除 Claude Code 访问权限，后恢复。定价政策可能波动，写这篇文章时的价格是：Pro $20/月、Max 5x $100/月、Max 20x $200/月。&lt;/p&gt;</description></item><item><title>别为了 Agent 而 Agent：2026 年多 Agent 工作流实战指南</title><link>https://blog.cpdd.fyi/posts/ai-agent-workflow-guide/</link><pubDate>Sat, 25 Apr 2026 10:00:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/ai-agent-workflow-guide/</guid><description>&lt;p&gt;一个团队花了两周，搭了三 Agent 流水线——研究员搜集数据、写作者整理报告、审核者把关质量。&lt;/p&gt;
&lt;p&gt;跑起来之后发现：每次执行花 40 秒，API 账单涨了 3 倍，调试时根本不知道哪个 Agent 说了什么。&lt;/p&gt;
&lt;p&gt;最后他们回到单 Agent，5 行代码解决了同样的问题。&lt;/p&gt;
&lt;p&gt;这不是个例。2026 年，多 Agent 工作流成了 AI 圈最热的方向。但&lt;strong&gt;很多团队的问题不是不会搭多 Agent，而是根本不需要。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;能单 Agent 解决的，别拆成三个。&lt;/p&gt;
&lt;p&gt;如果你看完这篇，判断自己的场景确实需要多 Agent——这篇能帮你选对框架、跑通示例、避开坑。如果判断不需要——恭喜你，省了两周围绕三个 Agent 转的日子。&lt;/p&gt;
&lt;h2 id="先判断你到底需不需要"&gt;先判断：你到底需不需要？&lt;/h2&gt;
&lt;p&gt;Agent 越多，调试越难，成本越高。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;该用的场景：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;复杂多步骤工作流，有分支、循环、暂停恢复——单 Agent 写出来像意大利面&lt;/li&gt;
&lt;li&gt;需要不同&amp;quot;角色&amp;quot;各司其职——研究员、写作者、审核者，每个角色的 system prompt 差异很大&lt;/li&gt;
&lt;li&gt;需要人类在环审批——跑到一半暂停，等人确认后再继续&lt;/li&gt;
&lt;li&gt;编码任务需要沙箱隔离——Agent 生成的代码不能直接在宿主机跑&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;不该用的场景：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;简单问答/单步任务——引入多 Agent 是过度工程，直接用单 Agent 或 OpenAI API&lt;/li&gt;
&lt;li&gt;高确定性流水线——对话式控制流不可预测，用传统状态机/工作流引擎&lt;/li&gt;
&lt;li&gt;实时性要求高——LLM 调用延迟 + 多轮累积延迟，用缓存 + 异步处理&lt;/li&gt;
&lt;li&gt;无 LLM 的纯规则引擎——不需要 LLM 能力，用 transitions、state_machine 等库&lt;/li&gt;
&lt;li&gt;严格合规/审计——对话路径难以完全预测和审计，用确定性工作流引擎&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;一句话判断：&lt;/strong&gt; 如果你的任务可以写成&amp;quot;输入 → 处理 → 输出&amp;quot;三步，别用多 Agent。如果需要分支、循环、多角色协作、人类审批，再考虑。&lt;/p&gt;</description></item><item><title>你以为自己会用 PostgreSQL，其实你只是会写 SQL</title><link>https://blog.cpdd.fyi/posts/postgresql-system-thinking/</link><pubDate>Mon, 13 Apr 2026 17:18:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/postgresql-system-thinking/</guid><description>&lt;p&gt;很多开发者天天在用 PostgreSQL，但只要你追问三个问题，场面就会立刻安静下来：它为什么并发强？为什么崩了还能恢复？为什么 JSONB、大文本这些大字段没把系统拖死？&lt;/p&gt;
&lt;p&gt;如果这些问题你平时很少认真想过，那你多半还停留在“会用 PostgreSQL”的表层。说得再狠一点：很多人其实不是理解 PostgreSQL，只是会写 SQL。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/postgresql-system-thinking/cover.svg" alt="PostgreSQL 从表层使用到系统思维的认知升级封面图"&gt;&lt;/p&gt;</description></item><item><title>我把 Claude Code 改造成了自动化工程系统，不再陪它聊天了</title><link>https://blog.cpdd.fyi/posts/claude-code-workflow-complete-guide/</link><pubDate>Tue, 07 Apr 2026 15:00:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/claude-code-workflow-complete-guide/</guid><description>&lt;p&gt;你有没有这种感觉：&lt;/p&gt;
&lt;p&gt;用 Claude Code 的时候，每次都要重新交代背景。&lt;/p&gt;
&lt;p&gt;让它写个脚本，要说明项目结构；让它查个 bug，要解释代码逻辑；让它写篇文章，要重复一遍要求。&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;/p&gt;
&lt;p&gt;&lt;strong&gt;问题在于，你一直在陪它聊天，而不是让它工作。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2025 年的 Claude Code，早已不是单纯的聊天工具。&lt;/p&gt;
&lt;p&gt;通过五层扩展机制，你可以把它改造成一个可靠的自动化工程系统：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;CLAUDE.md 是记忆，Skills 是知识，Commands 是流程，Subagents 是团队，Hooks 是纪律。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;今天这篇文章，不聊概念，只给能落地的框架。&lt;/p&gt;
&lt;p&gt;看完之后，你可以直接照着搭建自己的工作流。&lt;/p&gt;</description></item><item><title>PostgreSQL 入门：为什么说它能装下世间万物？</title><link>https://blog.cpdd.fyi/posts/postgresql-intro-why-it-can-store-everything/</link><pubDate>Tue, 07 Apr 2026 12:16:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/postgresql-intro-why-it-can-store-everything/</guid><description>&lt;p&gt;这是世界上最先进的开源关系型数据库。&lt;/p&gt;
&lt;p&gt;PostgreSQL 的能力早已超越了传统关系型数据库的范围。现代开发中，为了解决各种问题，各类花哨的工具层出不穷——缓存用 Redis、全文检索用 Elasticsearch、文档存储用 MongoDB、向量数据库用专门的方案、定时任务跑在 K8s 上。&lt;/p&gt;
&lt;p&gt;但有时候，仅仅一个 PostgreSQL，就能覆盖大部分后端开发需求。&lt;/p&gt;</description></item><item><title>Redis 最容易踩的 7 个坑，问题往往不在 Redis</title><link>https://blog.cpdd.fyi/posts/redis-pitfalls-from-douyin/</link><pubDate>Sat, 04 Apr 2026 12:10:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/redis-pitfalls-from-douyin/</guid><description>&lt;p&gt;很多人觉得 Redis 没什么门槛。&lt;/p&gt;
&lt;p&gt;命令不难，接入也快，压测时还总能打出很漂亮的数字。于是项目上线后，大家默认它“应该不会出事”。&lt;/p&gt;
&lt;p&gt;但我看过不少 Redis 问题，最后都不是 Redis 自己不行，而是前面的设计太随便：Key 怎么拆，TTL 怎么设，热点怎么扛，内存打满以后怎么办，这些事一开始没想清楚，后面都会用线上故障补课。&lt;/p&gt;
&lt;p&gt;我最近刷到一条讲 Redis 避坑的短视频，里面提到的点很典型。我没有照着转写，而是按“线上最容易出事故的顺序”重新整理成这篇文章。&lt;/p&gt;
&lt;p&gt;如果你只记一句话，那就是：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Redis 真正危险的坑，大多不是命令层面的，而是设计层面的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/redis-pitfalls-cover.svg" alt="Redis 常见架构陷阱配图：中心是 Redis，周围是 Big Key、TTL、Hot、OOM 等风险信号"&gt;&lt;/p&gt;</description></item><item><title>Gemma 4 刚发布，我连夜把它装进了 OpenClaw</title><link>https://blog.cpdd.fyi/posts/gemma4-local-deployment/</link><pubDate>Fri, 03 Apr 2026 10:20:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/gemma4-local-deployment/</guid><description>&lt;blockquote&gt;
&lt;p&gt;昨天 Gemma 4 刚发，今天它已经跑在我本地了，而且接进了 OpenClaw。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;很多人一看到新模型发布，第一反应是去搜教程。&lt;/p&gt;
&lt;p&gt;然后就会看到两类内容：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;一堆参数表，读完还是不知道怎么装&lt;/li&gt;
&lt;li&gt;直接甩一句 &lt;code&gt;ollama pull xxx&lt;/code&gt;，结果你一跑就报错，因为库里根本还没有，或者版本不对&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以这篇文章我不聊空话，只讲一条真的能跑通的链路：&lt;/p&gt;
&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;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hf download
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;→ ollama create
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;→ OpenClaw 接入
&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;/p&gt;
&lt;ul&gt;
&lt;li&gt;你知道模型文件是从哪里来的&lt;/li&gt;
&lt;li&gt;你知道自己下的是哪个量化版本&lt;/li&gt;
&lt;li&gt;你不需要赌 Ollama 官方库有没有同步&lt;/li&gt;
&lt;li&gt;你装完就能在 OpenClaw 里用，不是“理论上可行”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你只想要一句结论，那就是：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;先别冲 31B，也别一上来折腾一堆转换脚本。先从 Gemma 4 E4B 开始，用 GGUF 跑通本地链路，这是最稳的。&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Qwen 3.5 小模型本地部署实战：在 OpenClaw 中跑出自己的 AI 助手</title><link>https://blog.cpdd.fyi/posts/qwen3-5-local-deployment/</link><pubDate>Thu, 05 Mar 2026 10:37:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/qwen3-5-local-deployment/</guid><description>&lt;blockquote&gt;
&lt;p&gt;阿里最新发布 0.8B-9B 端侧模型，10 分钟完成部署，显存最低 500MB。本文实测 4 种型号在 OpenClaw 中的表现，含完整配置、性能数据、踩坑记录。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="写在前面"&gt;写在前面&lt;/h2&gt;
&lt;p&gt;2026 年 3 月初，阿里通义千问发布了 Qwen 3.5 小模型家族（0.8B/2B/4B/9B）。没有发布会，但技术文档里的几个数字让我停下了手头的工作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0.8B 模型显存占用 &lt;strong&gt;~500MB&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;4B 模型支持 &lt;strong&gt;原生多模态&lt;/strong&gt;（非适配器方案）&lt;/li&gt;
&lt;li&gt;9B 模型用了 &lt;strong&gt;Scaled RL&lt;/strong&gt; 强化学习&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我花了一下午把这四个型号全部署到 OpenClaw 里跑了一遍。结论先行：&lt;strong&gt;端侧 AI 的拐点，可能真的到了&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;下面是完整实测报告，包含部署步骤、配置方法、性能数据和踩坑记录。你可以直接照着做。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一qwen-35-小模型家族规格"&gt;一、Qwen 3.5 小模型家族规格&lt;/h2&gt;
&lt;h3 id="11-型号对比"&gt;1.1 型号对比&lt;/h3&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;th&gt;VRAM 占用&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;0.8B&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;边缘设备/IoT&lt;/td&gt;
 &lt;td&gt;传感器数据处理、简单指令&lt;/td&gt;
 &lt;td&gt;~500MB&lt;/td&gt;
 &lt;td&gt;120 tokens/s&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;2B&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;移动端/轻量任务&lt;/td&gt;
 &lt;td&gt;聊天机器人、文本分类&lt;/td&gt;
 &lt;td&gt;~1.5GB&lt;/td&gt;
 &lt;td&gt;85 tokens/s&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;4B&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;轻量级 Agent&lt;/td&gt;
 &lt;td&gt;多模态任务、自动化流程&lt;/td&gt;
 &lt;td&gt;~3GB&lt;/td&gt;
 &lt;td&gt;65 tokens/s&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;9B&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;推理与逻辑&lt;/td&gt;
 &lt;td&gt;代码生成、复杂推理&lt;/td&gt;
 &lt;td&gt;~6GB&lt;/td&gt;
 &lt;td&gt;42 tokens/s&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;测试环境：MacBook Pro M3，16GB 统一内存，量化版本 Q4_K_M&lt;/p&gt;</description></item></channel></rss>