<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Posts on Cloudside</title><link>https://cloudside.icyyan.com/posts/</link><description>Recent content in Posts on Cloudside</description><generator>Hugo</generator><language>en</language><lastBuildDate>Fri, 03 Jul 2026 10:00:00 +0800</lastBuildDate><atom:link href="https://cloudside.icyyan.com/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>架构演进的六个时代：从原始分布式到无服务</title><link>https://cloudside.icyyan.com/posts/architecture-evolution-six-eras/</link><pubDate>Fri, 03 Jul 2026 10:00:00 +0800</pubDate><guid>https://cloudside.icyyan.com/posts/architecture-evolution-six-eras/</guid><description>&lt;p&gt;作为后端程序员，架构知识通常是一点一点在工作中捡的：数据一致性踩坑了，知道了事务的重要性；服务改用 K8s 部署，真切体会到了声明式 API 给运维带来的便利性；性能遇到瓶颈了，加上缓存系统。每块都懂一点，但这些知识之间是什么关系，为什么要这么设计，一直没有形成体系化的思考，直到读到周志明老师的《凤凰架构》。这本书完整地梳理了服务端的知识地图，将 why 和 what 的问题讲得非常清楚，给了非常清晰的宏观视角。&lt;/p&gt;
&lt;p&gt;本文是第一章的总结和读后感：每一代架构风格的诞生，都是因为上一代遇到了它解决不了的具体问题。这些问题的性质各不相同，但贯穿始终的暗线是复杂度 —— 复杂度不会消失，只会转移。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;原文出处：微信公众号「张煜中」《架构演进的六个时代：从原始分布式到无服务》，作者张煜中。本文基于该文整理，作为个人读书笔记。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="原文内容"&gt;原文内容&lt;/h2&gt;
&lt;h3 id="原始的分布式"&gt;原始的分布式&lt;/h3&gt;
&lt;p&gt;通常我们会认为，服务架构是从单体开始的，为了逃离单体大泥球架构的地狱，才搞分布式。但历史上恰恰相反。对分布式架构的探索，从 20 世纪 70 年代就开始了。那时单机算力极其有限，16 位处理器、不到 5MHz 的主频，单机直接卡住了软件能做到的规模上限。人们不得不寻找多台计算机协作支持一套软件系统的方案。&lt;/p&gt;
&lt;p&gt;UNIX 设计风格强调：保持接口与实现的简单性，比系统的任何其他属性，包括准确性、一致性和完整性，都来得更加重要。&lt;/p&gt;
&lt;p&gt;理想很美 —— 远程调用应该尽可能透明，开发者无需关心自己调的是本地方法还是远程服务。但一旦触碰到&amp;quot;远程&amp;quot;二字，网络的不确定性便会带来相当的复杂度。远程的服务在哪里（服务发现），有多少个（负载均衡），网络出现分区、超时或者服务出错了怎么办（熔断、隔离、降级），方法的参数与返回结果如何表示（序列化协议），信息如何传输（传输协议），服务权限如何管理（认证、授权），如何保证通信安全（网络安全层），如何令调用不同机器的服务返回相同的结果（分布式数据一致性）—— 每一个都需要设计者耗费大量精力。&lt;/p&gt;
&lt;p&gt;这些探索催生了 RPC、DFS 等概念，人们也得到了一个价值千金的教训：某个功能能够进行分布式，并不意味着它就应该进行分布式，强行追求透明的分布式操作，只会自寻苦果。&lt;/p&gt;
&lt;p&gt;原始分布式时代的故事，是一次发现复杂度的过程。探索者试图用&amp;quot;透明调用&amp;quot;把分布式的复杂度屏蔽掉，让开发者像写本地程序一样写分布式程序。但现实证明，网络带来的不确定性是无法假装不存在的 —— 服务发现、一致性、网络分区，这些问题不会因为你不看它就消失。这次失败的意义不在于产出了什么可用的系统，而在于让整个行业认清了分布式复杂度的真实面貌。&lt;/p&gt;
&lt;p&gt;于是当硬件性能随摩尔定律起飞后，人们做了一个务实的选择：既然分布式的复杂度屏蔽不了，那就别分布式了。&lt;/p&gt;
&lt;h3 id="单体系统时代"&gt;单体系统时代&lt;/h3&gt;
&lt;p&gt;软件退回到单体 —— 所有代码跑在同一个进程里，不用想网络，不用想一致性。&lt;/p&gt;
&lt;p&gt;大型的单体系统，经常是微服务书籍批判的对象。但一定要注意这里的定语 —— &amp;ldquo;大型的&amp;rdquo;。小型单体系统，不仅易于开发、测试、部署，且由于系统中各个功能、模块、方法的调用过程都是进程内调用，没有进程间通信，运行效率也很高。三个人的团队、一台机器撑得住的系统，搞微服务纯粹是给自己找麻烦。&lt;/p&gt;
&lt;p&gt;单体系统的缺陷在于，缺乏自治和隔离能力。进程内调用虽然简单高效，但故障也难以隔离，某个模块的 bug 能导致整个系统崩溃。而在大型系统中，出错几乎是必然的 —— 大型系统意味着多人协作、频繁变更，缺乏隔离就意味着一个模块的内存泄漏能拖垮整个进程，一次局部的代码升级需要整体停机重启。&lt;/p&gt;
&lt;p&gt;单体系统的设计哲学是「让每一部分都尽量不出错」，靠高质量来保证高可靠。但系统越大，出错越是必然。从「追求不出错」到正视「出错是必然」的观念转变，才是微服务架构得以挑战单体的底气所在。&lt;/p&gt;
&lt;p&gt;单体时代面对的不是分布式复杂度 —— 它根本没有分布式。它面对的是规模带来的复杂度，而这种复杂度体现在多个维度上：可维护性（一次局部改动需要整体停机重启）、团队协作（多人改同一个代码库，互相踩脚）、可靠性（单点故障拖垮全局）。系统小的时候，这些问题都不存在；但规模一旦上去，它们会同时爆发，而且在单体架构下无解 —— 因为缺乏隔离和自治能力。&lt;/p&gt;
&lt;p&gt;为了获得这种能力，人们再次走向分布式。&lt;/p&gt;
&lt;h3 id="soa-时代"&gt;SOA 时代&lt;/h3&gt;
&lt;p&gt;但在微服务之前，业界走过一段弯路 —— SOA。&lt;/p&gt;
&lt;p&gt;SOA 的野心极大，它不仅要解决技术问题，还想建立一套自上而下的软件研发方法论：如何挖掘需求、如何分解业务、如何编排服务，一揽子全包。它有 IBM、Oracle 等巨头撑腰，有 SOAP 协议族做底座，有企业服务总线（ESB）做通信管道，从技术可行性上看确实解决了分布式的主要问题。&lt;/p&gt;
&lt;p&gt;但问题恰恰出在「太完美」上。过于精密的规范带来过度的复杂性，SOAP 之上层层叠加的 ESB、BPM、SCA、SDO，让整个技术栈变成了只有少数专业人员才能驾驭的奢侈品。&lt;/p&gt;
&lt;p&gt;SOA 与 EJB 的失败如出一辙，一旦脱离人民群众，终究会淹没在群众的海洋之中。&lt;/p&gt;</description></item><item><title>在阿里云 ECS 上最小化部署 Marinara Engine：不在服务器上跑本地模型</title><link>https://cloudside.icyyan.com/posts/marinara-engine-minimal-deployment/</link><pubDate>Mon, 29 Jun 2026 17:15:00 +0800</pubDate><guid>https://cloudside.icyyan.com/posts/marinara-engine-minimal-deployment/</guid><description>&lt;p&gt;Marinara Engine 的官方介绍里把它称为一个本地 AI 聊天、角色扮演和游戏引擎。听起来很容易让人以为：既然是“本地”，是不是服务器上还要准备 GPU、下载模型、跑 llama.cpp 或者 embedding 服务？&lt;/p&gt;
&lt;p&gt;如果只是想把 Marinara 当成一个 Web 应用来用，答案是不需要。我的阿里云 ECS 现在跑的就是这种最小化部署：Docker 里只跑 Marinara Engine 本体，模型能力走外部 API，容器端口只绑定到 &lt;code&gt;127.0.0.1&lt;/code&gt;，再由反向代理对外提供访问。&lt;/p&gt;
&lt;p&gt;本文记录的是这个部署方式。版本核对时间是 &lt;strong&gt;2026-06-29&lt;/strong&gt;：官方最新稳定版是 &lt;code&gt;v2.0.6&lt;/code&gt;，我当前服务器上实际运行的是 &lt;code&gt;ghcr.nju.edu.cn/pasta-devs/marinara-engine:2.0.5-lite&lt;/code&gt;。新部署可以优先用最新稳定版的 &lt;code&gt;*-lite&lt;/code&gt; tag；如果你想完全复刻本文环境，就固定到 &lt;code&gt;2.0.5-lite&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="先理解这个最小化是什么意思"&gt;先理解这个“最小化”是什么意思&lt;/h2&gt;
&lt;p&gt;最小化不是功能阉割到不能用，而是把不适合小云服务器承担的部分拿掉。&lt;/p&gt;
&lt;p&gt;Marinara Engine 官方提供 lite 镜像。根据官方容器安装文档，lite 镜像去掉了三类较重的离线能力：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;本地 sidecar 模型，也就是容器内直接跑本地 LLM 的那部分。&lt;/li&gt;
&lt;li&gt;本地 embedding 模型。&lt;/li&gt;
&lt;li&gt;依赖本地 embedding 的语义记忆检索。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;保留下来的能力仍然包括聊天、角色、游戏模式、agent、lorebook、角色卡、远程 LLM API 连接等。也就是说，只要你本来就打算用 OpenAI、OpenRouter、Gemini、Anthropic 或者其他远程 OpenAI-compatible API，这种部署方式就够了。&lt;/p&gt;
&lt;p&gt;我这里的结构是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;浏览器
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; 阿里云安全组开放 80/443
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; Pingap / Nginx / Caddy 这类反向代理
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; 127.0.0.1:7860
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; Docker 容器内的 Marinara Engine
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; 远程 LLM / 图片 / TTS API
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;看到这里，问题就变成了：服务器上到底需要跑什么？答案很少：Docker、一个 compose 文件、一个 &lt;code&gt;.env&lt;/code&gt; 文件、一个反向代理入口。&lt;/p&gt;</description></item><item><title>Python 垃圾回收机制：引用计数、循环引用和分代 GC</title><link>https://cloudside.icyyan.com/posts/python-garbage-collection-guide/</link><pubDate>Sat, 20 Jun 2026 10:00:00 +0800</pubDate><guid>https://cloudside.icyyan.com/posts/python-garbage-collection-guide/</guid><description>&lt;p&gt;很多人第一次关心 Python 的垃圾回收，不是因为写了多复杂的代码，而是因为遇到了几个很实际的问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;del obj&lt;/code&gt; 之后，内存为什么没有立刻降下来？&lt;/li&gt;
&lt;li&gt;明明没有全局变量引用某个对象，它为什么还活着？&lt;/li&gt;
&lt;li&gt;Python 不是有垃圾回收吗，为什么还会出现内存泄漏？&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gc.collect()&lt;/code&gt; 到底该不该在业务代码里手动调用？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些问题背后其实是同一个机制：Python 的内存回收不是单一算法，而是几层机制一起工作。平时我们说的“Python 垃圾回收”，在最常见的 CPython 解释器里，主要由两部分组成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;引用计数：对象引用数变成 0 时，通常立刻释放。&lt;/li&gt;
&lt;li&gt;循环垃圾回收器：专门处理引用计数解决不了的循环引用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这篇文章主要讨论 CPython，因为日常用 &lt;code&gt;python&lt;/code&gt; 命令启动的解释器，大多数情况下就是 CPython。PyPy、Jython、IronPython 等实现可以采用不同策略，所以不要把本文所有细节直接套到每一种 Python 实现上。&lt;/p&gt;
&lt;h2 id="先从变量和对象的关系说起"&gt;先从变量和对象的关系说起&lt;/h2&gt;
&lt;p&gt;在 Python 里，变量不是装对象的盒子，更像是贴在对象上的名字。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;a &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;b &lt;span style="color:#f92672"&gt;=&lt;/span&gt; a
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这段代码里没有创建两个列表。它创建了一个列表对象，然后让 &lt;code&gt;a&lt;/code&gt; 和 &lt;code&gt;b&lt;/code&gt; 都指向它：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;a ─┐
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ├──&amp;gt; [1, 2, 3]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;b ─┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;执行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;del&lt;/span&gt; a
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;删除的也不是列表对象本身，而是删除名字 &lt;code&gt;a&lt;/code&gt; 到列表对象的那条引用。因为 &lt;code&gt;b&lt;/code&gt; 还指向这个列表，所以对象仍然活着：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;b ───&amp;gt; [1, 2, 3]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这就是理解 Python 垃圾回收的入口：&lt;strong&gt;对象什么时候能被回收，取决于还有没有地方能继续访问它。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;del&lt;/code&gt; 的含义也要顺手纠正一下：&lt;code&gt;del name&lt;/code&gt; 删除的是名字绑定；&lt;code&gt;del obj.attr&lt;/code&gt; 删除的是属性引用；&lt;code&gt;del some_list[i]&lt;/code&gt; 删除的是容器里的一个引用。它们都不等于“立刻把某块内存还给操作系统”。&lt;/p&gt;</description></item><item><title>Rust 里的 Box 到底是什么</title><link>https://cloudside.icyyan.com/posts/rust-box-guide/</link><pubDate>Sun, 24 May 2026 10:00:00 +0800</pubDate><guid>https://cloudside.icyyan.com/posts/rust-box-guide/</guid><description>&lt;p&gt;学 Rust 时，&lt;code&gt;Box&amp;lt;T&amp;gt;&lt;/code&gt; 很容易被一句话带过：它可以把数据放到堆上。&lt;/p&gt;
&lt;p&gt;这句话没错，但如果只记住这一句，后面看到 &lt;code&gt;Box&amp;lt;dyn Trait&amp;gt;&lt;/code&gt;、&lt;code&gt;Box&amp;lt;List&amp;gt;&lt;/code&gt;、&lt;code&gt;Pin&amp;lt;Box&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;、&lt;code&gt;Box::leak&lt;/code&gt; 时，还是会觉得它像一个突然冒出来的语法补丁。&lt;/p&gt;
&lt;p&gt;更好的理解方式是：&lt;code&gt;Box&amp;lt;T&amp;gt;&lt;/code&gt; 不是“逃离所有权系统”的工具，而是 Rust 所有权系统里最基础的一种拥有型指针。它让一个值住在堆上，同时让所有权仍然清清楚楚地归某个变量管理。&lt;/p&gt;
&lt;h2 id="先看-box-解决了什么问题"&gt;先看 Box 解决了什么问题&lt;/h2&gt;
&lt;p&gt;普通变量通常可以这样理解：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; n &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;42&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;n&lt;/code&gt; 这个值本身就放在当前栈帧里。栈很快，进入函数时分配，函数返回时回收，生命周期也很清楚。&lt;/p&gt;
&lt;p&gt;但有些场景光靠栈不够舒服：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个类型递归地包含自己，编译器算不出它的大小。&lt;/li&gt;
&lt;li&gt;你想把不同具体类型放进同一个集合，只要求它们实现同一个 trait。&lt;/li&gt;
&lt;li&gt;一个值很大，你希望移动时只移动一个指针。&lt;/li&gt;
&lt;li&gt;某些 API 需要一个固定地址的拥有型对象，比如配合 &lt;code&gt;Pin&lt;/code&gt; 使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这时 &lt;code&gt;Box&amp;lt;T&amp;gt;&lt;/code&gt; 就出现了。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; n &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Box::new(&lt;span style="color:#ae81ff"&gt;42&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以把它粗略想成这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;栈上变量 n
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; 保存一个指针
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; 指向堆上的 42
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Box&amp;lt;T&amp;gt;&lt;/code&gt; 本身在栈上，里面保存指向堆数据的指针。真正的 &lt;code&gt;T&lt;/code&gt; 在堆上。变量离开作用域时，&lt;code&gt;Box&amp;lt;T&amp;gt;&lt;/code&gt; 会自动释放堆上的 &lt;code&gt;T&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;所以 &lt;code&gt;Box&amp;lt;T&amp;gt;&lt;/code&gt; 同时有两个特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它是指针，可以间接访问堆上的值。&lt;/li&gt;
&lt;li&gt;它拥有这个值，离开作用域时负责释放它。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这和 C 里的裸指针很不一样。你不用手写 &lt;code&gt;free&lt;/code&gt;，也不能随便复制出多个拥有者。Rust 仍然会检查所有权、移动和借用。&lt;/p&gt;
&lt;h2 id="最基本的用法boxnew"&gt;最基本的用法：Box::new&lt;/h2&gt;
&lt;p&gt;创建一个 &lt;code&gt;Box&amp;lt;T&amp;gt;&lt;/code&gt; 最常见的方式是 &lt;code&gt;Box::new&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Box::new(String::from(&lt;span style="color:#e6db74"&gt;&amp;#34;cloudside&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{name}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里 &lt;code&gt;String&lt;/code&gt; 这个值由 &lt;code&gt;Box&lt;/code&gt; 放到堆上管理，&lt;code&gt;name&lt;/code&gt; 是一个拥有它的 &lt;code&gt;Box&amp;lt;String&amp;gt;&lt;/code&gt;。注意，&lt;code&gt;String&lt;/code&gt; 自己内部还会管理一块字符串缓冲区；这里说的是 &lt;code&gt;String&lt;/code&gt; 这个三字段结构本身的位置。&lt;/p&gt;</description></item><item><title>请不要用 Redis 做任何缓存之外的事</title><link>https://cloudside.icyyan.com/posts/redis-cache-only/</link><pubDate>Sat, 23 May 2026 11:00:00 +0800</pubDate><guid>https://cloudside.icyyan.com/posts/redis-cache-only/</guid><description>&lt;p&gt;Redis 是程序员工具箱里很容易被“过度信任”的东西。&lt;/p&gt;
&lt;p&gt;它太顺手了。&lt;/p&gt;
&lt;p&gt;想存个值，&lt;code&gt;SET&lt;/code&gt; 一下。想自动过期，&lt;code&gt;EXPIRE&lt;/code&gt; 一下。想做计数器，&lt;code&gt;INCR&lt;/code&gt; 一下。想搞队列，&lt;code&gt;LPUSH&lt;/code&gt; / &lt;code&gt;BRPOP&lt;/code&gt; 一下。想搞锁，&lt;code&gt;SET NX PX&lt;/code&gt; 一下。再看一眼 Stream，好像连消息系统也能顺手安排。&lt;/p&gt;
&lt;p&gt;于是很多系统就这么一路滑坡：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;第一天：Redis 只是缓存。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;第二天：这个状态先放 Redis 吧。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;第三天：队列也先放 Redis 吧。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;第四天：库存扣减也先放 Redis 吧。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;第五天：Redis 挂了，大家开始翻日志考古。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这篇文章想讲的观点很简单：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Redis 能做很多事，但不要让它单独拥有真相。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果一份数据不能丢、不能错、不能重复处理、不能悄悄过期、不能被内存策略淘汰，那它就不应该只待在 Redis 里。&lt;/p&gt;
&lt;p&gt;Redis 最舒服的位置是缓存。它可以让系统跑得更快，但不应该决定系统还活不活得明白。&lt;/p&gt;
&lt;h2 id="先看边界缓存可以重建事实不能只靠-redis"&gt;先看边界：缓存可以重建，事实不能只靠 Redis&lt;/h2&gt;
&lt;p&gt;先把话说在前面：这不是 Redis 不行。&lt;/p&gt;
&lt;p&gt;Redis 很强，而且越来越强。它有 RDB，有 AOF，有复制，有 Sentinel，有 Cluster，有 Streams，还有很多高级数据结构。Redis 8 之后，JSON、Search、Time Series、概率数据结构等能力也被合进 Redis Open Source。&lt;/p&gt;
&lt;p&gt;所以问题不是“Redis 能不能干更多活”。&lt;/p&gt;
&lt;p&gt;问题是：&lt;strong&gt;你把活交给 Redis 之后，能不能接受它的失败方式。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;缓存的失败方式很朴素：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Redis 没了
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; 缓存 miss
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; 回源数据库
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; 重新写缓存
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这很烦，但不至于让业务当场失忆。&lt;/p&gt;</description></item><item><title>Redis 底层数据结构：一个 key 背后到底藏着什么</title><link>https://cloudside.icyyan.com/posts/redis-underlying-data-structures/</link><pubDate>Sat, 23 May 2026 10:00:00 +0800</pubDate><guid>https://cloudside.icyyan.com/posts/redis-underlying-data-structures/</guid><description>&lt;p&gt;很多人第一次学 Redis，记住的是五种常用类型：String、List、Hash、Set、ZSet。再往后背面试题，又会遇到 SDS、quicklist、listpack、intset、skiplist、hashtable。&lt;/p&gt;
&lt;p&gt;这些名字放在一起很容易让人迷糊：Redis 不是 key-value 数据库吗？为什么一个 &lt;code&gt;Hash&lt;/code&gt; 后面还会有 &lt;code&gt;listpack&lt;/code&gt; 和 &lt;code&gt;hashtable&lt;/code&gt; 两种实现？为什么同样是 &lt;code&gt;Set&lt;/code&gt;，有时是 &lt;code&gt;intset&lt;/code&gt;，有时又变成哈希表？&lt;/p&gt;
&lt;p&gt;真正要理解的不是“Redis 有哪些数据结构”这张清单，而是一个更具体的问题：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Redis 为什么要让同一种对外类型，在不同场景下切换不同的内部编码？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;顺着这个问题往下看，Redis 的底层数据结构会清楚很多。它不是为了把实现弄复杂，而是在内存、CPU、查询速度、扩容成本之间做了一组很工程化的取舍。&lt;/p&gt;
&lt;p&gt;本文写作和更新时的版本背景是：截至 2026 年 5 月 23 日，Redis Open Source 最新稳定版是 &lt;code&gt;8.6.3&lt;/code&gt;，这是 2026 年 5 月发布的安全修复版本；Redis GitHub releases 里还能看到 &lt;code&gt;8.8-RC1&lt;/code&gt;，但它是预发布版，官方说明不适合生产环境使用。&lt;/p&gt;
&lt;p&gt;从底层数据结构这条线看，Redis 8.6 最值得关注的不是“又多背几个结构名”，而是几类更贴近日常排查的问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;大 Hash 和大 ZSet 的内存占用继续被优化。&lt;/li&gt;
&lt;li&gt;Stream 增加了 &lt;code&gt;XADD IDMP&lt;/code&gt; / &lt;code&gt;IDMPAUTO&lt;/code&gt;，用于幂等写入。&lt;/li&gt;
&lt;li&gt;新增 &lt;code&gt;HOTKEYS&lt;/code&gt;，能直接辅助定位热点 key。&lt;/li&gt;
&lt;li&gt;新增 key 内存大小直方图相关能力，排查 List、Set、Hash、ZSet 的规模分布更方便。&lt;/li&gt;
&lt;li&gt;新增 &lt;code&gt;volatile-lrm&lt;/code&gt; 和 &lt;code&gt;allkeys-lrm&lt;/code&gt; 淘汰策略，按“最近修改时间”参与淘汰。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;旧版本里经常出现的 &lt;code&gt;ziplist&lt;/code&gt;，在 Redis 7 之后大多已经被 &lt;code&gt;listpack&lt;/code&gt; 替代，所以不要再只背“压缩列表”那套旧答案。&lt;/p&gt;</description></item><item><title>布隆过滤器：用少量内存判断一个元素是否可能存在</title><link>https://cloudside.icyyan.com/posts/bloom-filter-guide/</link><pubDate>Thu, 21 May 2026 10:00:00 +0800</pubDate><guid>https://cloudside.icyyan.com/posts/bloom-filter-guide/</guid><description>&lt;p&gt;如果一个接口被大量查询不存在的数据，比如用户不断请求不存在的商品 ID、缓存里没有的用户 ID，系统很容易被拖进一个尴尬局面：缓存没命中，请求继续打到数据库；数据库查不到，下一次同样的请求又会重复发生。&lt;/p&gt;
&lt;p&gt;这类问题的关键不是“如何更快地查到数据”，而是先回答一个更便宜的问题：&lt;strong&gt;这个东西是不是一定不存在？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;布隆过滤器解决的正是这个问题。它不能像哈希集合那样给出完全精确的答案，但它能用很少的内存，在极快的时间内告诉你：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果结果是“不存在”，那它一定不存在。&lt;/li&gt;
&lt;li&gt;如果结果是“存在”，那它只是可能存在。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个“可能”就是布隆过滤器最重要的取舍。它用少量误判，换来了很高的空间效率。&lt;/p&gt;
&lt;h2 id="先从一个普通集合说起"&gt;先从一个普通集合说起&lt;/h2&gt;
&lt;p&gt;假设我们要记录 1 亿个已经注册过的用户 ID，最直接的做法是把这些 ID 放进一个集合：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;registered_users &lt;span style="color:#f92672"&gt;=&lt;/span&gt; set()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;registered_users&lt;span style="color:#f92672"&gt;.&lt;/span&gt;add(&lt;span style="color:#e6db74"&gt;&amp;#34;user:10086&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;user:10086&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;in&lt;/span&gt; registered_users:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#34;exists&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个方案很好理解，也很精确。但问题是：集合为了支持快速查询，通常不只存储原始元素，还要维护哈希表、桶、指针、扩容状态等额外结构。数据量一大，内存会明显上去。&lt;/p&gt;
&lt;p&gt;如果我们的目标只是提前过滤掉明显不存在的请求，真的需要保存完整的字符串吗？&lt;/p&gt;
&lt;p&gt;布隆过滤器的答案是：不需要。它只保存一些 bit。&lt;/p&gt;
&lt;h2 id="布隆过滤器的直觉"&gt;布隆过滤器的直觉&lt;/h2&gt;
&lt;p&gt;可以把布隆过滤器想成一个很大的位图，也就是一串只包含 0 和 1 的数组：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;普通位图通常要求元素能直接映射成整数位置，比如数字 13 就对应第 13 个 bit。但现实里的元素可能是字符串、URL、邮箱、订单号，不一定能直接当数组下标。&lt;/p&gt;
&lt;p&gt;布隆过滤器在中间加了一层哈希函数。&lt;/p&gt;
&lt;p&gt;对于一个元素，它不用一个位置表示，而是用多个哈希函数算出多个位置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;element = &amp;#34;user:10086&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hash1(element) -&amp;gt; 3
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hash2(element) -&amp;gt; 9
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hash3(element) -&amp;gt; 14
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;插入这个元素时，就把这些位置都置为 1：&lt;/p&gt;</description></item><item><title>位图 Bitmap：用一个 bit 记录海量状态</title><link>https://cloudside.icyyan.com/posts/bitmap-data-structure-guide/</link><pubDate>Mon, 18 May 2026 10:00:00 +0800</pubDate><guid>https://cloudside.icyyan.com/posts/bitmap-data-structure-guide/</guid><description>&lt;p&gt;如果让你记录“今天哪些用户登录过”，最直接的想法可能是放进一个集合：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;logged_in_users &lt;span style="color:#f92672"&gt;=&lt;/span&gt; set()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;logged_in_users&lt;span style="color:#f92672"&gt;.&lt;/span&gt;add(&lt;span style="color:#ae81ff"&gt;10086&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这样当然能用。但如果用户 ID 范围很大、查询又特别频繁，集合会带来不少额外开销。有没有一种更省空间的办法？&lt;/p&gt;
&lt;p&gt;位图，也就是 Bitmap，解决的就是这类问题：&lt;strong&gt;用一个 bit 表示一个状态&lt;/strong&gt;。这个状态通常只有两种结果：有或没有、是或否、出现过或没出现过。&lt;/p&gt;
&lt;h2 id="位图到底是什么"&gt;位图到底是什么&lt;/h2&gt;
&lt;p&gt;先从一个字节说起。&lt;/p&gt;
&lt;p&gt;一个字节有 8 个 bit：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;0 0 0 0 0 0 0 0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;每个 bit 都可以表示一个编号是否存在。比如我们用 bit 记录数字是否出现过：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bit 0 表示数字 0 是否出现
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bit 1 表示数字 1 是否出现
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bit 2 表示数字 2 是否出现
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bit 7 表示数字 7 是否出现
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果数字 3 出现过，就把第 3 个 bit 置为 1：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;0 0 0 0 1 0 0 0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↑
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 数字 3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;当编号超过 7 时，就放到下一个字节里。比如数字 13：&lt;/p&gt;</description></item><item><title>用 Pingap 部署反向代理，以及从 Pingora 迁移时容易踩的坑</title><link>https://cloudside.icyyan.com/posts/pingap-reverse-proxy-guide-and-pitfalls/</link><pubDate>Sun, 17 May 2026 16:20:00 +0800</pubDate><guid>https://cloudside.icyyan.com/posts/pingap-reverse-proxy-guide-and-pitfalls/</guid><description>&lt;p&gt;如果你已经看过 Pingora 的最小反向代理示例，很容易产生一个后续问题：生产环境是不是也要自己继续写配置解析、证书加载、日志、热更新和管理界面？通常不需要。Pingap 就是基于 Pingora 做好的反向代理应用，它更接近一个可以直接部署的 Nginx 替代方案。&lt;/p&gt;
&lt;p&gt;这篇文章只讲 Pingap：怎么安装，怎么写一个最小反代配置，怎么加 HTTPS，怎么用 Docker 或 systemd 部署，以及从裸 Pingora 或 Nginx 迁移过来时有哪些容易忽略的坑。&lt;/p&gt;
&lt;p&gt;本文示例版本核对时间：&lt;strong&gt;2026-05-17&lt;/strong&gt;。示例固定使用 Pingap &lt;code&gt;v0.13.4&lt;/code&gt;。实际部署前建议再看一次官方 GitHub Releases 和文档，因为 Pingap 还在快速变化。&lt;/p&gt;
&lt;h2 id="pingap-和-pingora-的关系"&gt;Pingap 和 Pingora 的关系&lt;/h2&gt;
&lt;p&gt;Pingora 是框架，你通过 Rust 代码实现代理逻辑。Pingap 是应用，它把常见反向代理能力包装成 TOML 配置和 Web 管理界面。&lt;/p&gt;
&lt;p&gt;一个最小 Pingap 配置大概长这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#a6e22e"&gt;upstreams&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;addrs&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;127.0.0.1:3000&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#a6e22e"&gt;locations&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;host&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;app.example.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;path&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;upstream&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;app&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;enable_reverse_proxy_headers&lt;/span&gt; = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#a6e22e"&gt;servers&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;addr&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;0.0.0.0:6188&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;locations&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;app&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这段配置和一个最小 Pingora 代理做的是同一件事：监听 &lt;code&gt;6188&lt;/code&gt;，把匹配 &lt;code&gt;app.example.com&lt;/code&gt; 的请求转发到 &lt;code&gt;127.0.0.1:3000&lt;/code&gt;。差别在于，你不用实现 &lt;code&gt;ProxyHttp&lt;/code&gt;，也不用自己处理常见代理应用需要的周边能力。&lt;/p&gt;</description></item><item><title>用 Pingora 写一个最小反向代理，并把它部署起来</title><link>https://cloudside.icyyan.com/posts/pingora-minimal-reverse-proxy-guide/</link><pubDate>Sun, 17 May 2026 16:00:00 +0800</pubDate><guid>https://cloudside.icyyan.com/posts/pingora-minimal-reverse-proxy-guide/</guid><description>&lt;p&gt;Cloudflare 开源 Pingora 之后，很多人会问：它能不能替代 Nginx？如果问题只是“能不能把请求转发到后端服务”，答案是能，而且几十行 Rust 就够了。但 Pingora 不是一个读取 &lt;code&gt;nginx.conf&lt;/code&gt; 的服务器，它更像一套用来写代理、网关和网络服务的 Rust 框架。&lt;/p&gt;
&lt;p&gt;这篇只做一件事：用 Pingora 写一个最小反向代理，让 &lt;code&gt;http://127.0.0.1:6188/&lt;/code&gt; 转发到本机的 &lt;code&gt;127.0.0.1:3000&lt;/code&gt;，然后把这个二进制按服务方式部署起来。&lt;/p&gt;
&lt;p&gt;本文示例版本核对时间：&lt;strong&gt;2026-05-17&lt;/strong&gt;。示例使用 &lt;code&gt;pingora = &amp;quot;0.8&amp;quot;&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="先看等价的-nginx-配置"&gt;先看等价的 Nginx 配置&lt;/h2&gt;
&lt;p&gt;如果用 Nginx，最小反代大概长这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-nginx" data-lang="nginx"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;server&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;listen&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;6188&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;location&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_pass&lt;/span&gt; &lt;span style="color:#e6db74"&gt;http://127.0.0.1:3000&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;Host&lt;/span&gt; $host;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;X-Real-IP&lt;/span&gt; $remote_addr;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;X-Forwarded-For&lt;/span&gt; $proxy_add_x_forwarded_for;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color:#e6db74"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span style="color:#e6db74"&gt;http&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pingora 的思路不一样。你不是写配置块，而是实现 &lt;code&gt;ProxyHttp&lt;/code&gt; trait：请求进来以后，代码决定它要转发到哪里，转发前要改哪些请求头，最后日志怎么记。&lt;/p&gt;
&lt;h2 id="创建项目"&gt;创建项目&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cargo new mini-pingora-proxy
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd mini-pingora-proxy
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Cargo.toml&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#a6e22e"&gt;package&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;mini-pingora-proxy&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;0.1.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;edition&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;2021&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[&lt;span style="color:#a6e22e"&gt;dependencies&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;async-trait&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;0.1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;env_logger&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;0.11&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;0.4&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;pingora&lt;/span&gt; = { &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;0.8&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; = [&lt;span style="color:#e6db74"&gt;&amp;#34;proxy&amp;#34;&lt;/span&gt;] }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里用的是 &lt;code&gt;pingora&lt;/code&gt; 聚合 crate，并启用 &lt;code&gt;proxy&lt;/code&gt; feature。Pingora 没有默认打开所有能力；如果后面要做负载均衡，需要再启用 &lt;code&gt;lb&lt;/code&gt;；如果要监听 HTTPS 或连接 HTTPS upstream，还要选择对应的 TLS feature，比如 &lt;code&gt;openssl&lt;/code&gt;、&lt;code&gt;boringssl&lt;/code&gt;、&lt;code&gt;s2n&lt;/code&gt; 或实验性的 &lt;code&gt;rustls&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>Pingora 深度解析：为什么 Cloudflare 用 Rust 重写了 Nginx 时代的代理架构</title><link>https://cloudside.icyyan.com/posts/pingora-source-deep-dive/</link><pubDate>Sun, 17 May 2026 00:00:00 +0800</pubDate><guid>https://cloudside.icyyan.com/posts/pingora-source-deep-dive/</guid><description>&lt;p&gt;Cloudflare 公开 Pingora 时，最抓眼球的数字是：在相同流量下，新的代理系统比旧系统少用了约 70% CPU 和 67% 内存。&lt;/p&gt;
&lt;p&gt;看到这个数字，很多人的第一反应可能是：是不是 Nginx 不行了？是不是 Rust 天生就比 C 快很多？&lt;/p&gt;
&lt;p&gt;都不是。&lt;/p&gt;
&lt;p&gt;我们真正要回答的是：&lt;strong&gt;Cloudflare 为什么会觉得原来的 NGINX/OpenResty 代理不够用了？Pingora 又改掉了哪些最贵的地方？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;后面就顺着这个问题往下走。先看 Pingora 是什么，再看 Nginx 在普通场景里为什么很好用，最后再看 Cloudflare 这种规模下，连接池、线程模型和业务逻辑会怎样把成本放大。&lt;/p&gt;
&lt;p&gt;可以先带着三句话往下读，后面会一层层展开：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cloudflare 替换的是他们内部深度改造过的 NGINX/OpenResty 代理，不是说 Nginx 在所有场景下都慢。&lt;/li&gt;
&lt;li&gt;Pingora 开源版更像“用 Rust 写代理服务的工具箱”，不是一个直接读取 &lt;code&gt;nginx.conf&lt;/code&gt; 的 Nginx 替代品。&lt;/li&gt;
&lt;li&gt;重点不是“Rust 打败 Nginx”，而是连接复用、线程共享和复杂代理逻辑这些具体问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="1-pingora-不是一个新的-nginx"&gt;1. Pingora 不是一个新的 Nginx&lt;/h2&gt;
&lt;p&gt;如果你平时用 Nginx，可能会自然地把 Pingora 想成“另一个服务器软件”。但这样理解会有点偏。&lt;/p&gt;
&lt;p&gt;Pingora 更像一套用 Rust 写代理服务的工具箱。它帮你处理连接、TLS、HTTP 协议、负载均衡、连接池、优雅升级这些通用能力；你的业务逻辑则写在 Rust 回调里。&lt;/p&gt;
&lt;p&gt;它不是单个二进制 Web 服务器，而是一组 crate：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;crate&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pingora-core&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;底层协议、服务运行、监听器、连接器、优雅升级&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pingora-proxy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP 代理生命周期和 &lt;code&gt;ProxyHttp&lt;/code&gt; 回调接口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pingora-pool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;高并发连接池&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pingora-runtime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tokio runtime 封装，支持 work-stealing 与 no-steal 模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pingora-load-balancing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;负载均衡、健康检查、选择算法&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pingora-cache&lt;/code&gt; / &lt;code&gt;pingora-memory-cache&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;缓存相关能力&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这些名字不用急着都记住。先有一个印象就够了：Pingora 把“写代理服务”拆成了一组可以组合的 Rust 库。&lt;/p&gt;</description></item><item><title>Hugo + GitHub Actions：从本地 Markdown 到线上站点的全自动流水线</title><link>https://cloudside.icyyan.com/posts/hugo-github-actions-cicd/</link><pubDate>Tue, 13 May 2025 15:30:00 +0800</pubDate><guid>https://cloudside.icyyan.com/posts/hugo-github-actions-cicd/</guid><description>&lt;h2 id="0-背景与架构演进为什么我们需要流水线"&gt;0. 背景与架构演进：为什么我们需要流水线？&lt;/h2&gt;
&lt;h3 id="hugo-的运行机制"&gt;Hugo 的运行机制&lt;/h3&gt;
&lt;p&gt;Hugo 是一个基于 Go 语言的静态网站生成器（Static Site Generator）。它的核心工作流是将我们编写的 Markdown 源文件，结合站点配置和主题模板，在编译阶段直接渲染成纯静态的 HTML、CSS 和 JS 文件（输出到 &lt;code&gt;public/&lt;/code&gt; 目录）。
由于是纯静态站点，线上的服务器不需要运行任何数据库或动态后端（如 PHP/Node.js），只需要一个 Web 服务器来托管这些文件即可对外提供访问。这种架构带来了极高的访问速度和安全性。&lt;/p&gt;
&lt;h3 id="自动化构建的必然性"&gt;自动化构建的必然性&lt;/h3&gt;
&lt;p&gt;正因为 Hugo 所有的页面都是预先生成的，如果采用手动发布，我们每次更新文章就会陷入一个繁琐的循环：&lt;strong&gt;本地写 Markdown -&amp;gt; 本地运行 &lt;code&gt;hugo&lt;/code&gt; 构建出 &lt;code&gt;public&lt;/code&gt; 目录 -&amp;gt; 手动打包或通过 &lt;code&gt;scp&lt;/code&gt;/&lt;code&gt;rsync&lt;/code&gt; 把文件传到服务器&lt;/strong&gt;。
这三步里有两步是枯燥的机械劳动，不仅容易出错（比如忘了构建就上传），而且如果更换电脑写作，还得重新配置 Hugo 环境。因此，搭建一条自动化的 CI/CD 流水线，把编译和部署工作交给云端，是大幅提升博客写作体验的必选项。我们最终期望的体验是：&lt;strong&gt;只管写 Markdown，推代码即自动部署。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="为什么这样设计部署架构"&gt;为什么这样设计部署架构？&lt;/h3&gt;
&lt;p&gt;基于上述诉求，我们设计了当前的流水线架构：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;构建层（GitHub Actions）&lt;/strong&gt;：文章源码天然需要使用 Git 进行版本控制并托管在 GitHub 上。GitHub Actions 提供了与代码仓库深度绑定的免费 CI/CD 环境。它可以监听 &lt;code&gt;git push&lt;/code&gt; 事件，自动在云端运行容器，拉取子模块（主题）并执行 &lt;code&gt;hugo build&lt;/code&gt;。这样无论我们在哪里写代码，只要能 push，云端就能帮我们完成构建。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传输层（rsync）&lt;/strong&gt;：在云端构建完成后，我们使用 &lt;code&gt;rsync&lt;/code&gt; 经由 SSH 将生成的静态文件增量同步到阿里云 ECS。&lt;code&gt;rsync&lt;/code&gt; 非常轻量高效，配合 &lt;code&gt;--delete&lt;/code&gt; 参数能够确保服务器上的文件状态与最新编译结果完全一致（自动删除已移除的文件），不需要在服务器端部署任何复杂的 Agent。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网关与服务层（从 Nginx 到 Pingap）&lt;/strong&gt;：静态文件传到服务器的指定目录后，需要对外暴露。本站点早期用 Nginx 直接托管静态文件，后来迁移到 Pingap 统一入口。为了让 Hugo 生成的目录页、文章页和标签页都保持稳定的 &lt;code&gt;index.html&lt;/code&gt; 行为，当前采用 &lt;code&gt;darkhttpd&lt;/code&gt; 在本机监听静态目录，Pingap 负责 HTTPS、SNI、HTTP 跳转和反代。无论入口网关怎么演进，&lt;strong&gt;“云端编译静态文件并推送到服务器目录”&lt;/strong&gt; 始终是整套发布流程的核心。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="1-最终部署架构图"&gt;1. 最终部署架构图&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;本地写 Markdown ──git push──→ GitHub ──Actions──→ Hugo build ──rsync──→ ECS /var/www/hugo-site/
↑
SSH deploy key
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub&lt;/strong&gt;：源码仓库 + CI 触发器 + 版本历史&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt;：Ubuntu runner 上跑 hugo build，然后 rsync&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;阿里云 ECS&lt;/strong&gt;：实际跑服务的地方，&lt;code&gt;darkhttpd&lt;/code&gt; 承载静态文件，Pingap 对外反代&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="2-准备仓库初始化"&gt;2. 准备：仓库初始化&lt;/h2&gt;
&lt;p&gt;假设你已经有一个空仓库克隆到本地：&lt;/p&gt;</description></item><item><title>从 Nginx 迁移到 Pingap：一次接近零停机的实战记录</title><link>https://cloudside.icyyan.com/posts/nginx-to-pingap-migration/</link><pubDate>Tue, 13 May 2025 15:00:00 +0800</pubDate><guid>https://cloudside.icyyan.com/posts/nginx-to-pingap-migration/</guid><description>&lt;h2 id="0-背景与动机为什么要替换掉-nginx"&gt;0. 背景与动机：为什么要替换掉 Nginx？&lt;/h2&gt;
&lt;p&gt;在过去很长一段时间里，本站点的静态文件托管（Hugo）以及动态服务反代（SillyTavern）一直由 Nginx 承担。Nginx 久经考验、极其稳定，堪称业界标杆。但随着系统架构的演进和统一网关管理的考量，我们开始寻求更现代化的反向代理方案，并最终将目光投向了 &lt;a href="https://github.com/vicanso/pingap"&gt;Pingap&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么选择 Pingap？&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;现代化的底层基石&lt;/strong&gt;：Pingap 基于 Cloudflare 开源的 &lt;strong&gt;Pingora&lt;/strong&gt; 框架构建，采用 Rust 编写。Pingora 在 Cloudflare 内部已经受了万亿级请求的考验。得益于 Rust 的内存安全特性，它从根本上规避了传统 C/C++ 网关常见的内存越界和崩溃风险。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;更直观的配置模型&lt;/strong&gt;：Nginx 的配置语法虽然强大，但指令的上下文和隐式执行顺序往往让人头疼。Pingap 采用了基于 TOML 的声明式配置，将 &lt;code&gt;Server&lt;/code&gt;、&lt;code&gt;Location&lt;/code&gt;、&lt;code&gt;Upstream&lt;/code&gt; 和 &lt;code&gt;Plugin&lt;/code&gt; 彻底解耦，配置的心智模型更加清晰，更利于后期的维护与扩展。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原生的高级特性支持&lt;/strong&gt;：Pingap 对 HTTP/1.x、HTTP/2、WebSocket、gRPC-web、证书动态加载和插件化网关能力支持更直接。需要注意的是，HTTP/3/QUIC 这类能力应以当前官方 release 和文档为准，迁移生产入口时不要默认假设已经完整可用。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;本次实战的核心目标&lt;/strong&gt;：&lt;strong&gt;先在隔离端口上把新代理跑起来并完整验证，再用一次原子切换无缝替换 Nginx，全程保留 Nginx 配置以便出现问题时能够秒级回滚。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过这种“先验证后切换”的安全策略，我们最终实测的切换中断窗口仅为 &lt;strong&gt;174 ms&lt;/strong&gt;，线上流量几乎无感。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2026-05-17 复核：我重新 SSH 到服务器核对了一遍真实状态。当前服务器仍运行 &lt;code&gt;pingap 0.13.2&lt;/code&gt;，&lt;code&gt;pingap.service&lt;/code&gt; 使用 daemon 模式，Hugo 静态文件由 &lt;code&gt;darkhttpd&lt;/code&gt; 承载。本文以下配置以服务器现状为准；新装环境可以再按最新 release 调整。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="1-起点现状盘点"&gt;1. 起点：现状盘点&lt;/h2&gt;
&lt;p&gt;服务器：阿里云 ECS（Alibaba Cloud Linux 3 / RHEL 兼容），跑 Nginx 1.20.1，承载两个站点：&lt;/p&gt;</description></item></channel></rss>