<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="http://172.19.0.16:8080/feed.xml" rel="self" type="application/atom+xml" /><link href="http://172.19.0.16:8080/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-09-14T17:42:40+08:00</updated><id>http://172.19.0.16:8080/feed.xml</id><title type="html">Gerry’s blog</title><subtitle>他山之石，可以攻玉</subtitle><entry><title type="html">Go JSON 序列化对比：json-iterator 与主流编解码库</title><link href="http://172.19.0.16:8080/posts/go-json-serialization/" rel="alternate" type="text/html" title="Go JSON 序列化对比：json-iterator 与主流编解码库" /><published>2026-08-19T10:20:29+08:00</published><updated>2026-08-20T16:43:22+08:00</updated><id>http://172.19.0.16:8080/posts/go-json-serialization</id><content type="html" xml:base="http://172.19.0.16:8080/posts/go-json-serialization/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#principles" id="markdown-toc-principles">原理与核心概念</a>    <ul>
      <li><a href="#json-编解码的两层工作" id="markdown-toc-json-编解码的两层工作">JSON 编解码的两层工作</a></li>
      <li><a href="#标准库为什么会成为热点" id="markdown-toc-标准库为什么会成为热点">标准库为什么会成为热点</a></li>
      <li><a href="#json-iterator-的加速策略" id="markdown-toc-json-iterator-的加速策略">json-iterator 的加速策略</a></li>
    </ul>
  </li>
  <li><a href="#usage" id="markdown-toc-usage">使用方法</a>    <ul>
      <li><a href="#安装与-drop-in-替换" id="markdown-toc-安装与-drop-in-替换">安装与 drop-in 替换</a></li>
      <li><a href="#三套预置-config" id="markdown-toc-三套预置-config">三套预置 Config</a></li>
      <li><a href="#一行读取get-与-any" id="markdown-toc-一行读取get-与-any">一行读取：<code class="language-plaintext highlighter-rouge">Get</code> 与 <code class="language-plaintext highlighter-rouge">Any</code></a>        <ul>
          <li><a href="#get-vs-map" id="markdown-toc-get-vs-map">为什么 Get 比 map[string]interface{} 快</a></li>
        </ul>
      </li>
      <li><a href="#复用-iterator--stream" id="markdown-toc-复用-iterator--stream">复用 Iterator / Stream</a></li>
      <li><a href="#自定义编解码少用-marshaljson" id="markdown-toc-自定义编解码少用-marshaljson">自定义编解码：少用 <code class="language-plaintext highlighter-rouge">MarshalJSON</code></a></li>
    </ul>
  </li>
  <li><a href="#examples" id="markdown-toc-examples">使用示例</a>    <ul>
      <li><a href="#结构体-marshal--unmarshal" id="markdown-toc-结构体-marshal--unmarshal">结构体 Marshal / Unmarshal</a></li>
      <li><a href="#兼容性差异map-key-是否排序" id="markdown-toc-兼容性差异map-key-是否排序">兼容性差异：map key 是否排序</a></li>
      <li><a href="#流式-encoder--decoder" id="markdown-toc-流式-encoder--decoder">流式 Encoder / Decoder</a></li>
      <li><a href="#只取路径不绑定结构体" id="markdown-toc-只取路径不绑定结构体">只取路径，不绑定结构体</a></li>
    </ul>
  </li>
  <li><a href="#landscape" id="markdown-toc-landscape">主流编解码库对照</a>    <ul>
      <li><a href="#encodingjson默认且正确的起点" id="markdown-toc-encodingjson默认且正确的起点">encoding/json：默认且正确的起点</a></li>
      <li><a href="#easyjson--ffjson编译期换运行时" id="markdown-toc-easyjson--ffjson编译期换运行时">easyjson / ffjson：编译期换运行时</a></li>
      <li><a href="#goccygo-json更认真的-drop-in" id="markdown-toc-goccygo-json更认真的-drop-in">goccy/go-json：更认真的 drop-in</a></li>
      <li><a href="#sonicjit--simd-的上限" id="markdown-toc-sonicjit--simd-的上限">sonic：JIT + SIMD 的上限</a></li>
      <li><a href="#只解析jsonparsersimdjson-goget" id="markdown-toc-只解析jsonparsersimdjson-goget">只解析：jsonparser、simdjson-go、Get</a></li>
      <li><a href="#encodingjsonv2标准库自己的下一代" id="markdown-toc-encodingjsonv2标准库自己的下一代">encoding/json/v2：标准库自己的下一代</a></li>
    </ul>
  </li>
  <li><a href="#benchmarks" id="markdown-toc-benchmarks">性能对比</a>    <ul>
      <li><a href="#官方-medium-payloadjsoniter-readme" id="markdown-toc-官方-medium-payloadjsoniter-readme">官方 medium payload（jsoniter README）</a></li>
      <li><a href="#字符串数组-unmarshal腾讯云社区2022" id="markdown-toc-字符串数组-unmarshal腾讯云社区2022">字符串数组 Unmarshal（腾讯云社区，2022）</a></li>
      <li><a href="#早期综合评测20172018" id="markdown-toc-早期综合评测20172018">早期综合评测（2017–2018）</a></li>
      <li><a href="#较新的绑定库对比sonic-官方13kb" id="markdown-toc-较新的绑定库对比sonic-官方13kb">较新的绑定库对比（sonic 官方，~13KB）</a></li>
    </ul>
  </li>
  <li><a href="#best-practices" id="markdown-toc-best-practices">最佳实践</a>    <ul>
      <li><a href="#先测量再换库" id="markdown-toc-先测量再换库">先测量，再换库</a></li>
      <li><a href="#按场景选型而不是按博客标题" id="markdown-toc-按场景选型而不是按博客标题">按场景选型，而不是按博客标题</a></li>
      <li><a href="#用兼容配置包一层避免项目里混用三种-json" id="markdown-toc-用兼容配置包一层避免项目里混用三种-json">用兼容配置包一层，避免项目里混用三种 JSON</a></li>
      <li><a href="#减少反射与分配比换库更常赚到" id="markdown-toc-减少反射与分配比换库更常赚到">减少反射与分配，比换库更常赚到</a></li>
      <li><a href="#行为要对齐测试而不只对齐文档" id="markdown-toc-行为要对齐测试而不只对齐文档">行为要对齐测试，而不只对齐文档</a></li>
    </ul>
  </li>
  <li><a href="#caveats" id="markdown-toc-caveats">注意事项</a></li>
  <li><a href="#summary" id="markdown-toc-summary">总结</a></li>
  <li><a href="#references" id="markdown-toc-references">参考资源</a>    <ul>
      <li><a href="#官方与源码" id="markdown-toc-官方与源码">官方与源码</a></li>
      <li><a href="#社区评测文中引用" id="markdown-toc-社区评测文中引用">社区评测（文中引用）</a></li>
      <li><a href="#站内交叉引用" id="markdown-toc-站内交叉引用">站内交叉引用</a></li>
    </ul>
  </li>
</ul>

<blockquote class="prompt-info">
  <p>本文对照 <a href="https://github.com/json-iterator/go">json-iterator/go</a>、<a href="https://jsoniter.com/migrate-from-go-std.html">从标准库迁移</a>、以及若干社区评测（<a href="https://cloud.tencent.com/developer/article/1966308">腾讯云开发者社区</a>、<a href="https://javasgl.github.io/go-json-iterator/">javasgl</a>、<a href="https://segmentfault.com/a/1190000013022780">SegmentFault</a>）整理。站内 <a href="/posts/go-reflect-in-action/">Go Reflect</a> 解释反射为何慢，<a href="/posts/go-performance-in-action/">Go Performance</a> 讨论分配与 GC；本文聚焦 JSON 这一高频路径。</p>
</blockquote>

<p>JSON 是 Go 服务里最常见的数据交换格式：HTTP API、消息队列、配置文件、RPC payload 几乎都会经过 <code class="language-plaintext highlighter-rouge">Marshal</code> / <code class="language-plaintext highlighter-rouge">Unmarshal</code>。<a href="https://go.dev/blog/jsonv2-exp">Go 官方博客</a> 指出 <code class="language-plaintext highlighter-rouge">encoding/json</code> 已是全生态导入量第五的包。对大多数程序它够用；一旦日志解析、网关转发、广告竞价这类路径变成 CPU 热点，反射、分配、字符串转义就会同时出现在 pprof 里。</p>

<p>社区因此走出两条路：<strong>保持 <code class="language-plaintext highlighter-rouge">encoding/json</code> API、换更快实现</strong>（jsoniter、goccy/go-json、sonic），以及 <strong>用代码生成彻底去掉运行时反射</strong>（easyjson、ffjson）。json-iterator 是前一条路上用得最广的库：号称 100% 兼容标准库、无需 <code class="language-plaintext highlighter-rouge">go generate</code>，Kubernetes 等项目也曾引入。2025 年 12 月仓库被归档，选型时必须把「存量代码怎么迁」和「新项目还要不要用」分开看。</p>

<p>本文将覆盖：</p>

<ul>
  <li>原理：标准库慢在哪里，jsoniter 快在哪里</li>
  <li>用法：drop-in 替换、<code class="language-plaintext highlighter-rouge">Get</code> 路径读取、三套 Config</li>
  <li>示例：结构体绑定、流式编解码、自定义编码器</li>
  <li>对比：主流库的策略、兼容性与公开基准</li>
  <li>最佳实践与注意事项（含归档现状与 <code class="language-plaintext highlighter-rouge">json/v2</code>）</li>
</ul>

<h1 id="principles">原理与核心概念</h1>

<h2 id="json-编解码的两层工作">JSON 编解码的两层工作</h2>

<p>一次 JSON 往返可以拆成两层，这也是 Go 1.25 实验包 <a href="https://pkg.go.dev/encoding/json/jsontext"><code class="language-plaintext highlighter-rouge">encoding/json/jsontext</code></a> 与 <a href="https://pkg.go.dev/encoding/json/v2"><code class="language-plaintext highlighter-rouge">encoding/json/v2</code></a> 刻意分开的边界：</p>

<table>
  <thead>
    <tr>
      <th>层次</th>
      <th>关心的问题</th>
      <th>典型 API</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>语法（encode / decode）</strong></td>
      <td>字节流是否合法 JSON：token、字符串转义、数字扫描</td>
      <td><code class="language-plaintext highlighter-rouge">Decoder.Token</code>、jsoniter <code class="language-plaintext highlighter-rouge">Iterator</code> / <code class="language-plaintext highlighter-rouge">Stream</code></td>
    </tr>
    <tr>
      <td><strong>语义（marshal / unmarshal）</strong></td>
      <td>JSON 值如何对应 Go 值：结构体 tag、<code class="language-plaintext highlighter-rouge">omitempty</code>、<code class="language-plaintext highlighter-rouge">MarshalJSON</code></td>
      <td><code class="language-plaintext highlighter-rouge">json.Marshal</code>、<code class="language-plaintext highlighter-rouge">json.Unmarshal</code></td>
    </tr>
  </tbody>
</table>

<pre><code class="language-mermaid">flowchart TB
    subgraph semantic [语义层：Go 值 ↔ JSON 值]
        ST[struct / map / slice]
        TAG["json tag、MarshalJSON"]
    end
    subgraph syntax [语法层：JSON-text]
        TOK[token / value]
        BYTES["[]byte / io.Reader"]
    end
    ST &lt;--&gt; TAG
    TAG &lt;--&gt; TOK
    TOK &lt;--&gt; BYTES
</code></pre>

<p>标准库把两层揉在同一个包里，实现简单，但 <code class="language-plaintext highlighter-rouge">MarshalJSON</code> / <code class="language-plaintext highlighter-rouge">UnmarshalJSON</code> 的契约会强迫实现方先拼出完整 <code class="language-plaintext highlighter-rouge">[]byte</code>，再由库二次校验、再解析——嵌套自定义类型时复杂度接近二次。第三方库要么绕开这套接口（jsoniter 的 <code class="language-plaintext highlighter-rouge">RegisterTypeEncoder</code>），要么重新设计流式接口（<code class="language-plaintext highlighter-rouge">json/v2</code> 的 <code class="language-plaintext highlighter-rouge">MarshalJSONTo</code>）。</p>

<h2 id="标准库为什么会成为热点">标准库为什么会成为热点</h2>

<p><code class="language-plaintext highlighter-rouge">encoding/json</code> 用反射在运行时发现类型：字段名、tag、嵌套关系、<code class="language-plaintext highlighter-rouge">json.Marshaler</code> 是否实现。反射本身不便宜（见 <a href="/posts/go-reflect-in-action/">Go Reflect in Action</a>），再叠上下面几件事，热点就更明显：</p>

<ol>
  <li><strong>每次调用都要走类型分支</strong>。热路径上同一类型会被反复 <code class="language-plaintext highlighter-rouge">reflect.TypeOf</code> / <code class="language-plaintext highlighter-rouge">Value.Field</code>。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">MarshalJSON</code> 强制分配</strong>。方法签名返回 <code class="language-plaintext highlighter-rouge">[]byte</code>，库还要校验并按缩进重排。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">UnmarshalJSON</code> 要先切出完整值</strong>。库解析一遍定位边界，用户方法再解析一遍。</li>
  <li><strong>Encoder/Decoder 并不真正流式</strong>。即便对着 <code class="language-plaintext highlighter-rouge">io.Reader</code>，也会把整个 JSON 值缓冲进内存。</li>
  <li><strong>默认行为偏宽松、偏安全</strong>。HTML 转义、map key 排序、大小写不敏感匹配，都会换性能。</li>
</ol>

<p>这些限制大部分写进了公开 API，Go 1 兼容承诺下很难在 <code class="language-plaintext highlighter-rouge">encoding/json</code> 里直接改掉，这也是后来要做 <code class="language-plaintext highlighter-rouge">json/v2</code> 的原因。</p>

<blockquote class="prompt-tip">
  <p>结构体字段必须导出，标准库才能用反射写入。这是 <a href="/posts/100-go-mistakes/">100 Go Mistakes</a> 里讨论可见性时专门列出的例外。</p>
</blockquote>

<h2 id="json-iterator-的加速策略">json-iterator 的加速策略</h2>

<p><a href="https://github.com/json-iterator/go">json-iterator/go</a> 由滴滴开源，定位是 <strong>high-performance、100% compatible drop-in replacement</strong>。它不走 easyjson 那套静态代码生成，而是在运行时把「这个类型该怎么编解码」做成可缓存的编解码器：</p>

<ol>
  <li><strong>类型元数据只反射一次</strong>。第一次遇到 <code class="language-plaintext highlighter-rouge">*ColorGroup</code> 时构建字段描述符（名字、offset、elem 类型），之后复用。</li>
  <li><strong>Iterator / Stream 分离语法层</strong>。读走 <code class="language-plaintext highlighter-rouge">Iterator</code>，写走 <code class="language-plaintext highlighter-rouge">Stream</code>，避免标准库那种「先整段缓冲再语义化」的路径。</li>
  <li><strong>小结构体用 switch 而不是 map</strong>。字段数较少时，按 hash 做 <code class="language-plaintext highlighter-rouge">switch-case</code> 分发，比 <code class="language-plaintext highlighter-rouge">map[string]field</code> 更便宜（也因此存在 hash 碰撞讨论，见 <a href="https://github.com/goccy/go-json">goccy/go-json README</a>）。</li>
  <li><strong>对象池</strong>。<code class="language-plaintext highlighter-rouge">BorrowIterator</code> / <code class="language-plaintext highlighter-rouge">BorrowStream</code> 把扫描器和写缓冲放进 <code class="language-plaintext highlighter-rouge">sync.Pool</code>，降低分配。</li>
  <li><strong>可配置的「少做一点」</strong>。<code class="language-plaintext highlighter-rouge">ConfigFastest</code> 关掉 HTML 转义、用 6 位浮点、假设 object key 是简单字符串，用正确性换吞吐。</li>
</ol>

<pre><code class="language-mermaid">flowchart LR
    IN["[]byte JSON"] --&gt; ITER[Iterator]
    ITER --&gt; CACHE[encoder/decoder 缓存]
    CACHE --&gt; GO[Go 值]
    GO --&gt; ENC[ValEncoder]
    ENC --&gt; STREAM[Stream]
    STREAM --&gt; OUT["[]byte JSON"]
    POOL[(sync.Pool)] -.-&gt; ITER
    POOL -.-&gt; STREAM
</code></pre>

<p>和标准库一样，它仍承认 <code class="language-plaintext highlighter-rouge">json.Marshaler</code> / <code class="language-plaintext highlighter-rouge">json.Unmarshaler</code>、<code class="language-plaintext highlighter-rouge">NewEncoder</code> / <code class="language-plaintext highlighter-rouge">NewDecoder</code>、非 string key 的 map。官方迁移文档强调：<strong>现有实现了这两个接口的类型，以及 <code class="language-plaintext highlighter-rouge">Unmarshal</code> / <code class="language-plaintext highlighter-rouge">NewEncoder</code> / <code class="language-plaintext highlighter-rouge">NewDecoder</code>，都可以原样工作</strong>。</p>

<blockquote class="prompt-warning">
  <p>仓库已于 <strong>2025-12-15 归档</strong>（read-only）。最新正式版仍是 2021 年的 <code class="language-plaintext highlighter-rouge">v1.1.12</code>。它适合理解「缓存反射 + Iterator」这条技术路线，以及维护存量代码；<strong>新项目不应再把它当作默认依赖</strong>。</p>
</blockquote>

<h1 id="usage">使用方法</h1>

<h2 id="安装与-drop-in-替换">安装与 drop-in 替换</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>go get github.com/json-iterator/go
</code></pre></div></div>

<p>最小迁移来自 <a href="https://jsoniter.com/migrate-from-go-std.html">Migrate from go standard library</a>：把 import 换成 jsoniter，再用兼容配置包一层 <code class="language-plaintext highlighter-rouge">json</code> 变量，调用点不用改。</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="n">jsoniter</span> <span class="s">"github.com/json-iterator/go"</span>

<span class="k">var</span> <span class="n">json</span> <span class="o">=</span> <span class="n">jsoniter</span><span class="o">.</span><span class="n">ConfigCompatibleWithStandardLibrary</span>
</code></pre></div></div>

<p>之后 <code class="language-plaintext highlighter-rouge">json.Marshal</code>、<code class="language-plaintext highlighter-rouge">json.Unmarshal</code>、<code class="language-plaintext highlighter-rouge">json.NewEncoder</code>、<code class="language-plaintext highlighter-rouge">json.NewDecoder</code> 的用法与标准库相同。若只替换函数名而不包一层变量，也可以直接 <code class="language-plaintext highlighter-rouge">jsoniter.Marshal</code>，但默认配置 <strong>不会</strong> 排序 map key，字节级结果可能和标准库不同。</p>

<h2 id="三套预置-config">三套预置 Config</h2>

<p><code class="language-plaintext highlighter-rouge">Config</code> 经 <code class="language-plaintext highlighter-rouge">Froze()</code> 变成不可变的 <code class="language-plaintext highlighter-rouge">API</code>。源码里三套预置如下：</p>

<table>
  <thead>
    <tr>
      <th>配置</th>
      <th>行为要点</th>
      <th>适用</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ConfigDefault</code></td>
      <td><code class="language-plaintext highlighter-rouge">EscapeHTML: true</code></td>
      <td>一般替换，不追求字节级一致</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ConfigCompatibleWithStandardLibrary</code></td>
      <td>HTML 转义 + <strong>排序 map key</strong> + 校验 <code class="language-plaintext highlighter-rouge">RawMessage</code></td>
      <td>要和标准库输出对齐、单测比黄金文件</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ConfigFastest</code></td>
      <td>不转义 HTML；float 只保留 6 位有效数字；object key 当简单字符串</td>
      <td>吞吐优先、可接受精度与转义差异</td>
    </tr>
  </tbody>
</table>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="n">Config</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="n">IndentionStep</span>                 <span class="kt">int</span>
    <span class="n">MarshalFloatWith6Digits</span>       <span class="kt">bool</span>
    <span class="n">EscapeHTML</span>                    <span class="kt">bool</span>
    <span class="n">SortMapKeys</span>                   <span class="kt">bool</span>
    <span class="n">UseNumber</span>                     <span class="kt">bool</span>
    <span class="n">DisallowUnknownFields</span>         <span class="kt">bool</span>
    <span class="n">TagKey</span>                        <span class="kt">string</span>
    <span class="n">OnlyTaggedField</span>               <span class="kt">bool</span>
    <span class="n">ValidateJsonRawMessage</span>        <span class="kt">bool</span>
    <span class="n">ObjectFieldMustBeSimpleString</span> <span class="kt">bool</span>
    <span class="n">CaseSensitive</span>                 <span class="kt">bool</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote class="prompt-danger">
  <p><code class="language-plaintext highlighter-rouge">ConfigFastest</code> 的浮点是有损的。金额、地理坐标、科学计算不要用这档；需要 100% 兼容时用 <code class="language-plaintext highlighter-rouge">ConfigCompatibleWithStandardLibrary</code>。</p>
</blockquote>

<p>也可以自己 <code class="language-plaintext highlighter-rouge">Config{...}.Froze()</code>，例如打开 <code class="language-plaintext highlighter-rouge">DisallowUnknownFields</code>、改 <code class="language-plaintext highlighter-rouge">TagKey</code>，或设 <code class="language-plaintext highlighter-rouge">CaseSensitive: true</code> 避免标准库那种大小写不敏感匹配。</p>

<h2 id="一行读取get-与-any">一行读取：<code class="language-plaintext highlighter-rouge">Get</code> 与 <code class="language-plaintext highlighter-rouge">Any</code></h2>

<p>不必定义结构体时，用 <code class="language-plaintext highlighter-rouge">Get</code> 按路径取值，比先 <code class="language-plaintext highlighter-rouge">Unmarshal</code> 进 <code class="language-plaintext highlighter-rouge">map[string]interface{}</code> 更便宜、也更好读：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">val</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">(</span><span class="s">`{"ID":1,"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]}`</span><span class="p">)</span>
<span class="n">jsoniter</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="n">val</span><span class="p">,</span> <span class="s">"Colors"</span><span class="p">,</span> <span class="m">0</span><span class="p">)</span><span class="o">.</span><span class="n">ToString</span><span class="p">()</span> <span class="c">// "Crimson"</span>
</code></pre></div></div>

<p>路径参数是 <code class="language-plaintext highlighter-rouge">interface{}</code>：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">string</code>：进 object</li>
  <li><code class="language-plaintext highlighter-rouge">int</code>：进 array</li>
  <li><code class="language-plaintext highlighter-rouge">"*"</code>：映射到 array 每个元素或 object 每个 key</li>
</ul>

<p>返回值是 <code class="language-plaintext highlighter-rouge">Any</code>，可 <code class="language-plaintext highlighter-rouge">ToString</code> / <code class="language-plaintext highlighter-rouge">ToInt</code> / <code class="language-plaintext highlighter-rouge">ToBool</code> 或继续 <code class="language-plaintext highlighter-rouge">Get</code>。适合日志里只抽一两个字段、网关只读 <code class="language-plaintext highlighter-rouge">code</code>/<code class="language-plaintext highlighter-rouge">msg</code> 的场景。若字段很多且类型稳定，结构体绑定仍然更快、更安全。</p>

<h3 id="get-vs-map">为什么 Get 比 map[string]interface{} 快</h3>

<p>两条路径做的不是同一份工作。<code class="language-plaintext highlighter-rouge">Unmarshal</code> 进 <code class="language-plaintext highlighter-rouge">map[string]interface{}</code> 是先把整份 JSON <strong>建成一棵通用 DOM</strong>，再从树里取字段；<code class="language-plaintext highlighter-rouge">Get</code> 是在字节流上 <strong>按路径定位，路过的节点只 skip、不解码</strong>。官方迁移文档也写了这一点：比解析进 <code class="language-plaintext highlighter-rouge">map[string]interface{}</code> 更快，也更好读。源码在 <a href="https://github.com/json-iterator/go/blob/v1.1.12/any.go"><code class="language-plaintext highlighter-rouge">locatePath</code> / <code class="language-plaintext highlighter-rouge">locateObjectField</code></a>。</p>

<pre><code class="language-mermaid">flowchart TB
    JSON[JSON 字节]
    JSON --&gt; MAP[Unmarshal 进通用 map]
    JSON --&gt; GET[jsoniter Get 按路径]
    MAP --&gt; TREE[为每个字段建 map 和 slice]
    TREE --&gt; IDX[再从 map 里断言取值]
    GET --&gt; SCAN[扫 key 不匹配则 Skip]
    SCAN --&gt; HIT[命中字段切出原始字节]
    HIT --&gt; LAZY[Any 保持 lazy]
    LAZY --&gt; TO[ToString 或 ToInt 才解析]
</code></pre>

<p>以 <code class="language-plaintext highlighter-rouge">Get(val, "Colors", 0)</code> 为例，Iterator 会：</p>

<ol>
  <li>扫顶层 object 的 key。不是 <code class="language-plaintext highlighter-rouge">Colors</code> 的，对整段 value 调 <code class="language-plaintext highlighter-rouge">Skip()</code>：按括号配对跳过字符串、数字、嵌套结构，<strong>不分配 Go 值</strong>。</li>
  <li>碰到 <code class="language-plaintext highlighter-rouge">Colors</code>，用 <code class="language-plaintext highlighter-rouge">SkipAndReturnBytes()</code> 把这段 value 的原始字节切出来，再在数组里数到下标 <code class="language-plaintext highlighter-rouge">0</code>。</li>
  <li>只把这一个元素读成 <code class="language-plaintext highlighter-rouge">Any</code>。object / array / number 往往还是 lazy 的 <code class="language-plaintext highlighter-rouge">*objectLazyAny</code> 等，内部仍是 <code class="language-plaintext highlighter-rouge">[]byte</code>，直到 <code class="language-plaintext highlighter-rouge">ToString()</code> / <code class="language-plaintext highlighter-rouge">ToInt()</code> 才解析。</li>
</ol>

<p><code class="language-plaintext highlighter-rouge">locateObjectField</code> 的逻辑可以直接读成「匹配才留下字节，否则 skip」：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">locateObjectField</span><span class="p">(</span><span class="n">iter</span> <span class="o">*</span><span class="n">Iterator</span><span class="p">,</span> <span class="n">target</span> <span class="kt">string</span><span class="p">)</span> <span class="p">[]</span><span class="kt">byte</span> <span class="p">{</span>
    <span class="k">var</span> <span class="n">found</span> <span class="p">[]</span><span class="kt">byte</span>
    <span class="n">iter</span><span class="o">.</span><span class="n">ReadObjectCB</span><span class="p">(</span><span class="k">func</span><span class="p">(</span><span class="n">iter</span> <span class="o">*</span><span class="n">Iterator</span><span class="p">,</span> <span class="n">field</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span>
        <span class="k">if</span> <span class="n">field</span> <span class="o">==</span> <span class="n">target</span> <span class="p">{</span>
            <span class="n">found</span> <span class="o">=</span> <span class="n">iter</span><span class="o">.</span><span class="n">SkipAndReturnBytes</span><span class="p">()</span>
            <span class="k">return</span> <span class="no">false</span>
        <span class="p">}</span>
        <span class="n">iter</span><span class="o">.</span><span class="n">Skip</span><span class="p">()</span>
        <span class="k">return</span> <span class="no">true</span>
    <span class="p">})</span>
    <span class="k">return</span> <span class="n">found</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">map[string]interface{}</code> 则每一层都要付完整语义转换的代价：</p>

<table>
  <thead>
    <tr>
      <th>开销</th>
      <th><code class="language-plaintext highlighter-rouge">Unmarshal</code> → <code class="language-plaintext highlighter-rouge">map[string]interface{}</code></th>
      <th><code class="language-plaintext highlighter-rouge">Get</code> 按路径</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>扫描范围</td>
      <td>整份文档</td>
      <td>路径上的 key，以及为找路径而 skip 的兄弟节点</td>
    </tr>
    <tr>
      <td>分配</td>
      <td>每个 object 一张 map、每个 array 一个 slice、每个 string 一份拷贝</td>
      <td>基本只有命中路径上的那几段</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">interface{}</code> 装箱</td>
      <td>每个数字 / bool 都装箱上堆</td>
      <td>目标值才装箱，或 <code class="language-plaintext highlighter-rouge">ToInt</code> 直接成标量</td>
    </tr>
    <tr>
      <td>嵌套</td>
      <td>深层结构整棵树都建好</td>
      <td>未走到的子树保持原始字节</td>
    </tr>
    <tr>
      <td>取完之后</td>
      <td>还要断言 <code class="language-plaintext highlighter-rouge">m["Colors"].([]interface{})[0].(string)</code></td>
      <td><code class="language-plaintext highlighter-rouge">ToString()</code> 直接取出</td>
    </tr>
  </tbody>
</table>

<p>所以快的本质是：<strong>少做语义转换，多做字节级跳过</strong>。jsoniter 主页把这套 <code class="language-plaintext highlighter-rouge">Any</code> 称为 lazy parsing：没读到的部分保持 JSON 原文，性能会明显好于 <code class="language-plaintext highlighter-rouge">Map&lt;String, Object&gt;</code> 那种通用 DOM。</p>

<p>两个边界：</p>

<ul>
  <li>目标字段在很大的 object <strong>末尾</strong>时，前面的 key 仍要扫一遍。skip 比解码便宜，但不是零成本。</li>
  <li>随后对 <code class="language-plaintext highlighter-rouge">Any</code> 调 <code class="language-plaintext highlighter-rouge">GetInterface()</code>、<code class="language-plaintext highlighter-rouge">Keys()</code>，或把整棵树走完，lazy 优势就没了，接近又做了一次通用解码。</li>
</ul>

<blockquote class="prompt-tip">
  <p>只抽一两个字段时用 <code class="language-plaintext highlighter-rouge">Get</code>（以及 jsonparser、gjson）。字段几乎都要用，还是具体结构体更快、更安全，不要为了「不用定义类型」先 Unmarshal 成 <code class="language-plaintext highlighter-rouge">map[string]any</code>。</p>
</blockquote>

<h2 id="复用-iterator--stream">复用 Iterator / Stream</h2>

<p>官方「绝对性能」清单里，除了 <code class="language-plaintext highlighter-rouge">ConfigFastest</code>，就是把底层实例还回池子：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">iter</span> <span class="o">:=</span> <span class="n">jsoniter</span><span class="o">.</span><span class="n">ConfigFastest</span><span class="o">.</span><span class="n">BorrowIterator</span><span class="p">(</span><span class="n">buf</span><span class="p">)</span>
<span class="k">defer</span> <span class="n">jsoniter</span><span class="o">.</span><span class="n">ConfigFastest</span><span class="o">.</span><span class="n">ReturnIterator</span><span class="p">(</span><span class="n">iter</span><span class="p">)</span>
<span class="n">iter</span><span class="o">.</span><span class="n">ReadVal</span><span class="p">(</span><span class="o">&amp;</span><span class="n">out</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">stream</span> <span class="o">:=</span> <span class="n">jsoniter</span><span class="o">.</span><span class="n">ConfigFastest</span><span class="o">.</span><span class="n">BorrowStream</span><span class="p">(</span><span class="no">nil</span><span class="p">)</span>
<span class="k">defer</span> <span class="n">jsoniter</span><span class="o">.</span><span class="n">ConfigFastest</span><span class="o">.</span><span class="n">ReturnStream</span><span class="p">(</span><span class="n">stream</span><span class="p">)</span>
<span class="n">stream</span><span class="o">.</span><span class="n">WriteVal</span><span class="p">(</span><span class="n">v</span><span class="p">)</span>
<span class="n">b</span> <span class="o">:=</span> <span class="nb">append</span><span class="p">([]</span><span class="kt">byte</span><span class="p">(</span><span class="no">nil</span><span class="p">),</span> <span class="n">stream</span><span class="o">.</span><span class="n">Buffer</span><span class="p">()</span><span class="o">...</span><span class="p">)</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Marshal</code> 内部已经在借还 Stream；热点路径若自己拼 JSON 或反复解析同一形状的报文，显式借还可以少几次 pool miss。<strong>必须成对 Return</strong>，否则等于泄漏缓冲。</p>

<h2 id="自定义编解码少用-marshaljson">自定义编解码：少用 <code class="language-plaintext highlighter-rouge">MarshalJSON</code></h2>

<p>标准库接口会多一次拷贝与校验。jsoniter 提供 <code class="language-plaintext highlighter-rouge">RegisterTypeEncoder</code> / <code class="language-plaintext highlighter-rouge">RegisterTypeDecoder</code>，直接写 <code class="language-plaintext highlighter-rouge">Stream</code> / <code class="language-plaintext highlighter-rouge">Iterator</code>：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jsoniter</span><span class="o">.</span><span class="n">RegisterTypeEncoderFunc</span><span class="p">(</span><span class="s">"time.Time"</span><span class="p">,</span> <span class="k">func</span><span class="p">(</span><span class="n">ptr</span> <span class="n">unsafe</span><span class="o">.</span><span class="n">Pointer</span><span class="p">,</span> <span class="n">stream</span> <span class="o">*</span><span class="n">jsoniter</span><span class="o">.</span><span class="n">Stream</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">t</span> <span class="o">:=</span> <span class="o">*</span><span class="p">((</span><span class="o">*</span><span class="n">time</span><span class="o">.</span><span class="n">Time</span><span class="p">)(</span><span class="n">ptr</span><span class="p">))</span>
    <span class="n">stream</span><span class="o">.</span><span class="n">WriteString</span><span class="p">(</span><span class="n">t</span><span class="o">.</span><span class="n">UTC</span><span class="p">()</span><span class="o">.</span><span class="n">Format</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">RFC3339Nano</span><span class="p">))</span>
<span class="p">},</span> <span class="k">func</span><span class="p">(</span><span class="n">ptr</span> <span class="n">unsafe</span><span class="o">.</span><span class="n">Pointer</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">(</span><span class="o">*</span><span class="n">time</span><span class="o">.</span><span class="n">Time</span><span class="p">)(</span><span class="n">ptr</span><span class="p">)</span><span class="o">.</span><span class="n">IsZero</span><span class="p">()</span>
<span class="p">})</span>
</code></pre></div></div>

<p>这是典型的「为性能放弃一点可移植性」：编码器绑在 jsoniter 上，换回标准库就失效。新代码若还要兼容 <code class="language-plaintext highlighter-rouge">encoding/json</code>，继续实现 <code class="language-plaintext highlighter-rouge">MarshalJSON</code> 更稳妥。</p>

<h1 id="examples">使用示例</h1>

<h2 id="结构体-marshal--unmarshal">结构体 Marshal / Unmarshal</h2>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">import</span> <span class="p">(</span>
    <span class="s">"fmt"</span>
    <span class="s">"log"</span>

    <span class="n">jsoniter</span> <span class="s">"github.com/json-iterator/go"</span>
<span class="p">)</span>

<span class="k">var</span> <span class="n">json</span> <span class="o">=</span> <span class="n">jsoniter</span><span class="o">.</span><span class="n">ConfigCompatibleWithStandardLibrary</span>

<span class="k">type</span> <span class="n">ColorGroup</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="n">ID</span>     <span class="kt">int</span>
    <span class="n">Name</span>   <span class="kt">string</span>
    <span class="n">Colors</span> <span class="p">[]</span><span class="kt">string</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">group</span> <span class="o">:=</span> <span class="n">ColorGroup</span><span class="p">{</span>
        <span class="n">ID</span><span class="o">:</span>     <span class="m">1</span><span class="p">,</span>
        <span class="n">Name</span><span class="o">:</span>   <span class="s">"Reds"</span><span class="p">,</span>
        <span class="n">Colors</span><span class="o">:</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{</span><span class="s">"Crimson"</span><span class="p">,</span> <span class="s">"Red"</span><span class="p">,</span> <span class="s">"Ruby"</span><span class="p">,</span> <span class="s">"Maroon"</span><span class="p">},</span>
    <span class="p">}</span>
    <span class="n">b</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">json</span><span class="o">.</span><span class="n">Marshal</span><span class="p">(</span><span class="n">group</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
        <span class="n">log</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="kt">string</span><span class="p">(</span><span class="n">b</span><span class="p">))</span>

    <span class="k">var</span> <span class="n">out</span> <span class="n">ColorGroup</span>
    <span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">json</span><span class="o">.</span><span class="n">Unmarshal</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">out</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
        <span class="n">log</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"%+v</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">out</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>输出应与 <code class="language-plaintext highlighter-rouge">encoding/json</code> 一致：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{"ID":1,"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]}
</code></pre></div></div>

<h2 id="兼容性差异map-key-是否排序">兼容性差异：map key 是否排序</h2>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">m</span> <span class="o">:=</span> <span class="k">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="k">interface</span><span class="p">{}{</span>
    <span class="s">"3"</span><span class="o">:</span> <span class="m">3</span><span class="p">,</span>
    <span class="s">"1"</span><span class="o">:</span> <span class="m">1</span><span class="p">,</span>
    <span class="s">"2"</span><span class="o">:</span> <span class="m">2</span><span class="p">,</span>
<span class="p">}</span>

<span class="n">b1</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">jsoniter</span><span class="o">.</span><span class="n">Marshal</span><span class="p">(</span><span class="n">m</span><span class="p">)</span> <span class="c">// ConfigDefault：key 顺序不稳定</span>
<span class="n">b2</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">jsoniter</span><span class="o">.</span><span class="n">ConfigCompatibleWithStandardLibrary</span><span class="o">.</span><span class="n">Marshal</span><span class="p">(</span><span class="n">m</span><span class="p">)</span>
</code></pre></div></div>

<p>单测若用 <code class="language-plaintext highlighter-rouge">bytes.Equal</code> 对比黄金 JSON，必须用兼容配置，否则会随机失败。</p>

<h2 id="流式-encoder--decoder">流式 Encoder / Decoder</h2>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">enc</span> <span class="o">:=</span> <span class="n">json</span><span class="o">.</span><span class="n">NewEncoder</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">Stdout</span><span class="p">)</span>
<span class="n">enc</span><span class="o">.</span><span class="n">SetIndent</span><span class="p">(</span><span class="s">""</span><span class="p">,</span> <span class="s">"  "</span><span class="p">)</span>
<span class="n">_</span> <span class="o">=</span> <span class="n">enc</span><span class="o">.</span><span class="n">Encode</span><span class="p">(</span><span class="n">group</span><span class="p">)</span>

<span class="n">dec</span> <span class="o">:=</span> <span class="n">json</span><span class="o">.</span><span class="n">NewDecoder</span><span class="p">(</span><span class="n">bytes</span><span class="o">.</span><span class="n">NewReader</span><span class="p">(</span><span class="n">payload</span><span class="p">))</span>
<span class="k">for</span> <span class="p">{</span>
    <span class="k">var</span> <span class="n">item</span> <span class="n">ColorGroup</span>
    <span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">dec</span><span class="o">.</span><span class="n">Decode</span><span class="p">(</span><span class="o">&amp;</span><span class="n">item</span><span class="p">);</span> <span class="n">err</span> <span class="o">==</span> <span class="n">io</span><span class="o">.</span><span class="n">EOF</span> <span class="p">{</span>
        <span class="k">break</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
        <span class="n">log</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>API 形状与标准库相同，便于网关、NDJSON 日志这类「一条一条吐」的场景。</p>

<h2 id="只取路径不绑定结构体">只取路径，不绑定结构体</h2>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">raw</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">(</span><span class="s">`{"user":{"id":42,"roles":["admin","ops"]},"ok":true}`</span><span class="p">)</span>
<span class="n">id</span> <span class="o">:=</span> <span class="n">jsoniter</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="n">raw</span><span class="p">,</span> <span class="s">"user"</span><span class="p">,</span> <span class="s">"id"</span><span class="p">)</span><span class="o">.</span><span class="n">ToInt</span><span class="p">()</span>
<span class="n">role0</span> <span class="o">:=</span> <span class="n">jsoniter</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="n">raw</span><span class="p">,</span> <span class="s">"user"</span><span class="p">,</span> <span class="s">"roles"</span><span class="p">,</span> <span class="m">0</span><span class="p">)</span><span class="o">.</span><span class="n">ToString</span><span class="p">()</span>
</code></pre></div></div>

<p>适合 schema 不稳定、或只关心几个字段的遥测数据。底层为何比 <code class="language-plaintext highlighter-rouge">map[string]interface{}</code> 便宜，见上文 <a href="#get-vs-map">为什么 Get 比 map[string]interface{} 快</a>。错误路径上 <code class="language-plaintext highlighter-rouge">Any</code> 会变成 invalid，调用方应检查 <code class="language-plaintext highlighter-rouge">LastError()</code> / <code class="language-plaintext highlighter-rouge">ValueType()</code>，不要默认 <code class="language-plaintext highlighter-rouge">ToInt()</code> 的零值就是业务零。</p>

<h1 id="landscape">主流编解码库对照</h1>

<p>社区里常见的库可以按「要不要结构体绑定」和「快从哪里来」分组：</p>

<pre><code class="language-mermaid">flowchart TB
    subgraph bind [结构体绑定]
        STD["encoding/json&lt;br/&gt;反射基线"]
        JI["json-iterator&lt;br/&gt;缓存反射 + Iterator"]
        GJ["goccy/go-json&lt;br/&gt;opcode + typeptr"]
        SN["sonic&lt;br/&gt;JIT + SIMD"]
        EJ["easyjson / ffjson&lt;br/&gt;代码生成"]
        V2["encoding/json/v2&lt;br/&gt;实验，语法语义分离"]
    end
    subgraph parse [只解析、不绑定]
        GET["jsoniter.Get / sonic Get"]
        JP["buger/jsonparser"]
        SIMD["simdjson-go"]
        GJSON["tidwall/gjson"]
    end
</code></pre>

<table>
  <thead>
    <tr>
      <th>库</th>
      <th>策略</th>
      <th>与 <code class="language-plaintext highlighter-rouge">encoding/json</code></th>
      <th>生成代码</th>
      <th>维护状态（2026）</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="https://pkg.go.dev/encoding/json"><code class="language-plaintext highlighter-rouge">encoding/json</code></a></td>
      <td>反射</td>
      <td>自身</td>
      <td>否</td>
      <td>标准库，持续演进</td>
    </tr>
    <tr>
      <td><a href="https://github.com/json-iterator/go"><code class="language-plaintext highlighter-rouge">json-iterator/go</code></a></td>
      <td>缓存反射 + Iterator</td>
      <td>宣称 100%，实际 <a href="https://github.com/json-iterator/go/issues/229">有差异</a></td>
      <td>否</td>
      <td><strong>已归档</strong></td>
    </tr>
    <tr>
      <td><a href="https://github.com/mailru/easyjson"><code class="language-plaintext highlighter-rouge">mailru/easyjson</code></a></td>
      <td>静态生成 <code class="language-plaintext highlighter-rouge">MarshalJSON</code></td>
      <td>刻意砍掉部分特性换速度</td>
      <td>是</td>
      <td>仍常用</td>
    </tr>
    <tr>
      <td><a href="https://github.com/pquerna/ffjson"><code class="language-plaintext highlighter-rouge">pquerna/ffjson</code></a></td>
      <td>静态生成</td>
      <td>接近标准库</td>
      <td>是</td>
      <td>基本停滞</td>
    </tr>
    <tr>
      <td><a href="https://github.com/goccy/go-json"><code class="language-plaintext highlighter-rouge">goccy/go-json</code></a></td>
      <td>typeptr 缓存 + opcode VM</td>
      <td>目标完全兼容</td>
      <td>否</td>
      <td>活跃，推荐的 drop-in 之一</td>
    </tr>
    <tr>
      <td><a href="https://github.com/bytedance/sonic"><code class="language-plaintext highlighter-rouge">bytedance/sonic</code></a></td>
      <td>JIT + SIMD</td>
      <td>大体兼容；HTML 转义、SortKeys 等有差异</td>
      <td>否</td>
      <td>活跃；限 amd64/arm64</td>
    </tr>
    <tr>
      <td><a href="https://github.com/segmentio/encoding/tree/master/json"><code class="language-plaintext highlighter-rouge">segmentio/encoding/json</code></a></td>
      <td>优化反射</td>
      <td>部分兼容（Decoder.Token 等不全）</td>
      <td>否</td>
      <td>仍维护</td>
    </tr>
    <tr>
      <td><a href="https://github.com/buger/jsonparser"><code class="language-plaintext highlighter-rouge">buger/jsonparser</code></a></td>
      <td>按路径解析</td>
      <td>非绑定库，无 Marshal</td>
      <td>否</td>
      <td>解析专用</td>
    </tr>
    <tr>
      <td><a href="https://github.com/minio/simdjson-go"><code class="language-plaintext highlighter-rouge">minio/simdjson-go</code></a></td>
      <td>SIMD 解析</td>
      <td>仅 decode</td>
      <td>否</td>
      <td>解析专用</td>
    </tr>
    <tr>
      <td><a href="https://go.dev/blog/jsonv2-exp"><code class="language-plaintext highlighter-rouge">encoding/json/v2</code></a></td>
      <td>语法/语义分离、真流式</td>
      <td>行为有意修正（UTF-8、重复 key 等）</td>
      <td>否</td>
      <td>Go 1.25+ 实验</td>
    </tr>
  </tbody>
</table>

<h2 id="encodingjson默认且正确的起点">encoding/json：默认且正确的起点</h2>

<p>没有证据表明 JSON 是热点时，标准库是正确选择：零额外依赖、行为被全生态测试、安全补丁跟 Go 版本走。Go 版本迭代里标准库也在变快，早期「jsoniter 快 3～4 倍」的数字不能直接套到今天。</p>

<h2 id="easyjson--ffjson编译期换运行时">easyjson / ffjson：编译期换运行时</h2>

<p>两者都在 <code class="language-plaintext highlighter-rouge">go generate</code> 时写出具体类型的编解码函数，运行时几乎不反射。<a href="https://segmentfault.com/a/1190000013022780">hatlonely 的 2018 评测</a> 里 easyjson 序列化大约快 1 倍、反序列化大约快 3 倍，综合最优；ffjson 提升较小。近年独立基准里 easyjson 在「已知结构体」上仍经常是数量级领先，代价是：</p>

<ul>
  <li>结构体一改就要重新生成</li>
  <li>放弃部分标准库行为（key 大小写不敏感等）</li>
  <li>生成代码进入 diff，评审噪音大</li>
</ul>

<p>ffjson 已接近停止维护，新项目没有理由再选它。</p>

<h2 id="goccygo-json更认真的-drop-in">goccy/go-json：更认真的 drop-in</h2>

<p><a href="https://github.com/goccy/go-json">goccy/go-json</a> 明确把「兼容 <code class="language-plaintext highlighter-rouge">encoding/json</code>」写成目标，并指出 jsoniter 在不少边界上并不兼容且长期未修。实现上用 <code class="language-plaintext highlighter-rouge">typeptr</code> 找到类型专用编码器，把结构体编码编成 opcode 序列（类似小型 VM），用 <code class="language-plaintext highlighter-rouge">switch</code> 解释以减少间接调用，再用 opcode 融合减少分支。替换方式比 jsoniter 更直：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">-</span> <span class="k">import</span> <span class="s">"encoding/json"</span>
<span class="o">+</span> <span class="k">import</span> <span class="s">"github.com/goccy/go-json"</span>
</code></pre></div></div>

<p>适合「API 不能动、还想从归档的 jsoniter 迁走」的代码库。</p>

<h2 id="sonicjit--simd-的上限">sonic：JIT + SIMD 的上限</h2>

<p><a href="https://github.com/bytedance/sonic">bytedance/sonic</a> 用 JIT 为具体类型生成机器码，用 SIMD 并行扫引号、反斜杠、UTF-8。官方 medium payload（约 13KB）绑定编码约 2 GB/s，标准库约 0.8 GB/s，jsoniter 约 0.65 GB/s。约束同样明确：</p>

<ul>
  <li>Go 1.18–1.26，OS 为 Linux/macOS/Windows，CPU 为 amd64 或 arm64（arm64 需 Go 1.20+）</li>
  <li>不支持的平台回退到 <code class="language-plaintext highlighter-rouge">encoding/json</code></li>
  <li>默认 HTML 转义、可选 SortKeys 与 RFC 8259 / 标准库不完全一致</li>
  <li>依赖 <code class="language-plaintext highlighter-rouge">unsafe</code> 与链接器细节（Go 1.24.0 需避开或加 <code class="language-plaintext highlighter-rouge">-ldflags="-checklinkname=0"</code>）</li>
</ul>

<p>网关、序列化 QPS 极高、且运行在 amd64/arm64 上时，sonic 通常是目前最快的绑定库之一。</p>

<h2 id="只解析jsonparsersimdjson-goget">只解析：jsonparser、simdjson-go、Get</h2>

<p><a href="https://segmentfault.com/a/1190000013022780">hatlonely</a> 就把 jsonparser 排除在「序列化库」之外：它只提供按路径取值，每次调用都要重新扫描，没有 Marshal。日志管道、协议嗅探、只读 <code class="language-plaintext highlighter-rouge">$.status</code> 时，这类库（以及 jsoniter <code class="language-plaintext highlighter-rouge">Get</code>、sonic Get、gjson）往往比整包 Unmarshal 更合适。</p>

<h2 id="encodingjsonv2标准库自己的下一代">encoding/json/v2：标准库自己的下一代</h2>

<p>Go 1.25 以 <code class="language-plaintext highlighter-rouge">GOEXPERIMENT=jsonv2</code> 放出实验 API，见 <a href="https://go.dev/blog/jsonv2-exp">A new experimental Go API for JSON</a>。它要修的不只是速度，还有行为：拒绝非法 UTF-8 与重复 key、nil slice/map 默认编成空数组/对象、大小写敏感匹配、真正的流式 Encoder/Decoder。公开接口不稳定，<strong>生产默认路径仍应使用 <code class="language-plaintext highlighter-rouge">encoding/json</code></strong>；关心长期演进的团队可以在实验开关下跑回归，给 <a href="https://github.com/golang/go/issues/71497">proposal</a> 反馈。</p>

<h1 id="benchmarks">性能对比</h1>

<p>数字高度依赖 payload、Go 版本和机器。下面几组来自不同年代，用来看<strong>量级和趋势</strong>，不要直接当容量规划。</p>

<h2 id="官方-medium-payloadjsoniter-readme">官方 medium payload（jsoniter README）</h2>

<p>来源：<a href="https://github.com/json-iterator/go#benchmark">json-iterator/go Benchmark</a>，easyjson 需要静态生成。</p>

<table>
  <thead>
    <tr>
      <th>操作</th>
      <th style="text-align: right">ns/op</th>
      <th style="text-align: right">B/op</th>
      <th style="text-align: right">allocs/op</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>std decode</td>
      <td style="text-align: right">35510</td>
      <td style="text-align: right">1960</td>
      <td style="text-align: right">99</td>
    </tr>
    <tr>
      <td>easyjson decode</td>
      <td style="text-align: right">8499</td>
      <td style="text-align: right">160</td>
      <td style="text-align: right">4</td>
    </tr>
    <tr>
      <td><strong>jsoniter decode</strong></td>
      <td style="text-align: right"><strong>5623</strong></td>
      <td style="text-align: right"><strong>160</strong></td>
      <td style="text-align: right"><strong>3</strong></td>
    </tr>
    <tr>
      <td>std encode</td>
      <td style="text-align: right">2213</td>
      <td style="text-align: right">712</td>
      <td style="text-align: right">5</td>
    </tr>
    <tr>
      <td>easyjson encode</td>
      <td style="text-align: right">883</td>
      <td style="text-align: right">576</td>
      <td style="text-align: right">3</td>
    </tr>
    <tr>
      <td><strong>jsoniter encode</strong></td>
      <td style="text-align: right"><strong>837</strong></td>
      <td style="text-align: right"><strong>384</strong></td>
      <td style="text-align: right"><strong>4</strong></td>
    </tr>
  </tbody>
</table>

<p>解码大约是标准库的 6 倍快、分配次数从 99 降到 3；编码大约 2.6 倍。这是 jsoniter 对外宣传的核心数字。</p>

<h2 id="字符串数组-unmarshal腾讯云社区2022">字符串数组 Unmarshal（腾讯云社区，2022）</h2>

<p><a href="https://cloud.tencent.com/developer/article/1966308">jsoniter 与原生 json 对比</a> 测的是 <code class="language-plaintext highlighter-rouge">[]string</code>（元素长度约 10）：</p>

<table>
  <thead>
    <tr>
      <th>长度</th>
      <th style="text-align: right">std ns/op</th>
      <th style="text-align: right">jsoniter ns/op</th>
      <th style="text-align: right">大约倍数</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>10</td>
      <td style="text-align: right">7230</td>
      <td style="text-align: right">2921</td>
      <td style="text-align: right">~2.5×</td>
    </tr>
    <tr>
      <td>1000</td>
      <td style="text-align: right">426381</td>
      <td style="text-align: right">122974</td>
      <td style="text-align: right">~3.5×</td>
    </tr>
    <tr>
      <td>100000</td>
      <td style="text-align: right">43.0 ms</td>
      <td style="text-align: right">16.1 ms</td>
      <td style="text-align: right">~2.7×</td>
    </tr>
  </tbody>
</table>

<p>allocs/op 几乎相同（大数组时都是「一个元素一次分配」量级）。作者结论：<strong>时间能快 3～4 倍，内存分配差不大</strong>；分配爆炸仍要靠缓存或预分配，而不是换 JSON 库。</p>

<h2 id="早期综合评测20172018">早期综合评测（2017–2018）</h2>

<p><a href="https://javasgl.github.io/go-json-iterator/">javasgl（2017）</a> 数组 Unmarshal：json 2748 ns/op vs jsoniter 676 ns/op，分配 14 次 vs 3 次。</p>

<p><a href="https://segmentfault.com/a/1190000013022780">hatlonely（2018）</a> Marshal / Unmarshal 相对标准库：</p>

<table>
  <thead>
    <tr>
      <th>库</th>
      <th style="text-align: right">Marshal</th>
      <th style="text-align: right">Unmarshal</th>
      <th>备注</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>easyjson</td>
      <td style="text-align: right">~2×</td>
      <td style="text-align: right">~4×</td>
      <td>需预编译，综合最优</td>
    </tr>
    <tr>
      <td>jsoniter</td>
      <td style="text-align: right">~1.4×</td>
      <td style="text-align: right">~4×</td>
      <td>无预编译，接近 easyjson</td>
    </tr>
    <tr>
      <td>ffjson</td>
      <td style="text-align: right">提升有限</td>
      <td style="text-align: right">~2×</td>
      <td>生成代码</td>
    </tr>
    <tr>
      <td>codec-json</td>
      <td style="text-align: right">相近或更差</td>
      <td style="text-align: right">差</td>
      <td>使用复杂</td>
    </tr>
    <tr>
      <td>jsonparser</td>
      <td style="text-align: right">—</td>
      <td style="text-align: right">提升有限</td>
      <td>仅解析</td>
    </tr>
  </tbody>
</table>

<p>当时的建议是：<strong>综合选 jsoniter；极致性能选 easyjson</strong>。这个判断在「只要 drop-in」的年代是对的；2026 年要把归档事实和 sonic / go-json / <code class="language-plaintext highlighter-rouge">json/v2</code> 加进去。</p>

<h2 id="较新的绑定库对比sonic-官方13kb">较新的绑定库对比（sonic 官方，~13KB）</h2>

<p>摘自 <a href="https://github.com/bytedance/sonic">sonic README</a> Binding 场景（单线程，darwin/amd64）：</p>

<table>
  <thead>
    <tr>
      <th>库</th>
      <th style="text-align: right">Encode MB/s</th>
      <th style="text-align: right">Decode MB/s</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>sonic</td>
      <td style="text-align: right">~2079</td>
      <td style="text-align: right">~400</td>
    </tr>
    <tr>
      <td>go-json</td>
      <td style="text-align: right">~1568</td>
      <td style="text-align: right">~454</td>
    </tr>
    <tr>
      <td>jsoniter</td>
      <td style="text-align: right">~650</td>
      <td style="text-align: right">~371</td>
    </tr>
    <tr>
      <td>stdlib</td>
      <td style="text-align: right">~793</td>
      <td style="text-align: right">~117</td>
    </tr>
  </tbody>
</table>

<p>绑定解码上 jsoniter 仍明显快于标准库，但编码已被 sonic、go-json 超过；解码 sonic / go-json 与 jsoniter 同量级，sonic Fast 模式分配更少。<strong>「jsoniter 永远最快」已经不是事实。</strong></p>

<blockquote class="prompt-warning">
  <p>务必用自己的结构体和真实 payload 跑 <code class="language-plaintext highlighter-rouge">go test -bench</code>。官方和博客数字只能用来排除明显不合适的选项。</p>
</blockquote>

<h1 id="best-practices">最佳实践</h1>

<h2 id="先测量再换库">先测量，再换库</h2>

<p>在 pprof 里确认 <code class="language-plaintext highlighter-rouge">encoding/json.Marshal</code> / <code class="language-plaintext highlighter-rouge">Unmarshal</code>（或对应的 <code class="language-plaintext highlighter-rouge">jsoniter</code> 符号）占 CPU 或 alloc 的显著比例，再谈替换。CPU 只有 2% 时，换库的复杂度、行为差异和供应链风险都不划算。测量方法见 <a href="/posts/go-performance-in-action/">Go Performance in Action</a>。</p>

<h2 id="按场景选型而不是按博客标题">按场景选型，而不是按博客标题</h2>

<pre><code class="language-mermaid">flowchart TD
    A{JSON 是 CPU/alloc 热点?} --&gt;|否| STD[encoding/json]
    A --&gt;|是| B{结构体稳定且能接受 go generate?}
    B --&gt;|是| EJ[easyjson]
    B --&gt;|否| C{只要换 import?}
    C --&gt;|是| D{运行在 amd64/arm64 且可接受 unsafe/JIT?}
    D --&gt;|是| SN[sonic]
    D --&gt;|否| GJ[goccy/go-json]
    C --&gt;|否，只要抽几个字段| GET[Get / jsonparser / gjson]
    E[存量 jsoniter] --&gt; F[冻结版本 + 回归；新代码不要继续扩散]
</code></pre>

<table>
  <thead>
    <tr>
      <th>场景</th>
      <th>建议</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>内部工具、配置、低 QPS API</td>
      <td><code class="language-plaintext highlighter-rouge">encoding/json</code></td>
    </tr>
    <tr>
      <td>已有 jsoniter、行为已锁定</td>
      <td>继续用兼容配置；加回归；规划迁到 go-json 或 sonic</td>
    </tr>
    <tr>
      <td>新服务、要 drop-in 加速</td>
      <td>优先 <code class="language-plaintext highlighter-rouge">goccy/go-json</code> 或 <code class="language-plaintext highlighter-rouge">sonic</code>（看平台与安全策略）</td>
    </tr>
    <tr>
      <td>类型极少、QPS 极高</td>
      <td>easyjson</td>
    </tr>
    <tr>
      <td>日志/网关只读若干 key</td>
      <td><code class="language-plaintext highlighter-rouge">Get</code>、jsonparser、gjson、sonic Get</td>
    </tr>
    <tr>
      <td>跟进标准库长期方向</td>
      <td>实验环境试用 <code class="language-plaintext highlighter-rouge">json/v2</code>，不要当生产默认</td>
    </tr>
  </tbody>
</table>

<h2 id="用兼容配置包一层避免项目里混用三种-json">用兼容配置包一层，避免项目里混用三种 JSON</h2>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">jsonx</span>

<span class="k">import</span> <span class="n">jsoniter</span> <span class="s">"github.com/json-iterator/go"</span>

<span class="k">var</span> <span class="n">JSON</span> <span class="o">=</span> <span class="n">jsoniter</span><span class="o">.</span><span class="n">ConfigCompatibleWithStandardLibrary</span>
</code></pre></div></div>

<p>业务代码只依赖 <code class="language-plaintext highlighter-rouge">jsonx.JSON</code>。将来迁到 <code class="language-plaintext highlighter-rouge">github.com/goccy/go-json</code> 或等 <code class="language-plaintext highlighter-rouge">json/v2</code> 稳定，改一处即可。不要一部分包用 <code class="language-plaintext highlighter-rouge">jsoniter.Marshal</code>（默认不排序 key），另一部分用标准库，否则缓存、签名、黄金文件会对不上。</p>

<h2 id="减少反射与分配比换库更常赚到">减少反射与分配，比换库更常赚到</h2>

<p>换库解决的是「同一份工作做得更快」。下面这些改动往往效果更大：</p>

<ul>
  <li>不要 <code class="language-plaintext highlighter-rouge">map[string]interface{}</code> 来回转；用具体结构体（与 <a href="/posts/100-go-mistakes/">100 Go Mistakes</a> 里「能具体就不要 any」同一思路）</li>
  <li>切片预分配，避免 Unmarshal 后再 <code class="language-plaintext highlighter-rouge">append</code> 扩容</li>
  <li>热路径复用 <code class="language-plaintext highlighter-rouge">bytes.Buffer</code> / 对象池，或 jsoniter 的 Borrow API</li>
  <li>能抽字段就不要整包解析</li>
  <li>避免在 <code class="language-plaintext highlighter-rouge">MarshalJSON</code> 里再调 <code class="language-plaintext highlighter-rouge">json.Marshal</code> 造成二次解析</li>
</ul>

<h2 id="行为要对齐测试而不只对齐文档">行为要对齐测试，而不只对齐文档</h2>

<p>替换之后至少覆盖：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">null</code> vs 空数组 / 空对象</li>
  <li><code class="language-plaintext highlighter-rouge">omitempty</code>、匿名嵌套、冲突字段名</li>
  <li><code class="language-plaintext highlighter-rouge">time.Time</code>、<code class="language-plaintext highlighter-rouge">json.Number</code>、<code class="language-plaintext highlighter-rouge">json.RawMessage</code></li>
  <li>未知字段（是否 <code class="language-plaintext highlighter-rouge">DisallowUnknownFields</code>）</li>
  <li>HTML 字符 <code class="language-plaintext highlighter-rouge">&lt;&gt;&amp;</code></li>
  <li>浮点精度（尤其开了 <code class="language-plaintext highlighter-rouge">ConfigFastest</code>）</li>
  <li>非 UTF-8、重复 key（安全相关）</li>
</ul>

<p>标准库、jsoniter、sonic 在上述几点上并不完全一致。契约测试比「官方说 100% 兼容」可靠。</p>

<h1 id="caveats">注意事项</h1>

<p><strong>jsoniter 已归档。</strong> 2025-12-15 之后仓库只读，issue 与 Go 新版本（<code class="language-plaintext highlighter-rouge">go:linkname</code>、reflect 内部布局）不会再有官方修复。存量项目应 pin 已知可用版本，并准备迁移；新模块不要新增这个依赖。</p>

<p><strong>「100% 兼容」是营销语言。</strong> 默认不排序 map key；<code class="language-plaintext highlighter-rouge">ConfigFastest</code> 有损浮点、不转义 HTML；<a href="https://github.com/goccy/go-json#json-library-comparison">goccy 列举的差异</a> 和 <a href="https://github.com/json-iterator/go/issues/229">issue 229</a> 说明边界行为与标准库有缺口，且归档后不会补。</p>

<p><strong><code class="language-plaintext highlighter-rouge">unsafe</code> 与 <code class="language-plaintext highlighter-rouge">go:linkname</code>。</strong> jsoniter 依赖 <code class="language-plaintext highlighter-rouge">modern-go/reflect2</code> 直调 runtime。Go 1.18 改过 <code class="language-plaintext highlighter-rouge">mapiterinit</code> 签名，旧组合（如 jsoniter v1.1.10 + reflect2 v1.0.1）会在升级后 <strong>运行时 panic</strong>。升级 Go 时必须同步升级这对依赖。sonic 在 Go 1.24.0 上也有类似链接器限制。</p>

<p><strong>不要用 ConfigFastest 处理钱和科学数值。</strong> 6 位有效数字会静默改数据。</p>

<p><strong>MarshalJSON 仍可能把优势吃掉。</strong> 类型若大量实现标准库接口，jsoniter 仍要走拷贝路径。热点类型更适合 <code class="language-plaintext highlighter-rouge">RegisterTypeEncoder</code>，或换本身就优化这条路径的库。</p>

<p><strong>分配次数不一定下降。</strong> 腾讯云那组 <code class="language-plaintext highlighter-rouge">[]string</code> 基准里，allocs/op 几乎持平。大 slice、大 map 的元素分配是算法问题，换 JSON 库解决不了，该缓存还是要缓存。</p>

<p><strong>平台与二进制体积。</strong> sonic 的 JIT/SIMD 在不支持的架构上会静默回退；easyjson 会把生成代码推进仓库。选库时把 CI 矩阵、审计要求和二进制大小算进去。</p>

<p><strong>安全默认值。</strong> 标准库 <code class="language-plaintext highlighter-rouge">json/v2</code> 打算拒绝重复 key 和非法 UTF-8；旧 jsoniter / 标准库 v1 更宽松。对外暴露的解析入口，应用层要自己限制深度、大小和未知字段。</p>

<h1 id="summary">总结</h1>

<p>json-iterator 证明了一件事：在不牺牲 <code class="language-plaintext highlighter-rouge">encoding/json</code> 手感的前提下，靠缓存反射、Iterator/Stream 和可切换配置，可以把 JSON 从「随便用」变成「还能再快几倍」。它降低了换库门槛，也把 Kubernetes 等项目带进了这条路。</p>

<p>2026 年看，它的历史任务已经完成：仓库归档，标准库本身在变快，<code class="language-plaintext highlighter-rouge">json/v2</code> 在修 API 层面的结构性问题，goccy/go-json 把 drop-in 做得更严，sonic 把绑定性能推到 JIT/SIMD。对新代码，jsoniter 不再是默认答案；对老代码，它仍是必须读懂的一层依赖。</p>

<p>可执行的结论：</p>

<ol>
  <li><strong>默认用 <code class="language-plaintext highlighter-rouge">encoding/json</code></strong>，用 pprof 证明热点再优化。</li>
  <li><strong>存量 jsoniter 用 <code class="language-plaintext highlighter-rouge">ConfigCompatibleWithStandardLibrary</code> 包一层</strong>，加契约测试，停止扩散。</li>
  <li><strong>新的 drop-in 加速看 <code class="language-plaintext highlighter-rouge">goccy/go-json</code> 或 <code class="language-plaintext highlighter-rouge">sonic</code></strong>；类型极稳、要极限再上 easyjson。</li>
  <li><strong>只读几个字段用路径解析</strong>，不要为了方便 Unmarshal 成 <code class="language-plaintext highlighter-rouge">map[string]any</code>。</li>
  <li><strong>数字以自己的负载为准</strong>；跨年、跨库的 ns/op 不能直接比较。</li>
</ol>

<h1 id="references">参考资源</h1>

<h3 id="官方与源码">官方与源码</h3>

<ul>
  <li><a href="https://github.com/json-iterator/go">json-iterator/go</a> — 仓库（已归档）、官方基准与 drop-in 示例</li>
  <li><a href="https://jsoniter.com/migrate-from-go-std.html">Migrate from go standard library</a> — 兼容替换、<code class="language-plaintext highlighter-rouge">Get</code>、三套 Config、Borrow API</li>
  <li><a href="https://github.com/json-iterator/go/blob/v1.1.12/any.go">jsoniter Any / locatePath 源码</a> — 路径查找时对未命中字段 <code class="language-plaintext highlighter-rouge">Skip</code>，命中值可 lazy 保留 <code class="language-plaintext highlighter-rouge">[]byte</code></li>
  <li><a href="https://pkg.go.dev/encoding/json">encoding/json</a> — 标准库文档</li>
  <li><a href="https://go.dev/blog/jsonv2-exp">A new experimental Go API for JSON</a> — <code class="language-plaintext highlighter-rouge">json/v2</code> 与 <code class="language-plaintext highlighter-rouge">jsontext</code> 设计动机</li>
  <li><a href="https://github.com/goccy/go-json">goccy/go-json</a> — opcode / typeptr 实现说明与库对比表</li>
  <li><a href="https://github.com/bytedance/sonic">bytedance/sonic</a> — JIT/SIMD、平台限制与 Binding 基准</li>
  <li><a href="https://github.com/mailru/easyjson">mailru/easyjson</a> — 代码生成路线</li>
</ul>

<h3 id="社区评测文中引用">社区评测（文中引用）</h3>

<ul>
  <li><a href="https://cloud.tencent.com/developer/article/1966308">jsoniter 与原生 json 对比（腾讯云开发者社区）</a> — <code class="language-plaintext highlighter-rouge">[]string</code> 规模扫描</li>
  <li><a href="https://javasgl.github.io/go-json-iterator/">json-iterator：更快的 json 解析库（javasgl）</a> — 2017 年 Marshal/Unmarshal 对照，提及 K8s 引入</li>
  <li><a href="https://segmentfault.com/a/1190000013022780">golang json 性能分析（hatlonely / SegmentFault）</a> — ffjson / easyjson / jsoniter / codec / jsonparser</li>
  <li><a href="https://blog.csdn.net/gitblog_01165/article/details/153161847">Go json-iterator 相关整理（CSDN）</a></li>
  <li><a href="https://saraikin.com/posts/golang-json-marshalling/">Go JSON Performance Showdown（Saraikin）</a> — 含 sonic、go-json、easyjson 的较新对照</li>
</ul>

<h3 id="站内交叉引用">站内交叉引用</h3>

<ul>
  <li><a href="/posts/go-reflect-in-action/">Go Reflect in Action</a> — 反射开销</li>
  <li><a href="/posts/go-performance-in-action/">Go Performance in Action</a> — pprof 与分配</li>
  <li><a href="/posts/100-go-mistakes/">100 Go Mistakes</a> — 导出字段与 JSON、避免过度 <code class="language-plaintext highlighter-rouge">any</code></li>
  <li><a href="/posts/go-in-action/">Go in Action</a> — 语言与标准库基础</li>
</ul>]]></content><author><name></name></author><category term="GoLang" /><category term="GoLang" /><category term="JSON" /><category term="jsoniter" /><category term="encoding/json" /><category term="Performance" /><summary type="html"><![CDATA[从 encoding/json 的反射开销出发，系统介绍 json-iterator 的原理、兼容迁移与配置档，并对照 easyjson、goccy/go-json、sonic、jsonparser 以及实验性 encoding/json/v2 的性能与选型。]]></summary></entry><entry><title type="html">Argo CD 完全指南：Kubernetes GitOps 持续交付</title><link href="http://172.19.0.16:8080/posts/argo-cd-in-action/" rel="alternate" type="text/html" title="Argo CD 完全指南：Kubernetes GitOps 持续交付" /><published>2026-07-22T11:14:37+08:00</published><updated>2026-07-22T11:14:37+08:00</updated><id>http://172.19.0.16:8080/posts/argo-cd-in-action</id><content type="html" xml:base="http://172.19.0.16:8080/posts/argo-cd-in-action/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#概述" id="markdown-toc-概述">概述</a></li>
  <li><a href="#what-is-argo-cd" id="markdown-toc-what-is-argo-cd">什么是 Argo CD</a>    <ul>
      <li><a href="#为什么需要-gitops" id="markdown-toc-为什么需要-gitops">为什么需要 GitOps</a></li>
      <li><a href="#argo-cd-能做什么" id="markdown-toc-argo-cd-能做什么">Argo CD 能做什么</a></li>
    </ul>
  </li>
  <li><a href="#core-concepts" id="markdown-toc-core-concepts">核心概念</a>    <ul>
      <li><a href="#gitops-工作流" id="markdown-toc-gitops-工作流">GitOps 工作流</a></li>
      <li><a href="#关键-crd" id="markdown-toc-关键-crd">关键 CRD</a></li>
      <li><a href="#应用状态" id="markdown-toc-应用状态">应用状态</a></li>
      <li><a href="#同步策略sync-policy" id="markdown-toc-同步策略sync-policy">同步策略（Sync Policy）</a></li>
      <li><a href="#支持的-manifest-来源" id="markdown-toc-支持的-manifest-来源">支持的 Manifest 来源</a></li>
      <li><a href="#跟踪策略tracking" id="markdown-toc-跟踪策略tracking">跟踪策略（Tracking）</a></li>
    </ul>
  </li>
  <li><a href="#installation" id="markdown-toc-installation">安装 Argo CD</a>    <ul>
      <li><a href="#安装方式对比" id="markdown-toc-安装方式对比">安装方式对比</a></li>
      <li><a href="#快速安装非-ha评估开发" id="markdown-toc-快速安装非-ha评估开发">快速安装（非 HA，评估/开发）</a></li>
      <li><a href="#访问-ui-与-cli" id="markdown-toc-访问-ui-与-cli">访问 UI 与 CLI</a></li>
      <li><a href="#版本与-kubernetes-兼容性" id="markdown-toc-版本与-kubernetes-兼容性">版本与 Kubernetes 兼容性</a></li>
    </ul>
  </li>
  <li><a href="#usage-examples" id="markdown-toc-usage-examples">使用示例</a>    <ul>
      <li><a href="#示例-1cli-创建-application-并-sync" id="markdown-toc-示例-1cli-创建-application-并-sync">示例 1：CLI 创建 Application 并 Sync</a></li>
      <li><a href="#示例-2声明式-application-清单" id="markdown-toc-示例-2声明式-application-清单">示例 2：声明式 Application 清单</a></li>
      <li><a href="#示例-3helm-源" id="markdown-toc-示例-3helm-源">示例 3：Helm 源</a></li>
      <li><a href="#示例-4kustomize-源" id="markdown-toc-示例-4kustomize-源">示例 4：Kustomize 源</a></li>
      <li><a href="#示例-5多集群部署" id="markdown-toc-示例-5多集群部署">示例 5：多集群部署</a></li>
      <li><a href="#示例-6applicationset-批量创建应用" id="markdown-toc-示例-6applicationset-批量创建应用">示例 6：ApplicationSet 批量创建应用</a></li>
      <li><a href="#示例-7sync-waves-与-hooks" id="markdown-toc-示例-7sync-waves-与-hooks">示例 7：Sync Waves 与 Hooks</a></li>
    </ul>
  </li>
  <li><a href="#architecture" id="markdown-toc-architecture">架构与实现原理</a>    <ul>
      <li><a href="#组件架构" id="markdown-toc-组件架构">组件架构</a></li>
      <li><a href="#reconcile-循环" id="markdown-toc-reconcile-循环">Reconcile 循环</a></li>
      <li><a href="#与-ci-的边界" id="markdown-toc-与-ci-的边界">与 CI 的边界</a></li>
    </ul>
  </li>
  <li><a href="#best-practices" id="markdown-toc-best-practices">最佳实践</a>    <ul>
      <li><a href="#1-源码仓库与配置仓库分离" id="markdown-toc-1-源码仓库与配置仓库分离">1. 源码仓库与配置仓库分离</a></li>
      <li><a href="#2-选择合适的配置仓库粒度" id="markdown-toc-2-选择合适的配置仓库粒度">2. 选择合适的配置仓库粒度</a></li>
      <li><a href="#3-生产环境-pin-版本" id="markdown-toc-3-生产环境-pin-版本">3. 生产环境 Pin 版本</a></li>
      <li><a href="#4-启用-automated-sync-时理解-prune--selfheal" id="markdown-toc-4-启用-automated-sync-时理解-prune--selfheal">4. 启用 Automated Sync 时理解 prune / selfHeal</a></li>
      <li><a href="#5-使用-appproject-做多租户隔离" id="markdown-toc-5-使用-appproject-做多租户隔离">5. 使用 AppProject 做多租户隔离</a></li>
      <li><a href="#6-secret-不要明文进-git" id="markdown-toc-6-secret-不要明文进-git">6. Secret 不要明文进 Git</a></li>
      <li><a href="#7-提交前本地验证-manifest" id="markdown-toc-7-提交前本地验证-manifest">7. 提交前本地验证 Manifest</a></li>
      <li><a href="#8-结构化健康检查与通知" id="markdown-toc-8-结构化健康检查与通知">8. 结构化健康检查与通知</a></li>
      <li><a href="#9-用-applicationset-代替手工创建大量-application" id="markdown-toc-9-用-applicationset-代替手工创建大量-application">9. 用 ApplicationSet 代替手工创建大量 Application</a></li>
      <li><a href="#10-集群自举cluster-bootstrapping" id="markdown-toc-10-集群自举cluster-bootstrapping">10. 集群自举（Cluster Bootstrapping）</a></li>
    </ul>
  </li>
  <li><a href="#faq" id="markdown-toc-faq">常见问题与排查</a>    <ul>
      <li><a href="#1-application-长期-outofsync但-sync-成功" id="markdown-toc-1-application-长期-outofsync但-sync-成功">1. Application 长期 OutOfSync，但 Sync 成功</a></li>
      <li><a href="#2-sync-失败permission-denied--rbac" id="markdown-toc-2-sync-失败permission-denied--rbac">2. Sync 失败：<code class="language-plaintext highlighter-rouge">permission denied</code> / RBAC</a></li>
      <li><a href="#3-无法连接-git-私有仓库" id="markdown-toc-3-无法连接-git-私有仓库">3. 无法连接 Git 私有仓库</a></li>
      <li><a href="#4-helm-依赖构建失败" id="markdown-toc-4-helm-依赖构建失败">4. Helm 依赖构建失败</a></li>
      <li><a href="#5-crd-安装失败--annotation-too-long" id="markdown-toc-5-crd-安装失败--annotation-too-long">5. CRD 安装失败 / annotation too long</a></li>
      <li><a href="#6-资源一直-progressing" id="markdown-toc-6-资源一直-progressing">6. 资源一直 Progressing</a></li>
      <li><a href="#7-误删资源如何恢复" id="markdown-toc-7-误删资源如何恢复">7. 误删资源如何恢复</a></li>
      <li><a href="#8-与-flux-选哪个" id="markdown-toc-8-与-flux-选哪个">8. 与 Flux 选哪个？</a></li>
      <li><a href="#9-调试命令" id="markdown-toc-9-调试命令">9. 调试命令</a></li>
      <li><a href="#10-安全注意事项" id="markdown-toc-10-安全注意事项">10. 安全注意事项</a></li>
    </ul>
  </li>
  <li><a href="#caveats" id="markdown-toc-caveats">注意事项</a></li>
  <li><a href="#references" id="markdown-toc-references">参考资源</a>    <ul>
      <li><a href="#官方文档" id="markdown-toc-官方文档">官方文档</a></li>
      <li><a href="#社区与视频" id="markdown-toc-社区与视频">社区与视频</a></li>
      <li><a href="#站内延伸阅读" id="markdown-toc-站内延伸阅读">站内延伸阅读</a></li>
    </ul>
  </li>
</ul>

<h2 id="概述">概述</h2>

<blockquote class="prompt-info">
  <p><a href="https://argo-cd.readthedocs.io/en/stable/">Argo CD</a> 是 CNCF 毕业项目，面向 Kubernetes 的<strong>声明式 GitOps 持续交付</strong>工具。它以 Git 仓库为唯一事实来源（Single Source of Truth），持续对比集群<strong>期望状态</strong>与<strong>实际状态</strong>，并在偏差时报告或自动修复。</p>

  <p>官方定义：<em>Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes.</em></p>
</blockquote>

<p>本文与站内 <a href="/posts/helm-in-action/">Helm in Action</a>（Chart 渲染与 Release 管理）、<a href="/posts/k8s-in-action/">Kubernetes in Action</a>（集群基础概念）互为补充：Helm/Kustomize 负责<strong>生成</strong> Manifest，Argo CD 负责<strong>从 Git 拉取并持续同步</strong>到集群。</p>

<p>本文覆盖：</p>

<ul>
  <li>GitOps 与 Argo CD 基础介绍</li>
  <li>核心概念与 CRD</li>
  <li>安装与快速上手示例</li>
  <li>架构与实现原理</li>
  <li>最佳实践</li>
  <li>常见问题与排查</li>
</ul>

<hr />

<h2 id="what-is-argo-cd">什么是 Argo CD</h2>

<h3 id="为什么需要-gitops">为什么需要 GitOps</h3>

<p>传统 CI/CD 往往是 <strong>Push 模式</strong>：CI 流水线构建镜像后，由 Jenkins、GitLab CI 等工具<strong>主动</strong>向集群执行 <code class="language-plaintext highlighter-rouge">kubectl apply</code> 或 <code class="language-plaintext highlighter-rouge">helm upgrade</code>。这种模式存在几个问题：</p>

<table>
  <thead>
    <tr>
      <th>问题</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>权限集中</td>
      <td>CI 需要集群高权限凭证，泄露风险大</td>
    </tr>
    <tr>
      <td>状态不可审计</td>
      <td>集群当前配置与 Git 历史难以一一对应</td>
    </tr>
    <tr>
      <td>配置漂移</td>
      <td>运维或开发者用 <code class="language-plaintext highlighter-rouge">kubectl edit</code> 改现场，Git 与集群不一致</td>
    </tr>
    <tr>
      <td>回滚困难</td>
      <td>回滚依赖 CI 重新跑流水线，而非 Git revert</td>
    </tr>
  </tbody>
</table>

<p><strong>GitOps</strong> 将 Git 作为期望状态的唯一来源：变更通过 PR/MR 评审合并，由集群内的 <strong>GitOps Agent</strong>（如 Argo CD）<strong>拉取（Pull）</strong> 并应用。CI 只负责构建镜像、更新 Manifest 中的镜像 Tag，不再直接操作生产集群。</p>

<pre><code class="language-mermaid">flowchart LR
    subgraph 传统 Push CD
        CI1[CI Pipeline] --&gt;|kubectl apply| K8s1[Kubernetes]
    end
    subgraph GitOps Pull CD
        Dev[开发者] --&gt;|PR/MR| Git[Git 仓库]
        Git --&gt;|Pull &amp; Sync| Argo[Argo CD]
        Argo --&gt;|Reconcile| K8s2[Kubernetes]
        CI2[CI] --&gt;|更新镜像 Tag| Git
    end
</code></pre>

<h3 id="argo-cd-能做什么">Argo CD 能做什么</h3>

<p>根据 <a href="https://argo-cd.readthedocs.io/en/stable/">官方 Overview</a> 与 <a href="https://github.com/argoproj/argo-cd">GitHub 仓库</a>，Argo CD 主要能力包括：</p>

<ul>
  <li>自动或手动将应用部署到指定 Kubernetes 集群</li>
  <li>支持 <strong>Kustomize</strong>、<strong>Helm</strong>、Plain YAML/JSON、Jsonnet 及自定义 Config Management Plugin</li>
  <li>多集群管理与 RBAC 多租户（AppProject）</li>
  <li>检测配置漂移（OutOfSync）并可视化 Diff</li>
  <li>回滚到 Git 中任意历史 Commit</li>
  <li>Web UI、CLI、Webhook（GitHub/GitLab/Bitbucket）、Prometheus 指标</li>
  <li>PreSync / Sync / PostSync 钩子，配合 <a href="https://argo-rollouts.readthedocs.io/">Argo Rollouts</a> 实现金丝雀、蓝绿发布</li>
</ul>

<blockquote class="prompt-tip">
  <p><strong>与 CI 的分工</strong>：CI 构建并推送镜像、更新 Git 中的 Manifest；CD 由 Argo CD 完成。详见官方 <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/ci_automation/">Automation from CI Pipelines</a>。</p>
</blockquote>

<hr />

<h2 id="core-concepts">核心概念</h2>

<h3 id="gitops-工作流">GitOps 工作流</h3>

<p>典型端到端流程如下（参考 <a href="https://octopus.com/devops/argo-cd/">Octopus — Understanding Argo CD</a>）：</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant Dev as 开发者
    participant Git as Git 仓库
    participant CI as CI
    participant Argo as Argo CD
    participant K8s as Kubernetes

    Dev-&gt;&gt;Git: 修改应用代码 / Manifest（PR）
    CI-&gt;&gt;Git: 构建镜像，更新镜像 Tag
    Dev-&gt;&gt;Git: PR 合并到 main
    Git-&gt;&gt;Argo: Webhook 或轮询触发
    Argo-&gt;&gt;Git: Clone &amp; 渲染 Manifest
    Argo-&gt;&gt;K8s: 对比并 Sync
    K8s-&gt;&gt;K8s: Controller 协调至期望状态
    Argo-&gt;&gt;Dev: UI/通知：Synced &amp; Healthy
</code></pre>

<h3 id="关键-crd">关键 CRD</h3>

<p>Argo CD 通过自定义资源扩展 Kubernetes API，核心对象如下：</p>

<table>
  <thead>
    <tr>
      <th>资源</th>
      <th>作用</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Application</strong></td>
      <td>描述一个应用的 Git 源、目标集群/命名空间、同步策略</td>
    </tr>
    <tr>
      <td><strong>AppProject</strong></td>
      <td>项目边界：允许的 Git 仓库、目标集群、资源类型、RBAC</td>
    </tr>
    <tr>
      <td><strong>ApplicationSet</strong></td>
      <td>基于 Generator（Git 目录、Cluster、List 等）批量生成 Application</td>
    </tr>
    <tr>
      <td><strong>Repository</strong> / <strong>Cluster</strong> Secret</td>
      <td>存储 Git 与集群凭据（也可在 Application 中内联）</td>
    </tr>
  </tbody>
</table>

<h3 id="应用状态">应用状态</h3>

<p>Argo CD 为每个 Application 维护两类核心状态（见 <a href="https://argo-cd.readthedocs.io/en/stable/core_concepts/">Core Concepts</a>）：</p>

<table>
  <thead>
    <tr>
      <th>维度</th>
      <th>可能值</th>
      <th>含义</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Sync Status</strong></td>
      <td><code class="language-plaintext highlighter-rouge">Synced</code> / <code class="language-plaintext highlighter-rouge">OutOfSync</code></td>
      <td>Git 期望状态与集群 live 状态是否一致</td>
    </tr>
    <tr>
      <td><strong>Health Status</strong></td>
      <td><code class="language-plaintext highlighter-rouge">Healthy</code> / <code class="language-plaintext highlighter-rouge">Progressing</code> / <code class="language-plaintext highlighter-rouge">Degraded</code> / <code class="language-plaintext highlighter-rouge">Missing</code> / <code class="language-plaintext highlighter-rouge">Suspended</code> 等</td>
      <td>资源是否满足健康规则</td>
    </tr>
  </tbody>
</table>

<blockquote class="prompt-info">
  <p><strong>OutOfSync 不等于故障</strong>：新建 Application 在首次 Sync 前通常为 <code class="language-plaintext highlighter-rouge">OutOfSync</code> + <code class="language-plaintext highlighter-rouge">Missing</code>，属于正常现象。</p>
</blockquote>

<h3 id="同步策略sync-policy">同步策略（Sync Policy）</h3>

<table>
  <thead>
    <tr>
      <th>模式</th>
      <th>行为</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Manual</strong></td>
      <td>仅手动或 CLI/UI 触发 Sync</td>
    </tr>
    <tr>
      <td><strong>Automated</strong></td>
      <td>Git 变更后自动 Sync；可配 <code class="language-plaintext highlighter-rouge">prune</code>（删除 Git 中已移除的资源）、<code class="language-plaintext highlighter-rouge">selfHeal</code>（撤销集群内非 Git 变更）</td>
    </tr>
  </tbody>
</table>

<p>Automated 示例：</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">syncPolicy</span><span class="pi">:</span>
  <span class="na">automated</span><span class="pi">:</span>
    <span class="na">prune</span><span class="pi">:</span> <span class="kc">true</span>
    <span class="na">selfHeal</span><span class="pi">:</span> <span class="kc">true</span>
  <span class="na">syncOptions</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">CreateNamespace=true</span>
</code></pre></div></div>

<h3 id="支持的-manifest-来源">支持的 Manifest 来源</h3>

<ul>
  <li>Plain 目录 YAML/JSON</li>
  <li><a href="https://kustomize.io">Kustomize</a></li>
  <li><a href="https://helm.sh">Helm</a> Chart（与站内 <a href="/posts/helm-in-action/">Helm 指南</a> 配合使用）</li>
  <li>Jsonnet / Ksonnet</li>
  <li>任意 <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/config-management-plugins/">Config Management Plugin</a></li>
</ul>

<h3 id="跟踪策略tracking">跟踪策略（Tracking）</h3>

<p>Application 的 <code class="language-plaintext highlighter-rouge">targetRevision</code> 可指向分支、Tag 或固定 Commit，详见 <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/tracking_strategies/">Tracking Strategies</a>。生产环境建议 <strong>Pin 到 Tag 或 Commit</strong>，避免 <code class="language-plaintext highlighter-rouge">HEAD</code> 漂移。</p>

<hr />

<h2 id="installation">安装 Argo CD</h2>

<p>安装前需具备 <a href="https://kubernetes.io/docs/tasks/tools/install-kubectl/">kubectl</a>、可用的 kubeconfig，以及集群内 CoreDNS（本地集群如 microk8s 需 <code class="language-plaintext highlighter-rouge">microk8s enable dns</code>）。详见 <a href="https://argo-cd.readthedocs.io/en/stable/understand_the_basics/">Understand The Basics</a>。</p>

<h3 id="安装方式对比">安装方式对比</h3>

<table>
  <thead>
    <tr>
      <th>方式</th>
      <th>适用场景</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Multi-Tenant（标准）</strong></td>
      <td>平台团队服务多开发团队</td>
      <td>含 API Server + UI，最常见</td>
    </tr>
    <tr>
      <td><strong>HA Multi-Tenant</strong></td>
      <td>生产环境</td>
      <td>多副本，见 <a href="https://github.com/argoproj/argo-cd/blob/stable/manifests/ha/install.yaml">ha/install.yaml</a></td>
    </tr>
    <tr>
      <td><strong>Core</strong></td>
      <td>仅需 Controller、无 UI/多租户</td>
      <td><a href="https://github.com/argoproj/argo-cd/blob/stable/manifests/core-install.yaml">core-install.yaml</a>，<code class="language-plaintext highlighter-rouge">argocd login --core</code></td>
    </tr>
    <tr>
      <td><strong>Kustomize / Helm</strong></td>
      <td>需定制补丁或 Values</td>
      <td>官方推荐 Kustomize 引用 remote manifest 再 patch</td>
    </tr>
    <tr>
      <td><strong>namespace-install</strong></td>
      <td>仅命名空间权限、主要部署外部集群</td>
      <td>需单独安装 CRD</td>
    </tr>
  </tbody>
</table>

<h3 id="快速安装非-ha评估开发">快速安装（非 HA，评估/开发）</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl create namespace argocd
kubectl apply <span class="nt">-n</span> argocd <span class="nt">--server-side</span> <span class="nt">--force-conflicts</span> <span class="se">\</span>
  <span class="nt">-f</span> https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
</code></pre></div></div>

<blockquote class="prompt-warning">
  <p><strong>为何 <code class="language-plaintext highlighter-rouge">--server-side --force-conflicts</code>？</strong> 部分 CRD（如 ApplicationSet）体积超过 client-side <code class="language-plaintext highlighter-rouge">kubectl apply</code> 的 annotation 限制（262KB）；Server-Side Apply 可避免此问题。生产环境建议 Pin 到具体版本 Tag（如 <code class="language-plaintext highlighter-rouge">v3.4.5</code>），而非 <code class="language-plaintext highlighter-rouge">stable</code> 浮动分支。</p>
</blockquote>

<p>生产环境应使用 <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/installation/#high-availability">HA 安装清单</a> 或通过 <a href="https://github.com/argoproj/argo-helm/tree/main/charts/argo-cd">argo-helm/charts/argo-cd</a> 部署。</p>

<h3 id="访问-ui-与-cli">访问 UI 与 CLI</h3>

<p><strong>安装 CLI</strong>（macOS）：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew <span class="nb">install </span>argocd
</code></pre></div></div>

<p><strong>端口转发访问 UI</strong>（<a href="https://argo-cd.readthedocs.io/en/stable/getting_started/#3-access-argo-cd">Getting Started — Access</a>）：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl port-forward svc/argocd-server <span class="nt">-n</span> argocd 8080:443
<span class="c"># 浏览器打开 https://localhost:8080</span>
</code></pre></div></div>

<p><strong>初始 admin 密码</strong>：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>argocd admin initial-password <span class="nt">-n</span> argocd
argocd login localhost:8080 <span class="nt">--insecure</span>
argocd account update-password
</code></pre></div></div>

<p>修改密码后应删除 <code class="language-plaintext highlighter-rouge">argocd-initial-admin-secret</code>，避免明文密码长期留存。</p>

<h3 id="版本与-kubernetes-兼容性">版本与 Kubernetes 兼容性</h3>

<table>
  <thead>
    <tr>
      <th>Argo CD 版本</th>
      <th>测试过的 Kubernetes 版本</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>3.4</td>
      <td>v1.35, v1.34, v1.33, v1.32</td>
    </tr>
    <tr>
      <td>3.3</td>
      <td>v1.35, v1.34, v1.33, v1.32</td>
    </tr>
    <tr>
      <td>3.2</td>
      <td>v1.34, v1.33, v1.32, v1.31</td>
    </tr>
  </tbody>
</table>

<p>来源：<a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/installation/#tested-versions">Installation — Tested versions</a></p>

<hr />

<h2 id="usage-examples">使用示例</h2>

<p>以下示例基于官方 <a href="https://argo-cd.readthedocs.io/en/stable/getting_started/">Getting Started</a> 与 <a href="https://github.com/argoproj/argocd-example-apps">argocd-example-apps</a> 仓库中的 Guestbook 应用。</p>

<h3 id="示例-1cli-创建-application-并-sync">示例 1：CLI 创建 Application 并 Sync</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl config set-context <span class="nt">--current</span> <span class="nt">--namespace</span><span class="o">=</span>argocd

argocd app create guestbook <span class="se">\</span>
  <span class="nt">--repo</span> https://github.com/argoproj/argocd-example-apps.git <span class="se">\</span>
  <span class="nt">--path</span> guestbook <span class="se">\</span>
  <span class="nt">--dest-server</span> https://kubernetes.default.svc <span class="se">\</span>
  <span class="nt">--dest-namespace</span> default

argocd app get guestbook
argocd app <span class="nb">sync </span>guestbook
</code></pre></div></div>

<p>首次 <code class="language-plaintext highlighter-rouge">app get</code> 典型输出：<code class="language-plaintext highlighter-rouge">Sync Status: OutOfSync</code>，<code class="language-plaintext highlighter-rouge">Health Status: Missing</code>。Sync 成功后变为 <code class="language-plaintext highlighter-rouge">Synced</code> / <code class="language-plaintext highlighter-rouge">Healthy</code>。</p>

<h3 id="示例-2声明式-application-清单">示例 2：声明式 Application 清单</h3>

<p>将 Application 本身也放入 Git，实现 <strong>App of Apps</strong> 或 <strong>Git 管理 Argo CD</strong>（<a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/declarative-setup/">Declarative Setup</a>）：</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">argoproj.io/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Application</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">guestbook</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">argocd</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">project</span><span class="pi">:</span> <span class="s">default</span>
  <span class="na">source</span><span class="pi">:</span>
    <span class="na">repoURL</span><span class="pi">:</span> <span class="s">https://github.com/argoproj/argocd-example-apps.git</span>
    <span class="na">targetRevision</span><span class="pi">:</span> <span class="s">HEAD</span>
    <span class="na">path</span><span class="pi">:</span> <span class="s">guestbook</span>
  <span class="na">destination</span><span class="pi">:</span>
    <span class="na">server</span><span class="pi">:</span> <span class="s">https://kubernetes.default.svc</span>
    <span class="na">namespace</span><span class="pi">:</span> <span class="s">default</span>
  <span class="na">syncPolicy</span><span class="pi">:</span>
    <span class="na">automated</span><span class="pi">:</span>
      <span class="na">prune</span><span class="pi">:</span> <span class="kc">true</span>
      <span class="na">selfHeal</span><span class="pi">:</span> <span class="kc">true</span>
    <span class="na">syncOptions</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">CreateNamespace=true</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl apply <span class="nt">-f</span> guestbook-application.yaml
</code></pre></div></div>

<h3 id="示例-3helm-源">示例 3：Helm 源</h3>

<p>与 <a href="/posts/helm-in-action/#基本概念">Helm Chart 结构</a> 对应，Application 可指向 Chart 仓库：</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">spec</span><span class="pi">:</span>
  <span class="na">source</span><span class="pi">:</span>
    <span class="na">repoURL</span><span class="pi">:</span> <span class="s">https://charts.example.com</span>
    <span class="na">chart</span><span class="pi">:</span> <span class="s">my-app</span>
    <span class="na">targetRevision</span><span class="pi">:</span> <span class="s">1.2.0</span>
    <span class="na">helm</span><span class="pi">:</span>
      <span class="na">valueFiles</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">values-prod.yaml</span>
      <span class="na">parameters</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">replicaCount</span>
          <span class="na">value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3"</span>
</code></pre></div></div>

<p>Helm 参数也可在 UI 中覆盖，详见 <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/parameters/">Parameter Overrides</a>。</p>

<h3 id="示例-4kustomize-源">示例 4：Kustomize 源</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">spec</span><span class="pi">:</span>
  <span class="na">source</span><span class="pi">:</span>
    <span class="na">repoURL</span><span class="pi">:</span> <span class="s">https://github.com/org/k8s-manifests.git</span>
    <span class="na">targetRevision</span><span class="pi">:</span> <span class="s">main</span>
    <span class="na">path</span><span class="pi">:</span> <span class="s">overlays/production</span>
    <span class="na">kustomize</span><span class="pi">:</span>
      <span class="na">images</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">myregistry.io/app:v1.2.3</span>
</code></pre></div></div>

<h3 id="示例-5多集群部署">示例 5：多集群部署</h3>

<p>部署到 Argo CD 所在集群时使用 <code class="language-plaintext highlighter-rouge">https://kubernetes.default.svc</code>。外部集群需先注册：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl config get-contexts <span class="nt">-o</span> name
argocd cluster add &lt;CONTEXT_NAME&gt;
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">cluster add</code> 会在目标集群创建 <code class="language-plaintext highlighter-rouge">argocd-manager</code> ServiceAccount 并绑定 ClusterRole。可按最小权限收窄规则，但集群级 <code class="language-plaintext highlighter-rouge">get/list/watch</code> 仍为必需。</p>

<h3 id="示例-6applicationset-批量创建应用">示例 6：ApplicationSet 批量创建应用</h3>

<p>适合「一个 Git 目录对应一个微服务」或「每个集群一份部署」：</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">argoproj.io/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">ApplicationSet</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">guestbook-apps</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">argocd</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">generators</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">git</span><span class="pi">:</span>
        <span class="na">repoURL</span><span class="pi">:</span> <span class="s">https://github.com/argoproj/argocd-example-apps.git</span>
        <span class="na">revision</span><span class="pi">:</span> <span class="s">HEAD</span>
        <span class="na">directories</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s">guestbook</span>
  <span class="na">template</span><span class="pi">:</span>
    <span class="na">metadata</span><span class="pi">:</span>
      <span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">'</span>
    <span class="na">spec</span><span class="pi">:</span>
      <span class="na">project</span><span class="pi">:</span> <span class="s">default</span>
      <span class="na">source</span><span class="pi">:</span>
        <span class="na">repoURL</span><span class="pi">:</span> <span class="s">https://github.com/argoproj/argocd-example-apps.git</span>
        <span class="na">targetRevision</span><span class="pi">:</span> <span class="s">HEAD</span>
        <span class="na">path</span><span class="pi">:</span> <span class="s1">'</span><span class="s">'</span>
      <span class="na">destination</span><span class="pi">:</span>
        <span class="na">server</span><span class="pi">:</span> <span class="s">https://kubernetes.default.svc</span>
        <span class="na">namespace</span><span class="pi">:</span> <span class="s">default</span>
</code></pre></div></div>

<p>详见 <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/application-set/">Generating Applications with ApplicationSet</a>。</p>

<h3 id="示例-7sync-waves-与-hooks">示例 7：Sync Waves 与 Hooks</h3>

<p>复杂发布可结合 <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/sync-waves/">Sync Waves</a> 与 <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/resource_hooks/">Resource Hooks</a>：</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">metadata</span><span class="pi">:</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">argocd.argoproj.io/sync-wave</span><span class="pi">:</span> <span class="s2">"</span><span class="s">1"</span>
</code></pre></div></div>

<p>PreSync Job 做数据库迁移，Sync 阶段部署 Deployment，PostSync Job 做冒烟测试——顺序由 wave 与 hook 类型控制。</p>

<hr />

<h2 id="architecture">架构与实现原理</h2>

<h3 id="组件架构">组件架构</h3>

<p>Argo CD 以 Kubernetes Controller 模式运行，持续 Reconcile。官方架构如下（详见 <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/architecture/">Architecture</a>）：</p>

<pre><code class="language-mermaid">flowchart TB
    subgraph 用户面
        UI[Web UI]
        CLI[argocd CLI]
        WH[Git Webhook]
    end

    subgraph argocd_namespace [argocd 命名空间]
        API[argocd-server&lt;br/&gt;API / gRPC / REST]
        AC[application-controller&lt;br/&gt;应用控制器]
        RS[repo-server&lt;br/&gt;仓库服务]
        RC[redis&lt;br/&gt;缓存]
        NC[notifications-controller]
    end

    Git[(Git / Helm Repo)]
    K8s[(目标 Kubernetes 集群)]

    UI --&gt; API
    CLI --&gt; API
    WH --&gt; API
    API --&gt; AC
    API --&gt; RS
    AC --&gt; RS
    RS --&gt; Git
    AC --&gt; K8s
    RS --&gt; RC
    AC --&gt; RC
</code></pre>

<table>
  <thead>
    <tr>
      <th>组件</th>
      <th>职责</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>argocd-server</strong></td>
      <td>API、认证授权（OIDC/LDAP/SAML 等）、RBAC、Webhook 接入、Sync/Rollback 操作</td>
    </tr>
    <tr>
      <td><strong>repo-server</strong></td>
      <td>Clone Git、缓存 Manifest；执行 Kustomize build / Helm template / 插件渲染</td>
    </tr>
    <tr>
      <td><strong>application-controller</strong></td>
      <td>对比 live vs desired；执行 Sync；评估 Health；触发 Hooks</td>
    </tr>
    <tr>
      <td><strong>redis</strong></td>
      <td>缓存与会话，减轻 repo-server 压力</td>
    </tr>
    <tr>
      <td><strong>notifications-controller</strong></td>
      <td>对接 Slack、Email 等（可选）</td>
    </tr>
  </tbody>
</table>

<p>核心协调逻辑在 <a href="https://github.com/argoproj/argo-cd/tree/master/gitops-engine">gitops-engine</a> 中实现：使用类 <code class="language-plaintext highlighter-rouge">kubectl apply</code> 的三向合并，并支持 <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/resource_tracking/">Resource Tracking</a>（默认 label <code class="language-plaintext highlighter-rouge">app.kubernetes.io/instance</code>，或 annotation <code class="language-plaintext highlighter-rouge">argocd.argoproj.io/tracking-id</code>）。</p>

<h3 id="reconcile-循环">Reconcile 循环</h3>

<pre><code class="language-mermaid">sequenceDiagram
    participant AC as application-controller
    participant RS as repo-server
    participant Git as Git
    participant K8s as Kubernetes

    loop 每 3 分钟或 Webhook 触发
        AC-&gt;&gt;RS: 请求渲染 Manifest
        RS-&gt;&gt;Git: fetch revision
        RS--&gt;&gt;AC: 期望 Manifest 列表
        AC-&gt;&gt;K8s: list live 资源
        AC-&gt;&gt;AC: Diff（期望 vs 实际）
        alt OutOfSync 且 automated/手动 Sync
            AC-&gt;&gt;K8s: Apply / Prune / Hook
        end
        AC-&gt;&gt;AC: 评估 Health
    end
</code></pre>

<p><strong>selfHeal</strong>：若有人在集群内 <code class="language-plaintext highlighter-rouge">kubectl edit</code> 改了字段，Controller 检测到 live 偏离 Git，会在下次 Reconcile 时改回，Application 显示 <code class="language-plaintext highlighter-rouge">OutOfSync</code> 后自动修复。</p>

<p><strong>prune</strong>：Git 中删除某资源后，Sync 时从集群删除对应对象（需谨慎，建议配合 <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/project-specification/">AppProject 资源白名单</a>）。</p>

<h3 id="与-ci-的边界">与 CI 的边界</h3>

<pre><code class="language-mermaid">flowchart LR
    subgraph CI 负责
        Build[构建镜像]
        Scan[安全扫描]
        Bump[更新 Git 中 image tag]
    end
    subgraph Argo CD 负责
        Render[渲染 Manifest]
        Deploy[部署到集群]
        Drift[漂移检测与修复]
        Rollback[Git 历史回滚]
    end
    Build --&gt; Scan --&gt; Bump --&gt; Render --&gt; Deploy --&gt; Drift
</code></pre>

<p>CI <strong>不应</strong>再持有集群 admin kubeconfig 直接 <code class="language-plaintext highlighter-rouge">kubectl apply</code> 生产环境；仅更新 Git 即可触发 Argo CD。镜像 Tag 更新可用 <a href="https://argocd-image-updater.readthedocs.io/">Argo CD Image Updater</a> 等工具自动化。</p>

<hr />

<h2 id="best-practices">最佳实践</h2>

<p>综合 <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/best_practices/">官方 Best Practices</a>、<a href="https://octopus.com/devops/argo-cd/">Octopus Argo CD Best Practices</a> 与生产经验：</p>

<h3 id="1-源码仓库与配置仓库分离">1. 源码仓库与配置仓库分离</h3>

<table>
  <thead>
    <tr>
      <th>做法</th>
      <th>原因</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>应用代码一个 Repo，K8s Manifest 另一个 Repo</td>
      <td>改副本数不必触发应用构建；审计日志更清晰；权限可分离</td>
    </tr>
    <tr>
      <td>微服务各自 Manifest Repo 或目录</td>
      <td>发布节奏不同，避免 mono-repo 里 CI 循环触发</td>
    </tr>
  </tbody>
</table>

<h3 id="2-选择合适的配置仓库粒度">2. 选择合适的配置仓库粒度</h3>

<table>
  <thead>
    <tr>
      <th>组织规模</th>
      <th>建议</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>小团队</td>
      <td>单配置 mono-repo 可接受</td>
    </tr>
    <tr>
      <td>中型</td>
      <td>每团队一个 Repo</td>
    </tr>
    <tr>
      <td>大型</td>
      <td>每服务或每域一个 Repo，配合 ApplicationSet</td>
    </tr>
  </tbody>
</table>

<h3 id="3-生产环境-pin-版本">3. 生产环境 Pin 版本</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">source</span><span class="pi">:</span>
  <span class="na">targetRevision</span><span class="pi">:</span> <span class="s">v1.4.2</span>        <span class="c1"># Tag</span>
  <span class="c1"># 或 targetRevision: abc1234  # Commit SHA</span>
</code></pre></div></div>

<p>避免生产跟踪 <code class="language-plaintext highlighter-rouge">HEAD</code> 或浮动分支导致不可预期升级。</p>

<h3 id="4-启用-automated-sync-时理解-prune--selfheal">4. 启用 Automated Sync 时理解 prune / selfHeal</h3>

<ul>
  <li><strong>selfHeal</strong>：防止配置漂移，但会覆盖合法的临时扩缩容（若 replicas 写在 Git 里）</li>
  <li><strong>prune</strong>：删除 Git 中不存在的资源；误删 Manifest 可能导致生产资源被删</li>
</ul>

<p>建议：生产先 <strong>Manual Sync + PR 评审</strong>，稳定后再开 <code class="language-plaintext highlighter-rouge">automated</code>；关键应用使用 <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/sync-windows/">Sync Windows</a> 限制维护时段。</p>

<h3 id="5-使用-appproject-做多租户隔离">5. 使用 AppProject 做多租户隔离</h3>

<p>限制每个 Project 可访问的 Git 仓库、命名空间、集群与 API Group：</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">argoproj.io/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">AppProject</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">team-a</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">argocd</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">sourceRepos</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s1">'</span><span class="s">https://github.com/org/team-a-config.git'</span>
  <span class="na">destinations</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">namespace</span><span class="pi">:</span> <span class="s">team-a-*</span>
      <span class="na">server</span><span class="pi">:</span> <span class="s">https://kubernetes.default.svc</span>
  <span class="na">clusterResourceWhitelist</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">group</span><span class="pi">:</span> <span class="s1">'</span><span class="s">'</span>
      <span class="na">kind</span><span class="pi">:</span> <span class="s">Namespace</span>
</code></pre></div></div>

<h3 id="6-secret-不要明文进-git">6. Secret 不要明文进 Git</h3>

<p>使用 <a href="https://external-secrets.io/">External Secrets Operator</a>、<a href="https://github.com/bitnami-labs/sealed-secrets">Sealed Secrets</a>、<a href="https://argocd-vault-plugin.readthedocs.io/">Argo CD Vault Plugin</a> 或云厂商 Secret Manager。参见官方 <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/secret-management/">Secret Management</a>。</p>

<h3 id="7-提交前本地验证-manifest">7. 提交前本地验证 Manifest</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Kustomize</span>
kustomize build overlays/prod | kubeconform <span class="nt">-summary</span>

<span class="c"># Helm</span>
helm template myrelease ./chart <span class="nt">-f</span> values-prod.yaml | kubeconform <span class="nt">-summary</span>
</code></pre></div></div>

<p>避免无效 YAML 进入 Git 后在 Sync 阶段才发现失败。</p>

<h3 id="8-结构化健康检查与通知">8. 结构化健康检查与通知</h3>

<ul>
  <li>为 CRD 配置 <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/health/">Resource Health</a> 自定义规则</li>
  <li>配置 <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/notifications/">Notifications</a> 在 <code class="language-plaintext highlighter-rouge">Degraded</code> / <code class="language-plaintext highlighter-rouge">SyncFailed</code> 时告警</li>
  <li>暴露 <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/metrics/">Prometheus Metrics</a> 给 Grafana</li>
</ul>

<h3 id="9-用-applicationset-代替手工创建大量-application">9. 用 ApplicationSet 代替手工创建大量 Application</h3>

<p>减少 UI 点击与人为不一致，Git 目录结构即应用拓扑。</p>

<h3 id="10-集群自举cluster-bootstrapping">10. 集群自举（Cluster Bootstrapping）</h3>

<p>Argo CD 安装完成后，第一个 Application 指向「元配置」Repo，由该 Repo 管理其余 Application（App of Apps 模式）。见 <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/cluster-bootstrapping/">Cluster Bootstrapping</a>。</p>

<blockquote class="prompt-tip">
  <p><strong>进阶发布</strong>：金丝雀、蓝绿可结合 <a href="https://argo-rollouts.readthedocs.io/">Argo Rollouts</a>，与 Argo CD 同属 Argo 生态。</p>
</blockquote>

<hr />

<h2 id="faq">常见问题与排查</h2>

<h3 id="1-application-长期-outofsync但-sync-成功">1. Application 长期 OutOfSync，但 Sync 成功</h3>

<p><strong>可能原因</strong>：</p>

<ul>
  <li>集群默认字段与 Git 不一致（如 Service 的 <code class="language-plaintext highlighter-rouge">clusterIP</code>）</li>
  <li>使用了 mutating webhook 注入 sidecar</li>
  <li>Helm/Kustomize 渲染结果非确定性</li>
</ul>

<p><strong>处理</strong>：使用 <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/compare-options/">Compare Options</a> 忽略差异字段，例如：</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">spec</span><span class="pi">:</span>
  <span class="na">ignoreDifferences</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">group</span><span class="pi">:</span> <span class="s">apps</span>
      <span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span>
      <span class="na">jsonPointers</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">/spec/replicas</span>
</code></pre></div></div>

<h3 id="2-sync-失败permission-denied--rbac">2. Sync 失败：<code class="language-plaintext highlighter-rouge">permission denied</code> / RBAC</h3>

<p>检查 <code class="language-plaintext highlighter-rouge">argocd-application-controller</code> 与 <code class="language-plaintext highlighter-rouge">argocd-server</code> 的 ServiceAccount 权限；多集群场景检查 <code class="language-plaintext highlighter-rouge">argocd-manager</code> RoleBinding。AppProject 的 <code class="language-plaintext highlighter-rouge">destinations</code> 是否包含目标 NS。</p>

<h3 id="3-无法连接-git-私有仓库">3. 无法连接 Git 私有仓库</h3>

<p>在 Argo CD 中添加 Repository Credential（SSH key 或 HTTPS token）：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>argocd repo add https://github.com/org/private.git <span class="se">\</span>
  <span class="nt">--username</span> git <span class="nt">--password</span> &lt;token&gt;
</code></pre></div></div>

<p>或通过 Sealed Secret 声明式管理。</p>

<h3 id="4-helm-依赖构建失败">4. Helm 依赖构建失败</h3>

<p>repo-server Pod 需能访问 Chart 仓库；subchart 在 <code class="language-plaintext highlighter-rouge">Chart.lock</code> 锁定版本。可在 repo-server 启用 <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/helm/">Helm 缓存与自定义 repo</a>。</p>

<h3 id="5-crd-安装失败--annotation-too-long">5. CRD 安装失败 / annotation too long</h3>

<p>使用 <code class="language-plaintext highlighter-rouge">--server-side --force-conflicts</code> 安装；ApplicationSet CRD 单独 apply：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl apply <span class="nt">--server-side</span> <span class="nt">--force-conflicts</span> <span class="se">\</span>
  <span class="nt">-k</span> <span class="s1">'https://github.com/argoproj/argo-cd/manifests/crds?ref=stable'</span>
</code></pre></div></div>

<h3 id="6-资源一直-progressing">6. 资源一直 Progressing</h3>

<p>检查 Pod 事件与 <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/health/">Health 规则</a>：Deployment 需 ReadyReplicas 达标；Ingress 可能等待 LB 分配 IP。</p>

<h3 id="7-误删资源如何恢复">7. 误删资源如何恢复</h3>

<ul>
  <li><strong>Git 回滚</strong>：<code class="language-plaintext highlighter-rouge">git revert</code> 后 Sync，或 UI <strong>History and Rollback</strong> 到先前 Git revision</li>
  <li>若启用了 <code class="language-plaintext highlighter-rouge">prune</code> 且 Manifest 已删：从 Git 历史恢复 YAML 再 Sync</li>
</ul>

<h3 id="8-与-flux-选哪个">8. 与 Flux 选哪个？</h3>

<table>
  <thead>
    <tr>
      <th>维度</th>
      <th>Argo CD</th>
      <th>Flux</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>UI</td>
      <td>功能完整的 Web UI</td>
      <td>以 CLI/CR 为主（Weave GitOps UI 可选）</td>
    </tr>
    <tr>
      <td>多租户</td>
      <td>AppProject + RBAC 成熟</td>
      <td>依赖 Flux CR 与 RBAC 组合</td>
    </tr>
    <tr>
      <td>生态</td>
      <td>ApplicationSet、Image Updater、Rollouts</td>
      <td>与 Flagger、Helm Controller 等组合</td>
    </tr>
  </tbody>
</table>

<p>两者都是成熟的 GitOps 工具，选型取决于团队对 UI、多集群与现有 Argo 生态的偏好。</p>

<h3 id="9-调试命令">9. 调试命令</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 应用详情与 Diff</span>
argocd app get &lt;app&gt; <span class="nt">--show-params</span>
argocd app diff &lt;app&gt;

<span class="c"># Controller 日志</span>
kubectl logs <span class="nt">-n</span> argocd deploy/argocd-application-controller <span class="nt">-f</span>

<span class="c"># Repo 渲染错误</span>
kubectl logs <span class="nt">-n</span> argocd deploy/argocd-repo-server <span class="nt">-f</span>
</code></pre></div></div>

<p>官方 <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/troubleshooting/">Troubleshooting Tools</a> 还包含 <code class="language-plaintext highlighter-rouge">argocd admin</code> 子命令用于导出集群内 Application 状态。</p>

<h3 id="10-安全注意事项">10. 安全注意事项</h3>

<ul>
  <li>修改默认 admin 密码，接入 SSO（<a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/">User Management</a>）</li>
  <li>限制谁可创建 Cluster 级 Application</li>
  <li>阅读 <a href="https://argo-cd.readthedocs.io/en/stable/security_considerations/">Security Considerations</a></li>
  <li>生产使用 TLS Ingress，避免长期 <code class="language-plaintext highlighter-rouge">--insecure</code></li>
</ul>

<hr />

<h2 id="caveats">注意事项</h2>

<blockquote class="prompt-danger">
  <p><strong>不要在生产直接使用非 HA <code class="language-plaintext highlighter-rouge">install.yaml</code></strong>：评估可用，生产请用 HA 清单或 Helm Chart，并 Pin 版本。</p>
</blockquote>

<ul>
  <li><strong>CI 与 CD 职责分离</strong>：CI 更新 Git，Argo CD 部署；避免双写集群。</li>
  <li><strong>Automated prune 有风险</strong>：配合分支保护、PR 评审与备份策略。</li>
  <li><strong>本地示例 Guestbook 可能为 AMD64 镜像</strong>：ARM 节点需确认镜像架构（见 Getting Started Note）。</li>
  <li><strong>自定义 namespace 安装</strong>：需 patch <code class="language-plaintext highlighter-rouge">ClusterRoleBinding</code> 中的 ServiceAccount namespace（<a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/installation/#installing-argo-cd-in-a-custom-namespace">Installation — Custom Namespace</a>）。</li>
  <li><strong>升级前阅读 <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/upgrading/overview/">Upgrading Overview</a></strong>：CRD 与 RBAC 变更可能影响现有 Application。</li>
</ul>

<hr />

<h2 id="references">参考资源</h2>

<h3 id="官方文档">官方文档</h3>

<table>
  <thead>
    <tr>
      <th>说明</th>
      <th>链接</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>文档首页 / Overview</td>
      <td><a href="https://argo-cd.readthedocs.io/en/stable/">argo-cd.readthedocs.io</a></td>
    </tr>
    <tr>
      <td>Getting Started</td>
      <td><a href="https://argo-cd.readthedocs.io/en/stable/getting_started/">getting_started</a></td>
    </tr>
    <tr>
      <td>Installation</td>
      <td><a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/installation/">operator-manual/installation</a></td>
    </tr>
    <tr>
      <td>Core Concepts</td>
      <td><a href="https://argo-cd.readthedocs.io/en/stable/core_concepts/">core_concepts</a></td>
    </tr>
    <tr>
      <td>Architecture</td>
      <td><a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/architecture/">operator-manual/architecture</a></td>
    </tr>
    <tr>
      <td>Best Practices</td>
      <td><a href="https://argo-cd.readthedocs.io/en/stable/user-guide/best_practices/">user-guide/best_practices</a></td>
    </tr>
    <tr>
      <td>Application 规范</td>
      <td><a href="https://argo-cd.readthedocs.io/en/stable/user-guide/application-specification/">application-specification</a></td>
    </tr>
    <tr>
      <td>FAQ</td>
      <td><a href="https://argo-cd.readthedocs.io/en/stable/faq/">faq</a></td>
    </tr>
    <tr>
      <td>GitHub 源码</td>
      <td><a href="https://github.com/argoproj/argo-cd">github.com/argoproj/argo-cd</a></td>
    </tr>
    <tr>
      <td>项目官网</td>
      <td><a href="https://argoproj.github.io/cd/">argoproj.github.io/cd</a></td>
    </tr>
    <tr>
      <td>在线 Demo</td>
      <td><a href="https://cd.apps.argoproj.io/">cd.apps.argoproj.io</a></td>
    </tr>
  </tbody>
</table>

<h3 id="社区与视频">社区与视频</h3>

<table>
  <thead>
    <tr>
      <th>说明</th>
      <th>链接</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Octopus — Understanding Argo CD</td>
      <td><a href="https://octopus.com/devops/argo-cd/">octopus.com/devops/argo-cd</a></td>
    </tr>
    <tr>
      <td>Argo CD Overview Demo（SIG Apps）</td>
      <td><a href="https://youtu.be/aWDIQMbp1cc">YouTube — aWDIQMbp1cc</a></td>
    </tr>
    <tr>
      <td>Introduction to Argo CD</td>
      <td><a href="https://www.youtube.com/watch?v=4N23CGCNCJ8">YouTube — 4N23CGCNCJ8</a></td>
    </tr>
    <tr>
      <td>GitOps Deployment and Kubernetes</td>
      <td><a href="https://www.youtube.com/watch?v=nYVir5kY5kA">YouTube — nYVir5kY5kA</a></td>
    </tr>
    <tr>
      <td>Getting Started with ArgoCD</td>
      <td><a href="https://www.youtube.com/watch?v=8AJlVQy6Cx0">YouTube — 8AJlVQy6Cx0</a></td>
    </tr>
    <tr>
      <td>Awesome Argo 资源列表</td>
      <td><a href="https://github.com/terrytangy/awesome-argo">GitHub — awesome-argo</a></td>
    </tr>
  </tbody>
</table>

<h3 id="站内延伸阅读">站内延伸阅读</h3>

<table>
  <thead>
    <tr>
      <th>主题</th>
      <th>文章</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Helm Chart 与 Release</td>
      <td><a href="/posts/helm-in-action/">Helm in Action</a></td>
    </tr>
    <tr>
      <td>Kubernetes 基础</td>
      <td><a href="/posts/k8s-in-action/">Kubernetes in Action</a></td>
    </tr>
    <tr>
      <td>配置管理</td>
      <td><a href="/posts/kubernetes-configmap/">Kubernetes ConfigMap 完全指南</a></td>
    </tr>
    <tr>
      <td>可观测性</td>
      <td><a href="/posts/opentelemetry-in-action/">OpenTelemetry in Action</a></td>
    </tr>
  </tbody>
</table>]]></content><author><name></name></author><category term="云原生" /><category term="Argo CD" /><category term="GitOps" /><category term="Kubernetes" /><category term="DevOps" /><category term="持续交付" /><summary type="html"><![CDATA[从 GitOps 原理到 Argo CD 安装、Application 配置、同步策略与多集群管理，结合官方文档梳理架构、最佳实践与常见问题排查。]]></summary></entry><entry><title type="html">Golangci-lint 实战：用法、配置与最佳实践</title><link href="http://172.19.0.16:8080/posts/golangci-lint-in-action/" rel="alternate" type="text/html" title="Golangci-lint 实战：用法、配置与最佳实践" /><published>2026-07-06T10:28:57+08:00</published><updated>2026-08-11T14:22:25+08:00</updated><id>http://172.19.0.16:8080/posts/golangci-lint-in-action</id><content type="html" xml:base="http://172.19.0.16:8080/posts/golangci-lint-in-action/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#go-vet-与-staticcheck轻量静态检查" id="markdown-toc-go-vet-与-staticcheck轻量静态检查">go vet 与 Staticcheck：轻量静态检查</a>    <ul>
      <li><a href="#go-vet" id="markdown-toc-go-vet">go vet</a>        <ul>
          <li><a href="#基本用法" id="markdown-toc-基本用法">基本用法</a></li>
          <li><a href="#内置-checker-一览" id="markdown-toc-内置-checker-一览">内置 checker 一览</a></li>
          <li><a href="#选择性启用--禁用-checker" id="markdown-toc-选择性启用--禁用-checker">选择性启用 / 禁用 checker</a></li>
          <li><a href="#构建相关-flag" id="markdown-toc-构建相关-flag">构建相关 flag</a></li>
          <li><a href="#自定义分析器-vettool" id="markdown-toc-自定义分析器-vettool">自定义分析器：<code class="language-plaintext highlighter-rouge">-vettool</code></a></li>
          <li><a href="#go-vet-的边界" id="markdown-toc-go-vet-的边界">go vet 的边界</a></li>
        </ul>
      </li>
      <li><a href="#staticcheck" id="markdown-toc-staticcheck">Staticcheck</a>        <ul>
          <li><a href="#安装" id="markdown-toc-安装">安装</a></li>
          <li><a href="#基本用法-1" id="markdown-toc-基本用法-1">基本用法</a></li>
          <li><a href="#理解检查-explain" id="markdown-toc-理解检查-explain">理解检查：<code class="language-plaintext highlighter-rouge">-explain</code></a></li>
          <li><a href="#目标-go-版本-go" id="markdown-toc-目标-go-版本-go">目标 Go 版本：<code class="language-plaintext highlighter-rouge">-go</code></a></li>
          <li><a href="#测试代码-tests" id="markdown-toc-测试代码-tests">测试代码：<code class="language-plaintext highlighter-rouge">-tests</code></a></li>
          <li><a href="#构建-tag" id="markdown-toc-构建-tag">构建 tag</a></li>
          <li><a href="#输出格式-f" id="markdown-toc-输出格式-f">输出格式：<code class="language-plaintext highlighter-rouge">-f</code></a></li>
          <li><a href="#配置文件staticcheckconf" id="markdown-toc-配置文件staticcheckconf">配置文件：<code class="language-plaintext highlighter-rouge">staticcheck.conf</code></a></li>
          <li><a href="#忽略问题lint-directive" id="markdown-toc-忽略问题lint-directive">忽略问题：lint directive</a></li>
          <li><a href="#编辑器gopls" id="markdown-toc-编辑器gopls">编辑器：gopls</a></li>
        </ul>
      </li>
      <li><a href="#组合用法makefile-与-ci" id="markdown-toc-组合用法makefile-与-ci">组合用法：Makefile 与 CI</a>        <ul>
          <li><a href="#makefile" id="markdown-toc-makefile">Makefile</a></li>
          <li><a href="#github-actions" id="markdown-toc-github-actions">GitHub Actions</a></li>
          <li><a href="#与-golangci-lint-的关系" id="markdown-toc-与-golangci-lint-的关系">与 golangci-lint 的关系</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#golangci-lint-解决什么问题" id="markdown-toc-golangci-lint-解决什么问题">golangci-lint 解决什么问题</a></li>
  <li><a href="#安装-1" id="markdown-toc-安装-1">安装</a>    <ul>
      <li><a href="#本地首选installsh-二进制" id="markdown-toc-本地首选installsh-二进制">本地首选：<code class="language-plaintext highlighter-rouge">install.sh</code> 二进制</a></li>
      <li><a href="#mise多工具版本管理" id="markdown-toc-mise多工具版本管理">mise（多工具版本管理）</a></li>
      <li><a href="#docker" id="markdown-toc-docker">Docker</a></li>
      <li><a href="#包管理器便利但需注意版本" id="markdown-toc-包管理器便利但需注意版本">包管理器（便利但需注意版本）</a>        <ul>
          <li><a href="#macos--homebrew" id="markdown-toc-macos--homebrew">macOS — Homebrew</a></li>
          <li><a href="#linux--发行版包" id="markdown-toc-linux--发行版包">Linux — 发行版包</a></li>
          <li><a href="#windows--chocolatey--scoop" id="markdown-toc-windows--chocolatey--scoop">Windows — Chocolatey / Scoop</a></li>
          <li><a href="#macports" id="markdown-toc-macports">MacPorts</a></li>
        </ul>
      </li>
      <li><a href="#ci-环境官方推荐路径" id="markdown-toc-ci-环境官方推荐路径">CI 环境：官方推荐路径</a>        <ul>
          <li><a href="#如何应对版本与-linter-集双-pin" id="markdown-toc-如何应对版本与-linter-集双-pin">如何应对：版本与 linter 集「双 pin」</a></li>
          <li><a href="#github-actionsgithub-项目首选" id="markdown-toc-github-actionsgithub-项目首选">GitHub Actions（GitHub 项目首选）</a></li>
          <li><a href="#gitlab--buildkite--其他-ci" id="markdown-toc-gitlab--buildkite--其他-ci">GitLab / Buildkite / 其他 CI</a></li>
        </ul>
      </li>
      <li><a href="#不推荐从源码--go-install--go-tool" id="markdown-toc-不推荐从源码--go-install--go-tool">不推荐：从源码 / <code class="language-plaintext highlighter-rouge">go install</code> / <code class="language-plaintext highlighter-rouge">go tool</code></a></li>
      <li><a href="#安装方式对照小结" id="markdown-toc-安装方式对照小结">安装方式对照小结</a></li>
    </ul>
  </li>
  <li><a href="#快速开始" id="markdown-toc-快速开始">快速开始</a>    <ul>
      <li><a href="#lint" id="markdown-toc-lint">Lint</a></li>
      <li><a href="#format" id="markdown-toc-format">Format</a></li>
    </ul>
  </li>
  <li><a href="#配置文件" id="markdown-toc-配置文件">配置文件</a>    <ul>
      <li><a href="#v2-顶层结构" id="markdown-toc-v2-顶层结构">v2 顶层结构</a></li>
      <li><a href="#示例-1团队基线推荐起点" id="markdown-toc-示例-1团队基线推荐起点">示例 1：团队基线（推荐起点）</a></li>
      <li><a href="#示例-2严格模式新项目--绿色field" id="markdown-toc-示例-2严格模式新项目--绿色field">示例 2：严格模式（新项目 / 绿色field）</a></li>
      <li><a href="#示例-3存量大仓库只卡新代码" id="markdown-toc-示例-3存量大仓库只卡新代码">示例 3：存量大仓库「只卡新代码」</a></li>
      <li><a href="#示例-4模块边界与依赖治理" id="markdown-toc-示例-4模块边界与依赖治理">示例 4：模块边界与依赖治理</a></li>
      <li><a href="#示例-5ci-输出-sarifgithub-code-scanning" id="markdown-toc-示例-5ci-输出-sarifgithub-code-scanning">示例 5：CI 输出 SARIF（GitHub Code Scanning）</a></li>
    </ul>
  </li>
  <li><a href="#行内忽略nolint" id="markdown-toc-行内忽略nolint">行内忽略：nolint</a></li>
  <li><a href="#最佳实践" id="markdown-toc-最佳实践">最佳实践</a>    <ul>
      <li><a href="#1-pin-版本避免-ci-突然全红" id="markdown-toc-1-pin-版本避免-ci-突然全红">1. Pin 版本，避免 CI 突然全红</a></li>
      <li><a href="#2-分层本地快ci-全" id="markdown-toc-2-分层本地快ci-全">2. 分层：本地快、CI 全</a></li>
      <li><a href="#3-先保证能编译" id="markdown-toc-3-先保证能编译">3. 先保证能编译</a></li>
      <li><a href="#4-测试文件单独策略" id="markdown-toc-4-测试文件单独策略">4. 测试文件单独策略</a></li>
      <li><a href="#5-与-gofmt--goimports-分工" id="markdown-toc-5-与-gofmt--goimports-分工">5. 与 gofmt / goimports 分工</a></li>
      <li><a href="#6-大仓库落地路径" id="markdown-toc-6-大仓库落地路径">6. 大仓库落地路径</a></li>
    </ul>
  </li>
  <li><a href="#ci-与编辑器集成" id="markdown-toc-ci-与编辑器集成">CI 与编辑器集成</a>    <ul>
      <li><a href="#github-actions-1" id="markdown-toc-github-actions-1">GitHub Actions</a></li>
      <li><a href="#gitlab--buildkite" id="markdown-toc-gitlab--buildkite">GitLab / Buildkite</a></li>
      <li><a href="#vs-codego-扩展" id="markdown-toc-vs-codego-扩展">VS Code（Go 扩展）</a></li>
    </ul>
  </li>
  <li><a href="#从-v1-迁移到-v2" id="markdown-toc-从-v1-迁移到-v2">从 v1 迁移到 v2</a></li>
  <li><a href="#替代方案与选型" id="markdown-toc-替代方案与选型">替代方案与选型</a></li>
  <li><a href="#faq" id="markdown-toc-faq">FAQ</a>    <ul>
      <li><a href="#支持哪些-go-版本" id="markdown-toc-支持哪些-go-版本">支持哪些 Go 版本？</a></li>
      <li><a href="#ci-里怎么用" id="markdown-toc-ci-里怎么用">CI 里怎么用？</a></li>
      <li><a href="#为什么会出现-typecheck-错误" id="markdown-toc-为什么会出现-typecheck-错误">为什么会出现 typecheck 错误？</a></li>
      <li><a href="#为什么---new-from--有时漏报" id="markdown-toc-为什么---new-from--有时漏报">为什么 <code class="language-plaintext highlighter-rouge">--new-from-*</code> 有时「漏报」？</a></li>
      <li><a href="#--fast-only-第一次为什么仍慢" id="markdown-toc---fast-only-第一次为什么仍慢"><code class="language-plaintext highlighter-rouge">--fast-only</code> 第一次为什么仍慢？</a></li>
      <li><a href="#如何减少误报" id="markdown-toc-如何减少误报">如何减少误报？</a></li>
      <li><a href="#homebrew--go-install-与官方二进制不一致" id="markdown-toc-homebrew--go-install-与官方二进制不一致">Homebrew / go install 与官方二进制不一致？</a></li>
      <li><a href="#能否自定义-linter" id="markdown-toc-能否自定义-linter">能否自定义 linter？</a></li>
    </ul>
  </li>
  <li><a href="#常用命令速查" id="markdown-toc-常用命令速查">常用命令速查</a></li>
  <li><a href="#注意事项" id="markdown-toc-注意事项">注意事项</a></li>
  <li><a href="#参考资源" id="markdown-toc-参考资源">参考资源</a></li>
</ul>

<p>Go 项目做静态检查时，<strong>最常见的基础组合</strong>是 <code class="language-plaintext highlighter-rouge">go vet ./...</code> 加上 <a href="https://staticcheck.io/">Staticcheck</a>：前者随 Go 工具链自带，后者专注高信噪比的 bug / 性能 / 简化建议。两者覆盖「编译器级可疑写法」和「correctness 规则」，对小项目、库作者或「只要 correctness、不要风格大棒」的团队往往已经足够。下文 <a href="#go-vet-与-staticcheck轻量静态检查">go vet 与 Staticcheck</a> 一节展开详细用法；若还需要统一风格、安全、复杂度、测试规范，往往要再装 revive、gosec、errcheck 等十几个工具——这时 <a href="#golangci-lint-解决什么问题">golangci-lint</a> 的聚合价值才体现出来。</p>

<p class="prompt-info">站内若已读过 <a href="/posts/go-in-action/">Go 实战</a> 里 2019 年的简短介绍，或 <a href="/posts/100-go-mistakes/">100 Go Mistakes</a> 对 <code class="language-plaintext highlighter-rouge">go vet</code> / golangci-lint 的提及，本文会从零展开 <strong>vet + Staticcheck 基础路径</strong> 与 <strong>golangci-lint 团队规范</strong> 两条线，并补充 v2 迁移与替代方案讨论。</p>

<h1 id="go-vet-与-staticcheck轻量静态检查">go vet 与 Staticcheck：轻量静态检查</h1>

<pre><code class="language-mermaid">flowchart LR
    subgraph toolchain [Go 工具链自带]
        VET[go vet ./...]
    end
    subgraph sc [Staticcheck 单独安装]
        SC[staticcheck ./...]
    end
    SRC[Go 源码] --&gt; VET
    SRC --&gt; SC
    VET --&gt; OUT[终端 / CI 非零退出]
    SC --&gt; OUT
</code></pre>

<table>
  <thead>
    <tr>
      <th style="text-align: left">工具</th>
      <th style="text-align: left">安装</th>
      <th style="text-align: left">定位</th>
      <th style="text-align: left">典型命令</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">go vet</code></strong></td>
      <td style="text-align: left">Go 自带</td>
      <td style="text-align: left">官方可疑写法启发式检查</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">go vet ./...</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Staticcheck</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">go install</code> 或 Release 二进制</td>
      <td style="text-align: left">150+ 条低误报静态分析规则</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">staticcheck ./...</code></td>
    </tr>
  </tbody>
</table>

<p class="prompt-tip">Staticcheck 官方建议：<strong>在 <code class="language-plaintext highlighter-rouge">go vet ./...</code> 之外再跑</strong> <code class="language-plaintext highlighter-rouge">staticcheck ./...</code>，用法刻意对齐 <code class="language-plaintext highlighter-rouge">go build</code> / <code class="language-plaintext highlighter-rouge">go vet</code> 的包路径语义（见 <a href="https://staticcheck.dev/docs/getting-started/">Getting started</a>）。golangci-lint 内置的 <code class="language-plaintext highlighter-rouge">govet</code> 与 <code class="language-plaintext highlighter-rouge">staticcheck</code> linter 本质上就是在编排这两类（及更多）分析。</p>

<h2 id="go-vet">go vet</h2>

<p><code class="language-plaintext highlighter-rouge">go vet</code> 检查 Go 源码中的 <strong>可疑写法</strong>（如 <code class="language-plaintext highlighter-rouge">Printf</code> 格式串与参数不匹配、错误的 <code class="language-plaintext highlighter-rouge">defer</code>、向 <code class="language-plaintext highlighter-rouge">errors.As</code> 传入非指针等）。它使用启发式规则，<strong>不保证</strong>报出的每一条都是真 bug，但能在编译器未报错时发现常见问题。官方说明见 <a href="https://pkg.go.dev/cmd/vet"><code class="language-plaintext highlighter-rouge">go doc cmd/vet</code></a>。</p>

<h3 id="基本用法">基本用法</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 当前目录 package</span>
go vet

<span class="c"># 整个模块（最常用）</span>
go vet ./...

<span class="c"># 指定子树</span>
go vet ./cmd/... ./internal/...

<span class="c"># 单个 package 路径</span>
go vet my/module/pkg/foo
</code></pre></div></div>

<p>包路径规则与 <code class="language-plaintext highlighter-rouge">go test</code>、<code class="language-plaintext highlighter-rouge">go build</code> 相同：<code class="language-plaintext highlighter-rouge">./...</code> 表示递归所有 package；详见 <code class="language-plaintext highlighter-rouge">go help packages</code>。</p>

<p><strong>退出码</strong>：调用错误或发现问题时 <strong>非零</strong>，CI 可直接 <code class="language-plaintext highlighter-rouge">go vet ./...</code> 作为门禁。</p>

<h3 id="内置-checker-一览">内置 checker 一览</h3>

<p>运行 <code class="language-plaintext highlighter-rouge">go tool vet help</code> 可列出全部 checker。常用项包括：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Checker</th>
      <th style="text-align: left">检查内容</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">printf</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">Print</code>/<code class="language-plaintext highlighter-rouge">Printf</code>/<code class="language-plaintext highlighter-rouge">Sprint</code> 等与 format 参数一致性</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">assign</code></td>
      <td style="text-align: left">无效赋值</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">atomic</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">sync/atomic</code> 常见误用</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">copylocks</code></td>
      <td style="text-align: left">锁被值拷贝传递</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">errorsas</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">errors.As</code> 参数类型</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">httpresponse</code></td>
      <td style="text-align: left">HTTP response body 未关闭等</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">loopclosure</code></td>
      <td style="text-align: left">循环变量被闭包错误捕获</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">lostcancel</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">context.WithCancel</code> 的 cancel 未调用</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">shift</code></td>
      <td style="text-align: left">移位宽度超出整数位宽</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">slog</code></td>
      <td style="text-align: left">结构化日志 <code class="language-plaintext highlighter-rouge">slog</code> 调用合法性</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">structtag</code></td>
      <td style="text-align: left">struct tag 是否符合 <code class="language-plaintext highlighter-rouge">reflect.StructTag</code> 规则</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">testinggoroutine</code></td>
      <td style="text-align: left">在测试启动的 goroutine 里调用 <code class="language-plaintext highlighter-rouge">t.Fatal</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">unreachable</code></td>
      <td style="text-align: left">不可达代码</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">unusedresult</code></td>
      <td style="text-align: left">忽略某些函数的返回值</td>
    </tr>
  </tbody>
</table>

<p>查看某个 checker 的细节与专属 flag，例如：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>go tool vet <span class="nb">help printf</span>
</code></pre></div></div>

<h3 id="选择性启用--禁用-checker">选择性启用 / 禁用 checker</h3>

<p>默认 <strong>运行全部</strong> checker。若显式将某个 flag 设为 <code class="language-plaintext highlighter-rouge">true</code>，则 <strong>只跑</strong> 列出的那些；若设为 <code class="language-plaintext highlighter-rouge">false</code>，则 <strong>排除</strong> 该 checker：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 只跑 printf 检查</span>
go vet <span class="nt">-printf</span><span class="o">=</span><span class="nb">true</span> ./...

<span class="c"># 跑全部，但跳过 shift</span>
go vet <span class="nt">-shift</span><span class="o">=</span><span class="nb">false</span> ./...
</code></pre></div></div>

<h3 id="构建相关-flag">构建相关 flag</h3>

<p><code class="language-plaintext highlighter-rouge">go vet</code> 支持与 <code class="language-plaintext highlighter-rouge">go build</code> 相同的 <strong>包解析与执行</strong> flag，例如：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>go vet <span class="nt">-tags</span><span class="o">=</span>integration ./...
go vet <span class="nt">-C</span> subdir ./...    <span class="c"># 在 subdir 下执行（Go 1.20+）</span>
</code></pre></div></div>

<p>JSON 输出（便于 CI 解析）：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>go vet <span class="nt">-json</span> ./...
</code></pre></div></div>

<h3 id="自定义分析器-vettool">自定义分析器：<code class="language-plaintext highlighter-rouge">-vettool</code></h3>

<p>可替换或扩展 vet 使用的分析工具：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>go <span class="nb">install </span>golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest
go vet <span class="nt">-vettool</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>which shadow<span class="si">)</span><span class="s2">"</span> ./...
</code></pre></div></div>

<p>适合试验 <code class="language-plaintext highlighter-rouge">golang.org/x/tools/go/analysis</code> 生态里的单个 pass；团队日常仍以默认 <code class="language-plaintext highlighter-rouge">go vet</code> 为主。</p>

<h3 id="go-vet-的边界">go vet 的边界</h3>

<ul>
  <li>依赖 <strong>整 package 可编译</strong>；与 golangci-lint 的 <code class="language-plaintext highlighter-rouge">typecheck</code> 类似，缺文件或语法错误会导致分析失败。</li>
  <li>规则集 <strong>有限</strong>，不包含 unused 导出、API 废弃、性能级简化等——这些交给 Staticcheck。</li>
  <li>官方明确：应作 <strong>指导</strong>，不能当作「程序正确」的充分条件。</li>
</ul>

<h2 id="staticcheck">Staticcheck</h2>

<p><a href="https://staticcheck.io/">Staticcheck</a> 是 Go 生态里事实上的 <strong>correctness 向</strong> 静态分析工具：150+ 条检查（<a href="https://staticcheck.io/docs/checks/">Checks 列表</a>），强调 <strong>低误报、可行动</strong>；许多团队在 CI 里只开 vet + Staticcheck 而不引入 golangci-lint。站内 <a href="/posts/go-in-action/">Go 实战</a> 亦有简介。</p>

<h3 id="安装">安装</h3>

<p>Go 1.17+ 最常见方式（<strong>务必 pin 版本</strong>，勿在 CI 用 <code class="language-plaintext highlighter-rouge">@latest</code>）：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>go <span class="nb">install </span>honnef.co/go/tools/cmd/staticcheck@2025.1.1

staticcheck <span class="nt">-version</span>
</code></pre></div></div>

<p>也可从 <a href="https://github.com/dominikh/go-tools/releases">GitHub Releases</a> 下载预编译二进制，或通过发行版包管理器安装（版本可能滞后）。</p>

<p>Staticcheck 版本号格式为 <strong><code class="language-plaintext highlighter-rouge">YYYY.N</code></strong>（如 <code class="language-plaintext highlighter-rouge">2025.1.1</code>），与 Go 的 <code class="language-plaintext highlighter-rouge">1.22</code> 版本号不同；升级时需阅读 Release note，新版本可能 <strong>新增检查</strong> 导致 CI 失败。</p>

<h3 id="基本用法-1">基本用法</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 整个模块（与 go vet 相同习惯）</span>
staticcheck ./...

<span class="c"># 当前目录</span>
staticcheck <span class="nb">.</span>

<span class="c"># 指定 package</span>
staticcheck ./cmd/server/...
</code></pre></div></div>

<p>输出格式与 vet 类似，每条带 <strong>检查 ID</strong>：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>foo.go:42:9: unnecessary use of fmt.Sprintf (S1039)
</code></pre></div></div>

<h3 id="理解检查-explain">理解检查：<code class="language-plaintext highlighter-rouge">-explain</code></h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>staticcheck <span class="nt">-explain</span> S1039
</code></pre></div></div>

<p>会输出摘要、说明段落、首次引入版本及文档链接——排查「该不该修」时非常有用。</p>

<h3 id="目标-go-版本-go">目标 Go 版本：<code class="language-plaintext highlighter-rouge">-go</code></h3>

<p>默认读取 <code class="language-plaintext highlighter-rouge">go.mod</code> 里 <code class="language-plaintext highlighter-rouge">go</code> 指令作为目标版本（影响「某简化是否适用于当前最低 Go 版本」等建议）。可手动覆盖：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>staticcheck <span class="nt">-go</span> 1.22 ./...
</code></pre></div></div>

<h3 id="测试代码-tests">测试代码：<code class="language-plaintext highlighter-rouge">-tests</code></h3>

<p>默认 <strong>包含</strong> <code class="language-plaintext highlighter-rouge">_test.go</code>。若只想找「仅被测试引用」的死代码，可跳过测试：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>staticcheck <span class="nt">-tests</span><span class="o">=</span><span class="nb">false</span> ./...
</code></pre></div></div>

<h3 id="构建-tag">构建 tag</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>staticcheck <span class="nt">-tags</span><span class="o">=</span>integration ./...
</code></pre></div></div>

<p>与 <code class="language-plaintext highlighter-rouge">go vet -tags</code> 一致。</p>

<h3 id="输出格式-f">输出格式：<code class="language-plaintext highlighter-rouge">-f</code></h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>staticcheck <span class="nt">-f</span> stylish ./...
staticcheck <span class="nt">-f</span> json ./...
</code></pre></div></div>

<p>完整列表见 <a href="https://staticcheck.dev/docs/running-staticcheck/cli/formatters/">Formatters</a>。</p>

<h3 id="配置文件staticcheckconf">配置文件：<code class="language-plaintext highlighter-rouge">staticcheck.conf</code></h3>

<p>项目级配置放在 <strong><code class="language-plaintext highlighter-rouge">staticcheck.conf</code></strong>（TOML），对 <strong>子目录树</strong> 生效；子目录中的文件会 <strong>覆盖</strong> 上级选项（见 <a href="https://staticcheck.dev/docs/configuration/">Configuration</a>）。</p>

<p>目录示例：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.
├── staticcheck.conf          # 全仓库默认
├── internal/
│   └── staticcheck.conf      # 仅 internal/...
└── pkg/
    └── foo/
        └── staticcheck.conf  # 仅 pkg/foo/...
</code></pre></div></div>

<p><strong>默认等价配置</strong>（理解「出厂行为」）：</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">checks</span> <span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s">"all"</span><span class="p">,</span> <span class="s">"-SA9003"</span><span class="p">,</span> <span class="s">"-ST1000"</span><span class="p">,</span> <span class="s">"-ST1003"</span><span class="p">,</span> <span class="s">"-ST1016"</span><span class="p">,</span> <span class="s">"-ST1020"</span><span class="p">,</span> <span class="s">"-ST1021"</span><span class="p">,</span> <span class="s">"-ST1022"</span><span class="p">,</span> <span class="s">"-ST1023"</span><span class="p">]</span>
<span class="n">initialisms</span> <span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s">"ACL"</span><span class="p">,</span> <span class="s">"API"</span><span class="p">,</span> <span class="s">"ASCII"</span><span class="p">,</span> <span class="s">"CPU"</span><span class="p">,</span> <span class="s">"CSS"</span><span class="p">,</span> <span class="s">"DNS"</span><span class="p">,</span> <span class="s">"EOF"</span><span class="p">,</span> <span class="s">"GUID"</span><span class="p">,</span> <span class="s">"HTML"</span><span class="p">,</span> <span class="s">"HTTP"</span><span class="p">,</span> <span class="s">"HTTPS"</span><span class="p">,</span> <span class="s">"ID"</span><span class="p">,</span> <span class="s">"IP"</span><span class="p">,</span> <span class="s">"JSON"</span><span class="p">,</span> <span class="s">"QPS"</span><span class="p">,</span> <span class="s">"RAM"</span><span class="p">,</span> <span class="s">"RPC"</span><span class="p">,</span> <span class="s">"SLA"</span><span class="p">,</span> <span class="s">"SMTP"</span><span class="p">,</span> <span class="s">"SQL"</span><span class="p">,</span> <span class="s">"SSH"</span><span class="p">,</span> <span class="s">"TCP"</span><span class="p">,</span> <span class="s">"TLS"</span><span class="p">,</span> <span class="s">"TTL"</span><span class="p">,</span> <span class="s">"UDP"</span><span class="p">,</span> <span class="s">"UI"</span><span class="p">,</span> <span class="s">"GID"</span><span class="p">,</span> <span class="s">"UID"</span><span class="p">,</span> <span class="s">"UUID"</span><span class="p">,</span> <span class="s">"URI"</span><span class="p">,</span> <span class="s">"URL"</span><span class="p">,</span> <span class="s">"UTF8"</span><span class="p">,</span> <span class="s">"VM"</span><span class="p">,</span> <span class="s">"XML"</span><span class="p">,</span> <span class="s">"XMPP"</span><span class="p">,</span> <span class="s">"XSRF"</span><span class="p">,</span> <span class="s">"XSS"</span><span class="p">,</span> <span class="s">"SIP"</span><span class="p">,</span> <span class="s">"RTP"</span><span class="p">,</span> <span class="s">"AMQP"</span><span class="p">,</span> <span class="s">"DB"</span><span class="p">,</span> <span class="s">"TS"</span><span class="p">]</span>
</code></pre></div></div>

<p>常用定制：</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 启用全部，但关闭「空 if/else 分支」和「包注释」类风格检查</span>
<span class="n">checks</span> <span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s">"all"</span><span class="p">,</span> <span class="s">"-SA9003"</span><span class="p">,</span> <span class="s">"-ST1000"</span><span class="p">]</span>

<span class="c"># 在 inherited 基础上追加/排除</span>
<span class="n">checks</span> <span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s">"inherit"</span><span class="p">,</span> <span class="s">"-ST1000"</span><span class="p">]</span>

<span class="c"># 常见缩写词（影响 ST1003 等命名检查）</span>
<span class="n">initialisms</span> <span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s">"ACL"</span><span class="p">,</span> <span class="s">"API"</span><span class="p">,</span> <span class="s">"HTTP"</span><span class="p">,</span> <span class="s">"ID"</span><span class="p">,</span> <span class="s">"URL"</span><span class="p">,</span> <span class="s">"gRPC"</span><span class="p">]</span>
</code></pre></div></div>

<p>检查 ID 前缀含义：<code class="language-plaintext highlighter-rouge">SA</code> 静态分析 / bug，<code class="language-plaintext highlighter-rouge">S</code> 简化建议，<code class="language-plaintext highlighter-rouge">ST</code> 风格，<code class="language-plaintext highlighter-rouge">U</code> unused 等——详见 <a href="https://staticcheck.io/docs/checks/">Checks</a>。</p>

<h3 id="忽略问题lint-directive">忽略问题：lint directive</h3>

<p>优先 <strong>改代码</strong>；确需忽略时使用 directive（<strong>必须写 reason</strong>）：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">//lint:ignore SA4000 we intentionally compare two errors.New results in test</span>
<span class="k">if</span> <span class="n">errors</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="s">"abc"</span><span class="p">)</span> <span class="o">==</span> <span class="n">errors</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="s">"abc"</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">t</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">`New("abc") == New("abc")`</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>整文件（常见于生成代码）：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">//lint:file-ignore U1000 Ignore all unused code, it's generated</span>
</code></pre></div></div>

<p>过期的 <code class="language-plaintext highlighter-rouge">//lint:ignore</code> 会被 Staticcheck 报告为 <strong>应删除</strong>，避免注释与代码脱节。</p>

<h3 id="编辑器gopls">编辑器：gopls</h3>

<p>VS Code / GoLand 等通过 <strong>gopls</strong> 可启用 Staticcheck 分析（<code class="language-plaintext highlighter-rouge">"staticcheck": true</code>），见 <a href="https://github.com/golang/tools/blob/master/gopls/doc/settings.md#staticcheck-bool">gopls settings</a>。本地实时诊断 + CI 跑 <code class="language-plaintext highlighter-rouge">staticcheck ./...</code> 是常见组合。</p>

<h2 id="组合用法makefile-与-ci">组合用法：Makefile 与 CI</h2>

<h3 id="makefile">Makefile</h3>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">vet staticcheck lint</span>

<span class="nl">vet</span><span class="o">:</span>
	go vet ./...

<span class="nl">staticcheck</span><span class="o">:</span>
	staticcheck ./...

<span class="nl">lint</span><span class="o">:</span> <span class="nf">vet staticcheck</span>
</code></pre></div></div>

<h3 id="github-actions">GitHub Actions</h3>

<p>vet 用标准 <code class="language-plaintext highlighter-rouge">run</code>；Staticcheck 可用官方 <a href="https://staticcheck.dev/docs/running-staticcheck/ci/github-actions/">staticcheck-action</a>（<strong>pin <code class="language-plaintext highlighter-rouge">version</code></strong>）：</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">CI</span>
<span class="na">on</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">push</span><span class="pi">,</span> <span class="nv">pull_request</span><span class="pi">]</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">analyze</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/setup-go@v5</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">go-version</span><span class="pi">:</span> <span class="s">stable</span>
      <span class="pi">-</span> <span class="na">run</span><span class="pi">:</span> <span class="s">go vet ./...</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">dominikh/staticcheck-action@v1.3.0</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2025.1.1"</span>
          <span class="na">install-go</span><span class="pi">:</span> <span class="kc">false</span>   <span class="c1"># 上一步已装 Go</span>
</code></pre></div></div>

<p>若 job 里还有 <code class="language-plaintext highlighter-rouge">go test</code>，建议 <code class="language-plaintext highlighter-rouge">install-go: false</code> 并共用 Go 构建缓存，避免重复下载。</p>

<h3 id="与-golangci-lint-的关系">与 golangci-lint 的关系</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">维度</th>
      <th style="text-align: left"><code class="language-plaintext highlighter-rouge">go vet</code> + Staticcheck</th>
      <th style="text-align: left">golangci-lint</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">安装</td>
      <td style="text-align: left">vet 零成本；Staticcheck 一个二进制</td>
      <td style="text-align: left">一个二进制含 100+ linter</td>
    </tr>
    <tr>
      <td style="text-align: left">配置</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">staticcheck.conf</code>；vet 几乎无配置</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">.golangci.yml</code> 统一一切</td>
    </tr>
    <tr>
      <td style="text-align: left">速度</td>
      <td style="text-align: left">通常更快、依赖更少</td>
      <td style="text-align: left">功能多，冷启动更重</td>
    </tr>
    <tr>
      <td style="text-align: left">覆盖</td>
      <td style="text-align: left">correctness 为主</td>
      <td style="text-align: left">+ 安全、风格、复杂度、测试规范等</td>
    </tr>
    <tr>
      <td style="text-align: left">重叠</td>
      <td style="text-align: left">—</td>
      <td style="text-align: left">内置 <code class="language-plaintext highlighter-rouge">govet</code>、<code class="language-plaintext highlighter-rouge">staticcheck</code></td>
    </tr>
  </tbody>
</table>

<p><strong>选型建议</strong>：</p>

<ul>
  <li>库、小型服务、个人项目：<strong><code class="language-plaintext highlighter-rouge">go vet ./...</code> + <code class="language-plaintext highlighter-rouge">staticcheck ./...</code></strong> 往往足够。</li>
  <li>需要 gosec、revive、errcheck、depguard 等 <strong>团队政策</strong>：上 golangci-lint，或继续 vet + Staticcheck 再 <strong>按需</strong> 加个别工具。</li>
  <li>已有 golangci-lint 且启用 <code class="language-plaintext highlighter-rouge">govet</code>/<code class="language-plaintext highlighter-rouge">staticcheck</code>：<strong>不必</strong>再单独跑一遍（除非想拆分 CI job 或对比输出）。</li>
</ul>

<h1 id="golangci-lint-解决什么问题">golangci-lint 解决什么问题</h1>

<p><a href="https://golangci-lint.run/">golangci-lint</a> 的定位是 <strong>Go 静态分析聚合器（linters runner）</strong>：并行跑上百个 linter，复用 Go build cache 与自身分析缓存，用一份 YAML 配置统一开关与排除规则，输出格式可选 text、JSON、SARIF、JUnit 等。官方仓库见 <a href="https://github.com/golangci/golangci-lint">golangci/golangci-lint</a>；当前主线为 <strong>v2.x</strong>（本文以 <strong>v2.12.2</strong> 为准）。</p>

<pre><code class="language-mermaid">flowchart TB
    subgraph input [输入]
        SRC[Go 源码 ./...]
        CFG[.golangci.yml]
    end
    subgraph gcl [golangci-lint]
        TC[typecheck / 编译前端]
        PAR[并行 linter 调度]
        CACHE[Build cache + 分析缓存]
    end
    subgraph linters [内置 linter 示例]
        VET[govet]
        SC[staticcheck]
        EC[errcheck]
        SEC[gosec]
        REV[revive]
    end
    subgraph output [输出]
        TXT[彩色 text]
        SARIF[SARIF / Code Scanning]
        JUNIT[JUnit XML]
    end
    SRC --&gt; TC
    CFG --&gt; PAR
    TC --&gt; PAR
    PAR --&gt; CACHE
    CACHE --&gt; VET &amp; SC &amp; EC &amp; SEC &amp; REV
    VET &amp; SC &amp; EC &amp; SEC &amp; REV --&gt; TXT &amp; SARIF &amp; JUNIT
</code></pre>

<table>
  <thead>
    <tr>
      <th style="text-align: left">能力</th>
      <th style="text-align: left">说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>聚合</strong></td>
      <td style="text-align: left">一次命令跑多个 linter，不必逐个安装、逐个写 CI step</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>并行与缓存</strong></td>
      <td style="text-align: left">多核并行；复用 <code class="language-plaintext highlighter-rouge">GOCACHE</code> 与 golangci-lint 专用缓存，二次运行明显更快</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>配置集中</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">.golangci.yml</code> 控制 enable/disable、单 linter 参数、路径排除、<code class="language-plaintext highlighter-rouge">nolint</code> 策略</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>格式化</strong></td>
      <td style="text-align: left">v2 起 <code class="language-plaintext highlighter-rouge">golangci-lint fmt</code> 统一 gofmt / goimports / gofumpt / gci 等</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>生态集成</strong></td>
      <td style="text-align: left">GitHub Actions、GitLab CI、GoLand、VS Code、pre-commit 等均有成熟用法</td>
    </tr>
  </tbody>
</table>

<p class="prompt-tip">与「只跑 <code class="language-plaintext highlighter-rouge">staticcheck ./...</code>」相比，golangci-lint <strong>不是</strong>在算法上比 Staticcheck 更聪明，而是 <strong>编排层</strong>：把 Staticcheck、govet、errcheck 等打包、并行、可配置。许多团队仍把 Staticcheck 当作 correctness 金标准，再用 golangci-lint 管「团队规范」——二者互补，而非二选一。</p>

<h1 id="安装-1">安装</h1>

<p>官方文档把安装方式分为 <a href="https://golangci-lint.run/docs/welcome/install/local/">本地（Local Installation）</a> 与 <a href="https://golangci-lint.run/docs/welcome/install/ci/">CI（CI Installation）</a> 两类。核心原则一致：<strong>使用 <a href="https://github.com/golangci/golangci-lint/releases">Releases 页</a> 上的固定版本预编译二进制</strong>，本地与 CI 保持同一版本号，避免 <code class="language-plaintext highlighter-rouge">@latest</code> 或未 pin 的包管理器在某一时刻集体升级导致全仓库 CI 同时变红。</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">场景</th>
      <th style="text-align: left">官方推荐</th>
      <th style="text-align: left">说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>本地开发</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">install.sh</code> 安装二进制</td>
      <td style="text-align: left">首选；与 CI 可完全一致</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>版本管理工具</strong></td>
      <td style="text-align: left"><a href="https://mise.jdx.dev/">mise</a></td>
      <td style="text-align: left">通过 aqua 拉 GitHub Release 资产，仍是官方二进制</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>容器 / 无 root 环境</strong></td>
      <td style="text-align: left">Docker 镜像 <code class="language-plaintext highlighter-rouge">golangci/golangci-lint:vX.Y.Z</code></td>
      <td style="text-align: left">tag 必须与 Release 版本一致</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>GitHub 项目 CI</strong></td>
      <td style="text-align: left"><a href="https://github.com/golangci/golangci-lint-action">golangci-lint-action</a></td>
      <td style="text-align: left">内置缓存与 PR annotation，通常比裸装二进制更快</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>其他 CI</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">install.sh</code> 或 Docker</td>
      <td style="text-align: left">同样 pin 版本</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>不推荐</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">go install</code> / <code class="language-plaintext highlighter-rouge">go get</code> / <code class="language-plaintext highlighter-rouge">go tool</code></td>
      <td style="text-align: left">本地编译、依赖不可控，官方明确不保证可用</td>
    </tr>
  </tbody>
</table>

<p class="prompt-tip">下文版本号以 <strong>v2.12.2</strong> 为例；升级时把命令中的版本字符串一并替换即可。</p>

<h2 id="本地首选installsh-二进制">本地首选：<code class="language-plaintext highlighter-rouge">install.sh</code> 二进制</h2>

<p>官方维护的安装脚本从 GitHub Releases 下载 <strong>与平台匹配的预编译包</strong>，是经过项目测试的发行方式：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 安装到 $(go env GOPATH)/bin（确保该目录在 PATH 中）</span>
curl <span class="nt">-sSfL</span> https://golangci-lint.run/install.sh | sh <span class="nt">-s</span> <span class="nt">--</span> <span class="nt">-b</span> <span class="si">$(</span>go <span class="nb">env </span>GOPATH<span class="si">)</span>/bin v2.12.2

<span class="c"># 或安装到当前项目的 ./bin/（适合仓库内 tools/ 脚本统一调用）</span>
curl <span class="nt">-sSfL</span> https://golangci-lint.run/install.sh | sh <span class="nt">-s</span> v2.12.2

golangci-lint <span class="nt">--version</span>
</code></pre></div></div>

<p><strong>Alpine Linux</strong> 默认不带 <code class="language-plaintext highlighter-rouge">curl</code>，可用 <code class="language-plaintext highlighter-rouge">wget</code>：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget <span class="nt">-O-</span> <span class="nt">-nv</span> https://golangci-lint.run/install.sh | sh <span class="nt">-s</span> v2.12.2
</code></pre></div></div>

<p><strong>Windows</strong> 可在 Git for Windows 自带的 Git Bash 中执行上述 <code class="language-plaintext highlighter-rouge">curl</code> 命令。</p>

<p>验证安装成功后，<code class="language-plaintext highlighter-rouge">golangci-lint version</code> 应显示 <code class="language-plaintext highlighter-rouge">v2.12.2</code> 及构建所用的 Go 版本——该 Go 版本也决定了能分析的上游语法上限（见下文 FAQ）。</p>

<h2 id="mise多工具版本管理">mise（多工具版本管理）</h2>

<p>若已使用 <a href="https://mise.jdx.dev/">mise</a> 管理开发工具，官方文档指出 mise 通过 <strong>aqua 后端</strong> 安装，二进制同样来自 <strong>GitHub Release 资产</strong>（属于推荐的二进制路径，而非本地编译）：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mise use <span class="nt">-g</span> golangci-lint@v2.12.2
</code></pre></div></div>

<p>mise 集成本身 <strong>不由 golangci 团队维护</strong>；团队规范仍建议 pin 具体版本并在文档中写清。</p>

<h2 id="docker">Docker</h2>

<p>适合 CI agent、一次性检查或不想污染本机 PATH 的场景。镜像 tag <strong>必须</strong> 与 Release 版本一致：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">--rm</span> <span class="nt">-v</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span><span class="s2">"</span>:/app <span class="nt">-w</span> /app golangci/golangci-lint:v2.12.2 golangci-lint run
</code></pre></div></div>

<p>需要彩色输出时加 <code class="language-plaintext highlighter-rouge">-t</code>：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">-t</span> <span class="nt">--rm</span> <span class="nt">-v</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span><span class="s2">"</span>:/app <span class="nt">-w</span> /app golangci/golangci-lint:v2.12.2 golangci-lint run
</code></pre></div></div>

<p><strong>连续运行保留缓存</strong>（显著缩短二次分析时间）时，官方示例挂载 Go 与 golangci-lint 缓存目录：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">--rm</span> <span class="nt">-t</span> <span class="nt">-v</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span><span class="s2">"</span>:/app <span class="nt">-w</span> /app <span class="se">\</span>
  <span class="nt">--user</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">id</span> <span class="nt">-u</span><span class="si">)</span><span class="s2">:</span><span class="si">$(</span><span class="nb">id</span> <span class="nt">-g</span><span class="si">)</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nt">-v</span> <span class="s2">"</span><span class="si">$(</span>go <span class="nb">env </span>GOCACHE<span class="si">)</span><span class="s2">"</span>:/.cache/go-build <span class="nt">-e</span> <span class="nv">GOCACHE</span><span class="o">=</span>/.cache/go-build <span class="se">\</span>
  <span class="nt">-v</span> <span class="s2">"</span><span class="si">$(</span>go <span class="nb">env </span>GOMODCACHE<span class="si">)</span><span class="s2">"</span>:/.cache/mod <span class="nt">-e</span> <span class="nv">GOMODCACHE</span><span class="o">=</span>/.cache/mod <span class="se">\</span>
  <span class="nt">-v</span> ~/.cache/golangci-lint:/.cache/golangci-lint <span class="nt">-e</span> <span class="nv">GOLANGCI_LINT_CACHE</span><span class="o">=</span>/.cache/golangci-lint <span class="se">\</span>
  golangci/golangci-lint:v2.12.2 golangci-lint run
</code></pre></div></div>

<h2 id="包管理器便利但需注意版本">包管理器（便利但需注意版本）</h2>

<h3 id="macos--homebrew">macOS — Homebrew</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew <span class="nb">install </span>golangci-lint
brew upgrade golangci-lint
</code></pre></div></div>

<p>官方说明：Homebrew <strong>可能用与你预期不同的 Go 版本编译</strong> 该 formula，因此更推荐 <code class="language-plaintext highlighter-rouge">install.sh</code> 二进制，或至少确认 brew 安装的版本与 CI 一致。历史上项目曾提供 Homebrew tap，现已 <strong>推荐官方 formula</strong>，勿再使用旧 tap。</p>

<h3 id="linux--发行版包">Linux — 发行版包</h3>

<p>多数 Linux 发行版软件源提供 golangci-lint（见 <a href="https://repology.org/project/golangci-lint/versions">Repology 打包状态</a>）。便利性与 <strong>版本滞后</strong> 需自行权衡；生产 CI 仍建议 <code class="language-plaintext highlighter-rouge">install.sh</code> pin 版本。</p>

<h3 id="windows--chocolatey--scoop">Windows — Chocolatey / Scoop</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>choco <span class="nb">install </span>golangci-lint
<span class="c"># 或</span>
scoop <span class="nb">install </span>main/golangci-lint
</code></pre></div></div>

<p>Scoop 包 <strong>非 golangci 团队官方维护</strong>；Windows 上若要求可复现，仍优先 Git Bash + <code class="language-plaintext highlighter-rouge">install.sh</code>。</p>

<h3 id="macports">MacPorts</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>port <span class="nb">install </span>golangci-lint
</code></pre></div></div>

<p>社区维护，非官方支持。</p>

<h2 id="ci-环境官方推荐路径">CI 环境：官方推荐路径</h2>

<p><a href="https://golangci-lint.run/docs/welcome/install/ci/">CI Installation</a> 强调：<strong>可复现的 CI 必须安装指定 Release 版本</strong>。使用 <code class="language-plaintext highlighter-rouge">linters.default: all</code> 或未 pin 工具版本时，上游新增 linter 或升级依赖可能导致 <strong>所有 pipeline 同一时刻失败</strong>。</p>

<h3 id="如何应对版本与-linter-集双-pin">如何应对：版本与 linter 集「双 pin」</h3>

<p>问题根因是两层都在<strong>漂移</strong>：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">漂移来源</th>
      <th style="text-align: left">典型症状</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>未 pin golangci-lint 版本</strong></td>
      <td style="text-align: left">某次 CI 自动拉到新 Release，内置 linter 版本集体变化</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">linters.default: all</code></strong></td>
      <td style="text-align: left">golangci-lint 小版本新增 linter 即默认启用，旧代码 overnight 报上千条</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">go install @latest</code> / brew 无版本</strong></td>
      <td style="text-align: left">开发者本地与 CI 规则集不一致，MR 反复来回</td>
    </tr>
  </tbody>
</table>

<p>推荐做法（按优先级）：</p>

<p><strong>1. Pin 工具版本（必做）</strong></p>

<p>本地与 CI 使用<strong>同一 Release 号</strong>，写入仓库可检索的位置（<code class="language-plaintext highlighter-rouge">.github/workflows/</code>、<code class="language-plaintext highlighter-rouge">Makefile</code>、<code class="language-plaintext highlighter-rouge">mise.toml</code>、文档）：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 本地</span>
curl <span class="nt">-sSfL</span> https://golangci-lint.run/install.sh | sh <span class="nt">-s</span> <span class="nt">--</span> <span class="nt">-b</span> <span class="si">$(</span>go <span class="nb">env </span>GOPATH<span class="si">)</span>/bin v2.12.2
golangci-lint version   <span class="c"># 确认与 CI 一致</span>
</code></pre></div></div>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># GitHub Actions — version 必填，勿省略</span>
<span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">golangci/golangci-lint-action@v8</span>
  <span class="na">with</span><span class="pi">:</span>
    <span class="na">version</span><span class="pi">:</span> <span class="s">v2.12.2</span>
</code></pre></div></div>

<p>升级 golangci-lint 应走<strong>独立 MR</strong>： bump 版本 → 读 <a href="https://github.com/golangci/golangci-lint/blob/main/CHANGELOG.md">Changelog</a> → 本地全量跑 → 修完再合并。不要用 Dependabot 对 golangci-lint-action <strong>自动 merge</strong> 到新 minor。</p>

<p><strong>2. 不用 <code class="language-plaintext highlighter-rouge">default: all</code>，改用显式白名单（必做）</strong></p>

<p><code class="language-plaintext highlighter-rouge">all</code> 会把当前 Release 里<strong>全部</strong> linter 一次打开；新 Release 若注册了新 linter，你的 <code class="language-plaintext highlighter-rouge">.golangci.yml</code> 即使没改也会多出一批规则。</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 推荐：standard 基线 + 按需 enable</span>
<span class="na">linters</span><span class="pi">:</span>
  <span class="na">default</span><span class="pi">:</span> <span class="s">standard</span>
  <span class="na">enable</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">gosec</span>
    <span class="pi">-</span> <span class="s">revive</span>
    <span class="pi">-</span> <span class="s">errorlint</span>

<span class="c1"># 或严格白名单（大仓库 / 绿色field）</span>
<span class="na">linters</span><span class="pi">:</span>
  <span class="na">default</span><span class="pi">:</span> <span class="s">none</span>
  <span class="na">enable</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">errcheck</span>
    <span class="pi">-</span> <span class="s">govet</span>
    <span class="pi">-</span> <span class="s">staticcheck</span>
    <span class="pi">-</span> <span class="s">unused</span>
    <span class="c1"># … 团队逐项审批加入</span>

<span class="c1"># 避免：CI 与生产门禁</span>
<span class="na">linters</span><span class="pi">:</span>
  <span class="na">default</span><span class="pi">:</span> <span class="s">all</span>   <span class="c1"># 不推荐</span>
</code></pre></div></div>

<p>团队新增 linter 的流程：<strong>先在 MR 里 <code class="language-plaintext highlighter-rouge">enable</code> 一项 → 修存量或配合 <code class="language-plaintext highlighter-rouge">new-from-merge-base</code> → 再收紧</strong>。</p>

<p><strong>3. 存量仓库：CI 只卡「新引入」问题（强烈建议）</strong></p>

<p>历史债务与工具升级解耦，避免「升版本 = 全仓库同时变红」：</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">issues</span><span class="pi">:</span>
  <span class="na">new-from-merge-base</span><span class="pi">:</span> <span class="s">main</span>
</code></pre></div></div>

<p>或 PR 流水线：<code class="language-plaintext highlighter-rouge">golangci-lint run --new-from-merge-base=origin/main</code>。详见下文 <a href="#示例-3存量大仓库只卡新代码">示例 3：存量大仓库「只卡新代码」</a>。</p>

<p><strong>4. 分层执行：本地快、CI 全、升级单独验</strong></p>

<pre><code class="language-mermaid">flowchart TD
    A[日常开发] --&gt;|--fast-only / standard| B[秒级反馈]
    C[PR / pre-push] --&gt;|全量 .golangci.yml| D[与 CI 同配置]
    E[升级 golangci-lint MR] --&gt;|pin 新版本 + 全量 run| F[一次性修规则差异]
    F --&gt; G[合并后 CI 稳定]
</code></pre>

<p><strong>5. 静态检查依赖也 pin</strong></p>

<p>若 CI 除 golangci-lint 外还单独跑 Staticcheck、errcheck 等，同样禁止 <code class="language-plaintext highlighter-rouge">@latest</code>：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>go <span class="nb">install </span>honnef.co/go/tools/cmd/staticcheck@2025.1.1   <span class="c"># 写死版本</span>
</code></pre></div></div>

<p><strong>6. 配置即契约</strong></p>

<p><code class="language-plaintext highlighter-rouge">.golangci.yml</code> 提交进 Git，作为团队唯一真相；禁止在 CI workflow 里用 <code class="language-plaintext highlighter-rouge">args: --enable-all</code> 覆盖仓库配置。</p>

<blockquote class="prompt-tip">
  <p><strong>一句话</strong>：<strong>工具版本 pin 到 Release 号，linter 集 pin 到 explicit <code class="language-plaintext highlighter-rouge">enable</code> 列表</strong>；存量用 <code class="language-plaintext highlighter-rouge">new-from-merge-base</code> 缓冲，升级 golangci-lint 单独开 MR。勿用 <code class="language-plaintext highlighter-rouge">default: all</code> + <code class="language-plaintext highlighter-rouge">@latest</code> 组合跑生产门禁。</p>
</blockquote>

<h3 id="github-actionsgithub-项目首选">GitHub Actions（GitHub 项目首选）</h3>

<p>官方 <strong>推荐</strong> <a href="https://github.com/golangci/golangci-lint-action">golangci/golangci-lint-action</a>：内置智能缓存，通常比逐步 <code class="language-plaintext highlighter-rouge">curl install.sh</code> 更快，且会把 issue 写成 <strong>GitHub Annotation</strong>，无需在日志里全文搜索。</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>
<span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/setup-go@v5</span>
  <span class="na">with</span><span class="pi">:</span>
    <span class="na">go-version</span><span class="pi">:</span> <span class="s">stable</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">golangci-lint</span>
  <span class="na">uses</span><span class="pi">:</span> <span class="s">golangci/golangci-lint-action@v8</span>
  <span class="na">with</span><span class="pi">:</span>
    <span class="na">version</span><span class="pi">:</span> <span class="s">v2.12.2</span>    <span class="c1"># 必填：pin Release 版本</span>
    <span class="na">args</span><span class="pi">:</span> <span class="s">--timeout=5m</span>
</code></pre></div></div>

<h3 id="gitlab--buildkite--其他-ci">GitLab / Buildkite / 其他 CI</h3>

<ul>
  <li><strong>GitLab</strong>：可使用 <a href="https://docs.gitlab.com/ee/ci/testing/code_quality.html">Code Quality 官方组件</a>（<code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> 中 <code class="language-plaintext highlighter-rouge">include</code> golangci 组件）。</li>
  <li><strong>Buildkite</strong>：<a href="https://github.com/buildkite-plugins/golangci-lint-buildkite-plugin">golangci-lint 插件</a>，默认 Docker 镜像，也可改用 agent 上已 pin 的二进制。</li>
  <li><strong>通用 Jenkins / 自建 runner</strong>：与本地相同，在 job 开头执行 <code class="language-plaintext highlighter-rouge">install.sh</code> 指定版本，或使用上文 Docker 命令；<strong>不要</strong>在 CI 里写 <code class="language-plaintext highlighter-rouge">@latest</code> 或不带版本的 <code class="language-plaintext highlighter-rouge">brew install</code>。</li>
</ul>

<h2 id="不推荐从源码--go-install--go-tool">不推荐：从源码 / <code class="language-plaintext highlighter-rouge">go install</code> / <code class="language-plaintext highlighter-rouge">go tool</code></h2>

<p>官方在 <a href="https://golangci-lint.run/docs/welcome/install/local/">Local Installation — Install from Sources</a> 用 <strong>Warning</strong> 明确说明：<code class="language-plaintext highlighter-rouge">go install</code>、<code class="language-plaintext highlighter-rouge">go get</code>、tools pattern、<code class="language-plaintext highlighter-rouge">go tool</code> <strong>不保证可用</strong>，<strong>推荐二进制安装</strong>。主要原因包括：</p>

<ol>
  <li><strong>本地编译</strong>：构建所用 Go 版本取决于你的环境，与官方 Release 测试矩阵不一致；</li>
  <li><strong><code class="language-plaintext highlighter-rouge">go get -u</code></strong> 会升级 golangci-lint 的依赖，产物未经发布测试；</li>
  <li><strong>tools / <code class="language-plaintext highlighter-rouge">go tool</code></strong> 可能改动项目或其他工具的 module 依赖图；</li>
  <li><strong>module hash / replace</strong> 等边界问题可能导致不可复现构建；</li>
  <li>可能误装 <strong>main 分支</strong>，不稳定；</li>
  <li>比下载预编译包 <strong>更慢</strong>。</li>
</ol>

<p>若团队规范强制「Go 工具链统一管理」，官方在不得不用 <code class="language-plaintext highlighter-rouge">go tool</code> 时的 <strong>次优</strong> 做法是：用 <strong>独立 mod 文件</strong> 隔离 golangci-lint，避免污染业务 <code class="language-plaintext highlighter-rouge">go.mod</code>——但仍不如二进制推荐：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 独立 mod 文件（示例）</span>
go mod init <span class="nt">-modfile</span><span class="o">=</span>golangci-lint.mod github.com/org/repo/golangci-lint
go get <span class="nt">-tool</span> <span class="nt">-modfile</span><span class="o">=</span>golangci-lint.mod github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2
go tool <span class="nt">-modfile</span><span class="o">=</span>golangci-lint.mod golangci-lint run
</code></pre></div></div>

<p><strong>切勿</strong>手动 <code class="language-plaintext highlighter-rouge">go get -u</code> 升级 golangci-lint 的传递依赖。</p>

<p>仅作了解、不作为团队默认方式的 one-liner：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>go <span class="nb">install </span>github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2
</code></pre></div></div>

<h2 id="安装方式对照小结">安装方式对照小结</h2>

<pre><code class="language-mermaid">flowchart TD
    START[需要安装 golangci-lint] --&gt; WHERE{运行环境?}
    WHERE --&gt;|本地 macOS/Linux| BIN["curl install.sh + pin v2.12.2"]
    WHERE --&gt;|本地 多版本管理| MISE[mise use golangci-lint@v2.12.2]
    WHERE --&gt;|CI GitHub| ACTION[golangci-lint-action + version]
    WHERE --&gt;|CI 其他| CI_BIN[install.sh 或 Docker pin tag]
    WHERE --&gt;|快速试用| DOCKER[Docker 一次性 run]
    BIN --&gt; VERIFY[golangci-lint version]
    MISE --&gt; VERIFY
    ACTION --&gt; VERIFY
    CI_BIN --&gt; VERIFY
    DOCKER --&gt; VERIFY
    AVOID[避免: go install / @latest / 未 pin 的包管理器] -.-&gt;|官方不推荐| START
</code></pre>

<table>
  <thead>
    <tr>
      <th style="text-align: left">方式</th>
      <th style="text-align: center">可复现</th>
      <th style="text-align: left">官方态度</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">install.sh</code> + Release 版本</td>
      <td style="text-align: center">高</td>
      <td style="text-align: left"><strong>首选</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">mise @vX.Y.Z（aqua / GitHub assets）</td>
      <td style="text-align: center">高</td>
      <td style="text-align: left">推荐（二进制来源）</td>
    </tr>
    <tr>
      <td style="text-align: left">Docker <code class="language-plaintext highlighter-rouge">golangci/golangci-lint:vX.Y.Z</code></td>
      <td style="text-align: center">高</td>
      <td style="text-align: left">推荐</td>
    </tr>
    <tr>
      <td style="text-align: left">GitHub Actions + <code class="language-plaintext highlighter-rouge">version:</code></td>
      <td style="text-align: center">高</td>
      <td style="text-align: left"><strong>CI 首选（GitHub）</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">Homebrew / 发行版包</td>
      <td style="text-align: center">中（版本可能滞后）</td>
      <td style="text-align: left">可用，注意与 CI 对齐</td>
    </tr>
    <tr>
      <td style="text-align: left">Chocolatey / Scoop / MacPorts</td>
      <td style="text-align: center">中</td>
      <td style="text-align: left">非官方维护或社区维护</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">go install</code> / <code class="language-plaintext highlighter-rouge">go tool</code></td>
      <td style="text-align: center">低</td>
      <td style="text-align: left"><strong>不推荐</strong></td>
    </tr>
  </tbody>
</table>

<h1 id="快速开始">快速开始</h1>

<h2 id="lint">Lint</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 等价于 golangci-lint run ./...</span>
golangci-lint run

<span class="c"># 指定目录（目录默认不递归，需加 /...）</span>
golangci-lint run ./cmd/... ./internal/...

<span class="c"># 单文件（须与同 package 其他文件一起能编译）</span>
golangci-lint run path/to/file.go
</code></pre></div></div>

<p><strong>零配置</strong> 时默认启用（v2 <code class="language-plaintext highlighter-rouge">standard</code> 集）：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Linter</th>
      <th style="text-align: left">作用</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">errcheck</code></td>
      <td style="text-align: left">未检查的错误返回值</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">govet</code></td>
      <td style="text-align: left">近似 <code class="language-plaintext highlighter-rouge">go vet</code> 的可疑写法</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ineffassign</code></td>
      <td style="text-align: left">无效赋值</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">staticcheck</code></td>
      <td style="text-align: left">Staticcheck 规则集（含原 gosimple、stylecheck）</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">unused</code></td>
      <td style="text-align: left">未使用的常量、变量、函数、类型</td>
    </tr>
  </tbody>
</table>

<p>命令行临时开关：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 仅启用 errcheck，关闭默认集</span>
golangci-lint run <span class="nt">--default</span><span class="o">=</span>none <span class="nt">-E</span> errcheck

<span class="c"># 在默认集上追加 gosec</span>
golangci-lint run <span class="nt">-E</span> gosec

<span class="c"># 编辑器/本地快速反馈：只跑标记为 fast 的 linter</span>
golangci-lint run <span class="nt">--fast-only</span>
</code></pre></div></div>

<h2 id="format">Format</h2>

<p>v2 将格式化从 linter 列表拆到 <code class="language-plaintext highlighter-rouge">formatters</code> 段，CLI 为：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>golangci-lint <span class="nb">fmt</span>              <span class="c"># 格式化当前模块</span>
golangci-lint <span class="nb">fmt</span> ./pkg/...    <span class="c"># 指定路径</span>
golangci-lint <span class="nb">fmt</span> <span class="nt">--stdin</span>      <span class="c"># 供编辑器管道输入</span>
</code></pre></div></div>

<p>更多见 <a href="https://golangci-lint.run/docs/welcome/quick-start/">Quick Start</a>。</p>

<h1 id="配置文件">配置文件</h1>

<p>golangci-lint 按以下顺序查找配置（从当前目录向上直到根目录，再到用户主目录）：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">.golangci.yml</code> / <code class="language-plaintext highlighter-rouge">.golangci.yaml</code> / <code class="language-plaintext highlighter-rouge">.golangci.toml</code> / <code class="language-plaintext highlighter-rouge">.golangci.json</code></li>
</ul>

<p>不确定实际加载了哪一份时，加 <code class="language-plaintext highlighter-rouge">-v</code> 查看。完整字段见 <a href="https://github.com/golangci/golangci-lint/blob/main/.golangci.reference.yml">.golangci.reference.yml</a> 与 <a href="https://golangci-lint.run/docs/configuration/file/">Configuration File</a>。</p>

<h2 id="v2-顶层结构">v2 顶层结构</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2"</span>          <span class="c1"># v2 必填，唯一合法值</span>

<span class="na">linters</span><span class="pi">:</span>
  <span class="na">default</span><span class="pi">:</span> <span class="s">standard</span>   <span class="c1"># standard | all | none | fast</span>
  <span class="na">enable</span><span class="pi">:</span> <span class="pi">[]</span>
  <span class="na">disable</span><span class="pi">:</span> <span class="pi">[]</span>
  <span class="na">settings</span><span class="pi">:</span> <span class="pi">{}</span>
  <span class="na">exclusions</span><span class="pi">:</span> <span class="pi">{}</span>

<span class="na">formatters</span><span class="pi">:</span>
  <span class="na">enable</span><span class="pi">:</span> <span class="pi">[]</span>
  <span class="na">settings</span><span class="pi">:</span> <span class="pi">{}</span>
  <span class="na">exclusions</span><span class="pi">:</span> <span class="pi">{}</span>

<span class="na">issues</span><span class="pi">:</span> <span class="pi">{}</span>
<span class="na">output</span><span class="pi">:</span> <span class="pi">{}</span>
<span class="na">run</span><span class="pi">:</span> <span class="pi">{}</span>
<span class="na">severity</span><span class="pi">:</span> <span class="pi">{}</span>
</code></pre></div></div>

<p>配置文件中的选项与命令行等价；<strong>各 linter 的细粒度参数只能写在配置文件里</strong>，不能全部通过 CLI 传递。</p>

<h2 id="示例-1团队基线推荐起点">示例 1：团队基线（推荐起点）</h2>

<p>在默认 <code class="language-plaintext highlighter-rouge">standard</code> 上追加安全、错误处理与 revive 风格检查，并对测试文件放宽部分规则：</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2"</span>

<span class="na">run</span><span class="pi">:</span>
  <span class="na">timeout</span><span class="pi">:</span> <span class="s">5m</span>
  <span class="na">tests</span><span class="pi">:</span> <span class="kc">true</span>
  <span class="na">modules-download-mode</span><span class="pi">:</span> <span class="s">readonly</span>

<span class="na">linters</span><span class="pi">:</span>
  <span class="na">default</span><span class="pi">:</span> <span class="s">standard</span>
  <span class="na">enable</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">bodyclose</span>
    <span class="pi">-</span> <span class="s">errorlint</span>
    <span class="pi">-</span> <span class="s">gosec</span>
    <span class="pi">-</span> <span class="s">misspell</span>
    <span class="pi">-</span> <span class="s">revive</span>
    <span class="pi">-</span> <span class="s">sloglint</span>
  <span class="na">disable</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">unused</span>          <span class="c1"># 若与 IDE 实时分析重复，可按需关闭</span>
  <span class="na">settings</span><span class="pi">:</span>
    <span class="na">revive</span><span class="pi">:</span>
      <span class="na">rules</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">exported</span>
          <span class="na">disabled</span><span class="pi">:</span> <span class="kc">false</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">package-comments</span>
          <span class="na">severity</span><span class="pi">:</span> <span class="s">warning</span>
    <span class="na">gosec</span><span class="pi">:</span>
      <span class="na">excludes</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">G104</span>          <span class="c1"># 与 errcheck 重叠时可排除</span>
  <span class="na">exclusions</span><span class="pi">:</span>
    <span class="na">presets</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">std-error-handling</span>
      <span class="pi">-</span> <span class="s">common-false-positives</span>
    <span class="na">rules</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s">_test\.go</span>
        <span class="na">linters</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="s">gosec</span>
          <span class="pi">-</span> <span class="s">errcheck</span>
          <span class="pi">-</span> <span class="s">funlen</span>

<span class="na">issues</span><span class="pi">:</span>
  <span class="na">max-issues-per-linter</span><span class="pi">:</span> <span class="m">0</span>
  <span class="na">max-same-issues</span><span class="pi">:</span> <span class="m">0</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">max-issues-per-linter: 0</code> 表示不截断报告条数（默认每条 linter 最多 50 条），适合 CI 一次看全量问题。</p>

<h2 id="示例-2严格模式新项目--绿色field">示例 2：严格模式（新项目 / 绿色field）</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2"</span>

<span class="na">linters</span><span class="pi">:</span>
  <span class="na">default</span><span class="pi">:</span> <span class="s">none</span>
  <span class="na">enable</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">errcheck</span>
    <span class="pi">-</span> <span class="s">govet</span>
    <span class="pi">-</span> <span class="s">staticcheck</span>
    <span class="pi">-</span> <span class="s">unused</span>
    <span class="pi">-</span> <span class="s">ineffassign</span>
    <span class="pi">-</span> <span class="s">bodyclose</span>
    <span class="pi">-</span> <span class="s">errorlint</span>
    <span class="pi">-</span> <span class="s">gocritic</span>
    <span class="pi">-</span> <span class="s">gosec</span>
    <span class="pi">-</span> <span class="s">misspell</span>
    <span class="pi">-</span> <span class="s">revive</span>
    <span class="pi">-</span> <span class="s">unparam</span>
    <span class="pi">-</span> <span class="s">copyloopvar</span>
    <span class="pi">-</span> <span class="s">durationcheck</span>
    <span class="pi">-</span> <span class="s">noctx</span>
    <span class="pi">-</span> <span class="s">rowserrcheck</span>
    <span class="pi">-</span> <span class="s">sqlclosecheck</span>
  <span class="na">settings</span><span class="pi">:</span>
    <span class="na">gocritic</span><span class="pi">:</span>
      <span class="na">enabled-tags</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">diagnostic</span>
        <span class="pi">-</span> <span class="s">style</span>
      <span class="na">disabled-checks</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">hugeParam</span>     <span class="c1"># 按团队口味调整</span>
    <span class="na">funlen</span><span class="pi">:</span>
      <span class="na">lines</span><span class="pi">:</span> <span class="m">120</span>
      <span class="na">statements</span><span class="pi">:</span> <span class="m">80</span>

<span class="na">issues</span><span class="pi">:</span>
  <span class="na">fix</span><span class="pi">:</span> <span class="kc">true</span>            <span class="c1"># 支持 auto-fix 的 linter 自动修复</span>

<span class="na">formatters</span><span class="pi">:</span>
  <span class="na">enable</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">gofumpt</span>
    <span class="pi">-</span> <span class="s">goimports</span>
</code></pre></div></div>

<p>本地可 <code class="language-plaintext highlighter-rouge">golangci-lint run --fix</code> 或配置 <code class="language-plaintext highlighter-rouge">issues.fix: true</code> 应用可自动修复项；提交前仍应人工 review diff。</p>

<h2 id="示例-3存量大仓库只卡新代码">示例 3：存量大仓库「只卡新代码」</h2>

<p>官方 FAQ 推荐：<strong>不要一次性修完历史 issue</strong>，CI 只失败于新引入的问题。</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2"</span>

<span class="na">linters</span><span class="pi">:</span>
  <span class="na">default</span><span class="pi">:</span> <span class="s">standard</span>
  <span class="na">enable</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">gosec</span>
    <span class="pi">-</span> <span class="s">revive</span>

<span class="na">issues</span><span class="pi">:</span>
  <span class="na">new-from-merge-base</span><span class="pi">:</span> <span class="s">main</span>
  <span class="na">whole-files</span><span class="pi">:</span> <span class="kc">true</span>    <span class="c1"># 变更文件内、非 diff 行上的 issue 也报告（更严）</span>
</code></pre></div></div>

<p>等价命令行：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>golangci-lint run <span class="nt">--new-from-merge-base</span><span class="o">=</span>main
<span class="c"># 或对比上一 commit</span>
golangci-lint run <span class="nt">--new-from-rev</span><span class="o">=</span>HEAD~1
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">--new</code> 在有 unstaged 变更时只分析工作区改动，适合 pre-commit；CI 更稳妥的是 <code class="language-plaintext highlighter-rouge">--new-from-merge-base=main</code> 或 <code class="language-plaintext highlighter-rouge">--new-from-rev=HEAD~1</code>。</p>

<h2 id="示例-4模块边界与依赖治理">示例 4：模块边界与依赖治理</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2"</span>

<span class="na">linters</span><span class="pi">:</span>
  <span class="na">enable</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">depguard</span>
    <span class="pi">-</span> <span class="s">gomoddirectives</span>
    <span class="pi">-</span> <span class="s">gomodguard</span>
  <span class="na">settings</span><span class="pi">:</span>
    <span class="na">depguard</span><span class="pi">:</span>
      <span class="na">rules</span><span class="pi">:</span>
        <span class="na">main</span><span class="pi">:</span>
          <span class="na">deny</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="na">pkg</span><span class="pi">:</span> <span class="s">github.com/sirupsen/logrus</span>
              <span class="na">desc</span><span class="pi">:</span> <span class="s2">"</span><span class="s">use</span><span class="nv"> </span><span class="s">log/slog</span><span class="nv"> </span><span class="s">or</span><span class="nv"> </span><span class="s">internal</span><span class="nv"> </span><span class="s">logger"</span>
            <span class="pi">-</span> <span class="na">pkg</span><span class="pi">:</span> <span class="s">github.com/pkg/errors</span>
              <span class="na">desc</span><span class="pi">:</span> <span class="s2">"</span><span class="s">use</span><span class="nv"> </span><span class="s">fmt.Errorf</span><span class="nv"> </span><span class="s">with</span><span class="nv"> </span><span class="s">%w"</span>
          <span class="na">files</span><span class="pi">:</span>
            <span class="pi">-</span> <span class="s2">"</span><span class="s">!$test"</span>
</code></pre></div></div>

<p>结合站内 <a href="/posts/go-log-and-slog/">Go log 与 slog</a> 一文，可在 <code class="language-plaintext highlighter-rouge">depguard</code> 里禁止旧式日志库，推动结构化日志统一。</p>

<h2 id="示例-5ci-输出-sarifgithub-code-scanning">示例 5：CI 输出 SARIF（GitHub Code Scanning）</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2"</span>

<span class="na">output</span><span class="pi">:</span>
  <span class="na">formats</span><span class="pi">:</span>
    <span class="na">sarif</span><span class="pi">:</span>
      <span class="na">path</span><span class="pi">:</span> <span class="s">golangci-lint.sarif</span>
  <span class="na">path-mode</span><span class="pi">:</span> <span class="s">abs</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>golangci-lint run
<span class="c"># 将 golangci-lint.sarif 上传至 GitHub Security → Code scanning</span>
</code></pre></div></div>

<h1 id="行内忽略nolint">行内忽略：nolint</h1>

<p>与 Staticcheck 类似，可在行或块上标注：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">//nolint:errcheck // legacy API, error ignored by design</span>
<span class="k">func</span> <span class="n">legacy</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">_</span> <span class="o">=</span> <span class="n">doSomething</span><span class="p">()</span>
<span class="p">}</span>

<span class="c">//nolint:gosec // G404: weak random acceptable for non-crypto ID</span>
<span class="n">id</span> <span class="o">:=</span> <span class="n">rand</span><span class="o">.</span><span class="n">Int</span><span class="p">()</span>
</code></pre></div></div>

<p>建议：</p>

<ul>
  <li>写清 <strong>原因</strong>（<code class="language-plaintext highlighter-rouge">// reason</code> 或独立注释），便于 review；</li>
  <li>启用 <code class="language-plaintext highlighter-rouge">nolintlint</code> linter，防止滥用或过期 directive；</li>
  <li><strong>无法</strong> 用 <code class="language-plaintext highlighter-rouge">nolint</code> 跳过 <code class="language-plaintext highlighter-rouge">typecheck</code>（见下文 FAQ）。</li>
</ul>

<h1 id="最佳实践">最佳实践</h1>

<h2 id="1-pin-版本避免-ci-突然全红">1. Pin 版本，避免 CI 突然全红</h2>

<p>完整操作步骤见上文 <a href="#如何应对版本与-linter-集双-pin">如何应对：版本与 linter 集「双 pin」</a>。此处强调配置侧要点：</p>

<ul>
  <li>本地与 CI 使用 <strong>相同版本号</strong>（如 <code class="language-plaintext highlighter-rouge">v2.12.2</code>）；</li>
  <li>配置里用 <code class="language-plaintext highlighter-rouge">standard</code> 或显式 <code class="language-plaintext highlighter-rouge">enable</code> 列表，<strong>禁用 <code class="language-plaintext highlighter-rouge">default: all</code> 作为合并门禁</strong>；</li>
  <li>升级 golangci-lint 时在 MR 中单独 bump，并阅读 <a href="https://github.com/golangci/golangci-lint/blob/main/CHANGELOG.md">Changelog</a>；</li>
  <li>存量仓库配合 <code class="language-plaintext highlighter-rouge">issues.new-from-merge-base</code>，避免工具升级与历史债务在同一 MR 爆炸。</li>
</ul>

<h2 id="2-分层本地快ci-全">2. 分层：本地快、CI 全</h2>

<pre><code class="language-mermaid">flowchart LR
    DEV[本地编辑] --&gt;|golangci-lint run --fast-only| FAST[errcheck gov vet staticcheck ...]
    PRE[pre-commit / push] --&gt;|golangci-lint run 全量| FULL[含 gosec revive gocritic]
    CI[CI pipeline] --&gt;|pin 版本 + 可选 new-from-merge-base| GATE[合并门禁]
</code></pre>

<p>VS Code / GoLand 集成时官方建议加 <code class="language-plaintext highlighter-rouge">--fast-only</code>，否则全量分析可能 <strong>卡住编辑器</strong>。见 <a href="https://golangci-lint.run/docs/welcome/integrations/">Integrations</a>。</p>

<h2 id="3-先保证能编译">3. 先保证能编译</h2>

<p>golangci-lint 依赖 typecheck（Go 编译器前端）。代码或依赖不完整时，只会看到 <code class="language-plaintext highlighter-rouge">typecheck</code> 报错，其他 linter <strong>不会</strong> 产出报告。排查顺序：</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">golangci-lint version</code> — Go 版本是否匹配；</li>
  <li><code class="language-plaintext highlighter-rouge">go mod tidy</code>；</li>
  <li><code class="language-plaintext highlighter-rouge">go build ./...</code> 或 <code class="language-plaintext highlighter-rouge">go test ./...</code>；</li>
  <li>不要对缺依赖的单文件孤立分析。</li>
</ol>

<h2 id="4-测试文件单独策略">4. 测试文件单独策略</h2>

<p>生产代码严格、测试代码适度放宽是常见做法（见示例 1 的 <code class="language-plaintext highlighter-rouge">path: _test\.go</code> exclusions）。<code class="language-plaintext highlighter-rouge">testpackage</code>、<code class="language-plaintext highlighter-rouge">paralleltest</code>、<code class="language-plaintext highlighter-rouge">testifylint</code> 等适合只在 <code class="language-plaintext highlighter-rouge">_test.go</code> 或测试目录启用。</p>

<h2 id="5-与-gofmt--goimports-分工">5. 与 gofmt / goimports 分工</h2>

<p>格式化交给 <code class="language-plaintext highlighter-rouge">golangci-lint fmt</code> 或 <code class="language-plaintext highlighter-rouge">formatters.enable</code>；lint 阶段专注逻辑与风格。避免在 CI 里既跑 <code class="language-plaintext highlighter-rouge">gofmt -l</code> 又跑重复 formatter linter。</p>

<h2 id="6-大仓库落地路径">6. 大仓库落地路径</h2>

<table>
  <thead>
    <tr>
      <th style="text-align: left">阶段</th>
      <th style="text-align: left">动作</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">第 1 周</td>
      <td style="text-align: left">零配置或 <code class="language-plaintext highlighter-rouge">standard</code> 本地跑，收集 noise</td>
    </tr>
    <tr>
      <td style="text-align: left">第 2 周</td>
      <td style="text-align: left">提交 <code class="language-plaintext highlighter-rouge">.golangci.yml</code>，CI 用 <code class="language-plaintext highlighter-rouge">--new-from-merge-base=main</code></td>
    </tr>
    <tr>
      <td style="text-align: left">第 3 周起</td>
      <td style="text-align: left">按模块逐步收紧 enable 列表；历史债务 backlog 与 MR 门禁分离</td>
    </tr>
  </tbody>
</table>

<h1 id="ci-与编辑器集成">CI 与编辑器集成</h1>

<p>安装 golangci-lint 的 <strong>官方推荐方式</strong>（<code class="language-plaintext highlighter-rouge">install.sh</code> 二进制 pin 版本、GitHub Actions、mise、Docker）见上文 <a href="#安装">安装</a> 一节。本节只补充 <strong>运行集成</strong> 配置。</p>

<h2 id="github-actions-1">GitHub Actions</h2>

<p>与 <a href="#github-actionsgithub-项目首选">CI 环境：GitHub Actions（GitHub 项目首选）</a> 相同，核心是 <code class="language-plaintext highlighter-rouge">golangci-lint-action</code> + <code class="language-plaintext highlighter-rouge">version: v2.12.2</code>：</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>
<span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/setup-go@v5</span>
  <span class="na">with</span><span class="pi">:</span>
    <span class="na">go-version</span><span class="pi">:</span> <span class="s">stable</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">golangci-lint</span>
  <span class="na">uses</span><span class="pi">:</span> <span class="s">golangci/golangci-lint-action@v8</span>
  <span class="na">with</span><span class="pi">:</span>
    <span class="na">version</span><span class="pi">:</span> <span class="s">v2.12.2</span>
    <span class="na">args</span><span class="pi">:</span> <span class="s">--timeout=5m</span>
</code></pre></div></div>

<h2 id="gitlab--buildkite">GitLab / Buildkite</h2>

<p>GitLab 可使用 <a href="https://golangci-lint.run/docs/welcome/install/ci/">Code Quality 组件</a>；Buildkite 有 <a href="https://github.com/buildkite-plugins/golangci-lint-buildkite-plugin">golangci-lint 插件</a>。</p>

<h2 id="vs-codego-扩展">VS Code（Go 扩展）</h2>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"go.lintTool"</span><span class="p">:</span><span class="w"> </span><span class="s2">"golangci-lint"</span><span class="err">,</span><span class="w">
</span><span class="nl">"go.lintFlags"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
  </span><span class="s2">"--path-mode=abs"</span><span class="p">,</span><span class="w">
  </span><span class="s2">"--fast-only"</span><span class="w">
</span><span class="p">]</span><span class="err">,</span><span class="w">
</span><span class="nl">"go.formatTool"</span><span class="p">:</span><span class="w"> </span><span class="s2">"custom"</span><span class="err">,</span><span class="w">
</span><span class="nl">"go.alternateTools"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"customFormatter"</span><span class="p">:</span><span class="w"> </span><span class="s2">"golangci-lint"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="nl">"go.formatFlags"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"fmt"</span><span class="p">,</span><span class="w"> </span><span class="s2">"--stdin"</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>

<p>golangci-lint 会 <strong>自动发现</strong> 当前文件对应的 <code class="language-plaintext highlighter-rouge">.golangci.yml</code>，一般不必在 VS Code 里重复写 linter 列表。</p>

<p>GoLand 2025.1+ 已内置 golangci-lint 支持（v1 / v2 均可）。</p>

<h1 id="从-v1-迁移到-v2">从 v1 迁移到 v2</h1>

<p>v2 是 <strong>配置破坏性升级</strong>，必写 <code class="language-plaintext highlighter-rouge">version: "2"</code>。官方提供迁移命令：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>golangci-lint migrate
<span class="c"># 原文件备份为 .golangci.yml.bck.yml</span>
</code></pre></div></div>

<p>关键变更（详见 <a href="https://golangci-lint.run/docs/product/migration-guide/">Migration guide</a>）：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">v1</th>
      <th style="text-align: left">v2</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">linters.disable-all: true</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">linters.default: none</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">linters.enable-all: true</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">linters.default: all</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">linters.presets: [style, ...]</code></td>
      <td style="text-align: left">迁移为显式 <code class="language-plaintext highlighter-rouge">linters.enable</code> 列表</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">gofmt</code> / <code class="language-plaintext highlighter-rouge">goimports</code> 在 linters 里</td>
      <td style="text-align: left">移到 <code class="language-plaintext highlighter-rouge">formatters.enable</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">gosimple</code> / <code class="language-plaintext highlighter-rouge">stylecheck</code> 单独启用</td>
      <td style="text-align: left">合并进 <code class="language-plaintext highlighter-rouge">staticcheck</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">linters.fast: true</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">linters.default: fast</code> 或 CLI <code class="language-plaintext highlighter-rouge">--fast-only</code></td>
    </tr>
    <tr>
      <td style="text-align: left">若干 deprecated linter</td>
      <td style="text-align: left">已移除（如 <code class="language-plaintext highlighter-rouge">exportloopref</code>、<code class="language-plaintext highlighter-rouge">golint</code>）</td>
    </tr>
  </tbody>
</table>

<p>迁移不会保留 YAML 注释，需手工补回。</p>

<h1 id="替代方案与选型">替代方案与选型</h1>

<p>Reddit 等社区讨论中，常见诉求是 <strong>golangci-lint 全量跑太慢</strong>（大 monorepo 冷启动数秒到数十秒）。下列工具 <strong>生态与成熟度各不相同</strong>，生产环境请自行 benchmark，勿盲目替换。</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">工具</th>
      <th style="text-align: left">定位</th>
      <th style="text-align: left">特点</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong><a href="https://staticcheck.io/">Staticcheck</a></strong></td>
      <td style="text-align: left">correctness 分析</td>
      <td style="text-align: left">误报极低；<code class="language-plaintext highlighter-rouge">staticcheck ./...</code> 即可；golangci-lint 已内置其规则</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong><code class="language-plaintext highlighter-rouge">go vet</code></strong></td>
      <td style="text-align: left">编译器配套</td>
      <td style="text-align: left">零依赖、极快；覆盖有限</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong><a href="https://github.com/vint-go/vint">vint</a></strong></td>
      <td style="text-align: left">golangci-lint 替代</td>
      <td style="text-align: left">宣称同规则下显著更快；配置统一；仍在快速演进</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong><a href="https://github.com/GWinfinity/woofmt">woofmt</a></strong></td>
      <td style="text-align: left">Rust 实现 lint+fmt</td>
      <td style="text-align: left">强调增量与并行；benchmark 声称数量级加速</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong><a href="https://github.com/krait-go/krait">krait</a></strong></td>
      <td style="text-align: left">代码健康扫描</td>
      <td style="text-align: left">死代码、重复、复杂度、架构分层一体；零配置 <code class="language-plaintext highlighter-rouge">krait check</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong><a href="https://github.com/mentasystems/gox">gox</a></strong></td>
      <td style="text-align: left">LLM 生成代码门禁</td>
      <td style="text-align: left">规则少而严、默认 error；与 golangci-lint 互补而非替代</td>
    </tr>
  </tbody>
</table>

<p>务实组合：</p>

<ul>
  <li><strong>默认</strong>：golangci-lint（<code class="language-plaintext highlighter-rouge">standard</code> + 少量 enable）+ pin 版本 + CI <code class="language-plaintext highlighter-rouge">new-from-merge-base</code>；</li>
  <li><strong>极致速度本地反馈</strong>：编辑器 <code class="language-plaintext highlighter-rouge">--fast-only</code> 或单独跑 Staticcheck；</li>
  <li><strong>探索新工具</strong>：在子模块试点 vint / woofmt，对比 issue 集合与 CI 时间后再决定。</li>
</ul>

<p>社区帖：<a href="https://www.reddit.com/r/golang/comments/1jepzes/alternatives_to_golangcilint_that_are_fast/">Alternatives to golangci-lint that are fast</a>（讨论速度、增量分析与新项目成熟度，阅读时注意发布时间与环境差异）。</p>

<h1 id="faq">FAQ</h1>

<p>以下整理自 <a href="https://golangci-lint.run/docs/welcome/faq/">官方 FAQ</a> 与日常落地问题。</p>

<h2 id="支持哪些-go-版本">支持哪些 Go 版本？</h2>

<p>与 Go 团队策略一致：<strong>最近两个 minor 版本</strong>。golangci-lint 能分析的 Go 语法上限，取决于 <strong>编译该二进制时使用的 Go 版本</strong>。升级 Go 后应同步升级 golangci-lint 发行包。</p>

<h2 id="ci-里怎么用">CI 里怎么用？</h2>

<p>安装固定版本后执行 <code class="language-plaintext highlighter-rouge">golangci-lint run</code>，<strong>非零 exit code 即失败</strong>；不要每次 CI 拉 <code class="language-plaintext highlighter-rouge">@latest</code>。详见上文 <a href="#ci-环境官方推荐路径">CI 环境：官方推荐路径</a>。</p>

<h2 id="为什么会出现-typecheck-错误">为什么会出现 typecheck 错误？</h2>

<p><code class="language-plaintext highlighter-rouge">typecheck</code> <strong>不是 linter</strong>，而是编译错误的展示标签（缺少 import、语法错误、单文件分析导致 package 不完整等）。存在 typecheck 时，其他 linter 通常 <strong>不会有输出</strong>。</p>

<p><strong>不能</strong> 通过 <code class="language-plaintext highlighter-rouge">nolint</code> 或 exclusions 忽略 typecheck——必须先让 <code class="language-plaintext highlighter-rouge">go build ./...</code> 通过。</p>

<p>排查 checklist：</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">golangci-lint version</code> 与 <code class="language-plaintext highlighter-rouge">go version</code>；</li>
  <li><code class="language-plaintext highlighter-rouge">go mod tidy</code>；</li>
  <li>全 package 构建；</li>
  <li>CGO / 私有 module 的 <code class="language-plaintext highlighter-rouge">GOPROXY</code>、<code class="language-plaintext highlighter-rouge">GOSUMDB</code>、git 凭据；</li>
  <li>分析范围是否过小（应用 <code class="language-plaintext highlighter-rouge">./...</code> 或完整 package 路径）。</li>
</ol>

<h2 id="为什么---new-from--有时漏报">为什么 <code class="language-plaintext highlighter-rouge">--new-from-*</code> 有时「漏报」？</h2>

<p>这些选项通过 <strong>git diff 行号</strong> 过滤 issue：issue 所在行若不在 diff 范围内会被跳过。若希望 <strong>变更文件内所有 issue</strong> 都上报，加 <code class="language-plaintext highlighter-rouge">--whole-files</code> 或配置 <code class="language-plaintext highlighter-rouge">issues.whole-files: true</code>。</p>

<h2 id="--fast-only-第一次为什么仍慢"><code class="language-plaintext highlighter-rouge">--fast-only</code> 第一次为什么仍慢？</h2>

<p>首次运行要填充类型信息缓存，后续会快很多。适合本地已编译过的迭代开发。</p>

<h2 id="如何减少误报">如何减少误报？</h2>

<ul>
  <li>使用 <code class="language-plaintext highlighter-rouge">linters.exclusions.presets</code>（如 <code class="language-plaintext highlighter-rouge">common-false-positives</code>）；</li>
  <li>按 <code class="language-plaintext highlighter-rouge">path</code> / <code class="language-plaintext highlighter-rouge">text</code> / <code class="language-plaintext highlighter-rouge">linters</code> 写 <code class="language-plaintext highlighter-rouge">exclusions.rules</code>；</li>
  <li>调单 linter 的 <code class="language-plaintext highlighter-rouge">settings</code>（如 gosec <code class="language-plaintext highlighter-rouge">excludes</code>、revive <code class="language-plaintext highlighter-rouge">rules</code>）；</li>
  <li>查阅各 linter 的 <a href="https://golangci-lint.run/docs/linters/false-positives/">False Positives</a> 文档。</li>
</ul>

<h2 id="homebrew--go-install-与官方二进制不一致">Homebrew / go install 与官方二进制不一致？</h2>

<p>Homebrew 与 <code class="language-plaintext highlighter-rouge">go install</code> 可能用不同 Go 版本编译，依赖树也未经过官方 release 测试。团队 CI 与本地开发应统一用 <strong><code class="language-plaintext highlighter-rouge">install.sh</code> + 相同 version 字符串</strong>；完整对照见 <a href="#安装方式对照小结">安装方式对照小结</a>。</p>

<h2 id="能否自定义-linter">能否自定义 linter？</h2>

<p>可以：官方提供 <a href="https://golangci-lint.run/docs/plugins/module-plugins/">Module Plugin System</a> 与 <a href="https://golangci-lint.run/docs/plugins/go-plugins/">Go Plugin System</a>。维护成本较高，一般团队优先用现有 linter + <code class="language-plaintext highlighter-rouge">depguard</code> / <code class="language-plaintext highlighter-rouge">forbidigo</code> 表达架构约束。</p>

<h1 id="常用命令速查">常用命令速查</h1>

<table>
  <thead>
    <tr>
      <th style="text-align: left">命令</th>
      <th style="text-align: left">说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">golangci-lint run</code></td>
      <td style="text-align: left">分析当前模块</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">golangci-lint run ./...</code></td>
      <td style="text-align: left">递归所有 package</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">golangci-lint run -v</code></td>
      <td style="text-align: left">打印使用的配置文件路径</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">golangci-lint run --fix</code></td>
      <td style="text-align: left">应用可自动修复的问题</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">golangci-lint run --fast-only</code></td>
      <td style="text-align: left">仅 fast linter</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">golangci-lint run -E gosec -D errcheck</code></td>
      <td style="text-align: left">临时增删 linter</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">golangci-lint fmt</code></td>
      <td style="text-align: left">格式化</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">golangci-lint linters</code></td>
      <td style="text-align: left">列出所有 linter</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">golangci-lint help linters</code></td>
      <td style="text-align: left">默认启用集说明</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">golangci-lint migrate</code></td>
      <td style="text-align: left">v1 配置迁移 v2</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">golangci-lint completion bash</code></td>
      <td style="text-align: left">Shell 补全</td>
    </tr>
  </tbody>
</table>

<h1 id="注意事项">注意事项</h1>

<ul>
  <li><strong>分析范围</strong>：目录参数须加 <code class="language-plaintext highlighter-rouge">/...</code> 才递归；不能混用不同 package 的单文件。</li>
  <li><strong>并发锁</strong>：默认不允许多个 golangci-lint 同时写同一缓存；CI 并行 job 可设 <code class="language-plaintext highlighter-rouge">run.allow-parallel-runners: true</code>。</li>
  <li><strong>timeout</strong>：v2 默认 <strong>无</strong> timeout；大仓库建议在 <code class="language-plaintext highlighter-rouge">run.timeout</code> 或 CLI <code class="language-plaintext highlighter-rouge">--timeout=5m</code> 显式设置。</li>
  <li><strong>generated code</strong>：v2 对 generated 文件默认 <code class="language-plaintext highlighter-rouge">strict</code> 排除策略，与 v1 <code class="language-plaintext highlighter-rouge">lax</code> 不同；迁移时注意 <code class="language-plaintext highlighter-rouge">migrate</code> 会保留旧行为。</li>
</ul>

<h1 id="参考资源">参考资源</h1>

<table>
  <thead>
    <tr>
      <th style="text-align: left">说明</th>
      <th style="text-align: left">链接</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">官网与文档</td>
      <td style="text-align: left"><a href="https://golangci-lint.run/">golangci-lint.run</a></td>
    </tr>
    <tr>
      <td style="text-align: left">GitHub 仓库</td>
      <td style="text-align: left"><a href="https://github.com/golangci/golangci-lint">golangci/golangci-lint</a></td>
    </tr>
    <tr>
      <td style="text-align: left">Quick Start</td>
      <td style="text-align: left"><a href="https://golangci-lint.run/docs/welcome/quick-start/">Quick Start</a></td>
    </tr>
    <tr>
      <td style="text-align: left">FAQ</td>
      <td style="text-align: left"><a href="https://golangci-lint.run/docs/welcome/faq/">FAQ</a></td>
    </tr>
    <tr>
      <td style="text-align: left">配置文件参考</td>
      <td style="text-align: left"><a href="https://golangci-lint.run/docs/configuration/file/">Configuration File</a></td>
    </tr>
    <tr>
      <td style="text-align: left">v1 → v2 迁移</td>
      <td style="text-align: left"><a href="https://golangci-lint.run/docs/product/migration-guide/">Migration guide</a></td>
    </tr>
    <tr>
      <td style="text-align: left">本地安装</td>
      <td style="text-align: left"><a href="https://golangci-lint.run/docs/welcome/install/local/">Local Installation</a></td>
    </tr>
    <tr>
      <td style="text-align: left">CI 安装</td>
      <td style="text-align: left"><a href="https://golangci-lint.run/docs/welcome/install/ci/">CI Installation</a></td>
    </tr>
    <tr>
      <td style="text-align: left">编辑器集成</td>
      <td style="text-align: left"><a href="https://golangci-lint.run/docs/welcome/integrations/">Integrations</a></td>
    </tr>
    <tr>
      <td style="text-align: left">Staticcheck 官方</td>
      <td style="text-align: left"><a href="https://staticcheck.io/docs/">staticcheck.io</a></td>
    </tr>
    <tr>
      <td style="text-align: left">Staticcheck · Getting started</td>
      <td style="text-align: left"><a href="https://staticcheck.dev/docs/getting-started/">Getting started</a></td>
    </tr>
    <tr>
      <td style="text-align: left">Staticcheck · CLI</td>
      <td style="text-align: left"><a href="https://staticcheck.dev/docs/running-staticcheck/cli/">Command-line interface</a></td>
    </tr>
    <tr>
      <td style="text-align: left">Staticcheck · 配置</td>
      <td style="text-align: left"><a href="https://staticcheck.dev/docs/configuration/">Configuration</a></td>
    </tr>
    <tr>
      <td style="text-align: left">Staticcheck · CI</td>
      <td style="text-align: left"><a href="https://staticcheck.dev/docs/running-staticcheck/ci/">Continuous integration</a></td>
    </tr>
    <tr>
      <td style="text-align: left">go vet 文档</td>
      <td style="text-align: left"><a href="https://pkg.go.dev/cmd/vet">cmd/vet</a></td>
    </tr>
    <tr>
      <td style="text-align: left">站内 · Go 实战（含早期 golangci-lint 片段）</td>
      <td style="text-align: left"><a href="/posts/go-in-action/">Go in Action</a></td>
    </tr>
    <tr>
      <td style="text-align: left">站内 · slog 与日志规范</td>
      <td style="text-align: left"><a href="/posts/go-log-and-slog/">Go log 与 slog</a></td>
    </tr>
    <tr>
      <td style="text-align: left">社区 · 更快替代方案讨论</td>
      <td style="text-align: left"><a href="https://www.reddit.com/r/golang/comments/1jepzes/alternatives_to_golangcilint_that_are_fast/">r/golang — alternatives that are fast</a></td>
    </tr>
  </tbody>
</table>]]></content><author><name></name></author><category term="GoLang" /><category term="GoLang" /><category term="golangci-lint" /><category term="Static Analysis" /><category term="CI" /><category term="Best Practices" /><summary type="html"><![CDATA[系统介绍 go vet 与 Staticcheck 轻量静态检查路径，以及 golangci-lint 的原理、配置、CI 集成、v2 迁移与 FAQ，并对比社区替代方案。]]></summary></entry><entry><title type="html">Go 标准日志 log 与 slog：从文本输出到结构化可观测性</title><link href="http://172.19.0.16:8080/posts/go-log-and-slog/" rel="alternate" type="text/html" title="Go 标准日志 log 与 slog：从文本输出到结构化可观测性" /><published>2026-06-29T16:09:21+08:00</published><updated>2026-06-29T16:09:21+08:00</updated><id>http://172.19.0.16:8080/posts/go-log-and-slog</id><content type="html" xml:base="http://172.19.0.16:8080/posts/go-log-and-slog/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#背景为什么-go-需要-slog" id="markdown-toc-背景为什么-go-需要-slog">背景：为什么 Go 需要 slog</a></li>
  <li><a href="#两代标准日志能力" id="markdown-toc-两代标准日志能力">两代标准日志能力</a>    <ul>
      <li><a href="#log传统标准库日志" id="markdown-toc-log传统标准库日志">log：传统标准库日志</a></li>
      <li><a href="#sloggo-121-的结构化日志" id="markdown-toc-sloggo-121-的结构化日志">slog：Go 1.21 的结构化日志</a></li>
    </ul>
  </li>
  <li><a href="#核心架构" id="markdown-toc-核心架构">核心架构</a></li>
  <li><a href="#log-与-slog-的核心差异" id="markdown-toc-log-与-slog-的核心差异">log 与 slog 的核心差异</a></li>
  <li><a href="#log-的优点与限制" id="markdown-toc-log-的优点与限制">log 的优点与限制</a>    <ul>
      <li><a href="#优点" id="markdown-toc-优点">优点</a></li>
      <li><a href="#限制" id="markdown-toc-限制">限制</a></li>
    </ul>
  </li>
  <li><a href="#slog-的优点与限制" id="markdown-toc-slog-的优点与限制">slog 的优点与限制</a>    <ul>
      <li><a href="#优点-1" id="markdown-toc-优点-1">优点</a></li>
      <li><a href="#限制-1" id="markdown-toc-限制-1">限制</a></li>
    </ul>
  </li>
  <li><a href="#使用方法" id="markdown-toc-使用方法">使用方法</a>    <ul>
      <li><a href="#继续使用-log-的场景" id="markdown-toc-继续使用-log-的场景">继续使用 log 的场景</a></li>
      <li><a href="#使用-slog-输出-json" id="markdown-toc-使用-slog-输出-json">使用 slog 输出 JSON</a></li>
      <li><a href="#在-sdk-中暴露-slog-注入点" id="markdown-toc-在-sdk-中暴露-slog-注入点">在 SDK 中暴露 slog 注入点</a></li>
    </ul>
  </li>
  <li><a href="#msgtrace-go-场景为什么-slog-更合适" id="markdown-toc-msgtrace-go-场景为什么-slog-更合适">msgtrace-go 场景：为什么 slog 更合适</a></li>
  <li><a href="#选型建议" id="markdown-toc-选型建议">选型建议</a>    <ul>
      <li><a href="#优先选择-log" id="markdown-toc-优先选择-log">优先选择 log</a></li>
      <li><a href="#优先选择-slog" id="markdown-toc-优先选择-slog">优先选择 slog</a></li>
    </ul>
  </li>
  <li><a href="#最佳实践" id="markdown-toc-最佳实践">最佳实践</a>    <ul>
      <li><a href="#sdk-接口暴露-sloglogger而不是自定义-infof" id="markdown-toc-sdk-接口暴露-sloglogger而不是自定义-infof">SDK 接口暴露 *slog.Logger，而不是自定义 Infof</a></li>
      <li><a href="#高基数字段要谨慎" id="markdown-toc-高基数字段要谨慎">高基数字段要谨慎</a></li>
      <li><a href="#debug-日志中昂贵字段先判断-enabled" id="markdown-toc-debug-日志中昂贵字段先判断-enabled">Debug 日志中昂贵字段先判断 Enabled</a></li>
      <li><a href="#自定义-handler-要认真处理映射" id="markdown-toc-自定义-handler-要认真处理映射">自定义 Handler 要认真处理映射</a></li>
      <li><a href="#日志字段命名保持稳定" id="markdown-toc-日志字段命名保持稳定">日志字段命名保持稳定</a></li>
    </ul>
  </li>
  <li><a href="#注意事项" id="markdown-toc-注意事项">注意事项</a></li>
  <li><a href="#总结" id="markdown-toc-总结">总结</a></li>
  <li><a href="#延伸阅读" id="markdown-toc-延伸阅读">延伸阅读</a></li>
</ul>

<p>Go 标准库里的日志能力现在可以粗略分成两代：早期的 <code class="language-plaintext highlighter-rouge">log</code> 负责把一行文本稳定地写出去，Go 1.21 引入的 <code class="language-plaintext highlighter-rouge">log/slog</code> 则提供了标准化的结构化日志 API。前者非常适合小工具和简单程序，后者更适合服务端、SDK、云原生和日志平台检索场景。</p>

<p>一句话概括：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>log  = 简单文本日志工具
slog = 标准结构化日志接口
</code></pre></div></div>

<p class="prompt-tip"><code class="language-plaintext highlighter-rouge">slog</code> 并不是为了删除或废弃 <code class="language-plaintext highlighter-rouge">log</code>，而是补齐现代服务端日志需要的 level、字段、handler、group、过滤和扩展能力。小程序继续用 <code class="language-plaintext highlighter-rouge">log</code> 没问题；服务端 SDK、框架、中间件和可观测性组件更适合用 <code class="language-plaintext highlighter-rouge">slog</code> 作为标准抽象。</p>

<h1 id="背景为什么-go-需要-slog">背景：为什么 Go 需要 slog</h1>

<p>早期 Go 程序对日志的要求很直接：能把信息打印出来，能带时间，能写到标准错误或文件。标准库 <code class="language-plaintext highlighter-rouge">log</code> 正好解决这个问题：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">log</span><span class="o">.</span><span class="n">Print</span><span class="p">(</span><span class="s">"server started"</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"listen addr=%s"</span><span class="p">,</span> <span class="n">addr</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"request completed"</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="s">"failed to start"</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Panic</span><span class="p">(</span><span class="s">"unexpected state"</span><span class="p">)</span>
</code></pre></div></div>

<p>这种 API 简单、稳定、依赖少，几乎所有 Go 开发者都熟悉。但当程序进入服务端和云原生环境后，日志不再只是“给人看的一行文本”，而是可观测性系统里的数据源：需要按字段检索、按级别过滤、被采集器解析、和 trace / metric / alert 串起来。</p>

<p>例如一条消息投递日志，如果用 <code class="language-plaintext highlighter-rouge">log.Printf</code>，通常会变成一行拼好的字符串：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">log</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"MsgTraceKafkaSendSummary topic=%s partition=%d offset=%d count=%d"</span><span class="p">,</span>
    <span class="n">topic</span><span class="p">,</span> <span class="n">partition</span><span class="p">,</span> <span class="n">offset</span><span class="p">,</span> <span class="n">count</span><span class="p">)</span>
</code></pre></div></div>

<p>这对人类阅读还可以，但日志平台想按 <code class="language-plaintext highlighter-rouge">topic</code>、<code class="language-plaintext highlighter-rouge">partition</code>、<code class="language-plaintext highlighter-rouge">offset</code> 做过滤和聚合时，就要依赖正则或采集端解析规则。字段名称变了、顺序变了、格式多一个空格，都可能影响后续检索。</p>

<p><code class="language-plaintext highlighter-rouge">slog</code> 的目标不是让打印日志这件事更酷，而是给 Go 生态提供一个官方的结构化日志接口：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">slog</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"MsgTraceKafkaSendSummary"</span><span class="p">,</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"topic"</span><span class="p">,</span> <span class="n">topic</span><span class="p">),</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">Int</span><span class="p">(</span><span class="s">"partition"</span><span class="p">,</span> <span class="n">partition</span><span class="p">),</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">Int64</span><span class="p">(</span><span class="s">"offset"</span><span class="p">,</span> <span class="n">offset</span><span class="p">),</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">Int</span><span class="p">(</span><span class="s">"count"</span><span class="p">,</span> <span class="n">count</span><span class="p">),</span>
<span class="p">)</span>
</code></pre></div></div>

<p>日志从“文本”变成了“记录”：消息、级别、时间、字段、调用位置都可以被统一表达，再交给不同的 <code class="language-plaintext highlighter-rouge">Handler</code> 输出为文本、JSON、文件、控制台或第三方日志框架。</p>

<h1 id="两代标准日志能力">两代标准日志能力</h1>

<h2 id="log传统标准库日志">log：传统标准库日志</h2>

<p><code class="language-plaintext highlighter-rouge">log</code> 很早就存在，定位非常清晰：把一行文本输出到一个 <code class="language-plaintext highlighter-rouge">io.Writer</code>。它的核心类型是 <code class="language-plaintext highlighter-rouge">log.Logger</code>，默认 logger 写到 <code class="language-plaintext highlighter-rouge">os.Stderr</code>，并带有日期和时间前缀。</p>

<p>常用 API 包括：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">log</span><span class="o">.</span><span class="n">Print</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Panic</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
</code></pre></div></div>

<p>如果需要创建独立 logger，可以使用：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">logger</span> <span class="o">:=</span> <span class="n">log</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">Stdout</span><span class="p">,</span> <span class="s">"msgtrace: "</span><span class="p">,</span> <span class="n">log</span><span class="o">.</span><span class="n">LstdFlags</span><span class="o">|</span><span class="n">log</span><span class="o">.</span><span class="n">Lshortfile</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"topic=%s count=%d"</span><span class="p">,</span> <span class="n">topic</span><span class="p">,</span> <span class="n">count</span><span class="p">)</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">log</code> 也提供了一个容易被忽略但很重要的方法：<code class="language-plaintext highlighter-rouge">Output(calldepth, msg)</code>。通过 <code class="language-plaintext highlighter-rouge">calldepth</code>，封装日志函数时可以手动调整 caller 层级：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">Infof</span><span class="p">(</span><span class="n">format</span> <span class="kt">string</span><span class="p">,</span> <span class="n">args</span> <span class="o">...</span><span class="n">any</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">_</span> <span class="o">=</span> <span class="n">logger</span><span class="o">.</span><span class="n">Output</span><span class="p">(</span><span class="m">2</span><span class="p">,</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"[INFO] "</span><span class="o">+</span><span class="n">format</span><span class="p">,</span> <span class="n">args</span><span class="o">...</span><span class="p">))</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这也是很多早期 Go 项目在 <code class="language-plaintext highlighter-rouge">log</code> 之上封装 <code class="language-plaintext highlighter-rouge">Infof</code>、<code class="language-plaintext highlighter-rouge">Warnf</code>、<code class="language-plaintext highlighter-rouge">Errorf</code> 的基础。</p>

<h2 id="sloggo-121-的结构化日志">slog：Go 1.21 的结构化日志</h2>

<p><code class="language-plaintext highlighter-rouge">log/slog</code> 是 Go 1.21 引入的标准结构化日志包。它把日志拆成几层概念：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Logger</code>：业务代码使用的入口，例如 <code class="language-plaintext highlighter-rouge">Info</code>、<code class="language-plaintext highlighter-rouge">Debug</code>、<code class="language-plaintext highlighter-rouge">Warn</code>、<code class="language-plaintext highlighter-rouge">Error</code>。</li>
  <li><code class="language-plaintext highlighter-rouge">Record</code>：一条日志记录，包含时间、级别、消息、PC 和 attrs。</li>
  <li><code class="language-plaintext highlighter-rouge">Attr</code>：结构化字段，例如 <code class="language-plaintext highlighter-rouge">slog.String("topic", topic)</code>。</li>
  <li><code class="language-plaintext highlighter-rouge">Handler</code>：真正负责判断是否启用、格式化和输出日志。</li>
  <li><code class="language-plaintext highlighter-rouge">Group</code>：字段分组，适合组织嵌套对象或避免字段冲突。</li>
</ul>

<p>默认可以直接使用包级函数：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">slog</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"server started"</span><span class="p">,</span> <span class="n">slog</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"addr"</span><span class="p">,</span> <span class="n">addr</span><span class="p">))</span>
<span class="n">slog</span><span class="o">.</span><span class="n">Warn</span><span class="p">(</span><span class="s">"queue almost full"</span><span class="p">,</span> <span class="n">slog</span><span class="o">.</span><span class="n">Int</span><span class="p">(</span><span class="s">"len"</span><span class="p">,</span> <span class="n">n</span><span class="p">))</span>
<span class="n">slog</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="s">"send failed"</span><span class="p">,</span> <span class="n">slog</span><span class="o">.</span><span class="n">Any</span><span class="p">(</span><span class="s">"error"</span><span class="p">,</span> <span class="n">err</span><span class="p">))</span>
</code></pre></div></div>

<p>更常见的服务端写法是显式创建 logger，并选择 <code class="language-plaintext highlighter-rouge">TextHandler</code> 或 <code class="language-plaintext highlighter-rouge">JSONHandler</code>：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">logger</span> <span class="o">:=</span> <span class="n">slog</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="n">slog</span><span class="o">.</span><span class="n">NewJSONHandler</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">Stdout</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">slog</span><span class="o">.</span><span class="n">HandlerOptions</span><span class="p">{</span>
    <span class="n">Level</span><span class="o">:</span> <span class="n">slog</span><span class="o">.</span><span class="n">LevelInfo</span><span class="p">,</span>
<span class="p">}))</span>

<span class="n">logger</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"request completed"</span><span class="p">,</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"method"</span><span class="p">,</span> <span class="s">"GET"</span><span class="p">),</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"path"</span><span class="p">,</span> <span class="s">"/api/messages"</span><span class="p">),</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">Int</span><span class="p">(</span><span class="s">"status"</span><span class="p">,</span> <span class="m">200</span><span class="p">),</span>
<span class="p">)</span>
</code></pre></div></div>

<p>JSON 输出更适合日志平台采集：</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"time"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-06-29T16:09:21.000+08:00"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"level"</span><span class="p">:</span><span class="w"> </span><span class="s2">"INFO"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"msg"</span><span class="p">:</span><span class="w"> </span><span class="s2">"request completed"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"method"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GET"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"path"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/api/messages"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="mi">200</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>如果希望所有日志自动带上公共字段，可以使用 <code class="language-plaintext highlighter-rouge">With</code>：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">logger</span> <span class="o">:=</span> <span class="n">slog</span><span class="o">.</span><span class="n">Default</span><span class="p">()</span><span class="o">.</span><span class="n">With</span><span class="p">(</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"service"</span><span class="p">,</span> <span class="s">"msgtrace-go"</span><span class="p">),</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"component"</span><span class="p">,</span> <span class="s">"producer"</span><span class="p">),</span>
<span class="p">)</span>

<span class="n">logger</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"producer started"</span><span class="p">)</span>
</code></pre></div></div>

<p>如果字段天然属于同一个对象，可以使用 <code class="language-plaintext highlighter-rouge">Group</code>：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">logger</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"kafka send summary"</span><span class="p">,</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">Group</span><span class="p">(</span><span class="s">"kafka"</span><span class="p">,</span>
        <span class="n">slog</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"topic"</span><span class="p">,</span> <span class="n">topic</span><span class="p">),</span>
        <span class="n">slog</span><span class="o">.</span><span class="n">Int</span><span class="p">(</span><span class="s">"partition"</span><span class="p">,</span> <span class="n">partition</span><span class="p">),</span>
        <span class="n">slog</span><span class="o">.</span><span class="n">Int64</span><span class="p">(</span><span class="s">"offset"</span><span class="p">,</span> <span class="n">offset</span><span class="p">),</span>
    <span class="p">),</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">Int</span><span class="p">(</span><span class="s">"count"</span><span class="p">,</span> <span class="n">count</span><span class="p">),</span>
<span class="p">)</span>
</code></pre></div></div>

<h1 id="核心架构">核心架构</h1>

<p>理解 <code class="language-plaintext highlighter-rouge">slog</code> 的关键，是不要把它看成“增强版 <code class="language-plaintext highlighter-rouge">Printf</code>”。它更像一套标准接口：业务代码只依赖 <code class="language-plaintext highlighter-rouge">slog.Logger</code>，具体输出交给 <code class="language-plaintext highlighter-rouge">Handler</code>。</p>

<pre><code class="language-mermaid">flowchart LR
  A[业务代码] --&gt; B[slog.Logger]
  B --&gt; C[slog.Record]
  C --&gt; D{slog.Handler}
  D --&gt; E[TextHandler]
  D --&gt; F[JSONHandler]
  D --&gt; G[自定义 Handler]
  G --&gt; H[tRPC logger]
  G --&gt; I[zap / logrus]
  G --&gt; J[文件 / 控制台 / 日志平台]
</code></pre>

<p>这个模型的价值在于解耦：库和 SDK 不需要强制业务使用某个第三方日志框架，只要接受 <code class="language-plaintext highlighter-rouge">*slog.Logger</code> 或 <code class="language-plaintext highlighter-rouge">slog.Handler</code>，业务侧就可以按自己的基础设施适配。</p>

<h1 id="log-与-slog-的核心差异">log 与 slog 的核心差异</h1>

<table>
  <thead>
    <tr>
      <th>维度</th>
      <th><code class="language-plaintext highlighter-rouge">log</code></th>
      <th><code class="language-plaintext highlighter-rouge">slog</code></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>日志格式</td>
      <td>文本行</td>
      <td>结构化记录</td>
    </tr>
    <tr>
      <td>日志级别</td>
      <td>无内置 level</td>
      <td>Debug / Info / Warn / Error</td>
    </tr>
    <tr>
      <td>字段</td>
      <td>手动拼字符串</td>
      <td>原生 <code class="language-plaintext highlighter-rouge">slog.Attr</code></td>
    </tr>
    <tr>
      <td>过滤</td>
      <td>需要自己封装</td>
      <td><code class="language-plaintext highlighter-rouge">Handler.Enabled</code></td>
    </tr>
    <tr>
      <td>扩展</td>
      <td>主要是换 writer</td>
      <td>自定义 <code class="language-plaintext highlighter-rouge">Handler</code></td>
    </tr>
    <tr>
      <td>检索友好</td>
      <td>一般</td>
      <td>更好</td>
    </tr>
    <tr>
      <td>性能优化</td>
      <td>比较有限</td>
      <td>可通过 <code class="language-plaintext highlighter-rouge">Enabled</code> 避免构造开销</td>
    </tr>
    <tr>
      <td>caller / source</td>
      <td><code class="language-plaintext highlighter-rouge">log.Output(calldepth, ...)</code></td>
      <td><code class="language-plaintext highlighter-rouge">Record.PC</code> / Handler 处理</td>
    </tr>
    <tr>
      <td>兼容性</td>
      <td>最老最稳</td>
      <td>Go 1.21+</td>
    </tr>
  </tbody>
</table>

<p>这个表格背后的重点不是“哪个更高级”，而是“日志数据最终要服务谁”。如果日志主要给开发者临时排错看，文本行足够；如果日志要进入 ELK、Loki、Datadog、Cloud Logging、SLS 等平台做长期检索和分析，结构化字段会明显更稳定。</p>

<h1 id="log-的优点与限制">log 的优点与限制</h1>

<h2 id="优点">优点</h2>

<p><code class="language-plaintext highlighter-rouge">log</code> 最大的优点是简单。它几乎不需要解释，也不会带来额外抽象。对于命令行工具、一次性脚本、小型守护进程、学习示例，<code class="language-plaintext highlighter-rouge">log.Printf</code> 往往就是最合适的选择。</p>

<p>它的兼容性也最好。只要是 Go 程序，基本都能使用 <code class="language-plaintext highlighter-rouge">log</code>，不需要考虑 Go 1.21 版本门槛。对于仍需兼容旧版本 Go 的库，<code class="language-plaintext highlighter-rouge">log</code> 仍然是低成本选择。</p>

<p>另外，<code class="language-plaintext highlighter-rouge">log.Output(calldepth, msg)</code> 让封装 caller 成为可能。很多简单日志封装只需要调整 <code class="language-plaintext highlighter-rouge">calldepth</code>，就能让输出位置指向真正的业务调用方。</p>

<h2 id="限制">限制</h2>

<p><code class="language-plaintext highlighter-rouge">log</code> 的限制也来自它的定位：它只负责输出文本，不负责理解日志语义。</p>

<p>首先，它没有标准 level。<code class="language-plaintext highlighter-rouge">Info</code>、<code class="language-plaintext highlighter-rouge">Warn</code>、<code class="language-plaintext highlighter-rouge">Error</code>、<code class="language-plaintext highlighter-rouge">Debug</code> 都要自己约定，例如在字符串前面拼 <code class="language-plaintext highlighter-rouge">[INFO]</code>。不同库的约定不一致时，采集端很难统一识别。</p>

<p>其次，它没有结构化字段。字段只能拼到字符串里：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">log</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"topic=%s partition=%d offset=%d"</span><span class="p">,</span> <span class="n">topic</span><span class="p">,</span> <span class="n">partition</span><span class="p">,</span> <span class="n">offset</span><span class="p">)</span>
</code></pre></div></div>

<p>这使日志平台只能把整行当文本处理，后续按字段过滤、聚合、统计都依赖额外解析。</p>

<p>最后，debug 日志的成本也更难控制。如果调用前已经完成字符串拼接或复杂对象格式化，即使最终不输出，成本也已经发生：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">log</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"debug payload=%s"</span><span class="p">,</span> <span class="n">expensiveString</span><span class="p">(</span><span class="n">payload</span><span class="p">))</span>
</code></pre></div></div>

<p>当然可以自己加 <code class="language-plaintext highlighter-rouge">if debugEnabled</code>，但这会把过滤逻辑散落到业务代码里。</p>

<h1 id="slog-的优点与限制">slog 的优点与限制</h1>

<h2 id="优点-1">优点</h2>

<p><code class="language-plaintext highlighter-rouge">slog</code> 的第一个价值是标准化。Go 生态过去已经有很多优秀日志库，例如 zap、zerolog、logrus，但库作者如果直接依赖其中一个，就会把使用者绑到某个实现上。<code class="language-plaintext highlighter-rouge">slog</code> 出现后，SDK 可以暴露标准入口：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">SetLogger</span><span class="p">(</span><span class="n">logger</span> <span class="o">*</span><span class="n">slog</span><span class="o">.</span><span class="n">Logger</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="n">logger</span> <span class="o">==</span> <span class="no">nil</span> <span class="p">{</span>
        <span class="n">logger</span> <span class="o">=</span> <span class="n">slog</span><span class="o">.</span><span class="n">Default</span><span class="p">()</span>
    <span class="p">}</span>
    <span class="n">defaultLogger</span> <span class="o">=</span> <span class="n">logger</span>
<span class="p">}</span>
</code></pre></div></div>

<p>业务侧可以选择默认 handler、JSON handler，也可以写 adapter 转接到现有日志系统。</p>

<p>第二个价值是结构化字段。字段不再依赖字符串约定，而是以 <code class="language-plaintext highlighter-rouge">Attr</code> 形式存在：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">logger</span><span class="o">.</span><span class="n">Debug</span><span class="p">(</span><span class="s">"MsgTraceKafkaSendSummary"</span><span class="p">,</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"topic"</span><span class="p">,</span> <span class="n">topic</span><span class="p">),</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">Int</span><span class="p">(</span><span class="s">"partition"</span><span class="p">,</span> <span class="n">partition</span><span class="p">),</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">Int64</span><span class="p">(</span><span class="s">"offset"</span><span class="p">,</span> <span class="n">offset</span><span class="p">),</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">Int</span><span class="p">(</span><span class="s">"count"</span><span class="p">,</span> <span class="n">count</span><span class="p">),</span>
<span class="p">)</span>
</code></pre></div></div>

<p>这类日志天然适合在平台里按 <code class="language-plaintext highlighter-rouge">topic</code>、<code class="language-plaintext highlighter-rouge">partition</code>、<code class="language-plaintext highlighter-rouge">count</code> 检索或聚合。</p>

<p>第三个价值是低级别日志过滤。<code class="language-plaintext highlighter-rouge">Handler.Enabled</code> 可以在构造完整 <code class="language-plaintext highlighter-rouge">Record</code> 前判断当前级别是否启用。对于高频 debug summary、消息投递明细、采样日志，这一点很重要。</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="n">logger</span><span class="o">.</span><span class="n">Enabled</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">slog</span><span class="o">.</span><span class="n">LevelDebug</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">logger</span><span class="o">.</span><span class="n">DebugContext</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="s">"debug summary"</span><span class="p">,</span>
        <span class="n">slog</span><span class="o">.</span><span class="n">Any</span><span class="p">(</span><span class="s">"summary"</span><span class="p">,</span> <span class="n">buildSummary</span><span class="p">()),</span>
    <span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p class="prompt-info">如果字段构造本身很便宜，可以直接调用 <code class="language-plaintext highlighter-rouge">Debug</code>；如果字段需要遍历大对象、序列化 payload、计算摘要，显式 <code class="language-plaintext highlighter-rouge">Enabled</code> 判断更稳妥。</p>

<h2 id="限制-1">限制</h2>

<p><code class="language-plaintext highlighter-rouge">slog</code> 的 API 比 <code class="language-plaintext highlighter-rouge">log</code> 稍复杂。它引入了 <code class="language-plaintext highlighter-rouge">Logger</code>、<code class="language-plaintext highlighter-rouge">Handler</code>、<code class="language-plaintext highlighter-rouge">Record</code>、<code class="language-plaintext highlighter-rouge">Attr</code>、<code class="language-plaintext highlighter-rouge">Value</code>、<code class="language-plaintext highlighter-rouge">Group</code> 等概念，简单程序可能会觉得“为了打印一行日志多了很多东西”。</p>

<p>它也有 Go 版本要求。标准库 <code class="language-plaintext highlighter-rouge">log/slog</code> 从 Go 1.21 开始可用，如果项目还要兼容更老版本，就需要升级工具链，或使用 <code class="language-plaintext highlighter-rouge">golang.org/x/exp/slog</code> 的历史方案并承担迁移成本。</p>

<p>另外，caller / source 是否准确依赖 handler 实现。标准 <code class="language-plaintext highlighter-rouge">HandlerOptions{AddSource: true}</code> 可以输出 source，但自定义 adapter 如果没有正确处理 <code class="language-plaintext highlighter-rouge">Record.PC</code>，就可能出现调用位置丢失、偏移不准或统一指向 adapter 的问题。</p>

<p class="prompt-warning">与已有日志框架集成时，也通常需要写 adapter。adapter 要处理 level 映射、字段展开、group 表达、错误字段、source、上下文等细节，否则可能出现字段丢失、level 不准、caller 偏移等问题。</p>

<h1 id="使用方法">使用方法</h1>

<h2 id="继续使用-log-的场景">继续使用 log 的场景</h2>

<p>如果程序只是一个小工具，输出内容主要给人看，直接使用 <code class="language-plaintext highlighter-rouge">log</code> 是合理的：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">import</span> <span class="s">"log"</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">log</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"start cleanup"</span><span class="p">)</span>
    <span class="n">log</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"removed files=%d"</span><span class="p">,</span> <span class="m">12</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>需要写到文件时，更换 writer 即可：</p>

<div file="main.go" class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">file</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">OpenFile</span><span class="p">(</span><span class="s">"app.log"</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">O_CREATE</span><span class="o">|</span><span class="n">os</span><span class="o">.</span><span class="n">O_WRONLY</span><span class="o">|</span><span class="n">os</span><span class="o">.</span><span class="n">O_APPEND</span><span class="p">,</span> <span class="m">0o644</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
    <span class="n">log</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">defer</span> <span class="n">file</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>

<span class="n">logger</span> <span class="o">:=</span> <span class="n">log</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="n">file</span><span class="p">,</span> <span class="s">"cleanup: "</span><span class="p">,</span> <span class="n">log</span><span class="o">.</span><span class="n">LstdFlags</span><span class="o">|</span><span class="n">log</span><span class="o">.</span><span class="n">Lshortfile</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"job started"</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="使用-slog-输出-json">使用 slog 输出 JSON</h2>

<p>服务端程序通常更适合 JSON：</p>

<div file="main.go" class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">import</span> <span class="p">(</span>
    <span class="s">"log/slog"</span>
    <span class="s">"os"</span>
<span class="p">)</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">logger</span> <span class="o">:=</span> <span class="n">slog</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="n">slog</span><span class="o">.</span><span class="n">NewJSONHandler</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">Stdout</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">slog</span><span class="o">.</span><span class="n">HandlerOptions</span><span class="p">{</span>
        <span class="n">Level</span><span class="o">:</span> <span class="n">slog</span><span class="o">.</span><span class="n">LevelInfo</span><span class="p">,</span>
    <span class="p">}))</span>

    <span class="n">logger</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"server started"</span><span class="p">,</span>
        <span class="n">slog</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"addr"</span><span class="p">,</span> <span class="s">":8080"</span><span class="p">),</span>
        <span class="n">slog</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"env"</span><span class="p">,</span> <span class="s">"prod"</span><span class="p">),</span>
    <span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="在-sdk-中暴露-slog-注入点">在 SDK 中暴露 slog 注入点</h2>

<p>SDK 不应该轻易规定业务必须使用某个日志库。更稳妥的方式是默认使用 <code class="language-plaintext highlighter-rouge">slog.Default()</code>，同时允许业务注入：</p>

<div file="logger.go" class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">msgtrace</span>

<span class="k">import</span> <span class="s">"log/slog"</span>

<span class="k">var</span> <span class="n">logger</span> <span class="o">=</span> <span class="n">slog</span><span class="o">.</span><span class="n">Default</span><span class="p">()</span>

<span class="k">func</span> <span class="n">SetLogger</span><span class="p">(</span><span class="n">l</span> <span class="o">*</span><span class="n">slog</span><span class="o">.</span><span class="n">Logger</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="n">l</span> <span class="o">==</span> <span class="no">nil</span> <span class="p">{</span>
        <span class="n">logger</span> <span class="o">=</span> <span class="n">slog</span><span class="o">.</span><span class="n">Default</span><span class="p">()</span>
        <span class="k">return</span>
    <span class="p">}</span>
    <span class="n">logger</span> <span class="o">=</span> <span class="n">l</span>
<span class="p">}</span>
</code></pre></div></div>

<p>内部使用时只依赖 <code class="language-plaintext highlighter-rouge">slog</code>：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">logger</span><span class="o">.</span><span class="n">Debug</span><span class="p">(</span><span class="s">"MsgTraceKafkaSendSummary"</span><span class="p">,</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"topic"</span><span class="p">,</span> <span class="n">topic</span><span class="p">),</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">Int</span><span class="p">(</span><span class="s">"partition"</span><span class="p">,</span> <span class="n">partition</span><span class="p">),</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">Int64</span><span class="p">(</span><span class="s">"offset"</span><span class="p">,</span> <span class="n">offset</span><span class="p">),</span>
    <span class="n">slog</span><span class="o">.</span><span class="n">Int</span><span class="p">(</span><span class="s">"count"</span><span class="p">,</span> <span class="n">count</span><span class="p">),</span>
<span class="p">)</span>
</code></pre></div></div>

<p>业务侧可以将它转接到自己的日志体系：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">handler</span> <span class="o">:=</span> <span class="n">NewTRPCHandler</span><span class="p">(</span><span class="n">trpcLogger</span><span class="p">)</span>
<span class="n">msgtrace</span><span class="o">.</span><span class="n">SetLogger</span><span class="p">(</span><span class="n">slog</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="n">handler</span><span class="p">))</span>
</code></pre></div></div>

<p>这样 SDK 保持标准库依赖，业务基础设施也不会被强行替换。</p>

<h1 id="msgtrace-go-场景为什么-slog-更合适">msgtrace-go 场景：为什么 slog 更合适</h1>

<p>放到 <code class="language-plaintext highlighter-rouge">msgtrace-go</code> 这种旁路观测 SDK 场景，<code class="language-plaintext highlighter-rouge">log</code> 的问题会被放大：</p>

<ul>
  <li>SDK 日志没有标准 level，<code class="language-plaintext highlighter-rouge">Info/Warn/Error/Debug</code> 都需要自定义接口。</li>
  <li>debug summary 不好统一控制，调用方很难只打开某类低级别日志。</li>
  <li><code class="language-plaintext highlighter-rouge">Topic/Partition/Offset/Count</code> 只能拼字符串，不利于日志平台按字段过滤。</li>
  <li>业务想注入 tRPC logger 时，需要 SDK 自己定义 <code class="language-plaintext highlighter-rouge">Infof/Warnf/Errorf</code> 这类接口。</li>
  <li>不同业务线的日志框架不同，SDK 绑定任何一个具体框架都不合适。</li>
</ul>

<p>切到 <code class="language-plaintext highlighter-rouge">slog</code> 后，模型更自然：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">msgtrace-go</code> 内部统一使用 <code class="language-plaintext highlighter-rouge">slog</code>。</li>
  <li>默认使用 <code class="language-plaintext highlighter-rouge">slog.Default()</code>，不破坏简单接入体验。</li>
  <li>业务通过 <code class="language-plaintext highlighter-rouge">SetLogger(*slog.Logger)</code> 注入自己的 logger。</li>
  <li><code class="language-plaintext highlighter-rouge">jlibgo</code> 或业务基础库通过 <code class="language-plaintext highlighter-rouge">slog.Handler</code> 转接到 tRPC logger。</li>
  <li><code class="language-plaintext highlighter-rouge">MsgTraceKafkaSendSummary</code> 可以天然作为 Debug 日志。</li>
  <li><code class="language-plaintext highlighter-rouge">Topic/Partition/Offset/Count</code> 可以作为结构化字段输出。</li>
</ul>

<pre><code class="language-mermaid">sequenceDiagram
  participant SDK as msgtrace-go
  participant Slog as slog.Logger
  participant Handler as Custom Handler
  participant TRPC as tRPC Logger
  participant Platform as Log Platform

  SDK-&gt;&gt;Slog: Debug("MsgTraceKafkaSendSummary", attrs...)
  Slog-&gt;&gt;Handler: Enabled(ctx, Debug)
  Handler--&gt;&gt;Slog: true / false
  Slog-&gt;&gt;Handler: Handle(ctx, Record)
  Handler-&gt;&gt;TRPC: map level and attrs
  TRPC-&gt;&gt;Platform: output structured log
</code></pre>

<p>这里的核心收益不是“换了一个日志包”，而是 SDK 的日志契约从私有接口变成了 Go 官方标准接口。业务可以保留自己的日志后端，SDK 可以保留自己的结构化语义。</p>

<h1 id="选型建议">选型建议</h1>

<h2 id="优先选择-log">优先选择 log</h2>

<p>以下场景优先使用 <code class="language-plaintext highlighter-rouge">log</code>：</p>

<ul>
  <li>小型命令行工具、脚本、学习示例。</li>
  <li>日志只给人临时查看，不进入复杂日志平台。</li>
  <li>项目需要兼容非常老的 Go 版本。</li>
  <li>只需要输出简单文本，不需要 level、字段、过滤和 adapter。</li>
</ul>

<h2 id="优先选择-slog">优先选择 slog</h2>

<p>以下场景优先使用 <code class="language-plaintext highlighter-rouge">slog</code>：</p>

<ul>
  <li>服务端应用、微服务、云原生程序。</li>
  <li>SDK、框架、中间件，需要暴露标准日志注入模型。</li>
  <li>日志会被采集到平台，并按字段检索、聚合、告警。</li>
  <li>需要标准 level，尤其需要控制 debug 日志成本。</li>
  <li>希望将 Go 标准接口转接到 zap、logrus、tRPC logger 或内部日志系统。</li>
</ul>

<pre><code class="language-mermaid">flowchart TD
  A[需要写日志] --&gt; B{是否服务端或 SDK 场景}
  B -- 否 --&gt; C{是否只需要简单文本}
  C -- 是 --&gt; D[使用 log]
  C -- 否 --&gt; E[使用 slog]
  B -- 是 --&gt; F{是否需要字段检索 / level / 注入}
  F -- 是 --&gt; E
  F -- 否 --&gt; G[log 或 slog 都可]
  E --&gt; H[选择 TextHandler / JSONHandler / Custom Handler]
</code></pre>

<h1 id="最佳实践">最佳实践</h1>

<h2 id="sdk-接口暴露-sloglogger而不是自定义-infof">SDK 接口暴露 *slog.Logger，而不是自定义 Infof</h2>

<p>如果 SDK 自己定义：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="n">Logger</span> <span class="k">interface</span> <span class="p">{</span>
    <span class="n">Infof</span><span class="p">(</span><span class="n">format</span> <span class="kt">string</span><span class="p">,</span> <span class="n">args</span> <span class="o">...</span><span class="n">any</span><span class="p">)</span>
    <span class="n">Warnf</span><span class="p">(</span><span class="n">format</span> <span class="kt">string</span><span class="p">,</span> <span class="n">args</span> <span class="o">...</span><span class="n">any</span><span class="p">)</span>
    <span class="n">Errorf</span><span class="p">(</span><span class="n">format</span> <span class="kt">string</span><span class="p">,</span> <span class="n">args</span> <span class="o">...</span><span class="n">any</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>短期看很简单，长期会遇到几个问题：没有 <code class="language-plaintext highlighter-rouge">Debug</code> 的标准语义，没有结构化字段，没有 <code class="language-plaintext highlighter-rouge">Enabled</code>，也不容易表达 group 和 context。用 <code class="language-plaintext highlighter-rouge">*slog.Logger</code> 可以少发明一套接口。</p>

<h2 id="高基数字段要谨慎">高基数字段要谨慎</h2>

<p>结构化日志很适合字段检索，但不代表所有东西都应该成为字段。用户 ID、请求 ID、topic、partition 这类字段通常有价值；完整 payload、大对象、过长错误堆栈则要谨慎，避免增加日志成本和存储压力。</p>

<h2 id="debug-日志中昂贵字段先判断-enabled">Debug 日志中昂贵字段先判断 Enabled</h2>

<p>对于高频日志，尤其是 summary、批量明细、序列化结果，建议先判断：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="n">logger</span><span class="o">.</span><span class="n">Enabled</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">slog</span><span class="o">.</span><span class="n">LevelDebug</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">logger</span><span class="o">.</span><span class="n">DebugContext</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="s">"MsgTraceKafkaSendSummary"</span><span class="p">,</span>
        <span class="n">slog</span><span class="o">.</span><span class="n">Any</span><span class="p">(</span><span class="s">"summary"</span><span class="p">,</span> <span class="n">buildExpensiveSummary</span><span class="p">()),</span>
    <span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这能避免 debug 关闭时仍然构造昂贵字段。</p>

<h2 id="自定义-handler-要认真处理映射">自定义 Handler 要认真处理映射</h2>

<p>adapter 不是简单地把 <code class="language-plaintext highlighter-rouge">msg</code> 拼成字符串。至少要明确处理：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">slog.Level</code> 到目标日志库 level 的映射。</li>
  <li><code class="language-plaintext highlighter-rouge">Attr</code>、<code class="language-plaintext highlighter-rouge">Group</code>、<code class="language-plaintext highlighter-rouge">Any</code>、<code class="language-plaintext highlighter-rouge">error</code> 的展开方式。</li>
  <li><code class="language-plaintext highlighter-rouge">Record.PC</code> 和 source / caller 的处理。</li>
  <li><code class="language-plaintext highlighter-rouge">context.Context</code> 中 trace id、request id 等信息的注入。</li>
  <li>字段名冲突和保留字段，例如 <code class="language-plaintext highlighter-rouge">time</code>、<code class="language-plaintext highlighter-rouge">level</code>、<code class="language-plaintext highlighter-rouge">msg</code>。</li>
</ul>

<h2 id="日志字段命名保持稳定">日志字段命名保持稳定</h2>

<p>日志字段是面向平台和排障流程的接口。字段名一旦被仪表盘、告警、查询语句依赖，就应该像 API 一样谨慎修改。例如 <code class="language-plaintext highlighter-rouge">msg_name</code>、<code class="language-plaintext highlighter-rouge">topic</code>、<code class="language-plaintext highlighter-rouge">partition</code>、<code class="language-plaintext highlighter-rouge">offset</code>、<code class="language-plaintext highlighter-rouge">count</code> 这类字段应保持小写、稳定、可读。</p>

<h1 id="注意事项">注意事项</h1>

<p><code class="language-plaintext highlighter-rouge">slog</code> 默认并不会让所有第三方日志库自动变成结构化日志系统。只有 handler 正确实现，字段、level、source 才能完整保留下来。</p>

<p><code class="language-plaintext highlighter-rouge">slog.Any("error", err)</code> 可以输出错误，但很多团队更习惯统一字段名为 <code class="language-plaintext highlighter-rouge">err</code> 或 <code class="language-plaintext highlighter-rouge">error</code>。字段命名最好在项目内统一，避免查询时一半搜 <code class="language-plaintext highlighter-rouge">err</code>，一半搜 <code class="language-plaintext highlighter-rouge">error</code>。</p>

<p><code class="language-plaintext highlighter-rouge">AddSource: true</code> 很方便，但会带来额外开销。生产环境是否开启 source，要结合日志量、排障需求和性能预算决定。</p>

<p>不要把 <code class="language-plaintext highlighter-rouge">slog</code> 当成 trace 的替代品。日志适合记录事件和上下文，链路关系仍应由 OpenTelemetry 等 tracing 体系表达；两者可以通过 trace id / span id 关联。</p>

<p class="prompt-warning">如果库需要支持 Go 1.20 或更低版本，直接依赖标准库 <code class="language-plaintext highlighter-rouge">log/slog</code> 会提高使用门槛。此时要么继续使用 <code class="language-plaintext highlighter-rouge">log</code>，要么调整库的最低 Go 版本要求。</p>

<h1 id="总结">总结</h1>

<p><code class="language-plaintext highlighter-rouge">log</code> 和 <code class="language-plaintext highlighter-rouge">slog</code> 是 Go 标准库中面向不同阶段需求的两代日志能力。<code class="language-plaintext highlighter-rouge">log</code> 解决的是“稳定输出一行文本”，它简单、直接、兼容性好；<code class="language-plaintext highlighter-rouge">slog</code> 解决的是“用标准接口表达结构化日志”，它支持 level、attr、handler、group 和提前过滤，更适合服务端和可观测性场景。</p>

<p>对于普通小程序，<code class="language-plaintext highlighter-rouge">log.Printf</code> 依然是好选择。对于服务端 SDK、框架、中间件，尤其是像 <code class="language-plaintext highlighter-rouge">msgtrace-go</code> 这种旁路观测组件，<code class="language-plaintext highlighter-rouge">slog</code> 更适合作为统一日志抽象：SDK 内部获得结构化表达，业务侧保留自己的日志后端，日志平台也能更稳定地按字段检索和分析。</p>

<p>简短结论：<strong><code class="language-plaintext highlighter-rouge">log</code> 适合简单输出，<code class="language-plaintext highlighter-rouge">slog</code> 适合服务端 SDK 和可观测性场景。</strong></p>

<h1 id="延伸阅读">延伸阅读</h1>

<ul>
  <li><a href="https://pkg.go.dev/log">Go 标准库 log 文档</a></li>
  <li><a href="https://pkg.go.dev/log/slog">Go 标准库 log/slog 文档</a></li>
  <li><a href="https://go.dev/blog/slog">Structured Logging with slog - The Go Blog</a></li>
  <li><a href="https://go.googlesource.com/proposal/+/master/design/56345-structured-logging.md">Go slog package design discussion</a></li>
  <li><a href="/posts/go-in-action/">Go in Action</a></li>
  <li><a href="/posts/using-go-modules/">Using Go Modules</a></li>
</ul>]]></content><author><name></name></author><category term="GoLang" /><category term="GoLang" /><category term="log" /><category term="slog" /><category term="Logging" /><category term="Observability" /><summary type="html"><![CDATA[系统梳理 Go 标准库 log 与 log/slog 的关系、核心差异、使用方式、优劣对比，以及在服务端 SDK 和可观测性场景中的选型实践。]]></summary></entry><entry><title type="html">OpenAI Skills 最佳实践：从个人工作流到团队插件化分发</title><link href="http://172.19.0.16:8080/posts/openai-skills-best-practices/" rel="alternate" type="text/html" title="OpenAI Skills 最佳实践：从个人工作流到团队插件化分发" /><published>2026-06-29T10:16:36+08:00</published><updated>2026-06-29T10:22:08+08:00</updated><id>http://172.19.0.16:8080/posts/openai-skills-best-practices</id><content type="html" xml:base="http://172.19.0.16:8080/posts/openai-skills-best-practices/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#agent-skills-是什么" id="markdown-toc-agent-skills-是什么">Agent Skills 是什么</a></li>
  <li><a href="#skillsrulesmcpplugins-的边界" id="markdown-toc-skillsrulesmcpplugins-的边界">Skills、Rules、MCP、Plugins 的边界</a></li>
  <li><a href="#在新项目中正确使用-skills" id="markdown-toc-在新项目中正确使用-skills">在新项目中正确使用 Skills</a>    <ul>
      <li><a href="#第一步先写仓库级-skill" id="markdown-toc-第一步先写仓库级-skill">第一步：先写仓库级 Skill</a></li>
      <li><a href="#第二步把可确定的动作交给脚本" id="markdown-toc-第二步把可确定的动作交给脚本">第二步：把可确定的动作交给脚本</a></li>
      <li><a href="#第三步补充-ui-元数据和依赖" id="markdown-toc-第三步补充-ui-元数据和依赖">第三步：补充 UI 元数据和依赖</a></li>
    </ul>
  </li>
  <li><a href="#skill-的触发设计" id="markdown-toc-skill-的触发设计">Skill 的触发设计</a></li>
  <li><a href="#什么时候升级为-plugin" id="markdown-toc-什么时候升级为-plugin">什么时候升级为 Plugin</a></li>
  <li><a href="#最佳实践" id="markdown-toc-最佳实践">最佳实践</a>    <ul>
      <li><a href="#一个-skill-只解决一个工作" id="markdown-toc-一个-skill-只解决一个工作">一个 Skill 只解决一个工作</a></li>
      <li><a href="#把步骤写成命令式流程" id="markdown-toc-把步骤写成命令式流程">把步骤写成命令式流程</a></li>
      <li><a href="#给-skill-配参考资料而不是塞满主文件" id="markdown-toc-给-skill-配参考资料而不是塞满主文件">给 Skill 配参考资料，而不是塞满主文件</a></li>
      <li><a href="#为触发行为写测试提示" id="markdown-toc-为触发行为写测试提示">为触发行为写测试提示</a></li>
      <li><a href="#版本化和代码评审" id="markdown-toc-版本化和代码评审">版本化和代码评审</a></li>
    </ul>
  </li>
  <li><a href="#实际使用中的注意事项" id="markdown-toc-实际使用中的注意事项">实际使用中的注意事项</a>    <ul>
      <li><a href="#不要把-skill-当成权限系统" id="markdown-toc-不要把-skill-当成权限系统">不要把 Skill 当成权限系统</a></li>
      <li><a href="#不要在-skill-里写密钥或私有凭据" id="markdown-toc-不要在-skill-里写密钥或私有凭据">不要在 Skill 里写密钥或私有凭据</a></li>
      <li><a href="#谨慎使用隐式触发" id="markdown-toc-谨慎使用隐式触发">谨慎使用隐式触发</a></li>
      <li><a href="#scripts-要可审计可重复可失败" id="markdown-toc-scripts-要可审计可重复可失败">scripts 要可审计、可重复、可失败</a></li>
      <li><a href="#注意上下文预算" id="markdown-toc-注意上下文预算">注意上下文预算</a></li>
    </ul>
  </li>
  <li><a href="#推荐的新项目落地清单" id="markdown-toc-推荐的新项目落地清单">推荐的新项目落地清单</a></li>
  <li><a href="#参考资源" id="markdown-toc-参考资源">参考资源</a></li>
</ul>

<p>Agent Skills 是把“如何完成一类任务”的经验沉淀成可复用资产的一种方式。它不像普通提示词那样散落在聊天记录里，也不像 MCP 工具那样一定要提供外部能力；它更像一个轻量的、可版本化的操作手册：在需要时被 Agent 读取，然后指导 Agent 按稳定流程完成任务。</p>

<p>OpenAI Codex 文档对 Skills 和 Plugins 的边界说得很清楚：<strong>Skills 是可复用工作流的作者格式，Plugins 是在 Codex 中可安装、可分发的打包单元</strong>。如果你还在一个仓库或一个人的工作流里迭代，先写本地 Skill；当它需要跨团队安装、绑定 MCP、Hooks、App 集成或展示元数据时，再升级成 Plugin。</p>

<p class="prompt-warning">需要特别注意的是，<a href="https://github.com/openai/skills"><code class="language-plaintext highlighter-rouge">openai/skills</code></a> 仓库目前已经标记为 <strong>deprecated</strong>。它仍然可以作为了解早期 Skills Catalog 的历史参考，但不应再作为新项目的主要实现依据。新建 Skills 或 Plugins 时，应优先参考 <a href="https://developers.openai.com/codex/skills">OpenAI Codex Agent Skills</a>、<a href="https://developers.openai.com/codex/plugins/build">Build Plugins</a> 和当前的 <a href="https://github.com/openai/plugins"><code class="language-plaintext highlighter-rouge">openai/plugins</code></a> 示例仓库。</p>

<blockquote class="prompt-tip">
  <p>Skills 的价值不在于“多写一段提示词”，而在于把团队反复执行、容易遗漏、需要上下文的流程变成可审查、可测试、可演进的工程资产。</p>
</blockquote>

<h1 id="agent-skills-是什么">Agent Skills 是什么</h1>

<p>一个 Skill 本质上是一个目录，最小形态只需要一个 <code class="language-plaintext highlighter-rouge">SKILL.md</code>：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>my-skill/
├── SKILL.md
├── scripts/
├── references/
├── assets/
└── agents/
    └── openai.yaml
</code></pre></div></div>

<p>其中只有 <code class="language-plaintext highlighter-rouge">SKILL.md</code> 是必需的，<code class="language-plaintext highlighter-rouge">scripts/</code>、<code class="language-plaintext highlighter-rouge">references/</code>、<code class="language-plaintext highlighter-rouge">assets/</code> 和 <code class="language-plaintext highlighter-rouge">agents/openai.yaml</code> 都是可选增强。</p>

<p><code class="language-plaintext highlighter-rouge">SKILL.md</code> 需要包含 <code class="language-plaintext highlighter-rouge">name</code> 和 <code class="language-plaintext highlighter-rouge">description</code>，后面才是 Agent 要遵循的具体说明：</p>

<div file=".agents/skills/release-notes/SKILL.md" class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">release-notes</span>
<span class="na">description</span><span class="pi">:</span> <span class="s">Generate release notes from merged pull requests and grouped commits.</span>
<span class="nn">---</span>

When asked to prepare release notes, inspect merged PRs, group changes by user impact,
call out breaking changes first, and produce a concise Markdown changelog.
</code></pre></div></div>

<p>Codex 使用 Skills 时采用 <strong>progressive disclosure</strong>，也就是渐进式披露：启动时只把每个 Skill 的名称、描述和路径放进上下文；当任务匹配某个 Skill 时，才读取完整 <code class="language-plaintext highlighter-rouge">SKILL.md</code>。这让一个环境可以安装很多 Skills，而不必一开始就把所有说明塞进上下文窗口。</p>

<pre><code class="language-mermaid">flowchart LR
  A[用户任务] --&gt; B{是否匹配 Skill description}
  B -- 否 --&gt; C[按普通上下文处理]
  B -- 是 --&gt; D[读取 SKILL.md]
  D --&gt; E{是否需要附加资源}
  E -- references/assets --&gt; F[加载参考资料或模板]
  E -- scripts --&gt; G[执行确定性脚本]
  F --&gt; H[按 Skill 流程完成任务]
  G --&gt; H
  C --&gt; H
</code></pre>

<h1 id="skillsrulesmcpplugins-的边界">Skills、Rules、MCP、Plugins 的边界</h1>

<p>在新项目里最容易犯的错误，是把所有东西都塞进一个 Skill。更好的方式是先分清它和其他机制的职责：</p>

<ul>
  <li><strong>Rules / AGENTS.md</strong>：适合放“这个仓库一直成立”的约束，例如代码风格、测试命令、目录约定、安全边界。</li>
  <li><strong>Skills</strong>：适合放“当执行某类任务时才需要”的流程，例如发版说明、迁移计划、安全审计、客服工单归纳。</li>
  <li><strong>MCP</strong>：适合提供外部系统能力，例如查询 Jira、读取 Datadog、访问内部文档、调用数据库只读接口。</li>
  <li><strong>Plugins</strong>：适合把 Skills、MCP 配置、Hooks、App 集成和展示信息一起打包，分发给团队或组织。</li>
</ul>

<p>简单判断标准是：如果它是长期背景知识，放规则；如果它是触发式工作流，放 Skill；如果它要连接外部系统，考虑 MCP；如果它要让别人安装，考虑 Plugin。</p>

<h1 id="在新项目中正确使用-skills">在新项目中正确使用 Skills</h1>

<p>假设新项目是一个 SaaS 后端服务，团队希望让 Codex 在处理 PR、发布、数据库迁移时更稳定。推荐从三个层次开始。</p>

<h2 id="第一步先写仓库级-skill">第一步：先写仓库级 Skill</h2>

<p>仓库内 Skills 可以放在 <code class="language-plaintext highlighter-rouge">.agents/skills</code> 下。Codex 会从当前工作目录向上扫描到仓库根目录，因此根目录的 <code class="language-plaintext highlighter-rouge">.agents/skills</code> 适合放整个项目通用的工作流。</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.agents/
└── skills/
    ├── pr-review/
    │   └── SKILL.md
    ├── db-migration/
    │   ├── SKILL.md
    │   └── references/
    │       └── migration-checklist.md
    └── release-notes/
        └── SKILL.md
</code></pre></div></div>

<p>例如先创建一个 PR Review Skill：</p>

<div file=".agents/skills/pr-review/SKILL.md" class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">pr-review</span>
<span class="na">description</span><span class="pi">:</span> <span class="s">Review pull requests in this repository for correctness, tests, migrations, security risks, and compatibility. Use when the user asks for PR review or code review.</span>
<span class="nn">---</span>

Review the current branch as a senior backend engineer.
<span class="p">
1.</span> Start with high-risk behavior changes, data migrations, authorization, and external API contracts.
<span class="p">2.</span> Check whether tests cover the changed behavior, not just changed files.
<span class="p">3.</span> Verify database migrations have forward-only behavior and safe rollout notes.
<span class="p">4.</span> Report findings first, ordered by severity, with concrete file references.
<span class="p">5.</span> If no issues are found, say so and call out any residual test gaps.
</code></pre></div></div>

<p>这个 Skill 的重点不是“请认真 review”，而是明确输入、顺序、检查项和输出格式。这样 Agent 以后看到“帮我 review 这个 PR”时，就更容易触发正确的流程。</p>

<h2 id="第二步把可确定的动作交给脚本">第二步：把可确定的动作交给脚本</h2>

<p>官方最佳实践建议：<strong>优先写 instructions，只有在需要确定性行为或外部工具时才加入 scripts</strong>。比如发版说明的主体可以由 Agent 归纳，但收集 commit 和 tag 的步骤更适合脚本。</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.agents/skills/release-notes/
├── SKILL.md
└── scripts/
    └── collect_changes.sh
</code></pre></div></div>

<div file=".agents/skills/release-notes/SKILL.md" class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">release-notes</span>
<span class="na">description</span><span class="pi">:</span> <span class="s">Prepare release notes from git history, tags, merged pull requests, and issue references.</span>
<span class="nn">---</span>

Use this skill when the user asks to draft release notes, changelogs, or upgrade notes.

Workflow:
<span class="p">
1.</span> Run <span class="sb">`scripts/collect_changes.sh`</span> to collect candidate commits and tags.
<span class="p">2.</span> Group changes into Breaking Changes, Features, Fixes, Documentation, and Internal.
<span class="p">3.</span> Explain migration steps before ordinary feature notes.
<span class="p">4.</span> Keep user-facing language concise and avoid implementation-only details.
<span class="p">5.</span> Include unresolved risks if the git history is ambiguous.
</code></pre></div></div>

<p>脚本不应该偷偷做发布、推送、删库、改配置这类有副作用的动作。它更适合收集、校验、格式化、生成中间数据。</p>

<blockquote class="prompt-warning">
  <p>Skill 内的脚本越多，维护成本和安全审查成本越高。能用清晰步骤表达的流程，先不要急着写脚本。</p>
</blockquote>

<h2 id="第三步补充-ui-元数据和依赖">第三步：补充 UI 元数据和依赖</h2>

<p>如果希望 Skill 在 Codex App 里更清晰，或控制是否允许隐式触发，可以增加 <code class="language-plaintext highlighter-rouge">agents/openai.yaml</code>：</p>

<div file=".agents/skills/pr-review/agents/openai.yaml" class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">interface</span><span class="pi">:</span>
  <span class="na">display_name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Repository</span><span class="nv"> </span><span class="s">PR</span><span class="nv"> </span><span class="s">Review"</span>
  <span class="na">short_description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Review</span><span class="nv"> </span><span class="s">backend</span><span class="nv"> </span><span class="s">PRs</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">correctness,</span><span class="nv"> </span><span class="s">rollout</span><span class="nv"> </span><span class="s">risk,</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">test</span><span class="nv"> </span><span class="s">gaps."</span>
  <span class="na">default_prompt</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Review</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">current</span><span class="nv"> </span><span class="s">branch</span><span class="nv"> </span><span class="s">using</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">repository</span><span class="nv"> </span><span class="s">PR</span><span class="nv"> </span><span class="s">review</span><span class="nv"> </span><span class="s">workflow."</span>

<span class="na">policy</span><span class="pi">:</span>
  <span class="na">allow_implicit_invocation</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div></div>

<p>如果一个 Skill 依赖 MCP 工具，也可以在这里声明依赖，让使用者更容易理解它需要哪些外部能力。对安全敏感的 Skill，可以把 <code class="language-plaintext highlighter-rouge">allow_implicit_invocation</code> 设为 <code class="language-plaintext highlighter-rouge">false</code>，要求用户显式使用 <code class="language-plaintext highlighter-rouge">$skill-name</code> 才触发。</p>

<h1 id="skill-的触发设计">Skill 的触发设计</h1>

<p>Skills 可以被显式调用，也可以被隐式调用。</p>

<ul>
  <li>显式调用：用户在提示里直接提到某个 Skill，例如 <code class="language-plaintext highlighter-rouge">$release-notes</code>。</li>
  <li>隐式调用：Codex 根据 <code class="language-plaintext highlighter-rouge">description</code> 判断当前任务是否匹配。</li>
</ul>

<p>因此 <code class="language-plaintext highlighter-rouge">description</code> 是 Skill 的“入口设计”。它应该短、准、靠前包含触发词，并说明边界。</p>

<p>不推荐：</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">description</span><span class="pi">:</span> <span class="s">Helps with project tasks.</span>
</code></pre></div></div>

<p>推荐：</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">description</span><span class="pi">:</span> <span class="s">Prepare release notes from git tags, merged pull requests, and issue references. Use for changelog, release notes, and upgrade notes tasks.</span>
</code></pre></div></div>

<p>更好的描述通常包含三件事：要做什么、什么时候用、什么时候不用。对于容易误触发的 Skill，描述里可以写清“only use when…”或在 <code class="language-plaintext highlighter-rouge">agents/openai.yaml</code> 里关闭隐式调用。</p>

<h1 id="什么时候升级为-plugin">什么时候升级为 Plugin</h1>

<p>当 Skill 只服务于当前仓库，直接放在 <code class="language-plaintext highlighter-rouge">.agents/skills</code> 就够了。以下情况再考虑 Plugin：</p>

<ul>
  <li>需要让多个仓库或多个团队安装同一套 Skills。</li>
  <li>需要把多个 Skills 作为一个套件分发。</li>
  <li>需要同时分发 MCP server 配置、Hooks、App 映射或品牌展示信息。</li>
  <li>需要通过 marketplace 管理安装策略、版本、分类和展示文案。</li>
</ul>

<p>一个最小 Plugin 结构如下：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>my-plugin/
├── .codex-plugin/
│   └── plugin.json
└── skills/
    └── release-notes/
        └── SKILL.md
</code></pre></div></div>

<div file="my-plugin/.codex-plugin/plugin.json" class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"engineering-workflows"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.1.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Reusable engineering workflow skills for release notes and PR review."</span><span class="p">,</span><span class="w">
  </span><span class="nl">"skills"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./skills/"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Plugin 的路径要相对 plugin root，并以 <code class="language-plaintext highlighter-rouge">./</code> 开头。官方文档也强调，<code class="language-plaintext highlighter-rouge">.codex-plugin/plugin.json</code> 是入口，<code class="language-plaintext highlighter-rouge">skills/</code>、<code class="language-plaintext highlighter-rouge">hooks/</code>、<code class="language-plaintext highlighter-rouge">assets/</code>、<code class="language-plaintext highlighter-rouge">.mcp.json</code>、<code class="language-plaintext highlighter-rouge">.app.json</code> 等应该放在 plugin 根目录，而不是放进 <code class="language-plaintext highlighter-rouge">.codex-plugin/</code> 里。</p>

<p>如果要在仓库内提供本地 marketplace，可以增加：</p>

<div file=".agents/plugins/marketplace.json" class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"local-engineering-plugins"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"interface"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"displayName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Local Engineering Plugins"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"plugins"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"engineering-workflows"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"source"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"source"</span><span class="p">:</span><span class="w"> </span><span class="s2">"local"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"path"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./plugins/engineering-workflows"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"policy"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"installation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AVAILABLE"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"authentication"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ON_INSTALL"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"category"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Productivity"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>这个 marketplace 可以先只暴露一个本地 Plugin，后续逐步扩展成团队 curated list。</p>

<h1 id="最佳实践">最佳实践</h1>

<h2 id="一个-skill-只解决一个工作">一个 Skill 只解决一个工作</h2>

<p>不要创建“backend-helper”这种万能 Skill。它会让触发边界变模糊，也会让 Agent 在无关任务里加载过多说明。更好的拆法是：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">api-contract-review</code>：检查 API 兼容性和错误码。</li>
  <li><code class="language-plaintext highlighter-rouge">db-migration-plan</code>：生成迁移、回滚和灰度计划。</li>
  <li><code class="language-plaintext highlighter-rouge">release-notes</code>：根据历史记录写发布说明。</li>
  <li><code class="language-plaintext highlighter-rouge">incident-summary</code>：从日志、时间线和修复记录生成事故复盘。</li>
</ul>

<p>一个 Skill 越聚焦，越容易测试，也越容易被另一个项目复用。</p>

<h2 id="把步骤写成命令式流程">把步骤写成命令式流程</h2>

<p>Skill 是给 Agent 执行的，不是给人看的宣传页。建议多用“先做什么、再做什么、输出什么”的命令式写法。</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">1.</span> Identify the changed public API surface.
<span class="p">2.</span> Compare request and response compatibility with the previous version.
<span class="p">3.</span> Flag breaking changes before minor issues.
<span class="p">4.</span> Include a migration note for each breaking change.
</code></pre></div></div>

<p>避免大量抽象原则，例如“保持高质量”“考虑性能”“注意安全”。这些话没有错，但无法稳定改变 Agent 的行为。把原则落成检查项、输入、输出和停止条件，效果更好。</p>

<h2 id="给-skill-配参考资料而不是塞满主文件">给 Skill 配参考资料，而不是塞满主文件</h2>

<p><code class="language-plaintext highlighter-rouge">SKILL.md</code> 应该保持短而可执行。长篇规范、术语表、模板、历史案例可以放在 <code class="language-plaintext highlighter-rouge">references/</code> 或 <code class="language-plaintext highlighter-rouge">assets/</code>，并在 Skill 中说明何时读取。</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code>If the task involves database schema changes, read <span class="sb">`references/migration-checklist.md`</span>
before proposing a migration plan.
</code></pre></div></div>

<p>这样既能保留完整知识，也不会让每次触发都加载一大段低相关内容。</p>

<h2 id="为触发行为写测试提示">为触发行为写测试提示</h2>

<p>创建 Skill 后，至少用几类 prompt 测试它是否正确触发：</p>

<ul>
  <li>应该触发：<code class="language-plaintext highlighter-rouge">帮我写这次发布的 release notes</code>。</li>
  <li>应该触发：<code class="language-plaintext highlighter-rouge">根据这些 PR 生成 changelog</code>。</li>
  <li>不该触发：<code class="language-plaintext highlighter-rouge">解释一下 semver 的概念</code>。</li>
  <li>不该触发：<code class="language-plaintext highlighter-rouge">帮我修复一个构建失败</code>。</li>
</ul>

<p>如果误触发，就收窄 <code class="language-plaintext highlighter-rouge">description</code>；如果该触发却没触发，就把关键触发词前置。</p>

<h2 id="版本化和代码评审">版本化和代码评审</h2>

<p>团队级 Skills 应该像代码一样进入版本控制和 review。尤其要关注：</p>

<ul>
  <li>是否引入了有副作用的脚本。</li>
  <li>是否要求 Agent 读取敏感文件或访问外部系统。</li>
  <li>是否把临时项目细节写成了长期规则。</li>
  <li>是否复制了过期流程。</li>
  <li>是否明确了输出格式和验收标准。</li>
</ul>

<p>对高风险 Skill，建议要求显式调用，或者把 destructive 操作改成“生成计划并等待用户确认”。</p>

<h1 id="实际使用中的注意事项">实际使用中的注意事项</h1>

<h2 id="不要把-skill-当成权限系统">不要把 Skill 当成权限系统</h2>

<p>Skill 是行为指导，不是安全边界。它可以提醒 Agent “不要推送代码”“先确认再执行迁移”，但真正的权限控制仍然要依赖工具审批、沙箱、代码 review、CI、云端 IAM 和组织策略。</p>

<h2 id="不要在-skill-里写密钥或私有凭据">不要在 Skill 里写密钥或私有凭据</h2>

<p>即使 Skill 只在本地使用，也不应该包含 token、cookie、数据库密码、生产环境连接串。Skill 应该描述“如何获取授权”或“使用哪个 MCP server”，而不是保存秘密本身。</p>

<h2 id="谨慎使用隐式触发">谨慎使用隐式触发</h2>

<p>隐式触发很方便，但适合低风险、边界清晰的任务。以下 Skill 更适合显式触发：</p>

<ul>
  <li>会调用外部系统或写入远端状态。</li>
  <li>会运行复杂脚本。</li>
  <li>会影响安全、合规、发布、计费或生产数据。</li>
  <li>名称和描述容易与普通任务混淆。</li>
</ul>

<h2 id="scripts-要可审计可重复可失败">scripts 要可审计、可重复、可失败</h2>

<p>脚本应满足几个基本要求：</p>

<ul>
  <li>默认只读，除非用户明确要求写操作。</li>
  <li>输出结构化结果或清晰文本，便于 Agent 后续处理。</li>
  <li>失败时返回非零状态和明确错误。</li>
  <li>不依赖隐藏的本机状态。</li>
  <li>不在脚本里绕过用户确认或工具审批。</li>
</ul>

<h2 id="注意上下文预算">注意上下文预算</h2>

<p>Codex 会限制初始 Skills 列表占用的上下文。如果安装了大量 Skills，描述可能被截短，甚至部分 Skills 不进入初始列表。实践上要做到：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">description</code> 开头就写关键触发词。</li>
  <li>避免冗长描述。</li>
  <li>删除不用的实验 Skill。</li>
  <li>把个人 Skill 和仓库 Skill 分开管理。</li>
</ul>

<h1 id="推荐的新项目落地清单">推荐的新项目落地清单</h1>

<p>新项目可以按这个顺序落地：</p>

<ol>
  <li>先写 <code class="language-plaintext highlighter-rouge">AGENTS.md</code> 或项目规则，记录长期不变的工程约束。</li>
  <li>找出团队每周重复三次以上、且容易遗漏步骤的任务。</li>
  <li>为每类任务创建一个 <code class="language-plaintext highlighter-rouge">.agents/skills/&lt;name&gt;/SKILL.md</code>。</li>
  <li>让 <code class="language-plaintext highlighter-rouge">description</code> 明确触发词和边界。</li>
  <li>先用纯 instructions 跑通，再考虑 <code class="language-plaintext highlighter-rouge">references/</code>、<code class="language-plaintext highlighter-rouge">assets/</code> 或 <code class="language-plaintext highlighter-rouge">scripts/</code>。</li>
  <li>用真实任务 prompt 测试触发和输出。</li>
  <li>把 Skill 纳入代码评审。</li>
  <li>当多个项目都需要同一套 Skill 时，再封装为 Plugin。</li>
</ol>

<p>如果你在 Cursor、Codex 或其他支持 Agent Skills 的工具中工作，也可以参考我之前写的 /posts/cursor-in-action/，把项目规则、工作流技能和代码上下文结合起来，而不是把所有要求都放在一次性 prompt 里。</p>

<h1 id="参考资源">参考资源</h1>

<ul>
  <li><a href="https://developers.openai.com/codex/skills">OpenAI Codex Agent Skills</a></li>
  <li><a href="https://developers.openai.com/codex/plugins/build">OpenAI Codex Build Plugins</a></li>
  <li><a href="https://github.com/openai/skills">openai/skills</a>：已废弃的历史 Skills Catalog，仅建议作为历史背景参考，不建议作为新项目实现依据。</li>
  <li><a href="https://github.com/openai/plugins">openai/plugins</a>：Codex Plugin 示例集合。</li>
  <li><a href="https://agentskills.io/home">Agent Skills Overview</a>：Agent Skills 开放格式与生态说明。</li>
  <li><a href="https://agentskills.io/specification">Agent Skills Specification</a></li>
</ul>]]></content><author><name></name></author><category term="AI" /><category term="OpenAI" /><category term="Codex" /><category term="Agent Skills" /><category term="Plugins" /><category term="AI Agent" /><summary type="html"><![CDATA[系统介绍 OpenAI Codex Agent Skills 的核心概念、创建方法、新项目落地示例、最佳实践与实际使用中的注意事项，并说明何时升级为 Plugins 分发。]]></summary></entry><entry><title type="html">GPT-5.5 使用指南与 Prompting 实践</title><link href="http://172.19.0.16:8080/posts/gpt-5-5-in-action/" rel="alternate" type="text/html" title="GPT-5.5 使用指南与 Prompting 实践" /><published>2026-06-26T09:01:05+08:00</published><updated>2026-06-26T09:01:05+08:00</updated><id>http://172.19.0.16:8080/posts/gpt-5-5-in-action</id><content type="html" xml:base="http://172.19.0.16:8080/posts/gpt-5-5-in-action/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#概述" id="markdown-toc-概述">概述</a>    <ul>
      <li><a href="#适用场景" id="markdown-toc-适用场景">适用场景</a></li>
    </ul>
  </li>
  <li><a href="#核心变化" id="markdown-toc-核心变化">核心变化</a>    <ul>
      <li><a href="#相对前代的主要提升" id="markdown-toc-相对前代的主要提升">相对前代的主要提升</a></li>
      <li><a href="#五项关键行为变化" id="markdown-toc-五项关键行为变化">五项关键行为变化</a>        <ul>
          <li><a href="#1-推理力度默认-medium" id="markdown-toc-1-推理力度默认-medium">1. 推理力度默认 <code class="language-plaintext highlighter-rouge">medium</code></a></li>
          <li><a href="#2-图像输入保留更多视觉细节" id="markdown-toc-2-图像输入保留更多视觉细节">2. 图像输入保留更多视觉细节</a></li>
          <li><a href="#3-指令遵循更字面更彻底" id="markdown-toc-3-指令遵循更字面更彻底">3. 指令遵循更字面、更彻底</a></li>
          <li><a href="#4-默认更简洁直接" id="markdown-toc-4-默认更简洁直接">4. 默认更简洁直接</a></li>
          <li><a href="#5-编码工作流需要更强编排" id="markdown-toc-5-编码工作流需要更强编排">5. 编码工作流需要更强编排</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#迁移快速上手" id="markdown-toc-迁移快速上手">迁移快速上手</a>    <ul>
      <li><a href="#用-codex-自动迁移" id="markdown-toc-用-codex-自动迁移">用 Codex 自动迁移</a></li>
      <li><a href="#api-与模型参数" id="markdown-toc-api-与模型参数">API 与模型参数</a>        <ul>
          <li><a href="#responses-api-最小示例" id="markdown-toc-responses-api-最小示例">Responses API 最小示例</a></li>
          <li><a href="#prompt-迁移清单" id="markdown-toc-prompt-迁移清单">Prompt 迁移清单</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#prompting-指南" id="markdown-toc-prompting-指南">Prompting 指南</a>    <ul>
      <li><a href="#相对-gpt-54-的-prompt-侧变化" id="markdown-toc-相对-gpt-54-的-prompt-侧变化">相对 GPT-5.4 的 Prompt 侧变化</a></li>
      <li><a href="#personality-与协作风格" id="markdown-toc-personality-与协作风格">Personality 与协作风格</a></li>
      <li><a href="#preamble改善首-token-可见时间" id="markdown-toc-preamble改善首-token-可见时间">Preamble：改善首 token 可见时间</a></li>
      <li><a href="#outcome-first-与停止条件" id="markdown-toc-outcome-first-与停止条件">Outcome-first 与停止条件</a>        <ul>
          <li><a href="#绝对化用语" id="markdown-toc-绝对化用语">绝对化用语</a></li>
          <li><a href="#停止条件与缺失证据" id="markdown-toc-停止条件与缺失证据">停止条件与缺失证据</a></li>
        </ul>
      </li>
      <li><a href="#输出格式与-textverbosity" id="markdown-toc-输出格式与-textverbosity">输出格式与 <code class="language-plaintext highlighter-rouge">text.verbosity</code></a></li>
      <li><a href="#grounding引用与检索预算" id="markdown-toc-grounding引用与检索预算">Grounding、引用与检索预算</a></li>
      <li><a href="#创意起草护栏" id="markdown-toc-创意起草护栏">创意起草护栏</a></li>
      <li><a href="#前端工程与视觉品味" id="markdown-toc-前端工程与视觉品味">前端工程与视觉品味</a></li>
      <li><a href="#要求模型检查自己的工作" id="markdown-toc-要求模型检查自己的工作">要求模型「检查自己的工作」</a></li>
      <li><a href="#phase-参数" id="markdown-toc-phase-参数"><code class="language-plaintext highlighter-rouge">phase</code> 参数</a></li>
      <li><a href="#建议的-prompt-结构" id="markdown-toc-建议的-prompt-结构">建议的 Prompt 结构</a></li>
    </ul>
  </li>
  <li><a href="#推理模型与-api-能力协同" id="markdown-toc-推理模型与-api-能力协同">推理模型与 API 能力协同</a></li>
  <li><a href="#最佳实践" id="markdown-toc-最佳实践">最佳实践</a></li>
  <li><a href="#注意事项" id="markdown-toc-注意事项">注意事项</a></li>
  <li><a href="#参考资料" id="markdown-toc-参考资料">参考资料</a></li>
</ul>

<blockquote>
  <p><a href="https://developers.openai.com/api/docs/models/gpt-5.5">GPT-5.5</a> 是 OpenAI GPT-5 模型家族的最新成员，面向复杂生产工作流——编码智能体、工具密集型 Agent、长上下文检索、产品规格到执行计划、以及面向客户的高质量对话。官方文档见 <a href="https://developers.openai.com/api/docs/guides/latest-model">Using GPT-5.5</a> 与 <a href="https://developers.openai.com/api/docs/guides/prompt-guidance">Prompt guidance</a>。若你已在用 <a href="/posts/codex-in-action/">Codex</a> 或 Agents SDK 构建 Agent，本文可作为迁移与调 Prompt 的参考。</p>
</blockquote>

<h1 id="概述">概述</h1>

<p>GPT-5.5 将复杂生产任务的基线能力整体抬高，但并非 <code class="language-plaintext highlighter-rouge">gpt-5.2</code> / <code class="language-plaintext highlighter-rouge">gpt-5.4</code> 的「即插即用」替代品。官方建议把它当作<strong>新的模型家族</strong>来调优：从最小可行 Prompt 出发，再针对代表性样本调整 <code class="language-plaintext highlighter-rouge">reasoning.effort</code>、<code class="language-plaintext highlighter-rouge">text.verbosity</code>、工具描述与输出格式。</p>

<h2 id="适用场景">适用场景</h2>

<table>
  <thead>
    <tr>
      <th style="text-align: left">场景</th>
      <th style="text-align: left">说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">编码与工程</td>
      <td style="text-align: left">需要规划、工具调用、代码库导航、验证与多步执行的复杂开发任务</td>
    </tr>
    <tr>
      <td style="text-align: left">工具密集型 Agent</td>
      <td style="text-align: left">大工具面、多步服务编排、长时运行任务</td>
    </tr>
    <tr>
      <td style="text-align: left">grounded 助手</td>
      <td style="text-align: left">检索、引用、证据约束下的问答与决策</td>
    </tr>
    <tr>
      <td style="text-align: left">长上下文</td>
      <td style="text-align: left">多文档分析、RAG、跨会话状态管理</td>
    </tr>
    <tr>
      <td style="text-align: left">产品规格 → 计划</td>
      <td style="text-align: left">从意图到可执行下一步的转化</td>
    </tr>
    <tr>
      <td style="text-align: left">面向客户的工作流</td>
      <td style="text-align: left">对执行质量与回复 polish 有较高要求的场景</td>
    </tr>
  </tbody>
</table>

<p>GPT-5.5 支持 GPT-5.4 已有的 API 能力：<strong>Prompt Caching</strong>、Hosted Tools、Tool Search、Compaction，以及手动回放 assistant items 时的 <strong><code class="language-plaintext highlighter-rouge">phase</code> 处理</strong>。</p>

<pre><code class="language-mermaid">flowchart TB
  subgraph Input["输入与编排"]
    P[Outcome-first Prompt]
    R[reasoning.effort]
    V[text.verbosity]
    T[Tools / Hosted Tools]
  end
  subgraph API["Responses API"]
    M[GPT-5.5]
    C[Compaction / previous_response_id]
  end
  subgraph Output["输出与 UX"]
    Pre[Preamble 中间更新]
    Phase[phase: commentary / final_answer]
    SO[Structured Outputs]
  end
  P --&gt; M
  R --&gt; M
  V --&gt; M
  T --&gt; M
  C --&gt; M
  M --&gt; Pre
  M --&gt; Phase
  M --&gt; SO
</code></pre>

<h1 id="核心变化">核心变化</h1>

<h2 id="相对前代的主要提升">相对前代的主要提升</h2>

<ul>
  <li><strong>更高效的推理</strong>：在相同 <code class="language-plaintext highlighter-rouge">reasoning.effort</code> 下，往往用更少的 reasoning tokens 达到相近或更好的效果；工具链、多步工作流中 token 节省会累积。</li>
  <li><strong>Outcome-first 执行更强</strong>：更善于从清晰目标出发，保留约束，把产品意图转化为具体下一步；应描述<strong>期望结果、成功标准、允许的副作用、证据规则与输出形态</strong>，而非逐步流程（除非路径本身必须固定）。</li>
  <li><strong>工具调用更精准</strong>：在大工具目录、多步服务流程、长时 Agent 任务上表现突出，工具选择与参数填写更可靠。</li>
  <li><strong>默认风格更 polished 且更直接</strong>：回复往往更 warm、可读，且所需 Prompt 脚手架更少。</li>
</ul>

<h2 id="五项关键行为变化">五项关键行为变化</h2>

<h3 id="1-推理力度默认-medium">1. 推理力度默认 <code class="language-plaintext highlighter-rouge">medium</code></h3>

<p>GPT-5.5 默认 <code class="language-plaintext highlighter-rouge">reasoning.effort = medium</code>，建议将其作为<strong>质量、可靠性、延迟与成本</strong>的平衡起点。</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">档位</th>
      <th style="text-align: left">适用场景</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">none</code></td>
      <td style="text-align: left">极低延迟、无需推理或多链工具调用的轻量任务（语音短轮、快速检索、分类）</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">low</code></td>
      <td style="text-align: left">延迟敏感但仍需工具、规划、搜索或多步决策时，优先于 <code class="language-plaintext highlighter-rouge">none</code> 评估</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">medium</code></td>
      <td style="text-align: left"><strong>默认推荐</strong>；多数生产负载的平衡点</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">high</code> / <code class="language-plaintext highlighter-rouge">xhigh</code></td>
      <td style="text-align: left">复杂 Agent、异步重任务或评测边界；仅在 eval 证明质量增益值得额外延迟与成本时使用</td>
    </tr>
  </tbody>
</table>

<p class="prompt-warning"><strong>注意</strong>：更高 reasoning effort 并不总是更好。指令冲突、停止条件模糊、工具访问过于开放时，提高 effort 可能导致过度思考、无谓搜索或输出质量回退。应基于 eval 数据调档，而非直觉。</p>

<h3 id="2-图像输入保留更多视觉细节">2. 图像输入保留更多视觉细节</h3>

<p>默认图像处理策略更新，以提升 computer use 等视觉任务表现：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left"><code class="language-plaintext highlighter-rouge">image_detail</code></th>
      <th style="text-align: left">行为概要</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">未设置 / <code class="language-plaintext highlighter-rouge">auto</code></td>
      <td style="text-align: left">使用 <code class="language-plaintext highlighter-rouge">original</code> 行为：最高约 10,240,000 像素或 6,000px 边长，不放大 resize</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">high</code></td>
      <td style="text-align: left">最高约 2,500,000 像素或 2,048px 边长</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">low</code></td>
      <td style="text-align: left">侧重上下文效率，对超过 512px 边长的图像更积极缩小</td>
    </tr>
  </tbody>
</table>

<p>详见 <a href="https://developers.openai.com/api/docs/guides/images-vision">Images and vision</a>。</p>

<h3 id="3-指令遵循更字面更彻底">3. 指令遵循更字面、更彻底</h3>

<p>模型会<strong>按字面理解</strong> Prompt。长时、工具密集或需收集证据的工作流中，应明确<strong>成功标准与停止规则</strong>。这与 Outcome-first 写法天然契合。</p>

<h3 id="4-默认更简洁直接">4. 默认更简洁直接</h3>

<p>默认输出 efficient、direct、task-oriented。面向客户或强 conversational 体验时，需显式补充 personality、warmth、理由说明与格式要求。<code class="language-plaintext highlighter-rouge">text.verbosity</code> 默认 <code class="language-plaintext highlighter-rouge">medium</code>；追求简洁时 <strong><code class="language-plaintext highlighter-rouge">low</code> 往往是更好的起点</strong>——在 GPT-5.5 上，<code class="language-plaintext highlighter-rouge">low</code> 比 GPT-5.4 的 <code class="language-plaintext highlighter-rouge">low</code> 会<strong>更显著地缩短</strong>最终答案。</p>

<h3 id="5-编码工作流需要更强编排">5. 编码工作流需要更强编排</h3>

<p>GPT-5.5 适合需要规划、工具、导航、验证、多步执行的复杂编码。对编码 Agent 应明确：复用策略、子 Agent 委派、测试期望、验收标准，以及何时继续推进 vs 何时请求帮助。</p>

<h1 id="迁移快速上手">迁移快速上手</h1>

<h2 id="用-codex-自动迁移">用 Codex 自动迁移</h2>

<p>OpenAI 提供 <strong>OpenAI Docs Skill</strong>，可在 Codex 等 Agent 中执行：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openai-docs migrate this project to gpt-5.5
</code></pre></div></div>

<p>Skill 可从 <a href="https://github.com/openai/skills">OpenAI skills 仓库</a> 下载，用于其他编码 Agent。</p>

<h2 id="api-与模型参数">API 与模型参数</h2>

<table>
  <thead>
    <tr>
      <th style="text-align: left">项</th>
      <th style="text-align: left">建议</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">模型 slug</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">gpt-5.5</code></td>
    </tr>
    <tr>
      <td style="text-align: left">API</td>
      <td style="text-align: left">凡涉及推理、工具调用或多轮对话，优先 <strong>Responses API</strong></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">reasoning.effort</code></td>
      <td style="text-align: left">从 <code class="language-plaintext highlighter-rouge">medium</code> 或 <code class="language-plaintext highlighter-rouge">low</code> 起测；见上表</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">text.verbosity</code></td>
      <td style="text-align: left">需要更短回复时设 <code class="language-plaintext highlighter-rouge">low</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">phase</code></td>
      <td style="text-align: left">工具密集 / 长时工作流：验证应用是否正确处理 preambles 与 assistant-item 回放</td>
    </tr>
    <tr>
      <td style="text-align: left">评测</td>
      <td style="text-align: left">对比 accuracy、token 消耗、端到端延迟</td>
    </tr>
  </tbody>
</table>

<h3 id="responses-api-最小示例">Responses API 最小示例</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">openai</span> <span class="kn">import</span> <span class="n">OpenAI</span>

<span class="n">client</span> <span class="o">=</span> <span class="nc">OpenAI</span><span class="p">()</span>

<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="n">responses</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span>
    <span class="n">model</span><span class="o">=</span><span class="sh">"</span><span class="s">gpt-5.5</span><span class="sh">"</span><span class="p">,</span>
    <span class="n">reasoning</span><span class="o">=</span><span class="p">{</span><span class="sh">"</span><span class="s">effort</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">medium</span><span class="sh">"</span><span class="p">},</span>
    <span class="n">text</span><span class="o">=</span><span class="p">{</span><span class="sh">"</span><span class="s">verbosity</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">low</span><span class="sh">"</span><span class="p">},</span>
    <span class="nb">input</span><span class="o">=</span><span class="sh">"</span><span class="s">Summarize the migration checklist for GPT-5.5 in three bullets.</span><span class="sh">"</span><span class="p">,</span>
<span class="p">)</span>

<span class="nf">print</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">output_text</span><span class="p">)</span>
</code></pre></div></div>

<p>多轮状态推荐使用 <code class="language-plaintext highlighter-rouge">previous_response_id</code>；Zero Data Retention 等无状态场景，则每轮传回相关 output items。详见 <a href="https://developers.openai.com/api/docs/guides/conversation-state#passing-context-from-the-previous-response">Passing context from the previous response</a>。</p>

<h3 id="prompt-迁移清单">Prompt 迁移清单</h3>

<ul>
  <li>写出<strong>期望结果与成功标准</strong>，而非冗长步骤。</li>
  <li><strong>删除</strong> Prompt 里可交给 Structured Outputs 的 JSON schema 描述。</li>
  <li>优化缓存：<strong>静态内容在前，动态用户上下文在后</strong>；重复流量可对 common prefix 使用一致的 <code class="language-plaintext highlighter-rouge">prompt_cache_key</code>。</li>
  <li><strong>去掉当前日期</strong>：模型已知 UTC 当前日期；仅在有业务时区、政策生效日、用户本地日等非 UTC 需求时再注入。</li>
  <li>工具说明尽量写在 <strong>tool description</strong> 里（用途、时机、必填参数、副作用、重试安全、常见错误），跨工具的 operating policy 才放进 system instructions。</li>
</ul>

<pre><code class="language-mermaid">sequenceDiagram
  participant App as 应用
  participant API as Responses API
  participant M as GPT-5.5
  App-&gt;&gt;API: create(model=gpt-5.5, input, tools)
  API-&gt;&gt;M: 推理 + 可选 tool calls
  M--&gt;&gt;API: preamble (phase=commentary)
  M--&gt;&gt;API: tool results loop
  M--&gt;&gt;API: final_answer (phase=final_answer)
  API--&gt;&gt;App: output items + usage
  App-&gt;&gt;API: 下一轮 previous_response_id 或手动回放 items（保留 phase）
</code></pre>

<h1 id="prompting-指南">Prompting 指南</h1>

<p>以下模式摘自官方 <a href="https://developers.openai.com/api/docs/guides/prompt-guidance">GPT-5.5 Prompting Guide</a>，并结合生产实践做了归纳。核心原则：<strong>更短、更 outcome-oriented 的 Prompt 通常优于流程堆叠</strong>；从 GPT-5.4 迁过来时，不要原样搬运旧指令栈。</p>

<h2 id="相对-gpt-54-的-prompt-侧变化">相对 GPT-5.4 的 Prompt 侧变化</h2>

<ul>
  <li>更短、以结果为先的 Prompt 往往效果更好。</li>
  <li>推理更高效 → 升级 <code class="language-plaintext highlighter-rouge">high</code> / <code class="language-plaintext highlighter-rouge">xhigh</code> 前先重评 <code class="language-plaintext highlighter-rouge">low</code> / <code class="language-plaintext highlighter-rouge">medium</code>。</li>
  <li>Preamble、<code class="language-plaintext highlighter-rouge">phase</code> 回放对工具型 Responses 工作流仍然关键。</li>
  <li>面向客户与 Agent UX 时，显式 personality、检索预算、验证规则更有价值。</li>
</ul>

<h2 id="personality-与协作风格">Personality 与协作风格</h2>

<p>默认风格：efficient、direct、task-oriented。面向客户助手、支持流程、教练型产品时，区分两类短块：</p>

<ul>
  <li><strong>Personality</strong>：语气、warmth、直接度、正式程度、幽默、共情、 polish 程度。</li>
  <li><strong>Collaboration style</strong>：何时提问、何时假设、主动性、上下文量、何时自检、如何处理不确定性或风险。</li>
</ul>

<p>两者都应简短，<strong>不能替代</strong>清晰目标、成功标准、工具规则与停止条件。</p>

<p><strong>任务型助手示例</strong>（节选）：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Personality
You are a capable collaborator: approachable, steady, and direct. Assume the user is competent
and acting in good faith. Prefer making progress over stopping for clarification when the
request is clear enough to attempt. Ask only when missing information would materially change
the answer or create meaningful risk. Stay concise without becoming curt.
</code></pre></div></div>

<p><strong>表达型协作助手</strong>可额外加入 warmth、curiosity、观点与 tradeoff 说明，但仍保持块短小。</p>

<h2 id="preamble改善首-token-可见时间">Preamble：改善首 token 可见时间</h2>

<p>流式场景下，用户会感知「多久才看到第一个字」。GPT-5.5 可能在 reasoning、规划或准备 tool call 后才输出可见文本。对多步、工具型、长时 Agent 任务，可要求<strong>先输出 1–2 句 preamble</strong>：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Before any tool calls for a multi-step task, send a short user-visible update that
acknowledges the request and states the first step. Keep it to one or two sentences.
</code></pre></div></div>

<p>编码 Agent 若区分 analysis channel，可更明确：在 analysis channel 任何内容或 tool call 之前，必须先有 intermediary update。</p>

<p>这与 <a href="/posts/codex-in-action/">Codex</a> 等产品的「中间进度更新」UX 一致，并与下文 <code class="language-plaintext highlighter-rouge">phase: "commentary"</code> 配合。</p>

<h2 id="outcome-first-与停止条件">Outcome-first 与停止条件</h2>

<p><strong>推荐写法</strong>——描述目的地，而非每一步：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Resolve the customer's issue end to end.

Success means:
- the eligibility decision is made from the available policy and account data
- any allowed action is completed before responding
- the final answer includes completed_actions, customer_message, and blockers
- if evidence is missing, ask for the smallest missing field
</code></pre></div></div>

<p><strong>避免</strong>除非确有必要，否则不要写：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>First inspect A, then inspect B, then compare every field, then think through
all possible exceptions, then decide which tool to call...
</code></pre></div></div>

<h3 id="绝对化用语">绝对化用语</h3>

<p>旧 Prompt 里大量 <code class="language-plaintext highlighter-rouge">ALWAYS</code> / <code class="language-plaintext highlighter-rouge">NEVER</code> / <code class="language-plaintext highlighter-rouge">must</code> / <code class="language-plaintext highlighter-rouge">only</code> 是为约束弱模型。GPT-5.5 上应保留给<strong>真不变量</strong>（安全、必填字段、禁止动作）；对「何时搜索、何时澄清、是否继续迭代」等判断，改用<strong>决策规则</strong>。</p>

<h3 id="停止条件与缺失证据">停止条件与缺失证据</h3>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Resolve the user query in the fewest useful tool loops, but do not let loop minimization
outrank correctness, accessible fallback evidence, calculations, or required citation tags.

After each result, ask: "Can I answer the user's core request now with useful evidence?"
If yes, answer.

Use the minimum evidence sufficient to answer correctly, cite it precisely, then stop.
</code></pre></div></div>

<h2 id="输出格式与-textverbosity">输出格式与 <code class="language-plaintext highlighter-rouge">text.verbosity</code></h2>

<p>GPT-5.5 对格式与结构高度可控。API 层用 <code class="language-plaintext highlighter-rouge">text.verbosity</code>；Prompt 层描述输出形态、受众与长度预算。</p>

<p><strong>默认 conversational 格式</strong>（节选）：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Let formatting serve comprehension. Use plain paragraphs as the default. Use headers, bold,
bullets, and numbered lists sparingly—when the user requests them, when comparison/ranking
needs structure, or when prose would be harder to scan.
</code></pre></div></div>

<p><strong>业务受众 + 长度</strong>：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Write for a senior business audience. Keep the answer under 400 words. Prioritize the
conclusion first, then reasoning, then caveats.
</code></pre></div></div>

<p><strong>润色类任务</strong>：先声明要保留的 artifact、长度、结构与体裁，再要求 quietly 改善 clarity，避免擅自扩写或加 claim。</p>

<p class="prompt-tip"><strong>实践建议</strong>：最终答案长度与 reasoning 质量是两条轴。需要稳定 UI 产物时，配合 <strong>Structured Outputs</strong>，而不是在 Prompt 里重复 JSON schema。</p>

<h2 id="grounding引用与检索预算">Grounding、引用与检索预算</h2>

<p>Grounded 回答应在 Prompt 中定义：什么需要证据、何谓足够、缺证据时如何行为。<strong>没有证据 ≠ 事实性「否」</strong>。</p>

<p><strong>检索预算</strong>（ stopping rules for search）示例：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>For ordinary Q&amp;A, start with one broad search using short, discriminative keywords.
If the top results contain enough citable support, answer from those results instead of
searching again.

Make another retrieval call only when:
- The top results do not answer the core question.
- A required fact, parameter, owner, date, ID, or source is missing.
- The user asked for exhaustive coverage, a comparison, or a comprehensive list.
- A specific document, URL, or artifact must be read.
- The answer would otherwise contain an important unsupported factual claim.

Do not search again to improve phrasing, add examples, or cite nonessential details.
</code></pre></div></div>

<p>更多 citation 格式见 <a href="https://developers.openai.com/api/docs/guides/citation-formatting">Citation formatting</a>。</p>

<h2 id="创意起草护栏">创意起草护栏</h2>

<p>幻灯片、发布文案、客户摘要、talk track 等场景，区分<strong>有来源的事实</strong>与<strong>创意措辞</strong>：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>- Use retrieved or provided facts for concrete product, customer, metric, and roadmap claims; cite them.
- Do not invent specific names, metrics, customer outcomes, or capabilities to sound stronger.
- If there is little citable support, write a useful generic draft with placeholders or
  clearly labeled assumptions rather than unsupported specifics.
</code></pre></div></div>

<h2 id="前端工程与视觉品味">前端工程与视觉品味</h2>

<p>前端任务可参考官方 frontend 示例：产品与用户上下文、设计系统对齐、首屏可用性、熟悉控件、各状态（loading / empty / error）、响应式，以及避免 generic hero、嵌套 card、装饰渐变、可见 instructional 文案、 broken layout 等生成 UI 常见缺陷。</p>

<h2 id="要求模型检查自己的工作">要求模型「检查自己的工作」</h2>

<p>在可验证场景给模型工具与 explicit 验证指令。</p>

<p><strong>编码 Agent</strong>：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>After making changes, run the most relevant validation available:
- targeted unit tests for changed behavior
- type checks or lint checks when applicable
- build checks for affected packages
- a minimal smoke test when full validation is too expensive

If validation cannot be run, explain why and describe the next best check.
</code></pre></div></div>

<p><strong>视觉产物</strong>：要求 render 后检查 layout、clipping、spacing、缺失内容与视觉一致性。</p>

<p><strong>工程 / 计划类</strong>：implementation plan 应可追溯——需求映射、涉及资源、状态流转、验证命令、失败行为、安全隐私、开放问题。</p>

<h2 id="phase-参数"><code class="language-plaintext highlighter-rouge">phase</code> 参数</h2>

<p>自 GPT-5.4 起，长时或工具密集的 Responses 工作流可用 assistant item 的 <code class="language-plaintext highlighter-rouge">phase</code> 区分中间更新与最终答案；GPT-5.5 沿用同一模式。</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">场景</th>
      <th style="text-align: left">做法</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">使用 <code class="language-plaintext highlighter-rouge">previous_response_id</code></td>
      <td style="text-align: left">API 自动保留 assistant 状态</td>
    </tr>
    <tr>
      <td style="text-align: left">手动回放 output items</td>
      <td style="text-align: left"><strong>原样保留</strong>每条 assistant 的 <code class="language-plaintext highlighter-rouge">phase</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">phase: "commentary"</code></td>
      <td style="text-align: left">中间、用户可见的更新（含 preamble）</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">phase: "final_answer"</code></td>
      <td style="text-align: left">完成态答案</td>
    </tr>
    <tr>
      <td style="text-align: left">user messages</td>
      <td style="text-align: left"><strong>不要</strong>加 <code class="language-plaintext highlighter-rouge">phase</code></td>
    </tr>
  </tbody>
</table>

<p>若集成未正确保留 <code class="language-plaintext highlighter-rouge">phase</code>，常见问题包括：中间更新被当作最终答案、工具链状态断裂。详见 <a href="https://developers.openai.com/api/docs/guides/phase-parameter">Phase parameter</a>。</p>

<h2 id="建议的-prompt-结构">建议的 Prompt 结构</h2>

<p>复杂 Prompt 可用下列骨架，每节保持简短，只在能改变行为处加细节：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Role: [1-2 sentences: function, context, job]

# Personality
[tone and collaboration style]

# Goal
[user-visible outcome]

# Success criteria
[what must be true before the final answer]

# Constraints
[policy, safety, evidence, side-effect limits]

# Output
[sections, length, tone]

# Stop rules
[when to retry, fallback, abstain, ask, or stop]
</code></pre></div></div>

<h1 id="推理模型与-api-能力协同">推理模型与 API 能力协同</h1>

<p>GPT-5.5 的最佳实践往往分布在 <strong>Prompt 之外</strong>的 API 与编排层。从 GPT-4.1、o3 或更早 GPT-5 迁移时，建议一并审视：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">能力</th>
      <th style="text-align: left">要点</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>Responses API</strong></td>
      <td style="text-align: left">多轮用 <code class="language-plaintext highlighter-rouge">previous_response_id</code>；无状态则每轮传回相关 output items</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Structured Outputs</strong></td>
      <td style="text-align: left">不要在 Prompt 里描述 schema；用 API 校验</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Prompt caching</strong></td>
      <td style="text-align: left">稳定前缀 + 动态后缀；跟踪 <code class="language-plaintext highlighter-rouge">usage.prompt_tokens_details.cached_tokens</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Tool calling</strong></td>
      <td style="text-align: left">工具级指导进 description；Hosted Tools（web search、file search、code interpreter 等）优先于自研编排</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Tool search</strong></td>
      <td style="text-align: left">大工具目录时延迟加载相关子集</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Compaction</strong></td>
      <td style="text-align: left">长时 Agent 压缩状态时保留：已完成动作、活跃假设、ID、工具结果、未解 blocker、下一具体目标</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Agents SDK</strong></td>
      <td style="text-align: left">新 Agent 系统优先 SDK 的 orchestration、tracing、handoffs，而非从零写编排</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>当前日期</strong></td>
      <td style="text-align: left">无需在 system 里写 UTC 日期；业务时区/政策日等例外再注入</td>
    </tr>
  </tbody>
</table>

<pre><code class="language-mermaid">flowchart LR
  subgraph Tune["调优顺序"]
    A[最小 Outcome-first Prompt]
    B[reasoning.effort]
    C[text.verbosity]
    D[工具描述 / Tool Search]
    E[Structured Outputs]
    F[仅对 eval 失败模式加 Prompt 块]
  end
  A --&gt; B --&gt; C --&gt; D --&gt; E --&gt; F
</code></pre>

<h1 id="最佳实践">最佳实践</h1>

<ol>
  <li><strong>从 eval 出发，而非从旧 Prompt 出发</strong>：选代表性任务集，对比 GPT-5.4 / GPT-5.5 的准确率、token、延迟与 UX。</li>
  <li><strong>一次只改一类变量</strong>：先换模型 slug 与 API 参数，再精简 Prompt；避免同时改十处无法归因。</li>
  <li><strong>工具说明下沉到 tool definition</strong>：GPT-5.5 在大工具面上收益明显；system prompt 只保留跨工具 policy。</li>
  <li><strong>流式产品务必设计 preamble + phase</strong>：改善首 token 感知，并保证客户端区分 commentary 与 final_answer。</li>
  <li><strong>检索与 Agent 必写停止规则</strong>：防止无限 search loop 或「为了措辞再搜一轮」。</li>
  <li><strong>编码 Agent 写清验收与验证命令</strong>：与 <a href="/posts/superpowers-in-action/">Superpowers</a> 等工程化工作流插件的 TDD / 审查节奏可对齐。</li>
  <li><strong>谨慎使用 <code class="language-plaintext highlighter-rouge">xhigh</code></strong>：仅用于异步、eval 或质量瓶颈已证实的任务。</li>
</ol>

<h1 id="注意事项">注意事项</h1>

<ul>
  <li><strong>非 drop-in 替换</strong>：从 <code class="language-plaintext highlighter-rouge">gpt-5.4</code> 迁移时，旧有的 process-heavy Prompt 可能引入噪声；应删繁就简后再测。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">none</code> 不是默认省钱档</strong>：仍需要工具或轻量规划时，优先试 <code class="language-plaintext highlighter-rouge">low</code> 而非 <code class="language-plaintext highlighter-rouge">none</code>。</li>
  <li><strong>verbosity 与 reasoning 独立</strong>：缩短最终答案不等于降低推理质量；两者分别用 <code class="language-plaintext highlighter-rouge">text.verbosity</code> 与 <code class="language-plaintext highlighter-rouge">reasoning.effort</code> 控制。</li>
  <li><strong>图像与 computer use</strong>：若依赖视觉细节，检查 <code class="language-plaintext highlighter-rouge">image_detail</code> 是否符合成本与质量预期。</li>
  <li><strong>手动 state 管理</strong>：不用 <code class="language-plaintext highlighter-rouge">previous_response_id</code> 时，<code class="language-plaintext highlighter-rouge">phase</code> 丢失会导致 subtle 的 UX 与工具链 bug。</li>
  <li><strong>日期与时区</strong>：模型知 UTC「今天」；财务报表截止日、用户本地「今天」等需显式传入。</li>
  <li><strong>安全与合规</strong>：Personality 块不能替代安全、隐私与权限边界；高影响动作仍需 verification loop。</li>
</ul>

<h1 id="参考资料">参考资料</h1>

<table>
  <thead>
    <tr>
      <th style="text-align: left">类型</th>
      <th style="text-align: left">链接</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">官方</td>
      <td style="text-align: left"><a href="https://developers.openai.com/api/docs/guides/latest-model">Using GPT-5.5（Latest model guide）</a></td>
    </tr>
    <tr>
      <td style="text-align: left">官方</td>
      <td style="text-align: left"><a href="https://developers.openai.com/api/docs/guides/prompt-guidance">Prompt guidance（GPT-5.5 Prompting Guide）</a></td>
    </tr>
    <tr>
      <td style="text-align: left">官方</td>
      <td style="text-align: left"><a href="https://developers.openai.com/api/docs/guides/reasoning">Reasoning models</a></td>
    </tr>
    <tr>
      <td style="text-align: left">官方</td>
      <td style="text-align: left"><a href="https://developers.openai.com/api/docs/guides/responses">Responses API</a></td>
    </tr>
    <tr>
      <td style="text-align: left">官方</td>
      <td style="text-align: left"><a href="https://developers.openai.com/api/docs/guides/structured-outputs">Structured Outputs</a></td>
    </tr>
    <tr>
      <td style="text-align: left">官方</td>
      <td style="text-align: left"><a href="https://developers.openai.com/api/docs/guides/prompt-caching">Prompt caching</a></td>
    </tr>
    <tr>
      <td style="text-align: left">官方</td>
      <td style="text-align: left"><a href="https://developers.openai.com/api/docs/guides/phase-parameter">Phase parameter</a></td>
    </tr>
    <tr>
      <td style="text-align: left">官方</td>
      <td style="text-align: left"><a href="https://developers.openai.com/api/docs/guides/citation-formatting">Citation formatting</a></td>
    </tr>
    <tr>
      <td style="text-align: left">官方</td>
      <td style="text-align: left"><a href="https://github.com/openai/skills">OpenAI skills 仓库（含 Docs Skill）</a></td>
    </tr>
    <tr>
      <td style="text-align: left">站内</td>
      <td style="text-align: left"><a href="/posts/codex-in-action/">Codex in Action</a></td>
    </tr>
    <tr>
      <td style="text-align: left">站内</td>
      <td style="text-align: left"><a href="/posts/superpowers-in-action/">Superpowers 使用指南</a></td>
    </tr>
  </tbody>
</table>]]></content><author><name></name></author><category term="AI" /><category term="OpenAI" /><category term="GPT-5.5" /><category term="Prompt Engineering" /><category term="Responses API" /><category term="大语言模型" /><summary type="html"><![CDATA[介绍 OpenAI GPT-5.5 的核心能力、行为变化与迁移要点，并系统梳理 Outcome-first Prompting、Preamble、Phase 处理、检索预算等 Prompt 模式，附 Responses API 示例与生产实践建议。]]></summary></entry><entry><title type="html">SQLite 完全指南：原理、使用、与 MySQL 对比及数据恢复</title><link href="http://172.19.0.16:8080/posts/sqlite-in-action/" rel="alternate" type="text/html" title="SQLite 完全指南：原理、使用、与 MySQL 对比及数据恢复" /><published>2026-06-18T11:21:41+08:00</published><updated>2026-06-18T17:16:31+08:00</updated><id>http://172.19.0.16:8080/posts/sqlite-in-action</id><content type="html" xml:base="http://172.19.0.16:8080/posts/sqlite-in-action/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#概述" id="markdown-toc-概述">概述</a></li>
  <li><a href="#what-is-sqlite" id="markdown-toc-what-is-sqlite">什么是 SQLite</a></li>
  <li><a href="#architecture" id="markdown-toc-architecture">核心原理与架构</a>    <ul>
      <li><a href="#分层结构" id="markdown-toc-分层结构">分层结构</a></li>
      <li><a href="#单文件格式" id="markdown-toc-单文件格式">单文件格式</a></li>
      <li><a href="#事务日志与-wal" id="markdown-toc-事务日志与-wal">事务、日志与 WAL</a></li>
      <li><a href="#类型系统与-sql-兼容性" id="markdown-toc-类型系统与-sql-兼容性">类型系统与 SQL 兼容性</a></li>
    </ul>
  </li>
  <li><a href="#usage" id="markdown-toc-usage">使用方法</a>    <ul>
      <li><a href="#linux-install" id="markdown-toc-linux-install">Linux 环境安装</a></li>
      <li><a href="#命令行工具-sqlite3" id="markdown-toc-命令行工具-sqlite3">命令行工具 sqlite3</a></li>
      <li><a href="#crud-example" id="markdown-toc-crud-example">快速入门：增删改查（CRUD）</a></li>
      <li><a href="#连接字符串与内存库" id="markdown-toc-连接字符串与内存库">连接字符串与内存库</a></li>
      <li><a href="#编程语言绑定" id="markdown-toc-编程语言绑定">编程语言绑定</a></li>
      <li><a href="#常用-pragma" id="markdown-toc-常用-pragma">常用 PRAGMA</a></li>
      <li><a href="#扩展能力" id="markdown-toc-扩展能力">扩展能力</a></li>
    </ul>
  </li>
  <li><a href="#sqlite-vs-mysql" id="markdown-toc-sqlite-vs-mysql">与 MySQL 功能对比</a></li>
  <li><a href="#data-loss-risks" id="markdown-toc-data-loss-risks">数据丢失与损坏风险</a>    <ul>
      <li><a href="#常见损坏原因" id="markdown-toc-常见损坏原因">常见损坏原因</a></li>
      <li><a href="#与丢失相关的非损坏场景" id="markdown-toc-与丢失相关的非损坏场景">与「丢失」相关的非损坏场景</a></li>
    </ul>
  </li>
  <li><a href="#integrity-check" id="markdown-toc-integrity-check">完整性检测</a></li>
  <li><a href="#backup" id="markdown-toc-backup">备份策略</a>    <ul>
      <li><a href="#1-在线热备推荐-api" id="markdown-toc-1-在线热备推荐-api">1. 在线热备（推荐 API）</a></li>
      <li><a href="#2-文件级拷贝" id="markdown-toc-2-文件级拷贝">2. 文件级拷贝</a></li>
      <li><a href="#3-逻辑导出" id="markdown-toc-3-逻辑导出">3. 逻辑导出</a></li>
      <li><a href="#4-持续保护" id="markdown-toc-4-持续保护">4. 持续保护</a></li>
    </ul>
  </li>
  <li><a href="#recovery" id="markdown-toc-recovery">恢复方法</a>    <ul>
      <li><a href="#官方-recover-与-sqlite3_recover" id="markdown-toc-官方-recover-与-sqlite3_recover">官方 <code class="language-plaintext highlighter-rouge">.recover</code> 与 <code class="language-plaintext highlighter-rouge">sqlite3_recover</code></a></li>
      <li><a href="#使用-sqldiff-与重建" id="markdown-toc-使用-sqldiff-与重建">使用 <code class="language-plaintext highlighter-rouge">sqldiff</code> 与重建</a></li>
      <li><a href="#工程化增强元数据备份微信实践" id="markdown-toc-工程化增强元数据备份微信实践">工程化增强：元数据备份（微信实践）</a></li>
      <li><a href="#第三方与取证工具" id="markdown-toc-第三方与取证工具">第三方与取证工具</a></li>
      <li><a href="#恢复后验证" id="markdown-toc-恢复后验证">恢复后验证</a></li>
    </ul>
  </li>
  <li><a href="#best-practices" id="markdown-toc-best-practices">最佳实践</a></li>
  <li><a href="#caveats" id="markdown-toc-caveats">注意事项</a></li>
  <li><a href="#references" id="markdown-toc-references">参考资源</a>    <ul>
      <li><a href="#官方文档" id="markdown-toc-官方文档">官方文档</a></li>
      <li><a href="#社区与延伸阅读" id="markdown-toc-社区与延伸阅读">社区与延伸阅读</a></li>
      <li><a href="#站内相关" id="markdown-toc-站内相关">站内相关</a></li>
    </ul>
  </li>
</ul>

<h2 id="概述">概述</h2>

<blockquote class="prompt-info">
  <p><a href="https://sqlite.org/">SQLite</a> 是由 D. Richard Hipp 于 2000 年发布的 C 语言嵌入式关系型数据库引擎。它不是独立运行的数据库服务，而是以库的形式链接进应用程序——官网标语 <strong>「Small. Fast. Reliable. Choose any three.」</strong> 概括了其设计取向。源码进入公有领域（public domain），当前稳定版为 <a href="https://sqlite.org/changes.html">3.53.2（2026-06-03）</a>；官方 Git 镜像见 <a href="https://github.com/sqlite/sqlite">sqlite/sqlite</a>。</p>
</blockquote>

<p>本文将从以下几个方面对 SQLite 进行全面解析：</p>

<ul>
  <li>原理与核心架构</li>
  <li>Linux 安装与命令行 CRUD 入门</li>
  <li>日常使用与编程接口</li>
  <li>与 MySQL 的功能对比</li>
  <li>数据丢失与损坏风险</li>
  <li>检测、备份与恢复方法</li>
  <li>最佳实践与注意事项</li>
</ul>

<h2 id="what-is-sqlite">什么是 SQLite</h2>

<p>SQLite 属于 <strong>嵌入式（embedded）</strong>、<strong>无服务器（serverless）</strong> 关系型数据库：</p>

<table>
  <thead>
    <tr>
      <th>维度</th>
      <th>SQLite</th>
      <th>典型客户端–服务器数据库（如 MySQL）</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>部署形态</td>
      <td>单个 <code class="language-plaintext highlighter-rouge">.db</code> 文件或 <code class="language-plaintext highlighter-rouge">:memory:</code></td>
      <td>独立守护进程 + 网络端口</td>
    </tr>
    <tr>
      <td>进程模型</td>
      <td>库函数调用，无 IPC</td>
      <td>应用通过 TCP/Unix Socket 连接服务</td>
    </tr>
    <tr>
      <td>配置</td>
      <td>零配置（zero-configuration）</td>
      <td>需安装、启停、账号权限等</td>
    </tr>
    <tr>
      <td>访问控制</td>
      <td>依赖操作系统文件权限</td>
      <td><code class="language-plaintext highlighter-rouge">GRANT</code>、角色、网络 ACL</td>
    </tr>
    <tr>
      <td>典型场景</td>
      <td>移动端、桌面应用、边缘设备、测试、本地缓存</td>
      <td>多用户 Web 服务、集中式 OLTP</td>
    </tr>
  </tbody>
</table>

<p>据 <a href="https://en.wikipedia.org/wiki/SQLite">Wikipedia</a> 与官网统计，SQLite 可能是全球部署量最大的数据库引擎：内置于 Android、iOS、Windows 10+、Firefox/Chrome 等浏览器与 countless 桌面应用中；美国国会图书馆亦将 SQLite 3 列为长期数据集存储的推荐格式之一。</p>

<pre><code class="language-mermaid">flowchart TB
    subgraph App["应用程序进程"]
        CODE[业务代码]
        LIB[libsqlite3.so / sqlite3.dll]
        CODE --&gt;|函数调用| LIB
    end
    LIB --&gt;|VFS 读写| FILE[("app.db 单文件")]
    LIB --&gt;|可选| MEM[(":memory:")]
</code></pre>

<blockquote class="prompt-tip">
  <p>提示：<strong>读音没有唯一标准。</strong> 创始人 D. Richard Hipp 本人常读 <strong>/ˌɛsˌkjuːˌɛlˈaɪt/</strong>，即把 <strong>SQL 三个字母分开念</strong>，再接 <strong>-ite</strong>（类似 hematite、quartzite 等矿物名的后缀）；他也演示过 <strong>/ˈsiːkwəˌlaɪt/</strong>（把 SQL 读成 sequel，再接 lite）。他说过「你怎么念都行」——两种都常见。注意：名称灵感来自矿物命名里的 <strong>-ite</strong> 后缀，<strong>并不是</strong>要按锂矿石 trade name「sqlite」那个单词来发音；中文技术圈也常见「SQL 莱特」「西奎莱特」等口语说法。详见 <a href="https://en.wikipedia.org/wiki/SQLite">Wikipedia — SQLite</a> 与 <a href="https://changelog.com/podcast/201">The Changelog 访谈</a>。</p>
</blockquote>

<h2 id="architecture">核心原理与架构</h2>

<p>理解 SQLite 的可靠性讨论，需要先弄清其内部分层。官方 <a href="https://sqlite.org/arch.html">架构说明</a> 与 <a href="https://github.com/sqlite/sqlite">源码树</a> 将核心模块概括如下。</p>

<h3 id="分层结构">分层结构</h3>

<pre><code class="language-mermaid">flowchart TB
    SQL[SQL 语句] --&gt; Parser[SQL 解析器 parse.y]
    Parser --&gt; Planner[查询优化器 where.c]
    Planner --&gt; VDBE[VDBE 虚拟机 vdbe.c]
    VDBE --&gt; BTree[B-Tree 引擎 btree.c]
    BTree --&gt; Pager[Pager 事务层 pager.c]
    Pager --&gt; VFS[OS 抽象层 os_unix.c / os_win.c]
    VFS --&gt; Disk[(数据库文件)]
</code></pre>

<table>
  <thead>
    <tr>
      <th>模块</th>
      <th>职责</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Parser</strong></td>
      <td>将 SQL 解析为语法树；由 Lemon 生成 <code class="language-plaintext highlighter-rouge">parse.c</code></td>
    </tr>
    <tr>
      <td><strong>VDBE</strong></td>
      <td>虚拟机执行编译后的字节码（类似 Prepared Statement 的中间表示）</td>
    </tr>
    <tr>
      <td><strong>B-Tree</strong></td>
      <td>表与索引的存储；每个表/索引对应一棵 B+ 树</td>
    </tr>
    <tr>
      <td><strong>Pager</strong></td>
      <td>页缓存、日志、事务与 ACID；协调回滚日志或 WAL</td>
    </tr>
    <tr>
      <td><strong>VFS</strong></td>
      <td>可插拔虚拟文件系统，屏蔽不同 OS 的 I/O 差异</td>
    </tr>
  </tbody>
</table>

<h3 id="单文件格式">单文件格式</h3>

<p>整个数据库（表定义、索引、数据）通常存放在 <strong>一个跨平台文件</strong> 中（扩展名 <code class="language-plaintext highlighter-rouge">.db</code>、<code class="language-plaintext highlighter-rouge">.sqlite</code>、<code class="language-plaintext highlighter-rouge">.sqlite3</code> 等）。文件头 16 字节魔数为 ASCII <code class="language-plaintext highlighter-rouge">SQLite format 3</code>（见 <a href="https://sqlite.org/fileformat.html">文件格式</a>）。默认页大小 4096 字节；理论单库上限约 <strong>281 TB</strong>。</p>

<p>元数据集中在 <strong><code class="language-plaintext highlighter-rouge">sqlite_schema</code></strong>（旧称 <code class="language-plaintext highlighter-rouge">sql_master</code>）表：记录每张表的名称、根页号、建表 SQL 等。该表本身也是一棵 B+ 树，<strong>第 0 页即其根节点</strong>——这一点在损坏恢复中至关重要（后文详述）。</p>

<h3 id="事务日志与-wal">事务、日志与 WAL</h3>

<p>SQLite 提供 <strong>ACID</strong> 事务。持久化策略由 <code class="language-plaintext highlighter-rouge">PRAGMA synchronous</code> 与日志模式决定：</p>

<table>
  <thead>
    <tr>
      <th>模式</th>
      <th>说明</th>
      <th>并发读</th>
      <th>典型 <code class="language-plaintext highlighter-rouge">synchronous</code></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>DELETE</strong>（默认回滚日志）</td>
      <td>事务提交时写回滚日志，提交后删除</td>
      <td>写时阻塞读</td>
      <td><code class="language-plaintext highlighter-rouge">FULL</code> / <code class="language-plaintext highlighter-rouge">NORMAL</code></td>
    </tr>
    <tr>
      <td><strong>WAL</strong>（Write-Ahead Logging）</td>
      <td>3.7+；变更先写入 <code class="language-plaintext highlighter-rouge">-wal</code> 文件</td>
      <td>读不阻塞写、写不阻塞读</td>
      <td>常配合 <code class="language-plaintext highlighter-rouge">NORMAL</code></td>
    </tr>
  </tbody>
</table>

<pre><code class="language-mermaid">sequenceDiagram
    participant App
    participant Pager
    participant WAL as -wal 文件
    participant DB as .db 主文件

    App-&gt;&gt;Pager: BEGIN / INSERT ...
    Pager-&gt;&gt;WAL: 追加页变更
    App-&gt;&gt;Pager: COMMIT
    Pager-&gt;&gt;WAL: 写帧头、fsync
    Note over Pager,DB: checkpoint 时将 WAL 合并回主文件
</code></pre>

<blockquote class="prompt-warning">
  <p>警告：SQLite 依赖 <strong>文件系统锁</strong> 协调多进程写；高并发写入场景应优先选 MySQL/PostgreSQL 等服务端数据库。官网 <a href="https://sqlite.org/whentouse.html">Appropriate Uses For SQLite</a> 明确列出了适用与不适用的边界。</p>
</blockquote>

<h3 id="类型系统与-sql-兼容性">类型系统与 SQL 兼容性</h3>

<p>SQLite 采用 <strong>动态类型</strong>（值带类型标签），列声明的 <code class="language-plaintext highlighter-rouge">INTEGER</code>/<code class="language-plaintext highlighter-rouge">TEXT</code> 等是 <strong>类型亲和性（affinity）</strong> 而非严格约束——可将字符串写入整型列（会尝试转换）。2021 年起 <strong><code class="language-plaintext highlighter-rouge">STRICT</code> 表</strong> 可启用静态类型检查。</p>

<p>SQL 实现以 PostgreSQL 为参照（「What would PostgreSQL do」），覆盖大部分 SQL-92，但 <strong>不支持</strong> 存储过程、完整 <code class="language-plaintext highlighter-rouge">ALTER TABLE</code>、物化视图、对普通视图的 DML 等（可用 <code class="language-plaintext highlighter-rouge">INSTEAD OF</code> 触发器部分弥补）。完整差异见 <a href="https://sqlite.org/omitted.html">SQL Features That SQLite Does Not Implement</a>。</p>

<p><strong>外键</strong> 自 3.6.19 起支持，但 <strong>默认关闭</strong>，需：</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PRAGMA</span> <span class="n">foreign_keys</span> <span class="o">=</span> <span class="k">ON</span><span class="p">;</span>
</code></pre></div></div>

<h2 id="usage">使用方法</h2>

<h3 id="linux-install">Linux 环境安装</h3>

<p>多数 Linux 发行版仓库中的包名均为 <strong><code class="language-plaintext highlighter-rouge">sqlite</code></strong>（命令行工具）与 <strong><code class="language-plaintext highlighter-rouge">sqlite-devel</code></strong> / <strong><code class="language-plaintext highlighter-rouge">libsqlite3-dev</code></strong>（C 头文件与 <code class="language-plaintext highlighter-rouge">libsqlite3.so</code>，供编译链接）。安装后终端执行 <code class="language-plaintext highlighter-rouge">sqlite3 --version</code> 应输出版本号（如 <code class="language-plaintext highlighter-rouge">3.45.x</code> 或更高；与 <a href="https://sqlite.org/download.html">官网最新版</a> 可能略有差距，一般学习与小工具足够）。</p>

<p><strong>Debian / Ubuntu / Linux Mint</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt update
<span class="nb">sudo </span>apt <span class="nb">install</span> <span class="nt">-y</span> sqlite3 libsqlite3-dev
</code></pre></div></div>

<p><strong>Fedora / RHEL 9+ / CentOS Stream</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>dnf <span class="nb">install</span> <span class="nt">-y</span> sqlite sqlite-devel
</code></pre></div></div>

<p><strong>openSUSE</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>zypper <span class="nb">install</span> <span class="nt">-y</span> sqlite3 sqlite3-devel
</code></pre></div></div>

<p><strong>Arch Linux / Manjaro</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>pacman <span class="nt">-S</span> <span class="nt">--needed</span> sqlite
</code></pre></div></div>

<p><strong>Alpine Linux</strong>（容器镜像中常见）</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apk add sqlite
</code></pre></div></div>

<p><strong>验证安装</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sqlite3 <span class="nt">--version</span>
<span class="c"># 示例输出：3.45.1 2024-01-30 16:01:20 ...</span>
</code></pre></div></div>

<blockquote class="prompt-info">
  <p>信息：若需要 <strong>与官网同步的最新版</strong>（而非发行版冻结版本），可从 <a href="https://sqlite.org/download.html">sqlite.org/download.html</a> 下载 <code class="language-plaintext highlighter-rouge">sqlite-autoconf-*.tar.gz</code> 自行编译，或使用 Snap / 第三方 PPA。生产环境更常见做法是直接依赖发行版包，并通过 <code class="language-plaintext highlighter-rouge">libsqlite3</code> 供应用链接。</p>
</blockquote>

<p><strong>从源码编译（可选）</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-LO</span> https://www.sqlite.org/2024/sqlite-autoconf-3450100.tar.gz
<span class="nb">tar </span>xzf sqlite-autoconf-3450100.tar.gz
<span class="nb">cd </span>sqlite-autoconf-3450100
./configure <span class="nt">--prefix</span><span class="o">=</span>/usr/local
make <span class="nt">-j</span><span class="s2">"</span><span class="si">$(</span><span class="nb">nproc</span><span class="si">)</span><span class="s2">"</span>
<span class="nb">sudo </span>make <span class="nb">install</span>
/usr/local/bin/sqlite3 <span class="nt">--version</span>
</code></pre></div></div>

<h3 id="命令行工具-sqlite3">命令行工具 sqlite3</h3>

<p>发行版附带 <strong><code class="language-plaintext highlighter-rouge">sqlite3</code></strong> 交互式 Shell（见 <a href="https://sqlite.org/cli.html">Command Line Shell</a>）：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 创建或打开数据库</span>
sqlite3 myapp.db

<span class="c"># 常用点命令</span>
.tables
.schema <span class="nb">users</span>
.mode column
.headers on
SELECT <span class="k">*</span> FROM <span class="nb">users </span>LIMIT 5<span class="p">;</span>

<span class="c"># 执行文件中的 SQL</span>
sqlite3 myapp.db &lt; init.sql

<span class="c"># 导出</span>
.dump <span class="o">&gt;</span> backup.sql
</code></pre></div></div>

<h3 id="crud-example">快速入门：增删改查（CRUD）</h3>

<p>下面用一张 <strong><code class="language-plaintext highlighter-rouge">books</code></strong> 表，在 Linux 终端完成建表、插入、查询、更新、删除的完整流程。数据库文件为当前目录下的 <code class="language-plaintext highlighter-rouge">demo.db</code>（首次 <code class="language-plaintext highlighter-rouge">sqlite3 demo.db</code> 时自动创建）。</p>

<p><strong>1. 建表（Create）</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sqlite3 demo.db <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">SQL</span><span class="sh">'
PRAGMA foreign_keys = ON;

CREATE TABLE IF NOT EXISTS books (
    id    INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT    NOT NULL,
    author TEXT   NOT NULL,
    price REAL    DEFAULT 0,
  created_at TEXT DEFAULT (datetime('now', 'localtime'))
);
</span><span class="no">SQL
</span></code></pre></div></div>

<p>或在交互式 Shell 中：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sqlite3 demo.db
</code></pre></div></div>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">.</span><span class="n">headers</span> <span class="k">on</span>
<span class="p">.</span><span class="k">mode</span> <span class="k">column</span>
<span class="p">.</span><span class="k">schema</span> <span class="n">books</span>
</code></pre></div></div>

<p><strong>2. 插入（Insert）</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sqlite3 demo.db <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">SQL</span><span class="sh">'
INSERT INTO books (title, author, price) VALUES
  ('SQLite 权威指南', 'Mike Owens', 59.0),
  ('深入理解计算机系统', 'Randal E. Bryant', 139.0),
  ('数据库系统概念', 'Abraham Silberschatz', 99.0);
</span><span class="no">SQL
</span></code></pre></div></div>

<p>单条插入并返回自增主键：</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">books</span> <span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="n">author</span><span class="p">,</span> <span class="n">price</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="s1">'算法导论'</span><span class="p">,</span> <span class="s1">'Thomas H. Cormen'</span><span class="p">,</span> <span class="mi">128</span><span class="p">.</span><span class="mi">0</span><span class="p">);</span>
<span class="k">SELECT</span> <span class="n">last_insert_rowid</span><span class="p">();</span>
</code></pre></div></div>

<p><strong>3. 查询（Select）</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sqlite3 <span class="nt">-header</span> <span class="nt">-column</span> demo.db <span class="se">\</span>
  <span class="s2">"SELECT id, title, author, price FROM books ORDER BY price DESC;"</span>
</code></pre></div></div>

<p>常见查询写法：</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- 条件过滤</span>
<span class="k">SELECT</span> <span class="n">title</span><span class="p">,</span> <span class="n">price</span> <span class="k">FROM</span> <span class="n">books</span> <span class="k">WHERE</span> <span class="n">price</span> <span class="o">&lt;</span> <span class="mi">100</span><span class="p">;</span>

<span class="c1">-- 模糊匹配</span>
<span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">books</span> <span class="k">WHERE</span> <span class="n">title</span> <span class="k">LIKE</span> <span class="s1">'%SQLite%'</span><span class="p">;</span>

<span class="c1">-- 聚合</span>
<span class="k">SELECT</span> <span class="n">author</span><span class="p">,</span> <span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="k">AS</span> <span class="n">cnt</span><span class="p">,</span> <span class="k">AVG</span><span class="p">(</span><span class="n">price</span><span class="p">)</span> <span class="k">AS</span> <span class="n">avg_price</span>
<span class="k">FROM</span> <span class="n">books</span> <span class="k">GROUP</span> <span class="k">BY</span> <span class="n">author</span><span class="p">;</span>

<span class="c1">-- 分页（LIMIT / OFFSET）</span>
<span class="k">SELECT</span> <span class="n">id</span><span class="p">,</span> <span class="n">title</span> <span class="k">FROM</span> <span class="n">books</span> <span class="k">ORDER</span> <span class="k">BY</span> <span class="n">id</span> <span class="k">LIMIT</span> <span class="mi">2</span> <span class="k">OFFSET</span> <span class="mi">1</span><span class="p">;</span>
</code></pre></div></div>

<p><strong>4. 更新（Update）</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sqlite3 demo.db <span class="se">\</span>
  <span class="s2">"UPDATE books SET price = price * 0.9 WHERE title = 'SQLite 权威指南';"</span>
</code></pre></div></div>

<p>带条件的安全更新（先 <code class="language-plaintext highlighter-rouge">SELECT</code> 确认影响行数）：</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">BEGIN</span><span class="p">;</span>
<span class="k">UPDATE</span> <span class="n">books</span> <span class="k">SET</span> <span class="n">price</span> <span class="o">=</span> <span class="mi">88</span><span class="p">.</span><span class="mi">0</span> <span class="k">WHERE</span> <span class="n">id</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
<span class="k">SELECT</span> <span class="n">changes</span><span class="p">();</span>   <span class="c1">-- 返回上一语句修改的行数</span>
<span class="k">COMMIT</span><span class="p">;</span>
</code></pre></div></div>

<p><strong>5. 删除（Delete）</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sqlite3 demo.db <span class="se">\</span>
  <span class="s2">"DELETE FROM books WHERE id = 3;"</span>
</code></pre></div></div>

<p>删除前建议预览：</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">id</span><span class="p">,</span> <span class="n">title</span> <span class="k">FROM</span> <span class="n">books</span> <span class="k">WHERE</span> <span class="n">price</span> <span class="o">&gt;</span> <span class="mi">120</span><span class="p">;</span>
<span class="k">DELETE</span> <span class="k">FROM</span> <span class="n">books</span> <span class="k">WHERE</span> <span class="n">price</span> <span class="o">&gt;</span> <span class="mi">120</span><span class="p">;</span>
</code></pre></div></div>

<p><strong>6. 一条命令串联演示</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">rm</span> <span class="nt">-f</span> demo.db
sqlite3 demo.db <span class="s2">"
  CREATE TABLE books (id INTEGER PRIMARY KEY, title TEXT, qty INTEGER);
  INSERT INTO books VALUES (1, 'Item A', 10);
  INSERT INTO books VALUES (2, 'Item B', 5);
  SELECT * FROM books;
  UPDATE books SET qty = qty - 1 WHERE id = 1;
  DELETE FROM books WHERE qty = 0;
  SELECT '--- after changes ---';
  SELECT * FROM books;
"</span>
</code></pre></div></div>

<pre><code class="language-mermaid">flowchart LR
    C[CREATE TABLE] --&gt; I[INSERT]
    I --&gt; R[SELECT]
    R --&gt; U[UPDATE]
    U --&gt; D[DELETE]
</code></pre>

<blockquote class="prompt-tip">
  <p>提示：批量写入时用 <code class="language-plaintext highlighter-rouge">BEGIN;</code> … <code class="language-plaintext highlighter-rouge">COMMIT;</code> 包成事务，比逐条自动提交快一个数量级以上。命令行中可用 <code class="language-plaintext highlighter-rouge">sqlite3 demo.db ".read script.sql"</code> 执行脚本文件。</p>
</blockquote>

<h3 id="连接字符串与内存库">连接字符串与内存库</h3>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>文件库：sqlite3_open("path/to/app.db")
内存库：sqlite3_open(":memory:")
临时库：sqlite3_open("")   # 连接关闭后自动删除
</code></pre></div></div>

<h3 id="编程语言绑定">编程语言绑定</h3>

<p>主流语言均有官方或社区绑定：Python 标准库 <code class="language-plaintext highlighter-rouge">sqlite3</code>、Node <code class="language-plaintext highlighter-rouge">better-sqlite3</code>/<code class="language-plaintext highlighter-rouge">sql.js</code>、Go <code class="language-plaintext highlighter-rouge">database/sql</code> + <code class="language-plaintext highlighter-rouge">modernc.org/sqlite</code>、Java JDBC（<code class="language-plaintext highlighter-rouge">org.xerial:sqlite-jdbc</code>）、.NET <code class="language-plaintext highlighter-rouge">Microsoft.Data.Sqlite</code> 等。典型模式为 <strong>预编译语句（Prepared Statement）</strong> + 参数绑定，避免 SQL 注入并复用查询计划。</p>

<p><strong>Python 示例</strong>：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">sqlite3</span>

<span class="n">conn</span> <span class="o">=</span> <span class="n">sqlite3</span><span class="p">.</span><span class="nf">connect</span><span class="p">(</span><span class="sh">"</span><span class="s">myapp.db</span><span class="sh">"</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="nf">execute</span><span class="p">(</span><span class="sh">"</span><span class="s">PRAGMA foreign_keys = ON</span><span class="sh">"</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="nf">execute</span><span class="p">(</span><span class="sh">"""</span><span class="s">
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY,
        name TEXT NOT NULL,
        email TEXT UNIQUE
    ) STRICT
</span><span class="sh">"""</span><span class="p">)</span>

<span class="n">conn</span><span class="p">.</span><span class="nf">execute</span><span class="p">(</span><span class="sh">"</span><span class="s">INSERT INTO users(name, email) VALUES (?, ?)</span><span class="sh">"</span><span class="p">,</span> <span class="p">(</span><span class="sh">"</span><span class="s">Alice</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">a@example.com</span><span class="sh">"</span><span class="p">))</span>
<span class="n">conn</span><span class="p">.</span><span class="nf">commit</span><span class="p">()</span>

<span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">conn</span><span class="p">.</span><span class="nf">execute</span><span class="p">(</span><span class="sh">"</span><span class="s">SELECT id, name FROM users WHERE name LIKE ?</span><span class="sh">"</span><span class="p">,</span> <span class="p">(</span><span class="sh">"</span><span class="s">A%</span><span class="sh">"</span><span class="p">,)):</span>
    <span class="nf">print</span><span class="p">(</span><span class="n">row</span><span class="p">)</span>

<span class="n">conn</span><span class="p">.</span><span class="nf">close</span><span class="p">()</span>
</code></pre></div></div>

<p><strong>C 示例（核心 API）</strong>：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sqlite3</span> <span class="o">*</span><span class="n">db</span><span class="p">;</span>
<span class="n">sqlite3_open</span><span class="p">(</span><span class="s">"myapp.db"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">db</span><span class="p">);</span>
<span class="n">sqlite3_exec</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="s">"CREATE TABLE t(x INT);"</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>

<span class="n">sqlite3_stmt</span> <span class="o">*</span><span class="n">stmt</span><span class="p">;</span>
<span class="n">sqlite3_prepare_v2</span><span class="p">(</span><span class="n">db</span><span class="p">,</span> <span class="s">"INSERT INTO t VALUES(?)"</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">stmt</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="n">sqlite3_bind_int</span><span class="p">(</span><span class="n">stmt</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">42</span><span class="p">);</span>
<span class="n">sqlite3_step</span><span class="p">(</span><span class="n">stmt</span><span class="p">);</span>  <span class="cm">/* SQLITE_DONE */</span>
<span class="n">sqlite3_finalize</span><span class="p">(</span><span class="n">stmt</span><span class="p">);</span>
<span class="n">sqlite3_close</span><span class="p">(</span><span class="n">db</span><span class="p">);</span>
</code></pre></div></div>

<h3 id="常用-pragma">常用 PRAGMA</h3>

<table>
  <thead>
    <tr>
      <th>PRAGMA</th>
      <th>作用</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">journal_mode=WAL</code></td>
      <td>启用 WAL，改善并发读</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">synchronous=FULL</code> / <code class="language-plaintext highlighter-rouge">NORMAL</code> / <code class="language-plaintext highlighter-rouge">OFF</code></td>
      <td>控制 fsync 强度与性能权衡</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">foreign_keys=ON</code></td>
      <td>启用外键约束</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">busy_timeout=5000</code></td>
      <td>锁冲突时等待毫秒数</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">integrity_check</code></td>
      <td>完整性检测</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">quick_check</code></td>
      <td>更快的抽样完整性检查</td>
    </tr>
  </tbody>
</table>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PRAGMA</span> <span class="n">journal_mode</span><span class="o">=</span><span class="n">WAL</span><span class="p">;</span>
<span class="n">PRAGMA</span> <span class="n">synchronous</span><span class="o">=</span><span class="n">NORMAL</span><span class="p">;</span>
<span class="n">PRAGMA</span> <span class="n">busy_timeout</span><span class="o">=</span><span class="mi">5000</span><span class="p">;</span>
<span class="n">PRAGMA</span> <span class="n">integrity_check</span><span class="p">;</span>
</code></pre></div></div>

<h3 id="扩展能力">扩展能力</h3>

<ul>
  <li><strong>FTS5</strong>：全文检索（<a href="https://sqlite.org/fts5.html">FTS5 Extension</a>）</li>
  <li><strong>JSON / JSONB</strong>：<code class="language-plaintext highlighter-rouge">json1</code> 默认启用；JSONB 减少重复解析（<a href="https://sqlite.org/json1.html">JSON Functions</a>）</li>
  <li><strong>R-Tree / Geopoly</strong>：空间索引</li>
  <li><strong>sqlite3_rsync</strong>：3.50+ 增量同步工具</li>
</ul>

<h2 id="sqlite-vs-mysql">与 MySQL 功能对比</h2>

<p>下表从工程选型角度对比两者；MySQL 指 InnoDB 引擎下的常见部署。</p>

<table>
  <thead>
    <tr>
      <th>能力</th>
      <th>SQLite</th>
      <th>MySQL</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>架构</strong></td>
      <td>嵌入式库，单文件</td>
      <td>客户端–服务器，多实例/主从</td>
    </tr>
    <tr>
      <td><strong>并发写</strong></td>
      <td>单写者（WAL 下多读）</td>
      <td>行级锁，高并发 OLTP</td>
    </tr>
    <tr>
      <td><strong>网络访问</strong></td>
      <td>无（需应用内嵌或中间层）</td>
      <td>原生 TCP，远程连接</td>
    </tr>
    <tr>
      <td><strong>用户与权限</strong></td>
      <td>文件系统权限</td>
      <td><code class="language-plaintext highlighter-rouge">GRANT</code>、角色、SSL</td>
    </tr>
    <tr>
      <td><strong>复制 / 高可用</strong></td>
      <td>无内置（需 litestream、应用层同步）</td>
      <td>主从、Group Replication、InnoDB Cluster</td>
    </tr>
    <tr>
      <td><strong>分区 / 分库</strong></td>
      <td>单文件，ATTACH 多库</td>
      <td>分区表、分片中间件</td>
    </tr>
    <tr>
      <td><strong>存储过程 / 触发器</strong></td>
      <td>无存储过程；触发器部分支持</td>
      <td>完整存储过程、触发器</td>
    </tr>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">ALTER TABLE</code></strong></td>
      <td>能力有限（3.35+ 有 DROP COLUMN 等）</td>
      <td>功能完整</td>
    </tr>
    <tr>
      <td><strong>数据类型</strong></td>
      <td>动态 + 可选 STRICT</td>
      <td>严格静态类型</td>
    </tr>
    <tr>
      <td><strong>全文搜索</strong></td>
      <td>FTS5 扩展</td>
      <td><code class="language-plaintext highlighter-rouge">FULLTEXT</code> 索引</td>
    </tr>
    <tr>
      <td><strong>JSON</strong></td>
      <td>一等公民函数 + JSONB</td>
      <td><code class="language-plaintext highlighter-rouge">JSON</code> 类型与函数</td>
    </tr>
    <tr>
      <td><strong>备份</strong></td>
      <td>文件拷贝 + <code class="language-plaintext highlighter-rouge">.backup</code> API；热备需注意 WAL</td>
      <td><code class="language-plaintext highlighter-rouge">mysqldump</code>、物理备份、binlog PITR</td>
    </tr>
    <tr>
      <td><strong>监控</strong></td>
      <td>轻量，无内置仪表盘</td>
      <td>Performance Schema、慢查询日志</td>
    </tr>
    <tr>
      <td><strong>许可</strong></td>
      <td>公有领域</td>
      <td>GPL / 商业许可</td>
    </tr>
    <tr>
      <td><strong>适用规模</strong></td>
      <td>单用户～低并发、本地、边缘</td>
      <td>中大型 Web、多租户 SaaS</td>
    </tr>
  </tbody>
</table>

<pre><code class="language-mermaid">flowchart LR
    subgraph SQLite适用
        S1[移动 App 本地存储]
        S2[桌面/嵌入式配置]
        S3[单元测试替身]
        S4[分析型单机 ETL 缓存]
    end
    subgraph MySQL适用
        M1[多用户 Web 后端]
        M2[需要远程连接与权限隔离]
        M3[主从与高可用]
        M4[高并发写入 OLTP]
    end
</code></pre>

<blockquote class="prompt-info">
  <p>信息：Django、Rails 等框架可用 SQLite 作为开发默认库，生产环境通常切换至 PostgreSQL/MySQL。SQLite 官方亦强调：<strong>不要把它当作通过网络挂载的 NFS 上的多写数据库</strong>。</p>
</blockquote>

<h2 id="data-loss-risks">数据丢失与损坏风险</h2>

<p>SQLite 在断电、磁盘满、同步策略不当等场景下仍可能损坏。官网 <a href="https://sqlite.org/howtocorrupt.html">How To Corrupt An SQLite Database File</a> 列举了典型原因；<a href="https://cloud.tencent.com/developer/article/1005623">微信终端团队</a> 在移动端大规模实践中将其归纳为三类主因。</p>

<h3 id="常见损坏原因">常见损坏原因</h3>

<table>
  <thead>
    <tr>
      <th>原因</th>
      <th>机制</th>
      <th>典型场景</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>存储空间不足</strong></td>
      <td>分配新页或扩展 WAL 失败，文件处于不一致状态</td>
      <td>手机存储满、容器卷满</td>
    </tr>
    <tr>
      <td><strong>设备断电 / 强杀</strong></td>
      <td>提交过程中未完整 fsync</td>
      <td>笔记本断电、移动端 OOM 杀进程</td>
    </tr>
    <tr>
      <td><strong>文件 sync 失败</strong></td>
      <td><code class="language-plaintext highlighter-rouge">synchronous</code> 过低或 OS/存储重排序</td>
      <td>默认 <code class="language-plaintext highlighter-rouge">NORMAL</code> + 部分移动文件系统</td>
    </tr>
    <tr>
      <td><strong>文件锁缺陷</strong></td>
      <td>NFS、部分网络文件系统锁语义不完整</td>
      <td>多主机共享 <code class="language-plaintext highlighter-rouge">.db</code></td>
    </tr>
    <tr>
      <td><strong>误操作</strong></td>
      <td>覆盖、截断、用文本编辑器改二进制</td>
      <td>运维事故</td>
    </tr>
    <tr>
      <td><strong>内存损坏</strong></td>
      <td>罕见；官方测试覆盖断电模拟</td>
      <td>硬件故障</td>
    </tr>
  </tbody>
</table>

<p>微信团队曾报告约 <strong>0.02%</strong> 的现网损坏率，且早期官方 <code class="language-plaintext highlighter-rouge">.recover</code> 类算法成功率仅约 <strong>30%</strong>——对聊天记录等无法云端重建的数据，这意味着必须 <strong>预防 + 工程化修复</strong> 双管齐下。</p>

<h3 id="与丢失相关的非损坏场景">与「丢失」相关的非损坏场景</h3>

<ul>
  <li><strong>未提交事务</strong>：进程崩溃时自动回滚，数据回到上一提交点（ACID 保证）。</li>
  <li><strong>WAL 未 checkpoint</strong>：主 <code class="language-plaintext highlighter-rouge">.db</code> 可能滞后，但 <code class="language-plaintext highlighter-rouge">-wal</code>/<code class="language-plaintext highlighter-rouge">-shm</code> 齐全时仍一致；<strong>只拷贝 <code class="language-plaintext highlighter-rouge">.db</code> 而遗漏 WAL</strong> 会造成「看起来丢数据」。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">synchronous=OFF</code></strong>：最快，但 OS 崩溃可能损坏库；生产环境慎用。</li>
  <li><strong>删除库文件</strong>：无备份则无法恢复——SQLite 不提供 binlog 式时间点恢复。</li>
</ul>

<h2 id="integrity-check">完整性检测</h2>

<p>定期或故障后应运行：</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PRAGMA</span> <span class="n">integrity_check</span><span class="p">;</span>
<span class="c1">-- 或更快：</span>
<span class="n">PRAGMA</span> <span class="n">quick_check</span><span class="p">;</span>
</code></pre></div></div>

<p>命令行：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sqlite3 corrupted.db <span class="s2">"PRAGMA integrity_check;"</span>
</code></pre></div></div>

<p>返回 <code class="language-plaintext highlighter-rouge">ok</code> 表示未发现结构问题；否则列出损坏的页或 B-Tree 节点。</p>

<h2 id="backup">备份策略</h2>

<h3 id="1-在线热备推荐-api">1. 在线热备（推荐 API）</h3>

<p>使用 <strong>Backup API</strong> 或 Shell 的 <code class="language-plaintext highlighter-rouge">.backup</code>，在 WAL 模式下可安全复制逻辑一致快照：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sqlite3 source.db <span class="s2">".backup 'backup.db'"</span>
</code></pre></div></div>

<p>或在 SQL 中：</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">.</span><span class="n">backup</span> <span class="n">main</span> <span class="s1">'backup.db'</span>
</code></pre></div></div>

<h3 id="2-文件级拷贝">2. 文件级拷贝</h3>

<p>在 <strong>无活跃写入</strong> 或已执行 <code class="language-plaintext highlighter-rouge">PRAGMA wal_checkpoint(FULL)</code> 后，可复制 <code class="language-plaintext highlighter-rouge">app.db</code> 及（若存在）<code class="language-plaintext highlighter-rouge">-wal</code>、<code class="language-plaintext highlighter-rouge">-shm</code>。切勿在未知写入状态下只复制主文件。</p>

<h3 id="3-逻辑导出">3. 逻辑导出</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sqlite3 myapp.db .dump <span class="o">&gt;</span> myapp.sql
</code></pre></div></div>

<p>适合跨版本迁移；大库恢复较慢。</p>

<h3 id="4-持续保护">4. 持续保护</h3>

<ul>
  <li><a href="https://litestream.io/">Litestream</a>：将变更流式复制到 S3 等对象存储</li>
  <li>应用层双写、定时 <code class="language-plaintext highlighter-rouge">.backup</code></li>
  <li>微信方案：<strong>预分配备份空间</strong>、<strong>双份元数据备份</strong> + CRC（见下节）</li>
</ul>

<h2 id="recovery">恢复方法</h2>

<h3 id="官方-recover-与-sqlite3_recover">官方 <code class="language-plaintext highlighter-rouge">.recover</code> 与 <code class="language-plaintext highlighter-rouge">sqlite3_recover</code></h3>

<p>SQLite 3.29+ 命令行提供 <strong><code class="language-plaintext highlighter-rouge">.recover</code></strong>，尝试扫描所有页、重建 <code class="language-plaintext highlighter-rouge">sqlite_schema</code> 并导出可读的表数据：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sqlite3 corrupted.db <span class="s2">".recover"</span> | sqlite3 recovered.db
</code></pre></div></div>

<p>其思路与官网修复算法一致：从 <code class="language-plaintext highlighter-rouge">sqlite_schema</code> 读取表根节点，逐表 <code class="language-plaintext highlighter-rouge">SELECT</code> 能读多少算多少，写入新库。<strong>弱点</strong>：<code class="language-plaintext highlighter-rouge">sqlite_schema</code> 根在第 0 页，若元数据区损坏则整库失败——这正是微信团队指出的官方成功率低的原因。</p>

<h3 id="使用-sqldiff-与重建">使用 <code class="language-plaintext highlighter-rouge">sqldiff</code> 与重建</h3>

<p>若部分表完好，可 <code class="language-plaintext highlighter-rouge">.dump</code> 单表或对比差异：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sqldiff old.db new.db
</code></pre></div></div>

<h3 id="工程化增强元数据备份微信实践">工程化增强：元数据备份（微信实践）</h3>

<p><a href="https://cloud.tencent.com/developer/article/1005623">微信 SQLite 数据库修复实践</a> 的核心思路可抽象为：</p>

<pre><code class="language-mermaid">flowchart TB
    subgraph 预防
        P1[synchronous=FULL]
        P2[iOS: PRAGMA fullfsync=1]
        P3[空间监控与清理]
    end
    subgraph 元数据保护
        M[定时轮询 sqlite_schema]
        M --&gt; B1[备份文件 A + CRC]
        M --&gt; B2[备份文件 B + CRC]
        B1 --&gt;|交替覆盖| ROT[双缓冲轮换]
        B2 --&gt; ROT
    end
    subgraph 修复
        COR[损坏库] --&gt; USE[用有效备份还原 schema]
        USE --&gt; SCAN[按根页扫描各表 B-Tree]
        SCAN --&gt; NEW[写入新 DB]
    end
    预防 --&gt; COR
    元数据保护 --&gt; 修复
</code></pre>

<p>要点：</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">sqlite_schema</code> 仅在 CREATE/DROP TABLE 时变化</strong>，普通 INSERT 不改变根页，可定时轮询备份。</li>
  <li><strong>双备份 + CRC</strong>：优先覆盖已损坏副本；新备份未校验前仍用旧有效副本（类似 MVCC 思路）。</li>
  <li><strong>预分配 32KB（按 32KB 倍数增长）</strong>，避免空间不足时备份扩展失败。</li>
  <li>配合 <strong>WCDB</strong>（WeChat Database）等封装，将上述策略产品化。</li>
</ol>

<h3 id="第三方与取证工具">第三方与取证工具</h3>

<ul>
  <li><strong>sqlite3_analyzer</strong>：分析页布局与空闲空间</li>
  <li>商业/取证工具：对严重损坏页进行 carving（按 B-Tree 结构盲扫）</li>
  <li>只读挂载：部分情况下 <code class="language-plaintext highlighter-rouge">PRAGMA read_uncommitted</code> 无法修复物理损坏，仍需 <code class="language-plaintext highlighter-rouge">.recover</code> 或专用工具</li>
</ul>

<h3 id="恢复后验证">恢复后验证</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sqlite3 recovered.db <span class="s2">"PRAGMA integrity_check;"</span>
sqlite3 recovered.db <span class="s2">"SELECT COUNT(*) FROM critical_table;"</span>
</code></pre></div></div>

<p>与备份或业务日志交叉核对行数、校验和。</p>

<h2 id="best-practices">最佳实践</h2>

<ol>
  <li><strong>选对场景</strong>：单机、低并发、嵌入式优先 SQLite；多写、远程、HA 用 MySQL/PostgreSQL。</li>
  <li><strong>启用 WAL + 合理 synchronous</strong>：桌面/服务器常 <code class="language-plaintext highlighter-rouge">journal_mode=WAL</code> + <code class="language-plaintext highlighter-rouge">synchronous=NORMAL</code>；移动关键数据可参考微信的 <code class="language-plaintext highlighter-rouge">FULL</code> + <code class="language-plaintext highlighter-rouge">fullfsync</code>。</li>
  <li><strong>永远打开外键</strong>：<code class="language-plaintext highlighter-rouge">PRAGMA foreign_keys=ON</code>（每个连接都要设）。</li>
  <li><strong>使用预编译语句与事务</strong>：批量写入包在 <code class="language-plaintext highlighter-rouge">BEGIN…COMMIT</code> 中，减少 fsync 次数。</li>
  <li><strong>设置 busy_timeout</strong>：多线程/多进程访问同一文件时减少 <code class="language-plaintext highlighter-rouge">SQLITE_BUSY</code>。</li>
  <li><strong>定期 <code class="language-plaintext highlighter-rouge">.backup</code> 与 integrity_check</strong>：自动化任务，而非依赖「拷贝还在用的 db 文件」。</li>
  <li><strong>不要把库放在 NFS 上多进程写</strong>；若必须共享，用服务端数据库或 Litestream 等单写者复制。</li>
  <li><strong>升级前备份</strong>；关注 <a href="https://sqlite.org/changes.html">发布说明</a> 中文件格式变更。</li>
  <li><strong>关键元数据双备份</strong>：对「丢不起」的嵌入式场景，借鉴微信的 schema 备份策略。</li>
  <li><strong>测试断电恢复</strong>：官方测试套件模拟 power loss；业务侧可用副本做 kill -9 演练。</li>
</ol>

<h2 id="caveats">注意事项</h2>

<blockquote class="prompt-danger">
  <p>危险：<code class="language-plaintext highlighter-rouge">PRAGMA synchronous=OFF</code> 与忽略 <code class="language-plaintext highlighter-rouge">SQLITE_BUSY</code> 会显著提高损坏概率；仅在可重建的临时数据上使用。</p>
</blockquote>

<ul>
  <li><strong>线程安全</strong>：SQLite 编译为 <code class="language-plaintext highlighter-rouge">SQLITE_THREADSAFE</code>；多线程共享连接需序列化访问或使用连接池每线程一个连接。</li>
  <li><strong>大小限制</strong>：默认 <code class="language-plaintext highlighter-rouge">SQLITE_MAX_LENGTH</code> 等编译上限；单表过大时 VACUUM 与查询计划需关注。</li>
  <li><strong>Unicode</strong>：默认大小写折叠对非 ASCII 不完整，可选 ICU 扩展。</li>
  <li><strong>许可</strong>：SQLite 为公有领域，但官方 <strong>不接受未签署贡献声明的外部 patch</strong>（见 <a href="https://sqlite.org/copyright.html">Copyright</a>）。</li>
  <li><strong>GitHub 镜像</strong>：正式开发以 <a href="https://sqlite.org/src">Fossil</a> 为准，Git 仅为镜像。</li>
</ul>

<h2 id="references">参考资源</h2>

<h3 id="官方文档">官方文档</h3>

<table>
  <thead>
    <tr>
      <th>主题</th>
      <th>链接</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>SQLite 首页</td>
      <td><a href="https://sqlite.org/">sqlite.org</a></td>
    </tr>
    <tr>
      <td>入门指南</td>
      <td><a href="https://sqlite.org/getstarted.html">sqlite.org/getstarted.html</a></td>
    </tr>
    <tr>
      <td>下载与安装</td>
      <td><a href="https://sqlite.org/download.html">sqlite.org/download.html</a></td>
    </tr>
    <tr>
      <td>架构概览</td>
      <td><a href="https://sqlite.org/arch.html">sqlite.org/arch.html</a></td>
    </tr>
    <tr>
      <td>文件格式</td>
      <td><a href="https://sqlite.org/fileformat.html">sqlite.org/fileformat.html</a></td>
    </tr>
    <tr>
      <td>何时使用 SQLite</td>
      <td><a href="https://sqlite.org/whentouse.html">sqlite.org/whentouse.html</a></td>
    </tr>
    <tr>
      <td>损坏原因说明</td>
      <td><a href="https://sqlite.org/howtocorrupt.html">sqlite.org/howtocorrupt.html</a></td>
    </tr>
    <tr>
      <td>命令行 Shell</td>
      <td><a href="https://sqlite.org/cli.html">sqlite.org/cli.html</a></td>
    </tr>
    <tr>
      <td>C/C++ API</td>
      <td><a href="https://sqlite.org/cintro.html">sqlite.org/cintro.html</a></td>
    </tr>
    <tr>
      <td>发布与变更</td>
      <td><a href="https://sqlite.org/changes.html">sqlite.org/changes.html</a></td>
    </tr>
    <tr>
      <td>源码仓库（Fossil）</td>
      <td><a href="https://sqlite.org/src">sqlite.org/src</a></td>
    </tr>
  </tbody>
</table>

<h3 id="社区与延伸阅读">社区与延伸阅读</h3>

<table>
  <thead>
    <tr>
      <th>主题</th>
      <th>链接</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Wikipedia</td>
      <td><a href="https://en.wikipedia.org/wiki/SQLite">SQLite</a></td>
    </tr>
    <tr>
      <td>GitHub 镜像</td>
      <td><a href="https://github.com/sqlite/sqlite">github.com/sqlite/sqlite</a></td>
    </tr>
    <tr>
      <td>微信 SQLite 修复实践</td>
      <td><a href="https://cloud.tencent.com/developer/article/1005623">腾讯云开发者社区</a></td>
    </tr>
    <tr>
      <td>WCDB</td>
      <td><a href="https://github.com/Tencent/wcdb">Tencent/wcdb</a></td>
    </tr>
    <tr>
      <td>Litestream 实时备份</td>
      <td><a href="https://litestream.io/">litestream.io</a></td>
    </tr>
  </tbody>
</table>

<h3 id="站内相关">站内相关</h3>

<ul>
  <li>分布式协调与存储选型时可对照 <a href="/posts/etcd-in-action/">etcd in Action</a> 中的一致性场景讨论。</li>
</ul>]]></content><author><name></name></author><category term="Database" /><category term="SQLite" /><category term="MySQL" /><category term="数据库" /><category term="嵌入式" /><summary type="html"><![CDATA[深入解析 SQLite 的嵌入式架构与 B-Tree 存储原理，涵盖 Linux 安装、命令行 CRUD、多语言使用、与 MySQL 的功能对比、数据损坏风险及官方与工程化恢复手段。]]></summary></entry><entry><title type="html">Superpowers：让 AI 编程代理具备工程化超能力</title><link href="http://172.19.0.16:8080/posts/superpowers-in-action/" rel="alternate" type="text/html" title="Superpowers：让 AI 编程代理具备工程化超能力" /><published>2026-06-05T12:47:52+08:00</published><updated>2026-06-05T19:34:46+08:00</updated><id>http://172.19.0.16:8080/posts/superpowers-in-action</id><content type="html" xml:base="http://172.19.0.16:8080/posts/superpowers-in-action/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#资源链接" id="markdown-toc-资源链接">资源链接</a></li>
  <li><a href="#为什么需要-superpowers" id="markdown-toc-为什么需要-superpowers">为什么需要 Superpowers</a></li>
  <li><a href="#核心概念" id="markdown-toc-核心概念">核心概念</a>    <ul>
      <li><a href="#skills可组合的技能单元" id="markdown-toc-skills可组合的技能单元">Skills：可组合的技能单元</a></li>
      <li><a href="#harness承载环境" id="markdown-toc-harness承载环境">Harness：承载环境</a></li>
      <li><a href="#设计产物目录约定" id="markdown-toc-设计产物目录约定">设计产物目录约定</a></li>
    </ul>
  </li>
  <li><a href="#标准工作流" id="markdown-toc-标准工作流">标准工作流</a>    <ul>
      <li><a href="#1-brainstorming--先设计后写码" id="markdown-toc-1-brainstorming--先设计后写码">1. brainstorming — 先设计，后写码</a></li>
      <li><a href="#2-using-git-worktrees--隔离开发" id="markdown-toc-2-using-git-worktrees--隔离开发">2. using-git-worktrees — 隔离开发</a></li>
      <li><a href="#3-writing-plans--可执行的实现计划" id="markdown-toc-3-writing-plans--可执行的实现计划">3. writing-plans — 可执行的实现计划</a></li>
      <li><a href="#4-执行计划两种方式" id="markdown-toc-4-执行计划两种方式">4. 执行计划：两种方式</a>        <ul>
          <li><a href="#subagent-driven--subagent-driven-development" id="markdown-toc-subagent-driven--subagent-driven-development">Subagent-Driven — <code class="language-plaintext highlighter-rouge">subagent-driven-development</code></a></li>
          <li><a href="#inline-execution--executing-plans" id="markdown-toc-inline-execution--executing-plans">Inline Execution — <code class="language-plaintext highlighter-rouge">executing-plans</code></a></li>
        </ul>
      </li>
      <li><a href="#5-test-driven-development--铁律" id="markdown-toc-5-test-driven-development--铁律">5. test-driven-development — 铁律</a></li>
      <li><a href="#6-requesting-code-review--任务间审查" id="markdown-toc-6-requesting-code-review--任务间审查">6. requesting-code-review — 任务间审查</a></li>
      <li><a href="#7-finishing-a-development-branch--收尾" id="markdown-toc-7-finishing-a-development-branch--收尾">7. finishing-a-development-branch — 收尾</a></li>
    </ul>
  </li>
  <li><a href="#技能库全景" id="markdown-toc-技能库全景">技能库全景</a>    <ul>
      <li><a href="#systematic-debugging-四阶段摘要" id="markdown-toc-systematic-debugging-四阶段摘要">systematic-debugging 四阶段摘要</a></li>
    </ul>
  </li>
  <li><a href="#安装与启用cursor" id="markdown-toc-安装与启用cursor">安装与启用（Cursor）</a></li>
  <li><a href="#常用触发示例可复制口令" id="markdown-toc-常用触发示例可复制口令">常用触发示例（可复制口令）</a>    <ul>
      <li><a href="#brainstorming--梳理需求不要写代码" id="markdown-toc-brainstorming--梳理需求不要写代码">brainstorming — 梳理需求，不要写代码</a></li>
      <li><a href="#writing-plans--写实施计划" id="markdown-toc-writing-plans--写实施计划">writing-plans — 写实施计划</a></li>
      <li><a href="#subagent-driven-development--subagent-driven推荐" id="markdown-toc-subagent-driven-development--subagent-driven推荐">subagent-driven-development — Subagent-Driven（推荐）</a></li>
      <li><a href="#executing-plans--inline-execution当前会话逐步执行" id="markdown-toc-executing-plans--inline-execution当前会话逐步执行">executing-plans — Inline Execution（当前会话逐步执行）</a></li>
      <li><a href="#test-driven-development--先写失败测试" id="markdown-toc-test-driven-development--先写失败测试">test-driven-development — 先写失败测试</a></li>
      <li><a href="#systematic-debugging--排查根因" id="markdown-toc-systematic-debugging--排查根因">systematic-debugging — 排查根因</a></li>
      <li><a href="#requesting-code-review--准备代码审查" id="markdown-toc-requesting-code-review--准备代码审查">requesting-code-review — 准备代码审查</a>        <ul>
          <li><a href="#口令串联示例完整小功能" id="markdown-toc-口令串联示例完整小功能">口令串联示例（完整小功能）</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#端到端使用示例" id="markdown-toc-端到端使用示例">端到端使用示例</a>    <ul>
      <li><a href="#示例-a从零实现-api-限流中间件" id="markdown-toc-示例-a从零实现-api-限流中间件">示例 A：从零实现 API 限流中间件</a></li>
      <li><a href="#示例-b修复空邮箱能通过校验的-bug" id="markdown-toc-示例-b修复空邮箱能通过校验的-bug">示例 B：修复「空邮箱能通过校验」的 Bug</a></li>
      <li><a href="#示例-c大功能拆解--用户后台仪表盘" id="markdown-toc-示例-c大功能拆解--用户后台仪表盘">示例 C：大功能拆解 — 用户后台仪表盘</a></li>
    </ul>
  </li>
  <li><a href="#最佳实践" id="markdown-toc-最佳实践">最佳实践</a>    <ul>
      <li><a href="#对人human-partner" id="markdown-toc-对人human-partner">对人（Human Partner）</a></li>
      <li><a href="#对代理工作流" id="markdown-toc-对代理工作流">对代理工作流</a></li>
      <li><a href="#与自有-cursor-skills-共存" id="markdown-toc-与自有-cursor-skills-共存">与自有 Cursor Skills 共存</a></li>
    </ul>
  </li>
  <li><a href="#注意事项与常见误区" id="markdown-toc-注意事项与常见误区">注意事项与常见误区</a></li>
  <li><a href="#与同类方案对比简表" id="markdown-toc-与同类方案对比简表">与同类方案对比（简表）</a></li>
  <li><a href="#快速上手检查清单" id="markdown-toc-快速上手检查清单">快速上手检查清单</a></li>
  <li><a href="#参考资源" id="markdown-toc-参考资源">参考资源</a></li>
</ul>

<blockquote>
  <p><a href="https://github.com/obra/superpowers">Superpowers</a> 由 Jesse Vincent（<a href="https://primeradiant.com">Prime Radiant</a>）发起，是一套面向 <strong>AI 编程代理（Coding Agent）</strong> 的<strong>可组合技能（Skills）框架</strong>与<strong>软件工程方法论</strong>。它不把「写代码」当作起点，而是把<strong>需求澄清 → 设计评审 → 计划拆解 → TDD 实现 → 子代理协作 → 代码审查 → 分支收尾</strong>串成<strong>强制工作流</strong>，让代理在数小时内也能稳定推进复杂任务。本文基于官方仓库 v5.1.0 与公开技能文档整理，侧重 <strong>Cursor</strong> 场景下的上手与最佳实践。</p>
</blockquote>

<p>若你已读过站内 <a href="/posts/agentic-design-patterns-reading/">Agentic Design Patterns 读书笔记</a> 或 <a href="/posts/ai-coding-in-action/">AI Coding in Action</a>，可把 Superpowers 理解为：<strong>把书中「规划—工具—记忆—协作」等模式，落成一套可安装、可自动触发的代理技能库</strong>。</p>

<h1 id="资源链接">资源链接</h1>

<table>
  <thead>
    <tr>
      <th style="text-align: left">说明</th>
      <th style="text-align: left">链接</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">官方仓库</td>
      <td style="text-align: left"><a href="https://github.com/obra/superpowers">obra/superpowers</a></td>
    </tr>
    <tr>
      <td style="text-align: left">首发公告</td>
      <td style="text-align: left"><a href="https://blog.fsck.com/2025/10/09/superpowers/">Superpowers — Jesse Vincent</a></td>
    </tr>
    <tr>
      <td style="text-align: left">最新 Release</td>
      <td style="text-align: left"><a href="https://github.com/obra/superpowers/releases">v5.1.0</a></td>
    </tr>
    <tr>
      <td style="text-align: left">Issues / 社区</td>
      <td style="text-align: left"><a href="https://github.com/obra/superpowers/issues">GitHub Issues</a>、<a href="https://discord.gg/35wsABTejz">Discord</a></td>
    </tr>
    <tr>
      <td style="text-align: left">Cursor 插件</td>
      <td style="text-align: left">在 Agent 对话中执行 <code class="language-plaintext highlighter-rouge">/add-plugin superpowers</code></td>
    </tr>
  </tbody>
</table>

<h1 id="为什么需要-superpowers">为什么需要 Superpowers</h1>

<p>普通 AI 编程对话常见三类问题：</p>

<ol>
  <li><strong>过早写代码</strong>：需求还没对齐就开始改文件，返工成本高。</li>
  <li><strong>缺乏可验证性</strong>：「看起来能跑」就宣布完成，没有测试与根因分析。</li>
  <li><strong>上下文污染</strong>：长会话里计划漂移、遗漏边界、重复造轮子。</li>
</ol>

<p>Superpowers 的应对策略很直接：<strong>用技能（Skill）覆盖关键决策点，代理在行动前必须先加载对应技能并按清单执行</strong>。技能不是「建议」，而是<strong>强制工作流</strong>——例如实现功能前必须走 TDD 的 RED-GREEN-REFACTOR，修 Bug 前必须完成四阶段根因调查。</p>

<pre><code class="language-mermaid">flowchart LR
    subgraph before["传统 Agent"]
        U1[用户提需求] --&gt; A1[直接改代码]
        A1 --&gt; R1[手动验证]
        R1 --&gt; F1[遗漏边界 / 回归]
    end

    subgraph after["Superpowers Agent"]
        U2[用户提需求] --&gt; S1[brainstorming]
        S1 --&gt; S2[writing-plans]
        S2 --&gt; S3[TDD + subagent]
        S3 --&gt; S4[review + finish branch]
    end
</code></pre>

<p class="prompt-info"><strong>定位</strong>：Superpowers 是<strong>方法论 + 技能包</strong>，不是替代 Cursor / Claude Code / Codex 的 IDE。它运行在各类 <strong>Harness（承载环境）</strong> 之上，通过插件市场分发。</p>

<h1 id="核心概念">核心概念</h1>

<h2 id="skills可组合的技能单元">Skills：可组合的技能单元</h2>

<p>每个 Skill 是一个 <code class="language-plaintext highlighter-rouge">SKILL.md</code> 文件，包含：</p>

<ul>
  <li><strong>YAML 元数据</strong>：<code class="language-plaintext highlighter-rouge">name</code>、<code class="language-plaintext highlighter-rouge">description</code>（描述<strong>何时</strong>应触发）</li>
  <li><strong>正文指令</strong>：步骤清单、反模式、示例、与其他技能的衔接</li>
</ul>

<p>代理在收到用户消息后，按 <code class="language-plaintext highlighter-rouge">using-superpowers</code> 技能的规则：<strong>只要存在约 1% 可能性某技能适用，就必须先调用该技能</strong>，再回复或执行操作。</p>

<p><strong>指令优先级</strong>（高 → 低）：</p>

<ol>
  <li>用户显式指令（<code class="language-plaintext highlighter-rouge">CLAUDE.md</code>、<code class="language-plaintext highlighter-rouge">AGENTS.md</code>、对话中的要求）</li>
  <li>Superpowers 技能</li>
  <li>平台默认系统提示</li>
</ol>

<p>若你在项目里写了「不要用 TDD」，代理会服从项目规则；Superpowers 不会覆盖用户的工程偏好。</p>

<h2 id="harness承载环境">Harness：承载环境</h2>

<p>同一套技能可在多种代理环境中安装，但<strong>各环境需分别安装</strong>：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Harness</th>
      <th style="text-align: left">安装方式（摘要）</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>Cursor</strong></td>
      <td style="text-align: left">Agent 对话：<code class="language-plaintext highlighter-rouge">/add-plugin superpowers</code></td>
    </tr>
    <tr>
      <td style="text-align: left">Claude Code</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">/plugin install superpowers@claude-plugins-official</code></td>
    </tr>
    <tr>
      <td style="text-align: left">Codex CLI / App</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">/plugins</code> 搜索 <code class="language-plaintext highlighter-rouge">superpowers</code></td>
    </tr>
    <tr>
      <td style="text-align: left">Gemini CLI</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">gemini extensions install https://github.com/obra/superpowers</code></td>
    </tr>
    <tr>
      <td style="text-align: left">GitHub Copilot CLI</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">copilot plugin install superpowers@superpowers-marketplace</code></td>
    </tr>
    <tr>
      <td style="text-align: left">OpenCode</td>
      <td style="text-align: left">按 <code class="language-plaintext highlighter-rouge">.opencode/INSTALL.md</code> 指引</td>
    </tr>
  </tbody>
</table>

<h2 id="设计产物目录约定">设计产物目录约定</h2>

<p>Superpowers 默认把协作产物落在仓库内，便于审阅与版本管理：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">路径</th>
      <th style="text-align: left">内容</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">docs/superpowers/specs/</code></td>
      <td style="text-align: left">设计规格（brainstorming 产出）</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">docs/superpowers/plans/</code></td>
      <td style="text-align: left">实现计划（writing-plans 产出）</td>
    </tr>
  </tbody>
</table>

<p>用户可在项目规则中覆盖这些路径。</p>

<h1 id="标准工作流">标准工作流</h1>

<p>官方定义的 <strong>Basic Workflow</strong> 是七步闭环。下图是技能触发顺序与衔接关系：</p>

<pre><code class="language-mermaid">flowchart TB
    start([用户提出构建/修改需求])
    bs[brainstorming&lt;br/&gt;苏格拉底式设计澄清]
    wt[using-git-worktrees&lt;br/&gt;隔离工作区]
    wp[writing-plans&lt;br/&gt; bite-sized 任务计划]
    exec{执行方式}
    sad[subagent-driven-development&lt;br/&gt;Subagent-Driven 推荐&lt;br/&gt;按任务拆子代理 并行/分阶段]
    ep[executing-plans&lt;br/&gt;Inline Execution&lt;br/&gt;当前会话逐步执行]
    tdd[test-driven-development&lt;br/&gt;RED-GREEN-REFACTOR]
    rc[requesting-code-review]
    fin[finishing-a-development-branch&lt;br/&gt;合并/PR/保留/丢弃]

    start --&gt; bs
    bs --&gt;|设计获批| wt
    wt --&gt; wp
    wp --&gt; exec
    exec --&gt;|Subagent-Driven 推荐| sad
    exec --&gt;|Inline Execution| ep
    sad --&gt; tdd
    ep --&gt; tdd
    tdd --&gt; rc
    rc --&gt; fin
    fin --&gt; done([完成])
</code></pre>

<h2 id="1-brainstorming--先设计后写码">1. brainstorming — 先设计，后写码</h2>

<p><strong>触发</strong>：任何「创建功能、改行为、加组件」类创意工作。</p>

<p>关键约束：</p>

<ul>
  <li><strong>禁止</strong>在设计获批前写实现代码或调用实现类技能。</li>
  <li>一次只问<strong>一个问题</strong>，优先选择题。</li>
  <li>提出 <strong>2–3 种方案</strong>并给出推荐。</li>
  <li>设计分节展示，<strong>每节后征求确认</strong>。</li>
  <li>写入 <code class="language-plaintext highlighter-rouge">docs/superpowers/specs/YYYY-MM-DD-&lt;topic&gt;-design.md</code> 并提交。</li>
  <li>终端状态：调用 <code class="language-plaintext highlighter-rouge">writing-plans</code>，而非直接实现。</li>
</ul>

<p class="prompt-warning">反模式：「这个功能太简单，不需要设计」。官方明确指出：Todo 应用、单函数工具、配置改动<strong>都必须</strong>走设计门 —— 简单项目往往因未检验的假设浪费最多时间。</p>

<h2 id="2-using-git-worktrees--隔离开发">2. using-git-worktrees — 隔离开发</h2>

<p>设计通过后，在<strong>新分支 + 独立 worktree</strong> 中开发，避免污染 <code class="language-plaintext highlighter-rouge">main</code>，并验证测试基线干净。与 <code class="language-plaintext highlighter-rouge">finishing-a-development-branch</code> 配合，在合并或丢弃时按来源清理 worktree。</p>

<h2 id="3-writing-plans--可执行的实现计划">3. writing-plans — 可执行的实现计划</h2>

<p>计划面向「<strong>零项目上下文、品味一般、不爱写测试的初级工程师</strong>」仍可执行。要求：</p>

<ul>
  <li>任务粒度 <strong>2–5 分钟一步</strong>（写失败测试 → 跑失败 → 最小实现 → 跑通过 → commit）。</li>
  <li><strong>禁止占位符</strong>（<code class="language-plaintext highlighter-rouge">TBD</code>、<code class="language-plaintext highlighter-rouge">适当处理错误</code>、<code class="language-plaintext highlighter-rouge">类似 Task 3</code> 等）。</li>
  <li>每步含<strong>完整代码</strong>、<strong>精确文件路径</strong>、<strong>命令与期望输出</strong>。</li>
  <li>计划头必须声明执行子技能：<code class="language-plaintext highlighter-rouge">subagent-driven-development</code>（Subagent-Driven，推荐）或 <code class="language-plaintext highlighter-rouge">executing-plans</code>（Inline Execution）。</li>
</ul>

<p>计划保存至 <code class="language-plaintext highlighter-rouge">docs/superpowers/plans/YYYY-MM-DD-&lt;feature&gt;.md</code>。计划完成后，代理会请你二选一执行方式（见下节）。</p>

<h2 id="4-执行计划两种方式">4. 执行计划：两种方式</h2>

<p><code class="language-plaintext highlighter-rouge">writing-plans</code> 产出计划后，进入实现阶段有两种路径，<strong>都在当前 Harness 会话内完成</strong>，区别在于是主代理逐步做，还是按任务派发子代理：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">方式</th>
      <th style="text-align: left">技能</th>
      <th style="text-align: left">特点</th>
      <th style="text-align: left">更适合</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>Subagent-Driven（推荐）</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">subagent-driven-development</code></td>
      <td style="text-align: left">按计划把 Task <strong>拆成多个子代理</strong>执行，可并行或分阶段推进；每 Task 后 <strong>Spec 合规 + 代码质量</strong>双阶段评审</td>
      <td style="text-align: left">跨工具链、目录迁移、协议边界等大改造；任务多、上下文易膨胀</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Inline Execution</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">executing-plans</code></td>
      <td style="text-align: left"><strong>当前会话</strong>里由主代理按 plan <strong>逐步执行</strong>，节奏集中，检查点由你人工把关</td>
      <td style="text-align: left">范围清晰的中小型改动；希望全程在同一对话里跟进度</td>
    </tr>
  </tbody>
</table>

<h3 id="subagent-driven--subagent-driven-development">Subagent-Driven — <code class="language-plaintext highlighter-rouge">subagent-driven-development</code></h3>

<p><strong>推荐执行模式</strong>：每个任务派发<strong>全新子代理</strong>（不继承主会话历史），任务完成后进行<strong>两阶段评审</strong>：</p>

<ol>
  <li><strong>Spec 合规评审</strong>：是否多做/少做？</li>
  <li><strong>代码质量评审</strong>：结构、测试、可维护性</li>
</ol>

<p>子代理状态：<code class="language-plaintext highlighter-rouge">DONE</code> / <code class="language-plaintext highlighter-rouge">DONE_WITH_CONCERNS</code> / <code class="language-plaintext highlighter-rouge">NEEDS_CONTEXT</code> / <code class="language-plaintext highlighter-rouge">BLOCKED</code> —— 主代理必须按状态处理，不能强行跳过。Task 之间可连续推进，无需每个 Task 都停下来问你「是否继续」。</p>

<p class="prompt-tip"><strong>模型选型</strong>：机械性单文件任务用快模型；多文件集成用标准模型；架构与评审用最强大模型 —— 在质量与成本间折中。</p>

<h3 id="inline-execution--executing-plans">Inline Execution — <code class="language-plaintext highlighter-rouge">executing-plans</code></h3>

<p>主代理在<strong>当前会话</strong>中按 Task 顺序执行 plan，在预设<strong>检查点</strong>暂停供你审阅；每步仍遵循 TDD。对话上下文会随 Task 累积，<strong>长计划、多文件改动</strong>时更容易接近上下文上限，需你更频繁地在检查点纠偏。</p>

<h2 id="5-test-driven-development--铁律">5. test-driven-development — 铁律</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
</code></pre></div></div>

<ul>
  <li>先写失败测试并<strong>亲眼看到失败</strong>。</li>
  <li>写<strong>最小</strong>实现使测试通过。</li>
  <li>通过后 REFACTOR；若实现先于测试，<strong>删除实现重来</strong>（不是「保留作参考」）。</li>
</ul>

<h2 id="6-requesting-code-review--任务间审查">6. requesting-code-review — 任务间审查</h2>

<p>按严重程度列出问题；<strong>Critical</strong> 问题阻塞后续任务。</p>

<h2 id="7-finishing-a-development-branch--收尾">7. finishing-a-development-branch — 收尾</h2>

<p>测试全部通过后，检测是否在 worktree，向用户呈现<strong>固定选项</strong>（本地合并 / 推 PR / 保留 / 丢弃），并按选项清理环境。</p>

<h1 id="技能库全景">技能库全景</h1>

<p>除工作流技能外，Superpowers 还提供调试、协作与元技能：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">分类</th>
      <th style="text-align: left">技能</th>
      <th style="text-align: left">用途</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>测试</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">test-driven-development</code></td>
      <td style="text-align: left">RED-GREEN-REFACTOR，含测试反模式参考</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>调试</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">systematic-debugging</code></td>
      <td style="text-align: left">四阶段根因分析</td>
    </tr>
    <tr>
      <td style="text-align: left"> </td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">verification-before-completion</code></td>
      <td style="text-align: left">完成前验证确实修好</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>协作</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">brainstorming</code></td>
      <td style="text-align: left">设计澄清</td>
    </tr>
    <tr>
      <td style="text-align: left"> </td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">writing-plans</code></td>
      <td style="text-align: left">实施计划拆解</td>
    </tr>
    <tr>
      <td style="text-align: left"> </td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">subagent-driven-development</code></td>
      <td style="text-align: left">Subagent-Driven：子代理 + 双阶段评审</td>
    </tr>
    <tr>
      <td style="text-align: left"> </td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">executing-plans</code></td>
      <td style="text-align: left">Inline Execution：当前会话逐步执行</td>
    </tr>
    <tr>
      <td style="text-align: left"> </td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">dispatching-parallel-agents</code></td>
      <td style="text-align: left">并行子代理</td>
    </tr>
    <tr>
      <td style="text-align: left"> </td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">requesting-code-review</code> / <code class="language-plaintext highlighter-rouge">receiving-code-review</code></td>
      <td style="text-align: left">发起与响应评审</td>
    </tr>
    <tr>
      <td style="text-align: left"> </td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">using-git-worktrees</code></td>
      <td style="text-align: left">隔离分支</td>
    </tr>
    <tr>
      <td style="text-align: left"> </td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">finishing-a-development-branch</code></td>
      <td style="text-align: left">合并/PR 决策</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>元</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">using-superpowers</code></td>
      <td style="text-align: left">技能发现与调用规则</td>
    </tr>
    <tr>
      <td style="text-align: left"> </td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">writing-skills</code></td>
      <td style="text-align: left">编写新技能（贡献者向）</td>
    </tr>
  </tbody>
</table>

<h3 id="systematic-debugging-四阶段摘要">systematic-debugging 四阶段摘要</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">阶段</th>
      <th style="text-align: left">关键活动</th>
      <th style="text-align: left">完成标准</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">1. 根因调查</td>
      <td style="text-align: left">读错误信息、稳定复现、查近期变更、跨组件取证</td>
      <td style="text-align: left">弄清 WHAT 与 WHY</td>
    </tr>
    <tr>
      <td style="text-align: left">2. 模式分析</td>
      <td style="text-align: left">找可工作示例、完整读参考实现、对比差异</td>
      <td style="text-align: left">定位差异点</td>
    </tr>
    <tr>
      <td style="text-align: left">3. 假设验证</td>
      <td style="text-align: left">单一假设、最小改动验证</td>
      <td style="text-align: left">假设成立或换新假设</td>
    </tr>
    <tr>
      <td style="text-align: left">4. 实施修复</td>
      <td style="text-align: left">先写失败测试、单点修复、验证</td>
      <td style="text-align: left">Bug 消失且测试通过</td>
    </tr>
  </tbody>
</table>

<p>铁律：<strong>未完成阶段 1，不得提出修复方案</strong>。连续 3 次修复失败时，应质疑架构而非继续打补丁。</p>

<h1 id="安装与启用cursor">安装与启用（Cursor）</h1>

<p>在 <strong>Cursor Agent</strong> 对话中任选其一：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/add-plugin superpowers
</code></pre></div></div>

<p>或在插件市场搜索 <strong>superpowers</strong> 安装。当前插件版本 <strong>5.1.0</strong>，技能目录挂载于 <code class="language-plaintext highlighter-rouge">./skills/</code>。</p>

<p class="prompt-tip">安装后无需手动 <code class="language-plaintext highlighter-rouge">@</code> 每个技能。代理会在对话开始时按 <code class="language-plaintext highlighter-rouge">using-superpowers</code> 自动判断是否加载相关技能。你可通过项目根目录的 <code class="language-plaintext highlighter-rouge">AGENTS.md</code> / <code class="language-plaintext highlighter-rouge">.cursor/rules</code> 补充<strong>项目级约束</strong>（技术栈、目录约定、禁用 TDD 等）。</p>

<p>验证是否生效：提出一个明确的功能需求（见下文示例），观察代理是否<strong>先进入 brainstorming</strong>（提问、给方案）而非直接改文件。</p>

<h1 id="常用触发示例可复制口令">常用触发示例（可复制口令）</h1>

<p>Superpowers 安装后，多数场景会<strong>自动</strong>加载对应技能。若代理「跑偏」——例如跳过设计直接改文件、未看失败信息就猜修复——可用下面口令<strong>显式点名技能</strong>，强制进入正确工作流。在 Cursor Agent 对话中<strong>整段复制发送</strong>即可；把「这个需求 / 这个功能 / 这个计划」换成你的具体描述，必要时附上文件路径、报错日志或 spec/plan 文件链接。</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">技能</th>
      <th style="text-align: left">适用时机</th>
      <th style="text-align: left">可复制口令</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">brainstorming</code></td>
      <td style="text-align: left">新功能、改行为、架构取舍；<strong>禁止先写代码</strong></td>
      <td style="text-align: left">见下方</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">writing-plans</code></td>
      <td style="text-align: left">设计/spec 已确认，要拆成可执行任务</td>
      <td style="text-align: left">见下方</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">subagent-driven-development</code></td>
      <td style="text-align: left">大改造、多 Task；<strong>推荐</strong>，子代理 + 双阶段评审</td>
      <td style="text-align: left">见下方</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">executing-plans</code></td>
      <td style="text-align: left">中小型改动；<strong>当前会话逐步执行</strong>（Inline Execution）</td>
      <td style="text-align: left">见下方</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">test-driven-development</code></td>
      <td style="text-align: left">实现或修 Bug；<strong>必须先写失败测试</strong></td>
      <td style="text-align: left">见下方</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">systematic-debugging</code></td>
      <td style="text-align: left">测试失败、异常行为、构建/集成问题</td>
      <td style="text-align: left">见下方</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">requesting-code-review</code></td>
      <td style="text-align: left">一批改动完成，合并或 PR 前自检</td>
      <td style="text-align: left">见下方</td>
    </tr>
  </tbody>
</table>

<h2 id="brainstorming--梳理需求不要写代码">brainstorming — 梳理需求，不要写代码</h2>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>使用 brainstorming skill，和我一起梳理这个需求，不要直接写代码

【在此描述需求，例如：给博客搜索加按标签过滤，结果要分页】
</code></pre></div></div>

<p><strong>代理应做</strong>：查看项目上下文 → 逐条澄清问题 → 给出 2–3 种方案 → 分节展示设计并征求确认 → 写入 <code class="language-plaintext highlighter-rouge">docs/superpowers/specs/</code>。<strong>不应</strong>创建业务代码或调用实现类技能。</p>

<h2 id="writing-plans--写实施计划">writing-plans — 写实施计划</h2>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>使用 writing-plans skill，为这个改动写一个实施计划

【在此说明已批准的设计或改动范围；若有 spec 文件请写明路径，例如 docs/superpowers/specs/2026-06-05-xxx-design.md】
</code></pre></div></div>

<p><strong>代理应做</strong>：产出 <code class="language-plaintext highlighter-rouge">docs/superpowers/plans/YYYY-MM-DD-&lt;feature&gt;.md</code>，任务粒度 2–5 分钟一步，含完整测试代码、文件路径、命令与期望输出，<strong>无 TBD/占位符</strong>。完成后询问执行方式：<strong>Subagent-Driven（推荐）</strong> 或 <strong>Inline Execution</strong>。</p>

<h2 id="subagent-driven-development--subagent-driven推荐">subagent-driven-development — Subagent-Driven（推荐）</h2>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>使用 subagent-driven-development skill，执行这个计划

【附上计划路径，例如 docs/superpowers/plans/2026-06-05-migration.md】
</code></pre></div></div>

<p><strong>代理应做</strong>：按 Task 派发<strong>全新子代理</strong>（可并行或分阶段），每 Task 完成后 Spec 合规评审 → 代码质量评审 → 下一 Task。适合<strong>跨工具链、目录迁移、协议边界</strong>等任务多、改动面大的计划。</p>

<h2 id="executing-plans--inline-execution当前会话逐步执行">executing-plans — Inline Execution（当前会话逐步执行）</h2>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>使用 executing-plans skill，执行这个计划

【附上计划路径，例如 docs/superpowers/plans/2026-06-05-rate-limit.md】
</code></pre></div></div>

<p><strong>代理应做</strong>：在<strong>当前会话</strong>中由主代理按 Task <strong>逐步</strong>执行，在检查点暂停供你审阅；每步遵循 TDD。节奏集中，适合范围清晰的中型改动；<strong>长任务</strong>时注意上下文压力，必要时改选 Subagent-Driven。</p>

<p class="prompt-info"><strong>怎么选</strong>：大改造、多 Task、怕主会话上下文爆掉 → <strong>Subagent-Driven</strong>；想全程在同一对话里跟紧每一步 → <strong>Inline Execution</strong>（<code class="language-plaintext highlighter-rouge">executing-plans</code>）。</p>

<h2 id="test-driven-development--先写失败测试">test-driven-development — 先写失败测试</h2>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>使用 test-driven-development skill，实现这个功能，先写失败测试

【描述要实现的行为或接口，例如：submitForm 在 email 为空时返回 { error: 'Email required' }】
</code></pre></div></div>

<p><strong>代理应做</strong>：RED（写失败测试）→ 亲眼看 FAIL → GREEN（最小实现）→ 亲眼看 PASS → REFACTOR。若已有「先写的实现代码」，应删除后从测试重来。</p>

<h2 id="systematic-debugging--排查根因">systematic-debugging — 排查根因</h2>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>使用 systematic-debugging skill，排查这个测试失败的根因

【粘贴失败测试名、命令输出或报错栈；说明如何稳定复现】
</code></pre></div></div>

<p><strong>代理应做</strong>：四阶段流程 — 读错误、稳定复现、查近期变更、跨组件取证 → 对比可工作示例 → 单一假设最小验证 → <strong>先写失败测试再修复</strong>。未完成根因调查前<strong>不得</strong>抛出一串「可能修复」。</p>

<h2 id="requesting-code-review--准备代码审查">requesting-code-review — 准备代码审查</h2>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>使用 requesting-code-review skill，帮我准备一次代码审查

【说明审查范围：本次 PR 的改动、或相对 main 的 diff；可附 plan/spec 路径】
</code></pre></div></div>

<p><strong>代理应做</strong>：对照计划与 spec 做合规检查，按严重程度列出问题（Critical 阻塞合并），给出可操作的修改建议，便于你或他人做正式 Review。</p>

<h3 id="口令串联示例完整小功能">口令串联示例（完整小功能）</h3>

<p>按官方推荐顺序，一次小功能可依次发送（每步等上一步完成后再发下一条）：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 使用 brainstorming skill，和我一起梳理这个需求，不要直接写代码
   → 【需求描述】

2. 使用 writing-plans skill，为这个改动写一个实施计划
   → spec 已确认，路径：docs/superpowers/specs/...

3. 使用 subagent-driven-development skill，执行这个计划
   → 计划路径：docs/superpowers/plans/...
   （中小型、希望逐步跟进度时，可改为 executing-plans）

4. 使用 requesting-code-review skill，帮我准备一次代码审查
   → 审查相对 main 的本分支改动
</code></pre></div></div>

<p>修 Bug 时可把第 1–3 步替换为：</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>使用 systematic-debugging skill，排查这个测试失败的根因
→ 【失败信息】

使用 test-driven-development skill，实现这个功能，先写失败测试
→ 【用失败测试锁定的正确行为】
</code></pre></div></div>

<p class="prompt-tip">口令里的技能名与仓库目录一致（<code class="language-plaintext highlighter-rouge">brainstorming</code>、<code class="language-plaintext highlighter-rouge">writing-plans</code> 等）。在 Cursor 中一般<strong>不必</strong> <code class="language-plaintext highlighter-rouge">@</code> 技能文件；点名技能名即可让代理加载对应 <code class="language-plaintext highlighter-rouge">SKILL.md</code>。若项目 <code class="language-plaintext highlighter-rouge">AGENTS.md</code> 与技能冲突（例如禁用 TDD），以<strong>你的项目规则为准</strong>。</p>

<h1 id="端到端使用示例">端到端使用示例</h1>

<p>以下三个场景展示「用户说什么 → 代理应做什么 → 你可如何配合」。</p>

<h2 id="示例-a从零实现-api-限流中间件">示例 A：从零实现 API 限流中间件</h2>

<p><strong>你对 Cursor Agent 说：</strong></p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>给现有 Express 服务加一个可配置的 per-IP 限流中间件，超限返回 429。
</code></pre></div></div>

<p><strong>预期代理行为（Superpowers 启用后）：</strong></p>

<ol>
  <li><strong>brainstorming</strong>
    <ul>
      <li>查看项目结构、<code class="language-plaintext highlighter-rouge">package.json</code>、现有中间件模式。</li>
      <li>逐问：限流窗口（固定窗口 / 滑动窗口 / 令牌桶）？存储（内存 / Redis）？默认配额？</li>
      <li>给出 2–3 方案，例如：
        <ul>
          <li><strong>A</strong>：内存固定窗口（简单，单实例）</li>
          <li><strong>B</strong>：<code class="language-plaintext highlighter-rouge">rate-limiter-flexible</code> + Redis（多实例）</li>
          <li><strong>C</strong>：仅依赖反向代理层限流（运维向）</li>
        </ul>
      </li>
      <li>分节展示设计：接口形状、<code class="language-plaintext highlighter-rouge">req.ip</code> 获取方式、错误体格式、测试策略。</li>
      <li>写入 <code class="language-plaintext highlighter-rouge">docs/superpowers/specs/2026-06-05-rate-limit-design.md</code>。</li>
    </ul>
  </li>
  <li>
    <p><strong>你确认</strong>：「用方案 A，10 req/s，超限 JSON <code class="language-plaintext highlighter-rouge">{ error: "Too Many Requests" }</code>。」</p>
  </li>
  <li><strong>writing-plans</strong> 产出计划片段（示意）：</li>
</ol>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gu">### Task 1: Rate limiter core</span>

<span class="gs">**Files:**</span>
<span class="p">-</span> Create: <span class="sb">`src/middleware/rateLimit.ts`</span>
<span class="p">-</span> Test: <span class="sb">`tests/middleware/rateLimit.test.ts`</span>
<span class="p">
-</span> [ ] <span class="gs">**Step 1: Write the failing test**</span>

<span class="p">```</span><span class="nl">typescript
</span><span class="nf">test</span><span class="p">(</span><span class="dl">'</span><span class="s1">returns 429 when IP exceeds limit</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">limiter</span> <span class="o">=</span> <span class="nf">createRateLimiter</span><span class="p">({</span> <span class="na">max</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="na">windowMs</span><span class="p">:</span> <span class="mi">1000</span> <span class="p">});</span>
  <span class="kd">const</span> <span class="nx">req</span> <span class="o">=</span> <span class="p">{</span> <span class="na">ip</span><span class="p">:</span> <span class="dl">'</span><span class="s1">1.2.3.4</span><span class="dl">'</span> <span class="p">}</span> <span class="kd">as </span><span class="nx">Request</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">res</span> <span class="o">=</span> <span class="nf">mockResponse</span><span class="p">();</span>

  <span class="nf">limiter</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">,</span> <span class="nx">next</span><span class="p">);</span>
  <span class="nf">limiter</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">,</span> <span class="nx">next</span><span class="p">);</span>
  <span class="nf">limiter</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">,</span> <span class="nx">next</span><span class="p">);</span>

  <span class="nf">expect</span><span class="p">(</span><span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">).</span><span class="nf">toHaveBeenCalledWith</span><span class="p">(</span><span class="mi">429</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">```</span>
<span class="p">
-</span> [ ] <span class="gs">**Step 2: Run test to verify it fails**</span>

Run: <span class="sb">`npm test -- rateLimit.test.ts`</span>
Expected: FAIL — <span class="sb">`createRateLimiter`</span> not defined
</code></pre></div></div>

<ol>
  <li>
    <p><strong>subagent-driven-development</strong>（或中小型改动时用 <strong>executing-plans</strong> Inline 执行）：按 Task 推进，Subagent 模式下每 Task 新子代理 → Spec 评审 → 质量评审。</p>
  </li>
  <li>
    <p><strong>finishing-a-development-branch</strong>：全绿后问你：本地合并、开 PR、保留还是丢弃。</p>
  </li>
</ol>

<p><strong>你可主动加速的口令：</strong></p>

<ul>
  <li>设计阶段：「方案 A，开始写 spec。」</li>
  <li>计划阶段：「用 subagent-driven 执行计划。」</li>
  <li>收尾：「选项 2，推 PR。」</li>
</ul>

<h2 id="示例-b修复空邮箱能通过校验的-bug">示例 B：修复「空邮箱能通过校验」的 Bug</h2>

<p><strong>你说：</strong></p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>注册表单空邮箱仍能提交，帮我修。
</code></pre></div></div>

<p><strong>预期流程：</strong></p>

<ol>
  <li>加载 <code class="language-plaintext highlighter-rouge">systematic-debugging</code>（而非直接改 <code class="language-plaintext highlighter-rouge">if</code>）。</li>
  <li><strong>阶段 1</strong>：复现步骤、读校验链路、查最近改动。</li>
  <li><strong>阶段 4 + TDD</strong>：</li>
</ol>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// RED</span>
<span class="nf">test</span><span class="p">(</span><span class="dl">'</span><span class="s1">rejects empty email</span><span class="dl">'</span><span class="p">,</span> <span class="k">async </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">submitForm</span><span class="p">({</span> <span class="na">email</span><span class="p">:</span> <span class="dl">''</span> <span class="p">});</span>
  <span class="nf">expect</span><span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">error</span><span class="p">).</span><span class="nf">toBe</span><span class="p">(</span><span class="dl">'</span><span class="s1">Email required</span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>

<span class="c1">// 确认 FAIL: expected 'Email required', got undefined</span>

<span class="c1">// GREEN — 最小修复</span>
<span class="kd">function</span> <span class="nf">submitForm</span><span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="nx">FormData</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">data</span><span class="p">.</span><span class="nx">email</span><span class="p">?.</span><span class="nf">trim</span><span class="p">())</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">{</span> <span class="na">error</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Email required</span><span class="dl">'</span> <span class="p">};</span>
  <span class="p">}</span>
  <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<ol>
  <li>跑全量测试，再进入 <code class="language-plaintext highlighter-rouge">finishing-a-development-branch</code>（若已在功能分支上）。</li>
</ol>

<h2 id="示例-c大功能拆解--用户后台仪表盘">示例 C：大功能拆解 — 用户后台仪表盘</h2>

<p><strong>你说：</strong></p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>做一个带图表、表格导出、权限过滤的管理员仪表盘。
</code></pre></div></div>

<p><strong>brainstorming 应先做范围判断</strong>：多子系统（图表、导出、RBAC）→ <strong>拆分子项目</strong>，每个子项目独立 spec → plan → 实现循环，而不是一份巨型计划。</p>

<pre><code class="language-mermaid">flowchart LR
    big[管理员仪表盘]
    big --&gt; s1[子项目 1: RBAC 数据层]
    big --&gt; s2[子项目 2: 图表 API]
    big --&gt; s3[子项目 3: CSV 导出]
    s1 --&gt; c1[spec → plan → 实现]
    s2 --&gt; c2[spec → plan → 实现]
    s3 --&gt; c3[spec → plan → 实现]
</code></pre>

<p>你先批准 <strong>子项目 1</strong> 的设计，再进入实现；避免代理一次吞下无法评审的大泥球。</p>

<h1 id="最佳实践">最佳实践</h1>

<h2 id="对人human-partner">对人（Human Partner）</h2>

<ol>
  <li>
    <p><strong>把需求说清楚，但允许代理追问</strong><br />
Superpowers 刻意「慢启动」。brainstorming 的单问单答是在买后续自动化执行的确定性。</p>
  </li>
  <li>
    <p><strong>在检查点做决策，而不是微操每一行</strong><br />
设计节、spec 文件、计划文件、PR 选项 —— 这些是为你准备的<strong>可读检查点</strong>。</p>
  </li>
  <li>
    <p><strong>用项目规则表达硬约束</strong><br />
在 <code class="language-plaintext highlighter-rouge">AGENTS.md</code> 或 <code class="language-plaintext highlighter-rouge">.cursor/rules</code> 写明：测试框架、目录结构、禁止动哪些目录、是否允许 TDD 例外。用户规则优先级最高。</p>
  </li>
  <li>
    <p><strong>大功能坚持拆子项目</strong><br />
一个 spec 对应一个可独立交付、可独立测试的增量。</p>
  </li>
  <li>
    <p><strong>收尾时认真选 finishing 选项</strong><br />
开 PR 会<strong>保留 worktree</strong> 便于改评审意见；本地合并或丢弃才会清理 worktree。</p>
  </li>
</ol>

<h2 id="对代理工作流">对代理工作流</h2>

<ol>
  <li>
    <p><strong>技能先行的纪律</strong><br />
<code class="language-plaintext highlighter-rouge">using-superpowers</code> 列出多种「合理化跳过技能」的念头（「先看一下代码」「问题很简单」）—— 都应视为红旗。</p>
  </li>
  <li>
    <p><strong>计划严禁占位符</strong><br />
计划里的每一步都应是代理或子代理<strong>无需猜测即可执行</strong>的指令。</p>
  </li>
  <li>
    <p><strong>TDD 与调试联动</strong><br />
修 Bug 在 <code class="language-plaintext highlighter-rouge">systematic-debugging</code> 阶段 4 仍要用 TDD 写失败测试；「先修再补测」在技能层面属于违规。</p>
  </li>
  <li>
    <p><strong>子代理隔离上下文</strong><br />
实现子代理不应读取整份计划文件；主代理应提取<strong>当前 Task 全文 + 场景说明</strong>再派发。</p>
  </li>
  <li>
    <p><strong>评审顺序不可颠倒</strong><br />
先 Spec 合规 ✅，再代码质量评审；任一审有问题则打回实现者循环，不得带问题进入下一 Task。</p>
  </li>
</ol>

<h2 id="与自有-cursor-skills-共存">与自有 Cursor Skills 共存</h2>

<p>本博客仓库已有 <code class="language-plaintext highlighter-rouge">.cursor/skills-cursor/</code> 等技能（如 <code class="language-plaintext highlighter-rouge">create-rule</code>、<code class="language-plaintext highlighter-rouge">create-skill</code>）。二者关系：</p>

<ul>
  <li><strong>Superpowers</strong>：通用软件工程工作流（设计、计划、TDD、调试、分支）。</li>
  <li><strong>项目/产品技能</strong>：领域或工具专属（如 Cursor Automations、SDK 集成）。</li>
</ul>

<p>代理按 <code class="language-plaintext highlighter-rouge">using-superpowers</code> 的优先级：<strong>流程类技能先于实现类技能</strong>（先 brainstorming / debugging，再 domain skill）。</p>

<h1 id="注意事项与常见误区">注意事项与常见误区</h1>

<table>
  <thead>
    <tr>
      <th style="text-align: left">误区</th>
      <th style="text-align: left">实际情况</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">「装上就全自动，不用管」</td>
      <td style="text-align: left">设计门、spec 审阅、finishing 选项仍需人确认</td>
    </tr>
    <tr>
      <td style="text-align: left">「技能只是提示词建议」</td>
      <td style="text-align: left">技能设计为<strong>强制工作流</strong>；跳过等于放弃 Superpowers 的价值</td>
    </tr>
    <tr>
      <td style="text-align: left">「我可以后补测试， spirit 一样」</td>
      <td style="text-align: left">TDD 技能明确：后写测试通过<strong>不能证明</strong>测试有效</td>
    </tr>
    <tr>
      <td style="text-align: left">「简单 Bug 不用四阶段调试」</td>
      <td style="text-align: left">简单问题也有根因；系统化通常 <strong>15–30 分钟</strong> vs 随机修补 <strong>2–3 小时</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">「子代理可以并行改同一批文件」</td>
      <td style="text-align: left">官方禁止并行派发多个实现子代理，避免冲突</td>
    </tr>
    <tr>
      <td style="text-align: left">「在 main 上直接让代理开干」</td>
      <td style="text-align: left">应用 worktree 隔离；finishing 也假设功能在分支上</td>
    </tr>
  </tbody>
</table>

<p class="prompt-danger"><strong>安全</strong>：Superpowers 强化工程流程，<strong>不</strong>替代密钥审查、依赖审计与沙箱策略。涉及生产配置、凭证、强制推送时，仍需人工把关 —— 与站内 <a href="/posts/openclaw-in-action/">OpenClaw</a> 一文强调的「沙盒与权限」思路一致。</p>

<p><strong>贡献</strong>：官方欢迎修 Bug 与跨 Harness 改进，但<strong>一般不接纳随意新增技能</strong>；新技能须按 <code class="language-plaintext highlighter-rouge">writing-skills</code> 方法论并保证多平台可用。贡献请走 <code class="language-plaintext highlighter-rouge">dev</code> 分支与 PR 模板。</p>

<h1 id="与同类方案对比简表">与同类方案对比（简表）</h1>

<table>
  <thead>
    <tr>
      <th style="text-align: left">维度</th>
      <th style="text-align: left">Superpowers</th>
      <th style="text-align: left">仅依赖 IDE 默认 Agent</th>
      <th style="text-align: left">纯自定义 Rules</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">工作流完整性</td>
      <td style="text-align: left">七步闭环 + 调试/TDD 专技能</td>
      <td style="text-align: left">依模型与提示词，易漂移</td>
      <td style="text-align: left">取决于作者维护力度</td>
    </tr>
    <tr>
      <td style="text-align: left">可移植性</td>
      <td style="text-align: left">多 Harness 插件</td>
      <td style="text-align: left">绑定单一产品</td>
      <td style="text-align: left">常绑定 Cursor Rules</td>
    </tr>
    <tr>
      <td style="text-align: left">执行粒度</td>
      <td style="text-align: left">2–5 分钟任务 + 子代理评审</td>
      <td style="text-align: left">常大块改多文件</td>
      <td style="text-align: left">不统一</td>
    </tr>
    <tr>
      <td style="text-align: left">学习成本</td>
      <td style="text-align: left">需接受「先设计后写码」</td>
      <td style="text-align: left">低</td>
      <td style="text-align: left">中高（自写自维护）</td>
    </tr>
  </tbody>
</table>

<p>Superpowers <strong>不是</strong> LangChain/CrewAI 式运行时框架；它与 <a href="/posts/agentic-design-patterns-reading/">Agentic 设计模式</a> 互补：<strong>模式讲「是什么」，Superpowers 讲「代理每天应按什么顺序做」</strong>。</p>

<h1 id="快速上手检查清单">快速上手检查清单</h1>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />在 Cursor Agent 中安装 <code class="language-plaintext highlighter-rouge">superpowers</code> 插件</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />仓库根目录准备 <code class="language-plaintext highlighter-rouge">AGENTS.md</code>（可选但推荐）：技术栈、测试命令、分支策略</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />创建 <code class="language-plaintext highlighter-rouge">docs/superpowers/specs/</code> 与 <code class="language-plaintext highlighter-rouge">docs/superpowers/plans/</code>（或由代理首次运行时创建）</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />收藏「常用触发示例」口令，代理跑偏时显式点名技能</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />用<strong>小功能</strong>走通一遍：需求 → 设计确认 → 计划 → 实现 → PR</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />用<strong>一个真实 Bug</strong> 走通 <code class="language-plaintext highlighter-rouge">systematic-debugging</code> + TDD</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />熟悉 finishing 四选项，避免误删 worktree 或分支</li>
</ul>

<h1 id="参考资源">参考资源</h1>

<ul>
  <li>官方仓库：<a href="https://github.com/obra/superpowers">obra/superpowers</a></li>
  <li>首发文章：<a href="https://blog.fsck.com/2025/10/09/superpowers/">Superpowers — Jesse Vincent (2025-10-09)</a></li>
  <li>Release Notes：<a href="https://github.com/obra/superpowers/blob/main/RELEASE-NOTES.md">RELEASE-NOTES.md</a></li>
  <li>技能源码目录：<a href="https://github.com/obra/superpowers/tree/main/skills">skills/</a></li>
  <li>Cursor 插件清单：<a href="https://github.com/obra/superpowers/blob/main/.cursor-plugin/plugin.json">.cursor-plugin/plugin.json</a></li>
  <li>站内延伸：<a href="/posts/ai-coding-in-action/">AI Coding in Action</a>、<a href="/posts/agentic-design-patterns-reading/">Agentic Design Patterns Reading</a>、<a href="/posts/openclaw-in-action/">OpenClaw</a></li>
</ul>

<hr />

<p><em>本文随上游技能与插件版本演进，若你发现某技能文件名或安装命令有变，以 <a href="https://github.com/obra/superpowers/blob/main/README.md">官方 README</a> 为准。</em></p>]]></content><author><name></name></author><category term="Tools" /><category term="AI Coding" /><category term="Cursor" /><category term="Agent" /><category term="Superpowers" /><category term="TDD" /><summary type="html"><![CDATA[介绍 obra/superpowers 的核心理念、技能库、完整开发工作流与最佳实践，含 Cursor 安装与端到端使用示例，帮助读者快速上手 agentic 软件工程方法论。]]></summary></entry><entry><title type="html">Redpanda 入门与实践：与 Kafka 对比与选型</title><link href="http://172.19.0.16:8080/posts/redpanda-in-action/" rel="alternate" type="text/html" title="Redpanda 入门与实践：与 Kafka 对比与选型" /><published>2026-05-26T20:41:42+08:00</published><updated>2026-05-26T20:41:42+08:00</updated><id>http://172.19.0.16:8080/posts/redpanda-in-action</id><content type="html" xml:base="http://172.19.0.16:8080/posts/redpanda-in-action/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#概述" id="markdown-toc-概述">概述</a></li>
  <li><a href="#原理与核心概念" id="markdown-toc-原理与核心概念">原理与核心概念</a>    <ul>
      <li><a href="#流式平台在解决什么问题" id="markdown-toc-流式平台在解决什么问题">流式平台在解决什么问题</a></li>
      <li><a href="#与-kafka-对齐的名词" id="markdown-toc-与-kafka-对齐的名词">与 Kafka 对齐的名词</a></li>
      <li><a href="#redpanda-架构要点与-kafka-的差异根源" id="markdown-toc-redpanda-架构要点与-kafka-的差异根源">Redpanda 架构要点（与 Kafka 的差异根源）</a></li>
    </ul>
  </li>
  <li><a href="#与-apache-kafka-的对比" id="markdown-toc-与-apache-kafka-的对比">与 Apache Kafka 的对比</a>    <ul>
      <li><a href="#架构与运维" id="markdown-toc-架构与运维">架构与运维</a></li>
      <li><a href="#性能与资源如何理解10x" id="markdown-toc-性能与资源如何理解10x">性能与资源（如何理解「10x」）</a></li>
      <li><a href="#兼容性边界" id="markdown-toc-兼容性边界">兼容性边界</a></li>
    </ul>
  </li>
  <li><a href="#方案选型" id="markdown-toc-方案选型">方案选型</a></li>
  <li><a href="#安装与快速开始" id="markdown-toc-安装与快速开始">安装与快速开始</a>    <ul>
      <li><a href="#前置条件" id="markdown-toc-前置条件">前置条件</a></li>
      <li><a href="#单节点docker推荐用于-kafka-协议测试" id="markdown-toc-单节点docker推荐用于-kafka-协议测试">单节点：Docker（推荐用于 Kafka 协议测试）</a></li>
      <li><a href="#多节点本地集群rpk-container" id="markdown-toc-多节点本地集群rpk-container">多节点本地集群：<code class="language-plaintext highlighter-rouge">rpk container</code></a></li>
      <li><a href="#常用-rpk-命令单节点已用-docker-时" id="markdown-toc-常用-rpk-命令单节点已用-docker-时">常用 <code class="language-plaintext highlighter-rouge">rpk</code> 命令（单节点已用 Docker 时）</a></li>
    </ul>
  </li>
  <li><a href="#使用示例" id="markdown-toc-使用示例">使用示例</a>    <ul>
      <li><a href="#命令行验证管道" id="markdown-toc-命令行验证管道">命令行验证管道</a></li>
      <li><a href="#使用场景ibm-sarama-上报与本地校验" id="markdown-toc-使用场景ibm-sarama-上报与本地校验">使用场景：IBM Sarama 上报与本地校验</a>        <ul>
          <li><a href="#背景没有测试环境-kafka-时要验证什么" id="markdown-toc-背景没有测试环境-kafka-时要验证什么">背景：没有测试环境 Kafka 时要验证什么</a></li>
          <li><a href="#sarama-与-redpanda-如何对接" id="markdown-toc-sarama-与-redpanda-如何对接">Sarama 与 Redpanda 如何对接</a></li>
          <li><a href="#1-准备本地-broker-与-topic" id="markdown-toc-1-准备本地-broker-与-topic">1. 准备本地 broker 与 Topic</a></li>
          <li><a href="#2-最小-syncproducer与生产代码同构" id="markdown-toc-2-最小-syncproducer与生产代码同构">2. 最小 SyncProducer（与生产代码同构）</a></li>
          <li><a href="#3-不依赖远端-kafka-时如何验证上报内容正确" id="markdown-toc-3-不依赖远端-kafka-时如何验证上报内容正确">3. 不依赖远端 Kafka 时，如何验证「上报内容」正确</a></li>
          <li><a href="#4-asyncproducer-与错误通道" id="markdown-toc-4-asyncproducer-与错误通道">4. AsyncProducer 与错误通道</a></li>
          <li><a href="#5-与真实-kafka-测试环境的差异" id="markdown-toc-5-与真实-kafka-测试环境的差异">5. 与「真实 Kafka 测试环境」的差异</a></li>
        </ul>
      </li>
      <li><a href="#其他-go-客户端segmentiokafka-go" id="markdown-toc-其他-go-客户端segmentiokafka-go">其他 Go 客户端（<code class="language-plaintext highlighter-rouge">segmentio/kafka-go</code>）</a></li>
      <li><a href="#与现有-kafka-教程的对应关系" id="markdown-toc-与现有-kafka-教程的对应关系">与现有 Kafka 教程的对应关系</a></li>
    </ul>
  </li>
  <li><a href="#最佳实践" id="markdown-toc-最佳实践">最佳实践</a></li>
  <li><a href="#注意事项" id="markdown-toc-注意事项">注意事项</a></li>
  <li><a href="#小结" id="markdown-toc-小结">小结</a></li>
  <li><a href="#refer" id="markdown-toc-refer">Refer</a></li>
</ul>

<blockquote class="prompt-info">
  <p><a href="https://www.redpanda.com/">Redpanda</a> 是面向关键业务的<strong>实时流式引擎</strong>：Kafka® 协议兼容、<strong>无需 ZooKeeper</strong>、<strong>无 JVM</strong>、现有客户端与生态工具多数可直接复用。官方定位从笔记本上的原型到全球分布的 PB 级数据都能扩展，并在 Kafka 协议之上提供内联 WASM 变换、地理复制与分层存储等能力。Docker 镜像见 <a href="https://hub.docker.com/r/redpandadata/redpanda">redpandadata/redpanda</a>；本地多节点集群推荐 <a href="https://www.redpanda.com/blog/rpk-container"><code class="language-plaintext highlighter-rouge">rpk container</code></a>。</p>
</blockquote>

<h2 id="概述">概述</h2>

<p><strong>Apache Kafka</strong> 已成为事件驱动架构的事实标准：日志型存储、分区并行、消费者组、Exactly-once 语义（配合事务）等概念深入人心。但完整生产栈往往包含 <strong>ZooKeeper 或 KRaft</strong>、多个 JVM Broker、较重的内存与运维成本；本地开发还要维护 <code class="language-plaintext highlighter-rouge">docker-compose</code> 或脚本。</p>

<p><strong>Redpanda</strong> 用 <strong>C++</strong> 从零实现兼容 Kafka 的 broker，把协调、元数据与数据面收敛到单一进程，并配套 CLI <strong><code class="language-plaintext highlighter-rouge">rpk</code></strong>（Redpanda Keeper）做安装调优、Topic 管理、容器化本地集群等。对你而言：</p>

<table>
  <thead>
    <tr>
      <th>角色</th>
      <th>典型收益</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>应用开发者</td>
      <td><code class="language-plaintext highlighter-rouge">docker run</code> 或 <code class="language-plaintext highlighter-rouge">rpk container start</code> 即可起集群，客户端仍用 <code class="language-plaintext highlighter-rouge">localhost:9092</code></td>
    </tr>
    <tr>
      <td>平台 / SRE</td>
      <td>更少组件、更低延迟与内存占用，K8s 部署对象更简单</td>
    </tr>
    <tr>
      <td>架构师</td>
      <td>在「必须原生 Kafka」与「可接受兼容实现」之间多一个选型</td>
    </tr>
  </tbody>
</table>

<h2 id="原理与核心概念">原理与核心概念</h2>

<h3 id="流式平台在解决什么问题">流式平台在解决什么问题</h3>

<p>业务系统产生连续事件（订单、点击、指标、CDC 变更）。流式平台把这些事件<strong>按主题（Topic）持久化、可重放、可并行消费</strong>，解耦生产者与消费者，并支撑实时管道（Flink、Spark Streaming）、微服务集成（Kafka Connect）与日志聚合等场景。</p>

<pre><code class="language-mermaid">flowchart LR
    P1[Producer A] --&gt; T[Topic: orders]
    P2[Producer B] --&gt; T
    T --&gt; C1[Consumer Group: analytics]
    T --&gt; C2[Consumer Group: fulfillment]
</code></pre>

<h3 id="与-kafka-对齐的名词">与 Kafka 对齐的名词</h3>

<p>Redpanda 在协议与语义上对齐 Kafka，下列概念可直接沿用既有知识：</p>

<table>
  <thead>
    <tr>
      <th>概念</th>
      <th>含义</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Broker</strong></td>
      <td>接收、存储、复制分区数据的节点</td>
    </tr>
    <tr>
      <td><strong>Topic</strong></td>
      <td>逻辑消息流名称</td>
    </tr>
    <tr>
      <td><strong>Partition</strong></td>
      <td>Topic 的物理分片，决定并行度与顺序边界（分区内有序）</td>
    </tr>
    <tr>
      <td><strong>Replica</strong></td>
      <td>分区副本，用于高可用</td>
    </tr>
    <tr>
      <td><strong>Consumer Group</strong></td>
      <td>同组内分区分配，实现水平扩展消费</td>
    </tr>
    <tr>
      <td><strong>Offset</strong></td>
      <td>分区内消息位置，消费进度以此提交</td>
    </tr>
  </tbody>
</table>

<h3 id="redpanda-架构要点与-kafka-的差异根源">Redpanda 架构要点（与 Kafka 的差异根源）</h3>

<p>传统 Kafka 集群（尤其仍依赖 ZK 的部署）在逻辑上可拆成多层：</p>

<pre><code class="language-mermaid">flowchart TB
    subgraph KafkaClassic["典型 Kafka 栈（概念）"]
        ZK[ZooKeeper / 或 KRaft 控制器]
        KB1[Kafka Broker JVM]
        KB2[Kafka Broker JVM]
        ZK --&gt; KB1
        ZK --&gt; KB2
    end
    subgraph RedpandaStack["Redpanda 单进程模型"]
        RP[Redpanda Broker C++]
        RP --&gt; RP
    end
    Client[Kafka 协议客户端] --&gt; KB1
    Client --&gt; RP
</code></pre>

<p>Redpanda 将<strong>元数据管理、Raft 共识、存储引擎</strong>整合进同一 broker 进程，不再单独部署 ZooKeeper；也无需为 broker 准备 JVM 堆。存储侧采用<strong>线程-per-core</strong>、顺序写与零拷贝等设计，官方宣称在多种工作负载下相较传统 Kafka 有显著延迟与吞吐优势（具体倍数取决于硬件、副本数、压缩与消息大小，选型时应以你的压测为准）。</p>

<p>在 Kafka 协议之外，Redpanda 还提供（视版本与许可证而定）例如：</p>

<ul>
  <li><strong>内联 WASM Transform</strong>：在 broker 路径上对记录做轻量变换，减少外部流处理作业</li>
  <li><strong>分层 / 云存储（Tiered Storage、Cloud Topics 等）</strong>：冷热数据分离、跨地域复制，面向更大规模与成本优化</li>
</ul>

<p>开发测试阶段通常只需关心 <strong>Kafka API 兼容</strong> 与 <strong><code class="language-plaintext highlighter-rouge">rpk</code></strong>；高级特性在确定上生产 Redpanda 后再深入官方文档即可。</p>

<h2 id="与-apache-kafka-的对比">与 Apache Kafka 的对比</h2>

<h3 id="架构与运维">架构与运维</h3>

<table>
  <thead>
    <tr>
      <th>维度</th>
      <th>Apache Kafka</th>
      <th>Redpanda</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>实现语言</td>
      <td>Java（broker）</td>
      <td>C++</td>
    </tr>
    <tr>
      <td>协调 / 元数据</td>
      <td>ZK（旧版）或 KRaft（新方向）</td>
      <td>内置 Raft，无 ZK</td>
    </tr>
    <tr>
      <td>进程模型</td>
      <td>JVM + GC 调优</td>
      <td>原生进程，<code class="language-plaintext highlighter-rouge">rpk redpanda tune</code> 可协助 OS 调优</td>
    </tr>
    <tr>
      <td>本地开发重量</td>
      <td>常需 compose 多容器或较重镜像</td>
      <td>单容器即可监听 <code class="language-plaintext highlighter-rouge">9092</code></td>
    </tr>
    <tr>
      <td>客户端</td>
      <td>Kafka 协议</td>
      <td><strong>Kafka 协议兼容</strong>，多数客户端无需改代码</td>
    </tr>
    <tr>
      <td>生态</td>
      <td>Connect、Streams、MirrorMaker 等最完整</td>
      <td>兼容主流客户端；部分 Kafka 专属生态需逐项验证</td>
    </tr>
    <tr>
      <td>运维工具</td>
      <td>kafka-topics.sh、Cruise Control 等</td>
      <td><strong><code class="language-plaintext highlighter-rouge">rpk</code></strong> 统一集群/Topic/容器</td>
    </tr>
  </tbody>
</table>

<h3 id="性能与资源如何理解10x">性能与资源（如何理解「10x」）</h3>

<p>官方宣传中的「更快、更省资源」来自：无 JVM GC 停顿、更少的跨进程 RPC、针对 NVMe 的存储路径优化等。<strong>不应</strong>把营销数字直接当作 SLA；应在与生产相近的消息大小、压缩、副本因子、<code class="language-plaintext highlighter-rouge">acks</code> 配置下做基准测试。</p>

<h3 id="兼容性边界">兼容性边界</h3>

<p>Redpanda 目标为 <strong>Kafka 协议兼容</strong>，使现有 Producer/Consumer、多数 CLI 与监控集成能工作。但需注意：</p>

<ul>
  <li><strong>Kafka Connect 连接器、Kafka Streams、特定 Admin API 或极新 KIP 特性</strong>可能滞后或行为略有差异，上线前要在预发环境验证。</li>
  <li><strong>事务、幂等生产者、exactly-once</strong> 等高级语义：Redpanda 持续补齐；关键业务路径必须按官方兼容性矩阵与版本说明测试。</li>
  <li><strong>版本锁定</strong>：生产应固定 Redpanda 版本，并关注 <a href="https://github.com/redpanda-data/redpanda/releases">发布说明</a> 与 Kafka 协议级别。</li>
</ul>

<h2 id="方案选型">方案选型</h2>

<pre><code class="language-mermaid">flowchart TD
    Q1{是否需要完整 Kafka 生态\nConnect/Streams/MM2 等？}
    Q1 --&gt;|是且无法替代| K[Apache Kafka\n托管或自建]
    Q1 --&gt;|否或仅部分| Q2{延迟/资源/运维\n是否敏感？}
    Q2 --&gt;|本地开发/CI 联调| R1[Redpanda Docker / rpk container]
    Q2 --&gt;|新集群/云原生优先| R2[Redpanda 生产集群\n或 Redpanda Cloud]
    Q2 --&gt;|已有 Kafka 投资| K2[继续 Kafka\n或评估迁移工具]
    R2 --&gt; Q3{合规/厂商锁定？}
    Q3 --&gt;|接受| RP[Redpanda 企业特性]
    Q3 --&gt;|仅要开源兼容| OSS[Redpanda 社区版能力范围]
</code></pre>

<table>
  <thead>
    <tr>
      <th>场景</th>
      <th>更倾向</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>笔记本 / CI 需要 Kafka 协议联调</td>
      <td><strong>Redpanda</strong></td>
      <td>一条 <code class="language-plaintext highlighter-rouge">docker run</code> 或 <code class="language-plaintext highlighter-rouge">rpk container start</code>，内存占用远低于完整 Kafka+ZK</td>
    </tr>
    <tr>
      <td>Go 服务用 <strong>Sarama</strong> 发事件、无测试环境 Kafka</td>
      <td><strong>Redpanda + 本机消费校验</strong></td>
      <td><code class="language-plaintext highlighter-rouge">bootstrap</code> 指向 <code class="language-plaintext highlighter-rouge">127.0.0.1:9092</code>，用 <code class="language-plaintext highlighter-rouge">rpk</code> 或 Sarama Consumer 核对 payload</td>
    </tr>
    <tr>
      <td>已有大量 Kafka Streams / Connect 作业</td>
      <td><strong>Kafka</strong></td>
      <td>迁移成本高，除非逐项验证 Redpanda</td>
    </tr>
    <tr>
      <td>新建事件骨干、K8s 上希望组件少</td>
      <td><strong>Redpanda</strong></td>
      <td>单镜像、Helm/Operator 或 StatefulSet 部署</td>
    </tr>
    <tr>
      <td>超大规模、仅采购 Kafka 托管服务</td>
      <td><strong>Confluent Cloud / MSK 等</strong></td>
      <td>运维外包；Redpanda 作为替代需单独商务与技术评估</td>
    </tr>
    <tr>
      <td>强依赖某一 Kafka 专有 KIP</td>
      <td><strong>Kafka</strong></td>
      <td>以官方兼容性文档为准</td>
    </tr>
  </tbody>
</table>

<p class="prompt-tip prompt-tip"><strong>实践建议</strong>：当文档或教程写着「启动 Kafka 做测试」且机器能跑 Docker 时，用 <strong>Redpanda 监听 9092</strong> 往往更轻；应用侧 <code class="language-plaintext highlighter-rouge">bootstrap.servers=127.0.0.1:9092</code> 通常无需改代码。</p>

<h2 id="安装与快速开始">安装与快速开始</h2>

<h3 id="前置条件">前置条件</h3>

<ul>
  <li><strong>Docker</strong>：Linux 上 <a href="https://docs.docker.com/engine/install/">Docker Engine</a>，macOS 上 <a href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a></li>
  <li>可选：安装 <strong><code class="language-plaintext highlighter-rouge">rpk</code></strong>（与 Redpanda 同发版），用于 Topic 管理、<code class="language-plaintext highlighter-rouge">rpk container</code> 多节点集群等 — 见 <a href="https://docs.redpanda.com/docs/get-started/quick-start/">Quick Start</a></li>
</ul>

<h3 id="单节点docker推荐用于-kafka-协议测试">单节点：Docker（推荐用于 Kafka 协议测试）</h3>

<p>官方镜像 <a href="https://hub.docker.com/r/redpandadata/redpanda"><code class="language-plaintext highlighter-rouge">redpandadata/redpanda</code></a>（早期社区镜像名为 <code class="language-plaintext highlighter-rouge">vectorized/redpanda</code>，现已迁移到 <code class="language-plaintext highlighter-rouge">redpandadata</code> 组织）。在开发机上一行启动单 broker，对外暴露 <strong>9092</strong>：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">--rm</span> <span class="nt">-p</span> 9092:9092 redpandadata/redpanda:latest <span class="se">\</span>
  redpanda start <span class="se">\</span>
  <span class="nt">--overprovisioned</span> <span class="se">\</span>
  <span class="nt">--smp</span> 1 <span class="se">\</span>
  <span class="nt">--memory</span> 512M <span class="se">\</span>
  <span class="nt">--reserve-memory</span> 0M <span class="se">\</span>
  <span class="nt">--node-id</span> 0 <span class="se">\</span>
  <span class="nt">--check</span><span class="o">=</span><span class="nb">false</span> <span class="se">\</span>
  <span class="nt">--kafka-addr</span> 0.0.0.0:9092 <span class="se">\</span>
  <span class="nt">--advertise-kafka-addr</span> 127.0.0.1:9092
</code></pre></div></div>

<p>参数含义简述：</p>

<table>
  <thead>
    <tr>
      <th>参数</th>
      <th>作用</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--overprovisioned</code></td>
      <td>开发/容器环境，放宽对 CPU/磁盘的假设</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--smp 1</code></td>
      <td>使用 1 个 CPU 核（与轻量测试匹配）</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--memory 512M</code></td>
      <td>限制 broker 可用内存</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--kafka-addr</code> / <code class="language-plaintext highlighter-rouge">--advertise-kafka-addr</code></td>
      <td>监听地址与返回给客户端的地址（本机用 <code class="language-plaintext highlighter-rouge">127.0.0.1:9092</code>）</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--check=false</code></td>
      <td>跳过部分生产环境检查，便于本地快速起</td>
    </tr>
  </tbody>
</table>

<p>容器运行后，将任意 Kafka 客户端的 <code class="language-plaintext highlighter-rouge">bootstrap.servers</code> 指向 <strong><code class="language-plaintext highlighter-rouge">127.0.0.1:9092</code></strong> 即可。</p>

<h3 id="多节点本地集群rpk-container">多节点本地集群：<code class="language-plaintext highlighter-rouge">rpk container</code></h3>

<p><a href="https://www.redpanda.com/blog/rpk-container"><code class="language-plaintext highlighter-rouge">rpk container</code></a> 在 Docker 之上自动拉取镜像、组网并映射端口，<strong>无需手写 compose</strong>。首次会下载镜像，稍等片刻：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 3 节点集群</span>
rpk container start <span class="nt">-n</span> 3

<span class="c"># 查看集群（示例输出中的 broker 地址以实际为准）</span>
rpk cluster info <span class="nt">--brokers</span> 127.0.0.1:65279,127.0.0.1:65287,127.0.0.1:65286

<span class="c"># 创建 Topic</span>
rpk topic create demo <span class="nt">-p</span> 3 <span class="nt">-r</span> 3 <span class="se">\</span>
  <span class="nt">--brokers</span> 127.0.0.1:65279,127.0.0.1:65287,127.0.0.1:65286

<span class="c"># 停止 / 清空</span>
rpk container stop
rpk container purge   <span class="c"># 删除数据，需改节点数时先 purge</span>
</code></pre></div></div>

<p class="prompt-info prompt-info"><code class="language-plaintext highlighter-rouge">rpk container start</code> 再次执行时，若已有集群会<strong>复用</strong>原节点与端口；要改 <code class="language-plaintext highlighter-rouge">-n</code> 节点数，需先 <code class="language-plaintext highlighter-rouge">rpk container purge</code>。</p>

<h3 id="常用-rpk-命令单节点已用-docker-时">常用 <code class="language-plaintext highlighter-rouge">rpk</code> 命令（单节点已用 Docker 时）</h3>

<p>若已安装 <code class="language-plaintext highlighter-rouge">rpk</code> 且 broker 在 <code class="language-plaintext highlighter-rouge">127.0.0.1:9092</code>：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rpk cluster info
rpk topic create events <span class="nt">-p</span> 6 <span class="nt">-r</span> 1
rpk topic list
rpk topic describe events
rpk topic produce events <span class="nt">-k</span> <span class="s1">'key1'</span> <span class="nt">-v</span> <span class="s1">'hello'</span>
rpk topic consume events <span class="nt">-n</span> 1
</code></pre></div></div>

<h2 id="使用示例">使用示例</h2>

<h3 id="命令行验证管道">命令行验证管道</h3>

<p>终端 1 保持 Docker 或 <code class="language-plaintext highlighter-rouge">rpk container</code> 运行；终端 2：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rpk topic create hello <span class="nt">-p</span> 1 <span class="nt">-r</span> 1
<span class="nb">echo</span> <span class="s1">'{"msg":"world"}'</span> | rpk topic produce hello
rpk topic consume hello <span class="nt">-n</span> 1
</code></pre></div></div>

<h3 id="使用场景ibm-sarama-上报与本地校验">使用场景：IBM Sarama 上报与本地校验</h3>

<h4 id="背景没有测试环境-kafka-时要验证什么">背景：没有测试环境 Kafka 时要验证什么</h4>

<p>许多 Go 服务用 <a href="https://github.com/IBM/sarama">IBM/sarama</a>（<strong>Sarama is a Go library for Apache Kafka</strong>）把业务事件写到 Kafka：例如订单状态、审计日志、指标快照。<code class="language-plaintext highlighter-rouge">SyncProducer.SendMessage</code> 返回 <strong>partition / offset</strong> 只说明 broker <strong>收下了这条记录</strong>，并不保证：</p>

<ul>
  <li>Topic 名、<strong>Partition Key</strong> 是否符合设计（是否进错分区）；</li>
  <li>Value 序列化后的<strong>字节</strong>是否正确（JSON 字段名、Protobuf oneof、时间格式等）；</li>
  <li>Headers、压缩、<code class="language-plaintext highlighter-rouge">RequiredAcks</code> 等配置在真实链路上是否可用。</li>
</ul>

<p>若公司<strong>没有</strong>可随时使用的测试/预发 Kafka，常见做法是：</p>

<table>
  <thead>
    <tr>
      <th>做法</th>
      <th>局限</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>只写单元测试 + <strong><code class="language-plaintext highlighter-rouge">sarama/mocks</code></strong></td>
      <td>能断言「是否调用了 SendMessage、参数是什么」，但<strong>不经网络、不经真实 Topic</strong>，发现不了与 broker 交互类问题</td>
    </tr>
    <tr>
      <td>本地起 <strong>ZooKeeper + Kafka</strong></td>
      <td>资源占用大、compose 重，笔记本上常懒得开</td>
    </tr>
    <tr>
      <td>等联调环境</td>
      <td>反馈慢，Topic 未建、ACL 未开时难以区分是代码还是环境</td>
    </tr>
  </tbody>
</table>

<p><strong>Redpanda 在这里的角色</strong>：在本机提供<strong>兼容 Kafka 协议</strong>的 broker（默认 <code class="language-plaintext highlighter-rouge">127.0.0.1:9092</code>），让 Sarama 走与线上一致的 Produce 路径；配套 <strong><code class="language-plaintext highlighter-rouge">rpk</code></strong>（或上文单容器 <code class="language-plaintext highlighter-rouge">docker run</code>）负责<strong>起 broker、建 Topic、消费核对</strong>，无需部署 ZK/JVM。应用代码里仍只需配置 broker 列表（如 <code class="language-plaintext highlighter-rouge">KAFKA_BROKERS</code>），<strong>不必为本地单独换 SDK</strong>。</p>

<pre><code class="language-mermaid">flowchart LR
    subgraph 你的机器
        App[Go 服务\nSarama Producer]
        RP[Redpanda broker\n:9092]
        RPK[rpk\n topic create / consume]
        App --&gt;|Kafka 协议| RP
        RPK --&gt;|管理/读回| RP
    end
    Test[集成测试\nSarama Consumer] --&gt; RP
</code></pre>

<p>典型闭环：<strong>Docker 或 <code class="language-plaintext highlighter-rouge">rpk container</code> 起 Redpanda</strong> → <strong><code class="language-plaintext highlighter-rouge">rpk topic create</code></strong> 对齐线上 Topic 名 → <strong>跑服务或测试触发上报</strong> → 用 <strong><code class="language-plaintext highlighter-rouge">rpk topic consume</code></strong> 或 <strong>Sarama Consumer</strong> 读回 value，与预期 JSON/Protobuf 比对。下文按该顺序展开。</p>

<h4 id="sarama-与-redpanda-如何对接">Sarama 与 Redpanda 如何对接</h4>

<p>Sarama 是 MIT 协议的 Go Kafka 客户端（现由 IBM 维护）。业务里常用 <strong><code class="language-plaintext highlighter-rouge">SyncProducer</code></strong> 在请求路径末尾写 Topic，或用 <strong><code class="language-plaintext highlighter-rouge">AsyncProducer</code></strong> 做旁路日志。Sarama 只认 <strong>Kafka 协议</strong> 与 broker 地址，<strong>不区分</strong>背后是 Apache Kafka 还是 Redpanda——把 <code class="language-plaintext highlighter-rouge">bootstrap.servers</code> / <code class="language-plaintext highlighter-rouge">KAFKA_BROKERS</code> 指到本机 Redpanda 即可，<strong>无需改上报代码</strong>。</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant App as Go 服务 (Sarama Producer)
    participant RP as Redpanda :9092
    participant Verify as 校验方
    App-&gt;&gt;RP: Produce (topic, key, value)
    RP--&gt;&gt;App: partition, offset
    Verify-&gt;&gt;RP: rpk consume / Sarama Consumer
    RP--&gt;&gt;Verify: 原始字节流
    Verify-&gt;&gt;Verify: 比对 JSON/Protobuf/字段
</code></pre>

<h4 id="1-准备本地-broker-与-topic">1. 准备本地 broker 与 Topic</h4>

<p>终端 A 启动 Redpanda（见上文 <code class="language-plaintext highlighter-rouge">docker run</code>）。终端 B 创建与线上一致的 Topic 名（分区数本地可先用 1）：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">KAFKA_BROKERS</span><span class="o">=</span>127.0.0.1:9092

rpk topic create app-events <span class="nt">-p</span> 1 <span class="nt">-r</span> 1
rpk topic describe app-events
</code></pre></div></div>

<p class="prompt-tip prompt-tip">建议用环境变量统一 broker 地址，便于 CI 与本地切换：<code class="language-plaintext highlighter-rouge">KAFKA_BROKERS</code> 或项目惯用的 <code class="language-plaintext highlighter-rouge">KAFKA_PEERS</code>（Sarama 官方 <a href="https://github.com/IBM/sarama/tree/main/examples/http_server">http_server 示例</a> 使用后者）。</p>

<h4 id="2-最小-syncproducer与生产代码同构">2. 最小 SyncProducer（与生产代码同构）</h4>

<p>下列代码与多数线上 Sarama 配置一致：<code class="language-plaintext highlighter-rouge">Return.Successes</code> 为 true 时，<code class="language-plaintext highlighter-rouge">SendMessage</code> 返回的 <strong>partition / offset</strong> 可作为单条消息的唯一标识（与 Sarama 示例注释一致）。</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">import</span> <span class="p">(</span>
	<span class="s">"encoding/json"</span>
	<span class="s">"log"</span>
	<span class="s">"os"</span>
	<span class="s">"strings"</span>

	<span class="s">"github.com/IBM/sarama"</span>
<span class="p">)</span>

<span class="k">const</span> <span class="n">topic</span> <span class="o">=</span> <span class="s">"app-events"</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">brokers</span> <span class="o">:=</span> <span class="n">strings</span><span class="o">.</span><span class="n">Split</span><span class="p">(</span><span class="n">envOr</span><span class="p">(</span><span class="s">"KAFKA_BROKERS"</span><span class="p">,</span> <span class="s">"127.0.0.1:9092"</span><span class="p">),</span> <span class="s">","</span><span class="p">)</span>

	<span class="n">cfg</span> <span class="o">:=</span> <span class="n">sarama</span><span class="o">.</span><span class="n">NewConfig</span><span class="p">()</span>
	<span class="n">cfg</span><span class="o">.</span><span class="n">Producer</span><span class="o">.</span><span class="n">Return</span><span class="o">.</span><span class="n">Successes</span> <span class="o">=</span> <span class="no">true</span>
	<span class="n">cfg</span><span class="o">.</span><span class="n">Producer</span><span class="o">.</span><span class="n">RequiredAcks</span> <span class="o">=</span> <span class="n">sarama</span><span class="o">.</span><span class="n">WaitForLocal</span> <span class="c">// 本地单副本；生产可改为 WaitForAll</span>

	<span class="n">producer</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">sarama</span><span class="o">.</span><span class="n">NewSyncProducer</span><span class="p">(</span><span class="n">brokers</span><span class="p">,</span> <span class="n">cfg</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">log</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="k">defer</span> <span class="n">producer</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>

	<span class="n">payload</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">json</span><span class="o">.</span><span class="n">Marshal</span><span class="p">(</span><span class="k">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="n">any</span><span class="p">{</span>
		<span class="s">"event"</span><span class="o">:</span> <span class="s">"order_created"</span><span class="p">,</span>
		<span class="s">"id"</span><span class="o">:</span>    <span class="s">"ord-1001"</span><span class="p">,</span>
	<span class="p">})</span>

	<span class="n">partition</span><span class="p">,</span> <span class="n">offset</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">producer</span><span class="o">.</span><span class="n">SendMessage</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sarama</span><span class="o">.</span><span class="n">ProducerMessage</span><span class="p">{</span>
		<span class="n">Topic</span><span class="o">:</span> <span class="n">topic</span><span class="p">,</span>
		<span class="n">Key</span><span class="o">:</span>   <span class="n">sarama</span><span class="o">.</span><span class="n">StringEncoder</span><span class="p">(</span><span class="s">"ord-1001"</span><span class="p">),</span> <span class="c">// 同 key 进同一分区，便于顺序消费</span>
		<span class="n">Value</span><span class="o">:</span> <span class="n">sarama</span><span class="o">.</span><span class="n">ByteEncoder</span><span class="p">(</span><span class="n">payload</span><span class="p">),</span>
	<span class="p">})</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">log</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="n">log</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"ok topic=%s partition=%d offset=%d payload=%s"</span><span class="p">,</span>
		<span class="n">topic</span><span class="p">,</span> <span class="n">partition</span><span class="p">,</span> <span class="n">offset</span><span class="p">,</span> <span class="n">payload</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">envOr</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">def</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">string</span> <span class="p">{</span>
	<span class="k">if</span> <span class="n">v</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="n">key</span><span class="p">);</span> <span class="n">v</span> <span class="o">!=</span> <span class="s">""</span> <span class="p">{</span>
		<span class="k">return</span> <span class="n">v</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="n">def</span>
<span class="p">}</span>
</code></pre></div></div>

<p>运行：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>go mod init demo <span class="o">&amp;&amp;</span> go get github.com/IBM/sarama@latest
<span class="nv">KAFKA_BROKERS</span><span class="o">=</span>127.0.0.1:9092 go run <span class="nb">.</span>
</code></pre></div></div>

<p>若 <code class="language-plaintext highlighter-rouge">SendMessage</code> 成功并打印 offset，说明 <strong>Producer 配置、网络、Topic 权限</strong> 均正常；下一步是确认 <strong>写入的字节与预期一致</strong>（见下节）。</p>

<h4 id="3-不依赖远端-kafka-时如何验证上报内容正确">3. 不依赖远端 Kafka 时，如何验证「上报内容」正确</h4>

<table>
  <thead>
    <tr>
      <th>方式</th>
      <th>是否需要真实 broker</th>
      <th>验证什么</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">rpk topic consume</code></strong></td>
      <td>需要本机 Redpanda</td>
      <td>最快：肉眼或 <code class="language-plaintext highlighter-rouge">jq</code> 看 JSON</td>
    </tr>
    <tr>
      <td><strong>Sarama Consumer 集成测试</strong></td>
      <td>需要本机 Redpanda（或 CI 起容器）</td>
      <td>自动化断言 key/value/headers</td>
    </tr>
    <tr>
      <td><strong><code class="language-plaintext highlighter-rouge">sarama/mocks</code></strong></td>
      <td><strong>不需要</strong> broker</td>
      <td>单元测试：是否调用了正确的 Topic/消息体</td>
    </tr>
    <tr>
      <td><strong>Sarama 自带 examples / tools</strong></td>
      <td>视工具而定</td>
      <td>诊断、批量收发，见仓库 <code class="language-plaintext highlighter-rouge">examples/</code>、<code class="language-plaintext highlighter-rouge">tools/</code></td>
    </tr>
  </tbody>
</table>

<p><strong>方式 A：命令行快速核对（推荐先跑通）</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 从最早可读 offset 拉一条（新建 topic 后）</span>
rpk topic consume app-events <span class="nt">-n</span> 5 <span class="nt">--offset</span> start

<span class="c"># 或只关心最新写入</span>
rpk topic consume app-events <span class="nt">-n</span> 1
</code></pre></div></div>

<p>对照服务日志里的 <code class="language-plaintext highlighter-rouge">payload</code> 与 consume 输出；若使用 Protobuf，可先把 value 落盘再 <code class="language-plaintext highlighter-rouge">protoc --decode</code> 或写一小段 Go 反序列化脚本。</p>

<p><strong>方式 B：用 Sarama 消费端做自动化断言（集成测试）</strong></p>

<p>在仓库中增加 <code class="language-plaintext highlighter-rouge">//go:build integration</code> 测试文件，默认 <code class="language-plaintext highlighter-rouge">go test ./...</code> 跳过；本地或 CI 在 Redpanda 已启动时执行：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">KAFKA_BROKERS</span><span class="o">=</span>127.0.0.1:9092 go <span class="nb">test</span> <span class="nt">-tags</span><span class="o">=</span>integration ./internal/kafka/...
</code></pre></div></div>

<p>示例：生产后从 <strong>partition 0、offset 0</strong> 起读（本地单分区 Topic）；生产环境应使用 <strong>Consumer Group</strong> 与 <code class="language-plaintext highlighter-rouge">OffsetNewest</code>/<code class="language-plaintext highlighter-rouge">OffsetOldest</code> 策略，与线上一致。</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">//go:build integration</span>

<span class="k">package</span> <span class="n">kafka_test</span>

<span class="k">import</span> <span class="p">(</span>
	<span class="s">"encoding/json"</span>
	<span class="s">"os"</span>
	<span class="s">"strings"</span>
	<span class="s">"testing"</span>
	<span class="s">"time"</span>

	<span class="s">"github.com/IBM/sarama"</span>
<span class="p">)</span>

<span class="k">func</span> <span class="n">TestProduceAndConsumeAppEvent</span><span class="p">(</span><span class="n">t</span> <span class="o">*</span><span class="n">testing</span><span class="o">.</span><span class="n">T</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">brokers</span> <span class="o">:=</span> <span class="n">strings</span><span class="o">.</span><span class="n">Split</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"KAFKA_BROKERS"</span><span class="p">),</span> <span class="s">","</span><span class="p">)</span>
	<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">brokers</span><span class="p">)</span> <span class="o">==</span> <span class="m">0</span> <span class="o">||</span> <span class="n">brokers</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="o">==</span> <span class="s">""</span> <span class="p">{</span>
		<span class="n">t</span><span class="o">.</span><span class="n">Skip</span><span class="p">(</span><span class="s">"KAFKA_BROKERS not set; start Redpanda and export KAFKA_BROKERS=127.0.0.1:9092"</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="k">const</span> <span class="n">topic</span> <span class="o">=</span> <span class="s">"app-events"</span>

	<span class="c">// --- produce (被测代码可替换为对包函数的调用) ---</span>
	<span class="n">cfg</span> <span class="o">:=</span> <span class="n">sarama</span><span class="o">.</span><span class="n">NewConfig</span><span class="p">()</span>
	<span class="n">cfg</span><span class="o">.</span><span class="n">Producer</span><span class="o">.</span><span class="n">Return</span><span class="o">.</span><span class="n">Successes</span> <span class="o">=</span> <span class="no">true</span>
	<span class="n">producer</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">sarama</span><span class="o">.</span><span class="n">NewSyncProducer</span><span class="p">(</span><span class="n">brokers</span><span class="p">,</span> <span class="n">cfg</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">t</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="k">defer</span> <span class="n">producer</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>

	<span class="n">want</span> <span class="o">:=</span> <span class="k">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="n">any</span><span class="p">{</span><span class="s">"event"</span><span class="o">:</span> <span class="s">"order_created"</span><span class="p">,</span> <span class="s">"id"</span><span class="o">:</span> <span class="s">"ord-1001"</span><span class="p">}</span>
	<span class="n">body</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">json</span><span class="o">.</span><span class="n">Marshal</span><span class="p">(</span><span class="n">want</span><span class="p">)</span>
	<span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">producer</span><span class="o">.</span><span class="n">SendMessage</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sarama</span><span class="o">.</span><span class="n">ProducerMessage</span><span class="p">{</span>
		<span class="n">Topic</span><span class="o">:</span> <span class="n">topic</span><span class="p">,</span>
		<span class="n">Key</span><span class="o">:</span>   <span class="n">sarama</span><span class="o">.</span><span class="n">StringEncoder</span><span class="p">(</span><span class="s">"ord-1001"</span><span class="p">),</span>
		<span class="n">Value</span><span class="o">:</span> <span class="n">sarama</span><span class="o">.</span><span class="n">ByteEncoder</span><span class="p">(</span><span class="n">body</span><span class="p">),</span>
	<span class="p">})</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">t</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>

	<span class="c">// --- consume &amp; assert ---</span>
	<span class="n">consumer</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">sarama</span><span class="o">.</span><span class="n">NewConsumer</span><span class="p">(</span><span class="n">brokers</span><span class="p">,</span> <span class="no">nil</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">t</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="k">defer</span> <span class="n">consumer</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>

	<span class="n">pc</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">consumer</span><span class="o">.</span><span class="n">ConsumePartition</span><span class="p">(</span><span class="n">topic</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">sarama</span><span class="o">.</span><span class="n">OffsetOldest</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">t</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="k">defer</span> <span class="n">pc</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>

	<span class="k">select</span> <span class="p">{</span>
	<span class="k">case</span> <span class="n">msg</span> <span class="o">:=</span> <span class="o">&lt;-</span><span class="n">pc</span><span class="o">.</span><span class="n">Messages</span><span class="p">()</span><span class="o">:</span>
		<span class="k">if</span> <span class="kt">string</span><span class="p">(</span><span class="n">msg</span><span class="o">.</span><span class="n">Key</span><span class="p">)</span> <span class="o">!=</span> <span class="s">"ord-1001"</span> <span class="p">{</span>
			<span class="n">t</span><span class="o">.</span><span class="n">Fatalf</span><span class="p">(</span><span class="s">"key: got %q"</span><span class="p">,</span> <span class="n">msg</span><span class="o">.</span><span class="n">Key</span><span class="p">)</span>
		<span class="p">}</span>
		<span class="k">var</span> <span class="n">got</span> <span class="k">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="n">any</span>
		<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">json</span><span class="o">.</span><span class="n">Unmarshal</span><span class="p">(</span><span class="n">msg</span><span class="o">.</span><span class="n">Value</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">got</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
			<span class="n">t</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
		<span class="p">}</span>
		<span class="k">if</span> <span class="n">got</span><span class="p">[</span><span class="s">"id"</span><span class="p">]</span> <span class="o">!=</span> <span class="n">want</span><span class="p">[</span><span class="s">"id"</span><span class="p">]</span> <span class="o">||</span> <span class="n">got</span><span class="p">[</span><span class="s">"event"</span><span class="p">]</span> <span class="o">!=</span> <span class="n">want</span><span class="p">[</span><span class="s">"event"</span><span class="p">]</span> <span class="p">{</span>
			<span class="n">t</span><span class="o">.</span><span class="n">Fatalf</span><span class="p">(</span><span class="s">"value mismatch: got %#v"</span><span class="p">,</span> <span class="n">got</span><span class="p">)</span>
		<span class="p">}</span>
	<span class="k">case</span> <span class="o">&lt;-</span><span class="n">time</span><span class="o">.</span><span class="n">After</span><span class="p">(</span><span class="m">10</span> <span class="o">*</span> <span class="n">time</span><span class="o">.</span><span class="n">Second</span><span class="p">)</span><span class="o">:</span>
		<span class="n">t</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="s">"timeout waiting for message"</span><span class="p">)</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>方式 C：无 broker 的单元测试（<code class="language-plaintext highlighter-rouge">sarama/mocks</code>）</strong></p>

<p>若当前目标仅是验证「组装 <code class="language-plaintext highlighter-rouge">ProducerMessage</code> 是否正确、是否发到指定 Topic」，可用官方 <a href="https://github.com/IBM/sarama/tree/main/mocks"><strong>mocks</strong></a> 子包模拟 broker，<strong>不启动 Redpanda</strong>。适合 CI 中不占端口的快速测试；<strong>不能</strong>替代对序列化、压缩、大消息体与 broker 限制的端到端验证。</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">(</span>
	<span class="s">"testing"</span>

	<span class="s">"github.com/IBM/sarama"</span>
	<span class="s">"github.com/IBM/sarama/mocks"</span>
<span class="p">)</span>

<span class="k">func</span> <span class="n">TestReporterSend</span><span class="p">(</span><span class="n">t</span> <span class="o">*</span><span class="n">testing</span><span class="o">.</span><span class="n">T</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">cfg</span> <span class="o">:=</span> <span class="n">sarama</span><span class="o">.</span><span class="n">NewConfig</span><span class="p">()</span>
	<span class="n">cfg</span><span class="o">.</span><span class="n">Producer</span><span class="o">.</span><span class="n">Return</span><span class="o">.</span><span class="n">Successes</span> <span class="o">=</span> <span class="no">true</span>

	<span class="n">mock</span> <span class="o">:=</span> <span class="n">mocks</span><span class="o">.</span><span class="n">NewSyncProducer</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">cfg</span><span class="p">)</span>
	<span class="k">defer</span> <span class="n">mock</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>

	<span class="n">mock</span><span class="o">.</span><span class="n">ExpectSendMessageAndSucceed</span><span class="p">()</span>

	<span class="c">// 调用你的封装：report(mock, ...)</span>
	<span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">mock</span><span class="o">.</span><span class="n">SendMessage</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sarama</span><span class="o">.</span><span class="n">ProducerMessage</span><span class="p">{</span>
		<span class="n">Topic</span><span class="o">:</span> <span class="s">"app-events"</span><span class="p">,</span>
		<span class="n">Value</span><span class="o">:</span> <span class="n">sarama</span><span class="o">.</span><span class="n">ByteEncoder</span><span class="p">(</span><span class="s">`{"event":"ping"}`</span><span class="p">),</span>
	<span class="p">})</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">t</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="4-asyncproducer-与错误通道">4. AsyncProducer 与错误通道</h4>

<p>异步生产者需处理 <strong><code class="language-plaintext highlighter-rouge">Errors()</code></strong> 与 <strong><code class="language-plaintext highlighter-rouge">Successes()</code></strong> 通道（或在关闭前 <code class="language-plaintext highlighter-rouge">Close()</code> 刷盘）。Sarama <a href="https://github.com/IBM/sarama/blob/main/examples/http_server/http_server.go">http_server 示例</a> 展示了 <strong>SyncProducer（强一致路径）</strong> 与 <strong>AsyncProducer（高吞吐 access log）</strong> 的组合。本地用 Redpanda 调试 Async 路径时，同样先确认 <code class="language-plaintext highlighter-rouge">rpk topic consume</code> 能收到消息，再在测试里监听 <code class="language-plaintext highlighter-rouge">producer.Errors()</code> 是否为空。</p>

<h4 id="5-与真实-kafka-测试环境的差异">5. 与「真实 Kafka 测试环境」的差异</h4>

<table>
  <thead>
    <tr>
      <th>项</th>
      <th>本地 Redpanda</th>
      <th>公司测试/预发 Kafka</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>认证</td>
      <td>常无 SASL/TLS</td>
      <td>常有 SCRAM、证书</td>
    </tr>
    <tr>
      <td>副本 / <code class="language-plaintext highlighter-rouge">acks</code></td>
      <td>单副本，<code class="language-plaintext highlighter-rouge">WaitForLocal</code> 即可</td>
      <td>需 <code class="language-plaintext highlighter-rouge">WaitForAll</code>、多副本 Topic</td>
    </tr>
    <tr>
      <td>消息版本</td>
      <td>设置 <code class="language-plaintext highlighter-rouge">config.Version</code> 与集群接近</td>
      <td>与运维确认 broker 版本</td>
    </tr>
  </tbody>
</table>

<p>上线前仍应在<strong>目标 Kafka 版本</strong>上跑一轮集成测试；Redpanda 用于 <strong>开发与 MR 阶段</strong> 快速验证 Sarama 上报逻辑，减少「等测试环境 Kafka」的阻塞。</p>

<h3 id="其他-go-客户端segmentiokafka-go">其他 Go 客户端（<code class="language-plaintext highlighter-rouge">segmentio/kafka-go</code>）</h3>

<p>与连接 Kafka 相同，仅改 <code class="language-plaintext highlighter-rouge">bootstrap</code> 地址。示例：生产与消费各一次（需本机 <code class="language-plaintext highlighter-rouge">go mod init</code> 与 <code class="language-plaintext highlighter-rouge">go get github.com/segmentio/kafka-go</code>）：</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">import</span> <span class="p">(</span>
	<span class="s">"context"</span>
	<span class="s">"fmt"</span>
	<span class="s">"log"</span>
	<span class="s">"time"</span>

	<span class="s">"github.com/segmentio/kafka-go"</span>
<span class="p">)</span>

<span class="k">const</span> <span class="p">(</span>
	<span class="n">broker</span> <span class="o">=</span> <span class="s">"127.0.0.1:9092"</span>
	<span class="n">topic</span>  <span class="o">=</span> <span class="s">"hello-go"</span>
<span class="p">)</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">ctx</span> <span class="o">:=</span> <span class="n">context</span><span class="o">.</span><span class="n">Background</span><span class="p">()</span>

	<span class="n">w</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="n">kafka</span><span class="o">.</span><span class="n">Writer</span><span class="p">{</span>
		<span class="n">Addr</span><span class="o">:</span>     <span class="n">kafka</span><span class="o">.</span><span class="n">TCP</span><span class="p">(</span><span class="n">broker</span><span class="p">),</span>
		<span class="n">Topic</span><span class="o">:</span>    <span class="n">topic</span><span class="p">,</span>
		<span class="n">Balancer</span><span class="o">:</span> <span class="o">&amp;</span><span class="n">kafka</span><span class="o">.</span><span class="n">LeastBytes</span><span class="p">{},</span>
	<span class="p">}</span>
	<span class="k">defer</span> <span class="n">w</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>

	<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">w</span><span class="o">.</span><span class="n">WriteMessages</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">kafka</span><span class="o">.</span><span class="n">Message</span><span class="p">{</span>
		<span class="n">Key</span><span class="o">:</span>   <span class="p">[]</span><span class="kt">byte</span><span class="p">(</span><span class="s">"k1"</span><span class="p">),</span>
		<span class="n">Value</span><span class="o">:</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">(</span><span class="s">"from redpanda"</span><span class="p">),</span>
	<span class="p">});</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">log</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>

	<span class="n">r</span> <span class="o">:=</span> <span class="n">kafka</span><span class="o">.</span><span class="n">NewReader</span><span class="p">(</span><span class="n">kafka</span><span class="o">.</span><span class="n">ReaderConfig</span><span class="p">{</span>
		<span class="n">Brokers</span><span class="o">:</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{</span><span class="n">broker</span><span class="p">},</span>
		<span class="n">Topic</span><span class="o">:</span>   <span class="n">topic</span><span class="p">,</span>
		<span class="n">GroupID</span><span class="o">:</span> <span class="s">"demo-group"</span><span class="p">,</span>
	<span class="p">})</span>
	<span class="k">defer</span> <span class="n">r</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>

	<span class="n">ctx</span><span class="p">,</span> <span class="n">cancel</span> <span class="o">:=</span> <span class="n">context</span><span class="o">.</span><span class="n">WithTimeout</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="m">10</span><span class="o">*</span><span class="n">time</span><span class="o">.</span><span class="n">Second</span><span class="p">)</span>
	<span class="k">defer</span> <span class="n">cancel</span><span class="p">()</span>

	<span class="n">m</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">r</span><span class="o">.</span><span class="n">ReadMessage</span><span class="p">(</span><span class="n">ctx</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">log</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"partition=%d offset=%d key=%s value=%s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
		<span class="n">m</span><span class="o">.</span><span class="n">Partition</span><span class="p">,</span> <span class="n">m</span><span class="o">.</span><span class="n">Offset</span><span class="p">,</span> <span class="kt">string</span><span class="p">(</span><span class="n">m</span><span class="o">.</span><span class="n">Key</span><span class="p">),</span> <span class="kt">string</span><span class="p">(</span><span class="n">m</span><span class="o">.</span><span class="n">Value</span><span class="p">))</span>
<span class="p">}</span>
</code></pre></div></div>

<p class="prompt-warning prompt-warning">首次运行前用 <code class="language-plaintext highlighter-rouge">rpk topic create hello-go</code>（或让 <code class="language-plaintext highlighter-rouge">kafka-go</code> 在支持自动创建 Topic 的策略下运行）。生产环境应<strong>显式创建 Topic</strong> 并设置分区数、副本因子与 <code class="language-plaintext highlighter-rouge">retention</code>。</p>

<h3 id="与现有-kafka-教程的对应关系">与现有 Kafka 教程的对应关系</h3>

<table>
  <thead>
    <tr>
      <th>Kafka 教程中的步骤</th>
      <th>Redpanda 本地替代</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>启动 ZK + Kafka</td>
      <td><code class="language-plaintext highlighter-rouge">docker run … redpandadata/redpanda</code> 或 <code class="language-plaintext highlighter-rouge">rpk container start</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">kafka-topics.sh --create</code></td>
      <td><code class="language-plaintext highlighter-rouge">rpk topic create</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">kafka-console-producer</code></td>
      <td><code class="language-plaintext highlighter-rouge">rpk topic produce</code></td>
    </tr>
    <tr>
      <td>Java <code class="language-plaintext highlighter-rouge">KafkaProducer</code> / <code class="language-plaintext highlighter-rouge">KafkaConsumer</code></td>
      <td>相同 API，改 <code class="language-plaintext highlighter-rouge">bootstrap.servers</code></td>
    </tr>
    <tr>
      <td>Go <a href="https://github.com/IBM/sarama">IBM/sarama</a> <code class="language-plaintext highlighter-rouge">SyncProducer</code></td>
      <td><code class="language-plaintext highlighter-rouge">KAFKA_BROKERS=127.0.0.1:9092</code>，校验见下文 <strong>Sarama 上报与本地校验</strong></td>
    </tr>
  </tbody>
</table>

<h2 id="最佳实践">最佳实践</h2>

<ol>
  <li><strong>开发/CI 统一用 Redpanda 减配</strong>：固定上述 <code class="language-plaintext highlighter-rouge">docker run</code> 命令或 compose 片段写入仓库 <code class="language-plaintext highlighter-rouge">scripts/</code>，避免每人一套 ZooKeeper 端口。</li>
  <li><strong>显式 Topic 治理</strong>：分区数、副本数、<code class="language-plaintext highlighter-rouge">cleanup.policy</code>、<code class="language-plaintext highlighter-rouge">retention.ms</code> 纳入 IaC 或 <code class="language-plaintext highlighter-rouge">rpk topic create</code> 脚本，禁止依赖 auto-create 默认值上生产。</li>
  <li><strong>监控指标对齐 Kafka</strong>：Prometheus 抓取 Redpanda 指标，Grafana 大盘可复用大量 Kafka 运维经验（延迟、under-replicated partitions、consumer lag）。</li>
  <li><strong>客户端配置保持一致</strong>：<code class="language-plaintext highlighter-rouge">acks=all</code>、幂等生产者、压缩（<code class="language-plaintext highlighter-rouge">lz4</code>/<code class="language-plaintext highlighter-rouge">zstd</code>）等按业务语义配置；在 Redpanda 上仍应压测验证。</li>
  <li><strong>版本与升级</strong>：生产集群升级前阅读 <a href="https://docs.redpanda.com/">升级指南</a>，在 staging 用同版本 <code class="language-plaintext highlighter-rouge">rpk container</code> 或快照恢复演练。</li>
  <li><strong>生产调优</strong>：裸机或 VM 上运行正式集群时，执行 <code class="language-plaintext highlighter-rouge">rpk redpanda tune</code> 并按文档调整内核与磁盘；容器内 <code class="language-plaintext highlighter-rouge">--overprovisioned</code> <strong>仅用于开发</strong>。</li>
</ol>

<h2 id="注意事项">注意事项</h2>

<ul>
  <li><strong>许可证与功能</strong>：社区版与企业/云功能不同，WASM、分层存储、跨云复制等以 <a href="https://docs.redpanda.com/">Redpanda 文档</a> 与商务条款为准。</li>
  <li><strong>镜像名称</strong>：旧文提及 <code class="language-plaintext highlighter-rouge">vectorized/redpanda</code>，请改用 <strong><code class="language-plaintext highlighter-rouge">redpandadata/redpanda</code></strong>（见 <a href="https://hub.docker.com/r/redpandadata/redpanda">Docker Hub</a>）。</li>
  <li><strong>advertise 地址</strong>：客户端若在 Docker 网络外，需把 <code class="language-plaintext highlighter-rouge">--advertise-kafka-addr</code> 设为客户端能访问的 IP/主机名，否则 metadata 会返回不可达地址。</li>
  <li><strong>数据持久化</strong>：上述 <code class="language-plaintext highlighter-rouge">docker run --rm</code> 不落盘；需要保留数据时去掉 <code class="language-plaintext highlighter-rouge">--rm</code> 并挂载 volume，或使用 <code class="language-plaintext highlighter-rouge">rpk container</code> 的持久化行为。</li>
  <li><strong>不是银弹</strong>：强依赖 Kafka Connect 特定插件、Kafka Streams 内部主题或极新版本 Admin API 时，应保留 Kafka 对照环境做回归。</li>
</ul>

<h2 id="小结">小结</h2>

<p>Redpanda 以 <strong>Kafka 协议兼容</strong> 降低学习与迁移成本，以 <strong>无 ZK、无 JVM</strong> 简化架构与本地环境，适合作为<strong>开发测试默认后端</strong>、<strong>新事件平台候选</strong>或<strong>资源敏感场景</strong>的备选。选型时在「生态完整度（Kafka）」与「运维简洁/性能（Redpanda）」之间按上表决策，并用真实 workload 做基准与兼容性验证。</p>

<p>当教程要求「起一个 Kafka」时，若机器能跑 Docker，优先：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">--rm</span> <span class="nt">-p</span> 9092:9092 redpandadata/redpanda:latest <span class="se">\</span>
  redpanda start <span class="nt">--overprovisioned</span> <span class="nt">--smp</span> 1 <span class="nt">--memory</span> 512M <span class="nt">--reserve-memory</span> 0M <span class="se">\</span>
  <span class="nt">--node-id</span> 0 <span class="nt">--check</span><span class="o">=</span><span class="nb">false</span> <span class="se">\</span>
  <span class="nt">--kafka-addr</span> 0.0.0.0:9092 <span class="nt">--advertise-kafka-addr</span> 127.0.0.1:9092
</code></pre></div></div>

<p>需要多副本与分区高可用演练时，再使用 <strong><code class="language-plaintext highlighter-rouge">rpk container start -n 3</code></strong>。</p>

<h1 id="refer">Refer</h1>

<p><strong>官方</strong></p>

<ul>
  <li><a href="https://docs.redpanda.com/">Redpanda 文档</a> — Quick Start、Docker Compose、升级与调优</li>
  <li><a href="https://hub.docker.com/r/redpandadata/redpanda">redpandadata/redpanda — Docker Hub</a> — 镜像说明与标签</li>
  <li><a href="https://www.redpanda.com/blog/rpk-container">Introducing rpk container</a> — 本地多节点集群</li>
  <li><a href="https://github.com/redpanda-data/redpanda">redpanda-data/redpanda — GitHub</a> — 源码与 Release</li>
</ul>

<p><strong>对比与选型</strong></p>

<ul>
  <li><a href="https://kafka.apache.org/documentation/">Apache Kafka 文档</a> — 概念与配置基准</li>
  <li><a href="https://www.redpanda.com/compare/apache-kafka">Redpanda vs Kafka</a> — 厂商对比页（阅读时注意营销表述，以自测为准）</li>
</ul>

<p><strong>Go 客户端</strong></p>

<ul>
  <li><a href="https://github.com/IBM/sarama">IBM/sarama</a> — Go Kafka 客户端；<a href="https://github.com/IBM/sarama/tree/main/examples"><code class="language-plaintext highlighter-rouge">examples/</code></a>、<a href="https://github.com/IBM/sarama/tree/main/mocks"><code class="language-plaintext highlighter-rouge">mocks/</code></a>、<a href="https://pkg.go.dev/github.com/IBM/sarama">pkg.go.dev</a></li>
  <li><a href="https://github.com/segmentio/kafka-go">segmentio/kafka-go</a> — 另一常用 Go 客户端（本文 kafka-go 示例）</li>
</ul>]]></content><author><name></name></author><category term="云原生" /><category term="Redpanda" /><category term="Kafka" /><category term="Sarama" /><category term="消息队列" /><category term="流式计算" /><summary type="html"><![CDATA[介绍 Redpanda 流式平台的核心概念、本地与 Docker 用法、与 Kafka 的差异及选型；含 IBM Sarama Go 客户端在本机 Redpanda 上验证上报数据的实践。]]></summary></entry><entry><title type="html">ripgrep (rg)：比 grep 更高效的搜索工具</title><link href="http://172.19.0.16:8080/posts/ripgrep-in-action/" rel="alternate" type="text/html" title="ripgrep (rg)：比 grep 更高效的搜索工具" /><published>2026-05-22T16:50:00+08:00</published><updated>2026-06-01T17:04:39+08:00</updated><id>http://172.19.0.16:8080/posts/ripgrep-in-action</id><content type="html" xml:base="http://172.19.0.16:8080/posts/ripgrep-in-action/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#概述" id="markdown-toc-概述">概述</a></li>
  <li><a href="#原理与核心概念" id="markdown-toc-原理与核心概念">原理与核心概念</a>    <ul>
      <li><a href="#rg-在做什么" id="markdown-toc-rg-在做什么">rg 在做什么</a></li>
      <li><a href="#为什么比-grep-快实现原理" id="markdown-toc-为什么比-grep-快实现原理">为什么比 grep 快：实现原理</a>        <ul>
          <li><a href="#总览快在哪里" id="markdown-toc-总览快在哪里">总览：快在哪里</a></li>
          <li><a href="#并行多核同时搜多个文件" id="markdown-toc-并行多核同时搜多个文件">并行：多核同时搜多个文件</a></li>
          <li><a href="#少搜gitignore-与默认过滤" id="markdown-toc-少搜gitignore-与默认过滤">少搜：<code class="language-plaintext highlighter-rouge">.gitignore</code> 与默认过滤</a></li>
          <li><a href="#iommap-与缓冲策略" id="markdown-toc-iommap-与缓冲策略">I/O：mmap 与缓冲策略</a></li>
          <li><a href="#正则引擎为搜索设计而不是为最强表达力" id="markdown-toc-正则引擎为搜索设计而不是为最强表达力">正则引擎：为「搜索」设计，而不是为「最强表达力」</a>            <ul>
              <li><a href="#字面量literal快速路径" id="markdown-toc-字面量literal快速路径">字面量（literal）快速路径</a></li>
              <li><a href="#通用正则自动机--预筛选" id="markdown-toc-通用正则自动机--预筛选">通用正则：自动机 + 预筛选</a></li>
              <li><a href="#和-gnu-grep-的-pcre-对比概念上" id="markdown-toc-和-gnu-grep-的-pcre-对比概念上">和 GNU grep 的 PCRE 对比（概念上）</a></li>
            </ul>
          </li>
          <li><a href="#编码与行处理" id="markdown-toc-编码与行处理">编码与行处理</a></li>
          <li><a href="#为什么不是所有情况-rg-都更快" id="markdown-toc-为什么不是所有情况-rg-都更快">为什么不是「所有情况 rg 都更快」</a></li>
          <li><a href="#一句话总结" id="markdown-toc-一句话总结">一句话总结</a></li>
        </ul>
      </li>
      <li><a href="#与-grep-如何分工" id="markdown-toc-与-grep-如何分工">与 grep 如何分工</a></li>
    </ul>
  </li>
  <li><a href="#安装" id="markdown-toc-安装">安装</a>    <ul>
      <li><a href="#macos--linux-包管理器" id="markdown-toc-macos--linux-包管理器">macOS / Linux 包管理器</a></li>
      <li><a href="#发布页与版本" id="markdown-toc-发布页与版本">发布页与版本</a></li>
    </ul>
  </li>
  <li><a href="#快速上手" id="markdown-toc-快速上手">快速上手</a></li>
  <li><a href="#grep-与-rg-功能对照" id="markdown-toc-grep-与-rg-功能对照">grep 与 rg 功能对照</a></li>
  <li><a href="#场景用法rg-与等价-grep" id="markdown-toc-场景用法rg-与等价-grep">场景用法：rg 与等价 grep</a>    <ul>
      <li><a href="#1-在代码库里搜一个词最常用" id="markdown-toc-1-在代码库里搜一个词最常用">1. 在代码库里搜一个词（最常用）</a></li>
      <li><a href="#2-大小写不敏感" id="markdown-toc-2-大小写不敏感">2. 大小写不敏感</a></li>
      <li><a href="#3-整词匹配避免-foo-命中-foobar" id="markdown-toc-3-整词匹配避免-foo-命中-foobar">3. 整词匹配（避免 <code class="language-plaintext highlighter-rouge">foo</code> 命中 <code class="language-plaintext highlighter-rouge">foobar</code>）</a></li>
      <li><a href="#4-固定字符串路径ip含--的文本" id="markdown-toc-4-固定字符串路径ip含--的文本">4. 固定字符串（路径、IP、含 <code class="language-plaintext highlighter-rouge">.</code> 的文本）</a></li>
      <li><a href="#5-只搜某类扩展名--语言" id="markdown-toc-5-只搜某类扩展名--语言">5. 只搜某类扩展名 / 语言</a></li>
      <li><a href="#6-排除路径或目录" id="markdown-toc-6-排除路径或目录">6. 排除路径或目录</a></li>
      <li><a href="#7-列出包含匹配的文件名" id="markdown-toc-7-列出包含匹配的文件名">7. 列出包含匹配的文件名</a></li>
      <li><a href="#8-列出不包含匹配的文件grep-的--l" id="markdown-toc-8-列出不包含匹配的文件grep-的--l">8. 列出「不包含」匹配的文件（grep 的 -L）</a></li>
      <li><a href="#9-显示匹配次数" id="markdown-toc-9-显示匹配次数">9. 显示匹配次数</a></li>
      <li><a href="#10-上下文看调用附近几行" id="markdown-toc-10-上下文看调用附近几行">10. 上下文（看调用附近几行）</a></li>
      <li><a href="#11-排除匹配行看没出现某关键字的行" id="markdown-toc-11-排除匹配行看没出现某关键字的行">11. 排除匹配行（看「没出现某关键字」的行）</a></li>
      <li><a href="#12-限制目录深度" id="markdown-toc-12-限制目录深度">12. 限制目录深度</a></li>
      <li><a href="#13-搜索隐藏文件与-ignore-之外的文件" id="markdown-toc-13-搜索隐藏文件与-ignore-之外的文件">13. 搜索隐藏文件与 ignore 之外的文件</a></li>
      <li><a href="#14-pcre2-正则lookahead反向引用等" id="markdown-toc-14-pcre2-正则lookahead反向引用等">14. PCRE2 正则（lookahead、反向引用等）</a></li>
      <li><a href="#15-多行匹配跨行模式" id="markdown-toc-15-多行匹配跨行模式">15. 多行匹配（跨行模式）</a></li>
      <li><a href="#16-在指定文件或-stdin-中搜" id="markdown-toc-16-在指定文件或-stdin-中搜">16. 在指定文件或 stdin 中搜</a></li>
      <li><a href="#17-与-findxargs-组合只要文件名再-grep" id="markdown-toc-17-与-findxargs-组合只要文件名再-grep">17. 与 find、xargs 组合（只要文件名再 grep）</a></li>
      <li><a href="#18-输出给脚本解析" id="markdown-toc-18-输出给脚本解析">18. 输出给脚本解析</a></li>
    </ul>
  </li>
  <li><a href="#常用选项速查" id="markdown-toc-常用选项速查">常用选项速查</a></li>
  <li><a href="#最佳实践" id="markdown-toc-最佳实践">最佳实践</a></li>
  <li><a href="#注意事项" id="markdown-toc-注意事项">注意事项</a></li>
  <li><a href="#小结" id="markdown-toc-小结">小结</a></li>
  <li><a href="#官方仓库参考github" id="markdown-toc-官方仓库参考github">官方仓库参考（GitHub）</a>    <ul>
      <li><a href="#文档速链仓库-readme" id="markdown-toc-文档速链仓库-readme">文档速链（仓库 README）</a></li>
      <li><a href="#为何选用-ripgrep官方归纳" id="markdown-toc-为何选用-ripgrep官方归纳">为何选用 ripgrep（官方归纳）</a></li>
      <li><a href="#何时不必用-ripgrep" id="markdown-toc-何时不必用-ripgrep">何时不必用 ripgrep</a></li>
      <li><a href="#官方性能说明与-benchmark" id="markdown-toc-官方性能说明与-benchmark">官方性能说明与 benchmark</a></li>
      <li><a href="#与其它搜索工具的功能对比" id="markdown-toc-与其它搜索工具的功能对比">与其它搜索工具的功能对比</a></li>
      <li><a href="#安装与构建仓库摘要" id="markdown-toc-安装与构建仓库摘要">安装与构建（仓库摘要）</a></li>
      <li><a href="#相关生态" id="markdown-toc-相关生态">相关生态</a></li>
    </ul>
  </li>
  <li><a href="#参考资源" id="markdown-toc-参考资源">参考资源</a>    <ul>
      <li><a href="#官方github--文档" id="markdown-toc-官方github--文档">官方（GitHub / 文档）</a></li>
      <li><a href="#其它" id="markdown-toc-其它">其它</a></li>
    </ul>
  </li>
</ul>

<blockquote class="prompt-info">
  <p><a href="https://github.com/BurntSushi/ripgrep">ripgrep</a> 的命令名是 <strong><code class="language-plaintext highlighter-rouge">rg</code></strong>，在目录里按<strong>正则</strong>递归搜索<strong>文件内容</strong>。它是面向大型代码库的<strong>高效版 <code class="language-plaintext highlighter-rouge">grep -r</code></strong>：通过并行、少搜（<code class="language-plaintext highlighter-rouge">.gitignore</code>）、mmap/缓冲读与针对搜索优化的正则引擎，整体吞吐通常明显高于传统递归 <code class="language-plaintext highlighter-rouge">grep</code>；同时默认带行号、着色与智能大小写，使用上也比手写一长串 <code class="language-plaintext highlighter-rouge">--exclude-dir</code> 更简洁。</p>
</blockquote>

<p>本文与站内 <a href="/posts/bash-in-action/">Bash in Action</a>（管道、<code class="language-plaintext highlighter-rouge">grep</code> 别名）、<a href="/posts/linux-in-action/">Linux in Action</a> 互补：日常在仓库、日志目录里「搜一段文本」，优先用 <code class="language-plaintext highlighter-rouge">rg</code>；需要与 POSIX 脚本、极简环境或 <code class="language-plaintext highlighter-rouge">grep</code> 独有选项对齐时，再对照下文 <strong>grep 等价写法</strong>。</p>

<h2 id="概述">概述</h2>

<p>在终端里找「哪个文件哪一行出现了某段文字」，是开发、运维的高频动作。传统做法是：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="nt">-rn</span> <span class="s2">"pattern"</span> /path/to/project
</code></pre></div></div>

<p>还要自己排除 <code class="language-plaintext highlighter-rouge">node_modules</code>、<code class="language-plaintext highlighter-rouge">.git</code>、<code class="language-plaintext highlighter-rouge">vendor</code> 等，否则又慢又吵。<code class="language-plaintext highlighter-rouge">rg</code> 从设计上优先<strong>搜得快、搜得准</strong>：在 Git 仓库里自动读 <code class="language-plaintext highlighter-rouge">.gitignore</code> / <code class="language-plaintext highlighter-rouge">.ignore</code>（缩小搜索空间），默认跳过隐藏文件与二进制，多线程并行读盘与匹配，并针对字面量与常见正则走快速路径（详见下文实现原理）。</p>

<p>本文覆盖：</p>

<ul>
  <li><strong>原理与核心概念</strong>（为何比 <code class="language-plaintext highlighter-rouge">grep -r</code> 快、与 grep 的分工）</li>
  <li><strong>安装</strong></li>
  <li><strong>grep 与 rg 功能对照表</strong></li>
  <li><strong>场景用法</strong>：每种场景给出 <code class="language-plaintext highlighter-rouge">rg</code> 与对应的 <code class="language-plaintext highlighter-rouge">grep</code> 写法</li>
  <li><strong>最佳实践与注意事项</strong></li>
  <li><strong>官方 GitHub 仓库</strong>要点与文档速链</li>
  <li>文末 <strong>参考资源</strong></li>
</ul>

<h2 id="原理与核心概念">原理与核心概念</h2>

<h3 id="rg-在做什么">rg 在做什么</h3>

<p><code class="language-plaintext highlighter-rouge">rg</code> 只做一件事：<strong>在（一组）文件的内容里匹配正则</strong>，把命中的行（及文件名、行号）打印到标准输出。它不替代 <code class="language-plaintext highlighter-rouge">find</code> 找文件名、不替代 <code class="language-plaintext highlighter-rouge">sed</code>/<code class="language-plaintext highlighter-rouge">awk</code> 改文件；和 <code class="language-plaintext highlighter-rouge">grep</code> 一样，核心是 <strong>stream / file content search</strong>。</p>

<pre><code class="language-mermaid">flowchart TB
    subgraph 输入
        PATH["目录或文件路径"]
        PAT["正则 / 固定字符串"]
    end
    subgraph rg
        WALK["遍历文件\n(并行)"]
        FILTER["过滤\n.gitignore / glob / type"]
        MATCH["正则引擎\n(默认 Rust regex)"]
    end
    subgraph 输出
        OUT["file:line:col: 匹配行"]
    end
    PATH --&gt; WALK
    PAT --&gt; MATCH
    WALK --&gt; FILTER --&gt; MATCH --&gt; OUT
</code></pre>

<h3 id="为什么比-grep-快实现原理">为什么比 grep 快：实现原理</h3>

<p><code class="language-plaintext highlighter-rouge">rg</code>（ripgrep）比传统 <strong><code class="language-plaintext highlighter-rouge">grep -r</code> 快</strong>，通常不是因为「正则引擎魔法」，而是<strong>整条搜索流水线</strong>——从算法、I/O、并行到「少搜文件」——都针对「在目录里找文本」做了优化。下面按实现原理说明；与 GNU grep 的细节以你本机 <code class="language-plaintext highlighter-rouge">grep --version</code> 为准。</p>

<h4 id="总览快在哪里">总览：快在哪里</h4>

<p>两条典型路径对比如下：</p>

<pre><code class="language-mermaid">flowchart LR
    subgraph grep_r["grep -r 典型路径"]
        G1["单线程遍历目录"]
        G2["read 读文件"]
        G3["POSIX 正则逐行匹配"]
        G1 --&gt; G2 --&gt; G3
    end
    subgraph rg_path["rg 典型路径"]
        R1["并行遍历 + gitignore 剪枝"]
        R2["mmap / 缓冲读"]
        R3["字面量/SIMD 预筛 + 自动机匹配"]
        R1 --&gt; R2 --&gt; R3
    end
</code></pre>

<table>
  <thead>
    <tr>
      <th>环节</th>
      <th><code class="language-plaintext highlighter-rouge">grep -r</code>（常见 GNU grep）</th>
      <th><code class="language-plaintext highlighter-rouge">rg</code></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>目录遍历</td>
      <td>多为单线程</td>
      <td>多线程并行搜多个文件</td>
    </tr>
    <tr>
      <td>搜哪些文件</td>
      <td>常扫到 <code class="language-plaintext highlighter-rouge">vendor/</code>、<code class="language-plaintext highlighter-rouge">.git/</code> 等</td>
      <td>默认按 <code class="language-plaintext highlighter-rouge">.gitignore</code> 跳过大量路径</td>
    </tr>
    <tr>
      <td>读文件</td>
      <td><code class="language-plaintext highlighter-rouge">read()</code> 循环</td>
      <td>大文件常用 mmap，减少拷贝与系统调用</td>
    </tr>
    <tr>
      <td>正则引擎</td>
      <td>POSIX 正则（能力/语义不同）</td>
      <td>Rust <strong>regex</strong> 库：保证线性时间、针对搜索优化</td>
    </tr>
    <tr>
      <td>简单模式</td>
      <td>仍走通用正则路径</td>
      <td>可走<strong>字面量 + SIMD</strong> 等快速路径</td>
    </tr>
  </tbody>
</table>

<table>
  <thead>
    <tr>
      <th>维度（体验层面）</th>
      <th>ripgrep (<code class="language-plaintext highlighter-rouge">rg</code>)</th>
      <th>典型 <code class="language-plaintext highlighter-rouge">grep -r</code></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>忽略规则</td>
      <td>默认读 <code class="language-plaintext highlighter-rouge">.gitignore</code>、<code class="language-plaintext highlighter-rouge">.ignore</code>、<code class="language-plaintext highlighter-rouge">.rgignore</code></td>
      <td>需手写 <code class="language-plaintext highlighter-rouge">--exclude-dir</code> / <code class="language-plaintext highlighter-rouge">--exclude</code></td>
    </tr>
    <tr>
      <td>二进制</td>
      <td>默认跳过，避免乱码刷屏</td>
      <td>常需 <code class="language-plaintext highlighter-rouge">-I</code> 或误扫大文件</td>
    </tr>
    <tr>
      <td>输出</td>
      <td>默认带行号、彩色、智能大小写</td>
      <td>需 <code class="language-plaintext highlighter-rouge">-n</code>、<code class="language-plaintext highlighter-rouge">-i</code>、<code class="language-plaintext highlighter-rouge">--color</code> 等叠加</td>
    </tr>
  </tbody>
</table>

<h4 id="并行多核同时搜多个文件">并行：多核同时搜多个文件</h4>

<p><code class="language-plaintext highlighter-rouge">grep -r</code> 一般是：<strong>一个线程</strong>递归目录，逐个文件打开、读、匹配。</p>

<p><code class="language-plaintext highlighter-rouge">rg</code> 在目录模式下会：</p>

<ol>
  <li>先收集（或流式产出）一批待搜文件路径；</li>
  <li>用<strong>线程池</strong>把文件分给多个 worker，每个 worker 独立读 + 搜。</li>
</ol>

<p>磁盘若是随机读，并行不一定线性加速；但在<strong>多文件、中小文件</strong>、以及<strong>代码仓库</strong>（成千上万个小 <code class="language-plaintext highlighter-rouge">.go</code> / <code class="language-plaintext highlighter-rouge">.ts</code> 文件）这类场景里，CPU 与 I/O 能重叠，整体吞吐往往明显高于单线程 <code class="language-plaintext highlighter-rouge">grep -r</code>。</p>

<h4 id="少搜gitignore-与默认过滤">少搜：<code class="language-plaintext highlighter-rouge">.gitignore</code> 与默认过滤</h4>

<p><code class="language-plaintext highlighter-rouge">rg</code> 默认会解析 <code class="language-plaintext highlighter-rouge">.gitignore</code>（以及 <code class="language-plaintext highlighter-rouge">.ignore</code>、<code class="language-plaintext highlighter-rouge">.rgignore</code> 等），<strong>根本不打开</strong>被忽略的文件。</p>

<p>典型 Go 仓库里，<code class="language-plaintext highlighter-rouge">vendor/</code>、构建产物、<code class="language-plaintext highlighter-rouge">node_modules/</code>、<code class="language-plaintext highlighter-rouge">.git/</code> 等可能占大量 inode；<code class="language-plaintext highlighter-rouge">grep -r .</code> 若没手写 <code class="language-plaintext highlighter-rouge">--exclude-dir</code>，会白读很多字节。这不是「匹配算法更快」，而是<strong>搜索空间更小</strong>——对体感速度的影响往往非常大。</p>

<h4 id="iommap-与缓冲策略">I/O：mmap 与缓冲策略</h4>

<p>对每个文件，<code class="language-plaintext highlighter-rouge">rg</code> 会按大小选择策略，常见包括：</p>

<ul>
  <li><strong>小文件</strong>：读进内存缓冲区，顺序扫描；</li>
  <li><strong>大文件</strong>：<strong>mmap</strong> 映射到地址空间，用指针在内存里扫，减少 <code class="language-plaintext highlighter-rouge">read()</code> 次数和用户态/内核态拷贝。</li>
</ul>

<p><code class="language-plaintext highlighter-rouge">grep</code> 也可以用 mmap（部分实现/选项），但默认递归场景下，<code class="language-plaintext highlighter-rouge">rg</code> 把「大块连续内存 + 顺序扫描」与并行结合得更系统。</p>

<p class="prompt-tip prompt-tip">mmap 不是万能：极小文件、冷缓存、网络盘上，有时普通 <code class="language-plaintext highlighter-rouge">read</code> 更合适。<code class="language-plaintext highlighter-rouge">rg</code> 会根据文件大小和平台做启发式选择（详见官方 FAQ）。</p>

<h4 id="正则引擎为搜索设计而不是为最强表达力">正则引擎：为「搜索」设计，而不是为「最强表达力」</h4>

<p>GNU <code class="language-plaintext highlighter-rouge">grep</code> 常用 POSIX 基本/扩展正则（实现因版本而异），语义偏传统 Unix。</p>

<p><code class="language-plaintext highlighter-rouge">rg</code> 使用 Rust 生态的 <a href="https://github.com/rust-lang/regex"><strong>regex</strong></a> crate（作者 Andrew Gallant，也是 ripgrep 作者），设计目标之一是：</p>

<ul>
  <li>保证匹配时间在输入长度上<strong>线性</strong>（避免灾难性回溯把 CPU 吃光）；</li>
  <li><strong>不支持</strong>容易指数爆炸的特性（如回溯型 look-around 等，除非用 <code class="language-plaintext highlighter-rouge">-P</code> 走 PCRE2）；</li>
  <li>针对「在文本里找 pattern」做大量编译期与运行期优化。</li>
</ul>

<h5 id="字面量literal快速路径">字面量（literal）快速路径</h5>

<p>若 pattern 其实是普通字符串（无复杂元字符），引擎会识别为<strong>字面量搜索</strong>，可能使用：</p>

<ul>
  <li><strong>Boyer-Moore / memchr</strong> 类算法：先快速找首字节或子串候选，再确认；</li>
  <li><strong>SIMD</strong>：一次比较 16/32 字节（依赖 CPU 的 SSE/AVX 等），适合在长行、大文件里找固定子串。</li>
</ul>

<p>这是 <code class="language-plaintext highlighter-rouge">rg</code> 在代码搜索场景（大量 <code class="language-plaintext highlighter-rouge">import "foo/bar"</code> 这类固定串）特别快的重要原因之一。显式固定串可用 <code class="language-plaintext highlighter-rouge">rg -F</code>。</p>

<h5 id="通用正则自动机--预筛选">通用正则：自动机 + 预筛选</h5>

<p>复杂一点的正则，会编译成有限状态自动机（NFA/DFA 等组合），在扫描每一行或每一块 buffer 时做状态转移。实现里还会做：</p>

<ul>
  <li><strong>字面量提取</strong>：从正则里抽出「必定出现的子串」，先用快路径缩小位置，再在候选位置跑完整自动机（「过滤 + 验证」两阶段）；</li>
  <li><strong>行锚定优化</strong>：很多搜索按行进行，利于缓存和早停。</li>
</ul>

<h5 id="和-gnu-grep-的-pcre-对比概念上">和 GNU grep 的 PCRE 对比（概念上）</h5>

<p>有人用 <code class="language-plaintext highlighter-rouge">grep -P</code>（PCRE）获得更强正则，但 PCRE 的通用回溯引擎在<strong>最坏情况</strong>下可能很慢；<code class="language-plaintext highlighter-rouge">rg</code> 的 regex crate <strong>宁可限制表达能力，也要可预期的搜索性能</strong>。这是产品定位差异，不是单纯「谁更先进」。需要 PCRE2 语义时，<code class="language-plaintext highlighter-rouge">rg -P</code> 与 <code class="language-plaintext highlighter-rouge">grep -P</code> 都依赖相应后端，仍应注意复杂模式的开销。</p>

<h4 id="编码与行处理">编码与行处理</h4>

<ul>
  <li><code class="language-plaintext highlighter-rouge">rg</code> 默认按 <strong>UTF-8</strong> 处理，并可在非法序列上采用替换/宽松策略，避免为完整 Unicode 规范化付出极高代价（除非你需要）。</li>
  <li>搜索通常是<strong>按行</strong>或按 buffer 扫描，配合「显示行号、上下文」的流水线，内存访问模式对 CPU 缓存较友好。</li>
  <li><code class="language-plaintext highlighter-rouge">grep</code> 在字节流上同样高效，但若涉及复杂多字节 locale，历史行为与选项组合更多，路径不一定为「搜代码仓库」优化。</li>
</ul>

<h4 id="为什么不是所有情况-rg-都更快">为什么不是「所有情况 rg 都更快」</h4>

<table>
  <thead>
    <tr>
      <th>场景</th>
      <th>原因</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>单个大文件、管道输入</td>
      <td>并行用不上；谁快取决于引擎与 I/O</td>
    </tr>
    <tr>
      <td>必须搜 <code class="language-plaintext highlighter-rouge">.git</code> / 被 ignore 的目录</td>
      <td><code class="language-plaintext highlighter-rouge">rg</code> 要加 <code class="language-plaintext highlighter-rouge">--no-ignore</code>，「少搜」优势减弱</td>
    </tr>
    <tr>
      <td>极简单 pattern + 已精心 exclude 的 <code class="language-plaintext highlighter-rouge">grep</code></td>
      <td>两者都很快，差在毫秒级</td>
    </tr>
    <tr>
      <td>POSIX 特有语义</td>
      <td><code class="language-plaintext highlighter-rouge">rg</code> 语法与 <code class="language-plaintext highlighter-rouge">grep -E</code> 不完全相同，不能无脑替换</td>
    </tr>
  </tbody>
</table>

<blockquote class="prompt-tip">
  <p>性能并非绝对：<code class="language-plaintext highlighter-rouge">rg</code> 在<strong>已正确排除无关目录</strong>的小树上，与 <code class="language-plaintext highlighter-rouge">grep</code> 差距可能不大；优势主要体现在<strong>大型 monorepo、未精心写 exclude 的递归搜索</strong>。</p>
</blockquote>

<h4 id="一句话总结">一句话总结</h4>

<p><code class="language-plaintext highlighter-rouge">rg</code> 比 <code class="language-plaintext highlighter-rouge">grep -r</code> 快，往往是<strong>乘法效应</strong>：</p>

<ol>
  <li><strong>少搜</strong>（gitignore / 默认跳过二进制等）；</li>
  <li><strong>并行搜</strong>（多文件多核）；</li>
  <li><strong>读得快</strong>（mmap / 缓冲）；</li>
  <li><strong>匹配得快</strong>（字面量 + SIMD + 线性时间正则 + 两阶段过滤）。</li>
</ol>

<p><code class="language-plaintext highlighter-rouge">grep</code> 作为单文件、脚本、POSIX 语义的工具依然合适；在<strong>大型代码仓库</strong>里做递归内容搜索，才是 <code class="language-plaintext highlighter-rouge">rg</code> 按整条流水线优化的主场。延伸阅读：<a href="https://github.com/BurntSushi/ripgrep#why-should-i-use-ripgrep-instead-of-grep">ripgrep 为何快（官方说明）</a>、<a href="https://github.com/rust-lang/regex#design">regex crate 设计</a>。</p>

<h3 id="与-grep-如何分工">与 grep 如何分工</h3>

<ul>
  <li><strong>读代码库、搜日志目录</strong>：优先 <code class="language-plaintext highlighter-rouge">rg</code>。</li>
  <li><strong>只处理 stdin 的极简管道</strong>、<strong>脚本要求 POSIX grep</strong>、<strong>系统只有 BusyBox grep</strong>：继续用 <code class="language-plaintext highlighter-rouge">grep</code>。</li>
  <li><strong>要改文件内容</strong>：两者都不负责；用编辑器、<code class="language-plaintext highlighter-rouge">sed</code> 或 <code class="language-plaintext highlighter-rouge">rg</code> 作者另写的 <a href="https://github.com/phracker/ripgrep-all">rga</a> 等工具。</li>
</ul>

<h2 id="安装">安装</h2>

<h3 id="macos--linux-包管理器">macOS / Linux 包管理器</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># macOS</span>
brew <span class="nb">install </span>ripgrep

<span class="c"># Debian / Ubuntu</span>
<span class="nb">sudo </span>apt <span class="nb">install </span>ripgrep

<span class="c"># Fedora</span>
<span class="nb">sudo </span>dnf <span class="nb">install </span>ripgrep

<span class="c"># Arch</span>
<span class="nb">sudo </span>pacman <span class="nt">-S</span> ripgrep
</code></pre></div></div>

<h3 id="发布页与版本">发布页与版本</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg <span class="nt">--version</span>
</code></pre></div></div>

<p>二进制与源码发布见 <a href="https://github.com/BurntSushi/ripgrep/releases">GitHub Releases</a>。安装后命令名是 <strong><code class="language-plaintext highlighter-rouge">rg</code></strong>，包名多为 <code class="language-plaintext highlighter-rouge">ripgrep</code>。</p>

<h2 id="快速上手">快速上手</h2>

<p>在仓库根目录搜索字符串 <code class="language-plaintext highlighter-rouge">TODO</code>（递归、带行号、尊重 ignore）：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg TODO
</code></pre></div></div>

<p>功能上接近、但通常<strong>更慢</strong>且更易漏排除的 <code class="language-plaintext highlighter-rouge">grep</code> 写法：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="nt">-rn</span> TODO <span class="nb">.</span> <span class="se">\</span>
  <span class="nt">--exclude-dir</span><span class="o">=</span>.git <span class="se">\</span>
  <span class="nt">--exclude-dir</span><span class="o">=</span>node_modules
<span class="c"># 仍可能扫到 .gitignore 里已忽略但 grep 不知道的目录</span>
</code></pre></div></div>

<p>只搜当前目录下 <strong>Go 文件</strong>：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg TODO <span class="nt">-t</span> go
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="nt">-rn</span> TODO <span class="nb">.</span> <span class="nt">--include</span><span class="o">=</span><span class="s1">'*.go'</span>
</code></pre></div></div>

<h2 id="grep-与-rg-功能对照">grep 与 rg 功能对照</h2>

<p>下表按<strong>常用能力</strong>对照；「rg 默认」表示不写该选项时的行为。</p>

<table>
  <thead>
    <tr>
      <th>能力</th>
      <th>ripgrep (<code class="language-plaintext highlighter-rouge">rg</code>)</th>
      <th>GNU grep（递归场景）</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>递归搜索</td>
      <td>默认开启</td>
      <td><code class="language-plaintext highlighter-rouge">grep -r</code> / <code class="language-plaintext highlighter-rouge">grep -R</code></td>
      <td><code class="language-plaintext highlighter-rouge">rg</code> 对目录默认递归</td>
    </tr>
    <tr>
      <td>行号</td>
      <td>默认显示 <code class="language-plaintext highlighter-rouge">-n</code></td>
      <td><code class="language-plaintext highlighter-rouge">grep -n</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>忽略大小写</td>
      <td><code class="language-plaintext highlighter-rouge">-i</code></td>
      <td><code class="language-plaintext highlighter-rouge">grep -i</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>智能大小写</td>
      <td>默认：含大写则区分</td>
      <td>无，需 <code class="language-plaintext highlighter-rouge">-i</code></td>
      <td><code class="language-plaintext highlighter-rouge">rg</code> 的 <strong>smart case</strong></td>
    </tr>
    <tr>
      <td>整词匹配</td>
      <td><code class="language-plaintext highlighter-rouge">-w</code></td>
      <td><code class="language-plaintext highlighter-rouge">grep -w</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>固定字符串</td>
      <td><code class="language-plaintext highlighter-rouge">-F</code></td>
      <td><code class="language-plaintext highlighter-rouge">grep -F</code></td>
      <td>不解释正则元字符</td>
    </tr>
    <tr>
      <td>正则类型</td>
      <td>默认 Rust regex；<code class="language-plaintext highlighter-rouge">-P</code> PCRE2</td>
      <td>默认 BRE/ERE；<code class="language-plaintext highlighter-rouge">grep -E</code>；<code class="language-plaintext highlighter-rouge">-P</code> PCRE</td>
      <td> </td>
    </tr>
    <tr>
      <td>反选行</td>
      <td><code class="language-plaintext highlighter-rouge">-v</code></td>
      <td><code class="language-plaintext highlighter-rouge">grep -v</code></td>
      <td>打印<strong>不匹配</strong>的行</td>
    </tr>
    <tr>
      <td>仅文件名</td>
      <td><code class="language-plaintext highlighter-rouge">-l</code></td>
      <td><code class="language-plaintext highlighter-rouge">grep -l</code></td>
      <td>有匹配即列出文件</td>
    </tr>
    <tr>
      <td>无匹配的文件</td>
      <td><code class="language-plaintext highlighter-rouge">--files-without-match</code>（无 <code class="language-plaintext highlighter-rouge">-L</code> 短选项）</td>
      <td><code class="language-plaintext highlighter-rouge">grep -L</code></td>
      <td><code class="language-plaintext highlighter-rouge">grep -L</code> 与 <code class="language-plaintext highlighter-rouge">rg -L</code> <strong>不是一回事</strong>，见下行</td>
    </tr>
    <tr>
      <td>统计行数</td>
      <td><code class="language-plaintext highlighter-rouge">-c</code></td>
      <td><code class="language-plaintext highlighter-rouge">grep -c</code></td>
      <td>每文件一行计数</td>
    </tr>
    <tr>
      <td>上下文</td>
      <td><code class="language-plaintext highlighter-rouge">-A</code> / <code class="language-plaintext highlighter-rouge">-B</code> / <code class="language-plaintext highlighter-rouge">-C</code></td>
      <td>同左</td>
      <td> </td>
    </tr>
    <tr>
      <td>最大匹配数</td>
      <td><code class="language-plaintext highlighter-rouge">-m N</code></td>
      <td><code class="language-plaintext highlighter-rouge">grep -m N</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>隐藏文件</td>
      <td><code class="language-plaintext highlighter-rouge">--hidden</code></td>
      <td>无统一项；靠 find</td>
      <td>默认跳过点文件</td>
    </tr>
    <tr>
      <td>二进制当文本</td>
      <td><code class="language-plaintext highlighter-rouge">-a</code> / <code class="language-plaintext highlighter-rouge">--text</code></td>
      <td><code class="language-plaintext highlighter-rouge">grep -a</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>跳过二进制</td>
      <td>默认跳过</td>
      <td><code class="language-plaintext highlighter-rouge">grep -I</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>跟随符号链接</td>
      <td><code class="language-plaintext highlighter-rouge">-L</code> / <code class="language-plaintext highlighter-rouge">--follow</code></td>
      <td><code class="language-plaintext highlighter-rouge">grep -R</code> 会跟；<code class="language-plaintext highlighter-rouge">grep -r</code> 一般不跟</td>
      <td>与 <code class="language-plaintext highlighter-rouge">find -L</code> 一致；<strong>勿与</strong> <code class="language-plaintext highlighter-rouge">grep -L</code> 混淆</td>
    </tr>
    <tr>
      <td>文件类型</td>
      <td><code class="language-plaintext highlighter-rouge">-t go</code>、<code class="language-plaintext highlighter-rouge">-t py</code> 等</td>
      <td><code class="language-plaintext highlighter-rouge">--include='*.go'</code></td>
      <td><code class="language-plaintext highlighter-rouge">rg --type-list</code> 查看</td>
    </tr>
    <tr>
      <td>Glob 过滤</td>
      <td><code class="language-plaintext highlighter-rouge">-g '*.rs' -g '!tests/*'</code></td>
      <td><code class="language-plaintext highlighter-rouge">--include</code> / <code class="language-plaintext highlighter-rouge">--exclude</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>尊重 gitignore</td>
      <td><strong>默认</strong></td>
      <td>无</td>
      <td><code class="language-plaintext highlighter-rouge">rg --no-ignore</code> 关闭</td>
    </tr>
    <tr>
      <td>并行</td>
      <td>内置</td>
      <td>无</td>
      <td> </td>
    </tr>
    <tr>
      <td>替换/删除</td>
      <td><strong>不支持</strong></td>
      <td><code class="language-plaintext highlighter-rouge">grep</code> 也不改文件</td>
      <td>用 <code class="language-plaintext highlighter-rouge">sed</code> / 编辑器</td>
    </tr>
    <tr>
      <td>JSON 输出</td>
      <td><code class="language-plaintext highlighter-rouge">--json</code></td>
      <td>无</td>
      <td>便于工具链解析</td>
    </tr>
  </tbody>
</table>

<p class="prompt-warning prompt-warning"><strong>选项同名不同义</strong>：<code class="language-plaintext highlighter-rouge">grep -L</code> 列出「没有任何匹配行」的文件，对应 <code class="language-plaintext highlighter-rouge">rg --files-without-match</code>（ripgrep 未把 <code class="language-plaintext highlighter-rouge">-L</code> 留给该用途）；<code class="language-plaintext highlighter-rouge">rg -L</code> / <code class="language-plaintext highlighter-rouge">--follow</code> 表示<strong>跟随符号链接</strong>。对照时以 <code class="language-plaintext highlighter-rouge">rg --help</code> 为准。</p>

<h2 id="场景用法rg-与等价-grep">场景用法：rg 与等价 grep</h2>

<p>以下均在项目根目录 <code class="language-plaintext highlighter-rouge">.</code> 下举例；把 <code class="language-plaintext highlighter-rouge">PATTERN</code> 换成实际正则或固定串。</p>

<h3 id="1-在代码库里搜一个词最常用">1. 在代码库里搜一个词（最常用）</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg PATTERN
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="nt">-rn</span> PATTERN <span class="nb">.</span>
</code></pre></div></div>

<p>若还要排除常见目录（仍不如 gitignore 完整）：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="nt">-rn</span> PATTERN <span class="nb">.</span> <span class="se">\</span>
  <span class="nt">--exclude-dir</span><span class="o">=</span>.git <span class="se">\</span>
  <span class="nt">--exclude-dir</span><span class="o">=</span>node_modules <span class="se">\</span>
  <span class="nt">--exclude-dir</span><span class="o">=</span>vendor <span class="se">\</span>
  <span class="nt">--exclude-dir</span><span class="o">=</span>dist
</code></pre></div></div>

<h3 id="2-大小写不敏感">2. 大小写不敏感</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg <span class="nt">-i</span> PATTERN
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="nt">-rin</span> PATTERN <span class="nb">.</span>
</code></pre></div></div>

<h3 id="3-整词匹配避免-foo-命中-foobar">3. 整词匹配（避免 <code class="language-plaintext highlighter-rouge">foo</code> 命中 <code class="language-plaintext highlighter-rouge">foobar</code>）</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg <span class="nt">-w</span> foo
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="nt">-rnw</span> foo <span class="nb">.</span>
</code></pre></div></div>

<h3 id="4-固定字符串路径ip含--的文本">4. 固定字符串（路径、IP、含 <code class="language-plaintext highlighter-rouge">.</code> 的文本）</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg <span class="nt">-F</span> <span class="s1">'api.example.com/v1'</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="nt">-rnF</span> <span class="s1">'api.example.com/v1'</span> <span class="nb">.</span>
</code></pre></div></div>

<h3 id="5-只搜某类扩展名--语言">5. 只搜某类扩展名 / 语言</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg PATTERN <span class="nt">-t</span> rust
rg PATTERN <span class="nt">-g</span> <span class="s1">'*.md'</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="nt">-rn</span> PATTERN <span class="nb">.</span> <span class="nt">--include</span><span class="o">=</span><span class="s1">'*.rs'</span>
<span class="nb">grep</span> <span class="nt">-rn</span> PATTERN <span class="nb">.</span> <span class="nt">--include</span><span class="o">=</span><span class="s1">'*.md'</span>
</code></pre></div></div>

<h3 id="6-排除路径或目录">6. 排除路径或目录</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg PATTERN <span class="nt">-g</span> <span class="s1">'!**/tests/**'</span> <span class="nt">-g</span> <span class="s1">'!**/vendor/**'</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="nt">-rn</span> PATTERN <span class="nb">.</span> <span class="nt">--exclude-dir</span><span class="o">=</span>tests <span class="nt">--exclude-dir</span><span class="o">=</span>vendor
</code></pre></div></div>

<p>在 Git 仓库里，多数排除应写在 <code class="language-plaintext highlighter-rouge">.gitignore</code>，<code class="language-plaintext highlighter-rouge">rg</code> 会自动遵守，无需重复 <code class="language-plaintext highlighter-rouge">-g</code>。</p>

<h3 id="7-列出包含匹配的文件名">7. 列出包含匹配的文件名</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg <span class="nt">-l</span> PATTERN
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="nt">-rl</span> PATTERN <span class="nb">.</span>
</code></pre></div></div>

<h3 id="8-列出不包含匹配的文件grep-的--l">8. 列出「不包含」匹配的文件（grep 的 -L）</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># grep：无匹配行的文件</span>
<span class="nb">grep</span> <span class="nt">-rL</span> PATTERN <span class="nb">.</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># rg：等价能力用长选项（-L 已用于 --follow）</span>
rg <span class="nt">--files-without-match</span> PATTERN
</code></pre></div></div>

<p class="prompt-danger prompt-danger">不要混淆 <strong><code class="language-plaintext highlighter-rouge">grep -L</code></strong>（无匹配的文件）与 <strong><code class="language-plaintext highlighter-rouge">rg -L</code></strong>（跟随符号链接）。列出无匹配文件请用 <strong><code class="language-plaintext highlighter-rouge">rg --files-without-match</code></strong>。</p>

<h3 id="9-显示匹配次数">9. 显示匹配次数</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg <span class="nt">-c</span> PATTERN
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="nt">-rc</span> PATTERN <span class="nb">.</span>
</code></pre></div></div>

<h3 id="10-上下文看调用附近几行">10. 上下文（看调用附近几行）</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg <span class="nt">-C</span> 3 PATTERN
rg <span class="nt">-B</span> 2 <span class="nt">-A</span> 5 PATTERN
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="nt">-rnC</span> 3 PATTERN <span class="nb">.</span>
<span class="nb">grep</span> <span class="nt">-rn</span> <span class="nt">-B</span> 2 <span class="nt">-A</span> 5 PATTERN <span class="nb">.</span>
</code></pre></div></div>

<h3 id="11-排除匹配行看没出现某关键字的行">11. 排除匹配行（看「没出现某关键字」的行）</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg <span class="nt">-v</span> DEBUG
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="nt">-rnv</span> DEBUG <span class="nb">.</span>
</code></pre></div></div>

<h3 id="12-限制目录深度">12. 限制目录深度</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg PATTERN <span class="nt">--max-depth</span> 2
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">grep</code> 无 <code class="language-plaintext highlighter-rouge">--max-depth</code>，常见写法：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>find <span class="nb">.</span> <span class="nt">-maxdepth</span> 2 <span class="nt">-type</span> f <span class="nt">-exec</span> <span class="nb">grep</span> <span class="nt">-Hn</span> PATTERN <span class="o">{}</span> +
</code></pre></div></div>

<h3 id="13-搜索隐藏文件与-ignore-之外的文件">13. 搜索隐藏文件与 ignore 之外的文件</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg PATTERN <span class="nt">--hidden</span>
rg PATTERN <span class="nt">--no-ignore</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="nt">-rn</span> PATTERN <span class="nb">.</span> <span class="nt">--exclude-dir</span><span class="o">=</span>   <span class="c"># 仍不会自动读 .gitignore</span>
<span class="c"># 常配合 find 显式包含点文件</span>
find <span class="nb">.</span> <span class="nt">-name</span> <span class="s1">'.*'</span> <span class="nt">-prune</span> <span class="nt">-o</span> <span class="nt">-type</span> f <span class="nt">-print0</span> | xargs <span class="nt">-0</span> <span class="nb">grep</span> <span class="nt">-Hn</span> PATTERN
</code></pre></div></div>

<h3 id="14-pcre2-正则lookahead反向引用等">14. PCRE2 正则（lookahead、反向引用等）</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg <span class="nt">-P</span> <span class="s1">'\d+(?=px)'</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="nt">-rPn</span> <span class="s1">'\d+(?=px)'</span> <span class="nb">.</span>
</code></pre></div></div>

<p>需系统 <code class="language-plaintext highlighter-rouge">grep</code> 编译时带 PCRE（GNU grep 的 <code class="language-plaintext highlighter-rouge">-P</code>）。</p>

<h3 id="15-多行匹配跨行模式">15. 多行匹配（跨行模式）</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg <span class="nt">-U</span> <span class="s1">'struct \{[\s\S]*?name:'</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="nt">-rPzo</span> <span class="s1">'struct \{[\s\S]*?name:'</span> <span class="nb">.</span>   <span class="c"># GNU grep，-z 以 NUL 分隔「行」</span>
</code></pre></div></div>

<p>跨行搜索都需谨慎，性能与误报率更高。</p>

<h3 id="16-在指定文件或-stdin-中搜">16. 在指定文件或 stdin 中搜</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg PATTERN path/to/file.go
<span class="nb">cat </span>app.log | rg PATTERN
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">grep</span> <span class="nt">-n</span> PATTERN path/to/file.go
<span class="nb">grep </span>PATTERN &lt; app.log
</code></pre></div></div>

<h3 id="17-与-findxargs-组合只要文件名再-grep">17. 与 find、xargs 组合（只要文件名再 grep）</h3>

<p>传统写法：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>find <span class="nb">.</span> <span class="nt">-name</span> <span class="s1">'*.c'</span> <span class="nt">-print0</span> | xargs <span class="nt">-0</span> <span class="nb">grep</span> <span class="nt">-Hn</span> PATTERN
</code></pre></div></div>

<p>更简：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg PATTERN <span class="nt">-g</span> <span class="s1">'*.c'</span>
</code></pre></div></div>

<h3 id="18-输出给脚本解析">18. 输出给脚本解析</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg <span class="nt">--json</span> PATTERN | jq <span class="nb">.</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">grep</code> 无内置 JSON；需自行解析 <code class="language-plaintext highlighter-rouge">file:line:content</code> 文本格式。</p>

<h2 id="常用选项速查">常用选项速查</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg <span class="nt">--help</span>
rg <span class="nt">--type-list</span>          <span class="c"># 内置 -t 类型</span>
man rg                  <span class="c"># 若系统安装了手册页</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>选项</th>
      <th>含义</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-i</code></td>
      <td>忽略大小写</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-w</code></td>
      <td>整词</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-F</code></td>
      <td>固定字符串</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-n</code></td>
      <td>行号（默认已开）</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-l</code></td>
      <td>有匹配的文件</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--files-without-match</code></td>
      <td>无匹配的文件（对应 <code class="language-plaintext highlighter-rouge">grep -L</code>，无短选项）</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-L</code> / <code class="language-plaintext highlighter-rouge">--follow</code></td>
      <td>跟随符号链接（<strong>不是</strong> <code class="language-plaintext highlighter-rouge">grep -L</code>）</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-c</code></td>
      <td>计数</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-v</code></td>
      <td>反选行</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-A</code> / <code class="language-plaintext highlighter-rouge">-B</code> / <code class="language-plaintext highlighter-rouge">-C</code></td>
      <td>上下文</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-t TYPE</code></td>
      <td>按内置类型过滤</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-g GLOB</code></td>
      <td>glob，可 <code class="language-plaintext highlighter-rouge">!</code> 排除</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--hidden</code></td>
      <td>包含隐藏文件</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--no-ignore</code></td>
      <td>不读 ignore 规则</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-a</code></td>
      <td>把二进制当文本</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-P</code></td>
      <td>PCRE2</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">-U</code></td>
      <td>多行（<code class="language-plaintext highlighter-rouge">.</code> 含换行）</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">--max-depth N</code></td>
      <td>最大目录深度</td>
    </tr>
  </tbody>
</table>

<h2 id="最佳实践">最佳实践</h2>

<ol>
  <li><strong>在 Git 仓库里维护好 <code class="language-plaintext highlighter-rouge">.gitignore</code></strong>，<code class="language-plaintext highlighter-rouge">rg</code> 会跟着省掉大量无关扫描；临时要搜被 ignore 的生成物时用 <code class="language-plaintext highlighter-rouge">--no-ignore</code> 并收窄路径。</li>
  <li><strong>先 <code class="language-plaintext highlighter-rouge">-F</code> 再考虑正则</strong>：搜错误码、域名、配置键时，避免 <code class="language-plaintext highlighter-rouge">.</code> <code class="language-plaintext highlighter-rouge">*</code> 被当成元字符。</li>
  <li><strong>用 <code class="language-plaintext highlighter-rouge">-t</code> / <code class="language-plaintext highlighter-rouge">-g</code> 收窄范围</strong>：比全仓库扫完再肉眼过滤更快。</li>
  <li><strong>别名可选</strong>：<code class="language-plaintext highlighter-rouge">alias rg='rg --smart-case'</code> 通常不必写，smart case 已是默认；若习惯始终彩色，可确认终端与 <code class="language-plaintext highlighter-rouge">rg</code> 的 <code class="language-plaintext highlighter-rouge">--color</code> 设置。</li>
  <li><strong>CI / 脚本</strong>：需要稳定、可移植时，可固定 <code class="language-plaintext highlighter-rouge">rg</code> 版本并写清 <code class="language-plaintext highlighter-rouge">--no-ignore</code> 是否使用；与仅提供 <code class="language-plaintext highlighter-rouge">grep</code> 的极简镜像兼容时保留 <code class="language-plaintext highlighter-rouge">grep</code> 路径。</li>
</ol>

<h2 id="注意事项">注意事项</h2>

<ul>
  <li><strong>不是 grep 的 drop-in</strong>：选项字母部分重叠，但 <strong><code class="language-plaintext highlighter-rouge">grep -L</code>（无匹配文件）≠ <code class="language-plaintext highlighter-rouge">rg -L</code>（跟随符号链接）</strong>；无匹配文件用 <code class="language-plaintext highlighter-rouge">rg --files-without-match</code>。<code class="language-plaintext highlighter-rouge">rg</code> 默认递归、默认 ignore。</li>
  <li><strong>不会修改文件</strong>：只做搜索；批量替换请用 IDE、<code class="language-plaintext highlighter-rouge">sed</code> 或专用工具。</li>
  <li><strong>极老环境</strong>：嵌入式或最小容器可能没有 <code class="language-plaintext highlighter-rouge">rg</code>，保留 <code class="language-plaintext highlighter-rouge">grep -r</code> 知识仍然必要。</li>
  <li><strong>搜索压缩包、PDF、Office</strong>：标准 <code class="language-plaintext highlighter-rouge">rg</code> 只搜普通文件；扩展场景见 <a href="https://github.com/phracker/ripgrep-all">ripgrep-all</a>。</li>
  <li><strong>性能</strong>：在 NFS、慢盘上，过多并行可能反而抖动；可调 <code class="language-plaintext highlighter-rouge">RG_THREADS</code> 环境变量（见官方 FAQ）。</li>
</ul>

<h2 id="小结">小结</h2>

<table>
  <thead>
    <tr>
      <th>场景</th>
      <th>推荐</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>日常在代码库找字符串</td>
      <td><code class="language-plaintext highlighter-rouge">rg PATTERN</code></td>
    </tr>
    <tr>
      <td>大型仓库递归搜索（快 + 尊重 gitignore）</td>
      <td><code class="language-plaintext highlighter-rouge">rg</code></td>
    </tr>
    <tr>
      <td>管道里只有 stdin、无 rg</td>
      <td><code class="language-plaintext highlighter-rouge">grep</code></td>
    </tr>
    <tr>
      <td>脚本强调 POSIX / 最小依赖</td>
      <td><code class="language-plaintext highlighter-rouge">grep</code></td>
    </tr>
    <tr>
      <td>跨行、复杂正则（PCRE2）</td>
      <td><code class="language-plaintext highlighter-rouge">rg -P</code> / <code class="language-plaintext highlighter-rouge">grep -P</code>（视环境）</td>
    </tr>
  </tbody>
</table>

<p>把 <code class="language-plaintext highlighter-rouge">rg</code> 当成<strong>为性能优化的 <code class="language-plaintext highlighter-rouge">grep -r</code></strong> 即可：速度来自<strong>少搜 × 并行 × I/O × 匹配</strong>整条流水线，而非单一正则技巧；顺手好用的默认选项是附加收益。对照上表的 <strong>grep 等价命令</strong>，可以在团队仍用 <code class="language-plaintext highlighter-rouge">grep</code> 的机器或文档里无缝切换说法。</p>

<h2 id="官方仓库参考github">官方仓库参考（GitHub）</h2>

<p>权威说明与发布均在 <a href="https://github.com/BurntSushi/ripgrep">BurntSushi/ripgrep</a>。README 对项目的定义是：</p>

<blockquote>
  <p><strong>ripgrep</strong> is a line-oriented search tool that recursively searches the current directory for a regex pattern. By default, ripgrep will respect gitignore rules and automatically skip hidden files/directories and binary files.</p>
</blockquote>

<p>中文可概括为：<strong>按行匹配的正则搜索工具</strong>，默认递归当前目录，并尊重 <strong>gitignore</strong>、跳过隐藏项与二进制。与 The Silver Searcher（<code class="language-plaintext highlighter-rouge">ag</code>）、<code class="language-plaintext highlighter-rouge">ack</code>、<code class="language-plaintext highlighter-rouge">grep</code> 同类，但在代码仓库场景下通常更快。许可证为 <strong>MIT / Unlicense</strong> 双许可；支持 <strong>Windows、macOS、Linux</strong>，每版提供预编译二进制。</p>

<h3 id="文档速链仓库-readme">文档速链（仓库 README）</h3>

<table>
  <thead>
    <tr>
      <th>主题</th>
      <th>链接</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>安装</td>
      <td><a href="https://github.com/BurntSushi/ripgrep#installation">README — Installation</a></td>
    </tr>
    <tr>
      <td>用户指南</td>
      <td><a href="https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md">GUIDE.md</a></td>
    </tr>
    <tr>
      <td>FAQ</td>
      <td><a href="https://github.com/BurntSushi/ripgrep/blob/master/FAQ.md">FAQ.md</a></td>
    </tr>
    <tr>
      <td>正则语法</td>
      <td><a href="https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md#regular-expression-syntax">Regex syntax</a></td>
    </tr>
    <tr>
      <td>配置文件</td>
      <td><a href="https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md#configuration-file">Configuration files</a></td>
    </tr>
    <tr>
      <td>Shell 补全</td>
      <td><a href="https://github.com/BurntSushi/ripgrep/blob/master/FAQ.md#complete">Shell completions</a></td>
    </tr>
    <tr>
      <td>从源码构建</td>
      <td><a href="https://github.com/BurntSushi/ripgrep#building">README — Building</a></td>
    </tr>
    <tr>
      <td>变更记录</td>
      <td><a href="https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md">CHANGELOG.md</a></td>
    </tr>
    <tr>
      <td>发布包</td>
      <td><a href="https://github.com/BurntSushi/ripgrep/releases">Releases</a></td>
    </tr>
    <tr>
      <td>社区文档翻译列表</td>
      <td><a href="https://github.com/BurntSushi/ripgrep#translations">README — Translations</a>（含中文、西班牙文等）</td>
    </tr>
  </tbody>
</table>

<h3 id="为何选用-ripgrep官方归纳">为何选用 ripgrep（官方归纳）</h3>

<p>README <a href="https://github.com/BurntSushi/ripgrep#why-should-i-use-ripgrep-instead-of-grep">Why should I use ripgrep?</a> 中的要点，与本文「实现原理」一致，可对照阅读：</p>

<ul>
  <li><strong>速度与过滤</strong>：在保留多数 <code class="language-plaintext highlighter-rouge">grep</code> 能力的前提下通常更快；默认递归，并自动过滤 <code class="language-plaintext highlighter-rouge">.gitignore</code> / <code class="language-plaintext highlighter-rouge">.ignore</code> / <code class="language-plaintext highlighter-rouge">.rgignore</code>、隐藏文件、二进制（全部关闭过滤用 <code class="language-plaintext highlighter-rouge">rg -uuu</code>）。</li>
  <li><strong>按类型搜</strong>：<code class="language-plaintext highlighter-rouge">rg -tpy foo</code> 只搜 Python；<code class="language-plaintext highlighter-rouge">rg -Tjs foo</code> 排除 JavaScript；可用自定义规则扩展类型（<code class="language-plaintext highlighter-rouge">rg --type-list</code>）。</li>
  <li><strong>类 grep 体验</strong>：上下文、多 pattern、彩色高亮；<strong>Unicode 默认开启且保持较快</strong>（与 GNU grep 在 Unicode 场景下的表现对比，见官方 benchmark）。</li>
  <li><strong>可选 PCRE2</strong>：<code class="language-plaintext highlighter-rouge">-P</code> / <code class="language-plaintext highlighter-rouge">--pcre2</code> 或 <code class="language-plaintext highlighter-rouge">--engine auto</code> 支持 look-around、反向引用等（默认 Rust regex 不支持）。</li>
  <li><strong>其它能力</strong>（正文未逐条展开）：按匹配替换输出（rudimentary replacements）、<code class="language-plaintext highlighter-rouge">-E/--encoding</code> 非 UTF-8 编码、<code class="language-plaintext highlighter-rouge">-z/--search-zip</code> 搜压缩包内文本、预处理过滤器、<strong>配置文件</strong>、与 <code class="language-plaintext highlighter-rouge">rg --json</code> 配合的 <a href="https://github.com/dandavison/delta">delta</a> 分页高亮等。</li>
</ul>

<h3 id="何时不必用-ripgrep">何时不必用 ripgrep</h3>

<p>官方 <a href="https://github.com/BurntSushi/ripgrep#why-shouldnt-i-use-ripgrep">Why shouldn’t I use ripgrep?</a> 归纳：</p>

<ul>
  <li>需要<strong>到处都有、符合 POSIX</strong> 的工具 → 仍用 <code class="language-plaintext highlighter-rouge">grep</code>。</li>
  <li>依赖其它工具独有、且 <code class="language-plaintext highlighter-rouge">rg</code> 尚未实现的行为（可查 FAQ / 提 issue）。</li>
  <li>存在<strong>性能边角</strong>（某类 pattern 或语料上 <code class="language-plaintext highlighter-rouge">rg</code> 不占优；README 也提醒勿只信单次 benchmark）。</li>
  <li>目标平台<strong>无法安装</strong> <code class="language-plaintext highlighter-rouge">rg</code>。</li>
</ul>

<h3 id="官方性能说明与-benchmark">官方性能说明与 benchmark</h3>

<p>README <a href="https://github.com/BurntSushi/ripgrep#is-it-really-faster-than-everything-else">Is it really faster?</a> 将「快」归结为：</p>

<ul>
  <li>基于 <strong>Rust regex</strong>（有限自动机、SIMD、字面量优化）；可选 <strong>PCRE2</strong>（<code class="language-plaintext highlighter-rouge">-P</code>）。</li>
  <li>在 <strong>完整 Unicode</strong> 下仍保持可预期性能（UTF-8 解码融入 DFA）。</li>
  <li>按文件大小在 <strong>mmap</strong> 与<strong>增量缓冲</strong>间自动选择策略。</li>
  <li>用 <strong>RegexSet</strong> 同时匹配多条 gitignore glob。</li>
  <li>使用 <strong>crossbeam</strong> 与 <strong>ignore</strong>  crate 的<strong>无锁并行</strong>目录遍历。</li>
</ul>

<p>作者在 Linux 内核源码树（<code class="language-plaintext highlighter-rouge">make defconfig &amp;&amp; make -j8</code> 后）上对比 <code class="language-plaintext highlighter-rouge">rg</code>、<code class="language-plaintext highlighter-rouge">git grep</code>、<code class="language-plaintext highlighter-rouge">ag</code>、<code class="language-plaintext highlighter-rouge">ack</code>、<code class="language-plaintext highlighter-rouge">ugrep</code> 等；README 给出多组数据，例如搜 <code class="language-plaintext highlighter-rouge">[A-Z]+_SUSPEND</code>（整词）时 <code class="language-plaintext highlighter-rouge">rg</code> 约 <strong>0.082s</strong>，<code class="language-plaintext highlighter-rouge">git grep -P</code> 约 0.273s，<code class="language-plaintext highlighter-rouge">ag</code> 约 0.443s，<code class="language-plaintext highlighter-rouge">ack</code> 约 2.9s（<strong>单次 benchmark 不足以下结论</strong>，详见 <a href="https://blog.burntsushi.net/ripgrep/">blog post on ripgrep</a>）。</p>

<p class="prompt-info prompt-info">官方也列出<strong>性能悬崖</strong>：无字面量优化机会的正则、极高匹配行数（输出成为瓶颈）、个别 pattern 下其它工具可能更快。评估时应用自己的仓库与 pattern 实测。</p>

<h3 id="与其它搜索工具的功能对比">与其它搜索工具的功能对比</h3>

<p>ack 作者维护的对比表（含 ack、ag、git-grep、GNU grep、ripgrep）：<a href="https://beyondgrep.com/feature-comparison/">beyondgrep.com — feature comparison</a>。<code class="language-plaintext highlighter-rouge">rg</code> 近年还增加了配置文件、passthru、压缩文件搜索、多行、PCRE2 等，表中部分项可能未更新，以 <a href="https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md">CHANGELOG</a> 为准。</p>

<p>同类工具简述：</p>

<table>
  <thead>
    <tr>
      <th>工具</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="https://github.com/ggreer/the_silver_searcher">The Silver Searcher</a>（<code class="language-plaintext highlighter-rouge">ag</code>）</td>
      <td>面向代码库的 C 实现搜索器</td>
    </tr>
    <tr>
      <td><a href="https://github.com/beyondgrep/ack3">ack</a></td>
      <td>Perl 实现的代码搜索，可编程过滤强</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">git grep</code></td>
      <td>在 Git 跟踪文件内搜，与索引配合</td>
    </tr>
    <tr>
      <td><a href="https://github.com/Genivia/ugrep">ugrep</a></td>
      <td>强调 Unicode 与多格式的 grep 系工具</td>
    </tr>
    <tr>
      <td><a href="https://github.com/phracker/ripgrep-all">ripgrep-all</a>（<code class="language-plaintext highlighter-rouge">rga</code>）</td>
      <td>在 <code class="language-plaintext highlighter-rouge">rg</code> 上扩展 PDF、Office 等</td>
    </tr>
  </tbody>
</table>

<h3 id="安装与构建仓库摘要">安装与构建（仓库摘要）</h3>

<p>除本文「安装」一节外，README 还列出 <strong>Chocolatey / Scoop / Winget</strong>（Windows）、<strong>Nix / Guix / cargo install</strong> 等。Rust 用户：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo <span class="nb">install </span>ripgrep
<span class="c"># 或预编译二进制：cargo binstall ripgrep</span>
</code></pre></div></div>

<p>从源码构建（需 Rust <strong>1.85.0+</strong>）：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/BurntSushi/ripgrep
<span class="nb">cd </span>ripgrep
cargo build <span class="nt">--release</span>
./target/release/rg <span class="nt">--version</span>
</code></pre></div></div>

<p>启用 PCRE2：<code class="language-plaintext highlighter-rouge">cargo build --release --features pcre2</code>。试玩无需安装可使用社区 <a href="https://codapi.org/try/rg/">playground</a> 与<a href="https://github.com/stephengrice/ripgrep-tutorial">交互教程</a>（非官方维护）。</p>

<h3 id="相关生态">相关生态</h3>

<ul>
  <li><strong>delta</strong>：对 <code class="language-plaintext highlighter-rouge">rg --json</code> 输出做语法高亮分页，用法 <code class="language-plaintext highlighter-rouge">rg --json PATTERN | delta</code>（见 <a href="https://dandavison.github.io/delta/grep.html">delta 手册 — grep</a>）。</li>
  <li><strong>安全</strong>：漏洞报告见 README <a href="https://github.com/BurntSushi/ripgrep#vulnerability-reporting">Vulnerability reporting</a>。</li>
</ul>

<h2 id="参考资源">参考资源</h2>

<h3 id="官方github--文档">官方（GitHub / 文档）</h3>

<table>
  <thead>
    <tr>
      <th>说明</th>
      <th>链接</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>ripgrep 官方仓库</strong></td>
      <td><a href="https://github.com/BurntSushi/ripgrep">BurntSushi/ripgrep</a></td>
    </tr>
    <tr>
      <td>README 总览</td>
      <td><a href="https://github.com/BurntSushi/ripgrep/blob/master/README.md">README.md</a></td>
    </tr>
    <tr>
      <td>为何用 / 为何不用</td>
      <td><a href="https://github.com/BurntSushi/ripgrep#why-should-i-use-ripgrep-instead-of-grep">Why ripgrep</a> · <a href="https://github.com/BurntSushi/ripgrep#why-shouldnt-i-use-ripgrep">Why not</a></td>
    </tr>
    <tr>
      <td>是否更快（benchmark 摘要）</td>
      <td><a href="https://github.com/BurntSushi/ripgrep#is-it-really-faster-than-everything-else">Is it really faster?</a></td>
    </tr>
    <tr>
      <td>工具功能对比表（ack 站）</td>
      <td><a href="https://beyondgrep.com/feature-comparison/">Feature comparison</a></td>
    </tr>
    <tr>
      <td>用户指南</td>
      <td><a href="https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md">GUIDE.md</a></td>
    </tr>
    <tr>
      <td>FAQ</td>
      <td><a href="https://github.com/BurntSushi/ripgrep/blob/master/FAQ.md">FAQ.md</a></td>
    </tr>
    <tr>
      <td>变更记录</td>
      <td><a href="https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md">CHANGELOG.md</a></td>
    </tr>
    <tr>
      <td>发布与二进制</td>
      <td><a href="https://github.com/BurntSushi/ripgrep/releases">Releases</a></td>
    </tr>
    <tr>
      <td>手册页 <code class="language-plaintext highlighter-rouge">rg(1)</code></td>
      <td><a href="https://github.com/BurntSushi/ripgrep/blob/master/doc/rg.1">doc/rg.1</a> 或系统 <code class="language-plaintext highlighter-rouge">man rg</code></td>
    </tr>
    <tr>
      <td>作者详细 benchmark 博文</td>
      <td><a href="https://blog.burntsushi.net/ripgrep/">Andrew Gallant — ripgrep</a></td>
    </tr>
    <tr>
      <td>Rust regex 库（默认引擎）</td>
      <td><a href="https://github.com/rust-lang/regex">rust-lang/regex</a></td>
    </tr>
  </tbody>
</table>

<h3 id="其它">其它</h3>

<table>
  <thead>
    <tr>
      <th>说明</th>
      <th>链接</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>GNU grep 手册</td>
      <td><a href="https://www.gnu.org/software/grep/manual/">grep 文档</a></td>
    </tr>
    <tr>
      <td>delta（配合 <code class="language-plaintext highlighter-rouge">rg --json</code>）</td>
      <td><a href="https://github.com/dandavison/delta">dandavison/delta</a></td>
    </tr>
    <tr>
      <td>ripgrep-all（扩展文件类型）</td>
      <td><a href="https://github.com/phracker/ripgrep-all">phracker/ripgrep-all</a></td>
    </tr>
    <tr>
      <td>站内：Bash 与 grep 习惯</td>
      <td><a href="/posts/bash-in-action/">Bash in Action</a></td>
    </tr>
    <tr>
      <td>站内：Linux 运维</td>
      <td><a href="/posts/linux-in-action/">Linux in Action</a></td>
    </tr>
  </tbody>
</table>]]></content><author><name></name></author><category term="Linux" /><category term="Linux" /><category term="ripgrep" /><category term="grep" /><summary type="html"><![CDATA[介绍 ripgrep（命令 rg）：比 grep 更高效的目录内容搜索工具，含实现原理、安装、常用选项、与 grep 功能对照及典型场景等价写法。]]></summary></entry></feed>