<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Cursor on 小盒子的技术分享</title><link>https://xiaobox.github.io/tags/cursor/</link><description>Recent content in Cursor on 小盒子的技术分享</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Mon, 13 Apr 2026 03:56:46 +0000</lastBuildDate><atom:link href="https://xiaobox.github.io/tags/cursor/index.xml" rel="self" type="application/rss+xml"/><item><title>Postman 越来越臃肿了，我换了个开源的，还能让 AI 帮我写测试</title><link>https://xiaobox.github.io/p/2026-04-13-postman-yue-lai-yue-yong-zhong-le-wo-huan-le-ge-kai-yuan-de/</link><pubDate>Mon, 13 Apr 2026 03:56:46 +0000</pubDate><guid>https://xiaobox.github.io/p/2026-04-13-postman-yue-lai-yue-yong-zhong-le-wo-huan-le-ge-kai-yuan-de/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-04-13-postman-yue-lai-yue-yong-zhong-le-wo-huan-le-ge-kai-yuan-de-/cover.jpg" alt="Featured image of post Postman 越来越臃肿了，我换了个开源的，还能让 AI 帮我写测试" /&gt;&lt;p&gt;Postman 我用了好几年了。&lt;/p&gt;
&lt;p&gt;从最早的 Chrome 插件时代开始用的，那时候它还是个轻量小工具，打开就能测接口，干干净净。但最近这两年，怎么说呢，它开始「端着」了。&lt;/p&gt;
&lt;p&gt;打开就让我登录。不登录不让用。&lt;/p&gt;
&lt;p&gt;好不容易登录了，又弹窗让我升级 Team 版。&lt;/p&gt;
&lt;p&gt;集合还给我存到云端去了。我就测个本地接口，你把我的请求数据往你服务器上传干嘛？&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-04-13-postman-yue-lai-yue-yong-zhong-le-wo-huan-le-ge-kai-yuan-de-/001-293772cd.png"&gt;&lt;/p&gt;
&lt;p&gt;我一直忍着，直到有一天，团队里两个人同时改了同一个集合，Postman 云同步直接给合并冲突了，还没法像 Git 那样 diff 看变更。那天我就想，不行了，得换。&lt;/p&gt;
&lt;p&gt;然后我遇到了 Bruno。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-04-13-postman-yue-lai-yue-yong-zhong-le-wo-huan-le-ge-kai-yuan-de-/002-67da7d91.png"&gt;&lt;/p&gt;
&lt;p&gt;怎么形容呢，就像你一直在用一个越来越臃肿的 IDE，突然有人递给你一个 Vim，告诉你「够用了，而且是你的」。&lt;/p&gt;
&lt;p&gt;Bruno 干了一件特别简单但特别对的事情，它把你的 API 请求存成 .bru 文件，放在你本地文件夹里。&lt;/p&gt;
&lt;p&gt;就是普通的文本文件，打开长这样。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;meta { name: 用户登录 type: http seq: 1}post { url: {{baseUrl}}/auth/login body: json auth: none}headers { Content-Type: application/json}body:json { { &amp;#34;username&amp;#34;: &amp;#34;{{username}}&amp;#34;, &amp;#34;password&amp;#34;: &amp;#34;{{password}}&amp;#34;, &amp;#34;expiresInMins&amp;#34;: 30 }}script:post-response { if (res.status === 200) { bru.setVar(&amp;#34;authToken&amp;#34;, res.body.accessToken); bru.setVar(&amp;#34;userId&amp;#34;, res.body.id); }}tests { test(&amp;#34;登录应该返回 200&amp;#34;, function() { expect(res.status).to.equal(200); }); test(&amp;#34;响应中应该包含 accessToken&amp;#34;, function() { expect(res.body.accessToken).to.be.a(&amp;#34;string&amp;#34;); expect(res.body.accessToken.length).to.be.greaterThan(0); }); test(&amp;#34;响应中应该包含用户基本信息&amp;#34;, function() { expect(res.body.id).to.be.a(&amp;#34;number&amp;#34;); expect(res.body.username).to.equal(&amp;#34;emilys&amp;#34;); expect(res.body.email).to.be.a(&amp;#34;string&amp;#34;); }); test(&amp;#34;响应时间应该小于 3 秒&amp;#34;, function() { expect(res.responseTime).to.be.lessThan(3000); });}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;你看，GET 请求、Header、Body、断言，全都是纯文本。你用任何编辑器都能打开它，改完保存就行。&lt;/p&gt;
&lt;p&gt;这玩意最不同的地方在哪？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;它可以用 Git 管理。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;以前团队共享 Postman 集合，那叫一个痛苦。谁改了什么不知道，版本对不对不确定，冲突了还没法 resolve。&lt;/p&gt;
&lt;p&gt;现在用 Bruno，接口定义就是文件，扔进 Git 仓库，该 PR 就 PR，该 Code Review 就 Code Review。有人改了某个接口的 Header，diff 里看得一清二楚。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-04-13-postman-yue-lai-yue-yong-zhong-le-wo-huan-le-ge-kai-yuan-de-/003-c38b66fd.png"&gt;&lt;/p&gt;
&lt;p&gt;用程序员已经会的工具，解决程序员的问题。不用学新的协作方式，不用付费，不用担心数据被传到哪个云上。&lt;/p&gt;
&lt;p&gt;Bruno 官网上有一句话我印象特别深，大意是**「我们不会同步你的任何数据到云端，甚至连登录的概念都没有。我们看不到你在 Bruno 里输入了什么，也不会用你的数据训练任何 AI 模型」。**&lt;/p&gt;
&lt;p&gt;在这个年代，一个工具敢这么说，我觉得还是挺硬气的。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-04-13-postman-yue-lai-yue-yong-zhong-le-wo-huan-le-ge-kai-yuan-de-/004-2762e255.png"&gt;&lt;/p&gt;
&lt;p&gt;回到工具本身。说到这里，可能有朋友会想，开源 API 客户端一抓一大把，Insomnia、Hoppscotch、Thunder Client，凭什么是 Bruno？&lt;/p&gt;
&lt;p&gt;我自己用下来，让我有「这玩意不一样」的瞬间，是发现它有 CLI。&lt;/p&gt;
&lt;p&gt;但我说的不是「能在终端里跑测试」这种废话。Newman 也能跑，Postman 自己也有命令行。我说的是另一件事。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bruno 的 CLI，让 Claude Code 和 Cursor 这种 AI 编程工具，第一次能真正帮你写和跑接口测试。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这话有点大，我用两件刚发生的真事讲一下。&lt;/p&gt;
&lt;p&gt;回到 Bruno 的 CLI 本身。装它就一行，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;npm i -g @usebruno/cli
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;装完之后命令叫 &lt;code&gt;bru&lt;/code&gt;，跑整个集合的测试是这样，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;bru run --env production
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;输出长这样。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-04-13-postman-yue-lai-yue-yong-zhong-le-wo-huan-le-ge-kai-yuan-de-/005-95a75ce3.png"&gt;&lt;/p&gt;
&lt;p&gt;干净、彩色、有 ✓ 和 ✗、有总耗时、有失败原因。最重要的是，&lt;strong&gt;这是一段普通的终端命令，输出是普通的文本&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;为什么这件事重要？因为这两个特征，正好是 AI 编程 agent 工作的边界。&lt;/p&gt;
&lt;p&gt;Claude Code、Cursor、Codex 这些工具，它们能干什么？它们能读你的代码文件，能写新的文件，能在终端里跑命令，能读命令的输出。它们不能干什么？它们不能点击 Postman 的按钮，不能在你 GUI 里输入 token，不能登录任何账号。(&lt;strong&gt;或者说不方便，成本高，效率低&lt;/strong&gt;)&lt;/p&gt;
&lt;p&gt;Postman 的核心数据存在云端、操作靠 GUI、协作靠登录，这三件事每一件都把 AI agent 挡在外面。&lt;/p&gt;
&lt;p&gt;而 Bruno 的核心数据是 .bru 文本文件、操作靠 CLI、协作靠 Git。每一件都正好是 AI 最擅长的那种事。&lt;/p&gt;
&lt;p&gt;抽象的说完了，说点具体的。&lt;/p&gt;
&lt;p&gt;我前两天就让 Claude Code 帮我加了一个测试，过程是这样的。&lt;/p&gt;
&lt;p&gt;我跟它说，「我刚加了一个搜索商品的接口，帮我在 bruno 集合里加个测试用例」。&lt;/p&gt;
&lt;p&gt;它干了三件事，全程没问我任何问题。&lt;/p&gt;
&lt;p&gt;第一步，读了我现有的一个 .bru 文件，就是为了搞清楚我用的格式。比如我习惯加哪些 test，断言风格是什么样的。&lt;/p&gt;
&lt;p&gt;第二步，照着这个格式写了一个新的 06-搜索商品.bru 文件，放在我集合的根目录里。请求方法、URL、query 参数、4 条断言，全都给我加上了。&lt;/p&gt;
&lt;p&gt;第三步，它直接执行 &lt;code&gt;bru run 06-搜索商品.bru --env production&lt;/code&gt;，亲眼看着 4 个测试全绿，然后才回我一句「写完了，4/4 通过」。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-04-13-postman-yue-lai-yue-yong-zhong-le-wo-huan-le-ge-kai-yuan-de-/006-f94498cf.png"&gt;&lt;/p&gt;
&lt;p&gt;整个过程我没碰 Bruno 的 GUI，没敲一个字符的代码，没解释什么是 .bru 格式。AI 自己读自己写自己跑自己验证。&lt;/p&gt;
&lt;p&gt;你可能会问，这个事换成 Postman 不行吗？&lt;/p&gt;
&lt;p&gt;我得先把话说清楚，Postman 不是没有 CLI。Newman 一直都在，Postman Collection 也是 JSON 文件，AI 理论上能读能写。这条路是通的。&lt;/p&gt;
&lt;p&gt;但你真去试一下就会发现，那条路上全是石头。&lt;/p&gt;
&lt;p&gt;Postman 的 Collection JSON 格式不是给人手写的。我做了个最朴素的对比，同一个 GET 请求加一条「状态码等于 200」的断言，写成 .bru 是 15 行，导出成 Postman Collection JSON 是 44 行。区别在哪？.bru 里 URL 就是一个字符串 &lt;code&gt;https://api.example.com/users&lt;/code&gt;，Postman JSON 里 URL 被拆成 &lt;code&gt;protocol&lt;/code&gt;、&lt;code&gt;host&lt;/code&gt; 数组、&lt;code&gt;path&lt;/code&gt; 数组三个字段。.bru 里测试代码就是普通 JS，Postman JSON 里测试代码以字符串数组的形式塞在 event 里，每一行 JS 都得加引号、转义、再 JSON 序列化一次。还有一堆 &lt;code&gt;_postman_id&lt;/code&gt;、&lt;code&gt;_exporter_id&lt;/code&gt;、&lt;code&gt;schema&lt;/code&gt; 这种内部元数据，跟你的业务接口毫无关系，但你不写就报错。&lt;/p&gt;
&lt;p&gt;让 AI 写 .bru，它跟写 markdown 一样轻松。让 AI 写 Postman JSON，它更像在翻译一份配置文件，token 烧得多，出错概率高，写完你自己 review 都费劲，git diff 出来三行业务变更夹在二十行格式噪音里。&lt;/p&gt;
&lt;p&gt;所以不是 AI 不能写 Postman 的测试，是 Postman 的格式从一开始就没打算让人手写，更没打算让 AI 直接读写。它假设你有一个 GUI 在中间帮你管理这些内部细节。AI 进来之后，那个假设就有点尴尬了。&lt;/p&gt;
&lt;p&gt;我说的还不是最炸的场景。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;真正让我觉得 Bruno + AI 是 1 + 1 大于 10 的，是调试场景。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我又试了一次，让 Claude Code 帮我加一个购物车接口的测试。它写完跑了一遍，挂了。&lt;/p&gt;
&lt;p&gt;错误信息是这样的。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;✕ 购物车应该有 totalPrice 字段 expected undefined to be a number✕ 购物车应该有 products 数组 expected undefined to be an array
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;```bash
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;它猜错了字段名。这种事很正常，毕竟它没真的去看接口返回。但接下来才有意思。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;它没有问我、没有让我提供文档、也没有放弃。它自己执行了一条 curl 命令，把购物车接口的真实响应拉了下来。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;```bash
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;curl -s https://dummyjson.com/carts/1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;看到响应里字段是 &lt;code&gt;total&lt;/code&gt; 和 &lt;code&gt;products&lt;/code&gt;，不是 &lt;code&gt;totalPrice&lt;/code&gt; 和 &lt;code&gt;items&lt;/code&gt;。它马上把测试里的字段名改了，再跑一遍，全绿。&lt;/p&gt;
&lt;p&gt;整个调试过程，我做了什么？我什么也没做。我只是看着它一步一步跑完。&lt;/p&gt;
&lt;p&gt;这就是 Bruno + CLI + AI 的真正价值。你的 API 测试不再是一个孤立的、需要你手动维护的负担，而是变成了 AI 可以读、可以写、可以跑、可以调试的一种代码。&lt;/p&gt;
&lt;p&gt;它和你的源码、你的 Git 历史、你的 CI/CD、你的 AI agent，全都是一体的。&lt;/p&gt;
&lt;p&gt;写到这里我猜有人要拍桌子了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;「等等，国内程序员谁还用 Postman 啊？Apifox 它不香吗？」&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;发，我们专门聊聊 Apifox。&lt;/p&gt;
&lt;p&gt;我必须先承认它真的很强。一体化平台，API 文档、调试、Mock、自动化测试、团队协作全在一个工具里搞定，相当于 Postman + Swagger + JMeter 三合一。界面是中文，文档是中文，国内团队几乎零学习成本。如果你的团队 20 人以上，需要权限管理、需要实时协作、需要统一的 API 文档管理，Apifox 是比 Bruno 更合适的选择。这点我不否认。&lt;/p&gt;
&lt;p&gt;而且 Apifox 不傻，它也在跟上 AI 浪潮。它有 apifox-cli，能在终端里跑测试场景。它甚至专门做了 Apifox MCP Server，可以让 Cursor 和 Claude Desktop 读取 Apifox 项目里的 API 文档，帮你写代码。&lt;/p&gt;
&lt;p&gt;那问题来了，Bruno 和 Apifox 在 AI 这件事上到底有啥不一样？&lt;/p&gt;
&lt;p&gt;我读了一圈 Apifox 的官方文档，发现一个挺有意思的差别。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Apifox 接 AI 的方式，是让 AI 来连接 Apifox。Bruno 接 AI 的方式，是 Bruno 根本就是 AI 已经会读的格式。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;具体说，Apifox 的 AI 工作流是这样的，你的测试数据躺在 Apifox 云端项目里，你装一个 Apifox MCP Server，配置 Cursor 去连这个 MCP，AI 通过 MCP 协议向 Apifox 服务发请求，把 API 文档拿下来，再帮你生成代码。&lt;/p&gt;
&lt;p&gt;Bruno 的 AI 工作流是这样的，你的测试就是项目目录里的 .bru 文件，AI 直接 cat、edit、bru run。完了。&lt;/p&gt;
&lt;p&gt;你看出区别了吗？Apifox 把 AI 当成一个外部工具来对接，所以需要 MCP 这一层中转。而 Bruno 根本不需要 MCP，因为它跟 AI 用的是同一种原生语言：&lt;strong&gt;文本文件加终端命令。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;还有一个更现实的差别。Apifox 的 MCP Server 现在的能力，是让 AI 读取 API 文档来生成代码，它并不能让 AI 直接去编辑你的测试用例。AI 在 Apifox 这边的角色基本上是只读的。&lt;/p&gt;
&lt;p&gt;而我前面演示的那两个场景，Claude Code 帮我写新测试、自动调试、修字段名，那是完整的读写跑改循环。AI 不光在读，还在写，还在跑，还在调试。这是因为 .bru 是普通文件，Edit 工具就能改，Bash 工具就能跑。它中间没有任何一层，所以也没有任何一层会卡住。&lt;/p&gt;
&lt;p&gt;这不是说 Apifox 不好，是两条不同的路。Apifox 选的是「我是平台，AI 来连我」，Bruno 选的是「我什么都不是，我就是文件」。前者适合企业级场景，后者适合代码即测试的开发者工作流。&lt;/p&gt;
&lt;p&gt;我自己是后者。我喜欢我所有的东西都能塞进 Git 仓库，喜欢 AI 直接读我硬盘上的文件，不喜欢任何账号、登录、云端中转。所以我选 Bruno。&lt;/p&gt;
&lt;p&gt;如果你的工作场景更接近企业级 SaaS 那一套，那 Apifox 完全没问题，甚至更合适。但如果你跟我一样，希望让 AI 一句话把接口测试搞定，那 Bruno 这条路确实更顺。&lt;/p&gt;
&lt;p&gt;聊完 Apifox 我们继续。顺便提一下 CI 集成，因为这块也很丝滑。Bruno CLI 支持输出 JUnit XML，GitHub Actions、GitLab CI、Jenkins 直接吃这个格式。我的工作流文件大概长这样。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;- name: 安装 Bruno CLI run: npm install -g @usebruno/cli- name: 跑接口测试 run: bru run --env production --reporter-junit junit.xml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;两步。你的 PR 一提交，所有接口测试自动跑一遍，挂了直接拒绝合并。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-04-13-postman-yue-lai-yue-yong-zhong-le-wo-huan-le-ge-kai-yuan-de-/007-56b53ed9.png"&gt;&lt;/p&gt;
&lt;p&gt;生成的测试执行也挺好看的&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-04-13-postman-yue-lai-yue-yong-zhong-le-wo-huan-le-ge-kai-yuan-de-/008-cc895180.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-04-13-postman-yue-lai-yue-yong-zhong-le-wo-huan-le-ge-kai-yuan-de-/009-90333e8c.png"&gt;&lt;/p&gt;
&lt;p&gt;整套东西免费、开源、本地、Git 友好、AI 友好，不用登录、不用付钱、不用担心数据上云。&lt;/p&gt;
&lt;p&gt;安装很简单。桌面版，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;brew install bruno
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="sb"&gt;```&lt;/span&gt;bash
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;CLI，
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="sb"&gt;```&lt;/span&gt;bash
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;8&lt;/span&gt;&lt;span class="cl"&gt;npm i -g @usebruno/cli
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;用了一周之后我把 Postman 卸了。&lt;/p&gt;
&lt;p&gt;不是它不好，是我不再需要一个登录才能用、集合存在别人云上、免费版各种限制、AI 完全帮不上忙的接口测试工具了。&lt;/p&gt;
&lt;p&gt;我只需要一个文件夹、几个 .bru 文件、一条终端命令，和一个能读懂这一切的 AI。&lt;/p&gt;
&lt;p&gt;有时候工具的进步不是功能越加越多，而是把不该有的东西去掉。Bruno 去掉了登录、去掉了云端、去掉了 GUI 锁定，剩下的全是程序员真正需要的东西。&lt;/p&gt;
&lt;p&gt;而当你把这些不需要的东西去掉之后，你会发现一个意外的副作用，AI 进来了。&lt;/p&gt;
&lt;p&gt;好了就说这么多。&lt;/p&gt;
&lt;p&gt;如果你也受够了 Postman，试试 Bruno。说不定你也会像我一样，用完就回不去了。&lt;/p&gt;
&lt;p&gt;ps:我写的 demo 在这里，你可以拉下来试一下，可以 run 的 ：https://github.com/xiaobox/bruno-demo&lt;/p&gt;</description></item><item><title>让Agent快上100倍的秘密，其实藏在一本大一计算机教科书里</title><link>https://xiaobox.github.io/p/2026-04-11-rang-agent-kuai-shang-100-bei-de-mi-mi-qi-shi-cang-zai-yi-be/</link><pubDate>Sat, 11 Apr 2026 10:50:21 +0000</pubDate><guid>https://xiaobox.github.io/p/2026-04-11-rang-agent-kuai-shang-100-bei-de-mi-mi-qi-shi-cang-zai-yi-be/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-04-11-rang-agent-kuai-shang-100-bei-de-mi-mi-qi-shi-cang-zai-yi-be/cover.jpg" alt="Featured image of post 让Agent快上100倍的秘密，其实藏在一本大一计算机教科书里" /&gt;&lt;p&gt;事情是这样的。&lt;/p&gt;
&lt;p&gt;最近我几乎每天都在用Claude Code写东西。用得越多，我越产生一种奇怪的感觉。&lt;/p&gt;
&lt;p&gt;就是你给它下完一个任务之后，它开始一步一步地干活。先是啪，读了一个文件。然后哒，想了几秒。然后啪，又打开一个文件。再想几秒。再打开一个文件。就这么一直持续下去。&lt;/p&gt;
&lt;p&gt;你坐在椅子上看着进度条一格一格地亮起来，心里清楚得不能再清楚，这十个文件它明明可以一起读的，它们之间根本没有任何依赖关系。&lt;/p&gt;
&lt;p&gt;但它就是不。它就是要一个接一个地来。&lt;/p&gt;
&lt;p&gt;一时间无语凝噎。&lt;/p&gt;
&lt;p&gt;后来我跟几个同样重度用Agent的朋友聊了一下，他们也都有这个感受。说真的我始终觉得这是现在所有Agent产品共同的一个病，不管是 &lt;code&gt;Claude Code&lt;/code&gt;、&lt;code&gt;Cursor&lt;/code&gt;、&lt;code&gt;Manus&lt;/code&gt;还是那些MCP插件，只要你让它干稍微复杂一点的活，你就会看到它在那里慢悠悠地一步一步走，像一个做事非常有耐心但完全不会一心二用的老实人。&lt;/p&gt;
&lt;p&gt;前两天跟朋友吐槽这事的时候，我又想起了两年前Berkeley那帮人写的一篇论文。论文叫LLMCompiler，2024年就发在ICML上了，现在回头看它也不算新东西。但每次我被Agent气到的时候都会想起它，觉得它的思路到今天都没过时，甚至越品越有味道。&lt;/p&gt;
&lt;p&gt;它当时就已经把这个病的根源讲得很清楚了，这个慢不是LLM的错，也不是任务复杂度的错，是我们给它用的那套调度系统，还停留在1960年代的水平。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-04-11-rang-agent-kuai-shang-100-bei-de-mi-mi-qi-shi-cang-zai-yi-be/001-5d4b1967.png"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;这篇论文的名字挺干的，叫**《An LLM Compiler for Parallel Function Calling》**，ICML 2024。作者是Sehoon Kim、Amir Gholami那帮人，都在Berkeley和LBNL。它不是今年的新论文，但在我心里一直是Agent方向上最被低估的几篇之一。&lt;/p&gt;
&lt;p&gt;它在做的事其实非常cool。&lt;/p&gt;
&lt;p&gt;它在把大学一年级《计算机组成原理》那本书里的东西，原样搬到LLM的世界里。&lt;/p&gt;
&lt;p&gt;坦率的讲，你想想看过去60年整个计算机体系结构的历史，其实就是一部「怎么让本来是串行的指令跑得更并行」的历史。指令流水线、乱序执行、超标量、分支预测，这些听着就头大的名词，说到底都是在干一件事，就是让CPU不要一条指令一条指令傻乎乎地等，能同时干的活就一起干。&lt;/p&gt;
&lt;p&gt;这套东西人类已经研究得非常透了。透到什么程度呢？透到你今天买一颗普通的i5芯片，它每个时钟周期能同时发射的指令数，大概是80年代那种整栋楼的超级计算机的水平。&lt;/p&gt;
&lt;p&gt;但是。&lt;/p&gt;
&lt;p&gt;当我们把LLM当成一种新型处理器去用的时候，这套智慧全忘了。&lt;/p&gt;
&lt;p&gt;现在几乎所有的Agent框架，底层都是一个叫ReAct的东西。它是Yao等人2022年提的，全称是Reason + Act。工作方式非常朴素，想一步，做一步，看结果，再想一步，再做一步，再看结果。它是一个循环。&lt;/p&gt;
&lt;p&gt;听着很自然对吧？它确实自然。但你仔细看就会发现，这玩意从执行效率上来说，跟那种每次只能执行一条指令、做完一条才开始下一条的远古处理器，是一样的。&lt;/p&gt;
&lt;p&gt;一次一条。干等。&lt;/p&gt;
&lt;p&gt;而且这个问题在越来越多的Agent场景里暴露得越来越厉害，因为我们现在给Agent的活越来越复杂，一次要调用的工具越来越多。ReAct的串行执行就成了一个越来越重的镣铐。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;回到LLMCompiler这块。&lt;/p&gt;
&lt;p&gt;作者的思路简单粗暴，既然Agent执行工具调用的过程跟CPU执行指令长得一样，那就直接套编译器的架构好了。他们搞了三个组件。&lt;/p&gt;
&lt;p&gt;第一个叫 &lt;strong&gt;Function Calling Planner&lt;/strong&gt;，函数调用规划器。你可以把它想象成编译器里那个分析语义、构建依赖图的部分。用户给了一个问题，比如论文里举的那个例子，「微软的市值需要涨多少才能超过苹果？」，Planner要做的事情是先把这个问题拆成几个独立的任务，再搞清楚这些任务之间谁依赖谁。&lt;/p&gt;
&lt;p&gt;它会拆成三步。&lt;/p&gt;
&lt;p&gt;一，去查微软的市值。 二，去查苹果的市值。 三，用一个数学工具做减法，把差值算出来。&lt;/p&gt;
&lt;p&gt;然后它会发现一件事，任务1和任务2，彼此没有任何关系。它们完全可以同时去查。只有任务3需要等前两个都拿到结果。&lt;/p&gt;
&lt;p&gt;这就是一张 &lt;strong&gt;DAG&lt;/strong&gt;，有向无环图，编译器里最核心的数据结构之一。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-04-11-rang-agent-kuai-shang-100-bei-de-mi-mi-qi-shi-cang-zai-yi-be/002-04810f26.png"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;第二个组件叫 &lt;strong&gt;Task Fetching Unit&lt;/strong&gt;，任务获取单元。这个名字直接就是从CPU里偷来的。&lt;/p&gt;
&lt;p&gt;在现代CPU里有一个东西叫指令获取单元，它的任务是一旦前一条指令把某个寄存器的值算出来了，立刻把依赖这个寄存器的下一条指令发射出去，别等一整串指令都准备好再开搞，那样太慢。&lt;/p&gt;
&lt;p&gt;LLMCompiler的Task Fetching Unit做的事完全一样。Planner一吐出DAG，它就开始扫描，发现哪些任务的依赖已经解决了，立刻往下扔。任务1和任务2没有依赖？好，同时发射，两个搜索并行执行。任务3等着1和2的结果？好，等它们回来我再把结果塞进任务3里，然后发射。&lt;/p&gt;
&lt;p&gt;整个过程是流式的。Planner一边在吐计划，执行器一边在干活，中间没有「等Planner把所有计划全想完再开始」这种停顿。论文里专门做了个消融实验，流式处理本身就贡献了一个量级的加速。&lt;/p&gt;
&lt;p&gt;第三个组件叫 &lt;strong&gt;Executor&lt;/strong&gt;，执行器。这个没啥好说的，它就是真正去调工具的那个家伙。Task Fetching Unit告诉它哪个工具可以调了，它就调。&lt;/p&gt;
&lt;p&gt;三个东西加起来，整个架构就跟一台小号的CPU一模一样。有人分析程序，有人调度，有人执行。&lt;/p&gt;
&lt;p&gt;说到这我真的有点被打动。你知道我为啥被打动吗？因为这个思路其实任何一个学过编译原理的本科生都能想到。它没有任何复杂的数学，没有什么神秘的训练技巧，就是把一个用了60年的老配方，拿来炒一道新菜。&lt;/p&gt;
&lt;p&gt;但它偏偏有效。而且效果好到离谱。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;顺着上面的再聊聊，这篇论文最让我兴奋的其实是实验结果部分。&lt;/p&gt;
&lt;p&gt;作者用了四个benchmark来测试LLMCompiler。这四个测试排起来有一个隐藏的升番结构，从最简单的场景到最复杂的场景，效果一个比一个炸。我逐个说一下。&lt;/p&gt;
&lt;p&gt;第一个叫HotpotQA。这是个很经典的多跳问答数据集，论文的Figure 1就举了一个例子，「斯科特·德瑞克森和埃德·伍德是不是同一个国籍？」这种问题。用ReAct的话就是一步一步来，先搜A，拿到结果，再搜B，拿到结果，再对比。用LLMCompiler的话，A和B可以同时搜。&lt;/p&gt;
&lt;p&gt;速度快了1.8倍。成本降了3.37倍。准确率基本一样。&lt;/p&gt;
&lt;p&gt;就这个结果拎出来看已经很能打了，但它只是开胃菜。&lt;/p&gt;
&lt;p&gt;第二个叫Movie Recommendation。这个更有意思，它每次要你从8部电影里找出跟某部电影最像的那部。也就是要对8部电影分别做独立的搜索和分析。&lt;/p&gt;
&lt;p&gt;ReAct在这里干了一件特别傻的事。论文附录里有一张图我看完直接笑出声，它显示有大约85%的样本，ReAct根本没搜完8部就结束了。它搜到第五部就停下来，觉得「我好像够了」，然后给一个答案。&lt;/p&gt;
&lt;p&gt;你敢信？？？&lt;/p&gt;
&lt;p&gt;一个号称能干活的Agent，居然连把活干完都做不到。它会提前认输。&lt;/p&gt;
&lt;p&gt;LLMCompiler在这里就完全没这个问题，因为Planner一开始就把8个任务全部规划好了，Executor必须全部执行完才能汇总。结果是速度快了3.74倍，成本降了6.73倍，准确率还反超ReAct 7个多点。&lt;/p&gt;
&lt;p&gt;第三个叫Game of 24。这游戏你们可能玩过，给你4个数字让你用加减乘除搞出24。之前最强的解法叫Tree-of-Thoughts，让LLM自己去搜索各种可能的组合。LLMCompiler在这里做了一个很骚的事，它把「Tree-of-Thoughts的一次尝试」当成一个工具，然后让Planner去并行调度这些尝试。&lt;/p&gt;
&lt;p&gt;速度快了2倍。&lt;/p&gt;
&lt;p&gt;到这里我已经觉得够牛了。&lt;/p&gt;
&lt;p&gt;但是真正让我给整不会的是第四个benchmark，WebShop。这是一个模拟网上购物的环境，你要在一堆商品里找到符合某些需求的那一个。典型的操作是搜索→看结果→再搜索→再看结果。&lt;/p&gt;
&lt;p&gt;LLMCompiler在这里直接跑出了101.7倍的加速。&lt;/p&gt;
&lt;p&gt;不是10倍，不是50倍，是一百零一点七倍。&lt;/p&gt;
&lt;p&gt;而且成功率还比ReAct高了25.7个百分点。&lt;/p&gt;
&lt;p&gt;我第一次看到这个数字的时候真的愣住了。我来回看了好几遍论文的表格，生怕自己看错了小数点。101.7x。&lt;/p&gt;
&lt;p&gt;它的原因其实非常直观。WebShop里有大量「先广撒网再选最优」的搜索动作。LLMCompiler可以一口气把所有候选搜索并行发射出去，而ReAct得一个一个搜。你想想，如果你在淘宝上找一个东西，你是一次打开十几个标签页横向对比，还是一个一个点开再返回再点开？&lt;/p&gt;
&lt;p&gt;答案很明显。&lt;/p&gt;
&lt;p&gt;但前者需要你有一个「规划」的能力，得先知道哪十几个是值得看的。这恰好就是LLMCompiler在做的事。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-04-11-rang-agent-kuai-shang-100-bei-de-mi-mi-qi-shi-cang-zai-yi-be/003-df4faf7b.png"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;这块需要注意一下。LLMCompiler的意义不只是快，还有一个更深的点，它顺手救了准确率。&lt;/p&gt;
&lt;p&gt;这个我刚才提到了一嘴，但值得展开说说。作者分析了ReAct失败的案例之后发现，这些失败的绝大多数其实跟智力无关，跟纪律有关。&lt;/p&gt;
&lt;p&gt;两种典型的失败场景。一种是提前收工，它只搜了部分信息就觉得够了，开始瞎答。另一种更惨，是它会在同一个查询上无限循环，因为Wikipedia返回的信息不够精确，它就一直搜一直搜一直搜，直到context window爆掉。&lt;/p&gt;
&lt;p&gt;这两种失败加起来，贡献了ReAct绝大部分的失败样本。&lt;/p&gt;
&lt;p&gt;为啥会这样？我自己的理解是，ReAct是一种即兴架构。它没有全局视野，每一步都是基于上一步的观察临时决策的。这种即兴决策模式很像我们人脑，但它也天然带着人脑即兴决策的毛病，容易累、容易放弃、容易走进死胡同。&lt;/p&gt;
&lt;p&gt;LLMCompiler强迫模型在一开始就把所有要做的事列出来，这等于逼着它做一次系统性的规划。规划好了之后，执行阶段就只负责执行，不再思考。&lt;/p&gt;
&lt;p&gt;我觉得这里有一个非常深的启发。我们过去几年一直在迷信让LLM多想一步，搞出了Chain-of-Thought、Tree-of-Thoughts、Self-Reflection各种花活，都是在鼓励模型「思考得更细、更久、更多」。但其实有时候反过来，让它先想一次然后别再想了，反而更管用。&lt;/p&gt;
&lt;p&gt;CPU的设计哲学其实也是这样。现代CPU里最快的指令是那些不需要跳转、不需要预测、不需要动态决策的指令。凡是涉及到走一步看一步的指令，都会拖慢整条流水线。&lt;/p&gt;
&lt;p&gt;计算机硬件的人早就发现了，即兴决策是昂贵的。&lt;/p&gt;
&lt;p&gt;而这个老道理，现在又回到了AI Agent这边。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;坦率的讲，我觉得LLMCompiler这篇论文本身可能不是最大的新闻。真正的新闻是它揭示的那个更大的趋势。&lt;/p&gt;
&lt;p&gt;我们正在把整个计算机体系结构，重新发明一遍。&lt;/p&gt;
&lt;p&gt;你仔细想想这几年LLM推理和Agent方向上那些最亮眼的突破，几乎每一个都能在老教科书里找到原型。&lt;/p&gt;
&lt;p&gt;Speculative decoding，是把CPU的分支预测搬到了LLM推理。 KV cache，是把CPU的cache机制搬到了LLM推理。 Continuous batching，是把操作系统的进程调度搬到了LLM推理。 现在LLMCompiler，是把编译器的指令调度搬到了LLM Agent。&lt;/p&gt;
&lt;p&gt;每一个都在发生。每一个都带来10倍甚至100倍的加速。每一个的核心创意都不是横空出世的神来之笔，而是一句「等等，这个问题我们在硬件/OS层面已经解决过了，直接拿来用就好」。&lt;/p&gt;
&lt;p&gt;卡帕西前阵子说过一句我记了很久的话，他说LLM是一种新的计算机，一种以自然语言为指令集的计算机。这句话如果你真的认真对待，那它的所有推论都是自洽的。既然它是一种新的计算机，那我们给旧计算机发明的所有优化技巧，理论上都应该能再用一次。&lt;/p&gt;
&lt;p&gt;我有时候会觉得，我们这一代做AI的人特别幸运。我们在亲眼看一部已经拍过一遍的电影，被用新的道具重新拍摄。剧本是一样的，角色是一样的，剧情走向都是一样的。但因为道具全换了，看起来就像一部全新的片子。而且你手里只要有一本原版的剧本，你就能提前知道下一幕会发生什么。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-04-11-rang-agent-kuai-shang-100-bei-de-mi-mi-qi-shi-cang-zai-yi-be/004-65e386c4.png"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;回到这篇论文本身。&lt;/p&gt;
&lt;p&gt;我觉得它最重要的贡献其实不是那些benchmark数字，而是它开了一个非常清晰的方向。那就是Agent的慢不是不可解决的。&lt;/p&gt;
&lt;p&gt;你今天用Claude Code等十分钟，不是因为LLM笨，也不是因为你的任务太复杂。是因为底下那套调度系统还在用ReAct这种20世纪60年代级别的执行模式。只要换上哪怕一个粗糙的编译器思路，立刻就能快10倍、快100倍。&lt;/p&gt;
&lt;p&gt;其实这两年已经有不少框架在往这个方向走了，LangGraph、LlamaIndex都陆陆续续搞过类似的planner组件，多Agent框架里的并发调度也都能看到这套思路的影子。但奇怪的是，我们日常在用的那些最主流的Agent产品，Claude Code、Cursor这些，还是没有把这套东西吃得特别透。你还是经常能看到它们在那里一步一步串行地跑，跑得你抓狂。&lt;/p&gt;
&lt;p&gt;我始终觉得这是一件很可惜的事。一个两年前就该被充分吸收的好思路，到今天还只在部分框架里存在，绝大多数用户还是在吃ReAct的苦。&lt;/p&gt;
&lt;p&gt;其实之前OpenAI做过一个简化版，它叫Parallel Function Calling。但这篇论文里也明确对比了，OpenAI那个只能处理最简单的、完全独立的并行任务，一碰到有依赖关系的就歇菜了。LLMCompiler能处理有依赖的完整DAG，这是质变。而且论文在ParallelQA这个他们自己造的benchmark上，直接把OpenAI的并行函数调用给干穿了。&lt;/p&gt;
&lt;p&gt;还有一个让我很开心的点，LLMCompiler不依赖特定模型。它能跑在闭源的GPT系列上，也能跑在开源的LLaMA-2 70B上，效果都很好。这意味着你要用它，不需要求爷爷告奶奶去办一个特殊API，自己拿个开源模型搭一套就能跑。对整个开源生态是实实在在的利好。&lt;/p&gt;
&lt;p&gt;论文的代码早就开源在 &lt;a class="link" href="https://github.com/SqueezeAILab/LLMCompiler" target="_blank" rel="noopener"
 &gt;https://github.com/SqueezeAILab/LLMCompiler&lt;/a&gt; ，这两年我零零散散跑过一些例子，整体感觉是它确实好使，但对Planner的prompt质量非常敏感，稍微写粗糙一点就容易崩。这大概也是为啥它没在主流产品里全面铺开的原因之一，论文里优雅的架构，落到工程上总会多出一堆脏活。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;最后说点题外话。&lt;/p&gt;
&lt;p&gt;我一直觉得AI这个行业最迷人的地方，就在于它需要你是一个杂食动物。你得懂一点机器学习，懂一点系统，懂一点产品，懂一点用户。因为AI正在跟所有领域发生化学反应，任何一个你以为已经过时的角落，都可能突然长出一个全新的方向。&lt;/p&gt;
&lt;p&gt;LLMCompiler这篇论文就是一个典型的例子。它既不需要你是最顶尖的ML研究员，也不需要你是最强的系统工程师。它需要你有一个能从「我的LLM Agent跑得好慢啊」跳到「诶等等，CPU当年也有这个问题，是怎么解决的来着？」的跨界联想能力。&lt;/p&gt;
&lt;p&gt;我始终觉得这种联想能力，比任何单一领域的深度都重要。&lt;/p&gt;
&lt;p&gt;很多朋友问我怎么跟上AI的发展。我有时候觉得，与其拼命去看最新的模型发布，不如回头去翻翻那些老的、经典的、看起来跟AI毫无关系的书。编译原理、操作系统、计算机网络、数据库系统、图形学。这些书里有太多你以为已经过时的东西，在LLM时代突然又活了过来。&lt;/p&gt;
&lt;p&gt;你读过的每一本旧书，都可能在未来某天变成一枚重新上膛的子弹。&lt;/p&gt;
&lt;p&gt;前提是你得先把枪挂在墙上。&lt;/p&gt;
&lt;p&gt;以上。&lt;/p&gt;</description></item><item><title>OpenAI 开源 Symphony：AI 不再只是写代码，而是开始接管“工作流”</title><link>https://xiaobox.github.io/p/2026-03-07-openai-kai-yuan-symphony-ai-bu-zai-zhi-shi-xie-dai-ma-er-shi/</link><pubDate>Sat, 07 Mar 2026 11:39:53 +0000</pubDate><guid>https://xiaobox.github.io/p/2026-03-07-openai-kai-yuan-symphony-ai-bu-zai-zhi-shi-xie-dai-ma-er-shi/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-03-07-openai-kai-yuan-symphony-ai-bu-zai-zhi-shi-xie-dai-ma-er-shi/cover.jpg" alt="Featured image of post OpenAI 开源 Symphony：AI 不再只是写代码，而是开始接管“工作流”" /&gt;&lt;p&gt;最近跟业界一些朋友交流，不少公司正在做内部软件开发的 &lt;strong&gt;AI 自动化流程系统&lt;/strong&gt;，正好这两天，OpenAI 在 GitHub 上低调开源了一个很值得认真看的项目：&lt;strong&gt;Symphony&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-03-07-openai-kai-yuan-symphony-ai-bu-zai-zhi-shi-xie-dai-ma-er-shi/001-29d66690.png"&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-03-07-openai-kai-yuan-symphony-ai-bu-zai-zhi-shi-xie-dai-ma-er-shi/002-23143dde.png"&gt;&lt;/p&gt;
&lt;p&gt;如果只看名字，你很容易把它理解成“又一个多 Agent 编排框架”；但只要认真读完 README、SPEC.md 和参考实现里的 WORKFLOW.md，你会发现它真正想解决的，根本不是“让 AI 会写代码”，而是另一件更大的事：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如何把软件研发中的“工作”，交给一套可以持续运行、可隔离、可调度、可回收、可观测的系统去推进。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这就是 Symphony 最重要的定位。官方原话非常值得细品：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;它会把项目工作转成 isolated, autonomous implementation runs，让团队从“监督 coding agents”转向“管理 work”。README 里的 demo 也很直白：Symphony 盯着 Linear 看板拿任务，拉起 agent 处理 issue，回传 CI 状态、PR review 反馈、复杂度分析和 walkthrough 视频，最后在被接受后安全落 PR。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;很多人第一次看到这里，会本能地把它和 Copilot、Cursor、Claude Code 之类工具放在一起比较。但我觉得，真正准确的比较方式不是“谁代码写得更强”，而是：谁更接近一个面向研发现场的执行系统。 Copilot 类产品解决的是“我写代码时，旁边有个聪明助手”；Symphony 想解决的是“我有一堆 issue，能不能让系统自己取单、分配环境、拉起 Agent、推进状态、处理失败、保留上下文，并把结果交回给我验收”。这已经不只是“辅助编码”，而是&lt;strong&gt;开始触碰软件交付流水线本身&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="一symphony-到底是什么"&gt;&lt;a href="#%e4%b8%80symphony-%e5%88%b0%e5%ba%95%e6%98%af%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;一、Symphony 到底是什么？
&lt;/h2&gt;&lt;p&gt;从 SPEC.md 看，Symphony 的定义非常清晰：它是一个 &lt;strong&gt;long-running automation service&lt;/strong&gt;。在当前规范版本里，它会持续从 issue tracker 读取工作（v1 里明确是 Linear），为每个 issue 创建独立 workspace，并在这个 workspace 里运行 coding agent session。规范还特别强调了它要解决的四类问题：&lt;/p&gt;
&lt;p&gt;1.把 issue 执行变成守护式工作流&lt;/p&gt;
&lt;p&gt;2.把每个任务隔离到独立 workspace&lt;/p&gt;
&lt;p&gt;3.把工作流策略放回 repo 内的 WORKFLOW.md&lt;/p&gt;
&lt;p&gt;4.以及为多任务并发运行提供足够的 observability。&lt;/p&gt;
&lt;p&gt;这段定义很重要，因为它一下子把 Symphony 和大量“Agent Demo”拉开了距离。它不是一个写几个 prompt、串几个工具的 toy project，也不是一个单轮任务脚本。它有轮询、有调度、有状态机、有重试退避、有 workspace 生命周期、有运行期事件、有恢复逻辑。换句话说，&lt;strong&gt;它的思维方式更像一个 orchestrator，而不是一个单纯的 agent wrapper。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;更关键的是，SPEC 还专门写了一个“&lt;strong&gt;重要边界&lt;/strong&gt;”：Symphony 是 scheduler/runner 和 tracker reader。这句话很克制，也很专业。它的意思是，Symphony 的职责重点不是替你定义所有业务流程，而是负责任务编排、执行承载和状态协调；而 ticket 状态变更、评论、PR 链接等写操作，通常还是由 coding agent 借助工具在运行时完成。也就是说，&lt;strong&gt;它不是一个万能 PM 系统，而是一层面向软件交付的 agent orchestration 壳。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-03-07-openai-kai-yuan-symphony-ai-bu-zai-zhi-shi-xie-dai-ma-er-shi/003-d4fe840a.png"&gt;&lt;/p&gt;
&lt;h2 id="二它为什么比会写代码更进一步"&gt;&lt;a href="#%e4%ba%8c%e5%ae%83%e4%b8%ba%e4%bb%80%e4%b9%88%e6%af%94%e4%bc%9a%e5%86%99%e4%bb%a3%e7%a0%81%e6%9b%b4%e8%bf%9b%e4%b8%80%e6%ad%a5" class="header-anchor"&gt;&lt;/a&gt;二、它为什么比“会写代码”更进一步？
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;因为真正麻烦的，从来不是 AI 能不能生成一段代码，而是几十个任务并行推进时，系统怎么不失控。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Symphony 在这方面做得非常工程化。它有明确的内部状态机：Unclaimed、Claimed、Running、RetryQueued、Released。它还定义了 run attempt 的阶段：准备 workspace、构建 prompt、拉起 agent 进程、初始化 session、流式执行 turn、结束、成功、失败、超时、卡死、被 reconciliation 取消。它甚至规定了每次 poll tick 到来时，先 reconciliation，再校验配置，再拉候选 issue，再按优先级分发，最后通知 observability 消费者。&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-03-07-openai-kai-yuan-symphony-ai-bu-zai-zhi-shi-xie-dai-ma-er-shi/004-0d5ab841.png"&gt;&lt;/p&gt;
&lt;p&gt;这套设计背后的思想可以概括成一句话：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不要先问“怎么让 Agent 跑起来”，而要先问“怎么避免它跑重、跑错、跑飞”&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如 candidate selection 里就有一条很像真实研发现场的规则：如果 issue 还处于 Todo，而它依赖的 blocker 还没进入终态，那就不要派发。排序也不是瞎来，而是按 priority、创建时间、issue 标识顺序稳定分发。失败之后也不是简单重试，而是区分正常退出后的短延迟 continuation retry 和异常退出后的指数退避重试。这样的设计，明显已经不是“写代码助手”的思路，而是“任务执行系统”的思路。&lt;/p&gt;
&lt;h2 id="三每个-issue-一个-workspace这是-symphony-最值钱的工程细节"&gt;&lt;a href="#%e4%b8%89%e6%af%8f%e4%b8%aa-issue-%e4%b8%80%e4%b8%aa-workspace%e8%bf%99%e6%98%af-symphony-%e6%9c%80%e5%80%bc%e9%92%b1%e7%9a%84%e5%b7%a5%e7%a8%8b%e7%bb%86%e8%8a%82" class="header-anchor"&gt;&lt;/a&gt;三、每个 issue 一个 workspace：这是 Symphony 最值钱的工程细节
&lt;/h2&gt;&lt;p&gt;如果你只让我挑 Symphony 里最关键的一点，我会选这个：&lt;strong&gt;per-issue workspace。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SPEC 写得非常清楚：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;每个 issue 的 workspace 路径都必须位于配置的 workspace root 之下；coding agent 只能在该 issue 的 workspace 里执行；workspace 目录名必须净化；还支持 after_create、before_run、after_run、before_remove 等 hooks。工作区会跨运行复用，但终态 issue 可以在启动或状态变更时清理。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;**为什么这个设计这么重要？**因为一旦没有隔离，Agent 系统很快就会碰到三个问题：&lt;strong&gt;上下文污染、任务互相踩踏、失败后难以恢复&lt;/strong&gt;。Symphony 的思路很像给每个工单都分配一个独立工位，Agent 只能在自己的工位里思考、改代码、跑测试、记录状态。哪怕它中途失败了，下次重试回来，也可以在同一个 workspace 上继续，而不是重新失忆。&lt;/p&gt;
&lt;p&gt;这也是为什么我说 Symphony 更接近“工程执行系统”而不是“聊天式 Agent”。聊天系统强调对话连续；Symphony 强调的是 任务连续性。这两个东西，根本不是一个层级。&lt;/p&gt;
&lt;h2 id="四workflowmd-才是灵魂把-prompt-升级成-repo-内契约"&gt;&lt;a href="#%e5%9b%9bworkflowmd-%e6%89%8d%e6%98%af%e7%81%b5%e9%ad%82%e6%8a%8a-prompt-%e5%8d%87%e7%ba%a7%e6%88%90-repo-%e5%86%85%e5%a5%91%e7%ba%a6" class="header-anchor"&gt;&lt;/a&gt;四、WORKFLOW.md 才是灵魂：把 Prompt 升级成 repo 内契约
&lt;/h2&gt;&lt;p&gt;Symphony 很聪明的一点，是它没有把流程硬编码进平台，而是把策略收回到仓库里。SPEC 规定 WORKFLOW.md 由 YAML front matter 和 Markdown prompt body 组成，运行时会解析出 config 与 prompt template；很多核心行为——轮询间隔、workspace root、并发限制、hooks、agent 参数——都来自这份 repo-owned contract。&lt;/p&gt;
&lt;p&gt;参考实现里的 WORKFLOW.md 更是把这种思想写得非常彻底。它规定了 issue 在不同状态下该怎么流转：&lt;/p&gt;
&lt;p&gt;●Todo 要立即切到 In Progress，然后找或建唯一的 ## Codex Workpad 评论，再开始分析与实现；&lt;/p&gt;
&lt;p&gt;●Human Review 阶段不再改代码，只轮询 review 结果；&lt;/p&gt;
&lt;p&gt;●进入 Merging 后必须走专门的 land 技能，不能直接 gh pr merge。文档还要求把单个 workpad comment 当作进度和交接的唯一真相源，&lt;strong&gt;并且把 out-of-scope 改进拆成新的 Backlog issue，而不是在当前任务里偷偷扩 scope。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-03-07-openai-kai-yuan-symphony-ai-bu-zai-zhi-shi-xie-dai-ma-er-shi/005-d06eb76f.png"&gt;&lt;/p&gt;
&lt;p&gt;这件事的意义非常大。&lt;strong&gt;它意味着团队以后真正需要打磨的，不只是“怎么写 prompt”，而是“怎么把流程、约束、验收标准、状态流转、回退机制，写成一份和代码一起版本化的工程契约”。这比 prompt engineering 更接近组织能力。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="五为什么参考实现偏偏选了-elixir"&gt;&lt;a href="#%e4%ba%94%e4%b8%ba%e4%bb%80%e4%b9%88%e5%8f%82%e8%80%83%e5%ae%9e%e7%8e%b0%e5%81%8f%e5%81%8f%e9%80%89%e4%ba%86-elixir" class="header-anchor"&gt;&lt;/a&gt;五、为什么参考实现偏偏选了 Elixir？
&lt;/h2&gt;&lt;p&gt;这不是噱头，反而是我觉得 Symphony 最有工程味的地方之一。&lt;/p&gt;
&lt;p&gt;GitHub 仓库当前语言分布里，Elixir 约占 &lt;strong&gt;94.9%&lt;/strong&gt;；README 也直接写了 &lt;strong&gt;Why Elixir?&lt;/strong&gt;：因为 Elixir 运行在 Erlang/BEAM/OTP 之上，很适合监督长时间运行的进程，并且支持在不停止活跃 subagents 的情况下做 &lt;strong&gt;hot code reloading&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这和 Symphony 的问题形态是高度匹配的。一个普通 Web 请求可能几十毫秒就结束，但一个 coding agent 处理复杂任务时，可能会持续很久，还要接收事件、处理重试、维持会话、更新状态、暴露观测数据。BEAM/OTP 擅长的，恰好就是这种长生命周期、并发多、失败要可控隔离的系统。OpenAI 官方没有在 README 里展开讲 supervision tree 这些词，但它给出的理由已经足够说明方向：&lt;strong&gt;Symphony 不是在追求“AI 生态默认语言”，而是在追求“最适合承载 agent orchestration 的运行时”。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="六真正的前提不是更强模型而是-harness-engineering"&gt;&lt;a href="#%e5%85%ad%e7%9c%9f%e6%ad%a3%e7%9a%84%e5%89%8d%e6%8f%90%e4%b8%8d%e6%98%af%e6%9b%b4%e5%bc%ba%e6%a8%a1%e5%9e%8b%e8%80%8c%e6%98%af-harness-engineering" class="header-anchor"&gt;&lt;/a&gt;六、真正的前提不是更强模型，而是 Harness Engineering
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;如果说 Symphony 讲的是“如何调度 Agent”，那 Harness Engineering 讲的就是“怎样让 Agent 值得被调度”。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;OpenAI 在官方文章里把这件事说得很重：他们构建的产品里，应用逻辑、测试、CI、文档、可观测性和内部工具，全部由 Codex 写出；而人类工程师的角色，从直接写代码，转向设计环境、明确意图、构建反馈回路。文章里那句“Humans steer. Agents execute.”，几乎可以看作整个 Symphony 时代的软件工程宣言。&lt;/p&gt;
&lt;p&gt;也正因如此，README 才会明确写：Symphony 最适合已经采用 harness engineering 的代码库。意思很简单：如果你的仓库没有可靠测试、没有清晰边界、没有稳定构建入口、没有可验证的反馈回路，那么再强的 Agent 也只是更快地在混乱里打转。&lt;strong&gt;Symphony 的价值，不是替代工程纪律；恰恰相反，它会把工程纪律的重要性放大十倍。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="七它的边界也必须讲清楚"&gt;&lt;a href="#%e4%b8%83%e5%ae%83%e7%9a%84%e8%be%b9%e7%95%8c%e4%b9%9f%e5%bf%85%e9%a1%bb%e8%ae%b2%e6%b8%85%e6%a5%9a" class="header-anchor"&gt;&lt;/a&gt;七、它的边界也必须讲清楚
&lt;/h2&gt;&lt;p&gt;一个成熟的技术判断，不能只讲想象力，不讲边界。&lt;/p&gt;
&lt;p&gt;Symphony 现在仍是一个工程预览版，README 明确写了适用于 trusted environments；SPEC 也写了 approval policy、sandbox policy、operator confirmation posture 都是 implementation-defined，不同实现可以高信任，也可以更严格。它当前规范版本只定义了 Linear 作为 tracker，至于更多 issue tracker 适配器，还是 TODO。参考实现虽然带可选 Phoenix observability 服务和 JSON API，但整个项目还远没到“所有团队直接开箱上生产”的阶段。&lt;/p&gt;
&lt;p&gt;所以，最稳妥的结论不是“研发彻底无人化已经到来”，而是：&lt;/p&gt;
&lt;p&gt;OpenAI 正在把 AI Coding 从“单点能力演示”推进到“工程系统形态演示”。&lt;/p&gt;
&lt;p&gt;这一步，比单纯再出一个更强的代码模型，更值得关注。&lt;/p&gt;
&lt;h2 id="结语"&gt;&lt;a href="#%e7%bb%93%e8%af%ad" class="header-anchor"&gt;&lt;/a&gt;结语
&lt;/h2&gt;&lt;p&gt;如果一定要用一句话概括 Symphony，我会这样说：&lt;/p&gt;
&lt;p&gt;它不是在教 Agent 如何写代码，而是在教团队如何把“软件交付”本身改写成一套可执行、可编排、可观测的系统。&lt;/p&gt;
&lt;p&gt;过去，AI 是工程师的副驾驶；现在，Symphony 展示的是另一种可能：工程师不再盯着每一行代码，而是站到更高一层，去设计流程、约束环境、设定验收标准，然后管理一批持续运行的 agent 去推进工作。&lt;strong&gt;真正的变化，不是“AI 会不会写 CRUD”，而是“软件组织会不会因此改写自己的工作方式”。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这，才是 Symphony 最值得认真读的地方。&lt;/p&gt;</description></item><item><title>全程0人工写代码！干掉低级码农的不是大模型</title><link>https://xiaobox.github.io/p/2026-02-24-quan-cheng-0-ren-gong-xie-dai-ma-gan-diao-di-ji-ma-nong-de-b/</link><pubDate>Tue, 24 Feb 2026 03:46:39 +0000</pubDate><guid>https://xiaobox.github.io/p/2026-02-24-quan-cheng-0-ren-gong-xie-dai-ma-gan-diao-di-ji-ma-nong-de-b/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-02-24-quan-cheng-0-ren-gong-xie-dai-ma-gan-diao-di-ji-ma-nong-de-b/cover.jpg" alt="Featured image of post 全程0人工写代码！干掉低级码农的不是大模型" /&gt;&lt;h1 id="全程0人工写代码干掉低级码农的不是大模型"&gt;&lt;a href="#%e5%85%a8%e7%a8%8b0%e4%ba%ba%e5%b7%a5%e5%86%99%e4%bb%a3%e7%a0%81%e5%b9%b2%e6%8e%89%e4%bd%8e%e7%ba%a7%e7%a0%81%e5%86%9c%e7%9a%84%e4%b8%8d%e6%98%af%e5%a4%a7%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;全程0人工写代码！干掉低级码农的不是大模型
&lt;/h1&gt;&lt;p&gt;在当前全行业的 AI 辅助编程浪潮中，大多数工具仍停留在“交互式伴游”阶段，而支付巨头 Stripe 却打造了一套完全无人值守的端到端代码智能体——“小黄人”（Minions）&lt;/p&gt;
&lt;p&gt;小黄人是一个独立打工的“数字员工”。目前的惊人数据是：在 Stripe 内部，&lt;strong&gt;每周有超过 1300 个由小黄人完全生成的 Pull Requests（合并请求）被成功合并。这些代码在最终阶段会经过人类审查，但其中不包含任何人类编写的代码。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;更具挑战的是，Stripe 的代码库高达数亿行，主要使用较冷门的带有 Sorbet 类型的 Ruby 语言，且包含大量 LLM 根本没见过的大型内部自研库。此外，这些代码每年要处理超过 1 万亿美元的支付量，合规与容错要求极高。&lt;/p&gt;
&lt;p&gt;Stripe 是如何让 LLM 驾驭如此庞大且复杂的企业级代码库的？核心答案在于极其强大的定制化工程脚手架。&lt;/p&gt;
&lt;p&gt;以下是小黄人能高效运转的四大核心技术拆解。&lt;/p&gt;
&lt;h3 id="1-极致标准化的预热沙盒devboxes"&gt;&lt;a href="#1-%e6%9e%81%e8%87%b4%e6%a0%87%e5%87%86%e5%8c%96%e7%9a%84%e9%a2%84%e7%83%ad%e6%b2%99%e7%9b%92devboxes" class="header-anchor"&gt;&lt;/a&gt;1 极致标准化的预热沙盒（Devboxes）
&lt;/h3&gt;&lt;p&gt;要让全自动 Agent 大规模并行工作，绝不能让它们跑在开发者杂乱的本地笔记本上。Stripe 的解法是直接复用为人类工程师打造的云端开发机（Devboxes）。&lt;/p&gt;
&lt;p&gt;●&lt;strong&gt;10 秒极速“热启动”&lt;/strong&gt;：这些 Devbox 是 AWS EC2 实例。Stripe 预先配置并预热了一个资源池，里面已经克隆好了巨大的 Git 仓库，预热了 Bazel 构建缓存和类型检查缓存，甚至启动了持续运行的代码生成服务。因此，只要 10 秒钟，小黄人就能拿到一台随时可以运行测试和修改代码的机器。&lt;/p&gt;
&lt;p&gt;●&lt;strong&gt;免弹窗的完全提权&lt;/strong&gt;：为了让小黄人在后台静默运行，它需要无缝执行各种 Shell 命令。因为 Devbox 运行在与生产资源和外部互联网隔离的 QA 环境中，爆炸半径被严格限制，所以系统敢于跳过人类权限确认弹窗，给予小黄人完整的执行自由。&lt;/p&gt;
&lt;p&gt;●&lt;strong&gt;解决并发冲突&lt;/strong&gt;：如果用本地环境，并发运行多个 Agent 需要处理复杂的 git worktrees（这在 Stripe 的庞大代码库中无法扩展）。而在云端，工程师可以轻易地同时为 6 个不同的任务启动 6 个分配了独立 Devbox 的小黄人，实现物理级别的完美隔离&lt;/p&gt;
&lt;h3 id="2--蓝图编排blueprints将大模型装进确定性的盒子里"&gt;&lt;a href="#2--%e8%93%9d%e5%9b%be%e7%bc%96%e6%8e%92blueprints%e5%b0%86%e5%a4%a7%e6%a8%a1%e5%9e%8b%e8%a3%85%e8%bf%9b%e7%a1%ae%e5%ae%9a%e6%80%a7%e7%9a%84%e7%9b%92%e5%ad%90%e9%87%8c" class="header-anchor"&gt;&lt;/a&gt;2 “蓝图”编排（Blueprints）：将大模型装进确定性的盒子里
&lt;/h3&gt;&lt;p&gt;常规的 Agent 往往采用开放的循环机制，任由 LLM 自己决定下一步调什么工具，这极易导致出错和浪费 Token。 Stripe 创造性地引入了**“蓝图”（Blueprints）**状态机机制。蓝图将整个工作流视为一张图，将 LLM 的创造力与确定性的系统代码交织在一起：&lt;/p&gt;
&lt;p&gt;●&lt;strong&gt;确定性节点 vs Agent 节点&lt;/strong&gt;：在蓝图中，像“实现具体任务”或“修复 CI 失败”是让 LLM 自由发挥的 Agent 节点；但是，像“运行配置好的 Linter”或“推送 Git 变更”则是完全不调用 LLM 的纯代码确定性节点。&lt;/p&gt;
&lt;p&gt;●&lt;strong&gt;底线兜底&lt;/strong&gt;：这意味着小黄人无法绕过代码格式化等硬性规范。把大模型“关进受控的盒子里”，不仅极大地节省了 Token，还从系统层面提高了整体可靠性。各团队甚至可以编写自定义的蓝图，来处理复杂的、LLM 辅助的代码库迁移任务&lt;/p&gt;
&lt;h3 id="3-极其克制的上下文投喂规则文件与-toolshed"&gt;&lt;a href="#3-%e6%9e%81%e5%85%b6%e5%85%8b%e5%88%b6%e7%9a%84%e4%b8%8a%e4%b8%8b%e6%96%87%e6%8a%95%e5%96%82%e8%a7%84%e5%88%99%e6%96%87%e4%bb%b6%e4%b8%8e-toolshed" class="header-anchor"&gt;&lt;/a&gt;3 极其克制的上下文投喂：规则文件与 Toolshed
&lt;/h3&gt;&lt;p&gt;面对上亿行代码，如果把所有全局规则都塞给大模型，上下文窗口瞬间就会被撑爆。&lt;/p&gt;
&lt;p&gt;●&lt;strong&gt;按目录生效的局部规则&lt;/strong&gt;：Stripe 几乎只使用作用于特定子目录或文件模式的规则文件。他们巧妙地复用了人类工程师为 Cursor 编写的规则格式。这样，工程师在日常开发中沉淀的最佳实践，小黄人（以及 Claude Code）在遍历文件系统时就能直接动态读取并学习。&lt;/p&gt;
&lt;p&gt;●&lt;strong&gt;MCP 工具棚（Toolshed）&lt;/strong&gt;：小黄人通过模型上下文协议（MCP）获取网络信息（工单、文档、代码搜索等）。Stripe 建立了一个包含近 500 个内部与 SaaS 工具的中央服务器 Toolshed。但为了防止 Agent 分心，系统每次只会为小黄人精心挑选一个“小巧而高度相关”&lt;/p&gt;
&lt;h3 id="4-反馈左移shifting-feedback-left极速纠错循环"&gt;&lt;a href="#4-%e5%8f%8d%e9%a6%88%e5%b7%a6%e7%a7%bbshifting-feedback-left%e6%9e%81%e9%80%9f%e7%ba%a0%e9%94%99%e5%be%aa%e7%8e%af" class="header-anchor"&gt;&lt;/a&gt;4 反馈左移（Shifting Feedback Left）：极速纠错循环
&lt;/h3&gt;&lt;p&gt;无人值守 Agent 成功的关键在于能否实现自我闭环修正。Stripe 为其构建了多层极速反馈循环：&lt;/p&gt;
&lt;p&gt;●&lt;strong&gt;5 秒内的本地验证&lt;/strong&gt;：在小黄人把代码推送到 CI 之前，Devbox 上的后台守护进程会通过启发式算法自动运行相关的 Linter 和类型检查。这个本地节点耗时不到 5 秒，让小黄人在本地极速完成语法纠错。&lt;/p&gt;
&lt;p&gt;●&lt;strong&gt;克制的 CI 测试轮数&lt;/strong&gt;：Stripe 的 CI 拥有超过 300 万个测试用例。推送到 CI 后，系统会运行相关测试，并自动应用已有的修复脚本（Autofixes）。如果还有未修复的错误，报错会发回给小黄人。但为了平衡算力成本、时间与边际收益，小黄人最多只被允许进行 1 到 2 次的 CI 循环试错。之后无论成败，都会将其移交给人类处理，防止其陷入昂贵的死循环&lt;/p&gt;
&lt;h2 id="给我的启示"&gt;&lt;a href="#%e7%bb%99%e6%88%91%e7%9a%84%e5%90%af%e7%a4%ba" class="header-anchor"&gt;&lt;/a&gt;给我的启示
&lt;/h2&gt;&lt;p&gt;基于 Stripe 公开的这些技术细节，我得出了以下几点关于 AI 研发提效的深刻感悟：&lt;/p&gt;
&lt;p&gt;1.“&lt;strong&gt;对人类工程师有益的基础设施，对 LLM 同样有益&lt;/strong&gt;” 这是 Stripe 整个小黄人项目最核心的哲学。Stripe 并没有为了做 AI Agent 去凭空造一套新基建，而是直接将 AI 接入了他们多年打磨的 Devbox 环境、Pre-push hooks 和自动化测试管线中。这给所有企业的启示是：AI Agent 的天花板，取决于你现有工程基础设施的底座。 如果你的人类工程师本地环境经常崩溃、缺乏单测覆盖率、文档陈旧，那么大模型也一样会在这些泥坑里寸步难行。过去在人类开发者体验（Developer Productivity）上的每一分投资，都会在 AI 时代转化为巨大的复利回报。&lt;/p&gt;
&lt;p&gt;2.&lt;strong&gt;放弃追求纯粹的“全能 Agent”&lt;/strong&gt;，用“蓝图”管控不确定性 目前业界过度迷恋让一个 Agent 自主解决所有问题。但 Stripe 的蓝图（Blueprints）设计极其务实：能用一行 Bash 脚本或 Linter 稳定解决的问题（如代码格式化、Git 提交流程），就绝对不让 LLM 消耗 Token 去“推理”。在企业级生产环境中，**混合架构（确定性代码逻辑 + 局部受控的 LLM 节点）**才是保证系统高可靠性（SLA）的唯一出路。&lt;/p&gt;
&lt;p&gt;3.&lt;strong&gt;工程师的日常工作流正在被重塑&lt;/strong&gt; ，在 Stripe，触发小黄人的方式极度符合人体工程学：工程师可以直接在 Slack 的讨论线程里@小黄人，或者在内部的“CI 间歇性失败（Flaky test）”工单中点击一个按钮启动它。我们可以预见，未来的高级工程师将越来越像一个“包工头”：他们在值班（On-call）时并行启动几十个小黄人去处理琐碎的 Bug，自己则专注于审查 PR、设计架构，以及维护和编写能够指导小黄人的局部规则（Cursor rules）。工程师不再逐行敲击代码，而是定义意图并管理基础设施。&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2026-02-24-quan-cheng-0-ren-gong-xie-dai-ma-gan-diao-di-ji-ma-nong-de-b/001-a01d1a80.png"&gt;&lt;/p&gt;
&lt;h2 id="参考"&gt;&lt;a href="#%e5%8f%82%e8%80%83" class="header-anchor"&gt;&lt;/a&gt;参考
&lt;/h2&gt;&lt;p&gt;●https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents&lt;/p&gt;
&lt;p&gt;●https://stripe.dev/blog/minions-stripes-one-shot-end-to-end-coding-agents-part-2&lt;/p&gt;</description></item><item><title>Gemini 3 介绍</title><link>https://xiaobox.github.io/p/2025-11-19-gemini-3-jie-shao/</link><pubDate>Wed, 19 Nov 2025 13:22:13 +0000</pubDate><guid>https://xiaobox.github.io/p/2025-11-19-gemini-3-jie-shao/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-11-19-gemini-3-jie-shao/cover.jpg" alt="Featured image of post Gemini 3 介绍" /&gt;
 &lt;blockquote&gt;
 &lt;p&gt;Gemini应用每月用户超过6.5亿，超过70%的云服务客户在使用我们的人工智能，1300万开发者基于我们的生成式模型进行了开发，而这仅仅是我们所看到的影响的一小部分。 &amp;ndash; Google CEO Sundar Pichai&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;每一代 Gemini 都在以往的基础上不断发展，让你能够做更多事情。&lt;/p&gt;
&lt;p&gt;●Gemini 1 在原生多模态和长上下文窗口方面的突破，拓展了可处理信息的种类以及数量。&lt;/p&gt;
&lt;p&gt;●Gemini 2为智能体能力奠定了基础，并在推理与思考方面突破了前沿，助力完成更复杂的任务和构想，使 Gemini 2.5 Pro在LMArena上占据榜首超过六个月。&lt;/p&gt;
&lt;p&gt;今天 ，Google 终于憋出了大招，正式发布了 Gemini 3 系列。Google 这次明显是想通过 “Agentic（代理化）” 和 “Generative UI（生成式 UI）” 这两张牌，彻底改变我们开发和使用 AI 的方式。&lt;/p&gt;
&lt;h2 id="一核心模型不再只是-陪聊而是-干活-的"&gt;&lt;a href="#%e4%b8%80%e6%a0%b8%e5%bf%83%e6%a8%a1%e5%9e%8b%e4%b8%8d%e5%86%8d%e5%8f%aa%e6%98%af-%e9%99%aa%e8%81%8a%e8%80%8c%e6%98%af-%e5%b9%b2%e6%b4%bb-%e7%9a%84" class="header-anchor"&gt;&lt;/a&gt;一、核心模型：不再只是 “陪聊”，而是 “干活” 的
&lt;/h2&gt;&lt;p&gt;这次发布的重头戏有两个模型版本：&lt;/p&gt;
&lt;p&gt;1.Gemini 3 Pro&lt;/p&gt;
&lt;p&gt;○定位： 这是新的主力模型，Google 称之为 “最智能的模型”。&lt;/p&gt;
&lt;p&gt;○最大亮点 ——“Vibe Coding”：你不需要写精确的 prompt 或者伪代码，只需要用自然语言描述你想要的 “感觉（vibe）” 或功能，它就能生成全栈应用。比如 “做一个复古风格的太空射击游戏，障碍物要随着合成波音乐跳动”，它能直接给你生成带 UI 和交互的成品。&lt;/p&gt;
&lt;p&gt;○能力提升： 推理能力大幅增强，官方数据说在 LMArena 上 Elo 分数飙到了 1501（目前榜首）。&lt;/p&gt;
&lt;p&gt;○适用场景： 日常高频任务、代码生成、多模态理解（视频/图像/音频）。&lt;/p&gt;
&lt;p&gt;2.Gemini 3 Deep Think&lt;/p&gt;
&lt;p&gt;○定位： 专门用来 “死磕” 难题的推理模型，仅面向 Google AI Ultra 订阅用户。&lt;/p&gt;
&lt;p&gt;○对标对象： 显然是 OpenAI 的 o1 / o3 系列。&lt;/p&gt;
&lt;p&gt;○恐怖的数据： 在 Humanity&amp;rsquo;s Last Exam（人类终极考试）这个测试集上，Gemini 3 Pro 得分 37.5%，而 Deep Think 版本能干到 41.0%（作为对比，上一代 Gemini 2.5 Pro 只有 21.6%）。这意味着在数学、科学研究等需要深度思考的领域，它的可靠性会有质的飞跃。&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-11-19-gemini-3-jie-shao/001-7f39bd1d.png"&gt;&lt;/p&gt;
&lt;h2 id="二-ai-ide-google-antigravity-反重力"&gt;&lt;a href="#%e4%ba%8c-ai-ide-google-antigravity-%e5%8f%8d%e9%87%8d%e5%8a%9b" class="header-anchor"&gt;&lt;/a&gt;二、 AI IDE ：Google Antigravity (反重力)
&lt;/h2&gt;&lt;p&gt;Google 推出了一个全新的 Agentic IDE，叫 Google Antigravity&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-11-19-gemini-3-jie-shao/002-ad66a4c5.png"&gt;&lt;/p&gt;
&lt;p&gt;●这是什么？ 别把它想成 VS Code 的插件。这是一个独立的 IDE，专门为 “AI 代理开发” 设计的。&lt;/p&gt;
&lt;p&gt;●核心逻辑变了： 以前我们是用 Copilot 写代码（AI 辅助你），现在你是 “架构师”，你定义任务，Antigravity 里的 Agents（代理）去执行。&lt;/p&gt;
&lt;p&gt;●它能干嘛？&lt;/p&gt;
&lt;p&gt;○全自主干活： 代理可以在编辑器写代码、在终端跑命令、在浏览器里预览调试，三者打通。&lt;/p&gt;
&lt;p&gt;○Artifacts（产物）： 代理不仅仅是吐代码，还会生成任务清单、实施计划、甚至截图，让你像验收工作一样去 Check 它的产出。&lt;/p&gt;
&lt;p&gt;○模型任选： 这一点很良心，除了 Gemini 3，它居然支持 Anthropic 的 Claude Sonnet 4.5 和 OpenAI 的 GPT - OSS。Google 这次格局打开了，意思是 “用最好的工具解决问题”。&lt;/p&gt;
&lt;p&gt;这玩意儿就是冲着 Cursor 来的，而且试图在 “自主性” 上更进一步。建议大家赶紧去下个 Preview 版试试，特别是 Mac/Windows/Linux 都支持。&lt;/p&gt;
&lt;h2 id="三-用户体验革命generative-ui-生成式-ui"&gt;&lt;a href="#%e4%b8%89-%e7%94%a8%e6%88%b7%e4%bd%93%e9%aa%8c%e9%9d%a9%e5%91%bdgenerative-ui-%e7%94%9f%e6%88%90%e5%bc%8f-ui" class="header-anchor"&gt;&lt;/a&gt;三、 用户体验革命：Generative UI (生成式 UI)
&lt;/h2&gt;&lt;p&gt;Google 认为：“最好的 UI 是不需要设计的，是生成的。”&lt;/p&gt;
&lt;p&gt;Google 认为，AI 的回答不应该只是一堆文字。Gemini 3 引入了 Generative UI（生成式用户界面）&lt;/p&gt;
&lt;p&gt;●动态生成组件： 当用户问 “帮我规划去罗马的旅行” 时，它不再只是列个文字清单，而是可能会直接生成一个 “交互式的行程卡片”，或者当你问房贷时，直接生成一个 “房贷计算器组件”。&lt;/p&gt;
&lt;p&gt;●底层技术： 依靠 Gemini 3 强大的代码生成能力，即时生成前端代码并在客户端渲染。&lt;/p&gt;
&lt;p&gt;●Dynamic View： 在 Gemini App 里，这被称为 “Dynamic View”。它能根据你的意图，现场 “手搓” 一个最适合当前场景的 UI 界面给你。&lt;/p&gt;
&lt;p&gt;未来的 AI 应用，界面可能不再是写死的，而是 “流式生成” 的。&lt;/p&gt;
&lt;h2 id="四-实战与性能-benchmarks"&gt;&lt;a href="#%e5%9b%9b-%e5%ae%9e%e6%88%98%e4%b8%8e%e6%80%a7%e8%83%bd-benchmarks" class="header-anchor"&gt;&lt;/a&gt;四、 实战与性能 (Benchmarks)
&lt;/h2&gt;&lt;p&gt;如果不看跑分就不是科技圈了。简单列几个吓人的数据：&lt;/p&gt;
&lt;p&gt;●LMArena Elo: 1501 (目前世界第一)。&lt;/p&gt;
&lt;p&gt;●MathArena Apex: 23.4% (这是个新出的超难数学竞赛基准，其他模型基本是个位数，Claude 4.5 是 1.6%，GPT-5.1 是 1.0%&amp;hellip; Gemini 3 这个分数有点断层领先的意思)。&lt;/p&gt;
&lt;p&gt;●SWE-bench Verified (代码能力): 76.2%。虽然比 Claude 的 77.2% 略低一点点，但在 Antigravity 环境下的综合表现（Agentic coding）可能会更强。&lt;/p&gt;
&lt;p&gt;●多模态: 视频理解 (Video-MMMU) 达到了 87.6%，以后扔给它一段长视频让它总结或者找细节，应该会非常准。&lt;/p&gt;
&lt;p&gt;&lt;img alt="图片" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-11-19-gemini-3-jie-shao/003-49ccf38a.png"&gt;&lt;/p&gt;
&lt;h2 id="五-生态整合这才是-google-恐怖的地方"&gt;&lt;a href="#%e4%ba%94-%e7%94%9f%e6%80%81%e6%95%b4%e5%90%88%e8%bf%99%e6%89%8d%e6%98%af-google-%e6%81%90%e6%80%96%e7%9a%84%e5%9c%b0%e6%96%b9" class="header-anchor"&gt;&lt;/a&gt;五、 生态整合（这才是 Google 恐怖的地方）
&lt;/h2&gt;&lt;p&gt;Google 把 Gemini 3 塞进了所有角落：&lt;/p&gt;
&lt;p&gt;●Search: 搜索里加了 “AI Mode”，而且支持 “Thinking” 开关。以后搜复杂问题（比如做攻略、查论文），搜索体验会完全不同。&lt;/p&gt;
&lt;p&gt;●Android Studio: 安卓开发的同事注意了，Gemini 3 已经进驻，不仅是补全代码，还能帮你写 UI、查 Bug。&lt;/p&gt;
&lt;p&gt;●Gemini CLI: 对于运维和后端同事，新的 CLI 允许你在终端里直接用自然语言让 Gemini 3 帮你执行复杂的 Shell 命令组合，甚至排查云端服务的 Log。&lt;/p&gt;
&lt;p&gt;●Firebase: 推出了 &amp;ldquo;Firebase AI Logic&amp;rdquo;，后端逻辑也能由 AI 驱动了。&lt;/p&gt;
&lt;h2 id="六-总结与建议"&gt;&lt;a href="#%e5%85%ad-%e6%80%bb%e7%bb%93%e4%b8%8e%e5%bb%ba%e8%ae%ae" class="header-anchor"&gt;&lt;/a&gt;六、 总结与建议
&lt;/h2&gt;&lt;p&gt;Gemini 3 无疑是一次 “能力的平权”&lt;/p&gt;
&lt;p&gt;Gemini 3 不仅仅是 “更快更强”，它在尝试定义 AI 的下一阶段：&lt;/p&gt;
&lt;p&gt;1.从 Chat 到 Agent: 不再是 “一问一答”，而是 “通过代理解决多步骤复杂任务”。&lt;/p&gt;
&lt;p&gt;2.从 Text 到 UI: 输出形式从文本扩展到了动态界面。&lt;/p&gt;
&lt;p&gt;给产研内部的建议：&lt;/p&gt;
&lt;p&gt;●开发同学： 务必尝试 Google Antigravity 和 Gemini CLI。如果它真能像宣传那样自主改 Bug、重构代码，我们的开发效率可能会有质变。&lt;/p&gt;
&lt;p&gt;●产品同学： 关注 Generative UI 的交互模式。我们的 AI 产品是否也可以不仅仅吐文字，而是根据用户需求动态生成交互组件？&lt;/p&gt;
&lt;p&gt;●模型同学： 重点关注 Deep Think 的推理模式，看看 Google 是如何通过增加推理时间（Test-time compute）来换取高质量输出的。&lt;/p&gt;
&lt;p&gt;目前 Gemini 3 Pro 已经在 Gemini App 和 AI Studio 里能用了，Deep Think 还要等几周。大家可以先去玩玩 Pro 版的 “Vibe Coding”&lt;/p&gt;</description></item><item><title>AI 编程工具使用有感</title><link>https://xiaobox.github.io/p/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/</link><pubDate>Thu, 26 Jun 2025 08:14:34 +0000</pubDate><guid>https://xiaobox.github.io/p/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/cover.jpg" alt="Featured image of post AI 编程工具使用有感" /&gt;&lt;p&gt;经过近 2 年的 AI 编程工具使用，积累了一些使用这些工具的经验和心得，本文分享出来。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;以下分类纯属个人偏好，无任何客观依据，全是主观感受，仅供参考。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="第一梯队"&gt;&lt;a href="#%e7%ac%ac%e4%b8%80%e6%a2%af%e9%98%9f" class="header-anchor"&gt;&lt;/a&gt;第一梯队
&lt;/h2&gt;&lt;p&gt;Claude Code、Cursor、Augement Code、Windsurf、Warp、Gemini CLI&lt;/p&gt;
&lt;p&gt;第一梯队里的大家应该很熟悉，都是明星产品，我从接触时间的早晚来分别聊一下。&lt;/p&gt;
&lt;h3 id="cursor"&gt;&lt;a href="#cursor" class="header-anchor"&gt;&lt;/a&gt;Cursor
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/001-3275ab0e.png"&gt;&lt;/p&gt;
&lt;p&gt;Cursor 算是老朋友了，一直用。早期最令我兴奋的一款 AI 编程工具产品，还专门写了篇文章尬吹了一下 &lt;a class="link" href="https://mp.weixin.qq.com/s?__biz=MzI3Njk5ODg4OQ==&amp;amp;mid=2247489537&amp;amp;idx=1&amp;amp;sn=147a299e76c9d6147caf097c21fb1bb5&amp;amp;scene=21#wechat_redirect" target="_blank" rel="noopener"
 &gt;Cursor 一个真正让程序员产生危机感的 AI 编程工具&lt;/a&gt;，今天翻回去看，我觉得吹的内容用 Cursor 基本上都能实现了。&lt;/p&gt;
&lt;p&gt;经过多次版本更新，Cursor 的使用模式稳定在了三种&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/002-e6346e53.png"&gt;&lt;/p&gt;
&lt;p&gt;用过 AI 工具的都知道，工具最核心的是底层使用的模型是什么，模型强不强决定着 “输出” 质量高不高，也就决定着代码质量。这跟开发者的切身利益可是息息相关的。&lt;/p&gt;
&lt;p&gt;很多开发者使用 Cursor 最看重的就是它能够使用 Claude 家最能打的模型，过去是 &lt;code&gt;claude-3.5-sonnet&lt;/code&gt; 现在是 &lt;code&gt;claude-4-sonnet&lt;/code&gt; 以及 &lt;code&gt;claude-4-opus&lt;/code&gt; 。这对于非专业开发者可能不够熟悉，大众更熟悉的是 OpenAI 的 4o 或者 Google 家的 gemini，无可厚非，但毋庸置疑的是，上面提到的 claude 家的一系列模型在编程领域就是最强的。不然广大开发者也不会心甘情愿的 “氪金” 来使用它。&lt;/p&gt;
&lt;p&gt;越好的模型越受欢迎，自然价格也水涨船高，像下面这两个模型我一般不开，因为（&lt;code&gt;Max Only&lt;/code&gt;）除了订阅费以外还要单独收费&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/003-0e9b339f.png"&gt;&lt;/p&gt;
&lt;p&gt;除了常规的操作，Cursor 自然也加入了 MCP，你可以添加自定义的 MCP&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/004-b2b95477.png"&gt;&lt;/p&gt;
&lt;p&gt;不过从我的日常开发使用经验上来说，MCP 用的时候不多。Cursor 自己其实也内置了一些 MCP，比如它会读你的本地文件，新建终端并执行 CLI 命令，联网搜索相关问题的答案等。&lt;/p&gt;
&lt;p&gt;Cursor 一直在更新，无论是添加新功能，还是改善老功能，总之肉眼可见的在进步，然而还是有一些常见的使用问题，它比较影响用户体验。比如使用 &lt;code&gt;claude-4-sonnet&lt;/code&gt; 时经常报网络异常，你跟他开始聊得好好的，有时候就突然开始没法响应了，报网络异常，有些时候是真着急，因为你代码改到一半，消耗了那么多 token 和时间，总不能半途而费吧，可 Cursor 就是不给你响应了，等待让人焦虑和上火。这时候我通常会切换到其他模式试一试比如 gemini。gemini 的最大优点是上下文够长，1M 上下文一般一个 Thread 怎么也够用了。但这也有风险，就像你同时请教 2 位专家，一位说着说着停了，你转头问另一位，但关键是 2 个人就有可能有 2 种不同的思路，这时候修改代码就需要你特别注意了，不要被误导，因为第 2 位可能会完全颠覆第 1 位提供的代码和思路。（我已被坑过了 🤣）&lt;/p&gt;
&lt;p&gt;其实这个问题我要反思，用 AI 工具开发，不能真的是 &lt;code&gt;Vibe Coding&lt;/code&gt; ，因为我们是专业的开发人员，不能偷懒，否则容易被坑，得不偿失。**AI 要像 “领航员”、“辅助驾驶” 、“僚机” ，不能够完全托管给它，因为专业的开发人员写的代码不是玩具，也不是仅仅能 run 起来就行的代码。**我们要考虑的问题很多，比如架构、扩展性、易读性、性能、成本、团队情况等等。&lt;/p&gt;
&lt;p&gt;如果你抱着谨慎的态度使用 AI编程工具和你一起编程的话，可能使用最多的就是 &lt;code&gt;TAB&lt;/code&gt; ，而这一点 Cursor 做的相当不错，可以说是很神，你改了一个函数、参数、方法声明、方法实现，按一下 &lt;code&gt;TAB&lt;/code&gt; 它是真的很智能，基本上能够想到你应该做的下一步是什么，这些本来就需要做的，比如重构，让它完美的提醒，这正是使用 Cursor 这种 AI 编程工具的一个非常适合的场景。&lt;/p&gt;
&lt;p&gt;而如果你抱着宽容、放纵的态度使用 Cursor 的话就一定会被 AI 的另一面所伤，比如 “幻觉”，当然伤势情况根据你的宽容程度而定。如果你放手让他大刀阔斧的进行重构，它有可能删除很多文件，新建很多文件，理想的情况是完美的完成了，但前提也一定是你给他做好了任务规划，让他一次只完成一个具体的任务，而不是给一个比较笼统的要求，比如重构一下整个项目。&lt;/p&gt;
&lt;p&gt;还有一种情况是上下文，虽然像 gemini 这种模型具有长上下文，我们在使用时也要脑子里有这根弦，上下文不能够太长，太长很容易产生幻觉和出一些意想不到的问题。我记得有一次我为了解决一个 bug，一直在一个 Thread 中和模型交互，模型最后的解决方案是把我 bug 相关的代码全部删除了，然后跟我说：“我保证，这次一定不会有问题了” 。 🤣 我是真谢了～&lt;/p&gt;
&lt;p&gt;所以，&lt;strong&gt;我们到底应该抱以什么态度来使用 AI 编程工具是个关键的问题&lt;/strong&gt;，我认为要具体问题具体分析，如果你真的是为了写一个 demo 而写代码，真的是能 run 就行，那可以采取比较激进和宽容的态度，放手让AI 实现，没什么大不了的，大不了就从头重写。而如果你写的是公司的生产级代码，那么就需要秉承谨慎和适当放手的策略，全程监督，让 AI 辅助你，而不是完全让他 “自动驾驶”。AI 越来越强大，但人类需要学习，哪怕慢一点，边学边干。否则&amp;hellip;&amp;hellip;(此处省略一万字)&lt;/p&gt;
&lt;h3 id="windsurf"&gt;&lt;a href="#windsurf" class="header-anchor"&gt;&lt;/a&gt;Windsurf
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/005-dd8ac6e8.png"&gt;&lt;/p&gt;
&lt;p&gt;在早期，相对 Cursor,我更喜欢 Windsurf ，原因很简单，同样的底层模型，不知道为什么， Windsurf 给的结果就是更好，更实用、可用。其实发展到现在，从功能上讲它跟 Cursor 差别不大，但从我的使用习惯、使用频率、模型以及软件背后母公司的发展等多方面考虑，最后我还是选择使用 Cursor 作为主力 AI 编程工具。&lt;/p&gt;
&lt;p&gt;后来的事情大家都知道了：“OpenAI 同意以 30 亿美元的价格收购Windsurf”&lt;/p&gt;
&lt;p&gt;后来，Winsurf 发布了自己的新模型 : &lt;code&gt;SWE-1&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/006-81675349.png"&gt;&lt;/p&gt;
&lt;p&gt;总之，Winsurf 并不是牛夫人，它是一个很不错的软件，我也表示对它的敬畏和欣赏，Cursor 有的问题它也有，Cursor 没有的问题它不见得没有，只不过是我使用的时间没有那么长而已。&lt;/p&gt;
&lt;p&gt;也许有一天我还会转回使用 Winsurf，但现实就是如此，我不可能同时开那么多订阅，花那么多钱在多个 AI 编程工具上面。&lt;/p&gt;
&lt;h3 id="claude-code"&gt;&lt;a href="#claude-code" class="header-anchor"&gt;&lt;/a&gt;Claude Code
&lt;/h3&gt;&lt;p&gt;就在 Cursor 等一众 AI Coding 工具大杀四方的时候 Claude 低调地推出了它的 Claude Code。起初非常不起眼，我甚至都没有兴趣试用一下，直到后来有很多人用过了以后开始对它大为赞赏这才引起了我的兴趣。&lt;/p&gt;
&lt;p&gt;作为一个看起来有点儿 “土” 的工具，它是以终端命令的形式提供给用户使用的，不是 IDE，没有 GUI 界面，只有命令，你需要在终端使用命令和它交互。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/007-dc598b7e.png"&gt;&lt;/p&gt;
&lt;p&gt;其实作为专业的开发人员，对命令行并不陌生，反而会更亲近，这种形式没有 GUI，效率更高，但相对的是它对非专业人士没有 Cursor、VsCode 这种 IDE 友好。对于我来说，这无所谓，我都能接受。因为我看的是效果、结果、代码质量。&lt;/p&gt;
&lt;p&gt;但另一个问题就是个很大的门槛了，那就是 Claude 的账户问题。无论使用 Cursor 还是 Claude Code，花钱订阅几乎是必须的，但 Claude 家对账号的管控几近变态，动不动账户就被 ban 了，你需要拥有一个 “稳定” 的干净的账号，然后再订阅，其实说难也不难，但处理起来比较麻烦。当然也有使用 API 中转的方式，相对方便，这里就不多说了。&lt;/p&gt;
&lt;p&gt;我在深度体验了 2 天 Claude Code 后明白了为什么大家都想订阅 200每月的服务，因为真的很烧，而 的是不限 token 的，而且服务比较稳定，不像 Cursor 时不时的会抽风。&lt;/p&gt;
&lt;p&gt;我大致同意网上对 Claude Code 的赞美，但我也发现了很多无脑吹的，这和我的体验大相径庭，所以我得谈谈我的感受：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先，Claude Code 内置了许多 tools，而且它对任务的规划比较完整，再加上 tools，这样它对任务的执行就比较完整，注意不是完美，而是完整，不会让一个任务 “缺胳膊少腿”，总是差一点儿。&lt;/li&gt;
&lt;li&gt;完整的任务规划的代价就是烧 token，会用很多的 token，如果你没有订阅 200$ 这种无限量的，那可得省着点儿用。&lt;/li&gt;
&lt;li&gt;会思考，根据上下文、程序的输出日志、执行情况推断任务是否完成，是否重新执行，是否修改任务重新执行等，你看这么多事儿都是要调用模型的，也是要烧 token 的。&lt;/li&gt;
&lt;li&gt;无论哪个模型，仍然有幻觉，我们在其他 AI 工具上的经验可以移植，要谨慎，不要轻信它。&lt;/li&gt;
&lt;li&gt;它看起来很强大，可以思考、规划并自动完成你指定的任务，即便是比较大的任务。但很慢，对于你自己能干的事情，没必要浪费时间让它干，之前我偷懒让它完善一下 .gitignore 文件，它花了好久才搞定，虽然正确，但我干只需要一秒。而它要动用很多工具进行全面的分析，最后得出一个只改一行代码的结论。有点儿坑。&lt;/li&gt;
&lt;li&gt;对代码的管理依赖 git，虽然无可厚非，但确实不如 Cursor方便，鼠标点一下就能 restore 了。这一点儿我不争论，它是个习惯问题，就像不习惯使用 Vim 的虽然也承认 Vim 高效，但就是用不惯。&lt;/li&gt;
&lt;li&gt;用最强的模型+最优的 prompt 自然能产生最好的结果，这是人和 AI 配合的结果，我不知道这个优点能不能算到 Claude Code 的头上，因为对于其他 AI Coding 工具也适用。我不知道能不能算的原因还有一个是我不并知道 Cursor 内部做了什么，Claude Code 内部又做了什么，我只能拿结果比较，事实是 Claude Code 略胜一筹，但却让我有一丝担忧，因为它总让我有种快控制不了它的感觉。（指不定什么时候要出事儿）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;起初，我对 Claude Code 的热情是高涨的，但由于我的 2 个账号接连被 ban，订阅费又这么贵，合计了一下，还是回到 Cursor 了。这并不是说 Claude Code 不好，只不过对于我，这是个选择问题。&lt;/p&gt;
&lt;p&gt;哦，对了，因为不是 IDE，纯命令行，所以如果你用习惯了 &lt;code&gt;TAB&lt;/code&gt; ，Claude Code 可没有哈。使用模式要变。&lt;/p&gt;
&lt;h3 id="gemini-cli"&gt;&lt;a href="#gemini-cli" class="header-anchor"&gt;&lt;/a&gt;Gemini CLI
&lt;/h3&gt;&lt;p&gt;Google 是一家任谁都不能忽视的公司，在科技领域遍布它的身影，自然 AI Coding 工具也少不了它的参与。&lt;/p&gt;
&lt;p&gt;在我写这篇文章的上午 ，google 参战了，发布了 Gemini CLI，它是开源免费的，你用 Google 账号登录，就能直接使用，每天有 1000 次限额，够用了。&lt;/p&gt;
&lt;p&gt;它的产品逻辑和 Claude Code 一样，你可以把它理解成 Claude Code 的平替。它是开源的。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/008-cd7e2036.png"&gt;&lt;/p&gt;
&lt;p&gt;从功能、使用体验、效果等各个角度来看，目前它确实都不如 Claude Code，但它的优势也是那么的明显：免费、gemini 2.5模型上下文够长，量大管饱、大厂背书可信赖、将来生态集成可期&amp;hellip;&lt;/p&gt;
&lt;p&gt;从我在 Cursor 使用 gemini 模型的经验来看，我对 Google , 不，应该是 gemini 是有强烈好感的，它确实强：比我命还长的上下文（1M），强大的推理和思考能力，不输给任何模型的基础能力，你还要什么自行车呢？ 用就好了。&lt;/p&gt;
&lt;h3 id="warp"&gt;&lt;a href="#warp" class="header-anchor"&gt;&lt;/a&gt;Warp
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/009-45d184dd.png"&gt;&lt;/p&gt;
&lt;p&gt;Warp 最初是作为终端工具进入我的视野的，那时候 我还在用 iTerm2，使用了 Warp 以后，被它良好的用户体验和优美的外观吸引。随着它不同的更新，在最近两年加入了 AI 功能，成为了一个智能开发工具。&lt;/p&gt;
&lt;p&gt;由于我经常进行服务器的运维工作，终端工具的使用是非常频繁的，小到执行些基础的运维命令，大到写 shell 、python 脚本 ，进行服务器初始化 、k8s 部署。&lt;/p&gt;
&lt;p&gt;其实相比 Claude Code，Warp 新加的 AI 功能带给我的体验是很自然的。因为在命令行环境，我的输入、输出 Warp 都能捕获到，进而给我具体的建议和任务规划，当然，它背后仍然是大模型提供的能力。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/010-6a447362.png"&gt;&lt;/p&gt;
&lt;p&gt;在我敲命令时，它可以给我提示，当命令执行完成输出结果时，它可以指导我进行下一步的操作，如果我忘了命令该怎么写，它也可以完成命令，或者给出最合理的建议并自动执行。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/011-cc229cf4.png"&gt;&lt;/p&gt;
&lt;p&gt;当然用模型是要花钱的，它有一些免费的额度，用完了就需要付费了。最近它又加入了 “上下文” ，可以将你的代码工程作为上下文给他分析，同时与你一起结伴编程。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/012-a2741553.png"&gt;&lt;/p&gt;
&lt;p&gt;这就和 Claude Code 以及 Gemini CLI 很像了。不过说实话我用 Warp 还是基于命令行的场景，编程一般不会它，还是会用 Cursor。但它确实越来越强了，值得大家的关注。&lt;/p&gt;
&lt;h3 id="augement-code"&gt;&lt;a href="#augement-code" class="header-anchor"&gt;&lt;/a&gt;Augement Code
&lt;/h3&gt;&lt;p&gt;无论 Cursor、Claude Code 如何优秀，都有令人不满的地方，在体验了这么多产品以后，我发现 Augement Code 比较适合我，虽然我还没有正式订阅，但它是目前为止我最满意的。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/013-dc623d29.png"&gt;&lt;/p&gt;
&lt;p&gt;Augement Code 目前并不是一个独立的 IDE，而是一个插件，它可以在 IDEA 以及 VsCode 中安装使用。&lt;/p&gt;
&lt;p&gt;作为一个 AI Coding 工具，它的基础功能和 Cursor 差不多，就不赘述了。下面说说它的特点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;你无法选用模型，从现在知道的资料来看，貌似使用的是 &lt;code&gt;claude-3.5-sonnet&lt;/code&gt; ，但我觉得可能不是，很有可能是更强的。虽然无法选模型，但输出质量很高。不比 Cursor 这种可选模型的差。所以如果是 &lt;code&gt;claude-3.5-sonnet&lt;/code&gt; 就能输出这样的质量，那 Augement Code 团队可真了不起。&lt;/li&gt;
&lt;li&gt;会自动进行一系列的任务规划，并清楚地展示给你看具体细节&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/014-a6e2fe09.png"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;任务规划完成后，对于没有把握或你没有确认过的事项，更倾向于新建文件，而不是修改你原本的代码。这一点我很受用，因为 Cursor 经常在多轮对话中把代码越改越滥，最后迫不得已我点了 &lt;code&gt;restore checkpoint&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;稳定，很少出网络异常&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;不便宜，需要订阅，比 Cursor 贵，每月 50$ 起，而且是限量的（600 次消息）&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;目前我还在试用中，从现在的使用情况看，后面很有可能会换用 Augement Code，退订 Cursor。不过我还是有点儿犹豫，因为 Cursor 不限量啊。&lt;/p&gt;
&lt;h2 id="第二梯队"&gt;&lt;a href="#%e7%ac%ac%e4%ba%8c%e6%a2%af%e9%98%9f" class="header-anchor"&gt;&lt;/a&gt;第二梯队
&lt;/h2&gt;&lt;p&gt;Cline、Copilot、firebase&lt;/p&gt;
&lt;p&gt;从第二梯队开始，我在开发上的使用频率依次递减。&lt;/p&gt;
&lt;h3 id="copilot"&gt;&lt;a href="#copilot" class="header-anchor"&gt;&lt;/a&gt;Copilot
&lt;/h3&gt;&lt;p&gt;应该是也不用过多介绍了。Github ，哦不，微软家的。&lt;/p&gt;
&lt;p&gt;最开始是大哥，现在是小老弟了。平时几乎不用，偶尔测试一下，看看效果如何，一点儿都不能打，整体跟 Cursor 那一众差远了，&lt;/p&gt;
&lt;h3 id="firebase"&gt;&lt;a href="#firebase" class="header-anchor"&gt;&lt;/a&gt;firebase
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/015-19bdb3f6.png"&gt;&lt;/p&gt;
&lt;p&gt;也是 Google 家的，但总感觉不是给开发者用的，全 web 的形式，不是 IDE，用起来别扭，但因为模型强大，所以做个前端 demo 什么的效果还不错，真正在开发场景我是不会用的。&lt;/p&gt;
&lt;h3 id="cline"&gt;&lt;a href="#cline" class="header-anchor"&gt;&lt;/a&gt;Cline
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/016-bc045784.png"&gt;&lt;/p&gt;
&lt;p&gt;开源免费，插件形式提供。自己通过 API 配置模型，整体效果还可以，但维护的心智成本略高，用了几天弃了，用回了 Cursor。不是人家不好啊，是我的选择问题。用的人还是很多的，整体成本小，对于用量不大的，你随便买个 API 一接就能使了。&lt;/p&gt;
&lt;h2 id="第三梯队"&gt;&lt;a href="#%e7%ac%ac%e4%b8%89%e6%a2%af%e9%98%9f" class="header-anchor"&gt;&lt;/a&gt;第三梯队
&lt;/h2&gt;&lt;p&gt;Trae、通义灵码、MarsCode、微信开发者工具&lt;/p&gt;
&lt;h3 id="trae"&gt;&lt;a href="#trae" class="header-anchor"&gt;&lt;/a&gt;Trae
&lt;/h3&gt;&lt;p&gt;字节家的，对于它，早期乃至现在的噱头都很足，可以在国内网络环境下使用世界最先进的模型。但是，我真的不知道是什么原因，同样的模型，比如 &lt;code&gt;claude-4-sonnet&lt;/code&gt; Cursor 整体返回的结果就是 OK 的，Trae 就是稀烂。所以我怀疑不是模型的事儿，但又不知道是哪里出的问题。经过多次的尝试效果始终赶不上 Cursor，甚至还不如 Cline,弃了，以后也不会用。&lt;/p&gt;
&lt;p&gt;不过它有它的市场，好好做也许会越来越好吧，谁知道呢？&lt;/p&gt;
&lt;h3 id="通义灵码marscode微信开发者工具"&gt;&lt;a href="#%e9%80%9a%e4%b9%89%e7%81%b5%e7%a0%81marscode%e5%be%ae%e4%bf%a1%e5%bc%80%e5%8f%91%e8%80%85%e5%b7%a5%e5%85%b7" class="header-anchor"&gt;&lt;/a&gt;通义灵码、MarsCode、微信开发者工具
&lt;/h3&gt;&lt;p&gt;这些产品我都是早期的使用者，开始都很惊艳，因为早期我也是土包子没见过世面，觉得 AI 工具真的很强大。后来被其他工具征服了以后对这些就没兴趣了。嗯，这些是真正的 “牛夫人”。 如果有条件也不推荐别人用了，有更完善和强大的工具，没必要没苦硬吃。&lt;/p&gt;
&lt;h2 id="第四梯队"&gt;&lt;a href="#%e7%ac%ac%e5%9b%9b%e6%a2%af%e9%98%9f" class="header-anchor"&gt;&lt;/a&gt;第四梯队
&lt;/h2&gt;&lt;p&gt;DeepSeek、ChatGPT、Claude、通义千问、腾讯元宝等&lt;/p&gt;
&lt;p&gt;理论上这些在我这里都不算 AI 编程工具，它们全部都是 文生文的产品。只不过内置了可以预览和编程的工具。但那都仅仅限于代码片段。写个玩具还行，写项目？别闹。&lt;/p&gt;
&lt;p&gt;最近 OpenAI 贼心不死，收了 winsurf 后还要搞编程工具，出了个 codex 集成在 ChatGPT中。说实话 ，玩玩儿还行，有点儿鸡肋。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/017-bf9a9ab7.png"&gt;&lt;/p&gt;
&lt;h2 id="其他"&gt;&lt;a href="#%e5%85%b6%e4%bb%96" class="header-anchor"&gt;&lt;/a&gt;其他
&lt;/h2&gt;&lt;p&gt;v0、bolt、Lovable&lt;/p&gt;
&lt;p&gt;如何让 AI 编程赋能给普罗大众？ 以上这些产品给出了各自不同的答案 。&lt;/p&gt;
&lt;h3 id="v0"&gt;&lt;a href="#v0" class="header-anchor"&gt;&lt;/a&gt;v0
&lt;/h3&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/018-b1883e95.png"&gt;&lt;/p&gt;
&lt;p&gt;v0 是我使用最多的产品，一般画个架构图，写个页面 Demo，直接聊天就行了，效果也是越来越好。&lt;/p&gt;
&lt;h3 id="bolt"&gt;&lt;a href="#bolt" class="header-anchor"&gt;&lt;/a&gt;bolt
&lt;/h3&gt;&lt;p&gt;bolt 由 Supabase 集成支持，支持上传图片/文件作为提示生成应用原型，适合快速开发应用。支持从设计稿到应用的一键生成。（是这么宣传的，但越是复杂的项目越不可能）&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/019-e9ea52e1.png"&gt;&lt;/p&gt;
&lt;h3 id="lovable"&gt;&lt;a href="#lovable" class="header-anchor"&gt;&lt;/a&gt;Lovable
&lt;/h3&gt;&lt;p&gt;支持通过自然语言生成交互式界面代码，与 v0 类似，但更侧重于低代码与 AI 结合的开发模式，适合非技术人员快速构建应用。尤其偏重于设计师群体。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/020-ffc27caf.png"&gt;&lt;/p&gt;
&lt;p&gt;设计这个领域不止是 Lovable，国人也有产品发布，比如 YouWare。&lt;/p&gt;
&lt;p&gt;YouWare 拥有「代码快速转化与分享」「自然语言生成网站」「一键美化作品」「创意流动与共创」「和谐社区环境」「自主控制作品曝光」和「创作激励体系」七大产品亮点。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-06-26-ai-bian-cheng-gong-ju-shi-yong-you-gan/021-06fe40dc.png"&gt;&lt;/p&gt;
&lt;p&gt;创始人明超平曾在一加手机担任手机影像产品经理，在字节跳动负责剪映 / Capcut 手机端工具，在月之暗面做过核心产品负责人。&lt;/p&gt;
&lt;p&gt;YouWare 这个产品就是 &lt;strong&gt;帮助普通用户通过 AI Coding 将创意和灵感迅速转化为作品&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="最后"&gt;&lt;a href="#%e6%9c%80%e5%90%8e" class="header-anchor"&gt;&lt;/a&gt;最后
&lt;/h2&gt;&lt;p&gt;我将用一句话总结一下 AI Coding 的未来：&lt;strong&gt;帮助用户（专业的和普通的）通过 AI Coding 将想法利用代码作为媒介转化成产品。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;无论 是什么想法&lt;/li&gt;
&lt;li&gt;无论 是什么产品&lt;/li&gt;
&lt;li&gt;无论 是什么用户&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI Coding 未来的想象空间，理论上，是无限的。&lt;/p&gt;</description></item><item><title>基于 Vue2 的文件预览解决方案，全部代码由 Cursor AI 助手生成</title><link>https://xiaobox.github.io/p/2025-01-17-ji-yu-vue2-de-wen-jian-yu-lan-jie-jue-fang-an-quan-bu-dai-ma/</link><pubDate>Fri, 17 Jan 2025 04:35:49 +0000</pubDate><guid>https://xiaobox.github.io/p/2025-01-17-ji-yu-vue2-de-wen-jian-yu-lan-jie-jue-fang-an-quan-bu-dai-ma/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-01-17-ji-yu-vue2-de-wen-jian-yu-lan-jie-jue-fang-an-quan-bu-dai-ma/cover.jpg" alt="Featured image of post 基于 Vue2 的文件预览解决方案，全部代码由 Cursor AI 助手生成" /&gt;&lt;h2 id="项目介绍"&gt;&lt;a href="#%e9%a1%b9%e7%9b%ae%e4%bb%8b%e7%bb%8d" class="header-anchor"&gt;&lt;/a&gt;项目介绍
&lt;/h2&gt;&lt;p&gt;这是一个基于 Vue 2 的文件预览解决方案，支持主流办公文件的在线预览，包括 Word、Excel、PPT 和 PDF 文件。本项目采用 Vue 2 技术栈开发，确保了更好的兼容性和稳定性。&lt;/p&gt;
&lt;p&gt;该项目目前已开源：https://github.com/xiaobox/file-preview-demo&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2025-01-17-ji-yu-vue2-de-wen-jian-yu-lan-jie-jue-fang-an-quan-bu-dai-ma/001-c71411d9.png"&gt;&lt;/p&gt;
&lt;h2 id="项目开发说明"&gt;&lt;a href="#%e9%a1%b9%e7%9b%ae%e5%bc%80%e5%8f%91%e8%af%b4%e6%98%8e" class="header-anchor"&gt;&lt;/a&gt;项目开发说明
&lt;/h2&gt;&lt;p&gt;本项目是一个特殊的实验性项目，完全通过与 Cursor（AI 驱动的智能 IDE）的交互来完成。从项目初始化到最终完成，所有的代码都是通过与 Cursor AI 助手的对话生成的，没有手动编写任何一行代码。这个开发过程展示了 AI 辅助编程的潜力，以及如何利用先进的 AI 工具来加速开发流程。&lt;/p&gt;
&lt;p&gt;主要特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;零手写代码：所有代码均由 Cursor AI 生成&lt;/li&gt;
&lt;li&gt;完整对话驱动：通过自然语言描述需求，AI 理解并实现功能&lt;/li&gt;
&lt;li&gt;快速迭代：AI 能够根据反馈快速调整和优化代码&lt;/li&gt;
&lt;li&gt;高质量输出：生成的代码遵循最佳实践，包含完整的错误处理和用户体验考虑&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种开发方式展示了 AI 在软件开发中的应用前景，以及如何利用 AI 工具来提高开发效率。&lt;/p&gt;
&lt;h2 id="功能特性"&gt;&lt;a href="#%e5%8a%9f%e8%83%bd%e7%89%b9%e6%80%a7" class="header-anchor"&gt;&lt;/a&gt;功能特性
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;支持文件类型：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Word 文档 (.docx)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Excel 表格 (.xlsx)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PowerPoint 演示文稿 (.pptx)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PDF 文档 (.pdf)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;支持本地文件预览和远程 URL 文件预览&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;支持大文件分页加载&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;完整的跨平台支持：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;完美适配 PC 端和移动端&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;iOS（iPhone/iPad）和 Android 设备上表现优异&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;响应式设计，自适应不同屏幕尺寸&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;针对移动端优化了触控体验和性能&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;优雅的加载状态和错误处理&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="技术栈"&gt;&lt;a href="#%e6%8a%80%e6%9c%af%e6%a0%88" class="header-anchor"&gt;&lt;/a&gt;技术栈
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;核心框架：Vue 2.7.x（使用 Vue 2 的最新稳定版本）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;路由管理：Vue Router 3.6.x（与 Vue 2 配套的路由版本）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;文件预览：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Word：@vue-office/docx&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Excel：@vue-office/excel&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PPT：@vue-office/pptx&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PDF：pdfjs-dist（Mozilla PDF.js）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;开发工具：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Vue CLI&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Babel&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ESLint&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="技术方案说明"&gt;&lt;a href="#%e6%8a%80%e6%9c%af%e6%96%b9%e6%a1%88%e8%af%b4%e6%98%8e" class="header-anchor"&gt;&lt;/a&gt;技术方案说明
&lt;/h2&gt;&lt;h3 id="pdf-预览方案"&gt;&lt;a href="#pdf-%e9%a2%84%e8%a7%88%e6%96%b9%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;PDF 预览方案
&lt;/h3&gt;&lt;p&gt;本项目选择使用 Mozilla 的 PDF.js（pdfjs-dist）而不是 @vue-office/pdf 的原因：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;功能完整性：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;PDF.js 是 Mozilla 维护的成熟方案，功能更加完整&lt;/li&gt;
&lt;li&gt;支持文档大纲、缩放、搜索等高级功能&lt;/li&gt;
&lt;li&gt;支持表单填写和注释&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="3"&gt;
&lt;li&gt;性能优势：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;支持分页按需加载，减少内存占用&lt;/li&gt;
&lt;li&gt;渲染性能更好，适合大型 PDF 文件&lt;/li&gt;
&lt;li&gt;支持流式加载，无需等待整个文件下载完成&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="5"&gt;
&lt;li&gt;兼容性：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;浏览器兼容性更好&lt;/li&gt;
&lt;li&gt;支持更多 PDF 特性和格式&lt;/li&gt;
&lt;li&gt;渲染效果更接近原生&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="7"&gt;
&lt;li&gt;社区支持：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;有庞大的社区支持&lt;/li&gt;
&lt;li&gt;问题修复和更新及时&lt;/li&gt;
&lt;li&gt;文档完善，示例丰富&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="9"&gt;
&lt;li&gt;实践经验：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;在测试中发现，使用 @vue-office/pdf 预览大型 PDF 文件时，在移动端存在严重问题&lt;/li&gt;
&lt;li&gt;具体表现为：在文件未完全加载完成时，页面会自动多次重新加载&lt;/li&gt;
&lt;li&gt;这可能是由移动设备内存限制或操作系统的内存管理机制导致&lt;/li&gt;
&lt;li&gt;而使用 PDF.js 的分页渲染机制可以很好地解决这个问题&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="渲染实现说明"&gt;&lt;a href="#%e6%b8%b2%e6%9f%93%e5%ae%9e%e7%8e%b0%e8%af%b4%e6%98%8e" class="header-anchor"&gt;&lt;/a&gt;渲染实现说明
&lt;/h3&gt;&lt;p&gt;PDF.js 的渲染流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;核心渲染过程：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;pdfDoc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pageNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;viewport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getViewport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;-$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pageNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;canvas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2d&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;canvas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;viewport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;canvas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;viewport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;renderContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;canvasContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;viewport&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;renderContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;promise&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;技术细节：&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;直接将 PDF 内容渲染到 Canvas，而不是转换成图片再加载&lt;/li&gt;
&lt;li&gt;每个页面使用独立的 Canvas 元素&lt;/li&gt;
&lt;li&gt;采用分页渲染机制，提升渲染性能&lt;/li&gt;
&lt;li&gt;支持缩放、旋转等视图操作&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="4"&gt;
&lt;li&gt;性能优化：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;实现分页渲染，避免一次性渲染所有页面&lt;/li&gt;
&lt;li&gt;使用 Canvas 直接渲染，减少内存占用&lt;/li&gt;
&lt;li&gt;支持流式加载，提升首屏加载速度&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="6"&gt;
&lt;li&gt;移动端适配：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;针对移动端内存限制进行优化&lt;/li&gt;
&lt;li&gt;避免了整个文件内容同时加载到内存的问题&lt;/li&gt;
&lt;li&gt;解决了大型 PDF 在移动端反复重载的问题&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="旧版-office-格式支持方案"&gt;&lt;a href="#%e6%97%a7%e7%89%88-office-%e6%a0%bc%e5%bc%8f%e6%94%af%e6%8c%81%e6%96%b9%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;旧版 Office 格式支持方案
&lt;/h3&gt;&lt;p&gt;如果需要支持旧版 Office 格式（.doc、.xls、.ppt），建议采用以下方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;服务器转换方案：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;使用 LibreOffice/OpenOffice 搭建文件转换服务&lt;/li&gt;
&lt;li&gt;将旧版格式转换为新版格式后再预览&lt;/li&gt;
&lt;li&gt;优点：可控性强，无需第三方服务&lt;/li&gt;
&lt;li&gt;缺点：需要自行维护服务器&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="3"&gt;
&lt;li&gt;商业转换服务：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;使用 Microsoft Office 365 API&lt;/li&gt;
&lt;li&gt;使用金山 WPS 开放平台&lt;/li&gt;
&lt;li&gt;优点：稳定性好，维护成本低&lt;/li&gt;
&lt;li&gt;缺点：需要付费，依赖第三方服务&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="5"&gt;
&lt;li&gt;客户端转换：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;提示用户使用 Office 或 WPS 另存为新版格式&lt;/li&gt;
&lt;li&gt;优点：实现简单，无需额外服务&lt;/li&gt;
&lt;li&gt;缺点：用户体验不够友好&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="最后"&gt;&lt;a href="#%e6%9c%80%e5%90%8e" class="header-anchor"&gt;&lt;/a&gt;最后
&lt;/h2&gt;&lt;h3 id="谈谈感受"&gt;&lt;a href="#%e8%b0%88%e8%b0%88%e6%84%9f%e5%8f%97" class="header-anchor"&gt;&lt;/a&gt;谈谈感受
&lt;/h3&gt;&lt;p&gt;最近团队在开发文件预览的功能，本来觉得是一个比较常规和普遍的功能就没多过问，后来前端同学反馈问题比较多，比如移动端的兼容问题、新旧 office 格式文件的兼容问题、pdf 大文件预览问题等等。于是参与想了几个方案，本文介绍的项目是一个&lt;strong&gt;纯前端&lt;/strong&gt; 解决方案。整体来看占用的资源比较小，是一个性价比很高的方案。当然，这是在解决了刚才提到的那些问题的前提下。&lt;/p&gt;
&lt;p&gt;这个项目从技术难度上并没有多高，但从&lt;strong&gt;过程和形式上&lt;/strong&gt;却很有意思。&lt;/p&gt;
&lt;p&gt;因为&lt;strong&gt;这是又一个我用 Cursor 在不手写任何一行代码的基础上完成的项目。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我本来预计半天的时间就能搞定，没想到整整搞了一天。&lt;/p&gt;
&lt;p&gt;无论是在技术社区还是在社交媒体，经常看到有人说：&lt;strong&gt;自己不会写代码，用 Cursor 在自己不写一行代码的情况下，用很短的时间完成了一个 app 或者一个项目。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;其实这样的项目，我已经写了好几个了，但区别是，我是一名具有 15 年研发经验的工程师。&lt;/p&gt;
&lt;p&gt;从我的角度来说，我觉得：&lt;strong&gt;在自己完全不会写代码的情况下，用 Cursor 完成一个项目是可能的，但可能性不大。除非这个项目非常简单。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有过开发经验的朋友一定知道，一个项目在整个研发周期内多多少少会遇到一些问题，这些问题和挑战需要程序员们来解决，这些问题有大有小。我想表达的是，无论多少，你一定会遇到问题，而且很大概率是&lt;strong&gt;棘手的、不好解决的、针对你当前项目本身特定上下文的问题。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;那么问题来了，遇到这种问题，你如果完全不懂开发，不会写代码，仅凭着使用 AI 工具开发项目的热情和很可能出现幻觉的模型是很可能搞不定的。即使能搞定也会相当浪费时间和资源。生产率极其低！&lt;/p&gt;
&lt;p&gt;这是我在使用 Cursor 开发了几个项目后的结论，我不是说 Cursor 不好，相反，我觉得它很好，但工具是需要配备给适合使用它的人，无脑的宣传它的万能是不对的。&lt;/p&gt;
&lt;p&gt;而对于我来讲，利用 Cursor 解决问题、开发项目，在理想的情况下真的是分分钟的事儿。别忘了，我有 15 年的研发经验啊。拥有这样的经验和基础知识再加上 Cursor 确实如虎添翼。&lt;/p&gt;
&lt;p&gt;现在，任何语言，任何技术栈在我面前都不是问题，计算机世界的大门从来没有像今天一样对我这样敞开。我的学习效率和解决问题的效率有了极大的提高，说 10 倍可能夸张，至少有 3-5 倍。&lt;/p&gt;
&lt;p&gt;所以，我认为 Cursor 这类优秀的 AI 编程工具就像一个你的结对编程的伙伴，你们是协作关系，他像一名高级工程师，你们互相引导、激发、创造！&lt;/p&gt;
&lt;p&gt;你想想什么人才能结对编程，一个什么都不懂的人和你结对你觉得靠谱吗？ 我想你明白我想表达的是什么了。&lt;/p&gt;
&lt;p&gt;最近在社交媒体看到知名博主 “宝玉” 总结的 AI 编程工具的使用原则，结合我自己的使用经验我觉得总结得很靠谱，分享给大家：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;准确的描述清楚需求&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;架构能力，合理的将复杂系统拆分成松耦合的模块，让 AI 可以在一次会话中处理&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;专业编程能力，能分辨 AI 生成的代码的好坏&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;调试能力，当 AI 生成的代码出现问题，能快速定位，自己或者借助 AI&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>Cursor 一个真正让程序员产生危机感的 AI 编程工具</title><link>https://xiaobox.github.io/p/2024-08-15-cursor-yi-ge-zhen-zheng-rang-cheng-xu-yuan-chan-sheng-wei-ji/</link><pubDate>Thu, 15 Aug 2024 09:43:32 +0000</pubDate><guid>https://xiaobox.github.io/p/2024-08-15-cursor-yi-ge-zhen-zheng-rang-cheng-xu-yuan-chan-sheng-wei-ji/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-15-cursor-yi-ge-zhen-zheng-rang-cheng-xu-yuan-chan-sheng-wei-ji/cover.jpg" alt="Featured image of post Cursor 一个真正让程序员产生危机感的 AI 编程工具" /&gt;&lt;p&gt;&lt;a class="link" href="https://www.cursor.com/" target="_blank" rel="noopener"
 &gt;https://www.cursor.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-15-cursor-yi-ge-zhen-zheng-rang-cheng-xu-yuan-chan-sheng-wei-ji/001-489fbf36.png"&gt;&lt;/p&gt;
&lt;h2 id="起初"&gt;&lt;a href="#%e8%b5%b7%e5%88%9d" class="header-anchor"&gt;&lt;/a&gt;起初
&lt;/h2&gt;&lt;p&gt;最开始接触 cursor 的时候是在去年年初，openAI ChatGPT 带火了一批 AI 概念产品。GitHub 的 Copilot 自不用说，很早就在使用，有了大模型的加持当时也是如日中天。我记得 cursor 当时主打的点是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;可以无逢迁移 vscode ，vscode 的所有插件可以直接一键转移到 cursor。连界面都一模一样&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;轻巧、快速。体量小，启动快，编程效率高&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可用免费的 AI 模型进行提示。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当时体验下来发现也确实如宣传所说，是挺快，但是没有那么强的吸引力让我愿意换 vscode 和 idea 。我使用最多的还是 vscode+idea+copilot+chatgpt 。基本上满足我日常开发的需求了。当然后来又加上了 &lt;code&gt;warp&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="现在"&gt;&lt;a href="#%e7%8e%b0%e5%9c%a8" class="header-anchor"&gt;&lt;/a&gt;现在
&lt;/h2&gt;&lt;p&gt;最近我又体验了一下 cursor ，发现它和原来的版本有很大的不同。而这一次，彻底改变了对它的看法。目前我已将编程工具切换到了 cursor，很心甘情愿的切换了过去。&lt;/p&gt;
&lt;h3 id="原理"&gt;&lt;a href="#%e5%8e%9f%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;原理
&lt;/h3&gt;&lt;p&gt;先说最重要的，一切事情有困就有果，有果就有因，cursor 好用的功能有很多，但最重要的我认为只有一个。关于这个功能，我要说明一下它的原理。&lt;/p&gt;
&lt;p&gt;其实市面上的 AI 编程助手类工具不止一个，比较好用的有:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;github 的 copilot&lt;/li&gt;
&lt;li&gt;字节豆包的 marscode&lt;/li&gt;
&lt;li&gt;阿里的 通义灵码&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-15-cursor-yi-ge-zhen-zheng-rang-cheng-xu-yuan-chan-sheng-wei-ji/002-ab589355.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-15-cursor-yi-ge-zhen-zheng-rang-cheng-xu-yuan-chan-sheng-wei-ji/003-c0225f66.png"&gt;&lt;/p&gt;
&lt;p&gt;大家都知道，这些工具背后是各家的 LLM ,提示质量的高低主要取决于这些大模型的能力。而所有的工具都只是基于当前文件的。无论是代码解释、优化、生成注释，都是基于当前文件的内容，无论是针对文件、类、方法。你对代码提问的 codeBase 是单文件，上下文自然也是当前打开的这个单文件。&lt;/p&gt;
&lt;p&gt;这就是现在的这些 AI 编程工具的运行逻辑，从当前文件中获得代码的上下文再结合你的提问（prompt）,一起发给 LLM，最后得到结果。其实这已经能解决不少问题了，在没有 cursor 之前感觉很不错，写程序确实能提高效率。&lt;/p&gt;
&lt;p&gt;我们觉得上面那些工具很不错是因为我们没有用过更好的工具：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;cursor 的 codeBase 是整个工程&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cursor 的 codeBase 是整个工程&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cursor 的 codeBase 是整个工程&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可能有的伙伴看到这几个字立刻就懂我是什么意思了，对，就是那个你越想越激动的事情。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-15-cursor-yi-ge-zhen-zheng-rang-cheng-xu-yuan-chan-sheng-wei-ji/004-9ffd9c4a.png"&gt;&lt;/p&gt;
&lt;p&gt;cursor 的逻辑是，先将工程内的所有代码进行索引和向量化（Embedding），再之后你的所有提问都是基于整个工程给你答案，它会将你的提问结合整个工程的代码一起提交给 LLM，默认有这些模型：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-15-cursor-yi-ge-zhen-zheng-rang-cheng-xu-yuan-chan-sheng-wei-ji/005-3bf45d75.png"&gt;&lt;/p&gt;
&lt;p&gt;注意这里不包含 &lt;code&gt;deepseek-coder&lt;/code&gt;，那是我自己添加的。&lt;/p&gt;
&lt;p&gt;这很像基于 RAG 方法论的系统实现，只不过外挂的知识库是代码库而已。&lt;/p&gt;
&lt;p&gt;这就是我认为最重要的功能，我说清楚了它的逻辑，接下来我们来说基于这个功能能做什么，这才是最激动人心的部分&lt;/p&gt;
&lt;h2 id="能解决的问题"&gt;&lt;a href="#%e8%83%bd%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98" class="header-anchor"&gt;&lt;/a&gt;能解决的问题
&lt;/h2&gt;&lt;h3 id="代码补全"&gt;&lt;a href="#%e4%bb%a3%e7%a0%81%e8%a1%a5%e5%85%a8" class="header-anchor"&gt;&lt;/a&gt;代码补全
&lt;/h3&gt;&lt;p&gt;之前工具的代码补全虽然使用了 LLM，但仍然不那么精准，因为它只能把当前文件作为上下文，而 cursor,它的 codeBase 是基于整个工程的，它的代码补全相当于是分析了你整个工程的代码基础之上给的建议，那是正当的精准啊。这也就是为什么有的朋友说，现在用 cursor 写程序一路 tab 下来就完事儿了，比自己写的还好。简直就是自动化编程。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-15-cursor-yi-ge-zhen-zheng-rang-cheng-xu-yuan-chan-sheng-wei-ji/006-7d036f6d.png"&gt;&lt;/p&gt;
&lt;h3 id="智能纠错"&gt;&lt;a href="#%e6%99%ba%e8%83%bd%e7%ba%a0%e9%94%99" class="header-anchor"&gt;&lt;/a&gt;智能纠错
&lt;/h3&gt;&lt;p&gt;这代码你就放心写吧，如果你写着写着写错了，cursor 会在你输入的时候自动纠正你的错误&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-15-cursor-yi-ge-zhen-zheng-rang-cheng-xu-yuan-chan-sheng-wei-ji/007-494f7724.png"&gt;&lt;/p&gt;
&lt;p&gt;它为啥能纠错，它怎么知道我写错了？对，还是 codeBase，你的整个工程它都了如指掌。&lt;/p&gt;
&lt;h3 id="聊天"&gt;&lt;a href="#%e8%81%8a%e5%a4%a9" class="header-anchor"&gt;&lt;/a&gt;聊天
&lt;/h3&gt;&lt;p&gt;太基础的功能了，然而因为 codebase，它就有了无限可能。首先，你可以在当前文件中针对某一部分来提问，比如你要重构一个方法什么的&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-15-cursor-yi-ge-zhen-zheng-rang-cheng-xu-yuan-chan-sheng-wei-ji/008-6b38b91d.png"&gt;&lt;/p&gt;
&lt;p&gt;它会重构的比较好，因为它的 codebase 是整个工程。&lt;/p&gt;
&lt;p&gt;你也可以单独打开一个聊天窗口&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-15-cursor-yi-ge-zhen-zheng-rang-cheng-xu-yuan-chan-sheng-wei-ji/009-18a518d9.png"&gt;&lt;/p&gt;
&lt;p&gt;在这里提问可以仅针对当前文件、文件夹、图片、文档、网络或者整个 codebase&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-15-cursor-yi-ge-zhen-zheng-rang-cheng-xu-yuan-chan-sheng-wei-ji/010-a064956e.png"&gt;&lt;/p&gt;
&lt;p&gt;最重要就是这个 &lt;code&gt;Codebase&lt;/code&gt; 这是可以发挥无限想像的地方。&lt;/p&gt;
&lt;p&gt;由于篇幅的原因，我不会把所有的细节全部用图片或视频的形式放出来，因为太多了，但你看我的描述也一定能体会到 cursor 的强大，这里我举几个例子，这些例子我已经测试成功并且在工作中使用了，它很强，很实用：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;我是项目主要的开发者，我现在想针对某个功能进行重构，注意不是一个类，一个文件，而是整个功能的重构。我让 cursor 给出我具体的建议和修改的代码。&lt;strong&gt;它实现了，非常具体、清晰、详细、正确率高达 95 % 以上（claude 模型）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;我有一个陈旧的项目，代码中几乎没有注释，也没有接口文档。我现在想从代码中分析出一份 api 接口文档，要包括地址、请求类型、请求和响应字段，以及示例 json。&lt;strong&gt;它也实现了，就是我想要的内容，100% 正确&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;我有一个小白同事，刚进项目组，对他要负责的功能模块完全不知道流程是什么，不巧的是整个项目也没有什么文档，需要他去看代码自己梳理。他让 cursor 帮他梳理出项目中有关 oauth2 认证、鉴权的完整流程。从第一个请求开始，到最后一个请求数据返回，包括所有相关的代码片段和执行路径。&lt;strong&gt;cursor 瞬间完成了，正确率 100%&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;我有一个测试同事，想写关于某个重要模块的测试用例及测试报告，cursor 基于整个项目的 codebase 帮他一步一步实现了。&lt;/li&gt;
&lt;li&gt;我有个前端同事上传了一张别人设计的不错的界面的图片，他让 cursor 帮他根据他 vue2 项目的情况自动生成页面代码,&lt;strong&gt;cursor 瞬间完成了，和图片的相似度达到 85%&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;我有个大数据开发同事，他正在重构之前写的 SQL，他把建表语句告诉 cursor 后，让他把一批 sql文件根据他的要求进行了重构，cursor 很快就完成了。&lt;/li&gt;
&lt;li&gt;我有个运维同事，他之前把所有运维的工作全部代码化了。在一个仓库里，现在基础设施有一些变动，他让 cursor 根据现有的运维脚本和代码进行重构，&lt;strong&gt;cursor 瞬间就完成了，正确率 90%&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;我还有个产品同事，现在不怎么用 Axure 画原型了，他说和 cursor 交流一下基础上就能出前端代码，跟前端学了点儿基础知识，原型几分钟就搞定了。&lt;/li&gt;
&lt;li&gt;我有个朋友，现在想将 .net 项目转成 java，他原先估计要组一个团队至少 5 个后端一起干，现在他一个人正在一步一步地用 cursor 帮助他实现。&lt;/li&gt;
&lt;li&gt;我还有个朋友。。。。。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我想你应该知道我想说什么了，我想你也知道 cursor 为什么足以让我兴奋了。而所有的这些原因，都是因为它最重要的原理，它的 codebase，它和其他产品不一样的逻辑。&lt;/p&gt;
&lt;p&gt;cursor 当然还有一些其他功能我没有介绍到，不过那都不重要，你已经知道了它的逻辑，它的核心原理和功能，剩下的就交给你了，交给你的想象力和创造力了。&lt;/p&gt;
&lt;h2 id="优点和缺点"&gt;&lt;a href="#%e4%bc%98%e7%82%b9%e5%92%8c%e7%bc%ba%e7%82%b9" class="header-anchor"&gt;&lt;/a&gt;优点和缺点
&lt;/h2&gt;&lt;p&gt;以上的内容怎么看都是 cursor 的优点，然而在阅读的过程中你一定想到它还有许多令人担心的问题，没错。首先就是数据安全。虽然 cursor 官方宣称数据是保存在本地的，不会被上传，但是我知道你一定担心。这是个有意思的问题，因为关于这一点无论对方如何承诺你都不会轻信，隐私和方便它永远是问题的两端，我们不可能全都要，所以要做个取舍。&lt;/p&gt;
&lt;p&gt;然后就是价格，cursor 前两周是免费使用的，然后再用就要收费了，怎么收费呢？&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-15-cursor-yi-ge-zhen-zheng-rang-cheng-xu-yuan-chan-sheng-wei-ji/011-82956fc6.png"&gt;&lt;/p&gt;
&lt;p&gt;我说一下重点，如果你使用 cursor 是包含两部分费用的，一部分是软件的费用，这部分比如一个月 &lt;code&gt;20$&lt;/code&gt; 是付给 cursor 的，另一部分是模型的使用费用，这个是你付给像 openAI 这样的模型提供商的。那么加起来可能一个月你至少有 30$ 以上的成本。不过关于模型这部分，因为 cursor 可以添加 deepseek 的 coder 模型，所以模型使用成本算是打下来了，因为 deepseek 模型的 API 是白菜价&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-15-cursor-yi-ge-zhen-zheng-rang-cheng-xu-yuan-chan-sheng-wei-ji/012-eb626349.png"&gt;&lt;/p&gt;
&lt;p&gt;不但是白菜价，首次注册人家还送 500万 tokens&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-15-cursor-yi-ge-zhen-zheng-rang-cheng-xu-yuan-chan-sheng-wei-ji/013-9b4705c9.png"&gt;&lt;/p&gt;
&lt;p&gt;总结来说，除了优点都是缺点，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;成本不低&lt;/li&gt;
&lt;li&gt;数据安全&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这两点加起来对很多人来说就望而却步了，当然还要解决网络的问题。不过我觉得国内的公司一定不会坐以待毙，一定很快就会有类似的产品上线了，到时候网络就不是问题了。&lt;/p&gt;
&lt;h2 id="未来"&gt;&lt;a href="#%e6%9c%aa%e6%9d%a5" class="header-anchor"&gt;&lt;/a&gt;未来
&lt;/h2&gt;&lt;p&gt;正如我标题所写，因为看到了 cursor，这次我真的觉得程序员有危机了，尤其是对于初级的、新手程序员。因为我用工具虽然可能有一点点错误，但它可以瞬间完成一些基础的工作，完全可以替代人了，我不需要招那么多人来干那些 “脏活累活” ，我只需要几个高级并且会使用高级工具的人才就可以了，他们创造的人效是原来的 10 倍以上。&lt;/p&gt;
&lt;p&gt;再进一步，自动化编程可以期待了吗？也就是提一个描述得很清晰的需求给 AI，他能自动把程序写好，有公司正在做：https://www.cognition.ai/ 原先我觉得他在吹牛，现在，尤其是使用了 cursor 后，我觉得可能不远了。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2024-08-15-cursor-yi-ge-zhen-zheng-rang-cheng-xu-yuan-chan-sheng-wei-ji/014-330d3b2c.png"&gt;&lt;/p&gt;
&lt;h2 id="思考"&gt;&lt;a href="#%e6%80%9d%e8%80%83" class="header-anchor"&gt;&lt;/a&gt;思考
&lt;/h2&gt;&lt;p&gt;我在最近几年思考了一个问题，很多企业没有业务知识库，就算是有，文档也不全，也不及时更新，这个所谓的企业内部的业务知识库也是名存实亡。那如果需要了解业务的时候怎么办？比如需要大版本更新，重大业务调整的时候，怎么办呢？找开发看代码是最准的了，然后这些辛苦的工作又 TMD 转到开发这儿来了。&lt;/p&gt;
&lt;p&gt;我想来想去，感觉没有什么非常好的解法。虽然可以用 RAG 来解决一部分的问题，但没有完全解决，因为只要文档不是最新的，文档有问题，一切基于知识库的分析全都是错的。直到 cursor 出现了，我觉得问题可以以另外一种方式来解决了。因为代码是准的，代码就是错那也是代码的 bug。但它是准的，代码写错了，也是准的。代码什么样线上就是什么样，业务就是什么样。&lt;/p&gt;
&lt;p&gt;那么整个企业的业务知识就已经在代码里了，只需要从代码仓库提炼就可以了，我们借助 cursor 或者以后什么其他类似的工具再加工一下就完全可以提炼出准确、实时、可用的企业业务知识了。而这个 “知识” 才是企业真正的业务资产。代码就算没了，根据业务重建都可以，反过来，如果你对业务不了解，给你代码也没用。&lt;/p&gt;</description></item><item><title>百度 UidGenerator 源码解析</title><link>https://xiaobox.github.io/p/2021-11-05-bai-du-uidgenerator-yuan-ma-jie-xi/</link><pubDate>Fri, 05 Nov 2021 07:54:45 +0000</pubDate><guid>https://xiaobox.github.io/p/2021-11-05-bai-du-uidgenerator-yuan-ma-jie-xi/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-05-bai-du-uidgenerator-yuan-ma-jie-xi/cover.jpg" alt="Featured image of post 百度 UidGenerator 源码解析" /&gt;&lt;h2 id="简介"&gt;&lt;a href="#%e7%ae%80%e4%bb%8b" class="header-anchor"&gt;&lt;/a&gt;简介
&lt;/h2&gt;&lt;p&gt;先来看一下官方介绍：&lt;/p&gt;
&lt;h3 id="雪花算法"&gt;&lt;a href="#%e9%9b%aa%e8%8a%b1%e7%ae%97%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;雪花算法
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;雪花算法（Snowflake）是一种生成分布式全局唯一 ID 的算法，生成的 ID 称为 Snowflake IDs 或 snowflakes。这种算法由 Twitter 创建，并用于推文的 ID。Discord 和 Instagram 等其他公司采用了修改后的版本。一个 Snowflake ID 有 64 位。前 41 位是时间戳，表示了自选定的时期以来的毫秒数。接下来的 10 位代表计算机 ID，防止冲突。其余 12 位代表每台机器上生成 ID 的序列号，这允许在同一毫秒内创建多个 Snowflake ID。SnowflakeID 基于时间生成，故可以按时间排序。此外，一个 ID 的生成时间可以由其自身推断出来，反之亦然。该特性可以用于按时间筛选 ID，以及与之联系的对象。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-05-bai-du-uidgenerator-yuan-ma-jie-xi/001-691f1e93.jpg"&gt;&lt;/p&gt;
&lt;p&gt;第 1 位&lt;/p&gt;
&lt;p&gt;该位不用主要是为了保持 ID 的自增特性，若使用了最高位，int64 会表示为负数。在 Java 中由于 long 类型的最高位是符号位，正数是 0，负数是 1，一般生成的 ID 为正整数，所以最高位为 0&lt;/p&gt;
&lt;p&gt;41 位时间戳&lt;/p&gt;
&lt;p&gt;毫秒级的时间，一般实现上不会存储当前的时间戳，而是时间戳的差值（当前时间减去固定的开始时间），这样可以使产生的 ID 从更小值开始；&lt;/p&gt;
&lt;p&gt;41 bit 可以表示的数字多达 2^41 - 1，也就是可以标识 2 ^ 41 - 1 个毫秒值，换算成年就是表示 69 年的时间。&lt;/p&gt;
&lt;p&gt;(1L &amp;laquo; 41) / (1000L 60 60 24 365) = (2199023255552 / 31536000000) ≈ 69.73 年。&lt;/p&gt;
&lt;p&gt;10 位工作机器 ID&lt;/p&gt;
&lt;p&gt;Twitter 实现中使用前 5 位作为数据中心标识，后 5 位作为机器标识，可以部署 1024 （2^10）个节点。意思就是最多代表 2 ^ 5 个机房（32 个机房），每个机房里可以代表 2 ^ 5 个机器（32 台机器）。具体的分区可以根据自己的需要定义。比如拿出 4 位标识业务号，其他 6 位作为机器号。&lt;/p&gt;
&lt;p&gt;12 位序列号&lt;/p&gt;
&lt;p&gt;支持同一毫秒内同一个节点可以生成 4096 （2^12）个 ID，也就是同一毫秒内同一台机器所生成的最大 ID 数量为 4096。&lt;/p&gt;
&lt;p&gt;简单来说，你的某个服务假设要生成一个全局唯一 id，那么就可以发送一个请求给部署了 SnowFlake 算法的系统，由这个 SnowFlake 算法系统来生成唯一 id。这个 SnowFlake 算法系统首先肯定是知道自己所在的机器号，（这里姑且讲 10bit 全部作为工作机器 ID）接着 SnowFlake 算法系统接收到这个请求之后，首先就会用二进制位运算的方式生成一个 64 bit 的 long 型 id，64 个 bit 中的第一个 bit 是无意义的。接着用当前时间戳（单位到毫秒）占用 41 个 bit，然后接着 10 个 bit 设置机器 id。最后再判断一下，当前这台机房的这台机器上这一毫秒内，这是第几个请求，给这次生成 id 的请求累加一个序号，作为最后的 12 个 bit。&lt;/p&gt;
&lt;p&gt;优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;理论上 Snowflake 方案的 QPS 约为 409.6w/s（1000 * 2^12），这种分配方式可以保证在任何一个 IDC 的任何一台机器在任意毫秒内生成的 ID 都是不同的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缺点&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;强依赖机器时钟，如果机器上时钟回拨，会导致发号重复或者服务会处于不可用状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="uidgenerator"&gt;&lt;a href="#uidgenerator" class="header-anchor"&gt;&lt;/a&gt;UidGenerator
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;“&lt;/p&gt;
&lt;p&gt;UidGenerator 是 Java 实现的，基于 Snowflake 算法的唯一 ID 生成器。UidGenerator 以组件形式工作在应用项目中，支持自定义 workerId 位数和初始化策略，从而适用于 docker 等虚拟化环境下实例自动重启、漂移等场景。在实现上，UidGenerator 通过借用未来时间来解决 sequence 天然存在的并发限制；采用 RingBuffer 来缓存已生成的 UID, 并行化 UID 的生产和消费，同时对 CacheLine 补齐，避免了由 RingBuffer 带来的硬件级「伪共享」问题。最终单机 QPS 可达 600 万。&lt;/p&gt;
&lt;p&gt;”&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-05-bai-du-uidgenerator-yuan-ma-jie-xi/002-3b13d2df.jpg"&gt;&lt;/p&gt;
&lt;p&gt;UidGenerator 的实现跟 SnowFlake 原始算法不太一样，不过以下参数均可通过 Spring 进行自定义：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sign(1bit) 固定 1bit 符号标识，即生成的 UID 为正数。&lt;/li&gt;
&lt;li&gt;delta seconds (28 bits) 当前时间，相对于时间基点&amp;quot;2016-05-20&amp;quot;的增量值，单位：秒，最多可支持约 8.7 年&lt;/li&gt;
&lt;li&gt;worker id (22 bits) 机器 id，最多可支持约 420w 次机器启动。内置实现为在启动时由数据库分配，默认分配策略为用后即弃，后续可提供复用策略。&lt;/li&gt;
&lt;li&gt;sequence (13 bits) 每秒下的并发序列，13 bits 可支持每秒 8192 个并发。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;RingBuffer 环形数组，数组每个元素成为一个 slot。RingBuffer 容量，默认为 Snowflake 算法中 sequence 最大值，且为 2^N。可通过 boostPower 配置进行扩容，以提高 RingBuffer 读写吞吐量。&lt;/p&gt;
&lt;p&gt;Tail 指针、Cursor 指针用于环形数组上读写 slot：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Tail 指针 表示 Producer 生产的最大序号（此序号从 0 开始，持续递增）。Tail 不能超过 Cursor，即生产者不能覆盖未消费的 slot。当 Tail 已赶上 curosr，此时可通过 rejectedPutBufferHandler 指定 PutRejectPolicy&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cursor 指针 表示 Consumer 消费到的最小序号（序号序列与 Producer 序列相同）。Cursor 不能超过 Tail，即不能消费未生产的 slot。当 Cursor 已赶上 tail，此时可通过 rejectedTakeBufferHandler 指定 TakeRejectPolicy&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CachedUidGenerator 采用了双 RingBuffer，Uid-RingBuffer 用于存储 Uid、Flag-RingBuffer 用于存储 Uid 状态 (是否可填充、是否可消费)&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-05-bai-du-uidgenerator-yuan-ma-jie-xi/003-2d554ee4.jpg"&gt;&lt;/p&gt;
&lt;p&gt;由于数组元素在内存中是连续分配的，可最大程度利用 CPU cache 以提升性能。但同时会带来「伪共享」FalseSharing 问题，为此在 Tail、Cursor 指针、Flag-RingBuffer 中采用了 CacheLine 补齐方式。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-05-bai-du-uidgenerator-yuan-ma-jie-xi/004-dcabbe51.jpg"&gt;&lt;/p&gt;
&lt;p&gt;关于更多伪共享的知识，可以参考：https://www.cnblogs.com/cyfonly/p/5800758.html，&lt;/p&gt;
&lt;p&gt;总结来说，伪共享会导致性能问题，解决了能提升性能，就算不解决也不会出现数据不一致等严重的问题。&lt;/p&gt;
&lt;p&gt;RingBuffer 填充时机&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;初始化预填充 RingBuffer 初始化时，预先填充满整个 RingBuffer.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;即时填充 Take 消费时，即时检查剩余可用 slot 量 (tail - cursor)，如小于设定阈值，则补全空闲 slots。阈值可通过 paddingFactor 来进行配置&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;周期填充 通过 Schedule 线程，定时补全空闲 slots。可通过 scheduleInterval 配置，以应用定时填充功能，并指定 Schedule 时间间隔&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="源码分析"&gt;&lt;a href="#%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90" class="header-anchor"&gt;&lt;/a&gt;源码分析
&lt;/h2&gt;&lt;h3 id="概况"&gt;&lt;a href="#%e6%a6%82%e5%86%b5" class="header-anchor"&gt;&lt;/a&gt;概况
&lt;/h3&gt;&lt;p&gt;整个项目共 2386 行 java 代码
&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-05-bai-du-uidgenerator-yuan-ma-jie-xi/005-b4562dba.jpg"&gt;&lt;/p&gt;
&lt;p&gt;代码内部 class 的依赖结构是这样的：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-05-bai-du-uidgenerator-yuan-ma-jie-xi/006-0b6ac228.jpg"&gt;&lt;/p&gt;
&lt;p&gt;可见 &lt;code&gt;RingBuffer&lt;/code&gt; 是个核心类。&lt;/p&gt;
&lt;h3 id="目录结构"&gt;&lt;a href="#%e7%9b%ae%e5%bd%95%e7%bb%93%e6%9e%84" class="header-anchor"&gt;&lt;/a&gt;目录结构
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt; com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt; └── baidu
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt; └── fsg
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt; └── uid
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; ├── BitsAllocator.java - Bit 分配器 (C)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; ├── UidGenerator.java - UID 生成的接口 (I)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; ├── buffer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; │ ├── BufferPaddingExecutor.java - 填充 RingBuffer 的执行器 (C)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; │ ├── BufferedUidProvider.java - RingBuffer 中 UID 的提供者 (C)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; │ ├── RejectedPutBufferHandler.java - 拒绝 Put 到 RingBuffer 的处理器 (C)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; │ ├── RejectedTakeBufferHandler.java - 拒绝从 RingBuffer 中 Take 的处理器 (C)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; │ └── RingBuffer.java - 内含两个环形数组 (C)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; ├── exception
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt; │ └── UidGenerateException.java - 运行时异常
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt; ├── impl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt; │ ├── CachedUidGenerator.java - RingBuffer 存储的 UID 生成器 (C)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt; │ └── DefaultUidGenerator.java - 无 RingBuffer 的默认 UID 生成器 (C)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt; ├── utils
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt; │ ├── DateUtils.java
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt; │ ├── DockerUtils.java
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt; │ ├── EnumUtils.java
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt; │ ├── NamingThreadFactory.java
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt; │ ├── NetUtils.java
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt; │ ├── PaddedAtomicLong.java
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt; │ └── ValuedEnum.java
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt; └── worker
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;27&lt;/span&gt;&lt;span class="cl"&gt; ├── DisposableWorkerIdAssigner.java - 用完即弃的 WorkerId 分配器 (C)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;28&lt;/span&gt;&lt;span class="cl"&gt; ├── WorkerIdAssigner.java - WorkerId 分配器 (I)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;29&lt;/span&gt;&lt;span class="cl"&gt; ├── WorkerNodeType.java - 工作节点类型 (E)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;30&lt;/span&gt;&lt;span class="cl"&gt; ├── dao
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;31&lt;/span&gt;&lt;span class="cl"&gt; │ └── WorkerNodeDAO.java - MyBatis Mapper
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;32&lt;/span&gt;&lt;span class="cl"&gt; └── entity
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;33&lt;/span&gt;&lt;span class="cl"&gt; └── WorkerNodeEntity.java - MyBatis Entity
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="defaultuidgenerator"&gt;&lt;a href="#defaultuidgenerator" class="header-anchor"&gt;&lt;/a&gt;DefaultUidGenerator
&lt;/h3&gt;&lt;p&gt;UidGenerator 在应用中是以 Spring 组件的形式提供服务，&lt;code&gt;DefaultUidGenerator&lt;/code&gt; 提供了最简单的 Snowflake 式的生成模式，没有使用任何缓存来预存 UID，在需要生成 ID 的时候即时进行计算。&lt;/p&gt;
&lt;p&gt;所以我们结合源码来串一下最简单的默认生成模式的流程。&lt;/p&gt;
&lt;p&gt;首先引入&lt;code&gt;DefaultUidGenerator&lt;/code&gt;配置&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- DefaultUidGenerator --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;bean&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;defaultUidGenerator&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;com.baidu.fsg.uid.impl.DefaultUidGenerator&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;lazy-init&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;false&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;property&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;workerIdAssigner&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;disposableWorkerIdAssigner&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;&amp;lt;!--当前时间位数，前文图上是 28 位，这里设置 29 位 --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;property&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;timeBits&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;29&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;&amp;lt;!-- 机器 id 位数，设置 21 位 --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;property&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;workerBits&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;21&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;&amp;lt;!-- 每秒下的并发序列位数，13 bits 可支持每秒 8192 个并发 --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;property&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;seqBits&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;13&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;&amp;lt;!-- 相对于时间基点&amp;#34;2016-09-20&amp;#34;的增量值，单位是秒 --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;&amp;lt;!-- 用于计算时间戳的差值（当前时间减去固定的开始时间），这样可以使产生的 ID 从更小值开始--&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;property&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;epochStr&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;2016-09-20&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;bean&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- 用完即弃的 WorkerIdAssigner，依赖 DB 操作 --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;bean&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;disposableWorkerIdAssigner&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;com.baidu.fsg.uid.worker.DisposableWorkerIdAssigner&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;配置文件中的配置我都作了注释，这里重点说两个属性：&lt;/p&gt;
&lt;p&gt;epochStr&lt;/p&gt;
&lt;p&gt;是给一个过去时间的字符串，作为时间基点，比如&amp;quot;2016-09-20&amp;quot;，用于计算时间戳的差值（当前时间减去固定的开始时间），这样可以使产生的 ID 从更小值开始。这点从以下的两段源码可以看出来：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;setEpochStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;epochStr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StringUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isNotBlank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epochStr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;epochStr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;epochStr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;epochSeconds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TimeUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MILLISECONDS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parseByDayPattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epochStr&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * Get current second
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; */&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getCurrentSecond&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;currentSecond&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TimeUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MILLISECONDS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentTimeMillis&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentSecond&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;epochSeconds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bitsAllocator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMaxDeltaSeconds&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UidGenerateException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Timestamp bits is exhausted. Refusing UID generate. Now: &amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;currentSecond&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;currentSecond&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;disposableWorkerIdAssigner&lt;/p&gt;
&lt;p&gt;Worker ID 分配器，用于为每个工作机器分配一个唯一的 ID，目前来说是用完即弃，在初始化 Bean 的时候会自动向 MySQL 中插入一条关于该服务的启动信息，待 MySQL 返回其自增 ID 之后，使用该 ID 作为工作机器 ID 并柔和到 UID 的生成当中。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;@Transactional
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;public long assignWorkerId() {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt; // build worker node entity
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt; WorkerNodeEntity workerNodeEntity = buildWorkerNode();
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; // add worker node for new (ignore the same IP + PORT)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; workerNodeDAO.addWorkerNode(workerNodeEntity);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; LOGGER.info(&amp;#34;Add worker node:&amp;#34; + workerNodeEntity);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; return workerNodeEntity.getId();
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;buildWorkerNode() 为获取该启动服务的信息，兼容 Docker 服务。但要注意，无论是 docker 还是用 k8s，需要添加相关的环境变量 env 在配置文件中以便程序能够获取到。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt; /** Environment param keys 主要是端口和 host */
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt; private static final String ENV_KEY_HOST = &amp;#34;JPAAS_HOST&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; private static final String ENV_KEY_PORT = &amp;#34;JPAAS_HTTP_PORT&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;核心方法&lt;/p&gt;
&lt;p&gt;介绍完上面这些，我们来看下 &lt;code&gt;defaultUidGenerator&lt;/code&gt; 生成 ID 的核心方法（注意这个方法是&lt;code&gt;同步&lt;/code&gt;方法）&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt; protected synchronized long nextId() {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt; long currentSecond = getCurrentSecond();
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; // 时钟向后移动，拒绝生成 id （解决时钟回拨问题）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; if (currentSecond &amp;lt; lastSecond) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; long refusedSeconds = lastSecond - currentSecond;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; throw new UidGenerateException(&amp;#34;Clock moved backwards. Refusing for %d seconds&amp;#34;, refusedSeconds);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; // 如果是在同一秒内，那么增加 sequence
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; if (currentSecond == lastSecond) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; sequence = (sequence + 1) &amp;amp; bitsAllocator.getMaxSequence();
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt; // 如果超过了最大值，那么需要等到下一秒再进行生成
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt; if (sequence == 0) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt; currentSecond = getNextSecond(lastSecond);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt; // 不同秒的情况下，sequence 重新从 0 开始计数
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt; } else {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt; sequence = 0L;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt; lastSecond = currentSecond;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt; // Allocate bits for UID
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt; return bitsAllocator.allocate(currentSecond - epochSeconds, workerId, sequence);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到大部分代码用来处理异常情况，比如时钟回拨问题，这里的做法比较简单，就是直接抛出异常。&lt;/p&gt;
&lt;p&gt;最后一行才是根据传入的或计算好的参数进行 ID 的真正分配，通过二进制的移位和或运算得到最终的 long ID 值。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt; public long allocate(long deltaSeconds, long workerId, long sequence) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; return (deltaSeconds &amp;lt;&amp;lt; timestampShift) | (workerId &amp;lt;&amp;lt; workerIdShift) | sequence;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="cacheduidgenerator"&gt;&lt;a href="#cacheduidgenerator" class="header-anchor"&gt;&lt;/a&gt;CachedUidGenerator
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;CachedUidGenerator&lt;/code&gt; 是一个使用 &lt;code&gt;RingBuffer&lt;/code&gt; 预先缓存 UID 的生成器，在初始化时就会填充整个 &lt;code&gt;RingBuffer&lt;/code&gt;，并在 take() 时检测到少于指定的填充阈值之后就会异步地再次填充 &lt;code&gt;RingBuffer&lt;/code&gt;（默认值为 50%），另外可以启动一个定时器周期性检测阈值并及时进行填充。&lt;/p&gt;
&lt;p&gt;RingBuffer&lt;/p&gt;
&lt;p&gt;上文提到 &lt;code&gt;RingBuffer&lt;/code&gt; 是预先缓存 UID 的生成器，我们先看下它的成员变量情况：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt; /** 常量配置 */
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt; private static final int START_POINT = -1;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt; private static final long CAN_PUT_FLAG = 0L;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt; private static final long CAN_TAKE_FLAG = 1L;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; public static final int DEFAULT_PADDING_PERCENT = 50;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; /** RingBuffer 的 slot 的大小，每个 slot 持有一个 UID */
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; private final int bufferSize;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; private final long indexMask;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; /** 存 UID 的数组 */
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; private final long[] slots;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; /** 存放 UID 状态的数组（是否可读或者可写，或是否可填充、是否可消费） */
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; private final PaddedAtomicLong[] flags;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt; /** Tail: 要产生的最后位置序列 */
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt; private final AtomicLong tail = new PaddedAtomicLong(START_POINT);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt; /** Cursor: 要消耗的当前位置序列 */
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt; private final AtomicLong cursor = new PaddedAtomicLong(START_POINT);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt; /** 触发填充缓冲区的阈值 */
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt; private final int paddingThreshold; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt; /** 放置 缓冲区的拒绝策略 拒绝方式为打印日志 */
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt; private RejectedPutBufferHandler rejectedPutHandler = this::discardPutBuffer;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt; /** 获取 缓冲区的拒绝策略 拒绝方式为抛出异常并打印日志 */
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt; private RejectedTakeBufferHandler rejectedTakeHandler = this::exceptionRejectedTakeBuffer; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;27&lt;/span&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;28&lt;/span&gt;&lt;span class="cl"&gt; /** 填充缓冲区的执行者 */
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;29&lt;/span&gt;&lt;span class="cl"&gt; private BufferPaddingExecutor bufferPaddingExecutor;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到 &lt;code&gt;RingBuffer&lt;/code&gt; 内部有两个环形数组，一个用来存放 UID，一个用来存放 UID 的状态，这两个数组的大小都是一样的，也就是 &lt;code&gt;bufferSize&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;slots 用于存放 UID 的 long 类型数组，flags 的用于存放读写标识的 PaddedAtomicLong 类型数组。这什么用 PaddedAtomicLong？上文有提到过&lt;code&gt;伪共享&lt;/code&gt;的概念，这里就是为了解决这个问题，如果对 &lt;code&gt;伪共享&lt;/code&gt; 还不太理解的朋友可以看一下上文的参考链接理解一下。&lt;/p&gt;
&lt;p&gt;简单讲，由于 slots 实质是属于多读少写的变量，所以使用原生类型的收益更高。而 flags 则是会频繁进行写操作，为了避免伪共享问题所以手工进行补齐。&lt;/p&gt;
&lt;p&gt;RingBuffer 构造方法&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt; /**
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt; * 具有缓冲区大小的构造函数，paddingFactor 默认为 {@value #DEFAULT_PADDING_PERCENT}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt; * 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; * @param bufferSize 必须是正数和 2 的幂
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; */
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; public RingBuffer(int bufferSize) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; this(bufferSize, DEFAULT_PADDING_PERCENT);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; /**
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; * 具有缓冲区大小和填充因子的构造函数
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; * 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt; * @param bufferSize 必须是正数和 2 的幂
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt; * @param paddingFactor (0 - 100) 中的百分比。当剩余可用的 UID 数量达到阈值时，将触发填充缓冲区
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt; * Sample: paddingFactor=20, bufferSize=1000 -&amp;gt; threshold=1000 * 20 /100,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt; * 当 tail-cursor&amp;lt;threshold 时将触发填充缓冲区
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt; */
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt; public RingBuffer(int bufferSize, int paddingFactor) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt; // check buffer size is positive &amp;amp; a power of 2; padding factor in (0, 100)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt; Assert.isTrue(bufferSize &amp;gt; 0L, &amp;#34;RingBuffer size must be positive&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt; Assert.isTrue(Integer.bitCount(bufferSize) == 1, &amp;#34;RingBuffer size must be a power of 2&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt; Assert.isTrue(paddingFactor &amp;gt; 0 &amp;amp;&amp;amp; paddingFactor &amp;lt; 100, &amp;#34;RingBuffer size must be positive&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt; this.bufferSize = bufferSize;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt; this.indexMask = bufferSize - 1;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;27&lt;/span&gt;&lt;span class="cl"&gt; this.slots = new long[bufferSize];
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;28&lt;/span&gt;&lt;span class="cl"&gt; this.flags = initFlags(bufferSize);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;29&lt;/span&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;30&lt;/span&gt;&lt;span class="cl"&gt; this.paddingThreshold = bufferSize * paddingFactor / 100;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;31&lt;/span&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;bufferSize 的默认值 ，如果 sequence 是 13 位，那么默认最大值是 8192，且是支持扩容的。&lt;/p&gt;
&lt;p&gt;触发填充缓冲区的阈值也是支持配置的，&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- RingBuffer size 扩容参数，可提高 UID 生成的吞吐量。--&amp;gt;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- 默认：3， 原 bufferSize=8192, 扩容后 bufferSize= 8192 &amp;lt;&amp;lt; 3 = 65536 --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;property&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;boostPower&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;property&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- 指定何时向 RingBuffer 中填充 UID, 取值为百分比 (0, 100), 默认为 50 --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- 举例：bufferSize=1024, paddingFactor=50 -&amp;gt; threshold=1024 * 50 / 100 = 512. --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- 当环上可用 UID 数量 &amp;lt; 512 时，将自动对 RingBuffer 进行填充补全 --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;property&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;paddingFactor&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;50&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;property&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;RingBuffer 的填充和获取&lt;/p&gt;
&lt;p&gt;RingBuffer 的填充和获取操作是线程安全的，但是填充和获取操作的性能会受到 RingBuffer 的大小的影响，先来看下 &lt;code&gt;put&lt;/code&gt; 操作：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt; public synchronized boolean put(long uid) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt; long currentTail = tail.get();
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt; long currentCursor = cursor.get();
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; // 当 tail 追上了 cursor 时，表示 RingBuffer 满了，不能再放了
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; // Tail 不能超过 Cursor，即生产者不能覆盖未消费的 slot。当 Tail 已赶上 curosr，此时可通过 rejectedPutBufferHandler 指定 PutRejectPolicy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; long distance = currentTail - (currentCursor == START_POINT ? 0 : currentCursor);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; if (distance == bufferSize - 1) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; rejectedPutHandler.rejectPutBuffer(this, uid);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; return false;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; // 1. 预检查 flag 是否为 CAN_PUT_FLAG，首次 put 时，currentTail 为-1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt; int nextTailIndex = calSlotIndex(currentTail + 1);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt; if (flags[nextTailIndex].get() != CAN_PUT_FLAG) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt; rejectedPutHandler.rejectPutBuffer(this, uid);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt; return false;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt; // 2. put UID in the next slot
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt; slots[nextTailIndex] = uid;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt; // 3. update next slot&amp;#39; flag to CAN_TAKE_FLAG
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt; flags[nextTailIndex].set(CAN_TAKE_FLAG);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt; // 4. publish tail with sequence increase by one 移动 tail
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt; tail.incrementAndGet();
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;27&lt;/span&gt;&lt;span class="cl"&gt; // 上述操作的原子性由“synchronized”保证。换句话说
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;28&lt;/span&gt;&lt;span class="cl"&gt; // take 操作不能消费我们刚刚放的 UID，直到 tail 移动 (tail.incrementAndGet()) 才可以
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;29&lt;/span&gt;&lt;span class="cl"&gt; return true;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;30&lt;/span&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意 put 方法是 synchronized。再来看下 &lt;code&gt;take&lt;/code&gt; 方法：&lt;/p&gt;
&lt;p&gt;UID 的读取是一个无锁的操作。在获取 UID 之前，还要检查是否达到了 padding 阈值，在另一个线程中会触发 padding buffer 操作，如果没有更多可用的 UID 可以获取，则应用指定的 RejectedTakeBufferHandler&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt; public long take() {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt; // spin get next available cursor
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt; long currentCursor = cursor.get();
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt; // cursor 初始化为-1，现在 cursor 等于 tail，所以初始化时 nextCursor 为-1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; long nextCursor = cursor.updateAndGet(old -&amp;gt; old == tail.get() ? old : old + 1);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; // check for safety consideration, it never occurs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; // 初始化或者全部 UID 耗尽时 nextCursor == currentCursor
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; Assert.isTrue(nextCursor &amp;gt;= currentCursor, &amp;#34;Curosr can&amp;#39;t move back&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; // 如果达到阈值，则以异步模式触发填充
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; long currentTail = tail.get();
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; if (currentTail - nextCursor &amp;lt; paddingThreshold) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt; LOGGER.info(&amp;#34;Reach the padding threshold:{}. tail:{}, cursor:{}, rest:{}&amp;#34;, paddingThreshold, currentTail,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt; nextCursor, currentTail - nextCursor);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt; bufferPaddingExecutor.asyncPadding();
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt; // cursor 追上 tail ，意味着没有更多可用的 UID 可以获取
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt; if (nextCursor == currentCursor) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt; rejectedTakeHandler.rejectTakeBuffer(this);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt; // 1. check next slot flag is CAN_TAKE_FLAG
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt; int nextCursorIndex = calSlotIndex(nextCursor);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt; // 这个位置必须要是可以 TAKE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;27&lt;/span&gt;&lt;span class="cl"&gt; Assert.isTrue(flags[nextCursorIndex].get() == CAN_TAKE_FLAG, &amp;#34;Curosr not in can take status&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;28&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;29&lt;/span&gt;&lt;span class="cl"&gt; // 2. get UID from next slot
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;30&lt;/span&gt;&lt;span class="cl"&gt; // 3. set next slot flag as CAN_PUT_FLAG.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;31&lt;/span&gt;&lt;span class="cl"&gt; long uid = slots[nextCursorIndex];
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;32&lt;/span&gt;&lt;span class="cl"&gt; // 告知 flags 数组这个位置是可以被重用了
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;33&lt;/span&gt;&lt;span class="cl"&gt; flags[nextCursorIndex].set(CAN_PUT_FLAG);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;34&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;35&lt;/span&gt;&lt;span class="cl"&gt; // 注意：步骤 2，3 不能互换。如果我们在获取 slot 的值之前设置 flag，生产者可能会用新的 UID 覆盖 slot，这可能会导致消费者在一个 ring 中 获取 UID 两次
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;36&lt;/span&gt;&lt;span class="cl"&gt; return uid;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;37&lt;/span&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;BufferPaddingExecutor&lt;/p&gt;
&lt;p&gt;默认情况下，slots 被消费大于 50%的时候进行异步填充，这个填充由 BufferPaddingExecutor 所执行的，下面我们马上看看这个执行者的主要代码。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/**
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * Padding buffer fill the slots until to catch the cursor
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; * 该方法被即时填充和定期填充所调用
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; */&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;paddingBuffer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Ready to padding buffer lastSecond:{}. {}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lastSecond&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ringBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// is still running&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 这个是代表填充 executor 在执行，不是 RingBuffer 在执行。避免多个线程同时扩容。&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compareAndSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Padding buffer is still running. {}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ringBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// fill the rest slots until to catch the cursor&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;isFullRingBuffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isFullRingBuffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 填充完指定 SECOND 里面的所有 UID，直至填满&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uidList&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uidProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lastSecond&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incrementAndGet&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uidList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;isFullRingBuffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ringBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isFullRingBuffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;27&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;28&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// not running now&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;29&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compareAndSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;30&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;End to padding buffer lastSecond:{}. {}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lastSecond&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ringBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;31&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;当线程池分发多条线程来执行填充任务的时候，成功抢夺运行状态的线程会真正执行对 RingBuffer 填充，直至全部填满，其他抢夺失败的线程将会直接返回。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;该类还提供定时填充功能，如果有设置开关则会生效，默认不会启用周期性填充&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RIngBuffer 的填充时机有 3 个：CachedUidGenerator 时对 RIngBuffer 初始化、RIngBuffer#take() 时检测达到阈值和周期性填充（如果有打开）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用 RingBuffer 的 UID 生成器&lt;/p&gt;
&lt;p&gt;最后我们看一下利用 CachedUidGenerator 生成 UID 的代码，CachedUidGenerator 继承了 DefaultUidGenerator，实现了 UidGenerator 接口。&lt;/p&gt;
&lt;p&gt;该类在应用中作为 Spring Bean 注入到各个组件中，主要作用是初始化 RingBuffer 和 BufferPaddingExecutor。最重要的方法为 BufferedUidProvider 的提供者，即 lambda 表达式中的 nextIdsForOneSecond(long) 方法。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt; /**
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt; * Get the UIDs in the same specified second under the max sequence
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt; * 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt; * @param currentSecond
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; * @return UID list, size of {@link BitsAllocator#getMaxSequence()} + 1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; */
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; protected List&amp;lt;Long&amp;gt; nextIdsForOneSecond(long currentSecond) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; // Initialize result list size of (max sequence + 1)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; int listSize = (int) bitsAllocator.getMaxSequence() + 1;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; List&amp;lt;Long&amp;gt; uidList = new ArrayList&amp;lt;&amp;gt;(listSize);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; // Allocate the first sequence of the second, the others can be calculated with the offset
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; long firstSeqUid = bitsAllocator.allocate(currentSecond - epochSeconds, workerId, 0L);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt; for (int offset = 0; offset &amp;lt; listSize; offset++) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt; uidList.add(firstSeqUid + offset);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt; return uidList;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;获取 ID 是通过委托 RingBuffer 的 take() 方法达成的&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getUID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ringBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;take&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LOGGER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Generate unique id exception. &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UidGenerateException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里通过时序图再串一下 获取 id 的流程。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2021-11-05-bai-du-uidgenerator-yuan-ma-jie-xi/007-a48ddb68.jpg"&gt;&lt;/p&gt;
&lt;h2 id="几段位运算代码"&gt;&lt;a href="#%e5%87%a0%e6%ae%b5%e4%bd%8d%e8%bf%90%e7%ae%97%e4%bb%a3%e7%a0%81" class="header-anchor"&gt;&lt;/a&gt;几段位运算代码
&lt;/h2&gt;&lt;h3 id="判断是不是-2-的幂"&gt;&lt;a href="#%e5%88%a4%e6%96%ad%e6%98%af%e4%b8%8d%e6%98%af-2-%e7%9a%84%e5%b9%82" class="header-anchor"&gt;&lt;/a&gt;判断是不是 2 的幂
&lt;/h3&gt;&lt;p&gt;利用 bitCount 函数，原理是计算参数传递的 int 值的二进制值有多少个 1，如果只有 1 个，则说明是 2 的幂，否则不是。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;Integer.bitCount(bufferSize) == 1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="根据位数取最大值"&gt;&lt;a href="#%e6%a0%b9%e6%8d%ae%e4%bd%8d%e6%95%b0%e5%8f%96%e6%9c%80%e5%a4%a7%e5%80%bc" class="header-anchor"&gt;&lt;/a&gt;根据位数取最大值
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;// initialize max value
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;this.maxDeltaSeconds = ~(-1L &amp;lt;&amp;lt; timestampBits);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;this.maxWorkerId = ~(-1L &amp;lt;&amp;lt; workerIdBits);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;this.maxSequence = ~(-1L &amp;lt;&amp;lt; sequenceBits);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;比较有意思，是用负 1 的二进制先左移再取反，比如看一下 13 和-1 的二进制值就明白了：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toBinaryString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;1L&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toBinaryString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;1L&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Long&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toBinaryString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;1L&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;输出&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;1111111111111111111111111111111111111111111111111111111111111111&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;7&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;1111111111111111111111111111111111111111111111111110000000000000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;8&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;1111111111111&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="parse-uid"&gt;&lt;a href="#parse-uid" class="header-anchor"&gt;&lt;/a&gt;parse UID
&lt;/h3&gt;&lt;p&gt;与上面的有异曲同之处。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt; // parse UID
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;long sequence = (uid &amp;lt;&amp;lt; (totalBits - sequenceBits)) &amp;gt;&amp;gt;&amp;gt; (totalBits - sequenceBits);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;long workerId = (uid &amp;lt;&amp;lt; (timestampBits + signBits)) &amp;gt;&amp;gt;&amp;gt; (totalBits - workerIdBits);
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;long deltaSeconds = uid &amp;gt;&amp;gt;&amp;gt; (workerIdBits + sequenceBits);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="参考"&gt;&lt;a href="#%e5%8f%82%e8%80%83" class="header-anchor"&gt;&lt;/a&gt;参考
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.cnblogs.com/cyfonly/p/5800758.html" target="_blank" rel="noopener"
 &gt;https://www.cnblogs.com/cyfonly/p/5800758.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://www.semlinker.com/uuid-snowflake/" target="_blank" rel="noopener"
 &gt;http://www.semlinker.com/uuid-snowflake/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://programmer.group/snowflake-algorithm-improved-version-snowflake.html" target="_blank" rel="noopener"
 &gt;https://programmer.group/snowflake-algorithm-improved-version-snowflake.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/baidu/uid-generator/blob/master/README.zh" target="_blank" rel="noopener"
 &gt;https://github.com/baidu/uid-generator/blob/master/README.zh&lt;/a&gt;_cn.md&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="http://blog.chriscs.com/2017/08/02/baidu-uid-generator/" target="_blank" rel="noopener"
 &gt;http://blog.chriscs.com/2017/08/02/baidu-uid-generator/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Spring Cloud 二代组件</title><link>https://xiaobox.github.io/p/2020-07-22-spring-cloud-er-dai-zu-jian/</link><pubDate>Wed, 22 Jul 2020 23:00:00 +0000</pubDate><guid>https://xiaobox.github.io/p/2020-07-22-spring-cloud-er-dai-zu-jian/</guid><description>&lt;img src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-07-22-spring-cloud-er-dai-zu-jian/cover.jpg" alt="Featured image of post Spring Cloud 二代组件" /&gt;&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-07-22-spring-cloud-er-dai-zu-jian/001-5a56e50e.jpg"&gt;&lt;/p&gt;
&lt;h3 id="先来看一下第一代-spring-cloud-的组件"&gt;&lt;a href="#%e5%85%88%e6%9d%a5%e7%9c%8b%e4%b8%80%e4%b8%8b%e7%ac%ac%e4%b8%80%e4%bb%a3-spring-cloud-%e7%9a%84%e7%bb%84%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;先来看一下第一代 spring cloud 的组件
&lt;/h3&gt;&lt;table style="visibility: visible;"&gt;&lt;tbody style="visibility: visible;"&gt;&lt;tr style="visibility: visible;"&gt;&lt;td style="min-width: 90px; font-size: 14px; white-space: normal; border-color: rgb(217, 217, 217); padding: 4px 8px; cursor: default; visibility: visible;"&gt;&lt;p data-lake-id="80194537a7a8305345072ad8f4ea294e_p_0" style="font-size: 15px; color: rgb(64, 64, 64); line-height: 1.74; letter-spacing: 0.008em; outline-style: none; visibility: visible;"&gt;组件名称&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px; font-size: 14px; white-space: normal; border-color: rgb(217, 217, 217); padding: 4px 8px; cursor: default; visibility: visible;" width="168"&gt;&lt;p data-lake-id="fe91734e59baf7bf70096abd557f6c7b" style="font-size: 15px; color: rgb(64, 64, 64); line-height: 1.74; letter-spacing: 0.008em; outline-style: none; visibility: visible;"&gt;功能&lt;/p&gt;&lt;/td&gt;&lt;td rowspan="1" style="vertical-align: top; background-color: rgb(255, 255, 255); color: rgb(64, 64, 64); min-width: 90px; font-size: 14px; white-space: normal; border-color: rgb(217, 217, 217); padding: 4px 8px; cursor: default; visibility: visible;" width="264"&gt;&lt;p data-lake-id="a20d224c568e48b9d67847a2c66a8c01_p_0" style="font-size: 15px; line-height: 1.74; letter-spacing: 0.008em; outline-style: none; visibility: visible;"&gt;描述&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style="visibility: visible;"&gt;&lt;td colspan="1" rowspan="1" style="vertical-align: top; background-color: rgb(255, 255, 255); color: rgb(64, 64, 64); min-width: 90px; font-size: 14px; white-space: normal; border-color: rgb(217, 217, 217); padding: 4px 8px; cursor: default; visibility: visible;"&gt;&lt;span style="color: rgb(26, 26, 26); visibility: visible;"&gt;Eureka&lt;/span&gt;&lt;/td&gt;&lt;td colspan="1" style="vertical-align: top; background-color: rgb(255, 255, 255); color: rgb(64, 64, 64); min-width: 90px; font-size: 14px; white-space: normal; border-color: rgb(217, 217, 217); padding: 4px 8px; cursor: default; visibility: visible;" width="39"&gt;&lt;p data-lake-id="a20d224c568e48b9d67847a2c66a8c01_p_0" style="font-size: 15px; line-height: 1.74; letter-spacing: 0.008em; outline-style: none; visibility: visible;"&gt;服务治理（注册、发现......）&lt;/p&gt;&lt;/td&gt;&lt;td rowspan="1" style="vertical-align: top; background-color: rgb(255, 255, 255); color: rgb(64, 64, 64); min-width: 90px; font-size: 14px; white-space: normal; border-color: rgb(217, 217, 217); padding: 4px 8px; cursor: default; visibility: visible;" width="260"&gt;&lt;br style="visibility: visible;"&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style="visibility: visible;"&gt;&lt;td style="min-width: 90px; font-size: 14px; white-space: normal; border-color: rgb(217, 217, 217); padding: 4px 8px; cursor: default; visibility: visible;"&gt;&lt;p data-lake-id="af68ace328ceba1fee2cc254f0d23cb3" style="font-size: 15px; color: rgb(64, 64, 64); line-height: 1.74; letter-spacing: 0.008em; outline-style: none; visibility: visible;"&gt;&lt;span style="color: rgb(26, 26, 26); visibility: visible;"&gt;Ribbon&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px; font-size: 14px; white-space: normal; border-color: rgb(217, 217, 217); padding: 4px 8px; cursor: default; visibility: visible;" width="39"&gt;&lt;p data-lake-id="ab0729b1373a400ab8ff4f5dc8f7ae48" style="font-size: 15px; color: rgb(64, 64, 64); line-height: 1.74; letter-spacing: 0.008em; outline-style: none; visibility: visible;"&gt;客户端负载均衡器&lt;/p&gt;&lt;/td&gt;&lt;td rowspan="1" style="vertical-align: top; background-color: rgb(255, 255, 255); color: rgb(64, 64, 64); min-width: 90px; font-size: 14px; white-space: normal; border-color: rgb(217, 217, 217); padding: 4px 8px; cursor: default; visibility: visible;" width="260"&gt;&lt;br style="visibility: visible;"&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style="visibility: visible;"&gt;&lt;td style="min-width: 90px; font-size: 14px; white-space: normal; border-color: rgb(217, 217, 217); padding: 4px 8px; cursor: default; visibility: visible;"&gt;&lt;p data-lake-id="ad6208b5d932566be6bdd6d50e6d594b" style="font-size: 15px; color: rgb(64, 64, 64); line-height: 1.74; letter-spacing: 0.008em; outline-style: none; visibility: visible;"&gt;&lt;span style="color: rgb(26, 26, 26); visibility: visible;"&gt;Hystrix&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px; font-size: 14px; white-space: normal; border-color: rgb(217, 217, 217); padding: 4px 8px; cursor: default; visibility: visible;" width="39"&gt;&lt;p data-lake-id="4a2ed31e9a0e01248f12f39ebb8bed27" style="font-size: 15px; color: rgb(64, 64, 64); line-height: 1.74; letter-spacing: 0.008em; outline-style: none; visibility: visible;"&gt;&lt;span style="color: rgb(26, 26, 26); visibility: visible;"&gt;服务之间远程调用时的熔断保护&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td rowspan="1" style="vertical-align: top; background-color: rgb(255, 255, 255); color: rgb(64, 64, 64); min-width: 90px; font-size: 14px; white-space: normal; border-color: rgb(217, 217, 217); padding: 4px 8px; cursor: default; visibility: visible;" width="260"&gt;&lt;p data-lake-id="fb0fe7dea25afd456b161b0cce972d58" style="font-size: 15px; line-height: 1.74; letter-spacing: 0.008em; outline-style: none; visibility: visible;"&gt;Hystrix 的使用主要有三种方式&lt;/p&gt;&lt;ul data-lake-id="9be4695ce9ec75644d1e3011d09abe0a" lake-indent="0" style="padding-left: 23px; font-size: 15px; line-height: 1.74; letter-spacing: 0.008em; outline-style: none; visibility: visible;" class="list-paddingleft-2"&gt;&lt;li style="visibility: visible;"&gt;&lt;p style="visibility: visible;"&gt;HystrixCommand 注解方式&lt;/p&gt;&lt;/li&gt;&lt;li style="visibility: visible;"&gt;&lt;p style="visibility: visible;"&gt;结合 Feign 使用&lt;/p&gt;&lt;/li&gt;&lt;li style="visibility: visible;"&gt;&lt;p style="visibility: visible;"&gt;结合 Zuul 使用&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style="visibility: visible;"&gt;&lt;td style="min-width: 90px; font-size: 14px; white-space: normal; border-color: rgb(217, 217, 217); padding: 4px 8px; cursor: default; visibility: visible;"&gt;&lt;p data-lake-id="621646e47c4e9970932d19464eac24e6" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;&lt;span style="color: #1A1A1A;"&gt;Feign&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;" width="39"&gt;&lt;p data-lake-id="68cd6efa8271dd3be836728eaf6df4d2" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;&lt;span style="color: #1A1A1A;"&gt;通过定义接口的方式直接调用其他服务的 API&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td rowspan="1" style="vertical-align: top;background-color: rgb(255, 255, 255);color: rgb(64, 64, 64);min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;" width="260"&gt;&lt;br&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td rowspan="1" colspan="1" style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;span style="color: #1A1A1A;background-color: #FFFFFF;"&gt;Zuul&lt;/span&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;" width="39"&gt;&lt;p data-lake-id="323ac42010ffdff18e00de789eb09e73" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;服务网关&lt;/p&gt;&lt;/td&gt;&lt;td rowspan="1" style="vertical-align: top;background-color: rgb(255, 255, 255);color: rgb(64, 64, 64);min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;" width="260"&gt;&lt;p data-lake-id="a20d224c568e48b9d67847a2c66a8c01_p_0" style="font-size: 15px;line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;&lt;span style="color: #1A1A1A;"&gt;提供了路由、监控、弹性、安全等服务。Zuul 能够与 Eureka、Ribbon、Hystrix 等组件配合使用。&lt;/span&gt;&lt;span style="color: #1A1A1A;"&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="cbe732ebd9bee93024748ab1f5ea3313" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;&lt;span style="color: #1A1A1A;"&gt;Config&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;" width="39"&gt;&lt;p data-lake-id="a5835af68d3e37f9f6ef3a4cb50cbf9a" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;分布式配置中心组件&lt;/p&gt;&lt;/td&gt;&lt;td rowspan="1" style="vertical-align: top;background-color: rgb(255, 255, 255);color: rgb(64, 64, 64);min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;" width="260"&gt;&lt;br&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="97b1c29d769b577a16de88e4361c7a1d" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;&lt;span style="color: #1A1A1A;"&gt;Sleuth&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;" width="39"&gt;&lt;p data-lake-id="39e0d872f60052b645c8b4856a3178fc" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;&lt;span style="color: #1A1A1A;"&gt;用于请求链路跟踪&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td rowspan="1" style="vertical-align: top;background-color: rgb(255, 255, 255);color: rgb(64, 64, 64);min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;" width="260"&gt;&lt;br&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="8ddb67ade7b70ed588dfb9a3d7abbede" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;&lt;span style="color: #1A1A1A;"&gt;Stream&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;" width="39"&gt;&lt;p data-lake-id="8c5def182ad5e37cd834c21e7294fe18" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;&lt;span style="color: #1A1A1A;"&gt;用来为微服务应用构建消息驱动能力&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td rowspan="1" style="vertical-align: top;background-color: rgb(255, 255, 255);color: rgb(64, 64, 64);min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;" width="260"&gt;&lt;br&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;spring cloud 现在已经是一种标准了，各公司可以基于它的编程模型编写自己的组件 ，比如Netflix、阿里巴巴都有自己的一套通过spring cloud 编程模型开发的分布式服务组件 。&lt;/p&gt;
&lt;p&gt;Spring Cloud Alibaba 主要包含 Sentinel、Nacos、RocketMQ、Dubbo、Seata 等组件。&lt;/p&gt;
&lt;h3 id="spring-cloud-二代组件"&gt;&lt;a href="#spring-cloud-%e4%ba%8c%e4%bb%a3%e7%bb%84%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;Spring Cloud 二代组件
&lt;/h3&gt;&lt;p&gt;二代引入了 Spring Cloud Alibaba&lt;/p&gt;
&lt;table width="720"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="da7adf83307efeab48ba5fb7b7775b7d" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;第一代组件&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="e457b9be34990933dcdd9353415c73a4" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;第二代组件&amp;nbsp;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="f8e82f8d1378d8fcaa5153e3ded3e4a0" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;Eureka&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="6852b9944be304b3db1005b2252168ac" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;Nacos&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="d39342afd49c0d26d1b3de2bd4aca4c9" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;Config&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="9c278c46be437054b308a085fee3b56c" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;Apollo&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="827bc9b52e8a943b56dc20dda2ee38ec" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;Zuul&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="e3db44c2a6997fde52348be2e38950aa" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;spring cloud gateway&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="483b1245c36ddeac58721367dd2f5153" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;&lt;span style="color: #1A1A1A;"&gt;Hystrix&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;td rowspan="1" colspan="1" style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;span style="color: #1A1A1A;background-color: #FFFFFF;"&gt;Sentinel&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id="eureka-vs-nacos"&gt;&lt;a href="#eureka-vs-nacos" class="header-anchor"&gt;&lt;/a&gt;Eureka VS Nacos
&lt;/h3&gt;&lt;p&gt;Eureka 之前官方也宣布了暂停了 2.X 版本的开发，1.X 的版本还会维护。其实对于一般的服务规模，目前的 Eureka 完全够用了。而 Nacos 作为后起之秀，目前更新频率很高，社区也更活跃，使用 Nacos 是一个正确的选择。&lt;/p&gt;
&lt;h3 id="apollo-vs-spring-cloud-config"&gt;&lt;a href="#apollo-vs-spring-cloud-config" class="header-anchor"&gt;&lt;/a&gt;&lt;strong&gt;Apollo VS Spring Cloud Config&lt;/strong&gt;
&lt;/h3&gt;&lt;table width="720"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="08ca20eb36284487001463292f6550f4_p_0" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;功能&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="f699b6228a0ee572bf4fb4d49a92b3a0_p_0" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;&lt;strong&gt;spring cloud config&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="a20d224c568e48b9d67847a2c66a8c01_p_0" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;&lt;strong&gt;apollo&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="75c2d6da1a774b1f4f0fb5a7b075fed8" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;&lt;strong&gt;统一配置管理&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="e2a8391c6568b4f31e4eb38ea57664ee" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;集成Git&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="5c08561bbd4a3fb6e877e47d5253671d" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;自带存储(MySql)&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="182ba122416a12429fb2e403d2e777a5" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;&lt;strong&gt;多环境区分&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="7934d0b36cffa69207615b37b2bcfe88" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;配置指定&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="83300f08c2492de01993efe9270ab6f5" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;配置指定&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="cf9b2b88a75bdb890f556b4d7d40db9d" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;&lt;strong&gt;实时更新&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="4f963073e4510289e9074799a11352e0" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;Bus消息总线&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="5c0d93298c1692596d2fdb9a1fb02d4d" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;Http长连接&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="80d17a3dbad4062088f98bb3533edf5f" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;&lt;strong&gt;定时拉取&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="474c32fea8037eeb75e8d46382da44d9" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;需要自己扩展&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="f06aaaefa43bcb620e4b64ad7d908dcb" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;支持&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="2708ec55469155f6c79911ce3d06d610" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;&lt;strong&gt;权限控制&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="6f61c0e7120c265eb5ff8aef14639243" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;需要Git支持&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="f06aaaefa43bcb620e4b64ad7d908dcb" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;支持&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="68fd0587102991d0caeef295bf1731f0" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;&lt;strong&gt;版本管理&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="2dfad04a50931b57ea8fb2e6479c25ce" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;Git版本&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="ad359c29d8e0a3c76b96bfaeb86bf9c7" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;有直接的版本功能，一键恢复指定版本&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="e126e09ff42a23353723e1a7472a04d8" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;&lt;strong&gt;Web管理后台&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="8994d22a85d63892f61bb4070180752d" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;无&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="b0ba5375ed2cf9f03df93c95d56dbc6c" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;有&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id="zuul-vs-spring-cloud-gateway"&gt;&lt;a href="#zuul-vs-spring-cloud-gateway" class="header-anchor"&gt;&lt;/a&gt;Zuul VS Spring Cloud Gateway
&lt;/h3&gt;&lt;p&gt;在 Spring Cloud Gateway 出现之前，网关都是用 Zuul 构建的，虽然 Netflix 开源了 Zuul2，由于各种原因，官方并没有打算将 Zuul2 集成到 Spring Cloud 体系中。而是自己研发了一个全新的网关 Spring Cloud Gateway，由于 Zuul1 基于 Servlet 构建，使用的是阻塞的 IO，性能并不是很理想。Spring Cloud Gateway 则基于 Spring 5、Spring boot 2 和 Reactor 构建，使用 Netty 作为运行时环境，比较完美的支持异步非阻塞编程。&lt;/p&gt;
&lt;p&gt;官方提供的压测报告显示 Spring Cloud Gateway 的性能是 Zuul 的 1.5 倍，Spring Cloud Gateway 刚出不久，稳定性有待验证，主要是缺乏大规模流量的验证，而 Zuul 开源的时间较长，同时在 Netflix 内部经过了大规模流量的验证，比较稳定。长期发展来说，Spring Cloud Gateway 的优势比较大，毕竟官方主推。&lt;/p&gt;
&lt;h3 id="hystrix-vs-sentinel"&gt;&lt;a href="#hystrix-vs-sentinel" class="header-anchor"&gt;&lt;/a&gt;Hystrix VS Sentinel
&lt;/h3&gt;&lt;p&gt;Hystrix 替换成了 Sentinel，Hystrix 也停止了开发，这个时候 Spring Cloud Alibaba 中的 Sentinel 的优势就很明显了，Sentinel 支持多样化的流量控制，熔断降级等功能，完全可以替代 Hystrix。&lt;/p&gt;
&lt;h3 id="其他"&gt;&lt;a href="#%e5%85%b6%e4%bb%96" class="header-anchor"&gt;&lt;/a&gt;其他
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;分布式事务：Seata&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;消息队列: RocketMQ&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;调用链监控：Apache Skywalking&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;日志查询： ELK&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;指标监控： Prometheus&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分布式缓存: Redis&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分布式定时任务：XXL-JOB&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="整体架构组件"&gt;&lt;a href="#%e6%95%b4%e4%bd%93%e6%9e%b6%e6%9e%84%e7%bb%84%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;整体架构组件
&lt;/h3&gt;&lt;p&gt;基于以上，如果我来设计系统架构，那么将用以下组件&lt;/p&gt;
&lt;table width="720"&gt;&lt;tbody&gt;&lt;tr style="height: 33px;"&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="d3020c672ae2ee43a925116d0a18ffd9" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;组件&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="debb046a77ddc13e221bc1724a743969" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;功能&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style="height: 33px;"&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="2767527654b770a2a13e0bf0059e4e8f" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;Nacos&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="a65adfa2370fc37d81315d1ae112ba5b" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;服务注册中心&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style="height: 33px;"&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="9b80dc3201e1f4080535a76ffffe1bba" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;Apollo&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="e9a6a6fd67f072926c705fad87fb2274" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;分布式配置中心&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style="height: 33px;"&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="c1ab16fc16f20cef8a5d0dd70665328e" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;XXL-JOB&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="230863fde75ba47098ac62b2221c30c1" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;分布式定时任务中心&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style="height: 33px;"&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="f0e24ed067468681b2726d7c66480f3d" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;SpringBoot&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="eaf54c95b04b175aead6059ae0002c27" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;微服务组件&amp;nbsp;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style="height: 33px;"&gt;&lt;td rowspan="1" colspan="1" style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="a20d224c568e48b9d67847a2c66a8c01_p_0" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;Sentinel&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="d5dd9fe9869f8e04b7ce1c635f048d34" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;服务熔断限流组件&amp;nbsp;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style="height: 33px;"&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="dee654565952baa59f2cda14635e133b" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;Spring Cloud Gateway&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="fc17c84741cef5cdfbbdf6f1de40d265" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;微服务网关&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style="height: 33px;"&gt;&lt;td colspan="1" style="vertical-align: top;background-color: rgb(255, 255, 255);color: rgb(64, 64, 64);min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="a20d224c568e48b9d67847a2c66a8c01_p_0" style="font-size: 15px;line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;Spring Cloud OpenFeign&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" style="vertical-align: top;background-color: rgb(255, 255, 255);color: rgb(64, 64, 64);min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="a20d224c568e48b9d67847a2c66a8c01_p_0" style="font-size: 15px;line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;服务通信调用&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style="height: 33px;"&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="88ed8a1c6076d595d8db642b877bc547" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;Seata&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="f0d95f76725f101effc49be3c95bb717" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;分布式事务&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style="height: 33px;"&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="7bae3ec8c7394383a9e4d08db54acc7a" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;RocketMQ&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="a4799f138db5b969d2fbf173767f0551" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;消息队列&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style="height: 33px;"&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="a84649f09678ab257425d2ffd241b210" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;Skywalking&lt;/p&gt;&lt;/td&gt;&lt;td style="min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="091839649de39e41dbf04bc42026a513" style="font-size: 15px;color: rgb(64, 64, 64);line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;服务调用链监控系统&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style="height: 33px;"&gt;&lt;td colspan="1" style="vertical-align: top;background-color: rgb(255, 255, 255);color: rgb(64, 64, 64);min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="a20d224c568e48b9d67847a2c66a8c01_p_0" style="font-size: 15px;line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;Redis&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" style="vertical-align: top;background-color: rgb(255, 255, 255);color: rgb(64, 64, 64);min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="a20d224c568e48b9d67847a2c66a8c01_p_0" style="font-size: 15px;line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;分布式缓存&amp;nbsp;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style="height: 33px;"&gt;&lt;td colspan="1" style="vertical-align: top;background-color: rgb(255, 255, 255);color: rgb(64, 64, 64);min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="a20d224c568e48b9d67847a2c66a8c01_p_0" style="font-size: 15px;line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;ELK&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" style="vertical-align: top;background-color: rgb(255, 255, 255);color: rgb(64, 64, 64);min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="a20d224c568e48b9d67847a2c66a8c01_p_0" style="font-size: 15px;line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;日志收集、查询系统&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style="height: 33px;"&gt;&lt;td colspan="1" style="vertical-align: top;background-color: rgb(255, 255, 255);color: rgb(64, 64, 64);min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="a20d224c568e48b9d67847a2c66a8c01_p_0" style="font-size: 15px;line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;Prometheus&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" style="vertical-align: top;background-color: rgb(255, 255, 255);color: rgb(64, 64, 64);min-width: 90px;font-size: 14px;white-space: normal;border-color: rgb(217, 217, 217);padding: 4px 8px;cursor: default;"&gt;&lt;p data-lake-id="a20d224c568e48b9d67847a2c66a8c01_p_0" style="font-size: 15px;line-height: 1.74;letter-spacing: 0.008em;outline-style: none;"&gt;Metrics指标监控系统&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;此外，微服务集群是以容器的方式部署的，用K8S进行docker集群管理。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-07-22-spring-cloud-er-dai-zu-jian/002-3a0d7d8f.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://pub-f29bf2b53160470c9a85250116509a24.r2.dev/post/2020-07-22-spring-cloud-er-dai-zu-jian/003-42df053f.jpg"&gt;&lt;/p&gt;
&lt;p&gt;关注公众号 获取更多精彩内容&lt;/p&gt;</description></item></channel></rss>