<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>CAICAIIs</title><description>愚者在月影之中怀抱虚妄</description><link>https://caicaiis.cc</link><item><title>GSoC 2026 Wrap-up: Seata-Go and seata-ctl finally feel usable</title><link>https://caicaiis.cc/6/gsoc-seata-ctl</link><guid>10-60</guid><description>My GSoC notes on Seata-Go multi-registry work and seata-ctl read-only diagnostics</description><pubDate>Mon, 24 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is my wrap-up for this GSoC.&lt;/p&gt;
&lt;p&gt;The project started as two separate tracks. One track was Seata-Go: tighten the registry layer, make address updates flow into the connection layer, and leave room for Nacos, ZooKeeper, Consul, and Redis later. The other track was seata-ctl: turn the REPL into a real read-only diagnostic entry point.&lt;/p&gt;
&lt;p&gt;That split mattered. The Go side is about discovery and connection management. The CLI side is about visibility and day-to-day troubleshooting. They solve different problems, but they belong to the same story: make Seata easier to operate.&lt;/p&gt;
&lt;p&gt;I also found that this kind of project is easier to land when the scope is split by user behavior instead of by internal package boundaries. I did not ask, &amp;quot;what can I add to this repo?&amp;quot; I asked, &amp;quot;what is the next thing a user would actually need when Seata is not behaving?&amp;quot; That led naturally to a read-only diagnostic path first, and a registry/connection path that could keep evolving underneath it.&lt;/p&gt;
&lt;h2&gt;What I wanted to fix&lt;/h2&gt;
&lt;p&gt;The pain points were simple.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On the Seata-Go side, the registry interface was too narrow, so every new backend would keep adding more special cases.&lt;/li&gt;
&lt;li&gt;On the seata-ctl side, login already existed, but the tool still lacked a useful read-only command set.&lt;/li&gt;
&lt;li&gt;When something went wrong, I still had to jump across pages, logs, and code just to answer basic questions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So I kept the design plain:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;make discovery easier to extend,&lt;/li&gt;
&lt;li&gt;make seata-ctl good enough for actual troubleshooting,&lt;/li&gt;
&lt;li&gt;keep output in &lt;code&gt;table&lt;/code&gt;, &lt;code&gt;json&lt;/code&gt;, and &lt;code&gt;yaml&lt;/code&gt; so it works for both humans and scripts.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That also kept the PRs reviewable. Each piece had a clear input, a clear output, and a clear test shape. I tried hard not to turn a CLI feature into a little framework.&lt;/p&gt;
&lt;h2&gt;The Seata-Go side&lt;/h2&gt;
&lt;p&gt;I did not try to cram every registry into one giant patch.&lt;/p&gt;
&lt;p&gt;The main idea was to keep &lt;code&gt;RegistryService&lt;/code&gt; small and stable. It only needs lookup and close. If a backend supports live refresh, it can add subscription separately.&lt;/p&gt;
&lt;p&gt;That gave me a clean path for file and etcd, and it kept the connection layer away from backend-specific client types. Address changes go into a shared store first, then the session manager decides how to update Getty connections.&lt;/p&gt;
&lt;p&gt;The design choice I cared about most there was not the syntax of the interface, but the failure shape. A registry backend can fail in different ways: no data, partial data, stale data, or a temporary network issue. If those cases are pushed straight into the connection layer, the code gets noisy very fast. Keeping a normalized address store in the middle made the rest of the code easier to reason about.&lt;/p&gt;
&lt;h2&gt;The seata-ctl side&lt;/h2&gt;
&lt;p&gt;On the CLI side, I kept the scope deliberately small and useful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;diagnose run&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transaction list&lt;/code&gt; / &lt;code&gt;transaction show&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lock list&lt;/code&gt; / &lt;code&gt;lock check&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the TUI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The important part was not building a framework. The important part was making the REPL behave like a tool I would actually use.&lt;/p&gt;
&lt;p&gt;That means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;log in once and reuse the token,&lt;/li&gt;
&lt;li&gt;send auth on every read request,&lt;/li&gt;
&lt;li&gt;keep errors clear,&lt;/li&gt;
&lt;li&gt;keep the output formats boring and predictable,&lt;/li&gt;
&lt;li&gt;do not overwrite useful data in the TUI when a refresh fails.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The CLI work ended up being more about behavior than about command names. The REPL parser had a bug that could swallow the next command, so the shell felt flaky even when the command itself was fine. The diagnostic flow also had a hidden coupling problem: one failed query could make the whole report feel broken, even when the rest of the system was healthy. Fixing those two things made the tool feel much calmer.&lt;/p&gt;
&lt;h2&gt;What shipped&lt;/h2&gt;
&lt;p&gt;The user flow now looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;seata-ctl
login --ip 127.0.0.1 --port 7091 --username seata --password seata
transaction list
lock list
diagnose run
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The small details mattered a lot here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the REPL parser had to stop eating the next command,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transaction list&lt;/code&gt; and &lt;code&gt;lock list&lt;/code&gt; had to map flags to the console API cleanly,&lt;/li&gt;
&lt;li&gt;the TUI had to keep old data visible when a refresh failed,&lt;/li&gt;
&lt;li&gt;diagnostic queries had to stay independent so one bad query would not drag everything down.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The commands themselves are intentionally boring:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;transaction list&lt;/code&gt; shows active global sessions.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transaction show&lt;/code&gt; focuses on one XID.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lock list&lt;/code&gt; and &lt;code&gt;lock check&lt;/code&gt; cover the global lock side.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;diagnose run&lt;/code&gt; gives one sweep across the obvious checks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is enough surface area for a first useful version. Anything bigger would have pulled the tool away from the &amp;quot;read-only helper&amp;quot; role and into a much broader admin surface.&lt;/p&gt;
&lt;h2&gt;Related pull requests&lt;/h2&gt;
&lt;p&gt;The work landed across two Apache repositories. These are the main GSoC pull requests behind the design described here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/apache/incubator-seata-go/pull/1126&quot;&gt;incubator-seata-go #1126&lt;/a&gt;: add the discovery provider registry and address store.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/apache/incubator-seata-go/pull/1138&quot;&gt;incubator-seata-go #1138&lt;/a&gt;: wire registry change snapshots into the remoting address lifecycle.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/apache/incubator-seata-ctl/pull/21&quot;&gt;incubator-seata-ctl #21&lt;/a&gt;: add read-only diagnostics and transaction listing to &lt;code&gt;seata-ctl&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All three pull requests were merged. The links are here because the implementation details are easier to understand in the code review than in a retrospective paragraph: the Seata-Go changes explain the discovery and address-update path, while the seata-ctl change shows the user-facing diagnostic flow.&lt;/p&gt;
&lt;h2&gt;Verification&lt;/h2&gt;
&lt;p&gt;I checked the work with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;go test ./...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;go vet ./...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;go build -v ./...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git diff --check&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;a real REPL smoke test: login, &lt;code&gt;transaction list&lt;/code&gt;, &lt;code&gt;lock list&lt;/code&gt;, &lt;code&gt;quit&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I trust the REPL smoke test more than the unit tests here, because it proves the command path is real, not just mocked.&lt;/p&gt;
&lt;p&gt;The smoke test also helped catch one thing I cared about: the prompt should still behave like a prompt after login, and the new commands should not break the existing REPL flow. That sounds tiny, but it is exactly the kind of thing that decides whether a CLI feels polished or half-finished.&lt;/p&gt;
&lt;h2&gt;What Changed In Practice&lt;/h2&gt;
&lt;p&gt;If I compress the whole project into a few concrete changes, it comes down to this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Seata-Go now has a cleaner path for discovery and later registry expansion.&lt;/li&gt;
&lt;li&gt;seata-ctl now has a real read-only diagnostic slice instead of only a login shell.&lt;/li&gt;
&lt;li&gt;The output formats are stable enough to read manually and script against.&lt;/li&gt;
&lt;li&gt;The failure handling is calmer: clear errors, less noise, less accidental state loss.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is the kind of change that is easy to underestimate when you only look at the diff size, but it changes how the tool feels when you are under pressure.&lt;/p&gt;
&lt;h2&gt;What I Would Carry Forward&lt;/h2&gt;
&lt;p&gt;If I keep working on this line, the next steps are pretty obvious to me.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;make the Seata-Go discovery path absorb more backend differences without leaking them upward,&lt;/li&gt;
&lt;li&gt;keep the CLI focused on the operations that help with real debugging,&lt;/li&gt;
&lt;li&gt;keep the output and REPL behavior stable so the tool stays pleasant to use.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is probably the main thing I learned from this GSoC: the best infrastructure work is often the kind that quietly removes friction, then stays out of the way.&lt;/p&gt;
&lt;h2&gt;Closing note&lt;/h2&gt;
&lt;p&gt;For me, this GSoC was less about adding a pile of commands and more about turning two rough edges into something that can keep growing.&lt;/p&gt;
&lt;p&gt;Seata-Go now has a cleaner discovery story. seata-ctl now has a read-only path that is actually useful when I need to inspect the system.&lt;/p&gt;
&lt;p&gt;That feels like a good place to stop for a wrap-up and a good place to continue from next.&lt;/p&gt;
</content:encoded><category>Open Source Experience</category></item><item><title>Dubbo-Go 3.0 的 Metadata 模块宏观介绍</title><link>https://caicaiis.cc/4/dubbo-go-metadata</link><guid>10-50</guid><description>分享下 dubbo-go 3.0 中关于 Metadata 模块的大致状况</description><pubDate>Thu, 16 Apr 2026 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;只看名字会觉得 metadata 很像一个边角模块，像是给服务补一点描述信息、给注册中心多带几个参数。&lt;/p&gt;
&lt;p&gt;但实际它上并不只是“保存元数据”，而是在应用级服务发现模型之上，补上了一层完整的语义解释能力。注册中心负责告诉框架“有哪些应用实例”，而 metadata 负责告诉框架“这些实例到底提供了什么服务”。&lt;/p&gt;
&lt;p&gt;这篇文章不做逐行源码解析，而是比较宏观的从设计视角去梳理 dubbo-go 3.0 的 metadata 模块：它解决了什么问题、内部怎么组织、和其他模块怎么配合，以及为什么它会呈现出这样一套相对完整的形态。&lt;/p&gt;
&lt;p&gt;文中讨论以 &lt;code&gt;dubbo-go 3.0.x&lt;/code&gt; 这一代实现为主，这里主要对照 &lt;code&gt;v3.0.5 / release-3.0&lt;/code&gt; 这条代码线，不混用后续 &lt;code&gt;main&lt;/code&gt; 分支里继续演进出来的 metadata 能力。&lt;/p&gt;
&lt;h2&gt;为什么需要 Metadata（它解决了什么问题）&lt;/h2&gt;
&lt;p&gt;首先去查看它所处的服务发现背景&lt;/p&gt;
&lt;p&gt;在接口级注册发现模型里，consumer 订阅某个接口，注册中心直接返回这个接口对应的一组 provider 地址。这个模型简单直接，因为“接口”和“地址”天然绑定。&lt;/p&gt;
&lt;p&gt;但到了 &lt;code&gt;dubbo-go 3.0&lt;/code&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;li&gt;每个实例是否健康&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但 consumer 真正关心的是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这个应用实例到底暴露了哪些接口&lt;/li&gt;
&lt;li&gt;某个接口到底属于哪些应用&lt;/li&gt;
&lt;li&gt;这些接口的 group、version、protocol、path、methods 是什么&lt;/li&gt;
&lt;li&gt;provider 的服务集合变化后，consumer 怎么感知&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但这些问题注册中心本身不会去回答。&lt;/p&gt;
&lt;p&gt;于是这时候为了解决这部分问题 metadata 出现了，成为应用级服务发现能够真正落地所必须的语义层。&lt;/p&gt;
&lt;p&gt;所以简单的回答为什么需要 metadata：&lt;strong&gt;注册中心解决“找到实例”，metadata 解决“理解实例”。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/4/dubbo-go-metadata/attachments/why-metadata.png&quot; alt=&quot;why-metadata&quot;&gt;&lt;/p&gt;
&lt;h2&gt;从架构上看 metadata 处在什么位置&lt;/h2&gt;
&lt;p&gt;如果只看 &lt;code&gt;metadata/&lt;/code&gt; 目录，很容易觉得它是一个相对独立的小模块。但从实际运行链路看，它和 &lt;code&gt;config&lt;/code&gt;、&lt;code&gt;registry&lt;/code&gt;、&lt;code&gt;service discovery&lt;/code&gt;、&lt;code&gt;protocol&lt;/code&gt; 这些模块都有很强的关系。&lt;/p&gt;
&lt;p&gt;可以参考大致关系图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/4/dubbo-go-metadata/attachments/where-metadata.png&quot; alt=&quot;示例图片&quot;&gt;&lt;/p&gt;
&lt;p&gt;这分别对应了几层关系：&lt;/p&gt;
&lt;h3&gt;1.和 &lt;code&gt;config&lt;/code&gt; 的关系：装配入口&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;metadata&lt;/code&gt; 不是运行到一半突然出来的，它是在应用启动、服务导出、实例注册这些阶段被装配进去的。&lt;/p&gt;
&lt;p&gt;也就是说，它从一开始就不是外围能力，而是启动链路的一部分。&lt;/p&gt;
&lt;h3&gt;2. 和 &lt;code&gt;registry / service discovery&lt;/code&gt; 的关系：发现与解释&lt;/h3&gt;
&lt;p&gt;注册中心负责把实例发现出来，但实例只是一层“地址事实”；真正把它解释成“服务能力”的，是 &lt;code&gt;metadata&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;所以可以把两者理解成一组分工：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;service discovery&lt;/code&gt; 负责发现实例&lt;/li&gt;
&lt;li&gt;&lt;code&gt;metadata&lt;/code&gt; 负责恢复服务语义&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 和 &lt;code&gt;protocol&lt;/code&gt; 的关系：元数据也可以被远程访问&lt;/h3&gt;
&lt;p&gt;在 3.0.x 当前实现里，&lt;code&gt;metadata&lt;/code&gt; 不是只存在于中心存储里，&lt;code&gt;provider&lt;/code&gt; 自己也会维护并暴露一个 &lt;code&gt;MetadataService&lt;/code&gt;。这意味着 &lt;code&gt;metadata&lt;/code&gt; 不只是“被保存”，它本身也是一种可以通过 RPC 获取的运行时能力。&lt;/p&gt;
&lt;h3&gt;4. 和 &lt;code&gt;consumer&lt;/code&gt; 的关系：最终恢复调用视图&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;consumer&lt;/code&gt; 拿到的不是最终服务地址，而是一组应用实例。之后要靠 &lt;code&gt;metadata&lt;/code&gt; 把这些实例重新还原成可调用的服务 URL。这也是 &lt;code&gt;metadata&lt;/code&gt; 在运行时最直接的价值体现。&lt;/p&gt;
&lt;h2&gt;Dubbo-Go 3.0 中 metadata 具体做了什么&lt;/h2&gt;
&lt;p&gt;从宏观上看，3.0 的 metadata 主要做三件事。&lt;/p&gt;
&lt;h3&gt;1. 维护“应用当前暴露的服务集合”（核心）&lt;/h3&gt;
&lt;p&gt;一个应用启动后，到底提供了哪些服务，这些服务属于什么 group、version、protocol、path，有哪些方法，这些信息最终会形成一份应用级的 metadata 视图。&lt;/p&gt;
&lt;p&gt;这份视图不是为了展示，而是为了让 consumer 在发现应用实例之后，仍然能够知道这个实例背后到底能调用哪些服务。&lt;/p&gt;
&lt;p&gt;如果没有这层数据，应用级服务发现拿到的只会是一组裸实例地址，没办法直接支撑 Dubbo 的调用模型。&lt;/p&gt;
&lt;h3&gt;2. 给服务集合生成 Revision&lt;/h3&gt;
&lt;p&gt;3.0 里的 metadata 有一个非常关键的概念，叫 &lt;code&gt;revision&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;可以把它理解成：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;当前导出服务视图的一份轻量摘要&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 3.0 当前实现里，它更接近对“接口 + 方法集合”的摘要，而不是对全部服务语义的强哈希。&lt;/p&gt;
&lt;p&gt;所以更准确地说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;大多数服务集合变化会推动 revision 变化&lt;/li&gt;
&lt;li&gt;但不是所有参数级、语义级变化都一定会反映到 revision 上&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这件事的价值在于，它给了 consumer 一个轻量的判断依据：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;revision 没变，通常说明不需要重新拉取 metadata&lt;/li&gt;
&lt;li&gt;revision 变了，说明需要重新拉取 metadata&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;于是 metadata 不再是一份被动数据，而变成了一种可以被变化感知的运行时状态。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/4/dubbo-go-metadata/attachments/revision.png&quot; alt=&quot;revision&quot;&gt;&lt;/p&gt;
&lt;p&gt;这个设计是把“实例变化”和“服务语义变化”真正连接了起来&lt;/p&gt;
&lt;h3&gt;3. 建立“接口 -&amp;gt; 应用”的映射&lt;/h3&gt;
&lt;p&gt;应用级服务发现里，注册中心认识的是应用；但 Dubbo 的消费模型，很多时候仍然是按接口订阅。&lt;/p&gt;
&lt;p&gt;于是系统必须回答一个问题：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一个接口，到底属于哪些应用？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这就是 mapping 的意义。&lt;/p&gt;
&lt;p&gt;provider 在导出服务时，会把“当前接口属于当前应用”这件事登记下来；consumer 在订阅服务时，先通过这层映射找到应用，再去监听这些应用实例的变化。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;dubbo-go 3.0&lt;/code&gt; 的默认实现里，这层 mapping 不是纯本地能力，而是依赖 &lt;code&gt;metadata report&lt;/code&gt; 来登记和查询。如果没有 metadata report，自动 mapping 能力就会受限，这时候通常要靠 &lt;code&gt;provided-by&lt;/code&gt; 或 &lt;code&gt;subscribed-services&lt;/code&gt; 这类显式信息兜底。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/4/dubbo-go-metadata/attachments/mapping.png&quot; alt=&quot;mapping&quot;&gt;&lt;/p&gt;
&lt;p&gt;这一步看起来像是一个小功能，但实际上它是应用级发现和接口级调用之间最关键的桥。&lt;/p&gt;
&lt;p&gt;如果没有它，consumer 就只能按应用消费，很难保持原本 Dubbo 习惯里的接口视角。&lt;/p&gt;
&lt;h2&gt;Metadata 是怎么串起 Provider 和 Consumer 的&lt;/h2&gt;
&lt;p&gt;如果从运行流程去看，3.0 的 metadata 不是单点功能，而是一条完整链路。&lt;/p&gt;
&lt;h3&gt;Provider 侧&lt;/h3&gt;
&lt;p&gt;provider 在导出服务时，不只是把地址注册出去，还会同时做这些事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;把导出的 URL 记录到本地 metadata&lt;/li&gt;
&lt;li&gt;生成应用级服务集合视图&lt;/li&gt;
&lt;li&gt;计算 revision&lt;/li&gt;
&lt;li&gt;建立接口到应用的映射&lt;/li&gt;
&lt;li&gt;在实例 metadata 中写入 revision、storage type、protocol endpoints、metadata 入口参数等信息&lt;/li&gt;
&lt;li&gt;视情况把 app metadata 发布到远端 metadata center&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这意味着 provider 不只是“发布服务”，还在同步维护一份服务语义快照。&lt;/p&gt;
&lt;p&gt;不过 metadata 也不只有 provider 这一半状态。&lt;code&gt;consumer&lt;/code&gt; 本地同样会维护订阅 URL 集合，并生成一份 subscribed revision，只是它在运行时最直接的价值，还是体现在“把应用实例恢复成服务视图”这条主路径上。&lt;/p&gt;
&lt;h3&gt;Consumer 侧&lt;/h3&gt;
&lt;p&gt;consumer 在订阅服务时，也不是直接从注册中心拿一组接口地址就结束了。它的过程更像这样：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先从接口出发&lt;/li&gt;
&lt;li&gt;通过 mapping 找到对应应用&lt;/li&gt;
&lt;li&gt;监听这些应用实例变化&lt;/li&gt;
&lt;li&gt;收到实例后读取 revision&lt;/li&gt;
&lt;li&gt;根据 revision 获取应用级 metadata&lt;/li&gt;
&lt;li&gt;再把实例地址和 metadata 组合，恢复成最终服务 URL&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以从链路上看，metadata 的作用很像一个中间解释层：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上游接的是应用实例&lt;/li&gt;
&lt;li&gt;下游产出的是服务视图&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;和 Dubbo-Java 3.3 相比还缺了什么&lt;/h2&gt;
&lt;p&gt;如果把 &lt;code&gt;dubbo-go 3.0&lt;/code&gt; 放到更长的演进线上看，它的不足不在方向，而更多在成熟度和配套能力上。
也就是说，它已经实现了“应用级服务发现需要 metadata 这层语义补全”这个核心目标，但和 &lt;code&gt;dubbo-java 3.3&lt;/code&gt; 相比，仍然有几块明显偏薄。&lt;/p&gt;
&lt;h3&gt;1. 元数据模型本身还不够丰富&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;dubbo-go 3.0&lt;/code&gt; 的 metadata 核心已经能表达“应用暴露了哪些服务”，也维护了一部分 consumer 侧订阅状态，这对 Go 侧落地应用级服务发现已经够用了。&lt;/p&gt;
&lt;p&gt;但 &lt;code&gt;dubbo-java 3.3&lt;/code&gt; 的 metadata 模型要更完整一些。它不只是维护 &lt;code&gt;app + revision + services&lt;/code&gt;，还把更丰富的运行时内容一起纳进去了，比如原始 metadata 内容缓存、实例级参数、扩展参数、服务 URL 视图、参数过滤机制等。换句话说，Java 侧的 metadata 已经不只是“服务清单”，而更像“运行时元数据总视图”。&lt;/p&gt;
&lt;p&gt;所以从模型成熟度上说，Go 3.0 更像是一个足够可用的核心版，而 Java 3.3 已经发展成更丰富的体系版。&lt;/p&gt;
&lt;h3&gt;2. MetadataService 的能力边界更窄&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;dubbo-go 3.0&lt;/code&gt; 里，metadata service 已经承担了关键职责，但它更偏向“围绕服务发现恢复所需的最小能力”。&lt;/p&gt;
&lt;p&gt;而 &lt;code&gt;dubbo-java 3.3&lt;/code&gt; 的 &lt;code&gt;MetadataService&lt;/code&gt; 已经更像一个完整的元数据访问入口。除了基础的 metadata 查询，它还覆盖了多份 metadata 聚合、实例级 metadata 监听、metadata URL 获取、开放接口能力等。&lt;/p&gt;
&lt;p&gt;这意味着 Java 侧的 metadata service 不只是供 consumer 恢复地址使用，也更方便运维、控制台、治理侧能力接入。Go 3.0 在这一点上还明显偏保守，能力更集中在“服务发现主路径”本身。&lt;/p&gt;
&lt;h3&gt;3. 实例级元数据表达能力偏弱&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;dubbo-java 3.3&lt;/code&gt; 在 service instance metadata 上已经做得比较细了，比如多协议 endpoint、metadata 版本、metadata cluster、实例级 customizer 等能力都比较完整。&lt;/p&gt;
&lt;p&gt;相比之下，&lt;code&gt;dubbo-go 3.0&lt;/code&gt;  的实例级 metadata 仍然偏“够用就好”。它已经包含 revision、storage type、protocol endpoints、metadata service url params 这类最小可运行字段，但在 metadata 版本、多协议细节、实例级元数据扩展这些方面，表达能力还没有 Java 那么细腻。&lt;/p&gt;
&lt;p&gt;这类差异平时不一定立刻暴露，但一旦走到更复杂的注册发现场景、更多协议并存、或者治理侧能力增强的时候，差距就会变得明显。&lt;/p&gt;
&lt;h3&gt;4. 接口到应用映射的动态能力不够强&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;dubbo-go 3.0&lt;/code&gt; 已经有 service-app mapping，这一点非常关键，因为它让应用级发现和接口级订阅之间建立了桥。&lt;/p&gt;
&lt;p&gt;但如果和 &lt;code&gt;dubbo-java 3.3&lt;/code&gt; 相比，Go 3.0 这一层更多还是“能查到映射关系”，而 Java 3.3 已经把“映射变化监听、缓存、动态更新”这套机制做得更成熟。也就是说，Java 侧不仅能回答“现在是谁提供这个接口”，还更强调“如果这个映射关系变化了，系统怎么持续感知”。&lt;/p&gt;
&lt;p&gt;从这一点看，Go 3.0 的 mapping 更像是第一代完整方案，而 Java 3.3 已经是更偏长期运行和治理视角的版本。&lt;/p&gt;
&lt;h3&gt;5. 缺少更成熟的迁移配套能力&lt;/h3&gt;
&lt;p&gt;这是和 &lt;code&gt;dubbo-java 3.3&lt;/code&gt; 最明显的一类差异。&lt;/p&gt;
&lt;p&gt;Java 3.3 围绕应用级服务发现已经形成了比较成熟的迁移机制，比如双注册、双订阅、&lt;code&gt;register-mode&lt;/code&gt;、&lt;code&gt;APPLICATION_FIRST&lt;/code&gt; / &lt;code&gt;FORCE_APPLICATION&lt;/code&gt; 这类迁移和切流策略。它考虑的不只是“应用级发现怎么工作”，还考虑“旧模型怎么平滑迁到新模型”。&lt;/p&gt;
&lt;p&gt;而 &lt;code&gt;dubbo-go 3.0&lt;/code&gt; 的 metadata 虽然已经具备应用级发现的基础，但从整体上看，还没有形成 Java 那种成熟的迁移工具链。也就是说，它更像是在 Go 侧把新模型搭起来了，但还没有把机制做得像 Java 那么完整。&lt;/p&gt;
&lt;h2&gt;简单总结&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;dubbo-go 3.0&lt;/code&gt; 的 metadata，最大的价值在于它已经把&lt;strong&gt;应用级服务发现所必需的语义补全链路&lt;/strong&gt;搭起来了。它回答了实例如何恢复成服务、接口如何映射到应用、服务变化如何被感知这些关键问题，因此它绝不是一个边缘模块，而是 3.0 服务发现设计中的核心一环。&lt;/p&gt;
&lt;p&gt;但与此同时，它在模型丰富度、实例级 metadata 表达、动态映射、迁移机制和生态化配套上，还没有达到 &lt;code&gt;dubbo-java 3.3&lt;/code&gt; 那种更成熟、更治理化的程度。&lt;/p&gt;
</content:encoded><category>开源经验</category></item><item><title>开源贡献经验分享之——指数退避（ExponentialBackoff）</title><link>https://caicaiis.cc/3/exponentialbackoff</link><guid>10-50</guid><description>分享下在项目中学到的指数退避</description><pubDate>Sun, 01 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/3/exponentialbackoff/attachments/tietie.jpg&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;p&gt;一直拖着拖着终于起稿这篇博文啦，原本想一月份写的，结果都二月份了&lt;/p&gt;
&lt;p&gt;仅是记录我在一次项目贡献时候的一点经历、经验，以后应该还会有类似的好几期，尽情期待（鸽鸽鸽）&lt;/p&gt;
&lt;p&gt;接下来我们结合我长期参与的开源项目（dubbo-go）的具体场景来讲下我所使用的指数退避与抖动的方案&lt;/p&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;h3&gt;问题描述&lt;/h3&gt;
&lt;p&gt;在 dubbo-go 集群中，当多个 consumer（服务消费方） 节点检测到某些 provider（服务提供方） 故障（或超时）后，这些 consumer 会在同一时刻向其他存活的 provider（或等故障 provider 恢复后）发起重试。如果我们使用的是&lt;strong&gt;固定间隔&lt;/strong&gt;呢？那么这些重试请求会同步到达，形成重试风暴。&lt;/p&gt;
&lt;p&gt;如果这么说有点抽象的话，那么简单的说就是比如有一个人找你帮忙你觉得 ok，2 个人同时找你也还行，那要是同时有 10 个人找你帮忙那你一定受不了。那要是我们适当分散他们的请求，帮完一个再去听下一个，并每次帮忙之间给你的休息时间越来越多呢？&lt;/p&gt;
&lt;h3&gt;具体代码分析&lt;/h3&gt;
&lt;p&gt;我们来看 dubbo-go 项目之前的代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// cluster/cluster/failback/cluster_invoker.go:92-117
func (invoker *failbackClusterInvoker) process(ctx context.Context) {
    invoker.ticker = time.NewTicker(time.Second * 1)
    for range invoker.ticker.C {
        for {
            retryTask := value.(*retryTimerTask)
            // 问题：固定5秒判断
            if time.Since(retryTask.lastT).Seconds() &amp;lt; 5 {
                break
            }
            // 所有超过5秒的任务同时出队
            invoker.taskList.Get(1)
            go invoker.tryTimerTaskProc(ctx, retryTask)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到项目之前采用的就是&lt;strong&gt;固定间隔&lt;/strong&gt;的方法&lt;/p&gt;
&lt;p&gt;而我们不妨用这段代码进行压测来验证问题是否存在，结合 AI 来帮我们写下测试代码（可以直接跳过看结果）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// TestRetryStormProblem 演示固定间隔重试导致的&amp;quot;重试风暴&amp;quot;问题
func TestRetryStormProblem(t *testing.T) {
	fmt.Println(&amp;quot;场景：100个请求同时失败，使用固定5秒间隔重试&amp;quot;)
	fmt.Println()

	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	// 创建 mock invoker，记录调用次数
	var invokeCount atomic.Int64
	invoker := mock.NewMockInvoker(ctrl)

	extension.SetLoadbalance(&amp;quot;random&amp;quot;, random.NewRandomLoadBalance)

	invoker.EXPECT().GetURL().Return(failbackUrl).AnyTimes()
	invoker.EXPECT().IsAvailable().Return(true).AnyTimes()

	// 模拟服务持续失败
	invoker.EXPECT().Invoke(gomock.Any(), gomock.Any()).DoAndReturn(
		func(ctx context.Context, inv any) *result.RPCResult {
			count := invokeCount.Add(1)
			return &amp;amp;result.RPCResult{
				Err: perrors.Errorf(&amp;quot;service unavailable (invoke #%d)&amp;quot;, count),
			}
		},
	).AnyTimes()

	clusterInvoker := registerFailback(invoker).(*failbackClusterInvoker)
	defer clusterInvoker.Destroy()

	// 模拟100个请求同时失败
	failureCount := 100
	var wg sync.WaitGroup
	startTime := time.Now()

	fmt.Printf(&amp;quot;[%s] 开始发送 %d 个请求...\n&amp;quot;, time.Now().Format(&amp;quot;15:04:05.000&amp;quot;), failureCount)

	for i := 0; i &amp;lt; failureCount; i++ {
		wg.Add(1)
		go func(idx int) {
			defer wg.Done()
			inv := invocation.NewRPCInvocation(&amp;quot;test&amp;quot;, []interface{}{}, nil)
			clusterInvoker.Invoke(context.Background(), inv)
		}(i)
	}

	wg.Wait()
	initialInvokeCount := invokeCount.Load()
	fmt.Printf(&amp;quot;[%s] 初始调用完成，失败次数: %d\n\n&amp;quot;, time.Now().Format(&amp;quot;15:04:05.000&amp;quot;), initialInvokeCount)

	// 监控重试行为（观察12秒，足够看到2轮重试）
	fmt.Println(&amp;quot;监控重试行为（观察12秒）：&amp;quot;)
	fmt.Println(&amp;quot;时间点\t\t累计调用\t本轮调用\t问题分析&amp;quot;)
	fmt.Println(&amp;quot;---------------------------------------------------------------&amp;quot;)

	lastCount := initialInvokeCount
	retryWaves := make([]int64, 0)

	for i := 0; i &amp;lt; 12; i++ {
		time.Sleep(1 * time.Second)
		currentCount := invokeCount.Load()
		delta := currentCount - lastCount

		if delta &amp;gt; 0 {
			retryWaves = append(retryWaves, delta)
			problem := &amp;quot;&amp;quot;
			if delta &amp;gt; 50 {
				problem = &amp;quot;⚠️  重试风暴！大量请求同时重试&amp;quot;
			} else if delta &amp;gt; 10 {
				problem = &amp;quot;⚠️  重试压力较大&amp;quot;
			}
			fmt.Printf(&amp;quot;%ds\t\t%d\t\t%d\t\t%s\n&amp;quot;, i+1, currentCount, delta, problem)
		}
		lastCount = currentCount
	}

	// 分析结果
	fmt.Println(&amp;quot;\n========================================&amp;quot;)
	fmt.Println(&amp;quot;=== 问题分析 ===&amp;quot;)
	fmt.Println(&amp;quot;========================================&amp;quot;)
	fmt.Printf(&amp;quot;总执行时间: %v\n&amp;quot;, time.Since(startTime))
	fmt.Printf(&amp;quot;总调用次数: %d\n&amp;quot;, invokeCount.Load())
	fmt.Printf(&amp;quot;重试波次数: %d\n\n&amp;quot;, len(retryWaves))

	if len(retryWaves) &amp;gt; 0 {
		fmt.Println(&amp;quot;每次重试波的请求数量:&amp;quot;)
		for i, wave := range retryWaves {
			fmt.Printf(&amp;quot;  第 %d 波: %d 个请求&amp;quot;, i+1, wave)
			if wave &amp;gt; 50 {
				fmt.Printf(&amp;quot; ⚠️  风暴级别&amp;quot;)
			}
			fmt.Println()
		}
		fmt.Println()
	}

	// 验证确实发生了重试风暴
	assert.Greater(t, len(retryWaves), 0, &amp;quot;应该发生了重试&amp;quot;)
	if len(retryWaves) &amp;gt; 0 {
		maxWave := int64(0)
		for _, wave := range retryWaves {
			if wave &amp;gt; maxWave {
				maxWave = wave
			}
		}
		assert.Greater(t, maxWave, int64(50), &amp;quot;应该出现重试风暴（单次重试&amp;gt;50个请求）&amp;quot;)
	}
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而我们测试后的结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;场景：100个请求同时失败，使用固定5秒间隔重试
总执行时间：12.06秒
总调用次数：300次
重试波次数：4波
重试波详情：
- 第 1 波（6秒时）：95个请求 ⚠️ 风暴级别
- 第 2 波（7秒时）：5个请求
- 第 3 波（11秒时）：95个请求 ⚠️ 风暴级别
- 第 4 波（12秒时）：5个请求
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;核心问题&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;所有失败请求的 lastT 时间几乎相同（0.03秒内）&lt;/li&gt;
&lt;li&gt;5秒后，95个任务同时满足重试条件&lt;/li&gt;
&lt;li&gt;95个 goroutine 同时启动，形成&amp;quot;重试风暴&amp;quot;&lt;/li&gt;
&lt;li&gt;如果服务刚恢复，会被大量重试请求再次压垮&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;影响范围&lt;/h3&gt;
&lt;p&gt;如果在生产环境：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务故障时，大量请求同时失败&lt;/li&gt;
&lt;li&gt;5秒后，所有请求同时重试&lt;/li&gt;
&lt;li&gt;刚恢复的服务被重试风暴压垮&lt;/li&gt;
&lt;li&gt;形成&amp;quot;雪崩效应&amp;quot;，服务无法正常恢复&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以我们目标就是降低这种危害的发生率！&lt;/p&gt;
&lt;h2&gt;指数退避 + 随机抖动的原理&lt;/h2&gt;
&lt;h3&gt;指数退避&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;简单概括&lt;/strong&gt;：指数退避也是一种重试策略，当操作失败时，每次重试前的等待时间会按指数级增长（如 1s、2s、4s、8s...）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;计算公式&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;backoff = baseInterval × (2 ^ retries)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;第1次重试：1s  = 1 × 2^0
第2次重试：2s  = 1 × 2^1
第3次重试：4s  = 1 × 2^2
第4次重试：8s  = 1 × 2^3
第5次重试：16s = 1 × 2^4
第6次重试：32s = 1 × 2^5
第7次重试：60s = min(64, 60) // 设置上限
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自动分散重试时间&lt;/li&gt;
&lt;li&gt;失败越多，等待越久，给服务恢复时间&lt;/li&gt;
&lt;li&gt;避免频繁重试浪费资源&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果100个请求同时失败，它们的重试时间仍然相同&lt;/li&gt;
&lt;li&gt;仍可能产生&amp;quot;同步重试&amp;quot;问题&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;随机抖动（Jitter）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;简单概括&lt;/strong&gt;：是在&lt;strong&gt;固定时间间隔上增加随机波动&lt;/strong&gt;的技术。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心思想&lt;/strong&gt;： 在退避时间基础上加入随机偏移，打破同步性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常见抖动策略&lt;/strong&gt;：&lt;/p&gt;
&lt;h4&gt;策略1：Full Jitter（完全抖动）&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;sleep = random(0, backoff)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;优点：最大程度分散重试&lt;/li&gt;
&lt;li&gt;缺点：可能过于激进&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;策略2：Equal Jitter（均等抖动）&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;sleep = backoff/2 + random(0, backoff/2)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;优点：平衡分散和等待时间&lt;/li&gt;
&lt;li&gt;缺点：实现稍复杂&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;策略3：Decorrelated Jitter（去相关抖动）&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;sleep = min(cap, random(base, sleep × 3))
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;优点：更平滑的分布&lt;/li&gt;
&lt;li&gt;缺点：不够直观，随机性太强难以调试&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;指数退避+抖动组合&lt;/h3&gt;
&lt;p&gt;考虑到我们项目的改进需要有利他人调试，并且达成我们分散重试的目的，我们可以选择 Equal Jitter 来实现，大概思路：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func calculateBackoff(retries int64, baseInterval, maxInterval time.Duration) time.Duration {
    // 指数退避
    backoff := baseInterval * time.Duration(1&amp;lt;&amp;lt;retries)
    if backoff &amp;gt; maxInterval {
        backoff = maxInterval
    }

    // 均等抖动：50%固定 + 50%随机
    half := backoff / 2
    jitter := time.Duration(rand.Int63n(int64(half)))

    return half + jitter
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;效果对比&lt;/h4&gt;
&lt;p&gt;让 ai 帮我画了画&lt;/p&gt;
&lt;p&gt;固定间隔（改进前）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;时间轴：
0s  ████████████████████ (100个请求失败)
5s  ████████████████████ (95个请求同时重试) ⚠️ 风暴
10s ████████████████████ (95个请求再次同时重试) ⚠️ 风暴
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;指数退避 + 随机抖动（改进后）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;时间轴：
0s  ████████████████████ (100个请求失败)
1s  ██░░░░░░░░░░░░░░░░░░ (10-15个重试，分散在0.5-1.5s)
2s  ░░██░░░░░░░░░░░░░░░░ (8-12个重试，分散在1-3s)
4s  ░░░░████░░░░░░░░░░░░ (6-10个重试，分散在2-6s)
8s  ░░░░░░░░████░░░░░░░░ (5-8个重试，分散在4-12s)
16s ░░░░░░░░░░░░████░░░░ (剩余重试，分散在8-24s)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;适用场景&lt;/h3&gt;
&lt;p&gt;并非所有类似的场景都适用这个方案，而我大概列举一些仅供参考，也并不一定是相同场景下的最佳实践，还需具体情况具体分析。&lt;/p&gt;
&lt;h4&gt;场景1：异步后台任务&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;特点：&lt;/strong&gt; 非实时、允许延迟、必须送达&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;示例：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;消息通知（邮件、短信、推送）&lt;/li&gt;
&lt;li&gt;日志记录（审计日志、操作日志）&lt;/li&gt;
&lt;li&gt;数据同步（缓存更新、索引更新）&lt;/li&gt;
&lt;li&gt;统计上报（用户行为、业务指标）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;场景2：外部服务调用&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;特点：&lt;/strong&gt; 依赖第三方、可能限流、需要重试&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;示例：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;API调用（支付接口、地图API、云服务API）&lt;/li&gt;
&lt;li&gt;数据库连接（主从切换、网络抖动）&lt;/li&gt;
&lt;li&gt;消息队列（消费失败重试）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;场景3：资源竞争场景&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;特点：&lt;/strong&gt; 高并发、资源有限、需要错峰&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;示例：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;分布式锁获取&lt;/li&gt;
&lt;li&gt;连接池获取&lt;/li&gt;
&lt;li&gt;限流场景下的重试&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;不适合使用指数退避的场景：&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;实时性要求高：&lt;/strong&gt; 用户等待结果的场景（应该快速失败或使用 Failover）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;瞬时故障：&lt;/strong&gt; 网络抖动（应该用固定短间隔）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;事务操作：&lt;/strong&gt; 需要立即知道成功/失败的场景&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;强一致性要求：&lt;/strong&gt; 不能接受延迟的场景&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;项目中需要改进的模块&lt;/h2&gt;
&lt;p&gt;结合以上分析再回归项目（dubbo-go），我大概找到了两处比较适合这个策略的地方可以改进：&lt;/p&gt;
&lt;h4&gt;Failback 集群重试&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;文件：&lt;/strong&gt; &lt;code&gt;cluster/cluster/failback/cluster_invoker.go:106&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;当前问题：&lt;/strong&gt; 固定5秒间隔，已验证存在重试风暴&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Nacos 注册中心订阅重试&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;文件：&lt;/strong&gt; &lt;code&gt;registry/nacos/registry.go:202-213&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;当前问题：&lt;/strong&gt; 无间隔死循环重试，导致 &lt;strong&gt;CPU 100% 空转&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;问题代码：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func (nr *nacosRegistry) subscribeUntilSuccess(...) {
    for {
        if !nr.IsAvailable() {
            return
        }
        err := nr.subscribe(...)
        if err == nil {
            return
        }
        // 问题：没有 sleep，立即重试
        // 导致 CPU 疯狂空转
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;问题严重性：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;指标&lt;/th&gt;
&lt;th&gt;旧实现（无间隔）&lt;/th&gt;
&lt;th&gt;新实现（指数退避）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;每秒重试次数&lt;/td&gt;
&lt;td&gt;8800 万次&lt;/td&gt;
&lt;td&gt;5 次&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU 占用&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;~0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 小时总重试&lt;/td&gt;
&lt;td&gt;36 亿次&lt;/td&gt;
&lt;td&gt;130 次&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;CPU 监控对比：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;阶段 1（空闲）：CPU 0%&lt;/li&gt;
&lt;li&gt;阶段 2（旧实现运行）：CPU 56-74%（单核打满）&lt;/li&gt;
&lt;li&gt;阶段 3（新实现运行）：CPU 0.0-0.1%&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;改进思路&lt;/h2&gt;
&lt;h3&gt;设计原则&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;简洁性优先：&lt;/strong&gt; 直接使用成熟的第三方库而非自己实现，降低维护成本&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;渐进式改进：&lt;/strong&gt; 先改造最需要的 Failback 模块，验证效果后再推广&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;兼容性保障：&lt;/strong&gt; 保持原有 API 不变，对现有使用者透明&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可观测性：&lt;/strong&gt; 添加日志记录，方便问题排查&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;技术选型&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;选择 &lt;code&gt;github.com/cenkalti/backoff/v4&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个库是 Go 生态中最成熟的退避算法实现之一，有以下优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;stars 数量多，广泛使用，经过生产验证&lt;/li&gt;
&lt;li&gt;支持多种退避策略（指数、常量等）&lt;/li&gt;
&lt;li&gt;自带随机抖动（Randomization Factor）&lt;/li&gt;
&lt;li&gt;API 简洁，易于集成&lt;/li&gt;
&lt;li&gt;零依赖，不会引入额外包&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;配置参数：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;backoff := backoff.NewExponentialBackOff()
backoff.InitialInterval = 1 * time.Second      // 初始间隔 1s
backoff.MaxInterval = 60 * time.Second          // 最大间隔 60s
backoff.Multiplier = 1.5                        // 倍增因子（默认）
backoff.MaxElapsedTime = 0                      // 永不超时
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;重试间隔演变：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1s → 1.5s → 2.25s → 3.4s → 5s → 7.5s → ... → 60s (封顶)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;具体实现&lt;/h3&gt;
&lt;h4&gt;核心修改点&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;1. 在 &lt;code&gt;retryTimerTask&lt;/code&gt; 结构体中添加 backoff 字段&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type retryTimerTask struct {
    invocation   protocol.Invocation
    retries      int64
    lastT        time.Time
    interval     time.Duration
    invoker      protocol.Invoker
    invokersSuccessiveFailureTimes map[protocol.Invoker]int64
    mu                             sync.Mutex

    // 新增字段
    nextBackoff time.Duration          // 下次重试的等待时间
    backoff     *backoff.ExponentialBackOff  // 退避计算器
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 修改重试检查逻辑&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;将原来的固定 5 秒判断改为动态计算：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 改进前
if time.Since(retryTask.lastT).Seconds() &amp;lt; 5 {
    break
}

// 改进后
if time.Since(retryTask.lastT) &amp;lt; retryTask.nextBackoff {
    break
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 在每次重试后更新 nextBackoff&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func (invoker *failbackClusterInvoker) tryTimerTaskProc(ctx context.Context, retryTask *retryTimerTask) {
    // ... 执行重试逻辑

    // 计算下次退避时间
    retryTask.nextBackoff = retryTask.backoff.NextBackOff()
    if retryTask.nextBackoff == backoff.Stop {
        logger.Infof(&amp;quot;[Failback] Backoff stopped for task, removing from queue&amp;quot;)
        return
    }

    retryTask.retries++
    retryTask.lastT = time.Now()

    // 添加日志便于调试
    logger.Infof(&amp;quot;[Failback] Retry #%d scheduled after %v&amp;quot;,
        retryTask.retries, retryTask.nextBackoff)

    // 放回队列继续重试
    invoker.taskList.Put(retryTask)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. 初始化 backoff 实例&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func newRetryTimerTask(invocation protocol.Invocation, invoker protocol.Invoker) *retryTimerTask {
    // 创建 backoff 实例
    b := backoff.NewExponentialBackOff()
    b.InitialInterval = 1 * time.Second
    b.MaxInterval = 60 * time.Second
    b.MaxElapsedTime = 0  // 永不超时

    return &amp;amp;retryTimerTask{
        invocation: invocation,
        lastT:      time.Now(),
        invoker:    invoker,
        invokersSuccessiveFailureTimes: make(map[protocol.Invoker]int64),
        backoff:    b,
        nextBackoff: b.NextBackOff(),  // 获取首次退避时间
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Nacos 订阅重试实现&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;核心改动：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;backoff.NewExponentialBackOff()&lt;/code&gt; + &lt;code&gt;backoff.RetryNotify()&lt;/code&gt; 包装重试逻辑：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func (nr *nacosRegistry) subscribeUntilSuccess(serviceName string, notifyListener registry.NotifyListener) {
    // 创建指数退避配置
    bo := backoff.NewExponentialBackOff()
    bo.InitialInterval = 1 * time.Second    // 首次 1s
    bo.MaxInterval = 30 * time.Second       // 最大 30s
    bo.MaxElapsedTime = 0                   // 永不超时

    // 定义重试操作
    operation := func() error {
        // 检查注册中心是否可用
        if !nr.IsAvailable() {
            return backoff.Permanent(errors.New(&amp;quot;registry not available&amp;quot;))
        }
        // 执行订阅
        return nr.subscribe(getSubscribeName(serviceName), notifyListener)
    }

    // 定义通知函数（记录日志）
    notify := func(err error, duration time.Duration) {
        logger.Infof(&amp;quot;[Nacos] Subscribe failed for %s, retrying in %v: %v&amp;quot;,
            serviceName, duration, err)
    }

    // 执行带重试的订阅
    err := backoff.RetryNotify(operation, bo, notify)
    if err != nil {
        logger.Errorf(&amp;quot;[Nacos] Subscribe permanently failed for %s: %v&amp;quot;, serviceName, err)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关键点说明：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;backoff.Permanent()&lt;/strong&gt; - 用于标记不可恢复的错误（如注册中心关闭），直接停止重试&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;backoff.RetryNotify()&lt;/strong&gt; - 自动处理重试逻辑和间隔计算，简化代码&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;notify 回调&lt;/strong&gt; - 记录每次重试的时间间隔，便于排查问题&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;配置参数：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;InitialInterval: 1s     // 首次重试 1s
MaxInterval: 30s        // 最大间隔 30s（比 Failback 的 60s 短）
MaxElapsedTime: 0       // 永不超时，持续重试
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;重试间隔演变：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1s → 1.5s → 2.25s → 3.4s → 5s → 7.5s → ... → 30s (封顶)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;测试验证&lt;/h3&gt;
&lt;h4&gt;Failback 测试&lt;/h4&gt;
&lt;p&gt;为了确保改进有效，编写相应的测试来验证新的行为：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;测试重点：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;验证首次重试时间约为 1 秒&lt;/strong&gt;（而非原来的 5 秒）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;验证重试会持续进行&lt;/strong&gt;（不会因为 backoff 而停止）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用原子计数器&lt;/strong&gt;避免并发问题&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func TestFailbackClusterInvokerWithBackoff(t *testing.T) {
    // ... 设置 mock

    startTime := time.Now()

    // 触发失败
    clusterInvoker.Invoke(context.Background(), inv)

    // 等待 2 秒观察重试
    time.Sleep(2 * time.Second)

    elapsed := time.Since(startTime)
    retryCount := invokeCount.Load()

    // 断言：首次重试应该在 ~1s 发生（允许误差 ±0.5s）
    assert.Greater(t, elapsed, 500*time.Millisecond)
    assert.Less(t, elapsed, 2500*time.Millisecond)

    // 断言：应该至少发生了 1 次重试
    assert.Greater(t, retryCount, int64(1))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Nacos 测试&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;测试场景：模拟旧实现 vs 新实现的性能对比&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func TestNacosSubscribeWithBackoff(t *testing.T) {
    // 旧实现模拟：无间隔死循环
    oldImplCount := atomic.Int64{}
    stop := make(chan struct{})

    go func() {
        for {
            select {
            case &amp;lt;-stop:
                return
            default:
                oldImplCount.Add(1)
                // 无 sleep，疯狂空转
            }
        }
    }()

    // 运行 1 秒
    time.Sleep(1 * time.Second)
    close(stop)

    oldRetries := oldImplCount.Load()
    fmt.Printf(&amp;quot;旧实现：1秒内重试 %d 次\n&amp;quot;, oldRetries)

    // 新实现模拟：指数退避
    newImplCount := atomic.Int64{}
    stop2 := make(chan struct{})

    go func() {
        bo := backoff.NewExponentialBackOff()
        bo.InitialInterval = 100 * time.Millisecond
        bo.MaxInterval = 500 * time.Millisecond

        for {
            select {
            case &amp;lt;-stop2:
                return
            default:
                interval := bo.NextBackOff()
                newImplCount.Add(1)
                time.Sleep(interval)
            }
        }
    }()

    time.Sleep(1 * time.Second)
    close(stop2)

    newRetries := newImplCount.Load()
    fmt.Printf(&amp;quot;新实现：1秒内重试 %d 次\n&amp;quot;, newRetries)

    // 验证：新实现的重试次数应该远小于旧实现
    assert.Less(t, newRetries, oldRetries/1000000)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;测试结果：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;旧实现：1秒内重试 88000000 次（8800万次）
新实现：1秒内重试 5 次
性能提升：减少 99.99999% 的无效重试
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;效果对比&lt;/h3&gt;
&lt;h4&gt;Failback 集群重试效果&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th&gt;改进前（固定 5s）&lt;/th&gt;
&lt;th&gt;改进后（指数退避）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;首次重试&lt;/td&gt;
&lt;td&gt;5s&lt;/td&gt;
&lt;td&gt;1s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;重试间隔&lt;/td&gt;
&lt;td&gt;固定 5s&lt;/td&gt;
&lt;td&gt;1s → 1.5s → 2.25s → ... → 60s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;并发重试&lt;/td&gt;
&lt;td&gt;100 个请求同时重试&lt;/td&gt;
&lt;td&gt;分散在不同时间点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;重试风暴&lt;/td&gt;
&lt;td&gt;存在&lt;/td&gt;
&lt;td&gt;基本消除&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;服务恢复&lt;/td&gt;
&lt;td&gt;困难（持续被压垮）&lt;/td&gt;
&lt;td&gt;容易（压力逐步恢复）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4&gt;Nacos 订阅重试效果&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th&gt;改进前（无间隔）&lt;/th&gt;
&lt;th&gt;改进后（指数退避）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;每秒重试&lt;/td&gt;
&lt;td&gt;8800 万次&lt;/td&gt;
&lt;td&gt;5 次&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU 占用&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;~0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 小时总重试&lt;/td&gt;
&lt;td&gt;36 亿次&lt;/td&gt;
&lt;td&gt;130 次&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;资源浪费&lt;/td&gt;
&lt;td&gt;严重&lt;/td&gt;
&lt;td&gt;几乎无&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;关于随机抖动&lt;/h3&gt;
&lt;p&gt;为什么我们不需要手动添加抖动逻辑？&lt;/p&gt;
&lt;p&gt;&lt;code&gt;backoff.ExponentialBackOff&lt;/code&gt; 内置了 &lt;strong&gt;Randomization Factor&lt;/strong&gt;（默认值 0.5），意味着：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;实际等待时间 = 计算时间 × [0.5, 1.5]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如计算出的等待时间是 2 秒，实际等待可能是 1s - 3s 之间的随机值。这就是为什么即使 100 个请求同时失败，它们的重试时间也会自然分散。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么这样设计很巧妙？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;避免了手动实现抖动的复杂度&lt;/li&gt;
&lt;li&gt;让测试更稳定（不需要精确断言时间）&lt;/li&gt;
&lt;li&gt;提供了足够的分散性来避免重试风暴&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Nacos 并发安全：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;backoff.RetryNotify()&lt;/code&gt; 在单个 goroutine 中串行执行，天然避免了并发问题。每个订阅任务都在独立的 goroutine 中运行，互不干扰。&lt;/p&gt;
&lt;h3&gt;PR 合并情况&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;相关 PR：&lt;/strong&gt; &lt;a href=&quot;https://github.com/apache/dubbo-go/pull/3180&quot;&gt;apache/dubbo-go#3180&lt;/a&gt;、&lt;a href=&quot;https://github.com/apache/dubbo-go/pull/3178&quot;&gt;apache/dubbo-go#3178&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关联 Issue：&lt;/strong&gt; &lt;a href=&quot;https://github.com/apache/dubbo-go/issues/3179&quot;&gt;#3179 - Failback 固定间隔导致重试风暴&lt;/a&gt;、&lt;a href=&quot;https://github.com/apache/dubbo-go/issues/3177&quot;&gt;#3177 - Nacos 订阅重试无间隔导致 CPU 100%&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;题外话&lt;/h2&gt;
&lt;p&gt;虽然我这篇文章是以讲这么个技术为主，但是我更希望大家能够在遇到具体的场景问题时候再去探索调研有哪些合适的技术方案，而不推荐为了用而用。&lt;/p&gt;
&lt;p&gt;还有，如果愿意的话，欢迎一起来贡献我们 &lt;a href=&quot;https://github.com/apache/dubbo-go&quot;&gt;apache/dubbo-go&lt;/a&gt; 项目！&lt;/p&gt;
</content:encoded><category>开源经验</category></item><item><title>2025 年度总结</title><link>https://caicaiis.cc/2/summary</link><guid>10-50</guid><description>兜兜转转又一年</description><pubDate>Wed, 31 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/nijika.jpg&quot; alt=&quot;alt text&quot;&gt;
兜兜转转又一年&lt;/p&gt;
&lt;p&gt;在 2025 年的尾声我又再次重建了 blog，虽然已经快数不清自己已经整过多少个 blog 了，但希望能坚持产出，在这个互联网的小角落中多留下自己的思考。&lt;/p&gt;
&lt;p&gt;由于自己在今年大部分时间都是没有什么记录的习惯，所以现在回顾一年的渠道只剩下了翻相册（虽然相册也没有经过良好的整理就是了）以及查看 QQ空间 的一些碎片，悲&lt;/p&gt;
&lt;h2&gt;🌿 记忆碎片&lt;/h2&gt;
&lt;h3&gt;“每周总结”的结束&lt;/h3&gt;
&lt;p&gt;早期是我 QQ 好友并且没屏蔽我说说的朋友们可能会知道，我之前很喜欢把自己每周的学习总结发到 QQ动态 上，这个习惯我成功坚持了一年，并且在刚好一周年（11月/24日）当天凌晨终结了这个习惯。
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/summary.jpg&quot; alt=&quot;alt text&quot;&gt;
以上是我其中一期，可以看出我还是个分享欲极强的人。&lt;/p&gt;
&lt;p&gt;为何不继续下呢？先从这个的起始讲起，24 年那时候我还是个大一学生，我其实也并不算贪玩，但是以我现在的视角看过去自己当时大多时候还是东学西学，全凭感觉学习没有明确的目标，就算有小目标也不知道怎么走。大多时候都是自己在瞎摸索，加之自己当时不习惯请教学长学姐各种大佬们因此走了很多弯路，极端点也可以说自己大一的学习时间几乎都浪费了。&lt;/p&gt;
&lt;p&gt;当时开始写总结是因为我觉得这样一来我可以直观看到自己一周的学习情况，而不是觉得自己又虚度过了一周，二来是觉得能被大佬们看到能给我点指导，三来是适当满足自己分享欲。&lt;/p&gt;
&lt;p&gt;这是一个无形的压力，会催促自己一周内多学点。但是逐渐逐渐，发现自己对这个的态度却转变成了一种每周任务般的事情，明明学习可以是一件很享受的事情。所以觉得这是逐渐背离了初心导致我放弃了这一个形式。&lt;/p&gt;
&lt;p&gt;如今自己已经明确了以后要走的方向，并且逐渐学会怎么更好的和别人交流、请教，同辈们的进步也会督促自己努力追上他们，努力后所达成的结果也能分享出来满足分享欲，这已经形成了闭环，既然有更良性的方式又何必拘于以前的形式。再者自己如今也还是会总结在私人文档上，不过更加随意，内容更加丰富，在自己记录和思考的私人空间，不再公开出来罢了。&lt;/p&gt;
&lt;h2&gt;新的开源社区&lt;/h2&gt;
&lt;p&gt;加入新的开源社区纯属偶然。&lt;/p&gt;
&lt;p&gt;因为有群友是 Apache/dubbo 项目的 committer，所以我也对这个项目产生了兴趣。我也曾尝试贡献，但是那个项目的 issue 和现存 pr 量让我望而却步，而且没有前辈带带不知道从哪开始，便逐渐失去热情。&lt;/p&gt;
&lt;p&gt;而遇到如今的 dubbogo 社区也是偶然发现这个项目，虽然它体量没 dubbo java 那边大，但是 issue 数量不算多，而且 pr 处理的也很快且严谨。而我当时也是纯在各种开源项目打野，在 dubbo-go 项目中发现了些 todo 然后挑比较简单的顺手解决了。同时也被 committer 拉进了社区开发群，当时就感受到了社区的热情。&lt;/p&gt;
&lt;p&gt;在我们社区每周周末都会有周会，也有个类似组会的会，前者是主要讨论项目进展以及有没有人主动领取任务（像冒险者公会那样），或者处理剩下的 pr，后者是挨个点名在会上的成员，大概说说自己这一周在项目任务处理情况，是否需要帮助或者有要和社区讨论的地方。无论是社区的 ld 或者一些厉害的 committer 前辈，我都能看出他们对项目的热情，并且愿意给我指导，自己参与贡献也能锻炼各方面能力，百利无一害所以便决定长期呆在这个社区里发展。&lt;/p&gt;
&lt;p&gt;自己在项目的学习过程中也并不是一帆风顺，刚开始自己不熟悉项目，不知道从哪看起合适，没有章法的硬看源代码，并且试图把每个方法的具体实现看懂，还沉迷于做些脏活累活来满足自己。这个过程持续也比较长一段时间，究其根本就是自己当时学习项目经验不足还不习惯请教前辈们，他们一定比我更熟悉项目，也踩过很多坑，寻求帮助确实可以助我更快的适应项目。所以我发现这个问题后，主动加了一位前辈发了一段话表述自己的情况，也得到了好的回应。也是在那之后逐渐懂得请教他人，这也是团队合作中比较重要的部分。&lt;/p&gt;
&lt;p&gt;在项目贡献方面，自己也还并没有做多少有比较大意义的活，在这个这么好的平台，不多花时间做点大点的贡献有点可惜，所以在新的一年还是想继续贡献，勇敢接些大的 feature 类任务挑战自己，加油回报社区。&lt;/p&gt;
&lt;h2&gt;多记录，勤思考&lt;/h2&gt;
&lt;p&gt;正如前文所说，自己之前并没有记录的习惯，以前尝试搭的 blog 也因为自己不是很满意没有热情而没有推下去。并且 notion、 obsidian 等我也尝试用过，也是没有什么想记录的。但是在各种群友的交流下，也是形成了适合自己的记录方式，而现在用的知识管理系统是群友推荐的 siyuan。当时很快便上手了，并且支持项目也充了会员，不知为何反而 siyuan 这种能让我更有动力去记各种东西。
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/siyuan.png&quot; alt=&quot;alt text&quot;&gt;
后面还会记更多东西，然后不断更新迭代文档，一些文档觉得更新到了合适的程度便会再完善点发到 blog 上来，不止计算机学习方面，觉得有趣的我都可能会发。&lt;/p&gt;
&lt;h2&gt;新搭档与老战友&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/macbook.jpg&quot; alt=&quot;alt text&quot;&gt;
如图（随手拍的，拍的并不好看hhh），今年入手了人生第一台 macbook ，二手平台上淘的，价格还行。这 blog 我也是用 macbook 搭建的，以及这篇博文也是。所以我现在也算是熟悉了 macbook 的使用，当然这个新搭档也还有很多地方可以开发。&lt;/p&gt;
&lt;p&gt;能快速熟练使用是要感谢社团那些伙伴们，他们教的我如何使用，传我各种 app 以及告诉我些常用的快捷键，告诉我如何快速上手。当然我入手 macbook 还有个原因就是看到社团好几位伙伴也在用，我就有点心动。而如今自己很多编码环境都搭在了这 macbook 上，goland、 idea 等在这上面使用体验还挺不错的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/r9000p.jpg&quot; alt=&quot;alt text&quot;&gt;
老战友是指我这台 r9000p 可以看出已经很有岁月的痕迹了，不过是我平常很少清理他（因为有点重，所以懒得动），不过外表怎样无所谓了。现在这台上面是装了 win11，在 win11 上也装了 wsl，偶尔也会用这台编码，毕竟连着大屏幕以及键鼠还是爽的。不过主要用途还是看番和打游戏，每天饭点都会有点仪式感的清理桌面然后用这台看番，游戏更不用说了好歹也是游戏本。&lt;/p&gt;
&lt;p&gt;虽然 macbook 能远控这台游戏本，不过真要玩一些大点的游戏还是不太方便，延迟是一方面设备也是一方面，虽然也可以试着连上手柄然后远控玩些适配手柄的游戏，不过感觉还是不够滋味，所以就分工明确了。等何时脱离了学校再在体验上下功夫，现在就安于现状就好了。&lt;/p&gt;
&lt;h2&gt;年度最佳&lt;/h2&gt;
&lt;h3&gt;🍿影视&lt;/h3&gt;
&lt;p&gt;今年没有具体数自己看了几部动漫几场电影，不过给年度最佳还是能评出来的！&lt;/p&gt;
&lt;h4&gt;CAICAII 年度番剧：《时光流逝，饭菜依旧美味》&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/rice.jpg&quot; alt=&quot;alt text&quot;&gt;
一部讲述五位的女大学生在刚进入大学时候，无意间成立了食文化研究社，并在吃吃喝喝、欢声笑语中逐渐加深友情的暖心日常喜剧。整体节奏较缓，其主线“做菜”方面也不含糊，所以不仅能看到几位可爱的女孩子们的欢乐日常观看，也能欣赏制作组所呈现的美味饭菜，而我每次也都是在饭点边吃饭边看番，非常下饭&lt;/p&gt;
&lt;p&gt;虽然也会有几集表现的经费有所不足，但是制作组也靠自己的表现形式圆满呈现性格鲜明的吃货五人组，与那满满当当的青春味道。尤其是想推荐它的片尾曲《味噌汁とバター（味增汤和黄油）》，正如这首曲里所说的“一旦我们习以为常于幸福，我们会期盼再度感受幸福”，我也想着能有很多部像这样的番陪我度过无数的饭点，或者再演个千集也不是不可以口牙。&lt;/p&gt;
&lt;p&gt;除此之外还有《金牌得主》、《琉璃的宝石》、《赛马娘 芦毛灰姑娘》还有《我怎么可能成为你的恋人，不行不行！（※不是不可能！？）》等还有好几部就不一一列举了，续作方面更是多的多，也都非常喜欢。&lt;/p&gt;
&lt;p&gt;当然我也不会忘记年初还有部番叫：《BanG Dream! Ave Mujica》
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/mujica.jpg&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h4&gt;CAICAII 年度电影：《孤独摇滚！》剧场版&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/rock.jpg&quot; alt=&quot;alt text&quot;&gt;
众所周知，我是非常喜欢《孤独摇滚》这一 IP，所以选这个也不算奇怪。&lt;/p&gt;
&lt;p&gt;不过主要还是我今年看的电影并不多，只看了如《机动战士Gundam GQuuuuuuX》、《疯狂动物城2》等，屈指可数，所以明年争取多看点电影！&lt;/p&gt;
&lt;h3&gt;📚图书&lt;/h3&gt;
&lt;h4&gt;CAICAII 年度图书：《哲学问题》&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/philosophy.jpg&quot; alt=&quot;alt text&quot;&gt;
这本书从一些人们或许从未思考过的问题切入，然后逐渐引入和开展对罗素自己或者他所赞同的观点进行分析。其中还有引入很多有趣的例子，整体行文并不枯燥。&lt;/p&gt;
&lt;p&gt;多思考，我们的心灵是自由的，我们正朝着成为‘宇宙公民’的目标前行。&lt;/p&gt;
&lt;p&gt;今年还看了《科学哲学》、《夜莺与玫瑰》等书，总数并不算多，争取明年多看几本，下次年度总结在这一栏列的再丰富且详细点。&lt;/p&gt;
&lt;h3&gt;🎮游戏&lt;/h3&gt;
&lt;h4&gt;CAICAII 年度游戏：《樱之诗》/《樱之刻》&lt;/h4&gt;
&lt;p&gt;（此处年度游戏是指 CAICAII 于 25 年接触的新游戏，而不是 25 年新出的游戏）
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/sakura.jpg&quot; alt=&quot;alt text&quot;&gt;
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/time.jpg&quot; alt=&quot;alt text&quot;&gt;
十年樱诗，百年樱刻。两部作品我都无比喜欢，喜欢樱之诗的美，喜欢樱之刻的燃。&lt;/p&gt;
&lt;p&gt;之所以两部都写而不是挑其中一部的原因是个人认为樱之诗有着不完整的结局，而加上樱之刻主线才比较完整。虽整体来看大故事有所欠缺，但是切成很多个小故事各个又是描绘的非常精彩。无论是健一郎的篇章，还是夏目圭的过去，还是插在其中的许多日常情节，都有着很美的氛围。&lt;/p&gt;
&lt;p&gt;无论是美术，还是剧情这游戏都做的很好，而音乐方面我更是喜欢，今年的年度歌曲便是游戏 OST 之一《舞い上がる因果交流のひかり》，而且这两部的 op 我也一直都很喜欢听
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/song.jpg&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h2&gt;其他一些值得一提的事&lt;/h2&gt;
&lt;h3&gt;✈️旅游&lt;/h3&gt;
&lt;p&gt;今年也是去了很多地方旅游&lt;/p&gt;
&lt;h4&gt;&lt;em&gt;回家&lt;/em&gt;&lt;/h4&gt;
&lt;p&gt;也不算旅游，但作为身常在异乡的大学生还是倒也可以说是旅行。&lt;/p&gt;
&lt;p&gt;印象最深的便是今年的寒假回家时候也是一次性尝试了各种交通工具一条龙服务：
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/return.jpg&quot; alt=&quot;alt text&quot;&gt;
在老家也不是一直宅着，也有出门去逛，比如新年时候就带着外公和表弟一起去了不远的铜鼓岭逛了一趟，还能听到外公分享自己以前的各种往事
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/tongguling.jpg&quot; alt=&quot;alt text&quot;&gt;
并不算多大的山⛰️呢，不过给外公拍了很多照片挺有趣的。&lt;/p&gt;
&lt;h4&gt;&lt;em&gt;深圳和广州&lt;/em&gt;&lt;/h4&gt;
&lt;p&gt;当初暑假突发奇想想着回家前先顺路去深圳和广州玩一圈，虽然之前也来过了，算是故地重游不过是自己单人出游所以比较随心所欲。&lt;/p&gt;
&lt;p&gt;在深圳还能被许久不见的老哥请一顿饭
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/brother.jpg&quot; alt=&quot;alt text&quot;&gt;
还有去哈工深和网友面基，坐在他小电驴后座很快就逛完了校园一圈
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/hgs.jpg&quot; alt=&quot;alt text&quot;&gt;
在广州也是去住的上一次来的同个酒店，依然在北京路那，不远就能吃些甜品。然后在广州依旧是和各个网友面基，去了大学城，也去了华工另个校区，还进行食堂的试吃
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/hlg.jpg&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h4&gt;&lt;em&gt;南京&lt;/em&gt;&lt;/h4&gt;
&lt;p&gt;劳动节有段小假期，所以和同届朋友还有学长一起去了趟南京游玩，还在南京与我那最好的哥们会面，一起看电影和吃饭
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/bocchi.jpg&quot; alt=&quot;alt text&quot;&gt;
是的当时也是看的孤独摇滚剧场版&lt;/p&gt;
&lt;p&gt;也来真正的南大打卡了
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/nju.jpg&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;p&gt;底下有张三国杀角色牌的孙权
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/sunquan.jpg&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h4&gt;&lt;em&gt;上海与苏州&lt;/em&gt;&lt;/h4&gt;
&lt;p&gt;能去上海就得感谢俱乐部的报销出游了，主要是去参加 CCF中国开源大会，然后去和同行伙伴们体验各种稍微有点纸醉金迷的生活（笑
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/ccf.jpg&quot; alt=&quot;alt text&quot;&gt;
进行 github 的许多人面基
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/git.jpg&quot; alt=&quot;alt text&quot;&gt;
因为还有时间，同住一个房间的伙伴提议要不要去苏州玩一天第二天再从上海回去，于是我们就直接出发
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/suzhou.jpg&quot; alt=&quot;alt text&quot;&gt;
去看那里的小桥流水，享受和别人边聊天散步
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/water.jpg&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h4&gt;&lt;em&gt;厦门与福州&lt;/em&gt;&lt;/h4&gt;
&lt;p&gt;国庆假期也不想呆宿舍闲着，于是就去了还没去过的福建，虽然也没去几天
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/west.jpg&quot; alt=&quot;alt text&quot;&gt;
自己一个人在晚上去西湖公园散步，风很凉爽
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/south.jpg&quot; alt=&quot;alt text&quot;&gt;
传说中的南墙啊&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/kawaii.jpg&quot; alt=&quot;alt text&quot;&gt;
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/kawaii2.jpg&quot; alt=&quot;alt text&quot;&gt;
在回去的滨海快线站看到的可爱的画&lt;/p&gt;
&lt;h4&gt;&lt;em&gt;返校&lt;/em&gt;&lt;/h4&gt;
&lt;p&gt;只记得自己大二开学当天返校的晚霞很美
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/night.jpg&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h3&gt;🤼‍♀️社交&lt;/h3&gt;
&lt;p&gt;或许在我大多好友印象中，我就是个有很多朋友的人。当我发自内心的说自己不善交际而受到质疑的时候可以看出。不过这方面确实有在不断改变，原本比较内向，也是自从上了大学被许多积极友好的朋友们感染，而变得逐渐有点阳光外向。&lt;/p&gt;
&lt;p&gt;而今年也是时常和朋友们出去聚餐，一起逛街一起唱歌一起看电影，过着几年前的自己难以想象的生活。最好玩的时候是自己一整周就一两天是自己独自吃饭的，其余时候都在聚餐。&lt;/p&gt;
&lt;p&gt;当我生日时候也会有朋友们一起去 ktv 玩，吃自助火锅
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/happy.jpg&quot; alt=&quot;alt text&quot;&gt;
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/light.jpg&quot; alt=&quot;alt text&quot;&gt;
我也会去参加别人的生日聚餐，一起聊天，认识新的朋友&lt;/p&gt;
&lt;p&gt;这一年和老朋友逐渐熟悉，和新朋友也能比较自然的聊天。继续认识了很多世界各地的网友，也线下面基很多同伴。总的来说 CAICAII 的社交水平正在不断 UPUP&lt;/p&gt;
&lt;h3&gt;🪄 各种 app 的年度报告&lt;/h3&gt;
&lt;h4&gt;bilibili&lt;/h4&gt;
&lt;p&gt;照样是高强度水 b站，但是我全勤没了是怎么回事
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/bilibili.jpg&quot; alt=&quot;alt text&quot;&gt;
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/bilibili2.jpg&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h4&gt;github&lt;/h4&gt;
&lt;p&gt;并不高强度的码，不过自己也是逐渐上道，也相信以后能越走越远，会有更多有意义的开源贡献
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/github2.jpg&quot; alt=&quot;alt text&quot;&gt;
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/github.jpg&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h4&gt;Steam&lt;/h4&gt;
&lt;p&gt;25 年其实玩游戏并不多，而我希望自己新的一年能多留给自己时间去多玩点游戏
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/steam.jpg&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h4&gt;网易云音乐&lt;/h4&gt;
&lt;p&gt;似乎大家每年最期待的就是音乐播放平台的年度报告的，当然我也是
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/music.jpg&quot; alt=&quot;alt text&quot;&gt;
依旧初音未来
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/music2.jpg&quot; alt=&quot;alt text&quot;&gt;
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/music3.jpg&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h4&gt;飞书&lt;/h4&gt;
&lt;p&gt;主要是社团那边缘故开始用飞书，实际接触后我个人体验也挺好
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/feishu.jpg&quot; alt=&quot;alt text&quot;&gt;
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/feishu2.jpg&quot; alt=&quot;alt text&quot;&gt;
这飞书的 emoji 挺好玩的
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/feishu3.jpg&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h4&gt;知乎&lt;/h4&gt;
&lt;p&gt;今年知乎的使用率并不算高，不过个人感觉这样就好
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/zhihu.jpg&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
&lt;h2&gt;🎊 2026&lt;/h2&gt;
&lt;h3&gt;OKR&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;O1: In excellent health
&lt;ul&gt;
&lt;li&gt;KR1: Early to bed and early to rise, 00:30-8:00&lt;/li&gt;
&lt;li&gt;KR2: Healthy eating, less beverages, more protein and less sugar&lt;/li&gt;
&lt;li&gt;KR3: Stick to exercise, combining aerobic and anaerobic workouts, with at least 4 times per week&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;O2: Knowledge overflows my brain
&lt;ul&gt;
&lt;li&gt;KR1: Read for at least 7 hours per week&lt;/li&gt;
&lt;li&gt;KR2: Take notes on thoughts while reading, and write a book review after finishing the reading&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;O3: Be proficient in Japanese
&lt;ul&gt;
&lt;li&gt;KR1: Can play untranslated Galgame.&lt;/li&gt;
&lt;li&gt;KR2: Can understand what anime characters are saying without subtitles&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;O4: Become Apache committer
&lt;ul&gt;
&lt;li&gt;KR1: Complete more feature-related issues&lt;/li&gt;
&lt;li&gt;KR2: Write more technical solutions and project documents&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;O5: Highly productive like a sow
&lt;ul&gt;
&lt;li&gt;KR1: Blog, at least 1 blog per month&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;结尾&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;2025年过去了，我无比怀念它，而我也相信 2026年将会更加精彩，大家辛苦了&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;乾杯 - ( ゜- ゜)つロ
&lt;img src=&quot;https://raw.githubusercontent.com/CAICAIIs/CAICAIIs.cc/refs/heads/main/src/content/post/2/summary/attachments/2026.jpg&quot; alt=&quot;alt text&quot;&gt;&lt;/p&gt;
</content:encoded><category>总结</category></item><item><title>Hello World!</title><link>https://caicaiis.cc/1/ciallo</link><guid>10-50</guid><description>Ciallo～(∠・ω&lt; )⌒★</description><pubDate>Sat, 27 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Ciallo～(∠・ω&amp;lt; )⌒★
你好呀👋，这里是彩彩&lt;/p&gt;
</content:encoded><category>轻分享</category></item></channel></rss>