<?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>性能优化 on Cloudside</title><link>https://cloudside.icyyan.com/tags/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/</link><description>Recent content in 性能优化 on Cloudside</description><generator>Hugo</generator><language>en</language><lastBuildDate>Sat, 20 Jun 2026 10:00:00 +0800</lastBuildDate><atom:link href="https://cloudside.icyyan.com/tags/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/index.xml" rel="self" type="application/rss+xml"/><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>请不要用 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>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></channel></rss>