<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>流沙</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://lichuanyang.top/</id>
  <link href="https://lichuanyang.top/" rel="alternate"/>
  <link href="https://lichuanyang.top/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, 流沙</rights>
  <subtitle>聚沙成塔，让每一步都有选择</subtitle>
  <title>Mobility</title>
  <updated>2026-07-03T07:41:42.435Z</updated>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="AI实践" scheme="https://lichuanyang.top/categories/AI%E5%AE%9E%E8%B7%B5/"/>
    <category term="ai-agent" scheme="https://lichuanyang.top/tags/ai-agent/"/>
    <category term="skill管理" scheme="https://lichuanyang.top/tags/skill%E7%AE%A1%E7%90%86/"/>
    <category term="pks" scheme="https://lichuanyang.top/tags/pks/"/>
    <category term="工作流" scheme="https://lichuanyang.top/tags/%E5%B7%A5%E4%BD%9C%E6%B5%81/"/>
    <category term="多agent协作" scheme="https://lichuanyang.top/tags/%E5%A4%9Aagent%E5%8D%8F%E4%BD%9C/"/>
    <content>
      <![CDATA[<p>之前写过一篇文章：<a href="https://lichuanyang.top/posts/26060/">https://lichuanyang.top/posts/26060/</a> . 教大家构建与Agent无关的工作流，随时能拉起不同的Agent工作，从而能顺畅用上各个Agent提供的免费、试用套餐。这么实践下来，一个明显会变得繁琐的事情就是对于skill的管理。因此，我又写了一个工具，实现在不同Agent、不同项目间管理skills.</p><span id="more"></span><p>鉴于目前Agent的生态还比较凌乱，大家各干各的，skill的存放目录也自己定自己的。</p><p>比如 Cursor 放在 <code>~/.cursor/skills</code>，Claude Code 放在 <code>~/.claude/skills</code>，Trae 放在 <code>~/.trae/skills</code>，OpenCode 放在 <code>~/.config/opencode/skills</code>，还有 Windsurf、Qoder、Hermes 等等，每家都有自己的路径约定。</p><p>社区也在试图定义通用的.agents&#x2F;skill这样目录，有点作用，但不大。</p><p>当然，skill管理的问题，也不全是使用多Agent带来的。因为skill这种东西，天然就就有非常大的适用范围区别。有的skill是适用于公司的项目的，有的是适用于自己的项目的，有的适用范围还更窄一点，只适用于若干范围内的几个项目，还有的skill, 比如汇总新闻的，我想只配在某个agent里。</p><p>你要是问，不对skill做这些精细化的管理，行不行？那确实也没什么大问题，无非就是agent每次检索技能，多花点token. 但做技术的强迫症，还是希望把这些东西管的细一些，在不需要agent调起的地方，就压根别让agent能看到这些skill.</p><p>基于此，设计了pks这个工具。核心思路是在一个集中的地方管理所有的skill, 然后，按照需求，将skill写入agent的skill目录或者项目目录下。</p><p>具体来说，支持以下功能</p><p><strong>全局管理</strong>：所有 skill 集中存放在 <code>~/.local/share/pks/skills/</code> 下，用 <code>pks list</code> 查看，用 <code>pks new</code> 创建新 skill。</p><p><strong>项目级安装</strong>：在项目目录下执行 <code>pks init</code> 初始化后，可以用 <code>pks install</code> 把全局 skill 安装到当前项目的 <code>.skills/</code> 目录。</p><p><strong>Agent 级安装</strong>：用 <code>pks install-to &lt;agent&gt; &lt;skill&gt;</code> 把 skill 直接装到指定 agent 的 skill 目录（比如 <code>~/.cursor/skills/</code>）。</p><p><strong>双向同步</strong>：在项目里改了 skill 文件，用 <code>pks push</code> 把改动推回全局库。</p><p>实际使用上，我大概有这么些使用场景：</p><p>有一些skill是小范围适用的，比如相关的几个有限的工程，这时候，我不会将skill放到agent的配置里，而是会放到项目中，使用pks install命令，可以把本地技能库中的skill, 放到工程目录下。然后可以在AGENTS.md文件中指引agent去skill目录下找技能，对于支持项目内技能的agent, 也可以使用pks link命令，将agent对应的项目内技能目录，比如.opencode&#x2F;skills, 软链到skills目录下；</p><p>有些skill, 比如收集新闻的skill, 我只想让它在部分agent中出现，这样就执行pks install-to命令, 将其装到agent的skill目录下。</p><p>有时候skill文件需要修改，我会先在某个项目下进行修改，然后执行pks push, 将命令写回技能库。</p><p>这样，对于skill, 就有了一个比较妥善合理的处理流程。</p><p>项目地址在： <a href="https://github.com/lcy362/personal-skills-manager">https://github.com/lcy362/personal-skills-manager</a>  ， 欢迎试用。 大家有什么其他关于skill管理的经验，也欢迎提出。</p>]]>
    </content>
    <id>https://lichuanyang.top/posts/34689/</id>
    <link href="https://lichuanyang.top/posts/34689/"/>
    <published>2026-07-01T09:16:24.000Z</published>
    <summary>
      <![CDATA[<p>之前写过一篇文章：<a href="https://lichuanyang.top/posts/26060/">https://lichuanyang.top/posts/26060/</a> . 教大家构建与Agent无关的工作流，随时能拉起不同的Agent工作，从而能顺畅用上各个Agent提供的免费、试用套餐。这么实践下来，一个明显会变得繁琐的事情就是对于skill的管理。因此，我又写了一个工具，实现在不同Agent、不同项目间管理skills.</p>]]>
    </summary>
    <title>从薅 token 到管 skill：我的 pks 工具落地实践</title>
    <updated>2026-07-03T07:41:42.435Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="技术杂谈" scheme="https://lichuanyang.top/categories/%E6%8A%80%E6%9C%AF%E6%9D%82%E8%B0%88/"/>
    <category term="AI" scheme="https://lichuanyang.top/tags/AI/"/>
    <category term="知乎" scheme="https://lichuanyang.top/tags/%E7%9F%A5%E4%B9%8E/"/>
    <category term="Obsidian" scheme="https://lichuanyang.top/tags/Obsidian/"/>
    <category term="llm-wiki" scheme="https://lichuanyang.top/tags/llm-wiki/"/>
    <category term="知识管理" scheme="https://lichuanyang.top/tags/%E7%9F%A5%E8%AF%86%E7%AE%A1%E7%90%86/"/>
    <category term="微信读书" scheme="https://lichuanyang.top/tags/%E5%BE%AE%E4%BF%A1%E8%AF%BB%E4%B9%A6/"/>
    <category term="Notion" scheme="https://lichuanyang.top/tags/Notion/"/>
    <content>
      <![CDATA[<p>前段时间，偶然看到karpathy大神提出的llm-wiki, 有种相见恨晚的感觉。我一直是很喜欢写些东西的，但是写完之后，问题就是大量内容散落在各个地方，一直没有精力对它们进行有效的管理。而看到了llm-wiki后，我就意识到，之前写的那些东西，要开始发挥作用了。</p><span id="more"></span><h2 id="为什么需要知识中枢"><a href="#为什么需要知识中枢" class="headerlink" title="为什么需要知识中枢"></a>为什么需要知识中枢</h2><p>首先, llm-wiki是什么。</p><p>llm-wiki 是 Andrej Karpathy 在 最近提出的一个概念：把你过往积累的所有文字材料——笔记、博客、读书摘录、工作日志——作为”语料库”，让 LLM 自动从中提取概念、建立页面、编织交叉引用，最终形成一个结构化的、可持续迭代的个人 Wiki。</p><p>它的核心前提很简单：每个人在日常工作和学习中已经产生了大量有结构、有见解的文字，只是它们散落在各处，缺乏关联。llm-wiki 要做的就是用一个 LLM 驱动的流程，把这些散落的珍珠串起来。你负责持续产生和收集内容，LLM 负责组织和管理。</p><p>和传统的手动 Wiki 维护不同——建页面、写摘要、加链接，枯燥且难以坚持——llm-wiki 把组织成本降到了几乎为零。你只需要告诉 LLM 你的知识库结构和维护规则（即一个 AGENTS.md 文件），它就能反复执行摄入、更新、审计等操作。我自己实践下来的感受是，看着 AI 把零散笔记变成结构化的交叉引用网络，有一种”债务清零”的快感。</p><h2 id="数据接入"><a href="#数据接入" class="headerlink" title="数据接入"></a>数据接入</h2><p>我做的第一件事，就是把之前在notion里写的各种笔记，有开发知识、投资知识，还有各种各样零碎的记录，都导出然后放到了obsidian里。</p><p><strong>如何将 Notion 内容导入 Obsidian？</strong></p><p>实际操作并不复杂，核心步骤如下：</p><ol><li><p><strong>导出 Notion 数据</strong>：在 Notion 的”设置与成员 → 设置”中，选择”导出所有工作区内容”，格式选 <strong>Markdown &amp; CSV</strong>。导出后会得到一个 ZIP 包，解压后每个 Notion 页面对应一个 <code>.md</code> 文件，数据库则额外附带 CSV。注意：Notion 免费版每次只能导出一个工作区，如果你有多个工作区，需要分别操作。</p></li><li><p><strong>安装 Obsidian Importer 插件</strong>：在 Obsidian 社区插件市场搜索”Importer”并安装。这个插件支持从 Notion、Bear、Evernote、OneNote 等多种工具一键导入，会自动处理图片附件和内部链接。启用插件后，用 <code>Cmd+P</code> 打开命令面板，搜索”Importer: Open Importer”，选择 Notion 格式，选中刚才解压的文件夹即可。</p></li><li><p><strong>手动导入（备选方案）</strong>：如果不使用 Importer 插件，直接将解压后的文件夹放入 Obsidian vault 目录即可。Obsidian 原生支持 <code>[[wiki-link]]</code> 格式的内部链接，Notion 导出的 Markdown 中的链接通常已经转换为该格式。</p></li><li><p><strong>后续处理</strong>：导入后建议将原始文件放入一个专门的子目录（比如 <code>raw/notion-export/</code>），标记为”不可修改”。这样保留了原始数据的完整性——这是 llm-wiki 方法论中很重要的一环：原始素材永不可改，LLM 在此基础上生成结构化知识。如果你的 Notion 中有数据库，CSV 文件可以作为参考保留；如果某些页面嵌入了 Notion 特有的 Block（如日历、看板），导出后这些会丢失交互性，但文本内容会保留。</p></li></ol><p>整个流程走下来，我几千条分散的笔记就这样汇入了 Obsidian，成为了知识中枢的第一批”原料”。</p><p>然后将karpathy那篇gist喂给AI, 生成出项目的AGENTS.md文档，AI能够自然的写出llm-wiki所需的摄入、审计等操作。</p><p>接着就可以执行了。看着AI不停的生成wiki内容，把我之前的积累分类整理，还是非常舒适的。</p><p>再往后，我又做了几件事，就是把知乎的创作和微信读书的笔记也纳入进来。知乎上我写了上千篇回答，微信读书几年读了上百本书，除了笔记之外，这些也是我知识体系的重要组成部分。正好，差不多那段时间，微信读书发布了官方的skill, 我也就顺手用了起来。</p><h2 id="知乎收藏导入"><a href="#知乎收藏导入" class="headerlink" title="知乎收藏导入"></a>知乎收藏导入</h2><p><strong>如何将知乎创作同步到 Obsidian？</strong></p><p>知乎没有提供官方的数据导出 API，这里我用 Playwright 做了浏览器自动化。</p><p><strong>操作步骤</strong>：</p><ol><li>安装 Playwright：<code>pip install playwright &amp;&amp; playwright install chromium</code></li><li>首次运行脚本，在打开的 Chromium 浏览器中扫码或密码登录知乎</li><li>登录成功后脚本自动遍历个人主页，抓取所有回答、文章和想法</li><li>登录状态持久化保存到本地，后续通过 <code>--reuse</code> 参数静默执行，无需再次登录</li></ol><p><strong>特点</strong>：增量同步，每次只抓取新内容，已有文件不重复处理；按回答&#x2F;文章&#x2F;想法分类存放。</p><h2 id="微信读书笔记同步"><a href="#微信读书笔记同步" class="headerlink" title="微信读书笔记同步"></a>微信读书笔记同步</h2><p><strong>如何将微信读书笔记同步到 Obsidian？</strong></p><p>微信读书开放了 Agent API Gateway，申请 API Key 后即可调用。</p><p><strong>操作步骤</strong>：</p><ol><li>调用 <code>/user/notebooks</code> 接口获取有笔记的书籍列表</li><li>对每本新书，分别拉取划线和想法内容</li><li>按章节分组，输出为规范的 Markdown 文件</li></ol><p><strong>输出格式</strong>：书名和作者作为标题，每章划线以引用块形式列出（附带日期），笔记和想法附在对应原文下方。</p><p><strong>特点</strong>：完全增量同步，脚本维护已同步书籍 ID 的状态文件，每次运行只处理新增的书籍。151 本书的笔记就这样悄无声息地流入了 Obsidian，成为了知识中枢最丰富的一批原料。</p><h2 id="LLM-Wiki-智能检索"><a href="#LLM-Wiki-智能检索" class="headerlink" title="LLM Wiki 智能检索"></a>LLM Wiki 智能检索</h2><p>到此，内容层的准备基本就完成了。然后我又想，既然我大部分的知识和创作都在这儿了，是不是可以开始蒸馏我这个人了？</p><p>这块先搞了一版简单的，就是把拆分出一个和wiki类似的personal流程，也有ingest、lint等流程，区别就是wiki的重点是知识，而personal的重点是我这个人。</p><p><strong>知识库 vs 人格蒸馏：两种不同的 AI 处理逻辑</strong></p><p>这里有必要解释一下两者的区别——它们共享同一套原始素材，但目标和产出完全不同。</p><p><strong>知识库（Wiki）：回答”我知道什么”</strong></p><p>从笔记、博客和读书摘录中提取客观知识，生成概念页面（如”分布式一致性”）、实体页面（如”Raft 算法”）、来源摘要页面（如”《数据密集型应用设计》读书笔记”），并在它们之间建立密集的交叉引用。目标是让知识变得可查询、可复用，像一个外部化的第二大脑。</p><p><strong>人格蒸馏（Personal Model）：回答”我是谁”</strong></p><p>从创作和阅读中反向推导认知模式、表达风格和价值取向。例如，通过分析技术博客，可以归纳出”论点先行、案例驱动”的表达风格；通过分析知乎回答，可以发现”第一性原理还原”和”量化思维”等反复出现的认知特征。产出不是知识条目，而是一个人的认知图谱——擅长什么、怎么思考、看重什么。</p><p><strong>两者的异同</strong></p><table><thead><tr><th>维度</th><th>知识库</th><th>人格蒸馏</th></tr></thead><tbody><tr><td>核心问题</td><td>我知道什么</td><td>我是谁</td></tr><tr><td>输入</td><td>笔记、博客、读书摘录</td><td>一切个人创作和阅读记录</td></tr><tr><td>产出</td><td>概念&#x2F;实体&#x2F;来源页面 + 交叉引用</td><td>领域深度&#x2F;认知特征&#x2F;表达风格&#x2F;价值观</td></tr><tr><td>方向</td><td>向外看：结构化外部知识</td><td>向内看：建模个人认知</td></tr><tr><td>流程</td><td>摄入 → 查询 → Lint → 审计</td><td>摄入 → 查询 → Lint → 审计（同构）</td></tr></tbody></table><p>两者在流程上高度相似，但一个是向外看，结构化和整理你拥有的知识；一个是向内看，蒸馏和建模你作为个体的认知特征。这种”一体两面”的设计，是我觉得整个系统最有意思的地方。</p><p>这块最近还在看网上的女娲等项目，想看看有没有更好的蒸馏人格的方法。</p><h2 id="效果与心得"><a href="#效果与心得" class="headerlink" title="效果与心得"></a>效果与心得</h2><p>以上就是近期关于知识库的一些实录，有想法欢迎交流。</p><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h3 id="Q-Obsidian-适合程序员做知识管理吗？"><a href="#Q-Obsidian-适合程序员做知识管理吗？" class="headerlink" title="Q: Obsidian 适合程序员做知识管理吗？"></a>Q: Obsidian 适合程序员做知识管理吗？</h3><p>非常适合。Obsidian 的核心理念——本地 Markdown 文件、双向链接、图谱可视化——天然契合程序员的使用习惯。Markdown 语法你本来就会，文件存储在本地意味着数据完全可控、可以用 Git 做版本管理，双向链接让你能像管理代码依赖一样管理知识之间的引用关系。加上 llm-wiki 的思路，AI 可以自动帮你从散落的笔记中提取概念、建立页面和交叉引用，把零散文档变成结构化的知识网络。</p><h3 id="Q-LLM-Wiki-需要-GPU-吗？"><a href="#Q-LLM-Wiki-需要-GPU-吗？" class="headerlink" title="Q: LLM Wiki 需要 GPU 吗？"></a>Q: LLM Wiki 需要 GPU 吗？</h3><p>不需要自己部署 GPU。LLM Wiki 的核心理念是<strong>让 LLM 处理你的文本</strong>，而不是让你自己跑模型。你只需要调用大模型的 API（云端推理），用它来读取你的 Markdown 文件、提取概念、生成页面和交叉引用。这个过程走的是云端 API，不需要本地 GPU。实际上整个流程的”硬件”只需要 Obsidian 和一个能调用 LLM API 的工具（如 WorkBuddy 等 Agent）。</p><h3 id="Q-llm-wiki-和传统-Wiki-有什么区别？"><a href="#Q-llm-wiki-和传统-Wiki-有什么区别？" class="headerlink" title="Q: llm-wiki 和传统 Wiki 有什么区别？"></a>Q: llm-wiki 和传统 Wiki 有什么区别？</h3><p>传统 Wiki 需要你手动创建页面、写摘要、添加内部链接，维护成本高且难以坚持。llm-wiki 把组织成本降到了几乎为零——你只需要继续产生和收集文字内容，LLM 自动读取你的 AGENTS.md 规则，反复执行摄入、更新、审计等操作，生成结构化的交叉引用网络。换句话说，传统 Wiki 是”你组织知识”，llm-wiki 是”AI 帮你组织知识”。</p><h3 id="Q-知识蒸馏和人格蒸馏有什么不同？"><a href="#Q-知识蒸馏和人格蒸馏有什么不同？" class="headerlink" title="Q: 知识蒸馏和人格蒸馏有什么不同？"></a>Q: 知识蒸馏和人格蒸馏有什么不同？</h3><p>两者共享同一套原始素材，但目标和产出完全不同。**知识库（Wiki）**回答”我知道什么”——从笔记和读书摘录中提取客观知识，生成概念页面和交叉引用。**人格蒸馏（Personal Model）**回答”我是谁”——从创作和阅读记录中反向推导你的认知模式、表达风格和价值取向。一个向外看（结构化知识），一个向内看（建模个人认知），流程相似但方向相反。</p><h2 id="快速上手步骤"><a href="#快速上手步骤" class="headerlink" title="快速上手步骤"></a>快速上手步骤</h2><ol><li><strong>安装 Obsidian</strong>：从 <a href="https://obsidian.md/">obsidian.md</a> 下载客户端，创建本地 Vault（知识库），即为一个本地文件夹。</li><li><strong>配置 LLM Wiki</strong>：在 Vault 根目录创建 <code>AGENTS.md</code>，参照 karpathy 的 llm-wiki 思路编写知识维护规则，包括摄入（ingest）、更新、审计（audit）等流程定义。让 AI 工具读取该文件，自动从原始笔记中提取概念、建立交叉引用。</li><li><strong>导入 Notion 笔记</strong>：在 Notion 设置中导出为 Markdown + CSV 格式，使用 Obsidian Importer 插件一键导入，或将解压的 Markdown 文件夹直接放入 Vault 目录。</li><li><strong>接入微信读书</strong>：申请微信读书 API Key，调用 <code>/user/notebooks</code> 接口获取书籍列表，拉取划线和笔记，按章节分组输出为 Markdown 文件存入 Vault。</li><li><strong>导入知乎与博客</strong>：使用 Playwright 脚本自动抓取知乎回答和文章；将博客 Markdown 源文件复制到 Vault。完成后让 AI 执行一次全量 wiki 摄入，生成完整的知识交叉引用网络。</li></ol><p>原文地址：<a href="https://lichuanyang.top/posts/18804/">https://lichuanyang.top/posts/18804/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/posts/18804/</id>
    <link href="https://lichuanyang.top/posts/18804/"/>
    <published>2026-06-26T08:18:13.000Z</published>
    <summary>
      <![CDATA[<p>前段时间，偶然看到karpathy大神提出的llm-wiki, 有种相见恨晚的感觉。我一直是很喜欢写些东西的，但是写完之后，问题就是大量内容散落在各个地方，一直没有精力对它们进行有效的管理。而看到了llm-wiki后，我就意识到，之前写的那些东西，要开始发挥作用了。</p>]]>
    </summary>
    <title>把笔记、微信读书、知乎装进 Obsidian：我基于llm-wiki知识中枢搭建实录</title>
    <updated>2026-06-27T03:21:42.149Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="AI实践" scheme="https://lichuanyang.top/categories/AI%E5%AE%9E%E8%B7%B5/"/>
    <category term="AI视频" scheme="https://lichuanyang.top/tags/AI%E8%A7%86%E9%A2%91/"/>
    <category term="开源项目" scheme="https://lichuanyang.top/tags/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE/"/>
    <category term="免费工具" scheme="https://lichuanyang.top/tags/%E5%85%8D%E8%B4%B9%E5%B7%A5%E5%85%B7/"/>
    <category term="text-to-video" scheme="https://lichuanyang.top/tags/text-to-video/"/>
    <category term="Agnes AI" scheme="https://lichuanyang.top/tags/Agnes-AI/"/>
    <content>
      <![CDATA[<blockquote><p>“解决的办法不是压制 AI，而是让它变成一种更平权的能力，让每个人都知道如何借 AI 创造更多。这也是我们公司很重要的愿景，让世界级的 AI 属于每一个人。”</p></blockquote><p>这是 Agnes AI 创始人 Bruce Yang 接受采访时说的一段话。</p><p>现在很多国内的AI厂商，无论deepseek还是智谱，都在把AI的价格往下压。坦率的讲，像文字、代码的处理价格，确实已经被压到了一个相当低的价格。但视频不一样，现在做AI视频，门槛确实高得离谱——国外的 Runway、Pika 按月订阅几十美元，国内的即梦、可灵免费额度用完就按秒计费，想本地跑开源模型？一张能跑视频的显卡轻松上万。</p><p>客观来讲，视频的生成，现阶段确实成本较高，让工业级的视频生成能力属于每一个人，确实不现实。但普通人也应该有一些途径能更多的去尝试、去创作，感谢Agnes开放自己的视频模型，让大家有这个机会。而这个项目只是想为了这个做一些微不足道的贡献。 <a href="https://github.com/lcy362/agnes-video-generator">Agnes Video Generator</a>（<a href="https://video.lichuanyang.top/">官网</a>）。说白了就是一个免费的AI视频生成器，不是那种”免费试用3次”的套路，是从写文案到出片、配音、上字幕，全程不花一分钱。只需要去 <a href="https://platform.agnes-ai.com/">Agnes AI</a> 注册个免费API Key就行。</p><p>Agnes的视频模型，目前确实称不上完美，但我想用这么一个项目，和Agnes一起成长，为AI平权，贡献上一点微不足道的力量。</p><span id="more"></span><h2 id="多种玩法"><a href="#多种玩法" class="headerlink" title="多种玩法"></a>多种玩法</h2><p>给它一句话描述，它还你一条视频。分几种类型：</p><p><strong>简单视频。</strong> 纯粹对API的封装，用来测试效果的，接口的各种参数，基本都做成了配置。</p><p><strong>创意视频。</strong> 你写一个故事创意，比如”暗黑版青蛙王子”，AI全包：扩展故事→生成角色参考图→拆分场景→写分镜提示→逐段生成视频→配音→上字幕→拼接成片。全程10步自动跑完，你只需要等着看成片。通过预生成尾帧，可以最大限度的保障场景间视频的连贯性。</p><p><strong>文稿视频、数字人口播。</strong> 贴一篇长文章进去，自动按语音时长分段，每段生成画面，或者放一个数字人在那里念稿。用一条完整的TTS旁白和字幕串起来。做知识区内容的可以试试。</p><p>各模式的详细参数和玩法，可以到<a href="https://video.lichuanyang.top/">官网</a>上看，这里就不展开了。</p><h2 id="跑起来很简单"><a href="#跑起来很简单" class="headerlink" title="跑起来很简单"></a>跑起来很简单</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/lcy362/agnes-video-generator.git</span><br><span class="line"><span class="built_in">cd</span> agnes-video-generator</span><br><span class="line">./start.sh</span><br></pre></td></tr></table></figure><p>就这三步。<code>start.sh</code> 会自动帮你建虚拟环境、装依赖、启动服务。</p><p>启动后打开 <code>http://localhost:8765</code>，在页面顶部填上你的 Agnes AI API Key，选个模式，写你的创意，点生成，然后耐心等结果就行。</p><p>用 Cursor 或者 Claude 这些AI Agent的话更方便，我专门为Agent做了使用说明，直接让你的Agent读项目里的Agents.md文件，它自己就能把环境搞好、把服务跑起来。</p><h2 id="看看效果"><a href="#看看效果" class="headerlink" title="看看效果"></a>看看效果</h2><p>做了几个demo,可以看看效果：</p><ul><li><a href="https://v.douyin.com/L4F6KdGnD6U/">暗黑版《青蛙王子》无旁白版</a> — 5个场景用关键帧衔接，全自动生成</li><li><a href="https://v.douyin.com/l2FlbF1Jdz0/">同一个故事加了旁白字幕</a> — AI配音 + 自动字幕，可以看看字幕的效果</li><li><a href="https://v.douyin.com/eSGE9KENWVU/">文稿视频</a> — 贴了篇长文进去自动分段，每段配不同画面</li></ul><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>回到开头 Bruce Yang 说的那句话——「让世界级的 AI 属于每一个人」。</p><p>这个项目不是什么宏大的事业，就是想让 AI 视频创作这道门开着。不用订阅、不用好显卡、不用花一分钱，你只需要一个免费的 API Key 和一台能跑 Python 的电脑。</p><p>代码在 <a href="https://github.com/lcy362/agnes-video-generator">GitHub</a>，官网在 <a href="https://video.lichuanyang.top/">video.lichuanyang.top</a>。欢迎提bug。</p><p>原文地址：<a href="https://lichuanyang.top/posts/22470/">https://lichuanyang.top/posts/22470/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/posts/22470/</id>
    <link href="https://lichuanyang.top/posts/22470/"/>
    <published>2026-06-16T05:20:05.000Z</published>
    <summary>受够了AI视频工具按秒计费？我开源了一个100%免费的AI视频生成器，文字、图片、视频、配音全部免费，一键生成带旁白字幕的多场景AI视频。</summary>
    <title>免费AI视频生成器：我如何用零成本做出带旁白字幕的多场景AI视频</title>
    <updated>2026-06-27T03:22:02.934Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="AI实践" scheme="https://lichuanyang.top/categories/AI%E5%AE%9E%E8%B7%B5/"/>
    <category term="agnes" scheme="https://lichuanyang.top/tags/agnes/"/>
    <category term="AI视频" scheme="https://lichuanyang.top/tags/AI%E8%A7%86%E9%A2%91/"/>
    <category term="agnes免费模型" scheme="https://lichuanyang.top/tags/agnes%E5%85%8D%E8%B4%B9%E6%A8%A1%E5%9E%8B/"/>
    <category term="Agnes-Video-V2.0" scheme="https://lichuanyang.top/tags/Agnes-Video-V2-0/"/>
    <category term="开源" scheme="https://lichuanyang.top/tags/%E5%BC%80%E6%BA%90/"/>
    <category term="效率工具" scheme="https://lichuanyang.top/tags/%E6%95%88%E7%8E%87%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<p>前阵子我在薅各种免费AI token，写了一篇关于”哪里免费去哪里薅”的文章。当时提到过，各家平台会不定期放出免费模型。结果没等多久，Agnes AI 就给了我一个惊喜：<strong>视频模型也免费了。</strong></p><p>不是文字，不是图片，是视频。<code>agnes-video-v2.0</code>、<code>agnes-image-2.1-flash</code>、<code>agnes-2.0-flash</code>，三个模型全免费，注册就送API Key，不要信用卡，不要GPU。其中 Agnes-Video-V2.0 是目前免费 agnes video 生成方案里能力最强的一个，支持多种生成模式，质量相当能打。</p><p>这谁忍得住？我直接把开源框架 ViMax 改造成了 Agnes 全家桶方案，顺便把原来一些反人类的用法都优化了一遍。</p><span id="more"></span><h2 id="Agnes免费模型：到底给了什么"><a href="#Agnes免费模型：到底给了什么" class="headerlink" title="Agnes免费模型：到底给了什么"></a>Agnes免费模型：到底给了什么</h2><p>先说 Agnes 这波免费到底给了啥。在 <a href="https://platform.agnes-ai.com/">Agnes AI 平台</a> 注册之后，拿到一个 API Key，就能用三个模型：</p><ul><li><strong>agnes-2.0-flash</strong>（对话模型）：写故事、编剧本、生成prompt，标准的chat接口</li><li><strong>agnes-image-2.1-flash</strong>（图片模型）：text-to-image，生成角色参考图、关键帧</li><li><strong>agnes-video-v2.0</strong>（视频模型）：支持 text-to-video、image-to-video、keyframes 三种模式</li></ul><p>API 走的是 OpenAI 兼容格式，base URL 在 <code>https://apihub.agnes-ai.com/v1</code>，对接起来非常丝滑。视频模型是异步的——提交任务拿task_id，然后轮询结果，跟大多数云端视频生成服务一个套路。</p><p>关键是：<strong>这三个模型串起来，刚好覆盖了”创意 → 故事 → 图片 → 视频”的完整链路。</strong> 不需要混用别家服务，一个 API Key 搞定所有事。</p><h2 id="改造思路：从多供应商到-Agnes-一把梭"><a href="#改造思路：从多供应商到-Agnes-一把梭" class="headerlink" title="改造思路：从多供应商到 Agnes 一把梭"></a>改造思路：从多供应商到 Agnes 一把梭</h2><p>原版 ViMax 是港大开源的 Agentic 视频生成框架，设计得很通用——LLM 用一家的、图片生成用一家的、视频生成又是一家的。好处是灵活，坏处是你要管三套 API、三套认证、三套错误处理。</p><p>我的改造思路很简单：<strong>既然 Agnes 三个模型全免费，那就全部换成 Agnes，一个 API Key、一个 base URL，省事。</strong></p><p>具体改了什么：</p><p><strong>编剧模块</strong>：把原来的 LLM 调用全换成 <code>agnes-2.0-flash</code>。它负责从你的一句话创意出发，生成完整故事、拆分场景、写每场景的视觉prompt，还要给每个场景生成尾帧描述。用的是 OpenAI 兼容的 chat&#x2F;completions 接口，temperature 0.7，够用了。</p><p><strong>图片生成器</strong>：换成 <code>agnes-image-2.1-flash</code>（t2i）和 <code>agnes-image-2.0-flash</code>（i2i）。前者生成角色参考图，后者做场景间的过渡帧图片编辑。</p><p><strong>视频生成器</strong>：换成 <code>agnes-video-v2.0</code>，这是核心。支持三种模式——纯文字生成视频（t2v）、图片引导视频（ti2vid）、关键帧插值（keyframes）。每场景支持5到20秒，帧率24fps。</p><p>改完之后，整个项目只依赖一个 API 服务商。<code>.api_key</code> 文件里写一个 key，完事。</p><h2 id="易用性优化：别让用户想太多"><a href="#易用性优化：别让用户想太多" class="headerlink" title="易用性优化：别让用户想太多"></a>易用性优化：别让用户想太多</h2><p>原版 ViMax 的用法比较”研究项目风”——参数硬编码在代码里，改个创意得改源码。这不太行，所以我在易用性上做了一堆改进。</p><h3 id="YAML-创意配置"><a href="#YAML-创意配置" class="headerlink" title="YAML 创意配置"></a>YAML 创意配置</h3><p>最大的改动是引入了 YAML 创意文件。在 <code>creatives/</code> 目录下，一个创意就是一个 <code>.yaml</code> 文件：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">&quot;frog&quot;</span></span><br><span class="line"><span class="attr">idea:</span> <span class="string">|</span></span><br><span class="line"><span class="string">  暗黑童话版青蛙王子，公主亲了青蛙之后，</span></span><br><span class="line"><span class="string">  青蛙变成了一个更可怕的怪物</span></span><br><span class="line"><span class="string"></span><span class="attr">user_requirement:</span> <span class="string">|</span></span><br><span class="line"><span class="string">  5个场景，每个10秒，哥特暗黑风</span></span><br><span class="line"><span class="string"></span><span class="attr">style:</span> <span class="string">&quot;哥特暗黑童话风格&quot;</span></span><br><span class="line"><span class="attr">chaining_mode:</span> <span class="string">keyframes</span></span><br><span class="line"><span class="attr">video_width:</span> <span class="number">768</span></span><br><span class="line"><span class="attr">video_height:</span> <span class="number">1152</span></span><br></pre></td></tr></table></figure><p>想拍新视频？写个 YAML，跑一条命令，完事。不用再碰 Python 源码了。</p><h3 id="一键启动脚本"><a href="#一键启动脚本" class="headerlink" title="一键启动脚本"></a>一键启动脚本</h3><p><code>start.sh</code> 做了一键封装：自动加载 <code>.api_key</code>、自动激活虚拟环境、自动列出可用创意、自动跑 pipeline。不传参数就列出所有创意，传个名字就直接跑：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">./start.sh          <span class="comment"># 列出所有创意</span></span><br><span class="line">./start.sh frog     <span class="comment"># 跑&quot;青蛙王子&quot;</span></span><br></pre></td></tr></table></figure><h3 id="智能缓存与断点续跑"><a href="#智能缓存与断点续跑" class="headerlink" title="智能缓存与断点续跑"></a>智能缓存与断点续跑</h3><p>这个必须说，因为视频生成是真的慢。</p><p>每个中间结果——故事文本、场景脚本、角色参考图、每场景视频——全部持久化到磁盘。跑到一半断了？重新跑同一个创意，已完成的步骤直接跳过，只生成缺失的部分。</p><p>最细粒度的是视频缓存：5个场景跑了3个断了，重跑只生成剩下2个。不会从头来。</p><h3 id="多模态图片分析"><a href="#多模态图片分析" class="headerlink" title="多模态图片分析"></a>多模态图片分析</h3><p>你可以自己提供角色参考图，也可以给每个场景自定义尾帧图片。系统会通过多模态 LLM 分析这些图片的内容，把视觉信息融入故事和 prompt 生成。</p><p>比如你提供一张自己画的卡通人物，系统会自动识别它的外貌特征，在后续所有场景里保持一致。</p><h2 id="角色一致性：两阶段锁定"><a href="#角色一致性：两阶段锁定" class="headerlink" title="角色一致性：两阶段锁定"></a>角色一致性：两阶段锁定</h2><p>AI视频最大的坑就是角色一致性——第一个场景是黑发妹子，第二个场景突然变成金发了。</p><p>ViMax-Agnes 用了一个两阶段方案：</p><p><strong>第一阶段</strong>：先生成（或你提供）一张角色参考图。编剧模块会从故事里提取角色的详细外貌描述——体型、发型、服装、配色、标志性特征——然后让图片模型生成一张全身参考图。</p><p><strong>第二阶段</strong>：每个场景视频都以这张参考图为起始帧，通过 <code>ti2vid</code> 模式生成。视频模型从同一个视觉锚点出发，自然保持了角色的一致性。</p><p>实测下来，卡通&#x2F;风格化画风的一致性效果最好。写实风格的话，建议直接提供自己的参考图，比 AI 生成的更可控。</p><h2 id="三种场景串联模式"><a href="#三种场景串联模式" class="headerlink" title="三种场景串联模式"></a>三种场景串联模式</h2><p>这是我觉得改造最有意思的部分。原版 ViMax 有场景串联的概念，但我在 Agnes API 上做了三种模式的适配：</p><p><strong><code>none</code>（独立模式）</strong>：每个场景独立生成，共用同一张角色参考图。速度最快，但场景之间没有过渡，硬切。</p><p><strong><code>ti2vid</code>（过渡帧模式）</strong>：顺序生成，每个场景结束后提取尾帧，用 img2img 生成一张”过渡帧”，作为下个场景的起始帧。过渡比较自然，但误差会累积——前面场景的瑕疵会传到后面。</p><p><strong><code>keyframes</code>（关键帧模式）</strong>：这是推荐方案。每个场景同时指定首帧和尾帧，让视频模型在两个关键帧之间做插值。尾帧由 AI 根据场景描述自动生成（你也可以手动指定）。这样每个场景的起点和终点都是确定的，过渡最平滑。</p><p>三种模式在 YAML 里一个字段切换，不用改代码。</p><h2 id="实战效果"><a href="#实战效果" class="headerlink" title="实战效果"></a>实战效果</h2><p>跑几个创意试了一下：</p><ul><li><strong>青蛙王子</strong>：5场景暗黑童话，keyframes模式，全自动生成</li><li><strong>女孩扣篮</strong>：3场景运动主题，角色一致性保持得不错</li><li><strong>海边舞蹈</strong>：4场景MV风格，场景过渡比较丝滑</li><li><strong>温泉机器人</strong>：3场景治愈风，卡通风格一致性最好</li></ul><p>每个创意从提交到出片，主要瓶颈在视频生成——每场景大概需要几分钟等待。但因为有缓存，调试成本其实不高。</p><h2 id="一些-Agnes-Video-V2-0-技术细节"><a href="#一些-Agnes-Video-V2-0-技术细节" class="headerlink" title="一些 Agnes-Video-V2.0 技术细节"></a>一些 Agnes-Video-V2.0 技术细节</h2><p>给想深入玩的朋友补充几个实现细节：</p><p><strong>视频参数</strong>：帧数遵循 <code>8n+1</code> 规则，上限441帧。5秒121帧、10秒241帧、15秒361帧、18秒和20秒都是441帧（帧率分别是24和22fps）。</p><p><strong>图片上传</strong>：视频API需要图片URL而不是base64，所以本地图片会通过图片生成API”上传”——用 <code>agnes-image-2.1-flash</code> 做一次 prompt 为”保持图片不变”的 i2i 转换，拿到一个托管URL。如果上传失败，回退到 inline base64。</p><p><strong>重试机制</strong>：视频提交遇到429（限流）或5xx（服务端错误），会自动指数退避重试，最多5次。轮询没有超时——视频生成就是这么慢，你得等。</p><p><strong>最小依赖</strong>：整个项目只依赖5个Python包：requests、pydantic、PyYAML、moviepy、tenacity。不需要PyTorch，不需要CUDA，纯API编排层。</p><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>Agnes 这波免费模型的诚意还是挺足的。agnes免费模型覆盖了文字、图片、视频全链路，API格式兼容OpenAI，注册门槛极低。对于想玩AI视频生成但不想烧钱的朋友来说，是个不错的入门选择。</p><p>ViMax-Agnes 的改造也让我验证了一件事：<strong>当免费模型的质量够用的时候，”哪里免费去哪里薅”的策略完全可以从文本扩展到视频。</strong> 一个 API Key、一条命令、一个 YAML 文件，就能从一句话生成一个完整的多场景视频。</p><p>项目开源在这里：<a href="https://github.com/lcy362/vimax-agnes">github.com&#x2F;lcy362&#x2F;vimax-agnes</a>，欢迎 star 和提 issue。</p><blockquote><p><strong>2026年6月更新</strong>：本文工具已迭代为 <a href="https://github.com/lcy362/agnes-video-generator">Agnes Video Generator</a>，支持 Web UI 和多语言，功能更完善，推荐使用新版本。</p></blockquote><p>原文地址：<a href="https://lichuanyang.top/posts/65500/">https://lichuanyang.top/posts/65500/</a></p><h2 id="快速上手步骤"><a href="#快速上手步骤" class="headerlink" title="快速上手步骤"></a>快速上手步骤</h2><ol><li><strong>环境准备</strong>：注册 <a href="https://platform.agnes-ai.com/">Agnes AI 平台</a> 账号，获取 API Key。确保本地已安装 Python 3.8+ 和 Git。</li><li><strong>克隆项目</strong>：<code>git clone https://github.com/lcy362/vimax-agnes &amp;&amp; cd vimax-agnes</code>，将 API Key 写入 <code>.api_key</code> 文件。</li><li><strong>创建创意配置</strong>：在 <code>creatives/</code> 目录下新建 YAML 文件，定义 <code>name</code>、<code>idea</code>、<code>style</code>、<code>chaining_mode</code> 等参数。</li><li><strong>一键生成视频</strong>：运行 <code>./start.sh &lt;创意名称&gt;</code>，系统自动执行编剧生成、图片生成、视频生成全流程，中间结果自动缓存，支持断点续跑。</li><li><strong>查看效果</strong>：视频输出在 <code>output/</code> 目录下，检查角色一致性、场景过渡流畅度和整体视频质量。如需调整，修改 YAML 配置后重新运行即可。</li></ol>]]>
    </content>
    <id>https://lichuanyang.top/posts/65500/</id>
    <link href="https://lichuanyang.top/posts/65500/"/>
    <published>2026-06-11T14:00:00.000Z</published>
    <summary>Agnes AI 放出了免费视频模型 Agnes-Video-V2.0，无需GPU、无需信用卡。我把开源视频框架 ViMax 改造成了 Agnes 全家桶方案，顺便做了一堆易用性优化。</summary>
    <title>Agnes免费模型真能白嫖视频？我改造了ViMax来试试</title>
    <updated>2026-06-27T03:42:25.693Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="AI实践" scheme="https://lichuanyang.top/categories/AI%E5%AE%9E%E8%B7%B5/"/>
    <category term="效率工具" scheme="https://lichuanyang.top/tags/%E6%95%88%E7%8E%87%E5%B7%A5%E5%85%B7/"/>
    <category term="AI Agent" scheme="https://lichuanyang.top/tags/AI-Agent/"/>
    <category term="LLM" scheme="https://lichuanyang.top/tags/LLM/"/>
    <category term="工作流" scheme="https://lichuanyang.top/tags/%E5%B7%A5%E4%BD%9C%E6%B5%81/"/>
    <category term="CLI" scheme="https://lichuanyang.top/tags/CLI/"/>
    <content>
      <![CDATA[<p>上篇文章最后说了句：<strong>Agent只是工具，文档才是核心。</strong></p><p>这话说了之后，有人问我：道理都懂，但文档具体怎么管？总不能每个项目都复制粘贴一遍吧？</p><p>确实不能。</p><p>我后来写了个工具来解决这事——<a href="https://github.com/lcy362/personal-skills-manager">pks</a>（Personal Skills Manager），408行纯bash，零依赖。它干的事很简单：把你的AI工作流文档打包成skill，按需装到项目里，不绑定任何Agent平台。</p><span id="more"></span><h2 id="什么是skill"><a href="#什么是skill" class="headerlink" title="什么是skill"></a>什么是skill</h2><p>如果你用过OpenCode、Cursor或者其他Agent平台，大概率见过”skill”这个概念。不同平台叫法不一样——有的叫skill，有的叫rules，有的叫custom instructions——但说的都是同一件事：<strong>你给Agent的结构化指令，告诉它怎么处理特定类型的任务。</strong></p><p>比如”调用我们的API网关时，参数必须平铺在body顶层，不要嵌套在params里”，这是一条skill。”commit消息必须用中文，格式是type(scope):描述”，这也是。”数据库表名用snake_case，字段必须有注释”，还是。</p><p>Agent的底层模型提供了通用推理能力——它知道怎么写代码、怎么调API。但你的API网关有什么坑、你们团队的命名习惯是什么、你们项目为什么选了这个架构——这些知识模型不知道，得你告诉它。skill就是你告诉Agent这些东西的方式。</p><p>各平台管理skill的方式各不相同。OpenCode用<code>.opencode/skills/</code>目录，Cursor用<code>.cursorrules</code>，Claude Code读<code>CLAUDE.md</code>。格式不同，机制不同，但本质没区别：都是一份文档，Agent读到就按里面的规则干活。</p><p>skill本身不依赖任何平台。一个用Markdown写好的skill文件夹，丢进任何项目里，任何Agent找到了都会读、都会用。<strong>纯Markdown，谁来都能读。</strong></p><h2 id="问题出在哪"><a href="#问题出在哪" class="headerlink" title="问题出在哪"></a>问题出在哪</h2><p>skill是个好东西，但问题来了：你手上有好几个项目。</p><p>一份团队编码规范，项目A要用，项目B要用，项目C也要用。你怎么管？</p><p>常见的做法是，把这些配进agent本身的配置里，也就是常规的“安装skill”操作，比如给opencode装个微信读书skill, 这样，所有的项目用opencode打开，就都能用上。</p><p>这本来是没啥问题的，除非你看了我上一篇文章.. </p><p>在我们想打造agent无关的工作流的情况下，这么干问题就大了。哪天你看到有个新agent在送免费token, 下来一用，发现一堆skill都得自己迁移，这不就傻了吗。</p><p>所以我开发pks的初衷，就是把这一层抽出来，解套。让对这些不通用skill的管理，独立于所有的agent和项目，单独成一个体系。</p><h2 id="pks怎么解决"><a href="#pks怎么解决" class="headerlink" title="pks怎么解决"></a>pks怎么解决</h2><p>pks最适合管理那些<strong>非通用的、项目或团队特有的</strong>指令：你的编码风格偏好和commit规范、团队的代码规范和CI&#x2F;CD流程、项目的架构决策记录和API设计约束等。</p><p>pks要解决的就是一件事：<strong>在一个地方管理所有skill，按需装到任何项目里。</strong></p><p>然后，所有skill集中存在一个全局仓库里。项目需要哪些，装哪些。</p><p>全局操作就三个核心命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pks new my-skill       <span class="comment"># 基于模板创建skill骨架</span></span><br><span class="line">pks list               <span class="comment"># 看看你有哪些skill</span></span><br><span class="line">pks delete my-skill    <span class="comment"># 删掉不要的</span></span><br></pre></td></tr></table></figure><p>项目操作也很简单：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> your-project</span><br><span class="line">pks init                        <span class="comment"># 初始化（只执行一次）</span></span><br><span class="line">pks install my-skill            <span class="comment"># 装一个skill进来</span></span><br><span class="line">pks status                      <span class="comment"># 看看装了哪些</span></span><br><span class="line">pks uninstall my-skill          <span class="comment"># 不想要了就卸掉</span></span><br></pre></td></tr></table></figure><p>装好之后，skill会被复制到项目的<code>.skills/</code>目录：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">your-project/</span><br><span class="line">├── .skills/</span><br><span class="line">│   ├── INDEX.md              # 自动生成的索引</span><br><span class="line">│   └── my-skill/</span><br><span class="line">│       └── SKILL.md           # 你的skill内容</span><br><span class="line">└── ...</span><br></pre></td></tr></table></figure><h2 id="Agent怎么发现这些skill"><a href="#Agent怎么发现这些skill" class="headerlink" title="Agent怎么发现这些skill"></a>Agent怎么发现这些skill</h2><p>这其实是个很自然的流程。</p><p>每次install或uninstall，pks会自动重建<code>.skills/INDEX.md</code>。这个索引文件列出所有已安装的skill，附带描述和版本，并且包含一句话：</p><blockquote><p>Agents: read the <code>SKILL.md</code> file in each skill directory below for relevant instructions.</p></blockquote><p>任何Agent进到项目都会看到这个目录，读INDEX.md，然后按需加载具体的SKILL.md。</p><p>最多你就在项目的AGENTS.md里再加一句去skill目录下找技能。按我的经验，对大部分agent来说，都没这个必要，它们都很自然的就找到这些技能了。</p><h2 id="什么时候会用到"><a href="#什么时候会用到" class="headerlink" title="什么时候会用到"></a>什么时候会用到</h2><p>说个最常见的场景：你负责一个项目，新人要加入开发。</p><p>传统做法是什么？甩一份README，口头交代几句”我们commit要这么写”、”那个内部库要那么调”、”数据库字段别随便改”。剩下的全靠悟。</p><p>用Agent辅助也差不多——你把规则塞进CLAUDE.md或AGENTS.md，Agent每次对话都吃一遍，不管这次任务跟这些规则有没有关系。</p><p>换个思路。把这些知识拆成几个skill：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">skills/</span><br><span class="line">├── team-coding-style/     # 团队编码规范、命名约定、commit格式</span><br><span class="line">│   └── SKILL.md</span><br><span class="line">├── internal-api-gateway/  # 内部API网关：参数平铺、鉴权方式、踩坑记录</span><br><span class="line">│   ├── SKILL.md</span><br><span class="line">│   ├── pitfalls.md        # 常见坑点（比如参数不能嵌套在params里）</span><br><span class="line">│   └── fields.md          # 回包字段含义速查</span><br><span class="line">├── project-architecture/  # 架构决策：模块划分、目录结构、为什么这么设计</span><br><span class="line">│   └── SKILL.md</span><br><span class="line">└── db-conventions/        # 数据库规范：命名、索引策略、迁移流程</span><br><span class="line">    └── SKILL.md</span><br></pre></td></tr></table></figure><p>新人入职？<code>pks init</code>，<code>pks install team-coding-style</code>，<code>pks install project-architecture</code>，几分钟装好。Agent读到这些skill，立刻知道这个项目的规矩。换一个人、换一台机器、换一个Agent平台，同样的skill装上去，效果一样。</p><p>而且<strong>只有装了才消耗token</strong>。一个纯前端项目不需要装数据库规范的skill，一个老项目不需要装新人引导的skill。相比把所有规则塞进一个巨大的配置文件让Agent每次对话都吃一遍，按需安装能省下不少无用token——这本身就是在薅自己的token。</p><p>这种<strong>非通用类</strong>的指令才是pks的主场。通用能力Agent自己就有，但你的团队规范、你的项目约定、你踩过的坑——这些东西只对你有用，也只该在需要的时候才出现。</p><h2 id="为什么是纯bash"><a href="#为什么是纯bash" class="headerlink" title="为什么是纯bash"></a>为什么是纯bash</h2><p>pks整个CLI是408行bash脚本。不用Python，不用Node.js，不用任何运行时。git clone下来就能跑。</p><p>为什么这么极端？因为skill管理工具本身不应该引入任何负担。你的工作流已经够复杂了，管理它的工具应该尽可能简单——简单到不会因为某个运行时版本升级而挂掉。</p><p>bash在macOS和Linux上开箱即用，十年前的脚本今天还能跑。这就是零依赖的好处：<strong>你的工作流文档比任何框架都长寿。</strong></p><h2 id="设计上的几个小心思"><a href="#设计上的几个小心思" class="headerlink" title="设计上的几个小心思"></a>设计上的几个小心思</h2><p>pks有几个设计细节值得提一嘴。</p><p><strong>语义化版本</strong>：每个skill的YAML front matter里有version字段。skill迭代了，版本号跟着涨，项目里装了哪个版本一目了然。</p><p><strong>全局管理，按需安装</strong>：所有skill集中在一个仓库里维护，项目里只装需要的。</p><p><strong>模板保护</strong>：以<code>_</code>开头的skill（比如<code>_template</code>）不会出现在列表里，也不能被删除。<code>pks new</code>创建新skill时自动基于模板生成骨架，不用从零写起。</p><p><strong>路径无关</strong>：pks通过符号链接解析和相对路径定位，在任何位置调用都能正确找到skills目录。不管你从哪个路径执行命令，它都知道自己的家在哪。</p><h2 id="回到那句话"><a href="#回到那句话" class="headerlink" title="回到那句话"></a>回到那句话</h2><p>上篇说”Agent只是工具，文档才是核心”。这篇把它往前推了一步：<strong>文档化的工作流，是可以被管理的。</strong></p><p>pks不是什么高深的东西，408行bash而已。但它代表了一个态度：你的工作流是你的资产，不是任何平台的附属品。</p><p>该薅token薅token，该管skill管skill。钱要省，活要干好，这两件事不矛盾。</p><p>原文地址：<a href="https://lichuanyang.top/posts/26061/">https://lichuanyang.top/posts/26061/</a></p><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h3 id="Q-Skills-管理和-Agent-管理有什么区别？"><a href="#Q-Skills-管理和-Agent-管理有什么区别？" class="headerlink" title="Q: Skills 管理和 Agent 管理有什么区别？"></a>Q: Skills 管理和 Agent 管理有什么区别？</h3><p>Skills 管理是<strong>独立于 Agent 平台的</strong>，你把所有 skill 统一放在一个全局仓库里，项目需要什么就装什么。Agent 管理则是把 skill 配进某个 Agent 的内部配置中（如 OpenCode 的 <code>.opencode/skills/</code> 或 Cursor 的 <code>.cursorrules</code>），绑定到具体平台。前者的好处是换平台时不需要迁移任何 skill，后者虽然集成度高，但一旦切 Agent 就需要重新配置。</p><h3 id="Q-跨平台兼容性怎么保证？"><a href="#Q-跨平台兼容性怎么保证？" class="headerlink" title="Q: 跨平台兼容性怎么保证？"></a>Q: 跨平台兼容性怎么保证？</h3><p>关键是 <strong>纯 Markdown</strong>。pks 安装 skill 后生成的都是 <code>.md</code> 文件，放在项目的 <code>.skills/</code> 目录下，附带一个自动维护的 <code>INDEX.md</code> 索引文件。任何 Agent 扫描项目目录时都会发现这个结构，读 INDEX.md 就知道有哪些 skill，再按需加载具体的 SKILL.md。不需要特定格式，不需要平台 SDK，纯 Markdown 是最大的公约数。</p><h3 id="Q-pks-和直接在-Agent-里配置-skill-有什么不同？"><a href="#Q-pks-和直接在-Agent-里配置-skill-有什么不同？" class="headerlink" title="Q: pks 和直接在 Agent 里配置 skill 有什么不同？"></a>Q: pks 和直接在 Agent 里配置 skill 有什么不同？</h3><p>直接配在 Agent 里方便，但 skill 会”绑定”到那个 Agent。pks 的做法是<strong>从 Agent 中解耦 skill 管理</strong>——skill 存在全局仓库，按需安装到任何项目中。最大的实际好处：按需安装意味着<strong>只有装了的 skill 才消耗 token</strong>。一个纯前端项目不需要装数据库规范的 skill，一个老项目不需要装新人引导的 skill。相比把所有规则塞进一个大配置文件让 Agent 每次对话都吃一遍，pks 能省下大量无用 token。</p><h3 id="Q-为什么用-bash-而不是-Python-或-Node-js？"><a href="#Q-为什么用-bash-而不是-Python-或-Node-js？" class="headerlink" title="Q: 为什么用 bash 而不是 Python 或 Node.js？"></a>Q: 为什么用 bash 而不是 Python 或 Node.js？</h3><h2 id="零依赖。skill-管理工具本身不应该引入任何运行时负担——你的工作流已经够复杂了。408-行-bash-脚本在-macOS-和-Linux-上开箱即用，不需要安装解释器、不需要管理虚拟环境、不会因为某个运行时版本升级而挂掉。bash-十年前的脚本今天还能跑，这保证了你的工作流文档比任何框架都长寿。"><a href="#零依赖。skill-管理工具本身不应该引入任何运行时负担——你的工作流已经够复杂了。408-行-bash-脚本在-macOS-和-Linux-上开箱即用，不需要安装解释器、不需要管理虚拟环境、不会因为某个运行时版本升级而挂掉。bash-十年前的脚本今天还能跑，这保证了你的工作流文档比任何框架都长寿。" class="headerlink" title="零依赖。skill 管理工具本身不应该引入任何运行时负担——你的工作流已经够复杂了。408 行 bash 脚本在 macOS 和 Linux 上开箱即用，不需要安装解释器、不需要管理虚拟环境、不会因为某个运行时版本升级而挂掉。bash 十年前的脚本今天还能跑，这保证了你的工作流文档比任何框架都长寿。"></a>零依赖。skill 管理工具本身不应该引入任何运行时负担——你的工作流已经够复杂了。408 行 bash 脚本在 macOS 和 Linux 上开箱即用，不需要安装解释器、不需要管理虚拟环境、不会因为某个运行时版本升级而挂掉。bash 十年前的脚本今天还能跑，这保证了你的工作流文档比任何框架都长寿。</h2><p>原文地址：<a href="https://lichuanyang.top/posts/26061/">https://lichuanyang.top/posts/26061/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/posts/26061/</id>
    <link href="https://lichuanyang.top/posts/26061/"/>
    <published>2026-06-04T14:00:00.000Z</published>
    <summary>上篇说&quot;文档才是核心&quot;，那文档怎么管？我用400行bash写了个skill管理器，把AI工作流打包成可移植的技能包，按需装到项目里，不绑定任何Agent平台。</summary>
    <title>教你薅token（二）：构建agent无关的skills管理工作流</title>
    <updated>2026-06-27T03:21:42.177Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="AI实践" scheme="https://lichuanyang.top/categories/AI%E5%AE%9E%E8%B7%B5/"/>
    <category term="效率工具" scheme="https://lichuanyang.top/tags/%E6%95%88%E7%8E%87%E5%B7%A5%E5%85%B7/"/>
    <category term="AI Agent" scheme="https://lichuanyang.top/tags/AI-Agent/"/>
    <category term="LLM" scheme="https://lichuanyang.top/tags/LLM/"/>
    <category term="工作流" scheme="https://lichuanyang.top/tags/%E5%B7%A5%E4%BD%9C%E6%B5%81/"/>
    <category term="prompt" scheme="https://lichuanyang.top/tags/prompt/"/>
    <content>
      <![CDATA[<p>目前大家使用AI的最大或者唯一痛点，是什么？可能就是账单了。</p><p>无论是Claude、CodeX、Cursor，还是qoder或是mimo、minimax，基础套餐基本都要奔着20刀以上去，这还得省着用，很多时候用以来会很不爽。要想量大管饱，那就会更贵。</p><p>而同时，很多平台会有试用装，或是常规的免费模型。比如OpenCode里的deepseek flash长期免费。</p><p>我们怎么才能好好的薅一下这些免费模型呢？</p><p>我长期在各种不同的项目用Agent，有代码，有个人博客，有笔记知识库，有小说，还有我和AI完的小游戏。用着用着发现一个事：<strong>Agent的所有操作，本质上是：读你文档 → 拼prompt → 发给模型 → 按结果改文件。</strong></p><p>文档在哪里，智能就在哪里。你自己拼prompt，跟平台帮你拼，其实没差那么多。</p><p>所以我后来干脆不再纠结用哪个Agent平台了。文档维护好，哪里有免费token就去哪薅。</p><span id="more"></span><h2 id="Agent到底干了什么"><a href="#Agent到底干了什么" class="headerlink" title="Agent到底干了什么"></a>Agent到底干了什么</h2><p>不管哪家Agent，流程都差不多：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">用户说需求 → Agent看项目文档 → 读相关文件 → 拼prompt → 问模型 → 处理返回 → 改文件 → 循环上述流程</span><br></pre></td></tr></table></figure><p>比如让它帮你修个bug，它：</p><ol><li>先读你项目里的 <code>AGENTS.md</code>、<code>README.md</code>，知道这项目是啥、目录在哪</li><li>再找到bug涉及的几个文件，读源码</li><li>把”项目上下文 + 相关代码 + bug描述 + 修改要求”拼成一个prompt</li><li>发给大模型</li><li>把返回的结果写成代码修改</li></ol><p>这些步骤，agent硬有差别，能有多大差别？</p><p>别误会，这里不是要全盘否定一些公认的好Agent。好的Agent在上下文管理、流程控制、工具集成上确实做得顺手，不然也不会流行。但，“人的智能”，完全可以补上这些差距。</p><p>比如上下文管理，Agent能定位到”这个bug涉及用户认证模块，需要读这三个文件”——可前提是它已经看过了你的AGENTS.md。那些文件路径、模块职责、约定俗成的写法，本来就是你自己写进去的。</p><p>文档不好的时候，好的agent能有效率更高的方式找到需要的信息。但在文档完备的情况下，agent的差距就微乎其微了。</p><p>总之，好的Agent在某些场景下，确实是方便，但是，不值这个价格。省下的那点维护文档、切窗口的时间，换每个月几十上百刀订阅费，性价比太低了。</p><p>对我个人来说，维护一份完备的agent指南，性价比高多了。何况，这些指南文档，也完全可以让AI自己写。</p><h2 id="文档才是关键"><a href="#文档才是关键" class="headerlink" title="文档才是关键"></a>文档才是关键</h2><p>想明白上面那段之后，你还得接受一个反直觉的事实：<strong>真正值钱的，不是Agent平台，甚至不是模型，而是你自己的工作流。</strong></p><p>大模型谁都能调，Agent平台也大多只是编排层，功能都差不多。但你的项目文档和你对项目流程的管理，是唯一的，里面记着项目结构、设计决策、踩过的坑、你偏好的写法。</p><p>同样的文档换个Agent平台，效果基本没差；可文档写得垃圾的话，放哪个平台都救不回来。</p><p>我在代码项目里是这么维护的：</p><ul><li>项目概述文档：技术栈、目录结构、关键模块说明</li><li>工作流文档：”新增功能怎么走”、”修bug步骤”、”发布流程”</li><li>规则文档：命名规范、注释要求、踩坑记录</li></ul><p>同一个项目目录，用不同的agent打开，效果几乎没区别。</p><p>小说创作项目同理。世界观、角色档案、章节大纲、写作规范，这些文档不变，换不同模型产出的风格都能保持一致。<strong>文档的样子定了，内容质量就定了。</strong></p><p>包括模型这一层，差别也没那么大。deepseek-v4-flash, 足够处理绝大多数问题。</p><p>就算你真的要花钱买时间，该投资的也是文档本身，不是挑平台。</p><h2 id="薅token实战"><a href="#薅token实战" class="headerlink" title="薅token实战"></a>薅token实战</h2><p>工作流搬到文档之后，最大的好处就是：平台随便切。</p><p>哪家有免费token用哪家，用完换下一家。</p><p>比如这些：</p><ul><li>OpenCode：每天有免费DeepSeek Flash</li><li>Qoder：新用户送一些试用credits，还有免费模型额度</li><li>Codex：试用套装，我其实到现在都还没轮到去薅他，总想着遇到一些复杂场景再去用它的免费额度，免得浪费了。但实际上根本遇不到这种场景。</li><li>Hermes：不定期放出免费模型，有时候能意外捡到好模型，比如前阵子有免费的deepseek flash</li><li>trae: 完全免费，只是要排队，有时候真有些任务觉得免费模型搞不动了，临时去trae跑一下，也完全没问题</li></ul><p>除此之外还有不少路子：</p><ul><li>大模型API的新用户赠金：OpenAI、Anthropic、DeepSeek等，新号通常送几美元。直接调API比走Agent平台更便宜</li><li>开源模型的免费托管：HuggingFace、Together AI有免费推理额度，小任务完全够用</li><li>学生优惠：GitHub Student Pack之类的，送一堆平台credits</li><li>一些社区活动：经常有AI平台送额度</li></ul><p>所以这篇要说的结论其实挺简单：<strong>Agent只是工具，文档才是核心。</strong> 把文档维护好，平台随便换，哪里免费去哪薅。一个月省几十刀，换一顿火锅，不香吗？ </p><p>至于那些每个月烧几百刀token的重度用户——我只能说，你们的钱花得值，但我的token没花钱，效果也差不多。</p><p>原文地址：<a href="https://lichuanyang.top/posts/26060/">https://lichuanyang.top/posts/26060/</a></p><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h3 id="Q-Agent-和-Skill-是什么关系？"><a href="#Q-Agent-和-Skill-是什么关系？" class="headerlink" title="Q: Agent 和 Skill 是什么关系？"></a>Q: Agent 和 Skill 是什么关系？</h3><p>Agent 是”干活的人”，Skill 是”教它怎么干活的说明书”。Agent 的底层模型提供了通用推理能力，但它不知道你的项目结构、编码规范、API 设计约束——这些知识需要通过 Skill（结构化指令文档）告诉它。两者的关系可以这样理解：Agent &#x3D; 通用推理引擎 + 按需加载 Skill。把 Skill 维护好，换不同的 Agent 平台也能保持一致的输出质量。</p><h3 id="Q-Token-优化最有效的方法是什么？"><a href="#Q-Token-优化最有效的方法是什么？" class="headerlink" title="Q: Token 优化最有效的方法是什么？"></a>Q: Token 优化最有效的方法是什么？</h3><p>不是换更便宜的模型，而是<strong>写好文档</strong>。一份完备的 AGENTS.md 或 SKILL.md，能让 Agent 精准定位到需要读取的文件，避免每次做任务都全量扫描项目。相比之下，在不完备的文档上折腾模型选择或 prompt 精简，省下的 token 远远小于文档带来的提升。先花时间把文档写清楚，再去薅免费 token，这是效益最高的路径。</p><h3 id="Q-文档维护和-Agent-订阅哪个更值？"><a href="#Q-文档维护和-Agent-订阅哪个更值？" class="headerlink" title="Q: 文档维护和 Agent 订阅哪个更值？"></a>Q: 文档维护和 Agent 订阅哪个更值？</h3><p>文档维护更值。Agent 订阅每月花几十上百美元，本质上是为平台帮你”找文件、拼 prompt”的便利性付费。但如果你的项目文档已经足够完备——技术栈、目录结构、设计决策、编码规范都写清楚了——那么换个免费 Agent 效果几乎没差。文档是你自己的资产，可以跨平台复用；Agent 订阅是消耗品，换了平台就得重新来。</p><h3 id="Q-免费模型真的够用吗？"><a href="#Q-免费模型真的够用吗？" class="headerlink" title="Q: 免费模型真的够用吗？"></a>Q: 免费模型真的够用吗？</h3><p>对绝大多数日常任务够用。作者的经验是 DeepSeek Flash 级别的免费模型就能处理大部分代码修改、文档生成、知识整理等任务。遇到确实搞不动的复杂场景，可以临时切到有免费试用额度的平台（如 Trae、Codex 试用套装）。真正的瓶颈通常不是模型能力，而是你给它的上下文（文档）够不够好。</p><h2 id="快速上手步骤"><a href="#快速上手步骤" class="headerlink" title="快速上手步骤"></a>快速上手步骤</h2><ol><li><strong>理解 Agent 与 Skill 的关系</strong>：Agent 是执行引擎（负责调模型、读文件、改代码），Skill 是结构化指令文档（告诉 Agent 你的项目结构、编码规范、操作流程）。文档质量直接决定 Agent 的输出质量，好的文档可以让不同 Agent 平台的产出高度一致。</li><li><strong>编写项目文档</strong>：在项目根目录创建 <code>AGENTS.md</code>，至少包含：项目概述（技术栈、目录结构、关键模块）、工作流说明（新增功能怎么做、修 bug 步骤、发布流程）、规则文档（命名规范、注释要求、踩坑记录）。</li><li><strong>让 AI 辅助写文档</strong>：将 karpathy 的 llm-wiki gist 或已有的项目规范喂给 AI，让它自动生成 AGENTS.md 和各模块的 SKILL 文档，人工审核后放入项目目录。</li><li><strong>切换平台验证</strong>：用不同的免费 Agent 平台（OpenCode、Qoder、Hermes 等）打开同一个项目，执行相同任务（如修 bug、加功能），对比输出质量。如果文档完备，差异应该很小。</li></ol><p>原文地址：<a href="https://lichuanyang.top/posts/26060/">https://lichuanyang.top/posts/26060/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/posts/26060/</id>
    <link href="https://lichuanyang.top/posts/26060/"/>
    <published>2026-06-03T14:00:00.000Z</published>
    <summary>我折腾了一阵子AI Agent，发现它本质上就是把文档和prompt拼来拼去。与其花钱订阅，不如自己维护好项目文档，然后哪里免费token用哪里。</summary>
    <title>教你薅token：构建agent无关的AI工作流</title>
    <updated>2026-06-27T03:21:42.170Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="AI实践" scheme="https://lichuanyang.top/categories/AI%E5%AE%9E%E8%B7%B5/"/>
    <category term="效率工具" scheme="https://lichuanyang.top/tags/%E6%95%88%E7%8E%87%E5%B7%A5%E5%85%B7/"/>
    <category term="AI" scheme="https://lichuanyang.top/tags/AI/"/>
    <category term="AI Agent" scheme="https://lichuanyang.top/tags/AI-Agent/"/>
    <category term="hexo" scheme="https://lichuanyang.top/tags/hexo/"/>
    <category term="blog" scheme="https://lichuanyang.top/tags/blog/"/>
    <category term="automation" scheme="https://lichuanyang.top/tags/automation/"/>
    <category term="LLM" scheme="https://lichuanyang.top/tags/LLM/"/>
    <content>
      <![CDATA[<h2 id="背景：一个繁琐到让人崩溃的任务"><a href="#背景：一个繁琐到让人崩溃的任务" class="headerlink" title="背景：一个繁琐到让人崩溃的任务"></a>背景：一个繁琐到让人崩溃的任务</h2><p>最近想给自己的 Hexo 博客换个主题。用了好几年的 Next 主题，虽然经典，但想换个更现代的风格。</p><p>看起来很简单对吧？但实际操作过的朋友都知道，Hexo 主题迁移是一个极其繁琐的过程：</p><ol><li><strong>配置文件长达 1000+ 行</strong>，需要逐项对比新旧主题的配置格式</li><li><strong>功能映射复杂</strong>：Next 的 <code>leancloud_visitors</code> 对应 Butterfly 的 <code>busuanzi</code>，Next 的 <code>reading_progress</code> 对应 Butterfly 的 <code>preloader</code></li><li><strong>两个站点要同步</strong>：中英文站都要改，而且配置略有不同</li><li><strong>Font Awesome 版本兼容</strong>：Butterfly 默认配的 FA 7.1.0 根本不存在于 cdnjs</li><li><strong>YAML 格式敏感</strong>：缩进、换行符都可能导致配置失效</li></ol><p>如果手动操作，估计要花一整天，而且很容易出错。</p><h2 id="解决方案：让-AI-Agent-来干"><a href="#解决方案：让-AI-Agent-来干" class="headerlink" title="解决方案：让 AI Agent 来干"></a>解决方案：让 AI Agent 来干</h2><p>这次我尝试了一个不同的方式：<strong>让 AI Agent（Hermes）全程接管这个任务</strong>。</p><h3 id="AI-Agent-的工作流程"><a href="#AI-Agent-的工作流程" class="headerlink" title="AI Agent 的工作流程"></a>AI Agent 的工作流程</h3><p>整个过程，AI Agent 自主完成了以下工作：</p><ol><li><strong>环境分析</strong>：检查当前 Hexo 版本（7.0.0）、Node.js 版本（v24.14.0）、两个站点的配置差异</li><li><strong>版本调研</strong>：查询 GitHub API，获取最新 Hexo 版本（8.1.2）和 breaking changes</li><li><strong>依赖升级</strong>：批量更新 hexo 及所有插件到最新版本</li><li><strong>主题对比</strong>：分析 Next 和 Butterfly 的功能覆盖度，生成对比表格</li><li><strong>配置迁移</strong>：将 Next 的 1000+ 行配置逐项映射到 Butterfly 格式</li><li><strong>问题排查</strong>：<ul><li>发现 FA 7.1.0 在 cdnjs 上 404，降级到 6.7.2</li><li>发现知乎图标需要 <code>fab</code> 前缀</li><li>发现英文站菜单路径错误</li><li>发现 GA、百度统计、站长验证等关键配置丢失</li></ul></li><li><strong>测试验证</strong>：构建测试、本地预览服务器、检查 HTML 输出</li><li><strong>代码提交</strong>：自动生成 commit message 并 push</li></ol><h3 id="关键技术点"><a href="#关键技术点" class="headerlink" title="关键技术点"></a>关键技术点</h3><h4 id="1-Font-Awesome-版本兼容性问题"><a href="#1-Font-Awesome-版本兼容性问题" class="headerlink" title="1. Font Awesome 版本兼容性问题"></a>1. Font Awesome 版本兼容性问题</h4><p>这是最隐蔽的一个 bug。Butterfly 主题的 <code>plugins.yml</code> 配置了 FA 7.1.0：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">fontawesome:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">&#x27;@fortawesome/fontawesome-free&#x27;</span></span><br><span class="line">  <span class="attr">version:</span> <span class="number">7.1</span><span class="number">.0</span></span><br></pre></td></tr></table></figure><p>但 cdnjs 上根本没有这个版本！导致所有品牌图标（GitHub、StackOverflow、知乎）都不加载。</p><p>AI Agent 通过以下步骤定位问题：</p><ul><li>检查生成的 HTML，发现加载的是 FA 7.1.0</li><li>请求 cdnjs API，确认 7.1.0 返回 404</li><li>测试 FA 6.7.2，确认包含所有需要的图标</li><li>在配置中覆盖 CDN 地址</li></ul><h4 id="2-YAML-配置迁移的陷阱"><a href="#2-YAML-配置迁移的陷阱" class="headerlink" title="2. YAML 配置迁移的陷阱"></a>2. YAML 配置迁移的陷阱</h4><p>Hexo 的配置文件是 YAML 格式，对缩进和换行符非常敏感。AI Agent 在迁移过程中遇到了几个问题：</p><ul><li><strong>CRLF vs LF</strong>：Butterfly 默认配置是 CRLF 换行，用 sed 替换时匹配失败</li><li><strong>重复键</strong>：patch 工具导致 <code>scroll_percent</code> 出现两次</li><li><strong>值丢失</strong>：YAML 缩进不对导致配置值没有正确写入</li></ul><p>最终用 Python 直接操作文件内容才解决了这些问题。</p><h4 id="3-多站点同步"><a href="#3-多站点同步" class="headerlink" title="3. 多站点同步"></a>3. 多站点同步</h4><p>中英文站的配置大部分相同，但有几处差异：</p><ul><li>英文站的 <code>language</code> 需要改为 <code>en</code></li><li>英文站的菜单需要指向中文站</li><li>英文站的 Valine placeholder 需要用英文</li></ul><p>AI Agent 自动识别这些差异并分别处理。</p><h2 id="效果对比"><a href="#效果对比" class="headerlink" title="效果对比"></a>效果对比</h2><table><thead><tr><th>指标</th><th>手动操作</th><th>AI Agent</th></tr></thead><tbody><tr><td>耗时</td><td>4-8 小时</td><td>30 分钟</td></tr><tr><td>出错率</td><td>高（容易遗漏配置）</td><td>低（系统化检查）</td></tr><tr><td>问题排查</td><td>需要逐个 Google</td><td>自动定位根因</td></tr><tr><td>代码提交</td><td>手动写 commit message</td><td>自动生成</td></tr></tbody></table><h2 id="AI-Agent-的优势"><a href="#AI-Agent-的优势" class="headerlink" title="AI Agent 的优势"></a>AI Agent 的优势</h2><p>这次实践让我深刻体会到 <strong>AI Agent 在这类繁琐、重复性工作中</strong> 的巨大优势：</p><h3 id="1-系统化思维"><a href="#1-系统化思维" class="headerlink" title="1. 系统化思维"></a>1. 系统化思维</h3><p>AI Agent 不会像人一样”想到哪改到哪”。它会：</p><ul><li>先分析当前状态</li><li>制定完整计划</li><li>逐步执行并验证</li><li>发现问题及时修复</li></ul><h3 id="2-跨领域知识"><a href="#2-跨领域知识" class="headerlink" title="2. 跨领域知识"></a>2. 跨领域知识</h3><p>主题迁移涉及多个技术领域：</p><ul><li>Hexo 配置</li><li>Font Awesome 版本管理</li><li>YAML 格式处理</li><li>Git 工作流</li><li>前端资源加载</li></ul><p>AI Agent 能够在这些领域之间自由切换，而人类开发者通常只精通其中一两个。</p><h3 id="3-持久注意力"><a href="#3-持久注意力" class="headerlink" title="3. 持久注意力"></a>3. 持久注意力</h3><p>人类在处理 1000+ 行配置文件时，注意力会逐渐下降，容易遗漏。AI Agent 不会疲劳，每个配置项都会被检查到。</p><h3 id="4-自动化验证"><a href="#4-自动化验证" class="headerlink" title="4. 自动化验证"></a>4. 自动化验证</h3><p>AI Agent 不仅会修改配置，还会：</p><ul><li>运行构建测试</li><li>启动本地服务器验证</li><li>检查 HTML 输出</li><li>确认关键配置生效</li></ul><h2 id="适用场景"><a href="#适用场景" class="headerlink" title="适用场景"></a>适用场景</h2><p>基于这次经验，我认为 <strong>AI Agent 特别适合以下类型的工作</strong>：</p><ol><li><strong>配置迁移</strong>：不同系统之间的配置格式转换</li><li><strong>依赖升级</strong>：批量更新多个包并处理兼容性问题</li><li><strong>代码重构</strong>：大规模的代码格式调整</li><li><strong>文档整理</strong>：从多个来源整合信息</li><li><strong>环境搭建</strong>：新项目的初始化配置</li></ol><h2 id="局限性"><a href="#局限性" class="headerlink" title="局限性"></a>局限性</h2><p>当然，AI Agent 也有局限：</p><ol><li><strong>创意性工作</strong>：UI 设计、文案撰写等仍需人类主导</li><li><strong>业务决策</strong>：是否升级、选择哪个主题等决策需要人类判断</li><li><strong>复杂调试</strong>：某些运行时问题需要人工介入</li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这次用 AI Agent 完成 Hexo 主题迁移的实践，让我看到了 <strong>LLM（大语言模型）在软件工程领域的巨大潜力</strong>。</p><p>AI Agent 不是来取代开发者的，而是来增强我们的能力。它把我们从繁琐、重复的工作中解放出来，让我们能够专注于更有价值的事情。</p><p>如果你也有类似的”体力活”，不妨试试让 AI Agent 来帮忙。你会发现，<strong>AI 辅助开发</strong> 不是未来，而是现在。</p><h2 id="相关技术栈"><a href="#相关技术栈" class="headerlink" title="相关技术栈"></a>相关技术栈</h2><ul><li><strong>AI Agent</strong>：Hermes Agent</li><li><strong>LLM</strong>：DeepSeek V4 Pro &#x2F; MiMo v2.5</li><li><strong>静态站点生成器</strong>：Hexo 8.1.2</li><li><strong>主题</strong>：Butterfly 5.5.4</li><li><strong>图标库</strong>：Font Awesome 6.7.2</li><li><strong>评论系统</strong>：Valine (LeanCloud)</li><li><strong>统计分析</strong>：Google Analytics &#x2F; 百度统计</li></ul><hr><p><em>本文由 AI Agent 辅助撰写，记录了一次真实的主题迁移实践。</em></p><p>原文地址：<a href="https://lichuanyang.top/posts/48979/">https://lichuanyang.top/posts/48979/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/posts/48979/</id>
    <link href="https://lichuanyang.top/posts/48979/"/>
    <published>2026-05-28T14:00:00.000Z</published>
    <summary>用 AI Agent 全自动完成 Hexo 主题从 Next 到 Butterfly 的迁移，1000+ 行配置文件的自动映射与验证。</summary>
    <title>用 AI Agent 完成 Hexo 主题迁移：从 Next 到 Butterfly 的全自动化实践</title>
    <updated>2026-06-27T02:31:54.494Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="技术杂谈" scheme="https://lichuanyang.top/categories/%E6%8A%80%E6%9C%AF%E6%9D%82%E8%B0%88/"/>
    <category term="Vercel" scheme="https://lichuanyang.top/tags/Vercel/"/>
    <category term="博客" scheme="https://lichuanyang.top/tags/%E5%8D%9A%E5%AE%A2/"/>
    <category term="运维" scheme="https://lichuanyang.top/tags/%E8%BF%90%E7%BB%B4/"/>
    <category term="独立博客" scheme="https://lichuanyang.top/tags/%E7%8B%AC%E7%AB%8B%E5%8D%9A%E5%AE%A2/"/>
    <content>
      <![CDATA[<p>前几天写了篇新文章，照常往 Vercel 部署，结果 pipeline 直接报错了。一开始以为是构建问题，折腾了一会儿才发现不对——是 Vercel 账号出了状况。用 163 邮箱注册的账号怎么都登不上去了。后来才搞清楚，Vercel 把整个 163.com 邮箱根域给封禁了。</p><span id="more"></span><h2 id="先说说-Vercel-是什么"><a href="#先说说-Vercel-是什么" class="headerlink" title="先说说 Vercel 是什么"></a>先说说 Vercel 是什么</h2><p>如果你是搞独立博客的，大概率听过或者正在用 Vercel。简单说，它是一个前端部署平台，支持自动从 GitHub 仓库拉代码、构建、部署，给你的网站分配一个域名，还自带 CDN 加速。很多人的博客就是 GitHub Pages + Vercel 的组合——代码放在 GitHub，用 Vercel 来部署和托管。</p><p>我自己的博客也是这么搞的。GitHub Pages 本来也能直接部署静态页面，但 Vercel 在自定义域名、HTTPS、CDN 这些方面用起来更方便，所以就选了它。</p><h2 id="163-邮箱被封禁的来龙去脉"><a href="#163-邮箱被封禁的来龙去脉" class="headerlink" title="163 邮箱被封禁的来龙去脉"></a>163 邮箱被封禁的来龙去脉</h2><p>大概在 2026 年 5 月 4 号左右，大量使用 163 邮箱注册 Vercel 的用户发现，自己的账号突然无法登录了。我在网上搜了一下，发现不只是我一个人遇到这个问题。NodeSeek、V2EX 这些论坛上都有人在讨论。</p><p>根据网上的信息，Vercel 在 2026 年 4 月中旬遭遇了一次比较严重的安全事件。黑客团伙 ShinyHunters 通过一个第三方 AI 工具入侵了 Vercel 的内部系统，窃取了员工数据、企业后台权限和一些部署凭证，还索要了 200 万美元的赎金。</p><p>这次封禁 163 邮箱，大概率是安全事件后的应对措施之一。可能是因为 163 邮箱在此次事件中被大量用于恶意注册或攻击行为，也可能只是 Vercel 对某些邮箱域名做了更严格的风控。具体原因 Vercel 没有公开说明，但结果就是：整个 163.com 邮箱域名被一刀切了。</p><p>影响范围不小。很多国内开发者和独立博客作者都是用 163 邮箱注册的 Vercel，这次封禁直接导致这些人的博客全部无法访问。</p><p>不过如果你现在 Vercel 账号还处于登录状态（比如工作电脑的浏览器还保持着登录），其实是可以抢救一下的：进账号设置，添加一个新邮箱（Outlook、Gmail 都行），然后把 163 邮箱去掉。这样账号就能继续正常使用，不需要重新部署。网上有人分享了这个方法，能省不少事。但如果你跟我一样，发现的时候已经完全登不上去了，那就只能走下面的重建流程了。</p><h2 id="回忆我的博客部署架构"><a href="#回忆我的博客部署架构" class="headerlink" title="回忆我的博客部署架构"></a>回忆我的博客部署架构</h2><p>账号被封了，博客打不开了，第一件事就是得搞清楚自己的博客到底是怎么部署的。说实话，这个博客配置的时间已经比较久了，当时配完之后就没怎么动过，具体流程已经记不太清了。</p><p>花了不少功夫才回忆起来，大概的链路是这样的：</p><ol><li>博客源码放在 GitHub 仓库里</li><li>Vercel 连接 GitHub 仓库，负责构建和部署</li><li>域名解析这块，分了两层：阿里云的 DNS 指向 Cloudflare，Cloudflare 再指向 Vercel</li></ol><p>为什么要搞这么复杂？主要是为了用 Cloudflare 的 CDN 和安全防护能力。阿里云做域名注册商，Cloudflare 做中间层的 DNS 管理和 CDN，Vercel 做最终的静态页面托管。当年配的时候也是参考了网上的一些教程，虽然链路长了点，但稳定运行了很久，也就没再动过。</p><h2 id="恢复过程：新账号-重新部署"><a href="#恢复过程：新账号-重新部署" class="headerlink" title="恢复过程：新账号 + 重新部署"></a>恢复过程：新账号 + 重新部署</h2><p>搞清楚部署架构之后，恢复操作其实并不复杂。</p><p><strong>第一步：注册新的 Vercel 账号</strong></p><p>这次学聪明了，不用 163 邮箱了，用 Outlook 重新注册了一个 Vercel 账号。其实 Gmail 也行，但考虑到这次是 Vercel 封禁了特定邮箱域名，用国外主流的邮箱服务会更稳妥一些。</p><p><strong>第二步：重新部署 GitHub 项目</strong></p><p>登录新账号后，连接 GitHub，把博客仓库导入 Vercel。Vercel 自动识别了项目类型，构建和部署都是自动完成的，跟之前的操作基本一样。</p><p><strong>第三步：重新配置域名</strong></p><p>这一步是关键。因为之前域名解析是阿里云 → Cloudflare → Vercel 的链路，重新部署后需要把域名绑定到新的 Vercel 项目上。</p><p>让我惊喜的是，现在 Vercel 在添加自定义域名的时候，已经支持自动修改 Cloudflare 的 DNS 配置了。以前我记得需要手动去 Cloudflare 后台添加 CNAME 记录，现在 Vercel 直接通过 API 帮你搞定，点几下就完事了。</p><p>整个恢复过程，从注册新账号到博客重新可以访问，大概花了不到半小时。</p><h2 id="几点经验"><a href="#几点经验" class="headerlink" title="几点经验"></a>几点经验</h2><ol><li><p><strong>不要用国内邮箱注册海外服务</strong>。163、QQ 邮箱这类国内邮箱，在海外服务的风控体系里天然就是高风险对象。这次是 Vercel，下次可能是别的服务。建议主力账号用 Gmail 或 Outlook。</p></li><li><p><strong>记录好自己的部署架构</strong>。这次最浪费时间的环节就是回忆部署链路。当时配置完觉得都记住了，结果过了这么久早就忘得差不多了。建议把部署架构和关键配置写在笔记里，哪怕只是几句话。</p></li><li><p><strong>Vercel 现在对 Cloudflare 的集成做得不错</strong>。以前手动配 DNS 的时候容易出错，现在自动化程度高了很多，重新部署的成本降低了不少。</p></li><li><p><strong>有条件的话，做好备份和多平台准备</strong>。这次运气好，恢复起来比较快。但如果 GitHub 仓库也出了问题，或者 Vercel 彻底封了项目而不只是账号，恢复就会麻烦很多。重要的博客内容，建议在本地或者别的平台也有一份。</p></li></ol><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>这次 Vercel 封禁 163 邮箱的事，算是给所有用国内邮箱注册海外服务的人提了个醒。平台侧的安全措施我们控制不了，但自己的账号安全和部署架构，还是可以提前做好准备的。</p><p>如果你也遇到了 Vercel 163 邮箱被封、Vercel 账号无法登录的问题，希望这篇文章能帮到你。恢复的核心思路就是：换一个邮箱重新注册，把项目重新部署一遍，域名重新绑定一下。操作不复杂，但前提是你得记得自己的部署链路。</p><p>原文地址: <a href="https://lichuanyang.top/posts/39648/">https://lichuanyang.top/posts/39648/</a></p><hr><h2 id="快速上手步骤"><a href="#快速上手步骤" class="headerlink" title="快速上手步骤"></a>快速上手步骤</h2><h3 id="Step-1-发现问题"><a href="#Step-1-发现问题" class="headerlink" title="Step 1: 发现问题"></a>Step 1: 发现问题</h3><p>当博客无法访问或 Vercel 部署报错时，首先确认是否是账号被封禁。尝试登录 Vercel，如果使用 163 邮箱注册的账号无法登录，很可能已被封禁。</p><h3 id="Step-2-排查原因"><a href="#Step-2-排查原因" class="headerlink" title="Step 2: 排查原因"></a>Step 2: 排查原因</h3><p>搜索相关论坛（NodeSeek、V2EX 等），确认是否是 Vercel 大面积封禁 163 邮箱。了解封禁的背景和影响范围，做到心中有数。</p><h3 id="Step-3-迁移邮箱验证"><a href="#Step-3-迁移邮箱验证" class="headerlink" title="Step 3: 迁移邮箱验证"></a>Step 3: 迁移邮箱验证</h3><p>如果还能登录账号，立即在账号设置中添加 Gmail 或 Outlook 邮箱，并移除 163 邮箱。如果已无法登录，用新邮箱重新注册 Vercel 账号。建议使用 Gmail 或 Outlook 等海外主流邮箱。</p><h3 id="Step-4-重新部署"><a href="#Step-4-重新部署" class="headerlink" title="Step 4: 重新部署"></a>Step 4: 重新部署</h3><p>登录新账号后，连接 GitHub 导入博客仓库。Vercel 会自动识别项目类型并完成构建和部署，操作流程与首次部署基本一致。</p><h3 id="Step-5-验证恢复"><a href="#Step-5-验证恢复" class="headerlink" title="Step 5: 验证恢复"></a>Step 5: 验证恢复</h3><p>重新绑定自定义域名。Vercel 现已支持自动修改 Cloudflare DNS 配置，操作非常简便。确认博客所有页面可正常访问，检查 HTTPS、CDN 等功能是否正常运作。</p><hr>]]>
    </content>
    <id>https://lichuanyang.top/posts/39648/</id>
    <link href="https://lichuanyang.top/posts/39648/"/>
    <published>2026-05-11T11:30:00.000Z</published>
    <summary>Vercel 封禁 163 邮箱导致博客部署失败的亲身经历，以及从发现问题到迁移恢复的完整解决方案。</summary>
    <title>Vercel封禁163邮箱后，我是怎么恢复博客的</title>
    <updated>2026-06-27T03:43:25.702Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="AI实践" scheme="https://lichuanyang.top/categories/AI%E5%AE%9E%E8%B7%B5/"/>
    <category term="LLM" scheme="https://lichuanyang.top/tags/LLM/"/>
    <category term="知识管理" scheme="https://lichuanyang.top/tags/%E7%9F%A5%E8%AF%86%E7%AE%A1%E7%90%86/"/>
    <category term="安全规范" scheme="https://lichuanyang.top/tags/%E5%AE%89%E5%85%A8%E8%A7%84%E8%8C%83/"/>
    <category term="wiki" scheme="https://lichuanyang.top/tags/wiki/"/>
    <category term="实践" scheme="https://lichuanyang.top/tags/%E5%AE%9E%E8%B7%B5/"/>
    <content>
      <![CDATA[<p>最近在整理团队的安全开发规范时，遇到了一个老问题：安全规范文档越积越多，但真正用的时候却找不到。每次新人入职，都要翻遍各种文档才能拼凑出完整的安全规范；每次出安全故障，事后总结的经验也散落在各处，下次遇到类似问题还得重新排查。</p><p>我试过用Confluence、Notion、甚至Git仓库的README来管理，但效果都不理想。直到看到了Karpathy提的llm-wiki思路，才觉得这可能是个突破口。</p><span id="more"></span><h2 id="Karpathy的llm-wiki是什么？"><a href="#Karpathy的llm-wiki是什么？" class="headerlink" title="Karpathy的llm-wiki是什么？"></a>Karpathy的llm-wiki是什么？</h2><p>Karpathy在<a href="https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f">Gist</a>里提了个想法：让LLM维护一个Wiki中间层。原话是”the wiki is a persistent, compounding artifact”——这个Wiki层就像个编译器，把原始文档编译成结构化数据，这样LLM每次回答问题时就不用从头在原始文档里”找”信息，直接查Wiki就行。</p><p>我觉得这个思路特别妙。传统文档管理是”存”和”找”，而llm-wiki是”编译”和”查询”。原始文档可能很乱、有重复、甚至有矛盾，但Wiki层是干净、结构化、有交叉引用的。</p><p>举个例子：我们团队有十几份安全开发规范文档，从输入验证到渗透测试都有，还有近十份安全故障复盘报告。这些文档格式不一，有的在Word里，有的在Markdown里，有的甚至只是Slack聊天记录。如果让LLM直接处理这些原始材料，效率会很低。但如果先”编译”成Wiki，效果就完全不一样了。</p><h2 id="我的实现过程"><a href="#我的实现过程" class="headerlink" title="我的实现过程"></a>我的实现过程</h2><h3 id="第一步：生成CLAUDE-md"><a href="#第一步：生成CLAUDE-md" class="headerlink" title="第一步：生成CLAUDE.md"></a>第一步：生成CLAUDE.md</h3><p>我首先把Karpathy的Gist链接丢给Claude，让它基于这个思路生成一份CLAUDE.md文件。这个文件相当于给LLM的”操作手册”，告诉它如何维护Wiki。</p><p>生成的CLAUDE.md包含了几个关键部分：</p><ul><li>三层架构说明（raw → wiki → output）</li><li>Wiki页面规范（frontmatter格式、交叉引用规则）</li><li>摄入工作流（如何处理新素材）</li><li>查询工作流（如何回答用户问题）</li><li>Lint检查（如何保证Wiki健康）</li></ul><p>实际用下来，我发现模型生成的结果基本可用，没有做太多微调。只是另外让AI生成了output层，用于存放最终的规范文档。这样三层架构就完整了：raw放原始素材，wiki放结构化Wiki，output放最终输出。</p><h3 id="第二步：收集和整理原始素材"><a href="#第二步：收集和整理原始素材" class="headerlink" title="第二步：收集和整理原始素材"></a>第二步：收集和整理原始素材</h3><p>这是最耗时的一步。我把散落在各处的安全开发规范文档和故障案例都收集起来，统一放到raw&#x2F;sources&#x2F;目录下。为了保持原貌，我没有修改这些文件的内容，也没有统一格式——PDF、Word、PPT、Markdown等各种格式直接放到raw层就行，模型都可以处理。</p><p>具体来说，我收集了：</p><ul><li>十几份安全开发规范文档：涵盖输入验证、SQL注入防护、XSS防护、CSRF防护、敏感数据保护、日志安全等</li><li>近十份安全故障复盘报告：包括SQL注入漏洞、越权访问、敏感信息泄露、会话劫持等真实案例</li><li>几份安全架构方案文档：涉及零信任架构、安全编码指南、渗透测试流程等</li></ul><p>这些素材质量参差不齐。有的写得很规范，有清晰的标题和列表；有的就是随手记的笔记，甚至还有截图。但没关系，llm-wiki的妙处就在于它能处理这种”脏数据”。</p><h3 id="第三步：让LLM生成Wiki层"><a href="#第三步：让LLM生成Wiki层" class="headerlink" title="第三步：让LLM生成Wiki层"></a>第三步：让LLM生成Wiki层</h3><p>有了CLAUDE.md和原始素材，就可以让LLM开始工作了。我这里用的是Claude，主要是因为它的上下文窗口足够大，能一次性处理较多内容。</p><p>具体操作是这样的：</p><ol><li>把CLAUDE.md作为系统提示词</li><li>告诉LLM：”请按照CLAUDE.md中的规范，处理raw&#x2F;sources&#x2F;目录下的所有素材，生成wiki层”</li><li>LLM会依次读取每个原始文件，提取关键信息，生成对应的Wiki页面</li></ol><p>这个过程不是一次就能完成的。LLM生成的初版Wiki会有一些问题：</p><ul><li>交叉引用不够充分</li><li>有些概念没有被提取成独立页面</li><li>页面之间的逻辑关系不够清晰</li></ul><p>所以需要多轮迭代。我会检查生成的Wiki，指出问题，让LLM修正。比如：<br>“这个故障案例里提到了’连接池配置不当’，但为什么没有对应的concept页面？请创建一个[[connection-pool-best-practices]]页面，并在故障案例里引用它。”</p><p>经过5-6轮迭代，Wiki层才逐渐成型。</p><h3 id="第四步：生成最终规范文档"><a href="#第四步：生成最终规范文档" class="headerlink" title="第四步：生成最终规范文档"></a>第四步：生成最终规范文档</h3><p>Wiki层准备好后，就可以生成最终的规范文档了。我让LLM基于Wiki层，生成一份面向开发者的、可直接阅读的规范文档。</p><p>这里有个设计决策：最终文档是放在output&#x2F;目录下，而不是直接修改原始素材。这样保持了三层架构的清晰性：</p><ul><li>raw&#x2F;：原始素材，不可修改</li><li>wiki&#x2F;：结构化Wiki，LLM维护</li><li>output&#x2F;：最终输出，人类阅读</li></ul><p>生成的规范文档有几个特点：</p><ol><li>每个规范条目都有”为什么”——不只是说”要这样做”，还解释”为什么要这样做”</li><li>相关的故障案例会作为”反面教材”链接在旁边</li><li>有交叉引用，比如”数据库连接池配置”会链接到”连接池最佳实践”这个concept页面</li></ol><h2 id="实际效果"><a href="#实际效果" class="headerlink" title="实际效果"></a>实际效果</h2><h3 id="阅读体验"><a href="#阅读体验" class="headerlink" title="阅读体验"></a>阅读体验</h3><p>最终的规范文档可以直接用Obsidian打开阅读。因为是Markdown格式，支持：</p><ul><li>代码块高亮</li><li>内部链接跳转（点击就能跳转到相关概念或故障案例）</li><li>标签筛选（比如筛选所有#安全相关的规范）</li></ul><p>更重要的是，每条安全规范都关联着真实的故障案例。比如在”SQL注入防护”这条规范旁边，就链接着[[sql-injection-vulnerability-incident]]这个故障案例，详细描述了漏洞是怎么产生的、造成了什么影响、怎么修复的。这样开发者不仅能知道”要怎么做”，还能看到”不这么做会出什么问题”。</p><p>比起以前散落在各处的文档，阅读体验好了不止一个量级。</p><h3 id="LLM问答"><a href="#LLM问答" class="headerlink" title="LLM问答"></a>LLM问答</h3><p>更强大的是LLM问答能力。因为有了Wiki层，LLM回答问题时特别准。比如我问：<br>“数据库批量更新时要注意什么？”</p><p>LLM会：</p><ol><li>先在Wiki里找到[[batch-operation-security]]这个concept页面</li><li>结合[[sql-injection-batch-incident]]这个故障案例</li><li>给出具体的建议：必须使用参数化查询、禁止字符串拼接SQL、批量操作要有事务控制、超大批次要分页处理等</li><li>还会解释为什么：因为批量操作一旦出问题，影响范围是单条操作的N倍</li></ol><p>再比如问：”用户上传文件怎么处理才安全？”</p><p>LLM会：</p><ol><li>找到[[file-upload-security]]这个concept页面</li><li>结合[[malicious-file-upload-incident]]这个故障案例</li><li>给出具体建议：文件类型白名单校验、文件内容检测、重命名存储、限制文件大小、隔离存储等</li></ol><p>这比让LLM在十几份原始文档里”找”答案要靠谱得多。</p><h3 id="持续更新"><a href="#持续更新" class="headerlink" title="持续更新"></a>持续更新</h3><p>最让我满意的是更新机制。当有新的规范文档或故障报告时，我只需要：</p><ol><li>把新文件放到raw&#x2F;sources&#x2F;目录下</li><li>告诉LLM：”有新素材需要摄入”</li><li>LLM会自动更新Wiki层和规范文档</li></ol><p>比如上周我们出了个新的安全故障：因为用户输入未过滤导致XSS漏洞。我把故障报告放到raw目录后，LLM自动：</p><ol><li>创建了[[xss-user-input-incident]]这个故障案例页面</li><li>更新了[[input-validation-security]]这个concept页面，补充了XSS相关的注意事项</li><li>在规范文档的”输入验证规范”章节增加了相关条目</li></ol><h2 id="遇到的问题和解决方案"><a href="#遇到的问题和解决方案" class="headerlink" title="遇到的问题和解决方案"></a>遇到的问题和解决方案</h2><h3 id="问题1：LLM生成的Wiki质量不稳定"><a href="#问题1：LLM生成的Wiki质量不稳定" class="headerlink" title="问题1：LLM生成的Wiki质量不稳定"></a>问题1：LLM生成的Wiki质量不稳定</h3><p>刚开始时，LLM生成的Wiki质量波动很大。有时能很好地提取关键信息，有时会漏掉重要内容。</p><p><strong>解决方案</strong>：我细化了CLAUDE.md里的Wiki页面规范。比如要求每个页面必须有：</p><ul><li>Summary行：一句话描述核心内容</li><li>Tags行：用#标签标记主题</li><li>明确的页面类型（concept&#x2F;entity&#x2F;source&#x2F;comparison）</li></ul><hr><p>对于安全故障案例，还要求包含：漏洞类型、攻击向量、影响范围、修复方案、预防措施。这样LLM就有更清晰的指导，生成质量稳定了很多。</p><h3 id="问题2：交叉引用经常断裂"><a href="#问题2：交叉引用经常断裂" class="headerlink" title="问题2：交叉引用经常断裂"></a>问题2：交叉引用经常断裂</h3><p>Wiki的核心价值在于交叉引用，但LLM经常创建单向链接，或者链接到不存在的页面。</p><p><strong>解决方案</strong>：在CLAUDE.md里强化了交叉引用规则：</p><ol><li>必须双向链接：如果A页面引用了[[B]]，那么B页面也必须引用[[A]]</li><li>frontmatter里的related字段必须和正文里的[[wiki-link]]一致</li><li>只能引用已存在的页面，需要先创建再引用</li></ol><p>我还加了Lint检查，定期检查断裂链接和孤儿页面。</p><h3 id="问题3：处理非结构化素材效率低"><a href="#问题3：处理非结构化素材效率低" class="headerlink" title="问题3：处理非结构化素材效率低"></a>问题3：处理非结构化素材效率低</h3><p>有些原始素材质量很差，比如截图、手写笔记、甚至是Slack聊天记录的导出。LLM处理这些素材时效率很低。</p><p><strong>解决方案</strong>：对于这类素材，我会先手动整理成结构化的Markdown，再交给LLM处理。虽然增加了前期工作量，但保证了最终质量。</p><h2 id="一些经验教训"><a href="#一些经验教训" class="headerlink" title="一些经验教训"></a>一些经验教训</h2><ol><li><p><strong>不要追求一步到位</strong>。llm-wiki是个迭代过程。第一版Wiki肯定不完美，但可以通过持续迭代改进。</p></li><li><p><strong>CLAUDE.md要不断优化</strong>。随着使用，你会发现CLAUDE.md里缺失的规则。比如我后来加了”安全故障案例必须包含：漏洞类型、攻击向量、影响范围、修复方案、预防措施”这样的结构化要求。</p></li><li><p><strong>保持三层架构的纯净</strong>。raw&#x2F;就是raw&#x2F;，不要修改；wiki&#x2F;由LLM维护；output&#x2F;面向人类。混在一起会越来越乱。</p></li><li><p><strong>定期做健康检查</strong>。我会每周跑一次Lint，检查断裂链接、孤儿页面、单向链接等问题。发现问题及时修复。</p></li><li><p><strong>接受不完美</strong>。LLM生成的Wiki不会100%准确，会有遗漏和错误。但相比完全靠人工维护，已经好太多了。</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>用llm-wiki管理安全开发规范，本质上是把”文档管理”变成了”知识工程”。原始文档是”原材料”，Wiki是”半成品”，规范文档是”成品”。LLM在这个过程中充当了”编译器”和”维护者”的角色。</p><p>这个方法最大的价值是<strong>可持续性</strong>。传统的文档管理，时间一长就会荒废，因为维护成本太高。而llm-wiki把维护成本转移给了LLM，人类只需要：</p><ol><li>提供原始素材（这个避免不了）</li><li>检查和修正LLM的工作（比从头写轻松多了）</li><li>使用最终产出（享受成果）</li></ol><p>如果你也在为安全开发规范的管理头疼，不妨试试这个思路。不用完全照搬我的实现，关键是理解”Wiki中间层”这个核心理念。至于具体用什么工具、怎么组织目录，都可以根据实际情况调整。</p><p>毕竟，工具是为人服务的，不是吗？</p><p>原文地址: <a href="https://lichuanyang.top/posts/88001/">https://lichuanyang.top/posts/88001/</a></p><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h3 id="Q-LLM-生成的代码或规范文档安全吗？会不会引入新的安全风险？"><a href="#Q-LLM-生成的代码或规范文档安全吗？会不会引入新的安全风险？" class="headerlink" title="Q: LLM 生成的代码或规范文档安全吗？会不会引入新的安全风险？"></a>Q: LLM 生成的代码或规范文档安全吗？会不会引入新的安全风险？</h3><p>llm-wiki 方案中，LLM 的角色是”编译器”而非”独立决策者”——它将原始的安全规范文档<strong>提取、结构化、交叉引用</strong>，最终生成的规范文档来源于团队已有的安全知识，并不是 LLM 凭空创造的。真正需要关注的风险点是：1) 原始素材本身是否正确（LLM 不会替你验证这一点）；2) LLM 可能会<strong>遗漏</strong>某些安全条款（因此需要人工审核）。LLM 本身不会主动引入安全漏洞，但<strong>过度信赖未经审核的 LLM 输出</strong>才是真正的风险。</p><h3 id="Q-用-LLM-管理规范后，如何审核和保证质量？"><a href="#Q-用-LLM-管理规范后，如何审核和保证质量？" class="headerlink" title="Q: 用 LLM 管理规范后，如何审核和保证质量？"></a>Q: 用 LLM 管理规范后，如何审核和保证质量？</h3><p>文中总结了三层质量保障机制：1) <strong>CLAUDE.md 规范约束</strong>：通过结构化的模板要求（如安全故障案例必须包含漏洞类型、攻击向量、影响范围、修复方案、预防措施），让 LLM 有章可循；2) <strong>多轮迭代</strong>：初版 Wiki 不会完美，作者用了 5-6 轮迭代让 LLM 修正交叉引用、补充遗漏概念；3) <strong>定期 Lint 检查</strong>：每周检查断裂链接、孤儿页面、单向链接等结构问题。最可靠的是<strong>人工最终校验</strong>——LLM 降低维护成本，但不替代人工把关。</p><h3 id="Q-llm-wiki-方案和传统-lint-工具（SonarQube、ESLint-等）有何不同？"><a href="#Q-llm-wiki-方案和传统-lint-工具（SonarQube、ESLint-等）有何不同？" class="headerlink" title="Q: llm-wiki 方案和传统 lint 工具（SonarQube、ESLint 等）有何不同？"></a>Q: llm-wiki 方案和传统 lint 工具（SonarQube、ESLint 等）有何不同？</h3><p>两者是<strong>互补关系</strong>，解决不同层次的问题。传统 lint 工具是<strong>规则引擎</strong>——检查代码是否违反预定义规则（如 SQL 注入、XSS），工作在代码层面，规则固定。llm-wiki 是<strong>知识管理工具</strong>——解决规范文档散落、新人学习成本高、故障经验无法沉淀复用的问题，工作在知识层面。理想状态下，llm-wiki 产出的规范可以作为 lint 规则的来源，而 lint 工具则自动化地检查代码是否遵守这些规范。</p><h3 id="Q-三层架构（raw-wiki-output）为什么重要？混在一起会怎样？"><a href="#Q-三层架构（raw-wiki-output）为什么重要？混在一起会怎样？" class="headerlink" title="Q: 三层架构（raw&#x2F;wiki&#x2F;output）为什么重要？混在一起会怎样？"></a>Q: 三层架构（raw&#x2F;wiki&#x2F;output）为什么重要？混在一起会怎样？</h3><h2 id="三层架构的核心是保持知识流向的单向性：raw-是原始素材（不可修改，保持原貌），wiki-是-LLM-维护的结构化中间层（可迭代优化），output-是面向人类的最终产出（直接可读）。混在一起的后果：1-原始素材和加工产物无法区分，出问题时难以溯源；2-修改-output-后-wiki-层过时，LLM-下次查询会给出矛盾信息；3-维护成本回归传统模式——每次更新都要同时改多个地方。作者强调”保持三层架构的纯净”是方案可持续的关键。"><a href="#三层架构的核心是保持知识流向的单向性：raw-是原始素材（不可修改，保持原貌），wiki-是-LLM-维护的结构化中间层（可迭代优化），output-是面向人类的最终产出（直接可读）。混在一起的后果：1-原始素材和加工产物无法区分，出问题时难以溯源；2-修改-output-后-wiki-层过时，LLM-下次查询会给出矛盾信息；3-维护成本回归传统模式——每次更新都要同时改多个地方。作者强调”保持三层架构的纯净”是方案可持续的关键。" class="headerlink" title="三层架构的核心是保持知识流向的单向性：raw 是原始素材（不可修改，保持原貌），wiki 是 LLM 维护的结构化中间层（可迭代优化），output 是面向人类的最终产出（直接可读）。混在一起的后果：1) 原始素材和加工产物无法区分，出问题时难以溯源；2) 修改 output 后 wiki 层过时，LLM 下次查询会给出矛盾信息；3) 维护成本回归传统模式——每次更新都要同时改多个地方。作者强调”保持三层架构的纯净”是方案可持续的关键。"></a>三层架构的核心是<strong>保持知识流向的单向性</strong>：raw 是原始素材（不可修改，保持原貌），wiki 是 LLM 维护的结构化中间层（可迭代优化），output 是面向人类的最终产出（直接可读）。混在一起的后果：1) 原始素材和加工产物无法区分，出问题时难以溯源；2) 修改 output 后 wiki 层过时，LLM 下次查询会给出矛盾信息；3) 维护成本回归传统模式——每次更新都要同时改多个地方。作者强调”保持三层架构的纯净”是方案可持续的关键。</h2>]]>
    </content>
    <id>https://lichuanyang.top/posts/64/</id>
    <link href="https://lichuanyang.top/posts/64/"/>
    <published>2026-05-11T07:38:16.000Z</published>
    <summary>用 LLM 管理团队安全开发规范的实践，基于 Karpathy 的 llm-wiki 思路解决规范文档散落的问题。</summary>
    <title>用LLM管理安全开发规范：一次llm-wiki实践</title>
    <updated>2026-06-27T03:42:25.683Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="Java" scheme="https://lichuanyang.top/categories/Java/"/>
    <category term="java" scheme="https://lichuanyang.top/tags/java/"/>
    <category term="vaadin" scheme="https://lichuanyang.top/tags/vaadin/"/>
    <category term="springboot" scheme="https://lichuanyang.top/tags/springboot/"/>
    <category term="前端开发" scheme="https://lichuanyang.top/tags/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    <content>
      <![CDATA[<h2 id="后端工程师的前端困境"><a href="#后端工程师的前端困境" class="headerlink" title="后端工程师的前端困境"></a>后端工程师的前端困境</h2><p>后端工程师开发前端的痛点，通常来说莫过于太过繁琐，经常要为了一些很小的事查半天。Vaadin很好的解决了这个痛点，为后端工程师提供易上手、方便使用的前端代码编写解决方案，今天我们就来了解一下。</p><span id="more"></span><h2 id="Vaadin-是什么"><a href="#Vaadin-是什么" class="headerlink" title="Vaadin 是什么"></a>Vaadin 是什么</h2><p>大家好，今天跟大家介绍一个对后端工程师特别有价值的工具——Vaadin。</p><p>说起来，上手前端基本的html, css开发，确实并不难，但是如果只会这些基本的东西，开发起来会很繁琐。如果想要使用前端生态中的各种轮子，虽说便利度提升了，但学习成本也会同步上升。所以，如果不是职业的全栈工程师，只是作为一个后端，想临时写点前端代码，比如自己想做点小项目，通常来说都会有个很痛苦的过程。</p><p>Vaadin很好的解决了这个痛点。通过vaadin包装好的常用前端组件，我们几乎可以零学习成本的编写出功能完备、不太难看的页面。对于后端背景的程序员来说，无疑会大幅度降低自己做些小项目的成本。</p><h2 id="核心概念"><a href="#核心概念" class="headerlink" title="核心概念"></a>核心概念</h2><p>Vaadin提供的功能，就是可以直接用java代码来写页面。Vaadin提供了多种输入框、表单等等封装好的前端样式，而且与springboot做了深度的融合，使用起来非常方便。</p><p>Vaadin的实际原理并不复杂，主要是基于服务端渲染，即在后端生成最终的html代码，交给浏览器。服务端渲染，这个并不罕见，与客户端渲染的优势和劣势，我们在这里不多讲。当然，对于vaadin来说，使用服务端渲染，似乎也没什么好说的，毕竟是写的后端代码，直接在后端做渲染，是个再正常不过的实现路径。Vaadin的引擎对前后端之间的交互做了封装，所以对使用者来说，前后端之间的交互是无感的，在页面层，我们也可以正常的调用后端service.</p><h2 id="快速上手"><a href="#快速上手" class="headerlink" title="快速上手"></a>快速上手</h2><p>下面是我写的一段代码示例，可以更直观的感受Vaadin的作用：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">@Route(value = &quot;path&quot;, layout = MainView.class)</span><br><span class="line">@PageTitle(&quot;路径规划&quot;)</span><br><span class="line">public class PathView extends VerticalLayout &#123;</span><br><span class="line"></span><br><span class="line">    @Autowired</span><br><span class="line">    private PathService pathService;</span><br><span class="line"></span><br><span class="line">    public PathView() &#123;</span><br><span class="line">        TextField start = new TextField();</span><br><span class="line">        TextField end = new TextField();</span><br><span class="line">        HorizontalLayout path = new HorizontalLayout();</span><br><span class="line">        path.add(start, end);</span><br><span class="line">        Button pathCalculate = new Button(&quot;calculate path&quot;);</span><br><span class="line"></span><br><span class="line">        VerticalLayout result = new VerticalLayout();</span><br><span class="line">        TextField transferNum = new TextField();</span><br><span class="line">        TextField distance = new TextField();</span><br><span class="line">        Text stations = new Text(&quot;&quot;);</span><br><span class="line">        result.add(</span><br><span class="line">                new H3(&quot;换乘数: &quot;),</span><br><span class="line">                transferNum,</span><br><span class="line">                new H3(&quot;总距离 :&quot;),</span><br><span class="line">                distance,</span><br><span class="line">                new H3(&quot;途径站点详情:&quot;),</span><br><span class="line">                stations</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">        pathCalculate.addClickListener(click -&gt; &#123;</span><br><span class="line">            PathInfoVO pathResult = pathService.getPath(start.getValue(), end.getValue());</span><br><span class="line">            System.out.println(pathResult);</span><br><span class="line">            stations.setText(StringUtils.join(pathResult.getDetail(), &quot;,&quot;));</span><br><span class="line">            transferNum.setValue(String.valueOf(pathResult.getTransferNum()));</span><br><span class="line">            distance.setValue(String.valueOf(pathResult.getDistance()));</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        add(</span><br><span class="line">                new Text(&quot;hello world&quot;),</span><br><span class="line">                path,</span><br><span class="line">                pathCalculate,</span><br><span class="line">                result</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码中，我们完全使用java代码对页面中的各个组件进行了编排，包括button的click函数，也是使用java开发者习惯的方式定义，并且能够直接调用其他后端service, 可以说是几乎零学习成本了。</p><p>详细代码和运行效果，可以到 <a href="https://github.com/lcy362/mobo-subway">项目地址</a>中查看。</p><p>如果你对Vaadin感兴趣，或者有任何问题或想法，欢迎在评论区交流。一起探索如何更好地利用Vaadin，提升我们的开发效率吧！</p><p>原文地址： <a href="https://lichuanyang.top/posts/43947/">https://lichuanyang.top/posts/43947/</a></p><hr><h2 id="快速上手步骤"><a href="#快速上手步骤" class="headerlink" title="快速上手步骤"></a>快速上手步骤</h2><h3 id="Step-1-环境准备"><a href="#Step-1-环境准备" class="headerlink" title="Step 1: 环境准备"></a>Step 1: 环境准备</h3><p>确保已安装 JDK 和 Maven&#x2F;Gradle，推荐使用 Spring Boot 作为基础框架。在 IDE 中安装 Vaadin 插件以获得更好的开发体验。</p><h3 id="Step-2-创建第一个Vaadin项目"><a href="#Step-2-创建第一个Vaadin项目" class="headerlink" title="Step 2: 创建第一个Vaadin项目"></a>Step 2: 创建第一个Vaadin项目</h3><p>使用 Spring Initializr 或 Vaadin 官方脚手架创建项目，添加 Vaadin 依赖。项目启动后访问默认端口即可看到初始页面。</p><h3 id="Step-3-编写UI组件"><a href="#Step-3-编写UI组件" class="headerlink" title="Step 3: 编写UI组件"></a>Step 3: 编写UI组件</h3><p>使用 Vaadin 提供的组件类（如 <code>TextField</code>、<code>Button</code>、<code>VerticalLayout</code> 等）直接以 Java 代码构建页面布局。通过 <code>@Route</code> 注解定义页面路由，用 <code>@PageTitle</code> 设置页面标题。</p><h3 id="Step-4-数据绑定"><a href="#Step-4-数据绑定" class="headerlink" title="Step 4: 数据绑定"></a>Step 4: 数据绑定</h3><p>通过 <code>@Autowired</code> 注入业务 Service，在按钮的点击事件中调用后端逻辑，将返回结果设置到对应的 UI 组件中，实现前后端数据交互。</p><h3 id="Step-5-部署"><a href="#Step-5-部署" class="headerlink" title="Step 5: 部署"></a>Step 5: 部署</h3><p>使用 <code>mvn package</code> 或 <code>gradle build</code> 打包为可执行 JAR 文件，部署到服务器即可运行。</p><hr>]]>
    </content>
    <id>https://lichuanyang.top/posts/43947/</id>
    <link href="https://lichuanyang.top/posts/43947/"/>
    <published>2024-03-06T11:36:22.000Z</published>
    <summary>介绍 Vaadin 框架如何让后端工程师无需深入前端生态就能高效开发 Web 界面，解决后端开发前端的痛点。</summary>
    <title>Vaadin框架教程：Java工程师的前端开发秘籍</title>
    <updated>2026-06-27T03:43:25.703Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="技术杂谈" scheme="https://lichuanyang.top/categories/%E6%8A%80%E6%9C%AF%E6%9D%82%E8%B0%88/"/>
    <category term="hexo" scheme="https://lichuanyang.top/tags/hexo/"/>
    <category term="个人博客" scheme="https://lichuanyang.top/tags/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"/>
    <category term="i18n" scheme="https://lichuanyang.top/tags/i18n/"/>
    <category term="多语言" scheme="https://lichuanyang.top/tags/%E5%A4%9A%E8%AF%AD%E8%A8%80/"/>
    <content>
      <![CDATA[<h2 id="多语言博客的常见方案"><a href="#多语言博客的常见方案" class="headerlink" title="多语言博客的常见方案"></a>多语言博客的常见方案</h2><p>对于hexo的多语言方案，官方并没有提供很完备的支持，但是网络上有很多人尝试过不同的解决方案。今天，我们对这些方案简单做个总结，看看我们需要的多语言方案是什么样子的。</p><span id="more"></span><p>其实，hexo的多语言方案，说白了就是两个思路，一个是在文章维度切换语言，借助hexo原生的能力和hexo-generator-i18n等插件，用一套框架维护两个语言的内容；另一个则是独立维护不同语言的样式、内容，在站点维度切换语言，只是在域名层面合并到同一个域名。</p><p>具体要选择什么方案，取决于大家想要一个什么样的多语言网站。一开始我想象中的多语言网站的是第一种，即统一的首页，在首页和每篇文章上都支持切换语言，对每篇文章，可以通过切换语言，直达对应的其他语言译文上。</p><p>这个思路，借助于hexo-generator-i18n插件，可以实现，但要做比较多的定制开发。所以，我又重新思量了一下思路，我要的多语言网站应该是什么样。</p><h2 id="方案对比"><a href="#方案对比" class="headerlink" title="方案对比"></a>方案对比</h2><p>重新考虑之后，逐渐觉得第二种思路才是更合理的。我们要做一个多语言网站的目的是什么？对我来说，其实就是要获取一些英文流量。作为一个未备案网站，已经很久无法获取百度的搜索流量了，google在中国占的份额又太低，所以单凭中文内容，很难从搜索中获取较高流量。所以我希望通过提供一些英文内容，获取英文流量。这个目标下，完全不需要对所有的文章都维护多语言版本。何况，不同文章的语言受众也的确有很大区别，所以，语言的切换可以直接在站点层面进行。</p><p>具体操作方式上，一种是直接维护多个站点，两个站点内容、样式完全独立，只是部署在同一个域名下。比如中文站点根路径是lichuanyang.top，英文站点根路径是lichuanyang.top&#x2F;en . 两个站点上各做一个跳转链接，向对方跳转。</p><p>我采用的是另一种方式，相比上述方式，成本小一些，不需要实际维护两个站点。原理大致如下：</p><p>在本地，同样维护两个站点。但是英文站点不会去做实际的部署，只是用作生成静态网页的工具。英文版的内容生成之后，直接复制到中文网站的对应目录下，只对中文网站做部署即可。</p><h2 id="Hexo-双站实现细节"><a href="#Hexo-双站实现细节" class="headerlink" title="Hexo 双站实现细节"></a>Hexo 双站实现细节</h2><p>具体操作步骤如下：</p><ul><li><p>将博客目录整体复制一份，作为英文博客目录. 例如，我的博客根目录叫blog.source, 复制出一个blog.source.en. 这步完成后，如果博客源码也是以git维护的，可以直接在原目录的外层直接新建一个git项目，在外层做管理就行了。 示例如下：</p>  <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">blog(git根目录)/blog.source</span><br><span class="line">              /blog.source.en</span><br></pre></td></tr></table></figure></li><li><p>将en目录下的文章全部删除；站点描述等文本调整为英文内容；英文语言设置为en；英文站点根 (_config.yml中的root配置)设置成&#x2F;en</p></li><li><p>在两个站点下增加跳转链接。我是借助菜单功能，直接在菜单里加一个其他语言项。 next主题中配置如下：</p>  <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">中文站 ：    English: /en || fa fa-language</span><br><span class="line">英文站：     中文: https://lichuanyang.top || fa fa-language</span><br><span class="line"></span><br></pre></td></tr></table></figure></li><li><p>之后配置基本就完成了，可以在en目录下写英文文章，流程与之前写中文文章时完全一致</p></li></ul><h2 id="部署与-CI-CD"><a href="#部署与-CI-CD" class="headerlink" title="部署与 CI&#x2F;CD"></a>部署与 CI&#x2F;CD</h2><ul><li>最后就是生成和发布环节了，这一步要注意，每次生成时，我们需要先生成中文站点，再生成英文站点并将英文内容复制到中文目录下，避免生成中文内容时将英文内容覆盖掉。具体操作示例如下：</li></ul> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo clean &amp;&amp; hexo g &amp;&amp;cd ../blog.source.en &amp;&amp; hexo clean &amp;&amp; hexo g &amp;&amp; cd ../blog.source &amp;&amp;cp -r ../blog.source.en/public/. public/en/ &amp;&amp; hexo s</span><br></pre></td></tr></table></figure><p> 命令最后一段，使用hexo s就是本地启动，使用hexo d就是发布出去，和正常使用时一样。</p><p> 这样，我们就有了一个好用的多语言站点了。之后，写中文内容就在中文目录下进行，写英文内容就在英文目录下进行, 最后执行一下上边的命令就可以了。</p><h2 id="原文地址-https-lichuanyang-top-posts-40400"><a href="#原文地址-https-lichuanyang-top-posts-40400" class="headerlink" title="原文地址: https://lichuanyang.top/posts/40400/"></a>原文地址: <a href="https://lichuanyang.top/posts/40400/">https://lichuanyang.top/posts/40400/</a></h2>]]>
    </content>
    <id>https://lichuanyang.top/posts/40400/</id>
    <link href="https://lichuanyang.top/posts/40400/"/>
    <published>2024-01-22T10:12:31.000Z</published>
    <summary>总结 Hexo 多语言方案的两种思路：文章维度切换语言和独立站点维护，对比各方案的优劣和最佳实践。</summary>
    <title>hexo多语言方案总结及最佳实践</title>
    <updated>2026-06-27T02:15:49.476Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="技术杂谈" scheme="https://lichuanyang.top/categories/%E6%8A%80%E6%9C%AF%E6%9D%82%E8%B0%88/"/>
    <category term="知乎" scheme="https://lichuanyang.top/tags/%E7%9F%A5%E4%B9%8E/"/>
    <category term="油猴" scheme="https://lichuanyang.top/tags/%E6%B2%B9%E7%8C%B4/"/>
    <category term="Tampermonkey" scheme="https://lichuanyang.top/tags/Tampermonkey/"/>
    <content>
      <![CDATA[<p>知乎作为一个高质量的文字社区，评论区的讨论往往非常有深度。然而，知乎默认的评论区时间格式是个让人头疼的小问题——它只显示”3 小时前””昨天””5 天前”这种相对时间。这在日常浏览中没问题，但如果你想追踪一场讨论的时间线、对比不同评论的先后顺序，这种模糊的时间展示就完全不够用了。</p><span id="more"></span><h2 id="痛点：相对时间的局限"><a href="#痛点：相对时间的局限" class="headerlink" title="痛点：相对时间的局限"></a>痛点：相对时间的局限</h2><p>相对时间有几个明显的缺陷：</p><ol><li><strong>信息丢失</strong>：你永远不知道”3 小时前”到底是几点几分，无法在脑海中形成精确的时间锚点</li><li><strong>跨天混乱</strong>：到了第二天，”昨天”就变成了”前天”，时间参考系一直在变</li><li><strong>讨论追踪困难</strong>：一场技术讨论中，A 回复 B 的某个论点，但 B 的评论和 A 的回复可能都显示”2 天前”，你根本看不出谁先谁后</li></ol><h2 id="脚本的来历"><a href="#脚本的来历" class="headerlink" title="脚本的来历"></a>脚本的来历</h2><p>之前在<a href="https://meta.appinn.net/t/topic/47711/18">小众软件</a>上看到一个关于知乎评论区时间展示混乱的吐槽帖，有位大佬当场写了一个油猴脚本来解决这个问题。</p><p>我用了好几个月，体验非常好。直到前段时间知乎做了一轮前端更新，脚本开始出现展示混乱的问题。于是我花了一个下午研究了一下知乎的新 DOM 结构，修复了脚本的逻辑，顺便把它发布到了 GreasyFork 上——这样后续知乎再有调整，可以直接从 GreasyFork 推送更新，不用手动安装新版本了。</p><h2 id="安装和使用"><a href="#安装和使用" class="headerlink" title="安装和使用"></a>安装和使用</h2><p>脚本已发布在 GreasyFork：</p><p><strong><a href="https://greasyfork.org/zh-CN/scripts/482871-%E7%9F%A5%E4%B9%8E%E5%A2%9E%E5%BC%BA-%E8%AF%84%E8%AE%BA%E6%97%B6%E9%97%B4%E7%B2%BE%E7%A1%AE%E5%88%B0%E7%A7%92">安装地址</a></strong></p><p>安装前提是你需要在浏览器中安装 Tampermonkey（油猴）扩展：</p><ul><li><strong>Chrome</strong>：<a href="https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo">Chrome Web Store</a></li><li><strong>Edge</strong>：Edge 扩展商店直接搜索 Tampermonkey</li><li><strong>Firefox</strong>：<a href="https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/">Firefox Add-ons</a></li></ul><p>安装 Tampermonkey 后，点击上面的 GreasyFork 链接，再点「安装此脚本」即可。脚本会自动在知乎评论区生效，无需额外配置。</p><h2 id="技术实现简述"><a href="#技术实现简述" class="headerlink" title="技术实现简述"></a>技术实现简述</h2><p>脚本的核心逻辑其实不复杂，关键步骤有三步：</p><p><strong>1. 找到时间元素</strong></p><p>知乎评论区的时间信息挂在一个 <code>span</code> 元素上，旧版使用的是 <code>title</code> 属性存储完整时间戳。知乎更新后改用了其他属性，所以需要适配新的 DOM 结构。</p><p><strong>2. 提取并格式化</strong></p><p>从 DOM 元素中读取原始的 ISO 时间字符串（如 <code>2024-01-15T14:32:08.000Z</code>），然后用 JavaScript 格式化为 <code>2024-01-15 14:32:08</code> 这样的可读格式，替换掉原来的相对时间文字。</p><p><strong>3. 处理动态加载</strong></p><p>知乎评论区是懒加载的——滚动到底部才会加载更多评论。因此脚本需要监听 DOM 变化（<code>MutationObserver</code>），当新的评论被插入页面时，自动对新评论的时间做同样的精确化处理。</p><h2 id="后续计划"><a href="#后续计划" class="headerlink" title="后续计划"></a>后续计划</h2><p>我打算把这个脚本的功能继续扩展，比如：</p><ul><li><strong>一键跳转到被引用的评论</strong>：知乎的引用格式是”回复 xxx”，但无法直接跳转</li><li><strong>高亮 OP（题主）的评论</strong>：在长讨论中快速定位题主的发言</li><li><strong>评论导出功能</strong>：把有深度的讨论导出为 Markdown</li></ul><p>大家如果有其他知乎体验上的痛点，欢迎留言提需求。如果对油猴脚本开发感兴趣，后续我也会考虑出一期油猴脚本的入门教程。</p><hr>]]>
    </content>
    <id>https://lichuanyang.top/posts/44912/</id>
    <link href="https://lichuanyang.top/posts/44912/"/>
    <published>2023-12-22T09:24:18.000Z</published>
    <summary>油猴脚本：将知乎评论区时间精确到秒，解决知乎更新后时间显示混乱的问题。</summary>
    <title>知乎增强工具-评论时间精确到秒</title>
    <updated>2026-06-27T00:26:22.560Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="云原生" scheme="https://lichuanyang.top/categories/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <category term="kubernetes" scheme="https://lichuanyang.top/tags/kubernetes/"/>
    <content>
      <![CDATA[<p>这篇教程不会关注 kubernetes 的部署、架构、实现方式等内部原理，而是完全从一个使用 kubernetes 的开发人员的视角去介绍kubernetes是什么，从而帮助了解如何更好的去利用 kubernetes 的特性。</p><span id="more"></span><h2 id="基本介绍"><a href="#基本介绍" class="headerlink" title="基本介绍"></a>基本介绍</h2><p>对于kubernetes是什么，一个比较官方的定义，Kubernetes是一个开源容器编排平台，管理大规模分布式容器化软件应用，简称为k8s.</p><p>通俗一些来讲，k8s 的核心理念是以应用为中心，向下屏蔽基础设施的差异，向上通过容器镜像实现应用的标准化，帮助开发人员在仅需关注应用本身，无需考虑部署、容灾、伸缩等运维细节的情况下，开发出大规模、可靠的分布式应用。</p><h2 id="优势"><a href="#优势" class="headerlink" title="优势"></a>优势</h2><p>如上所述，k8s的核心优势就是降低运维成本，让应用开发人员能够专注于应用本身。具体来讲，包括如下几点：</p><ul><li>便捷的部署流程和大规模的复制分发能力：应用提供标准化的容器镜像，之后的部署发布流程都可以做到自动化；应用也无需再做更多的事情就可以实现跨平台、跨区域的部署。</li><li>服务自愈能力：自动摘除不健康的节点并做恢复。</li><li>敏捷的自动伸缩：根据设定的系统负载，做快速的自动伸缩，实现系统处理能力和成本之间的平衡。</li></ul><h2 id="主要概念及实现方式介绍"><a href="#主要概念及实现方式介绍" class="headerlink" title="主要概念及实现方式介绍"></a>主要概念及实现方式介绍</h2><p>这篇kubernetes教程的剩余部分会从应用开发人员视角，介绍需要了解的k8s概念，主要包括以下几类：</p><ul><li>Pod, service, deployment等基本概念</li><li>spec+controller这套基本实现原理</li><li>k8s提供给应用的扩展点</li></ul><h2 id="spec-controller"><a href="#spec-controller" class="headerlink" title="spec+controller"></a>spec+controller</h2><p>这是k8s内部大部分逻辑的实现方式，了解这个逻辑，可以帮助开发人员更好的理解某些特定情况下k8s的行为。</p><p>如果翻看k8s的yaml配置，就会发现k8s的大部分组件中都有一个或若干个spec字段，它的含义很好理解，就是对于某个属性的期望值，比如一个服务节点数量的期望值。</p><p>controller的作用就是持续监测期望值和实际指标是否一致，不一致时就进行相应的操作进行处理，比如发现节点数不够了，就会启动对应数量的新节点。</p><p>这样，无论是上线、扩缩容、还是故障恢复，我们会发现这些逻辑都变得非常清晰，都是触发一下对容器数期望值或实际值的调整，然后让controller去增删节点就可以了。</p><p>这套机制是k8s里广泛使用的一个底层设计原则，作用是解开监控和操作逻辑之间的耦合。举个例子，我们在很多情况下都需要触发启动新的节点，包括要做扩容时、部分节点挂了时、要上线时，等等。这些完全没关联的场景如何去触发相同的操作，代码要如何保持整洁呢？k8s的做法就是用一个期望值的概念做中间层，将操作的触发时机和操作本身拆分成独立的逻辑。</p><p>了解了这个逻辑，我们可以更容易的理解k8s是怎么工作的。 比如基于HPA做自动扩容，就是根据当前的CPU使用情况，和期望的CPU使用量，决定是增加还是减少节点。</p><h2 id="Deployment"><a href="#Deployment" class="headerlink" title="Deployment"></a>Deployment</h2><p>Deployment 用于指示 k8s 如何创建和更新应用程序，我们通常理解的一个“服务”、“应用”大体上就对应在deployment这一层上。 像镜像、内存cpu使用限制、节点数、环境变量等各种配置，都是在deployment上。</p><h2 id="Pod"><a href="#Pod" class="headerlink" title="Pod"></a>Pod</h2><p>pod比较好理解，就是我们通常理解的一个节点。pod是 k8s 中最小可管理单元。</p><h2 id="Service"><a href="#Service" class="headerlink" title="Service"></a>Service</h2><p>Service是一个需要着重了解的概念，它是一个抽象层，它选择具备某些特征的 Pod（容器组）并为它们定义一个访问方式。 通常的应用场景下，deployment和service是一一对应的，但实际上deployment和service之间并没有必然的联系。</p><p>一个请求恰好可以打到某个deployment关联的所有pod上，看起来是一件非常正常的事情。而事实上，这件事情的发生不是由于这些pod通过一个deployment产生，而是因为deployment上配置了一个标签，通过这个deployment生成的pod都获得了这个标签; 同时，另外存在一个service, 设置通过这个标签选择pod, 请求进入的是通过service选择出的一批pod.</p><h2 id="探针-probe-和钩子-hook"><a href="#探针-probe-和钩子-hook" class="headerlink" title="探针(probe)和钩子(hook)"></a>探针(probe)和钩子(hook)</h2><p>k8s提供了一些用于应用扩展的接口，其中就包括probe和hook, 可以帮助应用更好的利用k8s的能力。</p><p>k8s提供了 liveness和 readiness 探针，顾名思义，liveness probe的作用是判断应用是否存活，readiness probe的作用是判断应用是否已经启动完毕。</p><p>应用可以用http接口等不同形式提供探针，k8s则负责在探测到不同状态时做不同的操作。比如说探测到liveness probe返回状态异常，则可以认为此节点已经丢失，对此节点做删除处理。</p><p>hook则支持应用在一些特定阶段插入一些行为，比如preStop hook, 运行应用关闭之前先做一些操作，可以用来做graceful shutdown.</p><h2 id="实践思考"><a href="#实践思考" class="headerlink" title="实践思考"></a>实践思考</h2><p>通过对本篇kubernetes教程上文列出的概念的了解，相信大家可以从应用开发人员的视角完全理解kubernetes是什么, 也可以大致想象出应用方使用k8s的基本思路： 构建标准镜像，暴露出必要的监控数据，剩下的事情都交给k8s来做。</p><p>在准确的数据下，k8s可以帮我们做好系统的自愈、扩缩容等操作，而我们要做的是，结合应用场景，尽量给出更加准确的监控数据。</p><p>举一些例子，比如说应用提供的liveness探针，如何能尽量确保准确的展示系统的健康状态，某个接口可以访问，某个端口可以调通，能否等价的说明这个应用是否健康。</p><p>比如说k8s以cpu、内存等指标来判断系统负载时，这些机器指标能否真实的展现出应用的真实负载，是否有可能连接池、IO等其他瓶颈会在cpu、内存等瓶颈到来之前就达到。</p><p>这些都是需要应用方结合应用的实际情况精心设计的。</p><p>此外，k8s的设计理念决定了在k8s环境下，”重启”会是一件非常稀松平常的事，像硬件故障、小概率的死循环、不健康的gc, 等不同类型的问题，都可以在k8s的自主重启下实现自愈； 自动的扩缩容扩充中，也会经常性的有节点启停。 所以说，应用需要让自己的启动流程顺畅一些，避免大量耗时或大量资源消耗。</p><h2 id="快速上手步骤"><a href="#快速上手步骤" class="headerlink" title="快速上手步骤"></a>快速上手步骤</h2><ol><li><strong>安装 kubectl</strong>：从官方下载 <code>kubectl</code> 二进制文件，配置 <code>~/.kube/config</code> 连接到 K8s 集群，运行 <code>kubectl get nodes</code> 验证连接。</li><li><strong>创建第一个 Deployment</strong>：编写 Deployment YAML，指定容器镜像、端口（<code>containerPort</code>）、CPU&#x2F;内存限制，运行 <code>kubectl apply -f deployment.yaml</code>。</li><li><strong>暴露 Service</strong>：编写 Service YAML，通过 <code>selector</code> 标签关联 Deployment 的 Pod，指定 <code>type: LoadBalancer</code> 或 <code>NodePort</code>，运行 <code>kubectl apply -f service.yaml</code>。</li><li><strong>水平伸缩</strong>：运行 <code>kubectl autoscale deployment &lt;name&gt; --cpu-percent=80 --min=2 --max=10</code> 创建 HPA，通过压测观察 Pod 自动扩缩容。</li><li><strong>配置探针与钩子</strong>：在 Deployment 中添加 <code>livenessProbe</code>、<code>readinessProbe</code> 和 <code>lifecycle.preStop</code> 配置，确保应用在 K8s 环境下具备自愈和优雅关闭能力。</li></ol>]]>
    </content>
    <id>https://lichuanyang.top/posts/55227/</id>
    <link href="https://lichuanyang.top/posts/55227/"/>
    <published>2023-01-28T11:06:17.000Z</published>
    <summary>从开发人员视角介绍 Kubernetes 是什么，不关注部署和架构，专注于如何利用 K8s 特性提升开发效率。</summary>
    <title>kubernetes是什么-实用向教程</title>
    <updated>2026-06-27T02:31:54.492Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="技术杂谈" scheme="https://lichuanyang.top/categories/%E6%8A%80%E6%9C%AF%E6%9D%82%E8%B0%88/"/>
    <category term="知乎" scheme="https://lichuanyang.top/tags/%E7%9F%A5%E4%B9%8E/"/>
    <category term="油猴" scheme="https://lichuanyang.top/tags/%E6%B2%B9%E7%8C%B4/"/>
    <category term="Tampermonkey" scheme="https://lichuanyang.top/tags/Tampermonkey/"/>
    <content>
      <![CDATA[<p>知乎是个什么样的网站，大家都比较了解。今天我们换个角度——分享一个让”上班用知乎”这件事变得更安全、更高效的小工具。</p><span id="more"></span><h2 id="摸鱼经济学：为什么是知乎？"><a href="#摸鱼经济学：为什么是知乎？" class="headerlink" title="摸鱼经济学：为什么是知乎？"></a>摸鱼经济学：为什么是知乎？</h2><p>之前我翻了一下自己知乎账号的后台数据，发现一个很有意思的现象：<strong>PC 端的流量居然占了 40% 以上</strong>。在移动互联网时代，这个比例高得不太正常。</p><p>后来从一些其他渠道也了解到，知乎周末和节假日的流量明显不如工作日。这说明什么呢？</p><p><strong>上班刷知乎的你，不是一个人。</strong></p><p>对相当一部分人来说，知乎主要的使用场景就是——上班摸鱼。而且知乎这个网站确实天然适合摸鱼：</p><ul><li><strong>以文字内容为主</strong>，不像短视频那样有声音、耗流量</li><li><strong>有大量专业性内容</strong>，被老板路过看到屏幕时，可以说”我在查技术资料”</li><li><strong>文章篇幅适中</strong>，一条时间线看几分钟，心理负担小</li></ul><h2 id="知乎摸鱼的三个痛点"><a href="#知乎摸鱼的三个痛点" class="headerlink" title="知乎摸鱼的三个痛点"></a>知乎摸鱼的三个痛点</h2><p>但知乎 PC 端有些设计，对摸鱼党不太友好：</p><p><strong>1. 图片太大</strong></p><p>知乎文章的配图经常会占满大半个屏幕。不管文章本身多正经，一张巨大的图片出现在屏幕上，旁边同事一瞥就能看到——这时候你说”我在看技术文章”就显得很没说服力。</p><p><strong>2. 标题浮窗抢眼球</strong></p><p>浏览知乎问题时，页面上方会有一个非常显眼的标题浮窗。它不仅占了一行空间，而且白底黑字特别突出，路人一眼就知道你在看知乎。</p><p><strong>3. Logo 高调</strong></p><p>知乎的 logo 统一放在页面左上角，任何时候截图或别人瞄一眼屏幕，品牌辨识度拉满。</p><h2 id="脚本解决方案"><a href="#脚本解决方案" class="headerlink" title="脚本解决方案"></a>脚本解决方案</h2><p>我写了一个油猴脚本，针对性解决了这三个问题：</p><ul><li><strong>限制图片尺寸</strong>：最大宽度设为 300px，所有文章配图变成小图，不抢眼</li><li><strong>隐藏标题浮窗</strong>：问题页面的标题浮窗直接移除</li><li><strong>去掉知乎 Logo</strong>：页面左上角的知乎 logo 一并隐藏</li></ul><p>这样一来，知乎页面看起来就像一个普通的文字阅读器——干净、低调，非常适合在办公室环境中使用。</p><h2 id="安装和使用"><a href="#安装和使用" class="headerlink" title="安装和使用"></a>安装和使用</h2><p>脚本已发布在 GreasyFork：</p><p><strong><a href="https://greasyfork.org/zh-CN/scripts/483047-%E6%91%B8%E9%B1%BC%E5%8A%A9%E6%89%8B-%E7%9F%A5%E4%B9%8E">安装地址</a></strong></p><p>安装前提是浏览器中已安装 Tampermonkey（油猴）扩展。安装脚本后自动生效，无需配置。</p><h2 id="技术实现"><a href="#技术实现" class="headerlink" title="技术实现"></a>技术实现</h2><p>脚本的实现其实非常简单，核心就是通过 <code>GM_addStyle</code> 注入自定义 CSS：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 限制图片最大宽度</span></span><br><span class="line"><span class="title function_">GM_addStyle</span>(<span class="string">&#x27;.RichContent img &#123; max-width: 300px !important; &#125;&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 隐藏标题浮窗</span></span><br><span class="line"><span class="title function_">GM_addStyle</span>(<span class="string">&#x27;.QuestionHeader &#123; display: none !important; &#125;&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 去除 Logo</span></span><br><span class="line"><span class="title function_">GM_addStyle</span>(<span class="string">&#x27;.AppHeader-logo &#123; display: none !important; &#125;&#x27;</span>);</span><br></pre></td></tr></table></figure><p>本质上就是几行 CSS——但效果出奇的好。油猴脚本的妙处也正在于此：很多时候你不用写复杂的逻辑，只靠注入 CSS 就能显著改变网页的视觉体验。</p><h2 id="和”知乎增强工具”的区别"><a href="#和”知乎增强工具”的区别" class="headerlink" title="和”知乎增强工具”的区别"></a>和”知乎增强工具”的区别</h2><p>我之前还写过一个<a href="/posts/44912/">知乎增强工具</a>，那个脚本侧重于<strong>信息增强</strong>——把评论区时间精确到秒。而这个脚本侧重于<strong>视觉优化</strong>——让知乎在办公场景下看起来更像一个正经的阅读工具。</p><p>两个脚本可以同时安装，互不冲突。</p><p>大家如果对知乎使用有什么其他痛点，欢迎留言提。也欢迎去看看我另一个<a href="/posts/44912/">知乎增强工具</a>，评论区时间精确到秒，很适合追踪技术讨论的时间线。</p><hr>]]>
    </content>
    <id>https://lichuanyang.top/posts/23393/</id>
    <link href="https://lichuanyang.top/posts/23393/"/>
    <published>2022-12-28T10:29:01.000Z</published>
    <summary>分享一个优化知乎 PC 端使用体验的油猴脚本，让上班摸鱼更高效。</summary>
    <title>怎么更科学的用知乎摸鱼</title>
    <updated>2026-06-27T00:26:38.323Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="技术杂谈" scheme="https://lichuanyang.top/categories/%E6%8A%80%E6%9C%AF%E6%9D%82%E8%B0%88/"/>
    <category term="个人成长" scheme="https://lichuanyang.top/tags/%E4%B8%AA%E4%BA%BA%E6%88%90%E9%95%BF/"/>
    <category term="读书感悟" scheme="https://lichuanyang.top/tags/%E8%AF%BB%E4%B9%A6%E6%84%9F%E6%82%9F/"/>
    <category term="系统之美" scheme="https://lichuanyang.top/tags/%E7%B3%BB%E7%BB%9F%E4%B9%8B%E7%BE%8E/"/>
    <category term="逻辑思维" scheme="https://lichuanyang.top/tags/%E9%80%BB%E8%BE%91%E6%80%9D%E7%BB%B4/"/>
    <content>
      <![CDATA[<p>这本书的作用主要是教大家面对现实中的复杂问题，如何更有效的去思考。书本身偏重系统的基本概念，这些概念其实都比较好理解。另外，作者举了非常多的例子。初读这本书时，会感觉理论和实际例子有那么些割裂。 但是后来逐渐感觉到，这个话题确实是没那么容易讲的通俗易懂的，需要读者自己付出更多的思考，才能有实质性的收获。而作者举的这些例子，就是非常好的引导读者去思考的引子。通过跟着作者一起思考这些例子，我们就可以帮助自己更好的理解书中的概念和逻辑。</p><span id="more"></span><p>所以，这篇笔记中，我主要其实就是比较机械化的把书中的一些核心概念提炼出来，同时会尝试举一些身边的例子。 而若想更好的理解书中每一部分，还是推荐去读一遍书，并且跟着作者的思路一起去好好思考那些例子的逻辑。</p><h2 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h2><p>系统是一组相互连接的事物，在一定时间内，以特定的行为模式相互影响。</p><p>任何一个系统都包括三种构成要件：要素、连接、功能或目标。</p><p>存量是所有系统的基础，比如浴缸中的水、人口数量、书店中的书、树木的体积、银行里的钱，等等。但是，存量不一定非得是物质的，你的自信、在朋友圈中的良好口碑，或者对世界的美好希冀等，都可以是存量。</p><p>存量会随着时间的变化而不断改变，使其发生变化的就是“流量”</p><p>绘制存量-流量图，是理解系统的基础方式。</p><p>当某一个存量的变化影响到与其相关的流入量或流出量时，反馈回路就形成了。</p><p>调节回路与增强回路是两种很常见的反馈回路，比较好理解，就是字面意思。调节回路趋向于将系统存量维持稳定，增强回路则是会不断放大、增强原有的发展态势。</p><h2 id="常见的系统"><a href="#常见的系统" class="headerlink" title="常见的系统"></a>常见的系统</h2><p>接下来通过一些常见的系统模型，来帮助大家更好的理解系统是什么。</p><h2 id="单存量系统"><a href="#单存量系统" class="headerlink" title="单存量系统"></a>单存量系统</h2><ul><li><p>系统1.1:  一个存量、两个相互制衡的调节回路的系统， 比如温度调节器，通过一个升温调节回路、一个降温调节回路，控制存量（温度）稳定。</p></li><li><p>系统1.2：一个存量、一个增强回路以及一个调节回路的系统, 比如人口，新出生是增强回路，死亡是调节回路。</p></li><li><p>系统1.3：含有时间延迟的系统，比如库存，相比简单的温度调节器，主要区别在于调节回路上会存在延时。</p></li></ul><h2 id="双存量系统"><a href="#双存量系统" class="headerlink" title="双存量系统"></a>双存量系统</h2><ul><li><p>系统2.1：一个可再生性存量受到另外一个不可再生性存量约束的系统， 比如开采石油等不可再生资源，就会存在资本和资源两个存量，其中存在一个增强回路，即开采资源过程中会获得利润，获得的利润能够增加资本，从而扩大产能，加大开采量。同时，由于石油不可再生，开采难度会越来越大，也就形成了一个调节回路。</p></li><li><p>系统2.2：有两个可再生性存量的系统，比如渔业，和石油比较类似，只是鱼是可再生资源，会导致系统的发展形态会和上一类系统有所区别。</p></li></ul><h2 id="系统的三大特征"><a href="#系统的三大特征" class="headerlink" title="系统的三大特征"></a>系统的三大特征</h2><ul><li><p>适应力。 系统之所以会有适应力，是因为系统内部结构存在很多相互影响的反馈回路，正是这些回路相互支撑，即使在系统遭受巨大的扰动时，仍然能够以多种不同的方式使系统恢复至原有状态。比如人体就是一个适应力非常强的系统。</p></li><li><p>自组织。 系统所具备的使其自身结构更为复杂化的能力，被称为“自组织”。自组织对于系统来说，意味着系统可以自己去“进化”，从而完成更大的目标，或实现更复杂的功能。像我之前的老板，经常跟我说我们的目标是去建立一个具有“自组织”能力的团队。只有这样，团队才能跟着业务发展一起进步。</p></li><li><p>层次性。 在新结构不断产生、复杂性逐渐增加的过程中，自组织系统经常生成一定的层级或层次性。一个大的系统中包含很多子系统，一些子系统又可以分解成更多、更小的子系统。如果各个子系统基本上能够维系自身，发挥一定的功能，并服务于一个更大系统的需求，而更大的系统负责调节并强化各个子系统的运作，那么就可以产生并保持相对稳定的、有适应力和效率的结构。</p></li></ul><h2 id="系统的障碍"><a href="#系统的障碍" class="headerlink" title="系统的障碍"></a>系统的障碍</h2><p>我们认为自己所知道的关于这个世界的任何东西都只是一个模型。我们的模型通常是与现实世界高度一致的。这就是我们为什么会成为这个星球上最为成功的一个物种的原因。 我们的模型仍远远达不到能完整地描绘世界的程度。这就是我们为什么经常会犯错误、会感到出乎意料的原因。</p><p>所以，我们要看看从系统的角度可以发现哪些问题，帮助我们尽量的少犯错。作者主要整理了以下6点：</p><ul><li>系统结构是行为的根源，而系统行为体现为随时间而发生的一系列事件；而人们容易被表象所迷惑</li><li>在非线性的世界里，不要用线性的思维模式</li><li>需要恰当地划定系统边界</li><li>要能看清各种限制因素</li><li>系统中无所不在的时间延迟</li><li>人们只有有限理性, 导致决策往往并非整体最优</li></ul><h2 id="系统的陷阱与对策"><a href="#系统的陷阱与对策" class="headerlink" title="系统的陷阱与对策"></a>系统的陷阱与对策</h2><p>这一章则是一些更具体的问题，以及针对这些问题有哪些可能的对策。</p><h2 id="政策阻力；治标不治本"><a href="#政策阻力；治标不治本" class="headerlink" title="政策阻力；治标不治本"></a>政策阻力；治标不治本</h2><p>有一些长期持续的行为模式，可能并不符合人们的预期，往往被视为一个问题。尽管人们发明了各种技术、采取了多项政策措施，试图去“修复”它们，但系统好像很顽固，每年都产生相同的行为。这是一种常见的系统陷阱，人们习惯称之为“治标不治本”或“政策阻力”， 比如毒品泛滥、失业等。</p><p>“政策阻力”来自于系统中各个参与者的有限理性，每一个参与者都有自己的目标。当各个子系统的目标不同或不一致时，就会产生变革的阻力。应对“政策阻力”最有效的方式是，设法将各个子系统的目标协调一致，通常是设立一个更大的总体目标，让所有参与者突破各自的有限理性。</p><h2 id="公地悲剧"><a href="#公地悲剧" class="headerlink" title="公地悲剧"></a>公地悲剧</h2><p>对于人们共同分享的、有限的资源，很容易出现开发（或消耗）逐步升级或增长的态势。“公地悲剧”之所以产生，一个重要原因是资源的消耗与资源的使用者数量增长之间的反馈缺失了，或者时间延迟太长。</p><p>防止“公地悲剧”有以下三种方式：一是教育，帮助人们更清晰的看到后果；二是将资源私有化；三是采取一些配额制之类的管制措施。</p><h2 id="目标侵蚀"><a href="#目标侵蚀" class="headerlink" title="目标侵蚀"></a>目标侵蚀</h2><p>绩效标准受过去绩效的影响，尤其是当人们对过去的绩效评价偏负面，也就是过于关注坏消息时，将启动一个恶性循环，使得目标和系统的绩效水平不断下滑。通俗的说法，就是温水煮青蛙，形势在不知不觉中慢慢变差。</p><p>对策是保持一个绝对的绩效标准。更好的状况是，将绩效标准设定为过去的最佳水平，从而不断提高自己的目标，并以此激励自己。</p><h2 id="竞争升级"><a href="#竞争升级" class="headerlink" title="竞争升级"></a>竞争升级</h2><p>“以眼还眼，以牙还牙”。每一个参与者期望的系统状态都是相对于其他参与者而言的，并试图超越对方，领先一步，连并驾齐驱都不行；而且每个参与者都有高估对方的敌意、夸大对方实力的倾向。竞争升级的系统结构是一个增强回路，它是以指数级方式发展起来的，一旦超过某个限度，其使竞争激化的速度会超出绝大多数人的想象。</p><p>对策一种依托于一方主动让步；更优雅的方式则是期望双方达成协定。</p><h2 id="富者愈富"><a href="#富者愈富" class="headerlink" title="富者愈富"></a>富者愈富</h2><p>利用积累起来的财富、权力、特殊渠道或内部信息，可以创造出更多的财富、权力、渠道以及信息。这些都是另外一个被称为“富者愈富”的基模的例子。</p><p>对策：多元化，即允许在竞争中落败的一方可以退出，开启另外一场新的博弈；反垄断法，即严格限制赢家所占有的最大份额比例；修正竞赛规则，限制最强的一些参与者的优势，或对处于劣势的参与者给予一些特别关照，增强他们的竞争力（例如施舍、馈赠、税赋调节、转移支付等）；对获胜者给予多样化的奖励，避免他们在下一轮竞争中争夺同一有限的资源，或产生偏差。</p><h2 id="转嫁负担"><a href="#转嫁负担" class="headerlink" title="转嫁负担"></a>转嫁负担</h2><p>当面对一个系统性问题时，如果采用的解决方案根本无助于解决潜在的根本问题，只是缓解（或掩饰）了问题的症状时，就会产生转嫁负担、依赖性和上瘾的状况。</p><p>应对这一陷阱最好的办法是提前预防，防止跌入陷阱。一定要意识到只是缓解症状或掩饰信号的政策或做法，都不能真正地解决问题。</p><h2 id="规避规则"><a href="#规避规则" class="headerlink" title="规避规则"></a>规避规则</h2><p>任何规则都可能会有漏洞或例外情况，因而也存在规避规则的机会。也就是说，虽然一些行为在表面上遵守或未违背规则，但实质上却不符合规则的本意，甚至扭曲了系统。</p><p>对策是设计或重新设计规则，从规避规则的行为中获得创造性反馈，使其发挥积极的作用，实现规则的本来目的。</p><h2 id="目标错位"><a href="#目标错位" class="headerlink" title="目标错位"></a>目标错位</h2><p>系统行为对于反馈回路的目标特别敏感。如果目标定义不准确或不完整，即使系统忠实地执行了所有运作规则，其产出的结果却不一定是人们真正想要的。</p><p>对策就是恰当地设定目标及指标，以反映系统真正的福利。</p><h2 id="系统的变革方式，能够干预系统的杠杆点"><a href="#系统的变革方式，能够干预系统的杠杆点" class="headerlink" title="系统的变革方式，能够干预系统的杠杆点"></a>系统的变革方式，能够干预系统的杠杆点</h2><p>这一部分则是一些我们能够去干预系统的方式，按照有效性由低到高去排列。这一章里的很多理论，相信大家或多或少在其他的地方也见到过一些。很多优秀的人，即便没读过这本书，很多时候其实也是这么思考的。而系统之美这本书，能够帮助我们更加体系化的了解为什么要这么做。</p><p>12：数字，通过各种流量的数值来调节系统。像王者荣耀处理平衡性的策划，基本上很大一部分精力就在做这事，哪个英雄机制过强了，就狠狠的削一把数值，哪个英雄不大行，就加数值，让他站撸也有很高的强度。这种方式实际上是效力很低的一种方式，它无法改变系统基本的结构。但是大多数人90%的注意力都会集中在参数上。</p><p>11：缓冲器。通过提高缓冲器的容量，我们通常可以使系统稳定下来。但是，如果缓冲器过大，系统也将变得缺乏弹性，它对于变化的反应速度将过于缓慢。同时，要建立、扩大或维护某些缓冲器的容量，也需花费巨大的时间和资金，例如建设水库或仓库等。为此，一些企业发明了“零库存”的“及时生产”模式。在这些企业看来，与耗费巨资维持固定的库存相比，偶尔的波动或缺货造成的损失并不是很大。</p><p>10.存量—流量结构：实体系统及其交叉节点。这一点主要关乎于系统的整体设计。恰当的杠杆点，需要从一开始就被设计好。一旦实体的结构建立起来了，要想找到杠杆点，就需要理解系统的限制和瓶颈，在尽可能发挥它们的最大效率的同时，避免出现较大的波动或扩张，超出其承受能力。而如果系统已经运行起来，想再去调整其中的关键节点，就会变的很困难。软件工程中著名的“防腐层”概念，可以一定程度上优化这个问题，即通过一些预设的中间层，减少调整关键节点的成本。</p><p>9.时间延迟：系统对变化做出反应的速度。时间延迟是一个高杠杆点，但事实上，时间延迟通常不是很容易改变的。很多事物的发展有其内在规律，该花多长时间就得花多少时间。你不可能一夜之间积累起一大笔资本，孩子也不可能在一夜之间长大，拔苗助长也无法加快庄稼的生长。但如果有办法改变时间延迟，往往可以取得显著效果。比如近期疫情防控中，为什么要经常进行大规模的核酸检测，就是通过这种方式降低病毒从被传播到被发现这一段的时间延迟。</p><p>8.调节回路：试图修正外界影响的反馈力量。这一点很好理解，我们可以通过加调节回路的方式，加强对系统的控制。kubernetes里的controller可以认为就是调节回路的实现，通过不同的controller, 就可以将不同组件的值，也就是存量，控制在期望值上。</p><p>7.增强回路：驱动收益增长的反馈力量。和调节回路类似，只是目标不同。</p><p>6.信息流：谁能获得信息的结构。相当于一个新的回路，让人们在之前得不到信息的地方可以获得反馈。比如说资本主义罪恶的加班排名，就是将同事的加班信息告诉你，让你自然产生压力，从而促进加班。</p><p>5.系统规则：激励、惩罚和限制条件。这一部分，实际上就是将系统的范围、边界和自由度定义清楚。</p><p>4.自组织：系统结构增加、变化或进化的力量。接文章开头的话题，要建设一个自组织的团队，是要做很多事情的，比如提升团队中每个成员的能力，让大家都能有决策能力；加强团队内信息的透明度；等等。而这样的团队一旦能建成，不光产出会有质的提升，团队中成员的工作成就感和幸福度也会大幅度提升。</p><p>3.目标：系统的目的或功能。系统中的某个参与者可以清晰地设定、阐述、重复、支持并坚持新的目标，从而引导了系统的变革。这就是为什么这些年OKR这么流行的原因。通过明确合理的OKR, 指导大家更合理的决策和行动。</p><p>2.社会范式：决定系统之所以为系统的心智模式。一些社会公认的观念，一些潜在的基本假设以及关于社会现实本质的普遍看法，构成了社会的范式（paradigm），或者是一整套世界观，它们是人们普遍相信的、关于世界是如何运作的一系列基本假设、规则或信念。这些信念都是隐含的，因为在一个社会中，几乎每一个人都已经知道它们，因而无须特别申明。这些范式自然会对系统运行产生极其重大的影响，当然，要改变这些范式，也会比改变其他东西更困难。</p><p>1.超越范式。与改变范式相比，在更高的层次上，还有另外一个杠杆点，那就是使自己摆脱任何范式的控制。这一点其实有那么一点玄学的意思了，我们在这里就不多展开。核心点其实就是我们要有意识的去跳出当前系统。</p><h2 id="系统的生存法则，与系统共舞"><a href="#系统的生存法则，与系统共舞" class="headerlink" title="系统的生存法则，与系统共舞"></a>系统的生存法则，与系统共舞</h2><p>在工业社会长大的人，若热衷于系统思考，很可能会犯一个严重的错误。他们可能会假定，通过系统分析，可以认清系统中的相互联系以及复杂纠葛，借助计算机的威力，最后找到预测和控制系统的钥匙。不幸的是，这是错误的观念，其根源在于工业时代根深蒂固的心智模式，即相信存在一把预测和控制的钥匙。但实际上，要做到这一点是完全不现实的，我们需要认识到并愿意放弃控制的错觉，需要换一种截然不同的方式。这样，我们仍然可以有很大的作为空间。这种方式就是与系统共舞。我们无法控制系统，但是可以在系统中生存的更好。</p><p>接下来就是和系统共舞的一些法则，这一部分相当于一些小tips, 也都比较好理解，我们就简单列举一下：</p><ul><li>跟上系统的节拍：要观察系统是如何运作的，才能跟上它。我们需要多关注事实和数据，才不至于被别人的理论带着走。</li><li>把你的心智模式展现在阳光下，画出系统结构图，强迫自己把自己内心隐藏的各种假设投射出来，并精准地表述它们。</li><li>相信、尊重并分享信息</li><li>谨慎地使用语言，并用系统的概念去丰富语言</li><li>关注重要的，而不只是容易衡量的</li><li>为反馈系统制定带有反馈功能的政策。对于动态的、自我调节的反馈系统，不能用静止的、刚性的政策来进行管制，好的政策必须能够根据系统状态的变化及时地灵活调整。</li><li>追求整体利益</li><li>聆听系统的智慧</li><li>界定系统的职责</li><li>保持谦逊，做一名学习者</li><li>庆祝复杂性</li><li>扩展时间的范围</li><li>打破各种清规戒律</li><li>扩大关切的范围</li><li>不要降低“善”的标准</li></ul><h2 id="原文地址-https-lichuanyang-top-posts-53791"><a href="#原文地址-https-lichuanyang-top-posts-53791" class="headerlink" title="原文地址: https://lichuanyang.top/posts/53791/"></a>原文地址: <a href="https://lichuanyang.top/posts/53791/">https://lichuanyang.top/posts/53791/</a></h2>]]>
    </content>
    <id>https://lichuanyang.top/posts/53791/</id>
    <link href="https://lichuanyang.top/posts/53791/"/>
    <published>2022-05-16T10:34:01.000Z</published>
    <summary>《系统之美》读书笔记，学习面对复杂问题的系统性思考方法，建立正确的思维框架。</summary>
    <title>读书笔记《系统之美》,如何面对现实中的复杂问题</title>
    <updated>2026-06-27T02:15:49.476Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="架构设计" scheme="https://lichuanyang.top/categories/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/"/>
    <category term="分布式系统" scheme="https://lichuanyang.top/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/"/>
    <category term="分布式系统设计" scheme="https://lichuanyang.top/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1/"/>
    <content>
      <![CDATA[<p>之前翻译过一篇关于分布式系统的文章 <a href="https://lichuanyang.top/posts/3914/">https://lichuanyang.top/posts/3914/</a> ，在各个平台都取得了不错的反响。因此，最近又重新整理了一下相关的知识，结合一些这一年多里新的理解，重新整理了下这篇文章。</p><span id="more"></span><p>首先我们需要明确本文要讨论的分布式系统是什么，简单的说，就是满足多节点和有状态这两个条件即可。多节点很好理解，有状态则是指这个系统要维护一些数据，不然的话，其实我们无脑的水平扩容就没有任何问题，也就不存在分布式系统的问题了。</p><p>常见的分布式系统， 无论是mysql, cassandra, hbase这些数据库，还是rocketmq, kafka, pulsar这样的消息队列，还是zookeeper之类的基础设施，其实都满足这两个条件。</p><p>这些分布式系统的实现通常来说主要需要关注两个方面：一是自己本身功能的实现，二是在分布式环境下保持良好的性能与稳定性；即便是两个功能完全不一样的系统，其对第二类问题的处理方式也会有很多相似之处。本文的关注重点也即在对第二类问题的处理上。</p><h2 id="分布式系统的核心挑战"><a href="#分布式系统的核心挑战" class="headerlink" title="分布式系统的核心挑战"></a>分布式系统的核心挑战</h2><p>接下来，我们列举一下分布式系统都有哪些常见目标，包括而不限于：</p><ul><li>大量普通的服务器通过网络互联，对外作为整体提供服务；</li><li>随着集群规模增长，系统整体性能表现为线性增长；</li><li>能够自动容错，故障节点自动迁移，不同节点的数据要能保持一致性；</li></ul><p>要达成这些目标，又有哪些挑战呢？大概有以下这些：</p><ol><li><p>进程崩溃： 原因很多，包括硬件故障、软件故障、正常的例行维护等等，在云环境下会有一些更加复杂的原因；<br>进程崩溃导致的最大问题就是会丢数。出于性能的考虑，很多情况下我们不会进行同步的写磁盘，而是会将数据暂时放在内存的缓冲区，再定期刷入磁盘。而在进程崩溃的时候，内存缓冲区中的数据显然会丢失。</p></li><li><p>网络延迟和中断： 节点的通信变到很慢时，一个节点如何确认另一个节点是否正常； </p></li><li><p>网络分区： 集群中节点分裂成两个子集，子集内通信正常，子集之间断开（脑裂），这时候集群要如何提供服务。</p></li></ol><p> 这里插一个彩蛋，在CAP理论的前提下，现实中的系统通常只有两种模式：放弃高可用的CP模式和放弃强一致性的AP模式。为什么没有一种放弃分区容忍性的CA模式？就是因为我们无法假设网络通信一定正常，而一旦接受了集群变成两个分区，再想合并回来就不现实了。</p><ol start="4"><li><p>进程暂停：比如full gc之类的原因导致进程出现短暂的不可用后又迅速恢复，不可用期间集群有可能已经做出了相关的反应，当这个节点再恢复的时候如何维持状态的一致性。</p></li><li><p>时钟不同步和消息乱序：集群内不同节点的操作，我们希望它的顺序是明确的；不同节点之间的时钟不同步，会导致我们无法利用时间戳确保这件事。而消息的乱序就给分布式系统的处理带来了更大的难度。</p></li></ol><p>下面，我们就依次介绍，针对这些问题，都有什么处理方式。</p><h2 id="分区与复制"><a href="#分区与复制" class="headerlink" title="分区与复制"></a>分区与复制</h2><p>对于进程崩溃的问题，首先要明确的是，单纯实现进程崩溃下不丢数，没有任何难度，重要的是怎么在保证系统性能的前提下达到这个目标。</p><p>首先要介绍的就是write-ahead log这种模式，服务器将每个状态更改作为命令存储在硬盘上的仅附加(append-only)文件中。 append操作由于是顺序的磁盘写，通常是非常快的，因此可以在不影响性能的情况下完成。  在服务器故障恢复时，可以重播日志以再次建立内存状态。</p><p>其关键思路是先以一个小成本的方式写入一份持久化数据，不一定局限于顺序写磁盘，此时就可以向client端确认数据已经写入，不用阻塞client端的其他行为。server端再异步的去进行接下来高消耗的操作。</p><p>典型场景及变体：mysql redo log;  redis aof; kafka本身 ;业务开发中的常见行为:对于耗时较高的行为，先写一条数据库记录，表示这个任务将被执行，之后再异步进行实际的任务执行；</p><p>write-ahead log会附带一个小问题，日志会越攒越多，要如何处理其自身的存储问题呢？有两个很自然的思路： 拆分和清理。</p><p>拆分即将大日志分割成多个小日志，由于WAL的逻辑一般都很简单，所以其拆分也不复杂，比一般的分库分表要容易很多。这种模式叫做 Segmented Log,  典型的实现场景就是kafka的分区。</p><p>关于清理，有一种模式叫做low-water mark(低水位模式)， 低水位，即对于日志中已经可以被清理的部分的标记。标记的方式可以基于其数据情况(redolog), 也可以基于预设的保存时间（kafka)，也可以做一些更精细的清理和压缩(aof)。 </p><h2 id="一致性协议"><a href="#一致性协议" class="headerlink" title="一致性协议"></a>一致性协议</h2><p>再来看网络环境下的问题，首先使用一个非常简单的心跳(HeartBeat)模式，就可以解决节点间状态同步的问题。一段时间内没有收到心跳，就将这个节点视为已宕机处理。</p><p>而关于脑裂的问题，通常会使用大多数(Quorum)这种模式，即要求集群内存活的节点数要能达到一个Quorum值，（通常集群内有2f+1个节点时，最多只能容忍f个节点下线，即quorum值为f+1），才可以对外提供服务。我们看很多分布式系统的实现时，比如rocketmq, zookeeper, 都会发现需要满足至少存活多少个节点才能正常工作，正是Quorum模式的要求。</p><p>Quorum解决了数据持久性的问题，也就是说，成功写入的数据，在节点失败的情况下，是不会丢失的。但是单靠这个，无法提供强一致性的保证，因为不同节点上的数据是会存在时间差的，client连接到不同节点上时，会产生不同的结果。可以通过主从模式(Leader and Followers) 解决一致性的问题。其中一个节点被选举为主节点，负责协调节点间数据的复制，以及决定哪些数据对client是可见的。</p><p>高水位（High-Water Mark）模式是用来决定哪些数据对client可见的模式。一般来说，在quorum个从节点上完成数据写入后，这条数据就可以标记为对client可见。完成复制的这条线，就是高水位。</p><p>主从模式的应用范围实在太广，这里就不做举例了。分布式选举算法很多，比如bully, ZAB, paxos, raft等。其中，paxos无论是理解还是实现难度都太大，bully在节点频繁上下线时会频繁的进行选举，而raft可以说是一种稳定性、实现难度等各方面相对均衡，使用也最广泛的一种分布式选举算法。像elastic search, 在7.0版本里，将选主算法由bully更换为raft；kafka 2.8里，也由利用zk的ZAB协议，修改为raft.</p><p>到这儿，我们先总结一下。实际上，一个对分布式系统的操作，基本上就可以概括为下边这么几步：</p><ol><li>写主节点的Write-Ahead Log;</li><li>写1个从节点的 WAL</li><li>写主节点数据；</li><li>写1个从节点数据</li><li>写quorum个子节点WAL</li><li>写quorum个子节点数据</li></ol><p>其中，2-5步之间的顺序不是固定的。分布式系统平衡性能和稳定性的最重要方式，实质上就是决定这几步操作的顺序，以及决定在哪个时间点向client端返回操作成功的确认信息。例如，mysql的同步复制、异步复制、半同步复制，就是典型的这种区别的场景。</p><h2 id="故障检测与恢复"><a href="#故障检测与恢复" class="headerlink" title="故障检测与恢复"></a>故障检测与恢复</h2><p>关于进程暂停，造成的主要的问题场景是这样的：假如主节点暂停了，暂停期间如果选出了新的主节点，然后原来的主节点恢复了，这时候该怎么办。这时候，使用Generation Clock这种模式就可以，简单的说，就是给主节点设置一个单调递增的代编号，表示是第几代主节点。像raft里的term， ZAB里的epoch这些概念，都是generation clock这个思路的实现。</p><p>再看看时钟不同步问题，在分布式环境下，不同节点的时钟之间必然是会存在区别的。在主从模式下，这种问题其实已经被最大限度的减少了。很多系统会选择将所有操作都在主节点上进行，主从复制也是采取复制日志再重放日志的形式。这样，一般情况下，就不用考虑时钟的事情了。唯一可能出问题的时机就是主从切换的过程中，原主节点和新主节点给出的数就有可能存在乱序。</p><p>一种解决时钟不同步问题的方案就是搞一个专门的服务用来做同步，这种服务叫做NTP服务。但这种方案也不是完美的，毕竟涉及到网络操作，所以难免产生一些误差。所以想依靠NTP解决时钟不同步问题时，系统设计上需要能够容忍一些非常微弱的误差。</p><p>其实，除了强行去把时钟对齐之外，还有一些简单一些的思路可以考虑。首先思考一个问题，我们真的需要保证消息绝对的按照真实世界物理时间去排列吗？其实不是的，我们需要的只是 一个自洽、可重复的确定消息顺序的方式，让各个节点对于消息的顺序能够达成一致即可。也就是说，消息不一定按照物理上的先后排列，但是不同节点排出来的应该一样。</p><p>有一种叫Lamport Clock的技术就能达到这个目标。它的逻辑很简单，如图所示 </p><p><img src="/img/lamport.png" alt="lamport stamp"></p><p>就是本机上的操作会导致本机上的stamp加1，发生网络通信时，比如C接收到B的数据时，会比较自己当前的stamp, 和B的stamp+1, 选出较大的值，变成自己当前的戳。 这样一个简单的操作，就可以保证任何有相关性的两个操作（包括出现在同一节点、有通信两种情况）的顺序在不同节点之间看来是一致的。</p><h2 id="设计原则总结"><a href="#设计原则总结" class="headerlink" title="设计原则总结"></a>设计原则总结</h2><p>另外，还有一些相对简单些的事情，也是分布式系统设计中经常要考虑的，比如怎么让数据均匀的分布在各个节点上。对于这个问题，我们可能需要根据业务情况去找一个合适的分片key, 也可能需要找到一个合适的hash算法。另外，也有一致性哈希这种技术，让我们控制起来更自如。</p><p>分布式系统设计中还需要重点考虑的一块就是如何衡量系统性能，指标包括性能（延迟、吞吐量）、可用性、一致性、可扩展性等等，这些说起来都比较好理解，但要是想更完善的去衡量，尤其是想更方便的去观测这些指标的话，也是一个很大的话题。</p><p>原文地址: <a href="https://lichuanyang.top/posts/45718/">https://lichuanyang.top/posts/45718/</a></p><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h3 id="Q-分布式系统设计中最重要的是学什么？有哪些核心模式？"><a href="#Q-分布式系统设计中最重要的是学什么？有哪些核心模式？" class="headerlink" title="Q: 分布式系统设计中最重要的是学什么？有哪些核心模式？"></a>Q: 分布式系统设计中最重要的是学什么？有哪些核心模式？</h3><p>最核心的不是某个具体工具（MySQL、Kafka、ZooKeeper 怎么用），而是<strong>跨系统的通用设计模式</strong>。文中总结了六大核心模式：1) <strong>Write-Ahead Log</strong>：先顺序写日志再异步处理，解决性能与持久化的矛盾；2) <strong>Segmented Log + Low-Water Mark</strong>：日志拆分与清理；3) <strong>HeartBeat</strong>：节点间状态同步；4) <strong>Quorum</strong>：大多数投票，解决脑裂和持久性；5) <strong>Leader and Followers + High-Water Mark</strong>：主从复制与数据可见性控制；6) <strong>Generation Clock</strong>：代编号，解决进程暂停后的脑裂。理解了这些模式，再看任何分布式系统都能快速把握其设计思路。</p><h3 id="Q-CAP-理论的实践意义是什么？为什么没有-CA-系统？"><a href="#Q-CAP-理论的实践意义是什么？为什么没有-CA-系统？" class="headerlink" title="Q: CAP 理论的实践意义是什么？为什么没有 CA 系统？"></a>Q: CAP 理论的实践意义是什么？为什么没有 CA 系统？</h3><p>CAP 说一致性（C）、可用性（A）、分区容忍性（P）三者最多同时满足两个。实践中的关键是：<strong>网络分区在分布式系统中是不可避免的</strong>（网络延迟、丢包、断连随时可能发生），因此 P 是必须接受的。一旦接受了 P，就只能选择 CP（放弃可用性，保证一致）或 AP（放弃一致性，保证可用）。不存在 CA 系统是因为：CA 系统假设网络永远正常——实际环境中一旦发生分区，集群无法合并回来，CA 假设就崩溃了。现实中的数据库和中间件无一例外都是 CP 或 AP 的设计选择。</p><h3 id="Q-分布式系统初学者从哪里开始？有没有学习路线？"><a href="#Q-分布式系统初学者从哪里开始？有没有学习路线？" class="headerlink" title="Q: 分布式系统初学者从哪里开始？有没有学习路线？"></a>Q: 分布式系统初学者从哪里开始？有没有学习路线？</h3><p>从文中可以理出一条学习路线：1) <strong>先理解 what 和 why</strong>：分布式系统的定义（多节点 + 有状态）和核心挑战（进程崩溃、网络延迟、脑裂、进程暂停、时钟不同步）；2) <strong>掌握六大核心模式</strong>（WAL、Quorum、Leader-Follower 等），理解每种模式解决哪个挑战；3) <strong>找一个具体系统深挖</strong>：比如读 Raft 论文理解 Leadership 选举和日志复制，或深入 Kafka 的设计理解 WAL + Segmented Log 的实际落地；4) <strong>举一反三</strong>：横向对比 MySQL 半同步复制、Redis AOF、RocketMQ 的 Dledger 等，找出不同系统对同一挑战的不同解决思路。关键是<strong>建立知识体系</strong>而非逐个学工具——学透一个，其余的触类旁通。</p><h3 id="Q-在”性能”和”一致性”之间如何做权衡决策？"><a href="#Q-在”性能”和”一致性”之间如何做权衡决策？" class="headerlink" title="Q: 在”性能”和”一致性”之间如何做权衡决策？"></a>Q: 在”性能”和”一致性”之间如何做权衡决策？</h3><h2 id="文中给出了一个实用的操作框架：通过控制主从复制各步骤的顺序和确认时机来调节。一个分布式写操作可分为六步：写主-WAL-→-写从-WAL-→-写主数据-→-写从数据-→-写-Quorum-个从-WAL-→-写-Quorum-个从数据。越早向客户端返回成功，性能越好但一致性越弱。具体策略：1-完成第-1-步就返回-异步复制（最高性能，可能丢数据）；2-完成第-1-第-3-步再返回-半同步复制（折中）；3-完成-Quorum-级别复制再返回-强一致性（最低性能）。MySQL-的三种复制模式就是这种权衡的典型体现——没有银弹，根据业务对数据丢失的容忍度选择。"><a href="#文中给出了一个实用的操作框架：通过控制主从复制各步骤的顺序和确认时机来调节。一个分布式写操作可分为六步：写主-WAL-→-写从-WAL-→-写主数据-→-写从数据-→-写-Quorum-个从-WAL-→-写-Quorum-个从数据。越早向客户端返回成功，性能越好但一致性越弱。具体策略：1-完成第-1-步就返回-异步复制（最高性能，可能丢数据）；2-完成第-1-第-3-步再返回-半同步复制（折中）；3-完成-Quorum-级别复制再返回-强一致性（最低性能）。MySQL-的三种复制模式就是这种权衡的典型体现——没有银弹，根据业务对数据丢失的容忍度选择。" class="headerlink" title="文中给出了一个实用的操作框架：通过控制主从复制各步骤的顺序和确认时机来调节。一个分布式写操作可分为六步：写主 WAL → 写从 WAL → 写主数据 → 写从数据 → 写 Quorum 个从 WAL → 写 Quorum 个从数据。越早向客户端返回成功，性能越好但一致性越弱。具体策略：1) 完成第 1 步就返回 &#x3D; 异步复制（最高性能，可能丢数据）；2) 完成第 1+第 3 步再返回 &#x3D; 半同步复制（折中）；3) 完成 Quorum 级别复制再返回 &#x3D; 强一致性（最低性能）。MySQL 的三种复制模式就是这种权衡的典型体现——没有银弹，根据业务对数据丢失的容忍度选择。"></a>文中给出了一个实用的操作框架：<strong>通过控制主从复制各步骤的顺序和确认时机来调节</strong>。一个分布式写操作可分为六步：写主 WAL → 写从 WAL → 写主数据 → 写从数据 → 写 Quorum 个从 WAL → 写 Quorum 个从数据。<strong>越早向客户端返回成功，性能越好但一致性越弱</strong>。具体策略：1) 完成第 1 步就返回 &#x3D; 异步复制（最高性能，可能丢数据）；2) 完成第 1+第 3 步再返回 &#x3D; 半同步复制（折中）；3) 完成 Quorum 级别复制再返回 &#x3D; 强一致性（最低性能）。MySQL 的三种复制模式就是这种权衡的典型体现——没有银弹，根据业务对数据丢失的容忍度选择。</h2>]]>
    </content>
    <id>https://lichuanyang.top/posts/45718/</id>
    <link href="https://lichuanyang.top/posts/45718/"/>
    <published>2022-04-13T12:13:24.000Z</published>
    <summary>分布式系统设计通用方法总结，涵盖多节点有状态系统的核心设计模式和实践经验。</summary>
    <title>分布式系统设计中的通用方法</title>
    <updated>2026-06-27T03:42:25.687Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="架构设计" scheme="https://lichuanyang.top/categories/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/"/>
    <category term="高并发" scheme="https://lichuanyang.top/tags/%E9%AB%98%E5%B9%B6%E5%8F%91/"/>
    <category term="高并发解决方案" scheme="https://lichuanyang.top/tags/%E9%AB%98%E5%B9%B6%E5%8F%91%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/"/>
    <category term="高并发设计" scheme="https://lichuanyang.top/tags/%E9%AB%98%E5%B9%B6%E5%8F%91%E8%AE%BE%E8%AE%A1/"/>
    <content>
      <![CDATA[<p>高请求并发就一定会有高并发问题吗？其实不是的。假设一个应用全部是纯内存计算，无论请求量多大，简单加机器就能线性扩展——那就不存在什么高并发问题。</p><p>高并发问题之所以存在，是因为系统中存在<strong>无法靠粗暴扩容解决的单点瓶颈</strong>。而这个瓶颈，在 90% 以上的场景中，都是<strong>数据库</strong>。</p><span id="more"></span><h2 id="核心思路：降低单库的连接数"><a href="#核心思路：降低单库的连接数" class="headerlink" title="核心思路：降低单库的连接数"></a>核心思路：降低单库的连接数</h2><p>所有高并发技术方案，归根结底都是在做同一件事——<strong>降低单个数据库实例需要承载的并发连接数</strong>。围绕这个目标，有六个层次的技术手段：</p><h2 id="层次一：系统拆分（业务维度）"><a href="#层次一：系统拆分（业务维度）" class="headerlink" title="层次一：系统拆分（业务维度）"></a>层次一：系统拆分（业务维度）</h2><p>最粗粒度的优化：把不同业务模块的数据库物理分开。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">单体架构：所有业务 → 一个数据库（1000 QPS）</span><br><span class="line">拆分后：</span><br><span class="line">  用户服务 → 用户库（400 QPS）</span><br><span class="line">  订单服务 → 订单库（300 QPS）</span><br><span class="line">  商品服务 → 商品库（300 QPS）</span><br></pre></td></tr></table></figure><p>每个业务独享自己的数据库，互不影响。拆分的难点在于分布式事务——以前一个事务搞定的事，现在跨库了。解决方案包括 Saga 模式、Seata、RocketMQ 事务消息等。</p><h2 id="层次二：缓存（减少数据库命中率）"><a href="#层次二：缓存（减少数据库命中率）" class="headerlink" title="层次二：缓存（减少数据库命中率）"></a>层次二：缓存（减少数据库命中率）</h2><p>在应用和数据库之间加一层 Redis，热数据走缓存，只有冷数据才查库。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">请求 → 缓存（命中 90%）→ 返回</span><br><span class="line">     → 缓存（未命中 10%）→ 数据库 → 写入缓存 → 返回</span><br></pre></td></tr></table></figure><p>缓存架构的几个关键决策：</p><table><thead><tr><th>问题</th><th>方案</th></tr></thead><tbody><tr><td>缓存穿透</td><td>布隆过滤器、空值缓存</td></tr><tr><td>缓存击穿</td><td>互斥锁、逻辑过期</td></tr><tr><td>缓存雪崩</td><td>过期时间随机化、多级缓存</td></tr><tr><td>缓存一致性</td><td>Cache Aside（先更新 DB、再删缓存）</td></tr></tbody></table><h2 id="层次三：MQ-削峰（平滑流量）"><a href="#层次三：MQ-削峰（平滑流量）" class="headerlink" title="层次三：MQ 削峰（平滑流量）"></a>层次三：MQ 削峰（平滑流量）</h2><p>瞬时流量如洪水般涌入时，消息队列充当”水库”：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">10 万 QPS → MQ → 1000 QPS 匀速消费 → 数据库</span><br></pre></td></tr></table></figure><p>以秒杀为例，10 万人同时下单，数据库每秒只能处理 1000 个。用 RocketMQ&#x2F;Kafka 做缓冲，请求先进队列，后台服务按自己的节奏慢慢消费。用户体验变成”排队中”，而不是”系统崩溃”。</p><h2 id="层次四：分库分表（同一业务内拆分）"><a href="#层次四：分库分表（同一业务内拆分）" class="headerlink" title="层次四：分库分表（同一业务内拆分）"></a>层次四：分库分表（同一业务内拆分）</h2><p>当一个业务的单库也扛不住时，对同一业务做进一步拆分：</p><ul><li><strong>垂直拆分</strong>：按字段拆——用户基础信息一张表，用户扩展信息一张表</li><li><strong>水平拆分（分表）</strong>：按行拆——user_0, user_1, user_2… 按 user_id 取模路由</li><li><strong>读写分离</strong>：主库写、从库读——适合读多写少的场景</li></ul><p>分库分表的关键是 <strong>sharding key 的选择</strong>——尽量让大部分查询落在单个分片上，避免跨分片查询。ShardingSphere 是目前最流行的分库分表中间件。</p><h2 id="层次五：异构存储（不同引擎各司其职）"><a href="#层次五：异构存储（不同引擎各司其职）" class="headerlink" title="层次五：异构存储（不同引擎各司其职）"></a>层次五：异构存储（不同引擎各司其职）</h2><p>MySQL 不是万能的，不同类型的查询用不同的存储引擎：</p><table><thead><tr><th>数据引擎</th><th>擅长</th><th>场景</th></tr></thead><tbody><tr><td>MySQL</td><td>事务、关联查询</td><td>核心业务数据</td></tr><tr><td>Elasticsearch</td><td>全文搜索、聚合分析</td><td>商品搜索、日志查询</td></tr><tr><td>ClickHouse</td><td>OLAP 分析、海量聚合</td><td>用户行为分析、BI 报表</td></tr><tr><td>Redis</td><td>超高 QPS、简单 KV</td><td>缓存、计数器、排行榜</td></tr><tr><td>HBase</td><td>海量稀疏数据、宽表</td><td>用户画像、时序数据</td></tr></tbody></table><h2 id="层次六：限流与熔断（兜底保护）"><a href="#层次六：限流与熔断（兜底保护）" class="headerlink" title="层次六：限流与熔断（兜底保护）"></a>层次六：限流与熔断（兜底保护）</h2><p>前面的手段是”尽力而为”，限流是”保证下限”——即使扛不住，也别让整个系统崩溃。</p><p>常用的限流算法：</p><ul><li><strong>令牌桶</strong>（Guava RateLimiter）：匀速放令牌，请求来取令牌，没令牌就拒绝</li><li><strong>漏桶</strong>：固定速率漏水，请求像水倒进桶里，满了溢出</li><li><strong>滑动窗口</strong>（Sentinel）：统计窗口内的请求数，超过阈值拒绝</li></ul><p><strong>熔断</strong>（Circuit Breaker）：当某个下游服务持续失败时，不再徒劳地继续调用，快速失败，给下游喘息空间。Hystrix（已停更）→ Resilience4j → Sentinel，是熔断框架的演进路线。</p><h2 id="高并发架构全景图"><a href="#高并发架构全景图" class="headerlink" title="高并发架构全景图"></a>高并发架构全景图</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">             ┌──────────────┐</span><br><span class="line">限流 ←───────│   负载均衡    │───────→ 限流</span><br><span class="line">             └──────┬───────┘</span><br><span class="line">                    │</span><br><span class="line">      ┌─────────────┼─────────────┐</span><br><span class="line">      ▼             ▼             ▼</span><br><span class="line"> ┌─────────┐  ┌─────────┐  ┌─────────┐</span><br><span class="line"> │ 用户服务  │  │ 订单服务  │  │ 商品服务  │  ← 系统拆分</span><br><span class="line"> └────┬────┘  └────┬────┘  └────┬────┘</span><br><span class="line">      │             │             │</span><br><span class="line"> ┌────▼────┐  ┌────▼────┐  ┌────▼────┐</span><br><span class="line"> │  Redis   │  │   MQ    │  │   ES    │  ← 缓存/削峰/异构存储</span><br><span class="line"> └────┬────┘  └────┬────┘  └────┬────┘</span><br><span class="line">      │             │             │</span><br><span class="line"> ┌────▼─────────────▼─────────────▼────┐</span><br><span class="line"> │          MySQL（分库分表）              │  ← 分库分表</span><br><span class="line"> │  user_0  user_1  order_0  order_1   │</span><br><span class="line"> └─────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h2 id="池子（连接池-线程池）的调优"><a href="#池子（连接池-线程池）的调优" class="headerlink" title="池子（连接池&#x2F;线程池）的调优"></a>池子（连接池&#x2F;线程池）的调优</h2><p>各种”池子”本质也是一种限流手段：</p><ul><li><strong>Tomcat 线程池</strong>：限制同时处理的 HTTP 请求数</li><li><strong>数据库连接池</strong>（HikariCP）：限制同时打到数据库的连接数</li><li><strong>线程池</strong>：限制并发执行的任务数</li></ul><p>调优原则：</p><ul><li><strong>池子不能太大</strong>：超过数据库承载能力，大家都慢</li><li><strong>池子不能太小</strong>：资源利用不充分</li><li><strong>做好监控</strong>：activeCount、queueSize、waitCount —— 任何一个接近上限，就该准备扩容了</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>高并发的本质是一道”流量管理题”。六个层次的技术手段，从粗到细逐步降低单库的连接数：</p><blockquote><p>系统拆分 → 缓存 → MQ 削峰 → 分库分表 → 异构存储 → 限流熔断</p></blockquote><h2 id="理解了”降低单库连接数”这条主线，你会发现各种高并发技术方案不再是零散的技巧，而是一个层次分明的体系。"><a href="#理解了”降低单库连接数”这条主线，你会发现各种高并发技术方案不再是零散的技巧，而是一个层次分明的体系。" class="headerlink" title="理解了”降低单库连接数”这条主线，你会发现各种高并发技术方案不再是零散的技巧，而是一个层次分明的体系。"></a>理解了”降低单库连接数”这条主线，你会发现各种高并发技术方案不再是零散的技巧，而是一个层次分明的体系。</h2>]]>
    </content>
    <id>https://lichuanyang.top/posts/11970/</id>
    <link href="https://lichuanyang.top/posts/11970/"/>
    <published>2022-03-29T10:40:46.000Z</published>
    <summary>从数据库瓶颈的本质出发，轻松讲清楚高并发设计的核心思路和常见解决方案。</summary>
    <title>高并发解决方案很难吗？轻松聊清楚高并发设计</title>
    <updated>2026-06-27T02:06:09.318Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="技术杂谈" scheme="https://lichuanyang.top/categories/%E6%8A%80%E6%9C%AF%E6%9D%82%E8%B0%88/"/>
    <category term="计算广告" scheme="https://lichuanyang.top/tags/%E8%AE%A1%E7%AE%97%E5%B9%BF%E5%91%8A/"/>
    <category term="互联网广告" scheme="https://lichuanyang.top/tags/%E4%BA%92%E8%81%94%E7%BD%91%E5%B9%BF%E5%91%8A/"/>
    <category term="RTB" scheme="https://lichuanyang.top/tags/RTB/"/>
    <content>
      <![CDATA[<p>本文中，我会介绍一下互联网广告的发展状态，并梳理一下互联网广告的底层逻辑，帮助大家更好的理解这个行业。</p><span id="more"></span><h2 id="什么是广告"><a href="#什么是广告" class="headerlink" title="什么是广告"></a>什么是广告</h2><p>讲互联网广告，首先要从广告本身是什么开始讲起。只有理解了广告的底层逻辑，才能更好的帮助我们理解在互联网广告中会有什么问题, 为什么会出现广告的程序化交易，adx, ssp, dsp,dmp等等各种各样不同的系统为什么会出现，以及诸如此类的等等问题。</p><p>广告这个行业其实自古就有，从远古时代小商小贩的吆喝，各种建筑物上的告示，到纸质报纸、杂志、电视等传统媒体上的广告，再到互联网广告，这个行业总是再随着社会的发展产生它自己的变化。</p><p>对于广告，先上一个非常官方、非常标准的定义：广告是由已确定的出资人通过各种媒介进行的有关产品（商品、服务和观点）的，并且通常是有偿的、综合的、劝服性的非人员的信息传播活动。</p><p>而更通俗的讲，在广告交易中，核心的参与方有三个：媒体(即供给方，Supply), 广告主（即需求方， Demand），受众（即媒体的用户）。其中，媒体和广告主是主动的参与方，受众则是被动的参与方。而广告实际上就是这三方相互博弈的一个过程。对于广告主来说，希望有更多的受众能看到广告，并且从广告中获取更高的转化；对于媒体来说，希望在获得最大的广告利润的同时，又能减少对用户的打扰；而对受众，也就是普通用户，则是希望不被自己不想看到的广告打扰。</p><p>接下来我们分析一下这个流程里的博弈点。众所周知，如果一件事对于各参与方是零和博弈的话，这件事情就难免陷入到恶性竞争中，必定是难以发展的。而广告行业既然能持续健康的发展，主要是靠对广告生态之外产生的影响，所以我们就分析一下有哪些对广告生态外的影响因素。</p><p>我们先拆解下各方都有哪些考虑指标，对于媒体，主要就是广告收益和自己产品的留存，对于广告主，则是广告费用和通过广告获得的收益，受众作为被动参与者，在整个过程中能做的不多，只能用脚投票，离开广告太烂的产品。这里边，在整个广告生态之外的，就是广告主通过广告获得的收益，和减少广告对受众造成的打扰，两个方面。事实上，对这两点的提升也就是整个广告行业不断发展的源动力。</p><p>理解了这一点，能帮助我们更好的理解互联网广告的发展历程。</p><h2 id="互联网广告的发展历程"><a href="#互联网广告的发展历程" class="headerlink" title="互联网广告的发展历程"></a>互联网广告的发展历程</h2><h2 id="互联网广告的早期发展"><a href="#互联网广告的早期发展" class="headerlink" title="互联网广告的早期发展"></a>互联网广告的早期发展</h2><p>最早的互联网广告，实质上就是把传统媒体的广告形式直接搬到线上来。比如说一份报纸里，会有一个区域专门用来放广告。像我们早些年在搜狐、新浪这些网站上看到的广告，也都是这种形式。广告主直接购买网页上的某个区域，固定的显示某些广告内容。</p><p>接下来，有些人会逐渐注意到互联网广告和传统媒体的一个关键区别：互联网广告的内容是可以因人而异的。已经印刷出去的报纸，所有人看到的都一样，而对于网站来说，完全可以做到对不同人群，比如男性和女性展示不同的广告。而媒体只需向广告主保证投放量，以及投放量不能完成时的一些赔偿方式。这就是合约广告。</p><p>合约广告的一个很自然的发展趋势就是定向越来越精细，诸如性别、年龄、地域、兴趣爱好等，都可以成为合约条件的一部分。同时，参与进来的广告主越来越多。这两方面的因素都会导致履约的难度急剧上升。同时，这么复杂的逻辑下，媒体也会觉得自己很多流量没有卖出最高价。</p><p>在这种情况下，新的广告形式应运而生，也就是竞价广告。供给方不再以合约的方式给出量的保证，只保证单位流量的成本；每一次广告展示都按照收益最高的原则来决策。这样，广告就逐渐发展成了类似「流量交易」这种模式,  形成了程序化广告交易这种模式。</p><h2 id="程序化交易广告"><a href="#程序化交易广告" class="headerlink" title="程序化交易广告"></a>程序化交易广告</h2><p>在竞价广告的场景下，没有了合约的约束，大量媒体、广告主进入了一个多方博弈的环境，交易过程也就越来越复杂，产生了诸如RTB(real time bidding，实时竞价)这样的交易方式。程序化广告交易正是在这种情况下产生的。</p><p>同时，随着技术的发展，更多的数据、深入的计算与预测成为可能，广告主、媒体以及相关的广告代理等各个参与方也都需要更加丰富的功能。我们标题上提到的ssp,adx, dsp, rtb这些名词，也都是在这种情况下，逐步发展成为广告业态中非常重要的系统。</p><p>总体来说，程序化广告交易和股票交易等实时交易的场景都非常类似，都是存在一个交易场（ADX）, 买方（广告主）和卖方（媒体），各自在其中实时的做买卖决策。唯一一个重要的区别是，对于媒体来说，没有择时的机会，流量来了只能立刻卖出去，没有办法攒起来等一个好时机。可以说，这一点正是广告相关各项技术发展的出发点。</p><h2 id="互联网广告的关键问题和组成部分"><a href="#互联网广告的关键问题和组成部分" class="headerlink" title="互联网广告的关键问题和组成部分"></a>互联网广告的关键问题和组成部分</h2><p>前面说了，互联网广告发展的源动力有两点，一是提升广告主通过广告获得的收益，二是减少广告对受众造成的打扰。这两点关系到的是广告的有效性，所以我们分析一下广告有效性有哪些影响因素。</p><p>一个广告的生命周期大概可以拆解成这么几步：</p><p>曝光-关注-理解-接受-保持-决策</p><p>简单的说，就是广告先要展示出来，然后能被受众看到，并且理解广告想传达的信息，进一步，能接受这个信息，并且保持一段时间，最终完成对广告所宣传的产品决策（下载、购买、付费等）。</p><p>接下来，我们梳理一下每一步的具体影响，以及互联网技术能对其起到什么作用。</p><p>曝光：这一阶段主要取决于广告位的物理熟悉，比如一个app开屏位置的广告，和某个隐藏的很深的页面上的一条广告，其有效性必然差别很大。这一点，在现实世界的广告中也是一样的，也没有太多技术上可优化的空间。</p><p>关注：即让用户的注意力能够到这条广告上。值得注意的事，「看到」并不等于「关注到」，如果产品设计不合理，很容易导致用户对眼前的东西视而不见。比如说，用户到某个页面上是非常明确的要执行完一个流程的，比如要下载个东西，那么，你在他下载过程中强行插入一条广告，很可能会导致用户完全忽视掉它。所以，这一点和产品设计关系非常大。另外，这也是一个技术能发挥重要作用的阶段。通过搭建DMP平台，结合各种机器学习算法，将广告投放给更有可能感兴趣的用户，从而提升广告的有效性。</p><p>理解：这一步影响最大的是广告素材，素材内容设计的好，就能让用户更好的理解。</p><p>接受：这一点说白了就是能不能让用户认可你的广告给出来的信息或观点，这个一方面与素材是否专业有关，另一方面和媒体的自身属性也有很大关系，比如丁香医生和某莆田医院官网上放同样的信息，其有效性也必然是大为不同的。</p><p>保持和关注：这两步其实逐渐就脱离了广告业态的范围，不过通过一些优质的素材设计、合适的场景、精准的人群投放，还是可以为最终转化做好铺垫的。</p><p>此外，在其中这些技术无法直接干预的阶段，提供更完善的数据、更方便的测试方法，也是能给广告效果带来很大帮助的。</p><p>接下来，我们再分别从媒体和广告主出发，看看他们各自有什么需求, 以及简单看看ssp, dsp这些平台是怎么做的。由于篇幅限制，这一部分先不详细展开，如果感兴趣的话，我们后边再逐一展开介绍。</p><p>服务媒体的平台即常说的SSP, 其主要功能就是帮助媒体提升营收。里边一些基本的功能，比如让媒体配置一个广告位，以及广告位的长宽等信息。 对于提升营收，主要也就是通过合适的人群与广告匹配，优化定价策略等方式，同时设法控制网络等IT成本来进行。</p><p>与SSP相对的DSP, 就是服务广告主这一侧的平台。DSP的基本功能就是让广告主设置投放计划，比如在多长时间内，向什么样特征的用户，投放总预算多少的广告。而平台发挥的价值主要是帮助广告主花更少的钱获取更好的广告效果。</p><p>直到今天，广告生态中涉及到的每一个模块其实都还在持续不断的迭代中，但万变不离其宗，关键的动力其实就是让广告投放的效果更好，用户也能获得更好的浏览广告的体验。只有这样，整个行业才能朝着健康的方向去发展。</p><p>原文地址：<a href="https://lichuanyang.top/posts/27934/---">https://lichuanyang.top/posts/27934/---</a></p>]]>
    </content>
    <id>https://lichuanyang.top/posts/27934/</id>
    <link href="https://lichuanyang.top/posts/27934/"/>
    <published>2022-03-07T08:28:58.000Z</published>
    <summary>从广告的本质出发，系统梳理互联网广告的发展脉络，讲清楚 SSP、DSP、RTB、ADX 等核心概念及程序化交易的底层逻辑。</summary>
    <title>SSP、DSP、RTB、ADX都是什么？讲讲互联网广告的概念与发展</title>
    <updated>2026-06-27T03:57:07.064Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="数据库" scheme="https://lichuanyang.top/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <category term="数据库事务" scheme="https://lichuanyang.top/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E4%BA%8B%E5%8A%A1/"/>
    <category term="数据库" scheme="https://lichuanyang.top/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <category term="ACID" scheme="https://lichuanyang.top/tags/ACID/"/>
    <category term="事务隔离级别" scheme="https://lichuanyang.top/tags/%E4%BA%8B%E5%8A%A1%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB/"/>
    <content>
      <![CDATA[<p>前些日子读了周志明老师的《凤凰架构》这本书，对于很多方面的技术有了更深的认知，因此打算做一些总结。今天先以讲事务的这一段做个印子，结合书中内容和个人理解，争取将本地事务的相关知识讲个命名白白。如果有讲的不对的地方，欢迎大家多多指正。</p><span id="more"></span><p>所谓事务，就是保证数据库中的数据都是符合期望的，在不断的增删改查中，数据库会不断的从一个正确的状态变化到另一个正确的状态，而不会被外界感知到不“正确”的中间状态。</p><p>举个常见的例子，就是在 A有100元，B有100元的状态下，A要给B转账10元，肯定会有A先转出10元，再转给B,这样的中间状态。事务就是，对于用户来说，只能感知到 A100 B100 和 A90 B110 这两个状态，而不会感知到过程中的A90 B100等等奇奇怪怪的状态。</p><p>这个介绍，其实也就是事务ACID特性中一致性的概念。ACID这个说法虽然很流行，但A、C、I、D之间其实并不是平等的概念，简单来说，AID是方法，C是目的。也就是说，实现了原子性、持久性、隔离性，也就实现了一致性，也就实现了事务。</p><p>接下来，我们就依次看看AID分别要如何实现。</p><h2 id="原子性和持久性"><a href="#原子性和持久性" class="headerlink" title="原子性和持久性"></a>原子性和持久性</h2><p>实现原子性和持久性面临的相同问题其实挺多的，所以把它们放在一起介绍。</p><p>先复习一下基本概念，所谓原子性，就是事务内的操作要么都成功，要么都失败；所谓持久性，就是已经完成的操作不会丢失。</p><p>值得说明的是，单纯的“实现原子性和持久性”并不存在任何难度，要讨论的问题其实是“如何更高性能的实现原子性和持久性”，（后面要说的隔离性也是一个概念，只有高性能的隔离性才有意义）。</p><p>其中一个关键点在于，写磁盘是一个非常重的操作，所以通常会存在一个内存缓冲区，要写磁盘的数据会先写到缓冲区里，再择机落盘。那么假如在事务已提交而尚未落盘的这个时间点，系统出现故障，那么这部分未落盘的数据自然就会丢失，数据库也就失去了持久性。针对这个问题，一个很自然的想法就是事务提交的时候强制刷盘。这个方案可不可行？当然是可行的。但它的问题就是会影响性能。系统出故障毕竟是小概率事件，为了处理这个小概率事件，相当于所有操作都要额外付出一些代价。</p><p>实际上为了解决这个问题，一个常规的处理思路就是使用一个commit Log, 也就是在实际写数据之前，先将所有要修改的信息记录在一个log里，如果出现上面描述的问题，系统重启时，会先根据commit log进行数据恢复。而由于写这份log是一个顺序写磁盘的操作，性能会远远好于随机写磁盘，所以这个方式的性能是没有问题的。在数据真正写入之后，再加一个标记，表示这条log已经完成了持久化。</p><p>接下来，我们再看一下commit log是否还有优化空间？自然是有的。Commit log的一个重要缺陷就是所有真实的磁盘操作都必须发生在事务提交之后。假如说这个事务非常大，就会占用很大的内存缓冲区，这也会影响系统的性能。改进方案是 write-ahead log 这个机制，这个机制我在之前的文章(<a href="https://lichuanyang.top/posts/3914/">https://lichuanyang.top/posts/3914/</a>)里也介绍过，和commit log其实非常像，也是先顺序写一个log文件，唯一的区别就是write-ahead log允许在事务提交之前写入。mysql里的redo log，其实就是一个典型的write-ahead log实现。</p><p>讲到这里，我们先暂停一下，回顾一下上面的内容，会发现上边其实基本都在说持久性，原因是对于上边的机制来说，原子性其实都是自然而然的事情，commit log写进去了，这条事务就相当于完成了；commit log没写入，这个事务就相当于不存在。但是使用了write-ahead log的话，情况就不一样了，一个事务会涉及多次磁盘写入，所以也就不满足原子性了。因此，需要引入别的机制来保证原子性，undo log就是实现这个目标的一个典型思路。当变动数据写入磁盘前，必须先记录undo log，注明修改了哪个位置的数据、从什么值改成什么值等，以便在事务回滚或者崩溃恢复时根据undo log对提前写入的数据变动进行擦除。</p><p>像在mysql中，实际上也就是像我们上边讲的那样，利用redo log和undo log实现高效可靠的持久性和原子性。</p><h2 id="隔离性，隔离级别"><a href="#隔离性，隔离级别" class="headerlink" title="隔离性，隔离级别"></a>隔离性，隔离级别</h2><p>如何实现不同事务之间的隔离，一个很自然的思路就是加锁，实际上常规的数据库实现也就是这么做的。一般来说，有这么几种类型的锁：读锁（也叫共享锁），写锁（也叫排他锁），范围锁。</p><p>对于一条数据，只有一个事务能持有写锁；不同的事务可以同时持有读锁，数据被添加读锁后不能再添加写锁, 添加写锁后也不能再添加读锁；范围锁则是对一个范围加写锁，在这个范围内都不能写入数据。</p><p>我们知道，数据库有四种常见的隔离级别：可串行化，可重复读，读已提交，读未提交。其区别其实就是加锁粒度的不同。</p><p>如果我们把所有操作能加的锁都加上，实际上就是串行化的操作了。这种方式隔离性当然很好，但性能就没法说了，所以一般也不会有人使用。</p><p>可重复读则是对涉及到的数据加读锁和写锁，并持有到事务结束，但不会加范围锁。这样就会出现幻读的问题，即一个事务内执行两次范围查询，如果这两次查询之间有新的数据被插入，就会导致两次范围查询的结果不一致。</p><p>读已提交和可重复读的区别是他的读锁会在查询操作结束之后立刻释放掉，这样，在事务执行过程中，已经查询过的数据是可以被其他事务任意修改的，所以也就会有不可重复读的问题。</p><p>读未提交级别下，则完全不会加读锁。这样造成的问题是，由于读操作时不会去申请读锁，所以反而会导致能够读到其他事务上加了写锁的数据，也就会出现脏读的问题。</p><p>其实说到底，隔离性和性能就是一对相互矛盾的需求。加锁加的越多，隔离性自然越好，性能也自然越差。我们需要根据实际的使用场景来决定锁究竟要加到什么程度。</p><p>而另一个思路，则是再看看有没有锁以外的方式，考虑28原则，看看有没有什么办法，牺牲20%的性能去解决80%的问题。具体来讲，涉及隔离性问题的场景，其实可以简化为一个读事务+一个写事务，和两个写事务，这两种场景。大部分场景下，当然是读+写的情况更多，所以我们找个方式去解决读+写场景下的幻读问题。相信很多人已经猜到了，这种方式就是MVCC。关于MVCC, 网上介绍资料实在太多，我们就不再赘述了。</p><p>经过上面对于事务几个特性的介绍，相信大家已经对本地事务有了非常深刻的认识。如有问题，欢迎留言讨论。下一篇文章，我会继续讲一下分布式事务的相关知识，如果感兴趣，可以关注我的个人博客、知乎或者公众号追更~</p><h2 id="原文地址-https-lichuanyang-top-posts-7774"><a href="#原文地址-https-lichuanyang-top-posts-7774" class="headerlink" title="原文地址: https://lichuanyang.top/posts/7774/"></a>原文地址: <a href="https://lichuanyang.top/posts/7774/">https://lichuanyang.top/posts/7774/</a></h2>]]>
    </content>
    <id>https://lichuanyang.top/posts/7774/</id>
    <link href="https://lichuanyang.top/posts/7774/"/>
    <published>2022-02-07T08:40:02.000Z</published>
    <summary>从 redo log、undo log 到隔离级别，刨根问底讲清楚数据库事务和 ACID 的完整机制。</summary>
    <title>从redolog,undolog到隔离级别，刨根问底，讲清楚事务和ACID</title>
    <updated>2026-06-27T03:44:21.592Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="云原生" scheme="https://lichuanyang.top/categories/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <category term="kubernetes" scheme="https://lichuanyang.top/tags/kubernetes/"/>
    <category term="持续集成" scheme="https://lichuanyang.top/tags/%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90/"/>
    <content>
      <![CDATA[<p>很多创业公司相比于大厂，一个非常重要的劣势就是基础设施不完善，没有各种各样完善的工具。因此，我打算整理一下，基于开源社区提供的能力，如何用尽量小的运维和开发成本，去搭建出一套体验良好的开发流程。</p><span id="more"></span><p>首先，我们理一下整个开发流程中的必备步骤都有哪些，我大概罗列一下，包括项目的创建、开发、code review、部署测试环境、部署灰度和线上环境、查看线上监控、日志、以及排查问题等。</p><p>根据这整个流程链条，我们再来理一下为了尽可能的提升开发和运维效率，我们需要把哪些方面保障好，以及这些方面可以有什么低成本的解决方案。</p><p>有几个点吧：</p><ul><li>快速创建一个项目，并且将必备的web、监控、日志等组件添加好；</li><li>提升一些常规的代码开发，比如数据库增删改查的开发效率；</li><li>简化打包到部署的流程，测试环境能自动部署更好；</li><li>能够给每个服务轻松的加上一整套监控和报警，包括通用的机器、jvm、接口层面的指标，和根据业务自定义的指标；</li><li>能有地方集中看各个节点的日志；</li><li>必要的时候，可以在线上节点上有强力的工具帮助排查问题。</li></ul><p>接下来，我们就看一下这些问题分别怎么解决。</p><h2 id="部署环境"><a href="#部署环境" class="headerlink" title="部署环境"></a>部署环境</h2><p>首先，在本文的标题中，其实已经假定了部署环境就是kubernetes, 所以，我先说一下为什么只有kubernetes这一个选项。随便翻开一个kubernetes的介绍资料，我们都可以看到很多它的优势说明，这里我们不多赘述。而其中最关键的，也是每个人都能切身体会到的好处，其实就是它提供了完善的自动扩缩容机制。可以非常简单的设置一个cpu消耗的期望值，k8s集群就可以根据目前cpu的负载去调整pod数量。我对比了我们启用自动扩缩容前后的数据，每天的机器成本可以相差接近600美金，占总成本的1&#x2F;3还要多。</p><p>由于k8s本身的运维成本是很高的，推荐直接购买云服务商的服务，无论aws还是阿里云，都提供了很完善的k8s集群功能。</p><p>另外，推荐使用 kuboard (<a href="https://kuboard.cn/">https://kuboard.cn/</a>) 这个工具对k8s进行管理。kuboard是一个图像化的k8s管理工具，包括部署、配置、扩容、登入pod等常见操作，都可以在图形化界面下操作，使用很方便。</p><h2 id="项目开发"><a href="#项目开发" class="headerlink" title="项目开发"></a>项目开发</h2><p>一般基于spring initializr创建一个项目就可以。对于常用的日志、监控等各类配置，建议整理一份最佳实践并做一个模板项目。新项目可以基于这个模板项目来创建，可以利用mvn archetype或者类似的工具让这个过程更加顺畅。</p><p>至于一些常用的数据库、缓存等基本代码，感觉spring全家桶已经足够好用了。</p><p>对于code review, 我们可以使用gitlab的webhook, 当代码提交时，自动给项目组的人发通知。</p><h2 id="打包-部署"><a href="#打包-部署" class="headerlink" title="打包&amp;部署"></a>打包&amp;部署</h2><p>对于springboot项目，可以用buildpacks 打成docker镜像，而不用再考虑docker file的细节。具体的buildpacks产品，我们用的是paketo buildpacks, 其他的诸如cloudfoundry,heroku也都差不多，目前没有还研究这些包有什么具体需求。</p><p>接下来，我们仍然可以利用gitlab的webhook, 触发一次jenkins的构建任务。在jenkins的构建任务重，我们可以完成打包和部署到kubernetes测试集群这些操作。</p><h2 id="监控-报警"><a href="#监控-报警" class="headerlink" title="监控&amp;报警"></a>监控&amp;报警</h2><p>推荐使用 prometheus + grafana 套餐即可，关于prometheus的使用，以及在springboot项目下的配置，可以参考我之前的文章  h<a href="https://lichuanyang.top/posts/28288/">ttps:&#x2F;&#x2F;lichuanyang.top&#x2F;posts&#x2F;28288&#x2F;</a>  。</p><p>在k8s上，可以装一个weave cloud agent， 然后就可以配置对prometheus接口进行自动抓取了。</p><p>在grafana里，可以直接写promeql配置监控报表。 另外，grafana官网上，有大量的别人共享出来的图表，可以直接使用。</p><p>在grafana里，也可以配置各种各样自定义规则的报警。如果使用飞书的话，在飞书里配置grafana助手，可以很容易的将飞书作为grafana的报警通道。</p><h2 id="日志"><a href="#日志" class="headerlink" title="日志"></a>日志</h2><p>可以使用loki (<a href="https://grafana.com/oss/loki/">https://grafana.com/oss/loki/</a>)作为日志收集、查询的工具。loki可以认为是一个轻量级的ELK, 其维护成本会比ELK低很多。</p><h2 id="排查问题"><a href="#排查问题" class="headerlink" title="排查问题"></a>排查问题</h2><p>对于java的项目，使用神器arthas可以解决绝大多数问题排查的需求。对于arthas的接入，可以采用arthas springboot starter这种方式 。而对于k8s上的pod, 如果环境能够和办公环境打通，那可以利用k8s的port forward 功能，将arthas的端口转发到本地来。 当然，这样做的话，务必要控制好权限。</p><h2 id="金丝雀部署"><a href="#金丝雀部署" class="headerlink" title="金丝雀部署"></a>金丝雀部署</h2><p>关于金丝雀部署的作用和实现方式，可以参考我的另一篇文章 <a href="https://lichuanyang.top/posts/30764/">https://lichuanyang.top/posts/30764/</a></p><p>总结一下, 对于运维来说，只需要维护一些诸如gitlab, kuboard, prometheus, grafana, loki之类的基础设施，而且基本都是一些维护较简单的工具。在此基础上，我们辅助以合理的流程和技巧，就能实现一个非常好的开发体验。</p><h2 id="原文地址-https-lichuanyang-top-posts-40964"><a href="#原文地址-https-lichuanyang-top-posts-40964" class="headerlink" title="原文地址: https://lichuanyang.top/posts/40964/"></a>原文地址: <a href="https://lichuanyang.top/posts/40964/">https://lichuanyang.top/posts/40964/</a></h2>]]>
    </content>
    <id>https://lichuanyang.top/posts/40964/</id>
    <link href="https://lichuanyang.top/posts/40964/"/>
    <published>2022-01-26T07:19:32.000Z</published>
    <summary>创业公司低成本使用 Kubernetes 的实践经验，从项目创建到部署监控的全流程开源方案。</summary>
    <title>java项目低学习成本使用kubernetes的实践经验</title>
    <updated>2026-06-27T03:44:21.600Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="技术杂谈" scheme="https://lichuanyang.top/categories/%E6%8A%80%E6%9C%AF%E6%9D%82%E8%B0%88/"/>
    <category term="感悟" scheme="https://lichuanyang.top/tags/%E6%84%9F%E6%82%9F/"/>
    <content>
      <![CDATA[<p>2021年对于我个人来说可以说是剧变的一年，原本准备长期发展的，工作舒适度和前景都非常高的工作，因为某些原因突然崩塌。随之而来的，关于未来往哪走，也到了一个重要的岔路口上。至少到目前为止，感觉还是做出了一些正确的选择。对于年初定下的一些个人成长上的目标，也有了不错的进展。所以，我还是带着不错的心情来做这个年终总结的。</p><span id="more"></span><h2 id="职业回顾"><a href="#职业回顾" class="headerlink" title="职业回顾"></a>职业回顾</h2><p>2021年里，最大的变化还是来自工作，由于所在的行业一夜之间直接在风口上消失，原本在一家公司长期发展的愿望落空，所以我也被迫重新开始了新的职业规划。一个关键的考量点就是要去大公司还是小公司。我在前边为什么说自己走到了一个重要的岔路口上，其实最关键的一点原因就是感觉无论做哪个选择，在这个年龄其实都不会有什么回头路了。大公司和小公司对一个资深员工的能力要求，差别实际上是非常大的。到了这个阶段，不会再有刚毕业那几年随时大小公司左右横跳的机会。</p><p>为了做这个决策，我问了自己几个问题。</p><p>在大公司，个人能力会比在小公司成长的更快吗？</p><p>在大公司，生活是否会比小公司更舒适？</p><p>我在将来是否还需要一份新大公司经历做背书？</p><p>在大公司能获得更多的经济收入吗？</p><p>经过慎重的考虑，对这几个问题的回答其实都是否。个人成长看个人，我实质上不是一个会受环境影响太大的人。对于work life balance的问题，按理说其实和公司大小没太大的关系，只是凑巧国内的几个大公司这方面都不太行。背书的问题，因为我本来也有大厂的经历，包括综合学历和之前的其他经历，我觉得是不太有可能在这方面吃亏的，另外，根据这一波找工作，以及这两年面试别人的感受，我也会觉得项目本身的含金量，会比在哪个公司更重要一些。至于收入，看大环境、看公司的变化情况、看老板风格、看运气，总之不会和公司大小有太大的关系。</p><p>想来想去，感觉去大厂的唯一作用，就是说出去面子上好看一些。所以，也就把这个决定做下了，就是彻底放弃大公司的想法，而是去寻找一些氛围好、前景优良的创业公司机会。说到这儿，有时候我其实非常感激上天的眷顾，仿佛她总会在我一些关键节点上适时的给我抛出一些合适的选项。当然实际上主要是感谢猎头小姐姐提供的信息和整个过程中的帮助，让我来到了一个各方面都非常满意的新公司。在新公司也快4个月了，这段时间包括做事的空间、个人成长、WLB、经济回报，其实都是高于预期的。</p><h2 id="生活变化"><a href="#生活变化" class="headerlink" title="生活变化"></a>生活变化</h2><p>在新的工作时间下，也有了更多的时间陪女儿。小姑娘最近也处在一个语言的爆发期，基本每天都会说新的话，生活的幸福感也到了一个非常高的阶段。</p><h2 id="技术成长"><a href="#技术成长" class="headerlink" title="技术成长"></a>技术成长</h2><p>另外，我每年初都会定一些个人成长上的目标。2021年初，我的关键目标其实就是扩大和巩固自己的能力圈，争取掌握一些新的技能，并巩固已有的技能。具体来说包括本职工作相关的技术和管理能力提升，提升商业认知和投资能力，以及建立一些底层的同样技能。</p><p>本职工作上，阅读了周志明老师的《凤凰架构》这本书，这本书的关键作用是帮助我建立起了更完善的知识体系，也补全了一些知识上的盲区。学习了极客时间上王争小哥的设计模式专栏，对设计模式有了更深刻的理解，也在公司内做了一次设计模式主题的分享。此外，对于用了很久都没太认真了解过的elasticsearch, 做了一次集中的学习。</p><p>对于管理，今年内学习了一些理论知识，也在设法将理论知识运用到实际工作中，感觉还不错。</p><p>投资上，今年实际上做了一些非常错误的尝试。今年初开始，我在试图自己去分析公司的基本面，从而找到合适的投资标的，读了很多书，也看了很多大V的经验。我的目前结论是，这个事情的门槛是非常高的，通过自己的学习去达到专业人士的投资效果，是不现实的。基于此次尝试，我的投资收益在今年第一次变成负数。而在未来，我也会放弃个股投资，转向彻底的基金和指数定投。但是我倒不会停止对商业和投资知识的学习，因为我感受到了这些知识对于我思维方式的转变，实际上是会有不错的收获的。</p><p>关于底层能力，我今年着重培养的是”清晰力“这么一个能力。这个概念来自于《认知觉醒》这本书，意思是说我们的恐惧很多时候是“模糊”，即没有想明白要面对的究竟是个什么事。而一旦能对事情有清晰的认知，会发现事情并没有那么难。当第一次看到这个概念的时候，我立刻就感觉到我自己一直以来在这方面上问题很大，所以就着手在这方面上进行刻意的练习。效果也很不错，无论是工作还是生活上的事，处理起来都感觉得心应手了很多。</p><h2 id="2022-展望"><a href="#2022-展望" class="headerlink" title="2022 展望"></a>2022 展望</h2><p>总而言之，这一年对我来说，是一个关键的抉择之年，也在向着人生的目标稳步前行。感谢所有曾经帮助过我的人。希望2022也继续加油。</p><p>原文地址: <a href="https://lichuanyang.top/posts/2345/">https://lichuanyang.top/posts/2345/</a></p><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h3 id="Q-中年工程师的出路在哪？"><a href="#Q-中年工程师的出路在哪？" class="headerlink" title="Q: 中年工程师的出路在哪？"></a>Q: 中年工程师的出路在哪？</h3><p>出路不在公司大小，而在你的能力圈。到了职业中后期，比”去哪个公司”更关键的是：你对什么领域有独到见解、你能解决什么级别的技术问题、你的经验是否形成了可迁移的方法论。作者的选择——放弃大厂光环，去寻找氛围好、前景优良的创业公司——给出了一条可行的路径。但路径不是唯一的，关键是主动思考你要什么，而不是被动等机会砸下来。</p><h3 id="Q-技术人如何应对行业变化？"><a href="#Q-技术人如何应对行业变化？" class="headerlink" title="Q: 技术人如何应对行业变化？"></a>Q: 技术人如何应对行业变化？</h3><p>三个字：能力圈。行业风口可能一夜消失（如作者经历的教培行业），但如果你平时就在持续巩固和扩大自己的能力圈，就拥有了在不同行业间迁移的资本。具体来说：保持技术敏感度（读经典书籍如《凤凰架构》）、培养底层可迁移能力（如”清晰力”）、不要把职业安全感绑定在某一家公司或某一个行业上。</p><h3 id="Q-大公司还是小公司更适合资深工程师？"><a href="#Q-大公司还是小公司更适合资深工程师？" class="headerlink" title="Q: 大公司还是小公司更适合资深工程师？"></a>Q: 大公司还是小公司更适合资深工程师？</h3><h2 id="这取决于你现阶段最需要什么。大公司提供平台背书和资源规模，但个人成长速度和-WLB-未必理想；小公司提供更大的做事空间和更快的决策节奏，但稳定性相对较低。作者对比后的结论是：个人成长靠自己，不靠公司大小；大厂经历已有的话，背书需求不大；收入更多看大环境和个人运气。核心建议是——想清楚自己现阶段的选择标准，再做决策，因为到了这个阶段，不太会有回头路。"><a href="#这取决于你现阶段最需要什么。大公司提供平台背书和资源规模，但个人成长速度和-WLB-未必理想；小公司提供更大的做事空间和更快的决策节奏，但稳定性相对较低。作者对比后的结论是：个人成长靠自己，不靠公司大小；大厂经历已有的话，背书需求不大；收入更多看大环境和个人运气。核心建议是——想清楚自己现阶段的选择标准，再做决策，因为到了这个阶段，不太会有回头路。" class="headerlink" title="这取决于你现阶段最需要什么。大公司提供平台背书和资源规模，但个人成长速度和 WLB 未必理想；小公司提供更大的做事空间和更快的决策节奏，但稳定性相对较低。作者对比后的结论是：个人成长靠自己，不靠公司大小；大厂经历已有的话，背书需求不大；收入更多看大环境和个人运气。核心建议是——想清楚自己现阶段的选择标准，再做决策，因为到了这个阶段，不太会有回头路。"></a>这取决于你现阶段最需要什么。大公司提供平台背书和资源规模，但个人成长速度和 WLB 未必理想；小公司提供更大的做事空间和更快的决策节奏，但稳定性相对较低。作者对比后的结论是：个人成长靠自己，不靠公司大小；大厂经历已有的话，背书需求不大；收入更多看大环境和个人运气。核心建议是——想清楚自己现阶段的选择标准，再做决策，因为到了这个阶段，不太会有回头路。</h2>]]>
    </content>
    <id>https://lichuanyang.top/posts/2345/</id>
    <link href="https://lichuanyang.top/posts/2345/"/>
    <published>2022-01-04T13:49:57.000Z</published>
    <summary>2021 年终总结：行业剧变下的职业转折、个人成长目标的进展，以及对未来的思考。</summary>
    <title>剧变中的2021-一个中年工程师的年终总结</title>
    <updated>2026-06-27T02:31:54.492Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="云原生" scheme="https://lichuanyang.top/categories/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <category term="云原生" scheme="https://lichuanyang.top/tags/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <category term="持续集成" scheme="https://lichuanyang.top/tags/%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90/"/>
    <category term="金丝雀" scheme="https://lichuanyang.top/tags/%E9%87%91%E4%B8%9D%E9%9B%80/"/>
    <content>
      <![CDATA[<p>金丝雀发布其实是一种非常适合在云原生体系下进行的流程，因此相信很多人有在kubernetes下做金丝雀发布的需求。通过本文，我们就介绍一种非常简单的做金丝雀发布的思路。</p><span id="more"></span><h2 id="什么是金丝雀发布"><a href="#什么是金丝雀发布" class="headerlink" title="什么是金丝雀发布"></a>什么是金丝雀发布</h2><p>首先介绍一下金丝雀发布是什么，金丝雀这个名字，起源是，矿井工人发现，金丝雀对瓦斯气体很敏感，矿工会在下井之前，先放一只金丝雀到井中，如果金丝雀不叫了，就代表瓦斯浓度高。</p><p>在系统层面的具体含义就是，发布开始后，先启动一个新版本应用，但是并不直接将流量切过来，而是测试人员对新版本进行线上测试，启动的这个新版本应用，就是我们的金丝雀。在金丝雀上测试没有问题后，再将线上的流量切换到新版本上。</p><p>再具体点讲，可以将一个域名映射到两组服务器上，一组是正式的线上环境，另一组就可以是金丝雀环境了。</p><p>假如说我们不是用k8s做部署，而是直接部署到多台服务器上的话，金丝雀部署就很简单，任意指定一台或多台机器做金丝雀即可。</p><p>而在k8s环境下，由于k8s接管了部署的流程，我们就需要做一些别的事情来实现这个效果。</p><h2 id="K8s-环境下的金丝雀发布方案"><a href="#K8s-环境下的金丝雀发布方案" class="headerlink" title="K8s 环境下的金丝雀发布方案"></a>K8s 环境下的金丝雀发布方案</h2><p>有一种很简单的思路，就是创建两个deployment, 但是通过label关联到同一个service上，就可以将同一个service的流量分配到两组容器中了。两个deployment可以分别部署，也就可以部署不同版本的镜像了。</p><h2 id="部署配置示例"><a href="#部署配置示例" class="headerlink" title="部署配置示例"></a>部署配置示例</h2><p>例如下边两个deployment</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-deployment</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">3</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">  <span class="attr">template:</span></span><br><span class="line">    <span class="attr">metadata:</span></span><br><span class="line">      <span class="attr">labels:</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">nginx:1.7.9</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-service</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx-port</span></span><br><span class="line">    <span class="attr">protocol:</span> <span class="string">TCP</span></span><br><span class="line">    <span class="attr">port:</span> <span class="number">80</span></span><br><span class="line">    <span class="attr">nodePort:</span> <span class="number">32600</span></span><br><span class="line">    <span class="attr">targetPort:</span> <span class="number">80</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">NodePort</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-deployment-canary</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">    <span class="attr">track:</span> <span class="string">canary</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">1</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">      <span class="attr">track:</span> <span class="string">canary</span></span><br><span class="line">  <span class="attr">template:</span></span><br><span class="line">    <span class="attr">metadata:</span></span><br><span class="line">      <span class="attr">labels:</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">nginx</span></span><br><span class="line">        <span class="attr">track:</span> <span class="string">canary</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span></span><br><span class="line">        <span class="attr">image:</span> <span class="string">nginx:1.8.0</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>只在第一个deployment中配置service，其配置的selector规则是app:nginx, 然后两个deployment都打上app:nginx的label.</p><p>这样就实现了我们所说的效果。</p><h2 id="局限性"><a href="#局限性" class="headerlink" title="局限性"></a>局限性</h2><p>当然，这种方式只是实现了一种非常简单的金丝雀发布流程，是没有办法做更精细，比如说按用户信息去灰度的规则的。对于同一个用户，也有可能一次请求到了金丝雀中，下一次请求又到了正式环境中。假如需要更加精细的灰度规则，可以考虑采用spring cloud, istio等工具。</p><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h3 id="Q-金丝雀发布和蓝绿部署、滚动更新有什么区别？"><a href="#Q-金丝雀发布和蓝绿部署、滚动更新有什么区别？" class="headerlink" title="Q: 金丝雀发布和蓝绿部署、滚动更新有什么区别？"></a>Q: 金丝雀发布和蓝绿部署、滚动更新有什么区别？</h3><ul><li><strong>滚动更新</strong>：逐批替换旧 Pod，新版本逐步上线。最简单，但新旧版本短暂共存期间流量混合，回滚只能反向逐批操作。</li><li><strong>蓝绿部署</strong>：准备一套完整的”绿色”新环境，测试通过后一次性切换流量。切换快、回滚快，但需要双倍资源。</li><li><strong>金丝雀发布</strong>：先放一小部分流量到新版本（金丝雀），验证无误后逐步扩大。最安全，但需要更精细的流量控制能力。</li></ul><p>三种方式不是互斥的：金丝雀可以看作蓝绿的”渐进版”，实际项目中常常组合使用。</p><h3 id="Q-这种双-Deployment-方案的流量能精确控制吗？"><a href="#Q-这种双-Deployment-方案的流量能精确控制吗？" class="headerlink" title="Q: 这种双 Deployment 方案的流量能精确控制吗？"></a>Q: 这种双 Deployment 方案的流量能精确控制吗？</h3><p>不能精确控制百分比。Service 通过 kube-proxy 的 iptables&#x2F;ipvs 规则做负载均衡，默认是轮询，分配到两个 Deployment 的 Pod 数量比例大致等于 replica 数量比。如果你部署 3 个旧版本 Pod + 1 个新版本 Pod，大约 25% 流量到新版本——但这只是统计近似，不是精确控制。</p><p>如果需要精确的流量百分比（比如严格 10% 到金丝雀），需要借助 Ingress Controller（如 Nginx Ingress 的 canary 注解）或 Service Mesh（如 Istio 的 VirtualService）。</p><h3 id="Q-如果想按用户维度做灰度怎么办？"><a href="#Q-如果想按用户维度做灰度怎么办？" class="headerlink" title="Q: 如果想按用户维度做灰度怎么办？"></a>Q: 如果想按用户维度做灰度怎么办？</h3><p>本文介绍的方案是基于<strong>流量百分比</strong>的简单灰度，无法按用户维度（比如 VIP 用户走新版本、内部员工走新版本）做路由。如果需要按用户维度灰度，推荐：</p><ul><li><strong>Istio &#x2F; Linkerd</strong>：通过 HTTP Header（如 Cookie、User-Agent）做路由规则，可以做到”userId&#x3D;xxx 的请求走金丝雀”</li><li><strong>Spring Cloud Gateway</strong>：在网关层根据请求参数做路由</li><li><strong>Nginx Ingress canary-by-header</strong>：基于特定 Header 值路由到 canary 服务</li></ul><p>原文地址: <a href="https://lichuanyang.top/posts/30764/">https://lichuanyang.top/posts/30764/</a></p>]]>
    </content>
    <id>https://lichuanyang.top/posts/30764/</id>
    <link href="https://lichuanyang.top/posts/30764/"/>
    <published>2021-12-23T10:55:20.000Z</published>
    <summary>在 Kubernetes 环境下实现金丝雀发布的简单思路，通过逐步引流新版本来降低发布风险。</summary>
    <title>kubernetes环境下做金丝雀发布的一种思路</title>
    <updated>2026-06-27T02:31:54.495Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="云原生" scheme="https://lichuanyang.top/categories/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <category term="监控" scheme="https://lichuanyang.top/tags/%E7%9B%91%E6%8E%A7/"/>
    <category term="云原生" scheme="https://lichuanyang.top/tags/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <category term="prometheus" scheme="https://lichuanyang.top/tags/prometheus/"/>
    <category term="grafana" scheme="https://lichuanyang.top/tags/grafana/"/>
    <category term="prometheus教程" scheme="https://lichuanyang.top/tags/prometheus%E6%95%99%E7%A8%8B/"/>
    <content>
      <![CDATA[<p>作为云原生体系下的“默认”监控系统，prometheus正在获得越来越广泛的关注。今天，我们就写一篇教程，讲一下prometheus的设计理念，看看它是如何用非常简单的设计支撑起如此复杂的功能的。</p><span id="more"></span><p>首先，我们来思考一下，如果要做一个类似prometheus的监控系统，都有哪些难点，比如</p><ul><li>每个服务的监控需求都不一样，那么对于监控系统来说，要怎么设计其数据模型，才能取得易用性和通用性之间的平衡</li><li>大量的数据量要如何存储</li><li>怎样能实现各种复杂的报表</li><li>…</li></ul><p>带着这些问题，我们就来看看prometheus是怎么设计的。</p><h2 id="历史"><a href="#历史" class="headerlink" title="历史"></a>历史</h2><p>让我们先从历史说起，prometheus最早由SoundCloud开发，后来捐赠到开源社区。在2016年假如CNCF, 即云原生计算基金会。Prometheus是CNCF的第二个项目，仅次于kubernets。 因此，可想而知，promethous在整个云原生体系中有多么重要的作用。Prometheus也逐渐成了云原生下监控系统的事实标准。</p><h2 id="核心设计理念"><a href="#核心设计理念" class="headerlink" title="核心设计理念"></a>核心设计理念</h2><p>对于一个监控系统来说，核心要解决的问题其实就三个：</p><ol><li>监控指标用什么形式表示</li><li>怎么收集和存储指标</li><li>怎么利用指标生成报表</li></ol><p>对于这三个问题，prometheus都给出了很巧妙的解决方案。</p><h2 id="数据模型"><a href="#数据模型" class="headerlink" title="数据模型"></a>数据模型</h2><p>romethous的数据模型，简而言之，就是一个「时序」的 Metric数据。所谓metric, 就是数据的测量值，而所谓时序，就是这些metric, 会源源不断的产生不同时间点的数据。</p><p>Metric有唯一的名称标识，也可以设置多个label, 可以用于过滤和聚合，其格式如下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;metric name&gt;&#123;&lt;label name&gt;=&lt;label value&gt;, ...&#125;</span><br></pre></td></tr></table></figure><p>这样，对于任何业务，我们都可以将监控数据设计成统一的metric格式。这样对于promethous来说，方案可以足够简单，只用处理这一种数据格式就可以。而同时又足以方便的应对千变万化的业务场景。</p><p>Prometheus提供了 counter, gauge, histogram, summary 四种核心的metric, 不过其区别仅体现在client端和promQL中。截至目前(2021.11)， 不同的metric 类型在 prometheus server 这一侧并不会有什么区别，</p><h2 id="数据收集和存储"><a href="#数据收集和存储" class="headerlink" title="数据收集和存储"></a>数据收集和存储</h2><p>Prometheus server会定时从要监控的服务暴露出的http接口上抓取数据，是一种典型的拉模型。</p><p>相对推模型，拉模型会有一些好处，比如更容易监测某一个节点是否正常；更容易本地调试等。当然，对于一个监控系统来说，采用推还是拉，其实并不是一个主要问题。</p><p>Prometheus的数据是典型的时序数据，prometheus本身会将数据存储在本地磁盘上。要注意的是，本地存储不可复制，无法构建集群，如果本地磁盘或节点出现故障，存储将无法扩展和迁移。因此一般只能把本地存储视为近期数据的短暂滑动窗口。</p><p>而关于持久化存储的问题，prometheus实际上并没有试图解决。它的做法是定义出标准的读写接口，从而可以将数据存储到任意一个第三方存储上。</p><h2 id="生成报表"><a href="#生成报表" class="headerlink" title="生成报表"></a>生成报表</h2><p>Prometheus定义了功能强大的promQL, 可以满足各种复杂的查询场景，具体可参考 <a href="https://prometheus.io/docs/prometheus/latest/querying/basics/">https://prometheus.io/docs/prometheus/latest/querying/basics/</a></p><h2 id="周边生态"><a href="#周边生态" class="headerlink" title="周边生态"></a>周边生态</h2><p>一个开源项目的发展，当然离不开周边生态的发展。而prometheus目前已经有了很完善的生态，在java, go, python等主流的开发语言下，都有完善的client包可以使用； 像spring中，可以很容易的为多种组件增加打点，这一点，在下边的实战环节我们会细讲；在kubernetes中，可以轻易的配置自动去各个节点抓取prometheus数据；借助grafana等工具，也可以配置出多种多样的报表。</p><h2 id="实战"><a href="#实战" class="headerlink" title="实战"></a>实战</h2><p>教程的接下来一部分，我们会以springboot项目为例，来看一看prometheus的实际效果。</p><p>其核心思路就是使用spring-actuator 为springboot应用配置监控，并以promethous的结构暴露出来。</p><p>首先，引入依赖</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">implementation(&quot;org.springframework.boot:spring-boot-starter-actuator&quot;)</span><br><span class="line">implementation(&quot;io.micrometer:micrometer-registry-prometheus&quot;)</span><br></pre></td></tr></table></figure><p>然后添加spring配置</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">management:</span><br><span class="line">  endpoints:</span><br><span class="line">    web:</span><br><span class="line">      exposure:</span><br><span class="line">        include: &quot;prometheus&quot;</span><br><span class="line">  metrics:</span><br><span class="line">    distribution:</span><br><span class="line">      sla:</span><br><span class="line">        http:</span><br><span class="line">          server:</span><br><span class="line">            requests: &quot;100ms,150ms,250ms,500ms,1s&quot;</span><br><span class="line">      percentiles-histogram:</span><br><span class="line">        http:</span><br><span class="line">          server:</span><br><span class="line">            requests: true</span><br><span class="line">    web:</span><br><span class="line">      server:</span><br><span class="line">        request:</span><br><span class="line">          autotime:</span><br><span class="line">            enabled: true</span><br><span class="line">    export:</span><br><span class="line">      prometheus:</span><br><span class="line">        enabled: true</span><br><span class="line">    tags:</span><br><span class="line">      application: name</span><br></pre></td></tr></table></figure><p>这个配置里，其实做了几件事：将数据以prometheus的格式暴露出来；自动为http请求添加histogram监控；增加一个application标识，这个标识会作为一个label出现在所有metric中。</p><p>之后，启动springboot项目，并且访问&#x2F;actuator&#x2F;prometheus路径，就可以看到大量metric, 比如</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"># HELP executor_pool_size_threads The current number of threads in the pool</span><br><span class="line"># TYPE executor_pool_size_threads gauge</span><br><span class="line">executor_pool_size_threads&#123;application=&quot;ads-programad&quot;,name=&quot;asyncExecutor&quot;,&#125; 0.0</span><br><span class="line"># HELP tomcat_servlet_request_seconds  </span><br><span class="line"># TYPE tomcat_servlet_request_seconds summary</span><br><span class="line">tomcat_servlet_request_seconds_count&#123;application=&quot;ads-programad&quot;,name=&quot;dispatcherServlet&quot;,&#125; 1.0</span><br><span class="line">tomcat_servlet_request_seconds_sum&#123;application=&quot;ads-programad&quot;,name=&quot;dispatcherServlet&quot;,&#125; 0.0</span><br><span class="line"># HELP executor_pool_core_threads The core number of threads for the pool</span><br><span class="line"># TYPE executor_pool_core_threads gauge</span><br><span class="line">executor_pool_core_threads&#123;application=&quot;ads-programad&quot;,name=&quot;asyncExecutor&quot;,&#125; 70.0</span><br><span class="line"># HELP jvm_classes_unloaded_classes_total The total number of classes unloaded since the Java virtual machine has started execution</span><br><span class="line"># TYPE jvm_classes_unloaded_classes_total counter</span><br><span class="line">jvm_classes_unloaded_classes_total&#123;application=&quot;ads-programad&quot;,&#125; 0.0</span><br><span class="line"># HELP executor_completed_tasks_total The approximate total number of tasks that have completed execution</span><br><span class="line"># TYPE executor_completed_tasks_total counter</span><br><span class="line">executor_completed_tasks_total&#123;application=&quot;ads-programad&quot;,name=&quot;asyncExecutor&quot;,&#125; 0.0</span><br><span class="line"># HELP tomcat_threads_config_max_threads  </span><br><span class="line"># TYPE tomcat_threads_config_max_threads gauge</span><br><span class="line">tomcat_threads_config_max_threads&#123;application=&quot;ads-programad&quot;,name=&quot;http-nio-9000&quot;,&#125; 500.0</span><br><span class="line"># HELP process_cpu_usage The &quot;recent cpu usage&quot; for the Java Virtual Machine process</span><br><span class="line"># TYPE process_cpu_usage gauge</span><br><span class="line">process_cpu_usage&#123;application=&quot;ads-programad&quot;,&#125; 0.0</span><br><span class="line"># HELP tomcat_sessions_active_current_sessions  </span><br><span class="line"># TYPE tomcat_sessions_active_current_sessions gauge</span><br><span class="line">tomcat_sessions_active_current_sessions&#123;application=&quot;ads-programad&quot;,&#125; 0.0</span><br><span class="line"># HELP jvm_memory_committed_bytes The amount of memory in bytes that is committed for the Java virtual machine to use</span><br><span class="line"># TYPE jvm_memory_committed_bytes gauge</span><br><span class="line">jvm_memory_committed_bytes&#123;application=&quot;ads-programad&quot;,area=&quot;heap&quot;,id=&quot;G1 Eden Space&quot;,&#125; 3.5651584E7</span><br><span class="line">jvm_memory_committed_bytes&#123;application=&quot;ads-programad&quot;,area=&quot;heap&quot;,id=&quot;G1 Old Gen&quot;,&#125; 4.6137344E7</span><br><span class="line">jvm_memory_committed_bytes&#123;application=&quot;ads-programad&quot;,area=&quot;nonheap&quot;,id=&quot;Compressed Class Space&quot;,&#125; 5767168.0</span><br><span class="line">jvm_memory_committed_bytes&#123;application=&quot;ads-programad&quot;,area=&quot;nonheap&quot;,id=&quot;CodeHeap &#x27;non-profiled nmethods&#x27;&quot;,&#125; 8847360.0</span><br><span class="line">jvm_memory_committed_bytes&#123;application=&quot;ads-programad&quot;,area=&quot;nonheap&quot;,id=&quot;CodeHeap &#x27;non-nmethods&#x27;&quot;,&#125; 2555904.0</span><br><span class="line">jvm_memory_committed_bytes&#123;application=&quot;ads-programad&quot;,area=&quot;nonheap&quot;,id=&quot;Metaspace&quot;,&#125; 4.2287104E7</span><br><span class="line">jvm_memory_committed_bytes&#123;application=&quot;ads-programad&quot;,area=&quot;heap&quot;,id=&quot;G1 Survivor Space&quot;,&#125; 4194304.0</span><br><span class="line"># HELP tomcat_servlet_request_max_seconds  </span><br><span class="line"># TYPE tomcat_servlet_request_max_seconds gauge</span><br><span class="line">tomcat_servlet_request_max_seconds&#123;application=&quot;ads-programad&quot;,name=&quot;dispatcherServlet&quot;,&#125; 0.0</span><br><span class="line"># HELP tomcat_connections_current_connections  </span><br><span class="line"># TYPE tomcat_connections_current_connections gauge</span><br><span class="line">tomcat_connections_current_connections&#123;application=&quot;ads-programad&quot;,name=&quot;http-nio-9000&quot;,&#125; 3.0</span><br><span class="line"># HELP tomcat_sessions_active_max_sessions  </span><br><span class="line"># TYPE tomcat_sessions_active_max_sessions gauge</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>其中，除了我们显式配置的http监控，其实还有大量的jvm, 机器负载等基础的监控信息。</p><p>除此之外，对于其他组件的监控也很容易添加，诸如线程池、http连接池、自定义监控等，可以参考 <a href="https://github.com/lcy362/springboot-prometheus-demo">https://github.com/lcy362/springboot-prometheus-demo</a></p><p>这样，无论这个springboot项目如何部署，无论是用java原生的部署，还是用docker部署，还是部署在kubernetes上，都可以非常容易的获取各个监控metrics数据。</p><h2 id="快速上手步骤"><a href="#快速上手步骤" class="headerlink" title="快速上手步骤"></a>快速上手步骤</h2><h3 id="Step-1-安装和配置-Prometheus"><a href="#Step-1-安装和配置-Prometheus" class="headerlink" title="Step 1: 安装和配置 Prometheus"></a>Step 1: 安装和配置 Prometheus</h3><p>首先在服务器上下载并安装 Prometheus，编辑 <code>prometheus.yml</code> 配置文件，设置 global 抓取间隔和 evaluation interval 等基本参数。启动 Prometheus 后，访问 <code>http://localhost:9090</code> 即可打开 Web UI 界面。</p><h3 id="Step-2-配置数据采集目标"><a href="#Step-2-配置数据采集目标" class="headerlink" title="Step 2: 配置数据采集目标"></a>Step 2: 配置数据采集目标</h3><p>在配置文件的 <code>scrape_configs</code> 中定义需要监控的目标，包括你的应用暴露的 <code>/actuator/prometheus</code> 端点地址。通过 Spring Actuator 和 Micrometer 将 Spring Boot 应用的指标以 Prometheus 格式暴露出来，Prometheus 会按配置的间隔自动拉取数据。</p><h3 id="Step-3-编写-PromQL-查询"><a href="#Step-3-编写-PromQL-查询" class="headerlink" title="Step 3: 编写 PromQL 查询"></a>Step 3: 编写 PromQL 查询</h3><p>利用 Prometheus 内置的 PromQL 查询语言，可以对收集到的时序数据进行灵活查询。你可以按 metric 名称和 label 进行过滤、聚合、计算速率等操作，满足各种监控场景的分析需求。</p><h3 id="Step-4-对接-Grafana-可视化"><a href="#Step-4-对接-Grafana-可视化" class="headerlink" title="Step 4: 对接 Grafana 可视化"></a>Step 4: 对接 Grafana 可视化</h3><p>将 Prometheus 作为数据源添加到 Grafana 中，利用 Grafana 丰富的面板和仪表盘功能，将监控数据以图表等可视化形式呈现。通过 Grafana 还可以设置告警规则，在指标异常时及时通知。</p><p>原文地址: <a href="https://lichuanyang.top/posts/28288/">https://lichuanyang.top/posts/28288/</a></p><hr>]]>
    </content>
    <id>https://lichuanyang.top/posts/28288/</id>
    <link href="https://lichuanyang.top/posts/28288/"/>
    <published>2021-11-10T11:54:05.000Z</published>
    <summary>Prometheus 监控系统教程，从设计理念出发讲清楚它如何用简单设计支撑复杂功能。</summary>
    <title>prometheus教程： 一篇文章讲懂prometheus</title>
    <updated>2026-06-27T03:42:25.686Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="Java" scheme="https://lichuanyang.top/categories/Java/"/>
    <category term="开源项目" scheme="https://lichuanyang.top/tags/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE/"/>
    <content>
      <![CDATA[<p>一个高性能的 Java IP 地址归属国家查询工具，核心思路是用二分查找在预加载的 IP 段数组中定位，单次查询延迟在微秒级。</p><span id="more"></span><h2 id="设计思路"><a href="#设计思路" class="headerlink" title="设计思路"></a>设计思路</h2><p>IP 地址本质上是一个 32 位整数。所谓”IP 段”就是一段连续的整数范围。给定一个 IP，我们要找到它落在哪个 IP 段中——这是一个典型的<strong>区间查找</strong>问题。</p><p>最直接的做法是遍历所有 IP 段，O(n) 复杂度。但全球 IP 段有几十万个，遍历太慢。更好的做法是把 IP 段的起始地址排序后放入数组，用<strong>二分查找</strong>定位，O(log n) 复杂度，几十万个段只需要约 19 次比较。</p><h2 id="数据源"><a href="#数据源" class="headerlink" title="数据源"></a>数据源</h2><p>IP 地址库数据可以从 <a href="http://download.ip2location.com/lite/">IP2Location Lite</a> 免费获取。数据格式示例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">16781312,JP</span><br><span class="line">16785408,CN</span><br><span class="line">16793600,JP</span><br></pre></td></tr></table></figure><p>每行包含两个字段：IP 段的<strong>起始地址</strong>（转换后的整数）和<strong>国家代号</strong>。</p><p>原始数据其实还包含 IP 段的结束地址，但 IP2Location 的数据段是<strong>连续无间隙</strong>的——一个段的结束地址恰好是下一个段的起始地址减一。因此我们可以只存储起始地址，结束地址自然而然地由下一个段的起始地址确定。这个优化节省了一半的内存。</p><h2 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">IpCountryLookup</span> &#123;</span><br><span class="line">    <span class="comment">// IP 段的起始地址（有序）</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">long</span>[] startIps;</span><br><span class="line">    <span class="comment">// 对应的国家代号</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String[] countryCodes;</span><br><span class="line">    <span class="comment">// 总段数</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> size;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>两个并行数组：<code>startIps[i]</code> 和 <code>countryCodes[i]</code> 一一对应。二分查找在 <code>startIps</code> 上进行，找到后从 <code>countryCodes</code> 中取结果。</p><h2 id="核心实现：二分查找"><a href="#核心实现：二分查找" class="headerlink" title="核心实现：二分查找"></a>核心实现：二分查找</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> String <span class="title function_">lookup</span><span class="params">(String ip)</span> &#123;</span><br><span class="line">    <span class="type">long</span> <span class="variable">ipLong</span> <span class="operator">=</span> ipToLong(ip);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 二分查找：找最后一个 &lt;= ipLong 的起始地址</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">left</span> <span class="operator">=</span> <span class="number">0</span>, right = size - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">while</span> (left &lt;= right) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">mid</span> <span class="operator">=</span> left + (right - left) / <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (startIps[mid] &lt;= ipLong) &#123;</span><br><span class="line">            left = mid + <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            right = mid - <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// right 就是最后一个 &lt;= ipLong 的位置</span></span><br><span class="line">    <span class="keyword">if</span> (right &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> countryCodes[right];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;Unknown&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// IP 字符串转 long</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">long</span> <span class="title function_">ipToLong</span><span class="params">(String ip)</span> &#123;</span><br><span class="line">    String[] parts = ip.split(<span class="string">&quot;\\.&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> (Long.parseLong(parts[<span class="number">0</span>]) &lt;&lt; <span class="number">24</span>)</span><br><span class="line">         + (Long.parseLong(parts[<span class="number">1</span>]) &lt;&lt; <span class="number">16</span>)</span><br><span class="line">         + (Long.parseLong(parts[<span class="number">2</span>]) &lt;&lt; <span class="number">8</span>)</span><br><span class="line">         + Long.parseLong(parts[<span class="number">3</span>]);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="性能数据"><a href="#性能数据" class="headerlink" title="性能数据"></a>性能数据</h2><ul><li><strong>内存占用</strong>：约 30 万条 IP 段，两个数组各 ~30 万元素，总计约 4 MB</li><li><strong>查询延迟</strong>：单次查询 &lt; 10 微秒（二分查找约 19 次比较）</li><li><strong>QPS</strong>：单机轻松支撑百万级</li><li><strong>数据更新</strong>：IP 库约每月更新一次，定时任务自动拉取并重载</li></ul><h2 id="其他数据源对比"><a href="#其他数据源对比" class="headerlink" title="其他数据源对比"></a>其他数据源对比</h2><table><thead><tr><th>数据源</th><th>精度</th><th>免费版</th><th>特点</th></tr></thead><tbody><tr><td>IP2Location Lite</td><td>国家</td><td>✓</td><td>数据干净，格式统一</td></tr><tr><td>GeoIP2（MaxMind）</td><td>城市</td><td>✓（有限）</td><td>社区认可度高，Java API 完善</td></tr><tr><td>纯真 IP 库</td><td>运营商</td><td>✓</td><td>国内精度最高</td></tr><tr><td>ipip.net</td><td>城市&#x2F;运营商</td><td>✗</td><td>国内最准确，但收费</td></tr></tbody></table><p>如果只需要国家粒度，IP2Location Lite 足够。需要城市精度则推荐 MaxMind GeoLite2。</p><h2 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h2><p>项目基于 Spring Boot，支持两种运行方式：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 直接运行</span></span><br><span class="line">mvn spring-boot:run</span><br><span class="line"></span><br><span class="line"><span class="comment"># Docker</span></span><br><span class="line">docker run -p 8080:8080 lcy362/ip-country</span><br></pre></td></tr></table></figure><p>在线体验：<a href="http://ip-country.lichuanyang.top/">http://ip-country.lichuanyang.top/</a></p><p>项目源码：<a href="https://github.com/lcy362/ip-country">https://github.com/lcy362/ip-country</a></p><hr>]]>
    </content>
    <id>https://lichuanyang.top/posts/36780/</id>
    <link href="https://lichuanyang.top/posts/36780/"/>
    <published>2021-10-12T11:25:15.000Z</published>
    <summary>用 Java 实现高性能 IP 地址归属国家查询工具，基于 IP2Location 数据源，支持定时更新。</summary>
    <title>实现一个简单的java版本高性能获取ip地址所属国家工具</title>
    <updated>2026-06-27T03:45:23.489Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="技术杂谈" scheme="https://lichuanyang.top/categories/%E6%8A%80%E6%9C%AF%E6%9D%82%E8%B0%88/"/>
    <category term="iterm2" scheme="https://lichuanyang.top/tags/iterm2/"/>
    <category term="ssh自动登录" scheme="https://lichuanyang.top/tags/ssh%E8%87%AA%E5%8A%A8%E7%99%BB%E5%BD%95/"/>
    <category term="ssh免密码登录" scheme="https://lichuanyang.top/tags/ssh%E5%85%8D%E5%AF%86%E7%A0%81%E7%99%BB%E5%BD%95/"/>
    <content>
      <![CDATA[<h2 id="痛点：频繁-SSH-登录"><a href="#痛点：频繁-SSH-登录" class="headerlink" title="痛点：频繁 SSH 登录"></a>痛点：频繁 SSH 登录</h2><p>如果你像我一样，需要经常性的访问不同的远程服务器，记录服务器的ip和输入密码就是一件非常痛苦的事情。好在，通过在item2中做一些配置，可以很好的解决这个痛点。最终实现的效果，就是类似配置了一些ssh书签，能够在iterm2中记住ssh密码, 实现免密码登录和自动登录的效果。</p><span id="more"></span><h2 id="完整配置步骤"><a href="#完整配置步骤" class="headerlink" title="完整配置步骤"></a>完整配置步骤</h2><p>iterm2 （<a href="https://iterm2.com/">https://iterm2.com/</a>)  是mac下使用非常广泛的一款终端替代产品，提供了很多强大的功能。要实现ssh书签，实现免密码登录、自动登录的效果，关键点是其中的三个特性：profile, trigger 和 password manager.</p><p>profile顾名思义就是一套配置，像我们正常打开iterm2时，其实就是打开了default profile.  配置profile的入口就在工具栏 profiles 选项下，可以增加或编辑现有profile.  我们将需要的profile的general标签下的 commond 模块修改为 Command,  内容填入 ssh命令， 比如 ssh <a href="mailto:&#x72;&#111;&#x6f;&#116;&#x40;&#x31;&#46;&#x31;&#46;&#49;&#x2e;&#49;">root@1.1.1.1</a>, 就可以在打开profile时自动执行ssh命令。 profile中其他的文本、颜色等配置都不重要，可以按需填写。</p><p>trigger也是profile的一个特性，入口在profile配置页的advanced标签下，它的作用就是利用关键词触发一个动作，我们现在要做的就是用password这个关键词触发打开 password manager。 操作很简单，就是增加一个trigger, regular expression 填入 password,  action选择 open password manager, 注意勾选instant和enabled两个选项。</p><p>最后一个要配置的是password manager, password manager 就是一个密码管理器，是item2中会默认安装的一款插件，入口在工具栏 window 标签下。打开password manager, 将需要保存的密码都录入进去就可以了。</p><h2 id="安全提醒"><a href="#安全提醒" class="headerlink" title="安全提醒"></a>安全提醒</h2><p>这样，我们就实现了在iterm2中用“书签”保存远程服务器的地址和密码。使用时，直接访问对应的profile, 等待password manager 弹出，选择对应的密码记录，点击输入就可以了。</p><p>原文地址: <a href="https://lichuanyang.top/posts/20763/">https://lichuanyang.top/posts/20763/</a></p><hr><h2 id="快速上手步骤"><a href="#快速上手步骤" class="headerlink" title="快速上手步骤"></a>快速上手步骤</h2><h3 id="Step-1-生成SSH密钥"><a href="#Step-1-生成SSH密钥" class="headerlink" title="Step 1: 生成SSH密钥"></a>Step 1: 生成SSH密钥</h3><p>在终端执行 <code>ssh-keygen</code> 生成 SSH 密钥对，将公钥添加到目标服务器的 <code>~/.ssh/authorized_keys</code> 中，确保免密登录基础配置完成。</p><h3 id="Step-2-配置iTerm2-Profile"><a href="#Step-2-配置iTerm2-Profile" class="headerlink" title="Step 2: 配置iTerm2 Profile"></a>Step 2: 配置iTerm2 Profile</h3><p>打开 iTerm2，进入 Profiles → Open Profiles → Edit Profiles。在 General 标签页中将 Command 设置为 <code>ssh root@1.1.1.1</code> 格式的 SSH 命令。</p><h3 id="Step-3-添加书签"><a href="#Step-3-添加书签" class="headerlink" title="Step 3: 添加书签"></a>Step 3: 添加书签</h3><p>在 Profile 编辑页的 Advanced 标签页中添加 Trigger：Regular Expression 填入 <code>password</code>，Action 选择 Open Password Manager，勾选 Instant 和 Enabled。然后在 Window → Password Manager 中录入服务器密码。</p><h3 id="Step-4-验证自动登录"><a href="#Step-4-验证自动登录" class="headerlink" title="Step 4: 验证自动登录"></a>Step 4: 验证自动登录</h3><p>通过 Profiles 菜单选择配置好的 Profile，等待 Password Manager 弹出后选择对应密码记录，确认能自动完成 SSH 登录。</p><hr>]]>
    </content>
    <id>https://lichuanyang.top/posts/20763/</id>
    <link href="https://lichuanyang.top/posts/20763/"/>
    <published>2021-10-08T08:45:42.000Z</published>
    <summary>通过 iTerm2 配置 SSH 书签，实现记住密码和自动登录，告别反复输入 IP 和密码的痛苦。</summary>
    <title>iterm2配置ssh书签, 实现记住密码和自动登录</title>
    <updated>2026-06-27T02:14:57.843Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="技术杂谈" scheme="https://lichuanyang.top/categories/%E6%8A%80%E6%9C%AF%E6%9D%82%E8%B0%88/"/>
    <category term="技术分享" scheme="https://lichuanyang.top/tags/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
    <category term="技术分享选题" scheme="https://lichuanyang.top/tags/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB%E9%80%89%E9%A2%98/"/>
    <content>
      <![CDATA[<p>技术分享基本在每个公司都会有。分享的效果因人，因团队而异。在有些团队中，分享会因为种种原因，逐渐沦为一个没什么用的时间杀手。</p><p>写这篇文章，是希望从选题等方面出发，通过一些比较基础的规则，提升技术分享的下限，争取让80%的人都能做出一次80分的技术分享。</p><span id="more"></span><h2 id="选题"><a href="#选题" class="headerlink" title="选题"></a>选题</h2><h3 id="基本原则"><a href="#基本原则" class="headerlink" title="基本原则"></a><strong>基本原则</strong></h3><p>首先要了解到的是，线下的技术分享不是分享知识的唯一形式。其他的，比如写博客写文章，拍视频，或者简单的分享一两句话，都是常见的分享形式。针对不同的知识，要考虑用什么样的方式进行分享更加合适。</p><p>比如，对于某项技术的入门介绍，推荐以文章的形式进行。读者可以进行快速的浏览或者对关键信息进行检索，要比坐下来听一遍效率高很多。</p><h3 id="推荐选题"><a href="#推荐选题" class="headerlink" title="推荐选题"></a><strong>推荐选题</strong></h3><p>通常来说，比较好的选题需要有较强的实用性，能够吸引到听众，且难度适中。能够让听众在听得懂和有新收获之间达到一个平衡。</p><p>举一些比较好的选题例子，以及一些注意事项：</p><ul><li>对于新技术的介绍，需要介绍清楚新技术的产生背景，解决了什么问题，相对之前的类似解决方案有什么优缺点，引入了什么新问题，等；</li><li>对晦涩难懂的技术的讲解，这类选题粒度可以小一些，需要在充分理解问题的基础上有较好的分享技巧，保证大家听的明白；</li><li>比较细节的技术深入讨论，与上一类类似，要注意背景介绍；</li><li>对于一类问题的高度总结，比如cache的一致性、分布式id、分布式事务等，这类话题可以从实际存在的问题切入，分析不同解决方案的优劣，以及技术方案的演进等；</li><li>业务分享，这一类最好是选择常见的业务领域，同时要注意对背景的介绍；</li></ul><h2 id="内容准备"><a href="#内容准备" class="headerlink" title="内容准备"></a>内容准备</h2><p>对于分享效果，虽然会受各方面的影响，但归根结底最重要的还是分享内容中是否有足够的干货。</p><p>为了能够有干货, 最好的方法还是平常的积累，建议平常多思考，对平常工作中用到的东西建立起更深的认知。推荐阅读《卡片笔记写作法》这本书，平时有持续输入，才能有输出产生。</p><p>此外，</p><p>准备过程中注意思考和总结，形成自己的观点。单纯对其他人的东西做搬运，实际上造成的是自己时间的浪费；</p><p>最好能将实际的应用场景融汇到分享内容中；</p><p>比较难的知识点，注意分享技巧，让听众能听明白，可以考虑先就分享的关键部分，先找一两个同事试讲；</p><p>通过看视频，除了可以了解分享内容本身，也可以了解到其他人是怎么讲的，对分享效果比较有帮助。</p><p>除了分享的真正内容外，建议增加以下内容的准备：</p><p>预习资料。可以提前准备一些有助于理解分享内容的资料，比如某本书的某几章，某篇文章等，要求大家提前阅读。分享者需要给出每个材料的预计阅读时间，单次分享整体需要的阅读时间不宜超过1个小时。</p><p>对背景的详细阐述，尤其是涉及到一些业务知识或者不常见的场景时。</p><h2 id="分享流程"><a href="#分享流程" class="headerlink" title="分享流程"></a>分享流程</h2><p>准备好分享内容后，需要找人进行review，reviewer可以是自己的mentor, leader, 或者其他在相关领域非常熟悉的人。</p><p>reviewer在充分理解分享内容的基础上，主要对选题和分享内容进行审核，确保满足本文列举的其他要求。同时尽量保证分享内容不要有技术性的错误。</p><p>分享材料一定要提前发出来，让听众有提前熟悉的机会。</p><p>分享时注意语速控制，不宜过快。</p><p>原文地址: <a href="https://lichuanyang.top/posts/54216/">https://lichuanyang.top/posts/54216/</a></p><hr>]]>
    </content>
    <id>https://lichuanyang.top/posts/54216/</id>
    <link href="https://lichuanyang.top/posts/54216/"/>
    <published>2021-08-31T06:27:43.000Z</published>
    <summary>从选题、结构到演讲技巧，系统性地分享如何做出一次 80 分以上的技术分享。</summary>
    <title>怎样做一个好的技术分享</title>
    <updated>2026-06-08T07:50:49.062Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="云原生" scheme="https://lichuanyang.top/categories/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <category term="微服务" scheme="https://lichuanyang.top/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
    <category term="云原生架构" scheme="https://lichuanyang.top/tags/%E4%BA%91%E5%8E%9F%E7%94%9F%E6%9E%B6%E6%9E%84/"/>
    <content>
      <![CDATA[<h2 id="云原生的定义"><a href="#云原生的定义" class="headerlink" title="云原生的定义"></a>云原生的定义</h2><p>近年来，云原生在整个开源社区中正在成为越来越流行的概念。那么云原生究竟是什么？架构？平台？又能影响什么？系统安全？开发效率？所以我们今天就刨根问底，理一下到底什么是云原生。</p><span id="more"></span><p>要理解什么是云原生，就要从云原生的名字说起，云原生的英文名是cloud native, 很显然，其含义就包含云和原生两部分。云就是说应用是运行在云上，而非本地。原生，就是说应用要以最适合云的方式去运行，而不是仅仅从本地迁移到应用上。</p><p>那么，什么样的应用才是适合云的呢？其实就是能最大化利用云的能力，发挥云的优势。</p><p>而云计算的核心优势，其实无非就是将更多的资源集中管理，统一调配，也就更方便按需灵活配置资源，提高资源利用率。</p><p>类比一下，相信很多人用过storm等流式框架，它们的优势是什么呢？其中一个重要的因素就是可以将一个复杂的流程拆解成若干个子节点，每个节点可以根据其需求配置不同的并发度，并发需求高的节点可以获得更多资源。这样，资源利用率也就提升了。</p><h2 id="微服务"><a href="#微服务" class="headerlink" title="微服务"></a>微服务</h2><p>对微服务来说，也是类似，将不同功能拆分成不同的服务，就可以单独对更小粒度的功能单独做扩缩容。</p><p>值得注意的是，拆分不仅包含拆分不同的业务，也包括将业务代码、三方软件（第三方库）、非功能特性（高可用、安全、可观测性等三类代码进行分离。</p><p>单纯业务的拆分，实际上从软件开发的非常早期的阶段就在进行了。而伴随着云原生概念兴起的趋势，正是将云应用中的非业务代码部分进行最大化的剥离，从而让云设施接管应用中原有的大量非功能特性（如弹性、韧性、安全、可观测性、灰度等），也就是所谓的service mesh.</p><p>由于云上的资源和应用并不是强绑定的，为了能更方便的利用资源，我们需要一种更通用的运行形势，让应用可以和运行环境有一定程度的解耦。</p><h2 id="容器化"><a href="#容器化" class="headerlink" title="容器化"></a>容器化</h2><p>这就是容器技术。容器提供了一种逻辑打包机制，以这种机制打包的应用可以脱离其实际运行的环境。利用这种脱离，不管目标环境是私有数据中心、公有云，还是开发者的个人笔记本电脑，您都可以轻松、一致地部署基于容器的应用。容器化使开发者和 IT 运营团队的关注点泾渭分明 - 开发者专注于应用逻辑和依赖项，而 IT 运营团队可以专注于部署和管理，不必为具体的软件版本和应用特有的配置等应用细节分心。</p><h2 id="可观测性"><a href="#可观测性" class="headerlink" title="可观测性"></a>可观测性</h2><p>另一方面，服务更小粒度的拆分之后，系统本身的复杂度显然会有所提升，比如本地调用变成了网络请求，调用链也无法通过代码结构体现。因此，运维上需要更加智能化和自动化，要保障单个服务更强的稳定性；同时需要一个强大的监控系统，要能够分析出各个微服务之间的依赖关系，还要快速检测出系统中的异常。</p><p>同时，在单个服务规模更小，且监控数据很完善的前提下，我们有可能去更频繁的去部署，甚至每次更改之后直接部署到生产环境。如果部署有问题，我们可以通过监控及时的发现，从而将损失控制在更小的程度。而小规模的部署，也让我们更容易去定位问题或者回滚。</p><p>从上边的分析中，我们可以整理出和云原生相关的一些关键词，比如服务化、弹性、可观测、韧性、自动化等等，这些关键词可以被总结成4类，即微服务、DevOps、持续交付和容器化。</p><p>这4类的关键特征如下：</p><p>微服务：可被独立部署、更新、重启、scale</p><p>DevOps:  自动化、快速、开发运维协同</p><p>持续交付：频繁发布、快速反馈</p><p>容器化：逻辑打包机制</p><h2 id="云原生的思维方式"><a href="#云原生的思维方式" class="headerlink" title="云原生的思维方式"></a>云原生的思维方式</h2><p>上边讲了很多理论上的知识，那么，要采用云原生的话，有什么具体的执行路径呢？可以从以下几个方面考虑：</p><ol><li>业务服务的拆分：这是软件开发中非常基础的一件事，拆分需要满足SOLID原则等基础的设计原则。</li><li>完善的监控体系:  包括log, trace, metric, alert几个维度的信息都要收集起来，其中log侧重于记录代码运行过程中的信息，trace主要用于追踪同一请求在不同服务下的流转，metric是针对系统运行状况的监控，alert则是针对异常状态的报警。业界也已经有了很多开源的实现，比如prometheus, jaeger等等</li><li>容器及容器编排： 这一部分基本就是 docker和k8s</li><li>中间件mesh化： 即业务应用中只保留很薄的一层client, 中间件的主要逻辑放在在mesh层</li><li>DevOps和持续交付：这个主要是开发流程、和开发运维协作上的很多流程的事。在云环境下，我们更推崇小批量、频繁发布、快速反馈的模式。</li></ol><p>我是流沙，希望通过这篇文章，可以让大家更清晰的理解云原生究竟是什么。其实，云原生说起来很简单，就是采用各种方式去更好的利用云上的资源。而具体说起来，它又是一套非常庞大的体系，涵盖了从开发到运维的方方面面。欢迎大家关注我的公众号(Mobility) ，或者<a href="https://lichuanyang.top/">个人网站</a>, 我会在后续的文章里逐渐展开讲讲云原生的方方面面。</p><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h3 id="Q-云原生就是上-Kubernetes-吗？"><a href="#Q-云原生就是上-Kubernetes-吗？" class="headerlink" title="Q: 云原生就是上 Kubernetes 吗？"></a>Q: 云原生就是上 Kubernetes 吗？</h3><p>不是。Kubernetes 是云原生体系中容器编排的核心工具，但云原生远不止 K8s。云原生是一整套方法论，包括微服务架构、容器化、DevOps、持续交付、可观测性等多个维度。上了 K8s 不等于就”云原生”了——如果你的应用仍然是单体巨石、没有自动化 CI&#x2F;CD、没有监控告警，那只是”在 K8s 上跑传统应用”而已。</p><h3 id="Q-传统应用有必要迁移到云原生吗？"><a href="#Q-传统应用有必要迁移到云原生吗？" class="headerlink" title="Q: 传统应用有必要迁移到云原生吗？"></a>Q: 传统应用有必要迁移到云原生吗？</h3><p>取决于场景。如果应用规模小、迭代频率低、团队人数少，强行上全套云原生反而增加复杂度。但如果应用需要频繁迭代、弹性伸缩、高可用，或者团队规模大到需要微服务拆分——云原生就能发挥价值。一句话：不是为了云原生而云原生，而是为了解决实际问题。</p><h3 id="Q-容器化和虚拟化有什么区别？"><a href="#Q-容器化和虚拟化有什么区别？" class="headerlink" title="Q: 容器化和虚拟化有什么区别？"></a>Q: 容器化和虚拟化有什么区别？</h3><p>虚拟机（VM）虚拟的是硬件层，每个 VM 有独立的操作系统内核，启动慢、资源开销大。容器虚拟的是操作系统层，所有容器共享宿主机内核，只隔离应用和依赖，启动快（秒级）、资源占用小。可以简单理解：虚拟机是”一栋楼里每家一个厨房”，容器是”共享一个大厨房，每人一个灶台”。</p><h3 id="Q-微服务和云原生是什么关系？"><a href="#Q-微服务和云原生是什么关系？" class="headerlink" title="Q: 微服务和云原生是什么关系？"></a>Q: 微服务和云原生是什么关系？</h3><p>微服务是云原生架构的核心组成部分，但不是全部。微服务解决的是”如何拆分应用以独立部署和扩缩容”，而云原生还包括了这些微服务”如何运行”（容器和编排）、”如何交付”（CI&#x2F;CD）、”如何监控”（可观测性）、”如何通信”（Service Mesh）等。可以把微服务理解为云原生架构的”业务层”。</p><p>原文地址: <a href="https://lichuanyang.top/posts/42843/">https://lichuanyang.top/posts/42843/</a></p><hr>]]>
    </content>
    <id>https://lichuanyang.top/posts/42843/</id>
    <link href="https://lichuanyang.top/posts/42843/"/>
    <published>2021-06-09T11:54:37.000Z</published>
    <summary>从 Cloud Native 的字面含义出发，系统梳理云原生的定义、核心理念和技术生态。</summary>
    <title>云原生究竟是什么</title>
    <updated>2026-06-27T03:44:21.596Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="技术杂谈" scheme="https://lichuanyang.top/categories/%E6%8A%80%E6%9C%AF%E6%9D%82%E8%B0%88/"/>
    <category term="个人成长" scheme="https://lichuanyang.top/tags/%E4%B8%AA%E4%BA%BA%E6%88%90%E9%95%BF/"/>
    <category term="读书笔记" scheme="https://lichuanyang.top/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <content>
      <![CDATA[<p>《干法》是一本讲工作态度、工作方法的经典书籍。在劳资矛盾严重、阶级趋向固化的今天，是很难完全向该书作者稻盛和夫先生一样，去践行这一套理念的。但这不代表这本书就没有价值了。努力工作，提升自己的价值，一直都是藏在人类内心深处的精神诉求，我们要做的，只是要让价值提升后带来的收益，能够给到我们自己。</p><span id="more"></span><p>在读这本书之前，我强烈建议先认真思考一下你和工作的关系究竟是什么样的。如果你就是公司的老板，那没什么好说，公司的收益就是你的。如果你不是，那就要理一理你创造的价值去了哪，是有了经济收益，有了能力的提升，有了将来可以成为自己能力证明的资历，还是帮老板多买了两辆跑车呢？</p><p>之前读过一篇文章，说是将你自己当做一家公司来经营，将任职的公司当做自己的客户。我觉得这是一个很好的思考的思路，你需要考虑你这家「公司」，是立刻就去追求现金收入，还是先打磨能力与口碑，以期将来去获得更大的收益？要考虑与任职公司的交易是否公平？</p><p>想清楚了这些，再来读《干法》这本书。否则，你可能会觉得这本书是马老师写的。话说回来，马老师确实也在践行这本书的很多理念，在他当英语老师的时候。没有当年“业余”时间的辛勤奋斗，哪里来今天的福报呢？</p><p>好了，下面进入正题。我会从三个方面讲述书中传递出的工作相关的价值观，一是工作的意义，二是如何对待工作，三是工作方法和态度。</p><h2 id="工作的意义"><a href="#工作的意义" class="headerlink" title="工作的意义"></a>工作的意义</h2><p>就像我在文章一开头说的，努力工作，创造价值实际上是埋藏在人类内心深处的一种非常重要的精神诉求。可以了解到，很多已经财务自由的人，依然在寻求一些工作机会，或是创业，或是找个安逸自由的工作。这就是内心深处的这种诉求在驱动人的行为。</p><p>除了通过工作获得的经济收益以外，</p><blockquote><p>一心一意工作，精益求精，本身就是磨炼人格的修行，促进我们成长。劳动可以带来喜悦感，让人明白生活的意义，劳动是高贵的行为。</p></blockquote><p>如果你能接受「将自己当成一家公司来经营」这样的设定，你会发现，眼前的经济收益其实没有那么重要。而个人的成长、工作中带来的幸福感是更有意义的收获，而在收获了这些之后，经济收益也会自然而然的随之而来。</p><h2 id="如何对待工作"><a href="#如何对待工作" class="headerlink" title="如何对待工作"></a>如何对待工作</h2><blockquote><p>大多数人初出茅庐只能从自己不喜欢的工作开始。</p></blockquote><p>这个世界上，真正将自己的爱好当做工作的，本身就是少数。在这些少数之中，绝大部分人工作了之后，也会感到这个事情已经不是自己的爱好了。因为作为一个爱好，和作为一份工作，这之中的差别是非常大的。游戏很多人都爱玩吧，很多人会羡慕那些职业的电竞选手，认为他们就是在做自己喜欢的工作。但是关注一下电竞圈，会发现不好好训练的人大有人在，有些人会抓住各种时机，去玩别的游戏。原本是因为喜欢这个游戏做了职业选手，最后却发现自己渐渐的不喜欢这个游戏了。</p><p>所以，做不喜欢的工作，才是这个世界的常态。而我们要做的，是让我们喜欢上工作本身。如何让自己喜欢上工作，作者给我们提供了一些方法。</p><blockquote><p>付出了努力，才会让自己喜欢上工作。</p></blockquote><blockquote><p>为工作中的小小成功感到欣喜，将其带来的能量当做动力，更加努力的工作。</p></blockquote><h2 id="工作方法和态度"><a href="#工作方法和态度" class="headerlink" title="工作方法和态度"></a>工作方法和态度</h2><p>首先，要以高目标为动力。</p><blockquote><p>能力要用“将来进行时”</p></blockquote><p>可以想象一下，如果把一段时间之前的自己放到现在，是不是也觉得自己现在做的事情很难？这是一个正常的现象，当你处在一个正常的发展路径下的时候，能力的提升在不知不觉中就会产生。</p><p>有了高目标，就要持续的付出努力，要去思考目标怎么样才能实现。</p><blockquote><p>一定要去想。不认真思考，就什么都实现不了。</p></blockquote><p>自己的成长，只有自己能够负责，指望其他任何人都是不现实的。我最近看脉脉上的吐槽，很多人对公司的不满是「没有成长」，这就让我非常迷惑，成长和公司究竟是什么关系呢。这个要说回到人和公司的关系，公司作为你的一个客户，你完成一些事情，并且获得一些报酬。这和个人成长有任何关系吗？成长是需要自己去思考的，而不是等着别人派下来，说这些这些干完，你就成长了。我能想到的一个人在一家公司里会没有任何收获的唯一场景，就是他已经理解了这家公司的所有方面，可以轻而易举的经营另外一家公司来打败这家公司。</p><p>另外，要有完美主义。</p><blockquote><p>橡皮绝对擦不掉错误</p></blockquote><p>很多错误，即使你事后弥补了，也必然会留下一些痕迹。无论做什么事情，要尽力去将它做到，至少是自己认知范围内的，完美。像开发一个需求，要思考完整整个流程，确保没有逻辑上的缺陷，也没有实现上的bug, 而不是看一遍产品稿就简简单单的实现一遍，剩下的所有事交给测试。</p><blockquote><p>工作的结果&#x3D;思维方式<em>热情</em>能力</p></blockquote><p>在书的最后，作者给了一个公式。对于工作来说，思维方式和热情是和能力一样重要的。而在你具备这种认知的前提下，提升热情和改变思维方式，要远比提升能力更简单。所以，想提升工作的效果，其实没有那么难，关键是要建立起对于工作的合理的认知。</p><p>我是流沙，感谢阅读这篇文章。如果觉得写的还不错的话，请多多点赞关注。如有问题，也可以在<a href="https://lichuanyang.top/">个人博客</a>、微信公众号( Mobility ) 等平台和我交流</p><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h3 id="Q-程序员需要读《干法》这种书吗？"><a href="#Q-程序员需要读《干法》这种书吗？" class="headerlink" title="Q: 程序员需要读《干法》这种书吗？"></a>Q: 程序员需要读《干法》这种书吗？</h3><p>需要，但建议带着”审视”的心态去读，而不是全盘接受。这本书的核心价值在于让你先想清楚”我与工作的关系是什么”——你是为老板打工，还是在为自己积累能力和口碑？把这个问题想明白之后，书中关于热情、完美主义、持续付出的理念才会对你产生实际启发。建议配合”把自己当成一家公司经营”这个思维框架一起读，效果更好。</p><h3 id="Q-工作是为了什么？"><a href="#Q-工作是为了什么？" class="headerlink" title="Q: 工作是为了什么？"></a>Q: 工作是为了什么？</h3><p>既是经济来源，也是精神诉求。经济层面不言而喻，但更重要的是，创造价值本身就埋藏在人类内心深处——很多财务自由的人依然会选择工作。书中给出的答案很直接：工作不仅是获取报酬的途径，更是磨炼人格、带来喜悦感、让人理解生活意义的修行。如果暂时还无法完全认同这个观点，不妨从”把自己当成一家公司经营”开始，至少让你的工作产出能实实在在地转化为你自己的积累。</p><h3 id="Q-如何喜欢上自己不喜欢的工作？"><a href="#Q-如何喜欢上自己不喜欢的工作？" class="headerlink" title="Q: 如何喜欢上自己不喜欢的工作？"></a>Q: 如何喜欢上自己不喜欢的工作？</h3><h2 id="做不喜欢的工作才是常态——真正把爱好当工作的人极少，且即使如此，爱好变成工作后也往往变了味。书里给出了两个实用方法：一是先付出努力，努力带来的正反馈会逐渐让你产生投入感；二是为工作中的小小成功感到欣喜，把每一次”我搞定了”的能量积累成继续前行的动力。这个过程不是靠鸡汤支撑，而是靠行动带来的正循环。"><a href="#做不喜欢的工作才是常态——真正把爱好当工作的人极少，且即使如此，爱好变成工作后也往往变了味。书里给出了两个实用方法：一是先付出努力，努力带来的正反馈会逐渐让你产生投入感；二是为工作中的小小成功感到欣喜，把每一次”我搞定了”的能量积累成继续前行的动力。这个过程不是靠鸡汤支撑，而是靠行动带来的正循环。" class="headerlink" title="做不喜欢的工作才是常态——真正把爱好当工作的人极少，且即使如此，爱好变成工作后也往往变了味。书里给出了两个实用方法：一是先付出努力，努力带来的正反馈会逐渐让你产生投入感；二是为工作中的小小成功感到欣喜，把每一次”我搞定了”的能量积累成继续前行的动力。这个过程不是靠鸡汤支撑，而是靠行动带来的正循环。"></a>做不喜欢的工作才是常态——真正把爱好当工作的人极少，且即使如此，爱好变成工作后也往往变了味。书里给出了两个实用方法：一是<strong>先付出努力</strong>，努力带来的正反馈会逐渐让你产生投入感；二是<strong>为工作中的小小成功感到欣喜</strong>，把每一次”我搞定了”的能量积累成继续前行的动力。这个过程不是靠鸡汤支撑，而是靠行动带来的正循环。</h2>]]>
    </content>
    <id>https://lichuanyang.top/posts/3513/</id>
    <link href="https://lichuanyang.top/posts/3513/"/>
    <published>2021-05-15T09:29:34.000Z</published>
    <summary>稻盛和夫《干法》读书笔记，反思工作态度和工作方法，探讨如何在工作中实现个人价值提升。</summary>
    <title>读书笔记 稻盛和夫《干法》-思考应该怎样去工作</title>
    <updated>2026-06-27T02:31:54.481Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="技术杂谈" scheme="https://lichuanyang.top/categories/%E6%8A%80%E6%9C%AF%E6%9D%82%E8%B0%88/"/>
    <category term="开发规范" scheme="https://lichuanyang.top/tags/%E5%BC%80%E5%8F%91%E8%A7%84%E8%8C%83/"/>
    <content>
      <![CDATA[<h2 id="为什么需要-Code-Review"><a href="#为什么需要-Code-Review" class="headerlink" title="为什么需要 Code Review"></a>为什么需要 Code Review</h2><p>在通常的开发流程中，会有各种各样的评审(review)环节，比如产品稿评审、设计评审、测试用例评审、code review等。为了做这些评审，难免会引入一些会议。有的人会觉得这些事没什么用，太浪费时间。而实际上，这些事情是非常重要的，如果你产生了评审不重要的印象，原因无非两个，一是认知不到位，而是你所在的环境评审实施的不好。</p><span id="more"></span><h2 id="Review-对团队的价值"><a href="#Review-对团队的价值" class="headerlink" title="Review 对团队的价值"></a>Review 对团队的价值</h2><p>关于评审的价值，首先对于团队来说，最大的作用其实就是提升团队的下限。试想一下，如果各个环节都不进行评审，而是直接产品交付一个产品稿，开发就对着开发，任何个人的疏忽都会给整个项目带来无尽的风险。多次的评审环节，实际上就是将一个业务方案，分别用产品稿、代码、测试用例等形式描述，再分别引入更多的人来检查，避免一个人的疏忽造成业务上的风险。</p><p>此外，本文的重点，我实际上是想讲讲除了给团队带来的价值外，对于一个个体的人来说，参与评审有什么价值。</p><h2 id="Review-对被审查者的价值"><a href="#Review-对被审查者的价值" class="headerlink" title="Review 对被审查者的价值"></a>Review 对被审查者的价值</h2><p>首先说一下对于被评审的人，即产品稿、代码等的提交人来说，评审就是一个其他人来帮助你提升的过程，是难得的能获取直接的反馈信息的机会。其实对于很多人来说，相比于学生时代，工作时代学东西的最大难点就是没有人批改「作业」，看了书看了视频之后，没办法确认自己究竟学没学会。而及时的反馈，无论学什么，都是非常重要的。而在参与评审的人中，会包括你的上级，能力更强的同事。他们能够详细的了解你的方案、代码的话，相信是能够给出很多有价值的反馈的。评审，实际上就是给你自己提供一个机会，能够接收到他们的反馈信息。</p><p>这里其实隐含了一条对于提交人的要求，就是要能够清晰的表达出自己的方案，这样别人才能低成本的参与到评审中来。比如写代码，要运用合理的设计模式，写出易读的代码。写文档，要有合理的布局、分段，突出方案的重点。向别人讲方案时，要考虑听众的知识背景，确保他们可以听的懂你的方案。</p><p>评审对于提交人的另一个重要作用，是分担风险和责任。要知道，无论你公司的制度是什么样的，无论你的职位如何，当你负责的项目没有做好时，最终的结果，一定是有相当一部分是由你自己承担的。承担的形式，可能是直接的绩效损失，可能是别人对你的能力产生了质疑，不管怎样，都是大家不希望面对的。而评审，就是将一部分后果，转移到整个团队上。当然，别想太多，主体的责任一定是还在提交人本人身上。这里，同样体现了提交人清晰表达的重要性。提交人需要将自己方案的关键点，更多的让评审人们接受到。如果评审人成功的接收这些信息，并且在评审时进行充分的讨论并最终达成共识，这一部分决策就可以认为是团队共同做出的了。</p><h2 id="Review-对审查者的价值"><a href="#Review-对审查者的价值" class="headerlink" title="Review 对审查者的价值"></a>Review 对审查者的价值</h2><p>接下来再来看对于评审人来说，积极的参与对其他人的评审，又有什么作用。首先的价值还是一个学习的机会。所谓三人行，必有我师，从别人的方案里，一定是能够发现一些非常好的点，值得自己学习的。当发现别人的方案和自己的设想不一致时，是一个进行深度思考的机会，可以对自己进行一个连续的追问，看看到底哪种方案比较好，直到得出一个无法质疑的结论。这种深度的思考，对于自己的思考方式会有很好的改善作用。</p><p>另外，参与评审也是一个快速提升经验的方法。参与评审，实际上是花费评审的时间，得到一个无限接近于 自己做了这件事 的效果。评审的足够细致的话，你就会对这件事怎么做，有一个非常全面的认识，这件事也就完全可以作为你项目经历的一部分。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文到这里就要结束了，不知道大家能否通过本文建立起对为什么要做评审、怎么做评审的认知。大家可以通过 公众号( Mobility ), <a href="https://lichuanyang.top/">个人网站</a> 等和我交流。</p><h2 id="原文地址-https-lichuanyang-top-posts-57205"><a href="#原文地址-https-lichuanyang-top-posts-57205" class="headerlink" title="原文地址: https://lichuanyang.top/posts/57205"></a>原文地址: <a href="https://lichuanyang.top/posts/57205">https://lichuanyang.top/posts/57205</a></h2>]]>
    </content>
    <id>https://lichuanyang.top/posts/57205/</id>
    <link href="https://lichuanyang.top/posts/57205/"/>
    <published>2021-04-27T10:07:11.000Z</published>
    <summary>从个人成长角度分析 code review 的价值，不仅是发现 bug，更是团队知识共享和代码质量提升的关键手段。</summary>
    <title>review的个人价值</title>
    <updated>2026-06-27T00:55:31.538Z</updated>
  </entry>
  <entry>
    <author>
      <name>流沙</name>
    </author>
    <category term="技术杂谈" scheme="https://lichuanyang.top/categories/%E6%8A%80%E6%9C%AF%E6%9D%82%E8%B0%88/"/>
    <category term="职业发展" scheme="https://lichuanyang.top/tags/%E8%81%8C%E4%B8%9A%E5%8F%91%E5%B1%95/"/>
    <content>
      <![CDATA[<p>今天结合我的个人经历来聊一聊应届生的职业选择问题，主要针对后端开发（对岗位的选择，话题也比较大，后边有机会的话会单独开一篇文章写），我这几年的时间里历经了国企、大公司、小公司这样不同的工作环境，所以对于不同公司的情况还是比较有发言权的。</p><span id="more"></span><h2 id="四个维度的取舍"><a href="#四个维度的取舍" class="headerlink" title="四个维度的取舍"></a>四个维度的取舍</h2><p>职业选择，最重要的无非就是城市和公司选择。城市选择是一件非常主观的事情，比如我，作为一个北方人，既不喜欢南方城市的气候，又想离家近，还需要大城市的机会，北京就几乎成了唯一选择。当然，给大家的建议，功利点的话，建议选择一些快速发展中的强二线城市，比如重庆、合肥，这样随着城市平台的发展，你自己身处其中，身价也会提升。</p><p>接下来，我会限定在北京的范围内，结合我自己的经历，说一下公司的选择。</p><h2 id="户口：一线城市的入场券"><a href="#户口：一线城市的入场券" class="headerlink" title="户口：一线城市的入场券"></a>户口：一线城市的入场券</h2><p>提到北京，一个不得不提的问题就是户口的问题。不过，<strong>我强烈不建议你依据一个户口就决定了整个人生怎么走</strong>，比如明明不想进体制内，却为了个户口就去了。务必在考虑户口问题之前，优先想好职业发展方向的问题。</p><p>很多人会纠结于体制内&#x2F;体制外、大公司&#x2F;小公司这些明面上的选择，但这些其实都只是表象而已，同样是体制内，也可能有天差地别的变化。一个正确的做法是想清楚自己真正想要的，然后再去寻找合适的工作机会。对于什么是自己想要的，确实不是每个人都能想的清楚。所以，我把这个问题拆解一下，看看工作的选择究竟意味着什么。大家可以思考以下几个问题：</p><ul><li>你最希望从工作中获得什么？钱？权力？还是内心的成就感？或是并不想从工作中获得任何东西？</li><li>你希望工作在你的人生中占据多大的比例</li><li>对于工作中获取的资源（人脉、信息等），和通过工作提升的能力，那一项你更有信心能带到其他的环境里</li><li>你喜欢做常规的事务性工作，还是希望持续的去挑战困难问题</li></ul><p>对于上边的任何一个问题，任何回答都没有高下之分，比如挑战困难问题并不比做常规工作更优越，这个只是大家根据个人价值观做出的选择而已。</p><p>这些选择的任意排列组合，相信都可以找到合适的机会。比如不期待从工作中有所得，不想工作占据太多人生，也愿意做常规的工作，那就可以考虑一些边缘的体制内单位；期望有权力，可以让工作占据很多生活时间，更想通过工作积累资源，就可以考虑核心的、有实权的体制内单位；而如果期望挣钱，更希望通过工作提升个人能力，程序员就是个不错的选择。</p><p>想好了上边的问题，就可以选择公司了。很多时候，我们可能无法找到并顺利的进入一个满足所有要求的地方。这也没关系，我们可以利用跳槽，每次解决其中一部分问题，并且持续的搜集信息和提升能力，寻找到合适的机会，并有能力获得这个机会。</p><h2 id="大厂：平台的价值与代价"><a href="#大厂：平台的价值与代价" class="headerlink" title="大厂：平台的价值与代价"></a>大厂：平台的价值与代价</h2><p>我以自己的经历讲一下我的几个关键选择是怎么考虑的。</p><p>刚毕业的时候，我的期望是获得一个北京户口，并且能留在互联网行业内。那个时候我的选择有两个大方向，一是去互联网公司竞争ssp offer, 但是当时的能力确实不足，所以这条路很难走的通；另一条路就是在体制内寻找业务接近互联网场景的机会，最好是面向普通用户的C端产品，因为这样可以有充足的用户量和数据量，让我不至于离业内先进的技术太远。很幸运，我找到了这样的机会，也成功的抓住了。入职之后，也基本如我所料，虽然公司技术水平并不高，企业文化也有很多问题，但是业务场景是非常好的，我也有充足的自己发挥的机会，借助工作，也学到了很多东西。</p><p>接下来，我要解决的问题就是大厂经历，所以接下来跳槽的时候就只考虑了几个一线大厂。关于工作本身，倒没什么好说的，这段经历虽然过的很不开心，但是希望达到的目标也达到了，所以也没什么遗憾的。</p><p>再下一次跳槽，我期望解决的问题有三个：大幅提高收入，抹平因为在国企呆的时间比较长造成的和同龄人的收入差距；别加班太多；人际关系轻松一些。这三个问题看似矛盾是不是？但是用心去找总是能找到合适的机会的，我也就这样来到了现在的公司。当然不是所有问题都解决的非常好，但是已经足够让我的工作体验非常幸福了。</p><p>通过我自己的经历，其实大家也可以看到，我总是似乎在提互相矛盾的要求，总是想鱼与熊掌兼得，而且最后也能幸运的都得到。其实这世界上公司那么多，并不是说有哪两个条件是必然互相矛盾的。<strong>大家没有必要过早的就舍弃什么，比如做程序员就一定意味着996，意味着放弃生活吗？显然不是。</strong></p><p>上边可以衍生出两个比较细的问题，我也一起说下吧。</p><p>一是要不要选择北京户口。我先说一下我通过这个户口获得了什么吧，首先是赶上了15，16年那波房价大涨，让家庭资产在我并没有什么理财观念的时候，也获得了大幅增值。这一点我觉得放在今天，已经不成立了，房住不炒的大前提下，就算北京的房子，也不可能跑的过股市。第二点，就是一些手续办的方便一点，这个我觉得基本可以忽略不计吧，一年估计都办不了一次事，而且越来越多的手续也支持异地办理了。第三点，就是可以放心的让孩子在北京接受教育，不用担忧未来的不确定性。为什么我说是不确定性，因为我觉得接近二十年之后，非京的孩子是否依旧不能在北京高考，是有很大的不确定性的。北京的高考难度会卷成什么样，也是有很大的不确定性的。北京户口，只能说让我个人未来的选择固定了下来，但这个选择未来会是什么效果，其实很难说的准。</p><p>所以说，当前来看，北京户口的价值其实非常有限。回到2015年的话，我仍然会毫不犹豫的选择要北京户口。但是如果让我在2021年的今天做这个选择，其实我大概率是不要的。</p><p>第二点，是关于大厂经历。这一点，我的看法是必须要有，而且尽早有，但是没必要一辈子都呆在大公司里。大厂经历的价值，其实很多人已经说的很清楚了，所以我也不想再赘述。感兴趣的话可以私下找我沟通。</p><h2 id="如何选择"><a href="#如何选择" class="headerlink" title="如何选择"></a>如何选择</h2><p>上边的篇幅里，主要说的是方向的选择。选定了方向之后，还有个重要的问题是具体公司的选择。面对一个公司，其实考虑两个问题就可以：</p><ul><li>我通过这个工作能得到什么</li><li>我因为这个工作会失去什么，将来有什么办法可以弥补</li></ul><p>以我的第一份工作来说，获得的就是户口和互联网的业务场景。同时会失去大公司的学习资源、优良的公司氛围、高薪水等等，这些问题都需要自己想办法去解决。</p><p>好了，上文已经讲完了本文的大部分内容。有个点，我一直没有提，就是中年危机的问题，可能会有人问。我为什么不提呢，因为我不觉得这是一个能靠选择解决的问题。有些人会觉得体制内很稳定，即使天天混日子也没人能把你怎么着。但是我不觉得这种依赖外部条件的事情能算稳定，历史上体制内不是没出过问题。走这条路或许你有99.99%的机会一辈子过的安安稳稳，但是如果那万分之一的可能性发生了，你打算怎么办呢。而做程序员就不一样，或许只有50%的机会能平稳的呆在一家公司，但是随着工作提升的能力，让我可以很安心的应对另一半可能性。解决中年危机的唯一方式就是提升自己，不管是提升能力还是积累资源，你总要从工作中有所得，才能持续的保持自己的竞争力。</p><p>我是流沙，大家可以通过公众号( Mobility ), <a href="https://lichuanyang.top/">个人网站</a> 等和我交流。</p><p>原文地址: <a href="https://lichuanyang.top/posts/34931/">https://lichuanyang.top/posts/34931/</a></p><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h3 id="Q-一线城市户口还值不值得争取？2021-年的户口价值发生了什么变化？"><a href="#Q-一线城市户口还值不值得争取？2021-年的户口价值发生了什么变化？" class="headerlink" title="Q: 一线城市户口还值不值得争取？2021 年的户口价值发生了什么变化？"></a>Q: 一线城市户口还值不值得争取？2021 年的户口价值发生了什么变化？</h3><p>户口曾经最大的价值是绑定房产增值（作者在 2015-16 年赶上了那波行情）。但在”房住不炒”大背景下，这个红利已基本消失。教育价值方面，近二十年后的政策不确定性很大，非京籍能否在京高考很难预测。所以 2021 年的观点是：北京户口的实际价值已经非常有限，<strong>不建议仅凭一个户口就决定职业方向</strong>——如果回归 2015 年作者仍会要户口，但如果回到 2021 年，大概率不会要。</p><h3 id="Q-大厂还是小厂？应届生第一份工作选什么最重要？"><a href="#Q-大厂还是小厂？应届生第一份工作选什么最重要？" class="headerlink" title="Q: 大厂还是小厂？应届生第一份工作选什么最重要？"></a>Q: 大厂还是小厂？应届生第一份工作选什么最重要？</h3><p>作者的观点很明确：<strong>大厂经历必须要有，且尽早有，但不用一辈子呆在大厂</strong>。大厂经历的价值在于平台背书、技术视野和成长加速度，这些已经被反复讨论过。第一份工作最重要的不是户口也不是薪资，而是想清楚四个问题：1) 你最想从工作中获得什么（钱&#x2F;权力&#x2F;成就感&#x2F;什么都不想获得）；2) 工作占人生的比例；3) 资源和人脉 vs 个人能力，哪个更有信心带走；4) 喜欢常规事务性工作还是持续挑战困难问题。</p><h3 id="Q-想鱼与熊掌兼得（高薪-不加班-氛围好）可能吗？"><a href="#Q-想鱼与熊掌兼得（高薪-不加班-氛围好）可能吗？" class="headerlink" title="Q: 想鱼与熊掌兼得（高薪 + 不加班 + 氛围好）可能吗？"></a>Q: 想鱼与熊掌兼得（高薪 + 不加班 + 氛围好）可能吗？</h3><p>作者用自己的经历证明：<strong>可能，但需要用心去找</strong>。这世上公司足够多，并没有哪两个条件是必然互斥的——做程序员不等于必然 996、不等于必然放弃生活。关键是不要过早预设”只能选一个”，先问清楚自己真正想要什么，然后持续搜集信息、提升能力，总能找到满足大部分条件的机会。一次跳槽解决不了所有问题就分多次。</p><h3 id="Q-解决中年危机的根本方式是什么？靠选择体制内能一劳永逸吗？"><a href="#Q-解决中年危机的根本方式是什么？靠选择体制内能一劳永逸吗？" class="headerlink" title="Q: 解决中年危机的根本方式是什么？靠选择体制内能一劳永逸吗？"></a>Q: 解决中年危机的根本方式是什么？靠选择体制内能一劳永逸吗？</h3><h2 id="作者明确表示：不觉得中年危机是能靠选择解决的。体制内的”稳定”本质上是依赖外部条件——历史上体制内不是没出过问题。即使有-99-99-的概率安稳，但那万分之一一旦发生，你毫无应对能力。真正的解决方式是在每一份工作中持续积累可带走的能力——做程序员可能只有-50-的概率平稳呆在一家公司，但积累的能力让你能安心应对另一半不确定性。"><a href="#作者明确表示：不觉得中年危机是能靠选择解决的。体制内的”稳定”本质上是依赖外部条件——历史上体制内不是没出过问题。即使有-99-99-的概率安稳，但那万分之一一旦发生，你毫无应对能力。真正的解决方式是在每一份工作中持续积累可带走的能力——做程序员可能只有-50-的概率平稳呆在一家公司，但积累的能力让你能安心应对另一半不确定性。" class="headerlink" title="作者明确表示：不觉得中年危机是能靠选择解决的。体制内的”稳定”本质上是依赖外部条件——历史上体制内不是没出过问题。即使有 99.99% 的概率安稳，但那万分之一一旦发生，你毫无应对能力。真正的解决方式是在每一份工作中持续积累可带走的能力——做程序员可能只有 50% 的概率平稳呆在一家公司，但积累的能力让你能安心应对另一半不确定性。"></a>作者明确表示：<strong>不觉得中年危机是能靠选择解决的</strong>。体制内的”稳定”本质上是依赖外部条件——历史上体制内不是没出过问题。即使有 99.99% 的概率安稳，但那万分之一一旦发生，你毫无应对能力。真正的解决方式是<strong>在每一份工作中持续积累可带走的能力</strong>——做程序员可能只有 50% 的概率平稳呆在一家公司，但积累的能力让你能安心应对另一半不确定性。</h2>]]>
    </content>
    <id>https://lichuanyang.top/posts/34931/</id>
    <link href="https://lichuanyang.top/posts/34931/"/>
    <published>2021-03-30T10:36:15.000Z</published>
    <summary>结合国企、大厂、小公司的工作经历，为应届程序员提供城市和公司选择的务实建议。</summary>
    <title>户口?大厂?高薪?生活?聊聊应届程序员的职业选择</title>
    <updated>2026-06-27T03:42:25.694Z</updated>
  </entry>
</feed>
