<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>终点线技术博客 — AI 编程开发经验</title><description>AI 编程开发经验（仅公开文章全文）</description><link>https://www.tkrjw.cn/</link><language>zh-cn</language><item><title>Astro 混合渲染怎么做？SSG 与 SSR 按页面级切换的完整实践</title><link>https://www.tkrjw.cn/posts/dev/astro-hybrid-rendering/</link><guid isPermaLink="true">https://www.tkrjw.cn/posts/dev/astro-hybrid-rendering/</guid><description>Astro 5 混合渲染实操指南：output: &apos;server&apos; + 适配器，用一行 prerender 决定页面走静态生成还是服务端渲染，附付费墙场景的完整落地方案。</description><pubDate>Mon, 28 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Astro 的混合渲染只需要两个动作：全局设 &lt;code&gt;output: &apos;server&apos;&lt;/code&gt;，再在静态页面顶部加一行 &lt;code&gt;export const prerender = true&lt;/code&gt;。这样构建期就把首页、列表页打成静态 HTML，只有真正需要读 Cookie 的付费文章页走 Node 服务端按请求渲染，性能与权限门控两全。&lt;/p&gt;
&lt;h2 id=&quot;为什么内容站需要混合渲染&quot;&gt;为什么内容站需要混合渲染？&lt;/h2&gt;
&lt;p&gt;纯静态最快，但付费墙必须在服务端读会话并截断正文；纯 SSR 灵活，却把首页、归档这类&amp;quot;内容永不变&amp;quot;的页面也变成了每请求计算。混合渲染的本质是：&lt;strong&gt;按页面分配渲染时机&lt;/strong&gt;——内容在构建期已确定的页面在构建期渲染，内容依赖请求上下文的页面在请求期渲染。&lt;/p&gt;
&lt;h2 id=&quot;怎么配置-astro-5-的混合渲染&quot;&gt;怎么配置 Astro 5 的混合渲染？&lt;/h2&gt;
&lt;p&gt;先装 Node 适配器，然后在 &lt;code&gt;astro.config.mjs&lt;/code&gt; 里声明 server 输出：&lt;/p&gt;
&lt;pre class=&quot;shiki shiki-themes one-light one-dark-pro&quot; style=&quot;--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;// astro.config.mjs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E45649;--shiki-dark:#E06C75&quot;&gt;defineConfig&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt; &apos;astro/config&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E45649;--shiki-dark:#E06C75&quot;&gt; node&lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt; &apos;@astrojs/node&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E45649;--shiki-dark:#C678DD&quot;&gt; default&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt; defineConfig&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E45649;--shiki-dark:#E06C75&quot;&gt;  output&lt;/span&gt;&lt;span style=&quot;--shiki-light:#0184BC;--shiki-dark:#ABB2BF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt; &apos;server&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;,                 &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;// 默认 SSR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#E45649;--shiki-dark:#E06C75&quot;&gt;  adapter&lt;/span&gt;&lt;span style=&quot;--shiki-light:#0184BC;--shiki-dark:#ABB2BF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt; node&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E45649;--shiki-dark:#E06C75&quot;&gt;mode&lt;/span&gt;&lt;span style=&quot;--shiki-light:#0184BC;--shiki-dark:#ABB2BF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt; &apos;standalone&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;静态页面在文件顶部声明预渲染：&lt;/p&gt;
&lt;pre class=&quot;shiki shiki-themes one-light one-dark-pro&quot; style=&quot;--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-astro&quot;&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;// src/pages/index.astro&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#986801;--shiki-dark:#E5C07B&quot;&gt; prerender&lt;/span&gt;&lt;span style=&quot;--shiki-light:#0184BC;--shiki-dark:#56B6C2&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#986801;--shiki-dark:#D19A66&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;;      &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;// 这一行让该页在构建期生成静态 HTML&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;prerender&lt;/code&gt; 是&lt;strong&gt;页面级&lt;/strong&gt;开关，不影响其他路由。需要按请求读 Cookie 的页面保持默认（SSR）即可。&lt;/p&gt;
&lt;h2 id=&quot;付费文章页为什么要-ssr&quot;&gt;付费文章页为什么要 SSR？&lt;/h2&gt;
&lt;p&gt;判断&amp;quot;当前用户能不能看全文&amp;quot;只能发生在服务端：读会话 Cookie → 查会员身份/单篇授权 → 决定输出全文还是截断版。这个流程发生在每一条请求上，因此文章详情页整体走 SSR 是最简单可靠的做法。&lt;/p&gt;
&lt;pre class=&quot;shiki shiki-themes one-light one-dark-pro&quot; style=&quot;--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;// src/pages/posts/[...slug].astro（示意）&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#986801;--shiki-dark:#E5C07B&quot;&gt; prerender&lt;/span&gt;&lt;span style=&quot;--shiki-light:#0184BC;--shiki-dark:#56B6C2&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#986801;--shiki-dark:#D19A66&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;;     &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;// SSR：每请求判定权限&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#986801;--shiki-dark:#E5C07B&quot;&gt;slug&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;--shiki-light:#0184BC;--shiki-dark:#56B6C2&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#E5C07B&quot;&gt; Astro&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E45649;--shiki-dark:#E06C75&quot;&gt;params&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#986801;--shiki-dark:#E5C07B&quot;&gt; entry&lt;/span&gt;&lt;span style=&quot;--shiki-light:#0184BC;--shiki-dark:#56B6C2&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt; getEntry&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt;&apos;posts&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#E06C75&quot;&gt;slug&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#986801;--shiki-dark:#E5C07B&quot;&gt; decision&lt;/span&gt;&lt;span style=&quot;--shiki-light:#0184BC;--shiki-dark:#56B6C2&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt; resolvePostAccess&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#E5C07B&quot;&gt;Astro&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E45649;--shiki-dark:#E5C07B&quot;&gt;locals&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#E45649;--shiki-dark:#E06C75&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#E06C75&quot;&gt;entry&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;// decision.full ? 输出全文 : 输出服务端截断后的墙前内容&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两个必须守住的底线：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;截断在服务端完成&lt;/strong&gt;——绝不能把全文发给浏览器再用 CSS/JS 藏起来，查看源码即泄露；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;墙前内容进初始 HTML&lt;/strong&gt;——标题、目录、摘要、FAQ 必须是服务端渲染的静态标签，AI 爬虫多数不执行 JS。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;上线前怎么验证效果&quot;&gt;上线前怎么验证效果？&lt;/h2&gt;
&lt;p&gt;三步：&lt;code&gt;npm run build&lt;/code&gt; 检查构建日志中各页面的渲染方式；用 &lt;code&gt;curl&lt;/code&gt; 匿名请求一篇付费文章，确认返回的 HTML 里没有付费段正文；跑一次 Lighthouse 确认静态页性能达标。更完整的权限判定实现可参考本站源码 &lt;code&gt;src/lib/access.js&lt;/code&gt;，页面级切换的思路也适用于 &lt;a href=&quot;/posts/dev/node-stream-backpressure&quot;&gt;Node.js 流式处理&lt;/a&gt; 等服务端场景。&lt;/p&gt;
&lt;h2 id=&quot;常见坑&quot;&gt;常见坑&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;中间件在构建期也会执行&lt;/strong&gt;：混合输出下，prerender 页面在 build 时会跑一遍中间件，因此中间件里不要做&amp;quot;必须有请求上下文&amp;quot;的强假设（读 Cookie 返回空是正常的）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;适配器与托管平台绑定&lt;/strong&gt;：&lt;code&gt;@astrojs/node&lt;/code&gt; 用于自有服务器/Nginx 反代，上 Vercel/Netlify 则换对应适配器。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sitemap 要自己补 SSR 页面&lt;/strong&gt;：&lt;code&gt;@astrojs/sitemap&lt;/code&gt; 只收录预渲染页，付费文章页需要手工加入站点地图。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>astro</category><category>ssr</category><category>ssg</category><category>性能</category></item><item><title>Node.js 流为什么能省内存？背压机制详解与实战代码</title><link>https://www.tkrjw.cn/posts/dev/node-stream-backpressure/</link><guid isPermaLink="true">https://www.tkrjw.cn/posts/dev/node-stream-backpressure/</guid><description>用 Node.js 流（Stream）处理大文件能保持常数级内存占用，核心在于背压机制：readable.pipe/writable.cork 之后的 drain 信号让生产者等待消费者，本文附可运行示例与常见误区。</description><pubDate>Sun, 30 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Node.js 流省内存的关键不是&amp;quot;分块读&amp;quot;本身，而是&lt;strong&gt;背压（backpressure）&lt;/strong&gt;：当下游消费不过来时，&lt;code&gt;write()&lt;/code&gt; 返回 &lt;code&gt;false&lt;/code&gt;，上游暂停生产，直到 &lt;code&gt;drain&lt;/code&gt; 事件通知缓冲已排空再继续。这让内存占用与文件大小无关，始终保持在常数级。&lt;/p&gt;
&lt;h2 id=&quot;用-buffer-一次性读文件会发生什么&quot;&gt;用 Buffer 一次性读文件会发生什么？&lt;/h2&gt;
&lt;pre class=&quot;shiki shiki-themes one-light one-dark-pro&quot; style=&quot;--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;// 反例：整个文件进内存&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E45649;--shiki-dark:#E06C75&quot;&gt;readFileSync&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt; &apos;node:fs&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#986801;--shiki-dark:#E5C07B&quot;&gt; buf&lt;/span&gt;&lt;span style=&quot;--shiki-light:#0184BC;--shiki-dark:#56B6C2&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt; readFileSync&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt;&apos;access-10gb.log&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;// 10GB 日志 → OOM 或换页抖动&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;处理 10GB 文件时，这段代码至少需要 10GB 堆内存，V8 默认堆上限内直接崩溃。而同样的任务用流只需几十 MB：&lt;/p&gt;
&lt;pre class=&quot;shiki shiki-themes one-light one-dark-pro&quot; style=&quot;--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;// 正例：常数级内存&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E45649;--shiki-dark:#E06C75&quot;&gt;createReadStream&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E45649;--shiki-dark:#E06C75&quot;&gt;createWriteStream&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt; &apos;node:fs&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E45649;--shiki-dark:#E06C75&quot;&gt;pipeline&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt; &apos;node:stream/promises&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E45649;--shiki-dark:#E06C75&quot;&gt;createGzip&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt; &apos;node:zlib&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt; pipeline&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt;  createReadStream&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt;&apos;access-10gb.log&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;),   &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;// 按块读&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt;  createGzip&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(),                          &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;// 边读边压&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt;  createWriteStream&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt;&apos;access-10gb.log.gz&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;背压到底是怎么工作的&quot;&gt;背压到底是怎么工作的？&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;writable.write(chunk)&lt;/code&gt; 把数据放进内部缓冲区（上限由 &lt;code&gt;highWaterMark&lt;/code&gt; 控制，默认 64KB）。当缓冲超过上限，&lt;code&gt;write()&lt;/code&gt; 返回 &lt;code&gt;false&lt;/code&gt;——这不是报错，而是&lt;strong&gt;协议信号&lt;/strong&gt;：请暂停写入。缓冲被消费完之后触发 &lt;code&gt;drain&lt;/code&gt; 事件，上游恢复写入。手写这个协议长这样：&lt;/p&gt;
&lt;pre class=&quot;shiki shiki-themes one-light one-dark-pro&quot; style=&quot;--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;// 手动背压协作（理解原理用，生产推荐 pipeline）&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#986801;--shiki-dark:#E5C07B&quot;&gt; readable&lt;/span&gt;&lt;span style=&quot;--shiki-light:#0184BC;--shiki-dark:#56B6C2&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt; createReadStream&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt;&apos;in.log&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#986801;--shiki-dark:#E5C07B&quot;&gt; writable&lt;/span&gt;&lt;span style=&quot;--shiki-light:#0184BC;--shiki-dark:#56B6C2&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt; createWriteStream&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt;&apos;out.log&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#E5C07B&quot;&gt;readable&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt;&apos;data&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic&quot;&gt;chunk&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;  if&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#0184BC;--shiki-dark:#56B6C2&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#E5C07B&quot;&gt;writable&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt;write&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#E06C75&quot;&gt;chunk&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;)) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#E5C07B&quot;&gt;    readable&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt;pause&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;();                  &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;// 缓冲已满，暂停生产&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#E5C07B&quot;&gt;    writable&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt;once&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt;&apos;drain&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#E5C07B&quot;&gt; readable&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt;resume&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;()); &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;// 排空后恢复&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#E5C07B&quot;&gt;readable&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt;on&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt;&apos;end&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#E5C07B&quot;&gt; writable&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt;end&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;());&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pipe()&lt;/code&gt; 和 &lt;code&gt;pipeline()&lt;/code&gt; 做的就是这件事的健壮版本——包括错误传播与资源清理，因此生产代码永远优先 &lt;code&gt;pipeline&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id=&quot;实战逐行处理-10gb-日志并统计&quot;&gt;实战：逐行处理 10GB 日志并统计&lt;/h2&gt;
&lt;p&gt;结合 &lt;code&gt;readline&lt;/code&gt;（基于流的行解析）与批量写库，是日志分析最常用的模式：&lt;/p&gt;
&lt;pre class=&quot;shiki shiki-themes one-light one-dark-pro&quot; style=&quot;--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E45649;--shiki-dark:#E06C75&quot;&gt;createReadStream&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt; &apos;node:fs&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E45649;--shiki-dark:#E06C75&quot;&gt;createInterface&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt; &apos;node:readline&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E45649;--shiki-dark:#E06C75&quot;&gt;pipeline&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; } &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;from&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt; &apos;node:stream/promises&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#986801;--shiki-dark:#E5C07B&quot;&gt; stats&lt;/span&gt;&lt;span style=&quot;--shiki-light:#0184BC;--shiki-dark:#56B6C2&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt; Map&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(); &lt;/span&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;// url -&gt; count&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt; pipeline&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt;  createReadStream&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt;&apos;access-10gb.log&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;, { &lt;/span&gt;&lt;span style=&quot;--shiki-light:#E45649;--shiki-dark:#E06C75&quot;&gt;encoding&lt;/span&gt;&lt;span style=&quot;--shiki-light:#0184BC;--shiki-dark:#ABB2BF&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt; &apos;utf8&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;  async&lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt; function*&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic&quot;&gt;source&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;    for&lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#986801;--shiki-dark:#E5C07B&quot;&gt; line&lt;/span&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt; of&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#E06C75&quot;&gt; source&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A626A4;--shiki-dark:#C678DD&quot;&gt;      const&lt;/span&gt;&lt;span style=&quot;--shiki-light:#986801;--shiki-dark:#E5C07B&quot;&gt; url&lt;/span&gt;&lt;span style=&quot;--shiki-light:#0184BC;--shiki-dark:#56B6C2&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#E5C07B&quot;&gt; line&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt;split&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#50A14F;--shiki-dark:#98C379&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;)[&lt;/span&gt;&lt;span style=&quot;--shiki-light:#986801;--shiki-dark:#D19A66&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#E5C07B&quot;&gt;      stats&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#E06C75&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#E5C07B&quot;&gt;stats&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#4078F2;--shiki-dark:#61AFEF&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#E06C75&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#0184BC;--shiki-dark:#56B6C2&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;--shiki-light:#986801;--shiki-dark:#D19A66&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;--shiki-light:#0184BC;--shiki-dark:#56B6C2&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;--shiki-light:#986801;--shiki-dark:#D19A66&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#383A42;--shiki-dark:#ABB2BF&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic&quot;&gt;// stats 里就是全量统计结果，峰值内存 &amp;#x3C; 100MB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;async generator 也是流：它天然遵守背压协议，上游快了会自动暂停迭代。&lt;/p&gt;
&lt;h2 id=&quot;三个常见误区&quot;&gt;三个常见误区&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;忽略 &lt;code&gt;write()&lt;/code&gt; 的返回值又不用 pipe&lt;/strong&gt;——缓冲无限增长，流等于白用；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在 &lt;code&gt;pipe&lt;/code&gt; 链里忘了错误处理&lt;/strong&gt;——某一段崩了，其他段不会自动销毁，用 &lt;code&gt;pipeline&lt;/code&gt; 一并解决；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对流做 &lt;code&gt;JSON.parse&lt;/code&gt; 整体操作&lt;/strong&gt;——流的价值在&amp;quot;逐块、无需全量&amp;quot;的场景，需要全量数据时应改用聚合策略。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;把这套思路放到 Web 服务里同样成立：本站的付费文章页就是用服务端截断输出控制响应体大小的，底层思想与 &lt;a href=&quot;/posts/dev/astro-hybrid-rendering&quot;&gt;Astro 混合渲染实践&lt;/a&gt; 一脉相承——按需生产，不做无用功。&lt;/p&gt;
</content:encoded><category>nodejs</category><category>stream</category><category>性能</category><category>后端</category></item><item><title>Prompt 工程有哪些被验证有效的模式？附可复用模板</title><link>https://www.tkrjw.cn/posts/dev/prompt-patterns/</link><guid isPermaLink="true">https://www.tkrjw.cn/posts/dev/prompt-patterns/</guid><description>沉淀 8 个在生产环境被反复验证的 Prompt 工程模式：角色与边界、少示例、结构化输出、思维链、自检回路等，每个模式给出适用场景与反例。</description><pubDate>Wed, 22 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Prompt 工程被验证有效的模式并不多：角色与边界、结构化输出、少示例、思维链、自检回路、上下文分区、失败降级、版本化评测。这 8 个模式覆盖了生产环境 90% 的场景，本文逐一给出模板与反例。&lt;/p&gt;
&lt;h2 id=&quot;为什么大多数-prompt-失败&quot;&gt;为什么大多数 Prompt 失败？&lt;/h2&gt;
&lt;p&gt;失败 Prompt 有个共同点：&lt;strong&gt;把模型当搜索引擎用&lt;/strong&gt;——一句话需求丢进去，指望它理解你没有说出口的约束。有效 Prompt 的本质是工程约定：输入是什么、输出长什么样、边界在哪、错了怎么办。下面按模式展开。&lt;/p&gt;
&lt;h2 id=&quot;模式一角色与边界role--boundary&quot;&gt;模式一：角色与边界（Role + Boundary）&lt;/h2&gt;
&lt;pre class=&quot;shiki shiki-themes one-light one-dark-pro&quot; style=&quot;--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;你是一名资深代码评审员。职责：只评审给出代码的正确性与安全问题。&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;边界：不讨论代码风格，不重写整个文件；发现的问题按 [严重度][位置][原因][建议] 四段输出。&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;如果代码无法评审（信息不足），直接回复&quot;需要补充上下文&quot;并列出缺失项。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要点：&lt;strong&gt;边界句和职责句同样重要&lt;/strong&gt;。没有边界，模型会用&amp;quot;热心&amp;quot;填补空白——重写你的代码、解释基础概念，输出不可控。&lt;/p&gt;
&lt;h2 id=&quot;模式二结构化输出schema-first&quot;&gt;模式二：结构化输出（Schema First）&lt;/h2&gt;
&lt;p&gt;用示例把格式&amp;quot;钉死&amp;quot;，而不是用形容词描述格式：&lt;/p&gt;
&lt;pre class=&quot;shiki shiki-themes one-light one-dark-pro&quot; style=&quot;--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;输出必须严格是 JSON 数组，不要任何额外文字：&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[{&quot;issue&quot;: &quot;问题描述&quot;, &quot;severity&quot;: &quot;high|medium|low&quot;, &quot;line&quot;: 12}]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;示例：&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[{&quot;issue&quot;: &quot;未校验空指针&quot;, &quot;severity&quot;: &quot;high&quot;, &quot;line&quot;: 42}]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;反例：&amp;quot;请结构化地输出你的分析结果&amp;quot;——&amp;quot;结构化&amp;quot;是人类的想象，模型需要的是具体 schema。&lt;/p&gt;
&lt;h2 id=&quot;模式三少示例few-shot&quot;&gt;模式三：少示例（Few-shot）&lt;/h2&gt;
&lt;p&gt;2–5 个精选示例的效果稳定优于零示例，也优于 20 个冗余示例。选示例的标准：&lt;strong&gt;覆盖边界情况&lt;/strong&gt;，尤其要包含一个&amp;quot;应拒绝/判空&amp;quot;的示例，否则模型对模糊输入会强行编造答案。&lt;/p&gt;
&lt;h2 id=&quot;模式四思维链按任务类型启用&quot;&gt;模式四：思维链（按任务类型启用）&lt;/h2&gt;
&lt;p&gt;推理类任务（数学、多条件判断、根因分析）在要求&amp;quot;先列出推理步骤，再给最终答案&amp;quot;后有可测量提升；简单分类与抽取任务不要加，纯属浪费 token 与延迟。经验法则：&lt;strong&gt;答案需要组合两个以上事实的任务，才值得思维链&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&quot;模式五自检回路self-check&quot;&gt;模式五：自检回路（Self-check）&lt;/h2&gt;
&lt;pre class=&quot;shiki shiki-themes one-light one-dark-pro&quot; style=&quot;--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;完成草稿后，先按以下清单自检并修正，再输出最终答案：&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1. 输出格式是否符合 schema&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2. 每个事实性断言是否有材料支撑&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3. 是否存在未处理的空值/异常分支&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实测能拦截大部分格式错误与编造断言，代价是一轮额外推理。对质量敏感、对延迟不敏感的管线值得开启。&lt;/p&gt;
&lt;h2 id=&quot;模式六上下文分区&quot;&gt;模式六：上下文分区&lt;/h2&gt;
&lt;p&gt;长上下文 prompt 按固定顺序组织：&lt;code&gt;角色与规则 → 材料（带编号） → 任务指令 → 输出格式&lt;/code&gt;。检索增强场景下，把最强证据放在材料的&lt;strong&gt;开头与结尾&lt;/strong&gt;（模型对中间内容注意力弱），这一条在 &lt;a href=&quot;/posts/dev/rag-from-scratch&quot;&gt;RAG 系统搭建实践&lt;/a&gt; 里有详细展开。&lt;/p&gt;
&lt;h2 id=&quot;模式七失败降级&quot;&gt;模式七：失败降级&lt;/h2&gt;
&lt;p&gt;预定义三档输出：正常答案 / &amp;quot;资料未覆盖&amp;quot; / &amp;quot;需要人工&amp;quot;。让模型&lt;strong&gt;有台阶下&lt;/strong&gt;，是压住幻觉最便宜的手段——大量幻觉来自模型不敢说&amp;quot;不知道&amp;quot;。&lt;/p&gt;
&lt;h2 id=&quot;模式八版本化与评测&quot;&gt;模式八：版本化与评测&lt;/h2&gt;
&lt;p&gt;Prompt 进 Git、变更写说明、每次修改跑同一套评测集（哪怕只有 30 条）。没有回归评测的 Prompt 迭代等于裸奔。把这些模式组合进工程管线的方法，可参考逆向分析里的&lt;a href=&quot;/posts/reverse/reverse-methodology&quot;&gt;提示词驱动辅助分析&lt;/a&gt;思路——方法论是通用的。&lt;/p&gt;
</content:encoded><category>prompt</category><category>llm</category><category>ai工程</category></item></channel></rss>