<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>小生说大声讲</title><link href="https://www.chenxiaosheng.com/" rel="alternate"/><link href="https://www.chenxiaosheng.com/feeds/all.atom.xml" rel="self"/><id>https://www.chenxiaosheng.com/</id><updated>2026-06-06T15:00:00+08:00</updated><entry><title>我只是想打开一个 Markdown 文件看一眼——于是 vibe coding 了一个编辑器</title><link href="https://www.chenxiaosheng.com/posts/2026-06-06/markdown2-native-markdown-editor.html" rel="alternate"/><published>2026-06-06T15:00:00+08:00</published><updated>2026-06-06T15:00:00+08:00</updated><author><name>小生说大声讲</name></author><id>tag:www.chenxiaosheng.com,2026-06-06:/posts/2026-06-06/markdown2-native-markdown-editor.html</id><summary type="html">&lt;p&gt;我的需求小到有点可笑：在 macOS 上随手双击打开一个 Markdown 文件看一眼，能顺手改两笔就更好。可挑遍主流编辑器，要么收费、要么是 Electron 套壳的庞然大物、要么年久失修、要么三天两头更新就崩。索性用 SwiftUI + AppKit 自己 vibe coding 了一个原生轻量的 Markdown2，本文记录这趟从"挑不到"到"自己造"的心路，以及途中那些有意思的坑。&lt;/p&gt;</summary><content type="html">&lt;h2&gt;一个小到可笑的需求&lt;/h2&gt;
&lt;p&gt;先说清楚我到底想要什么，因为这决定了后面所有的取舍：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我只是想在 macOS 上&lt;strong&gt;随手打开一个 Markdown 文件看一眼&lt;/strong&gt;，如果能顺便改两笔就更好——仅此而已。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我不需要笔记库（vault）、不需要双向链接、不需要标签系统、不需要文件树、不需要发布到博客的一键流程、不需要 30 个主题。这些功能不是不好，而是它们都在解决"管理大量笔记"的问题，而我的问题是"&lt;strong&gt;此刻&lt;/strong&gt;打开&lt;strong&gt;这一个&lt;/strong&gt;文件"。&lt;/p&gt;
&lt;p&gt;这个需求小到，理论上应该有一大把现成软件能完美满足。但真当我一个个试过去，发现没有一个让我舒服的。&lt;/p&gt;
&lt;h2&gt;我试过的那些编辑器，以及为什么都不行&lt;/h2&gt;
&lt;p&gt;把候选者摊开，按我最在意的几个维度排一张表：&lt;/p&gt;
&lt;table style="width: 100%;"&gt;
    &lt;tr&gt;
        &lt;th&gt;编辑器&lt;/th&gt;
        &lt;th&gt;实现方式&lt;/th&gt;
        &lt;th&gt;价格&lt;/th&gt;
        &lt;th&gt;维护状态&lt;/th&gt;
        &lt;th&gt;我的槽点&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;Typora&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;Electron / CEF&lt;/td&gt;
        &lt;td&gt;付费&lt;/td&gt;
        &lt;td&gt;活跃&lt;/td&gt;
        &lt;td&gt;体验确实是标杆，但收费 + Electron 套壳，进程动辄几百 MB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;MacDown&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;原生 WebView&lt;/td&gt;
        &lt;td&gt;免费开源&lt;/td&gt;
        &lt;td&gt;年久失修&lt;/td&gt;
        &lt;td&gt;老牌良心货，但更新近乎停滞，对新语法支持滞后&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;MacDown3000&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;MacDown 衍生&lt;/td&gt;
        &lt;td&gt;免费&lt;/td&gt;
        &lt;td&gt;不稳定&lt;/td&gt;
        &lt;td&gt;每次跟着更新就容易"罢工"，可靠性堪忧&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;Mark Text&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;Electron&lt;/td&gt;
        &lt;td&gt;免费开源&lt;/td&gt;
        &lt;td&gt;基本停更&lt;/td&gt;
        &lt;td&gt;曾经很惊艳，如今 issue 堆积、长期没有新版&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;Obsidian&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;Electron&lt;/td&gt;
        &lt;td&gt;个人免费&lt;/td&gt;
        &lt;td&gt;活跃&lt;/td&gt;
        &lt;td&gt;强大但"重"，是知识库工具，为"打开一个文件"杀鸡用牛刀&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;iA Writer&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;原生&lt;/td&gt;
        &lt;td&gt;付费&lt;/td&gt;
        &lt;td&gt;活跃&lt;/td&gt;
        &lt;td&gt;原生、极简、很优雅，但要付费，且偏纯写作&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;Marked 2&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;原生&lt;/td&gt;
        &lt;td&gt;付费&lt;/td&gt;
        &lt;td&gt;活跃&lt;/td&gt;
        &lt;td&gt;只读预览器，不能编辑，定位和我的需求错位&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;VS Code&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;Electron&lt;/td&gt;
        &lt;td&gt;免费&lt;/td&gt;
        &lt;td&gt;活跃&lt;/td&gt;
        &lt;td&gt;它是 IDE，为看一个 md 启动整个编辑器，心理负担太大&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;把这张表看下来，会发现它们恰好踩满了我的四条雷区：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;要么收费&lt;/strong&gt;——Typora、iA Writer、Marked 2，体验好但要掏钱，而我的需求廉价到不值得为它付费；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;要么是 Electron 套壳&lt;/strong&gt;——Mark Text、Obsidian、VS Code，随便开一个进程就吃掉几百 MB 内存，启动还有可感知的延迟，跟"随手看一眼"的轻快感背道而驰；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;要么年久失修&lt;/strong&gt;——MacDown 这样的老牌开源货，代码还在，但维护近乎停摆，新语法跟不上；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;要么不稳定&lt;/strong&gt;——MacDown 衍生出来的 MacDown3000，我用得最闹心，常常一更新就不能好好工作。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最让我难受的是第 2 条。Markdown 这种纯文本格式，本应是最轻量的东西，结果主流方案却普遍建立在一整个浏览器内核之上。为了渲染几段文字和代码块，背一个 Chromium，怎么想都不对劲。&lt;/p&gt;
&lt;h2&gt;那就自己写一个吧&lt;/h2&gt;
&lt;p&gt;既然挑不到，那就自己造。正好赶上 AI 辅助编程（大家戏称 "vibe coding"）已经足够顺手，我决定亲自下场，做一个完全贴合自己需求的编辑器。给它起名 &lt;strong&gt;Markdown2&lt;/strong&gt;——"Markdown Editor &lt;strong&gt;Too&lt;/strong&gt;"，一个不太正经的双关：既是"另一个 Markdown 编辑器"，也是"我也想要一个 Markdown 编辑器"。&lt;/p&gt;
&lt;p&gt;立项时我给自己定了几条死规矩，全部是对前面四条雷区的正面回应：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原生，不用 Electron&lt;/strong&gt;——用 SwiftUI + AppKit，做成真正的 macOS app，启动快、内存小；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;轻量，专注阅读/编辑&lt;/strong&gt;——单窗口，只有"编写"和"阅读"两种模式，不做文件管理、不做笔记库；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;离线，不依赖网络&lt;/strong&gt;——公式、图表的渲染资源全部内置打包，断网照样工作；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开源免费&lt;/strong&gt;——MIT 协议，自己用得放心，也省得有人重蹈我"挑不到"的覆辙。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;技术栈选 Swift 而不是继续套 Electron，理由很直接：我想要的就是"原生的轻"。文本编辑这件事，AppKit 的 &lt;code&gt;NSTextView&lt;/code&gt; 经过几十年打磨，底子比任何 Web 富文本都扎实；外层用 SwiftUI 搭界面和状态管理，写起来又足够现代。两者配合，正好是"原生底座 + 现代外壳"。&lt;/p&gt;
&lt;p&gt;整个项目用 Swift Package Manager 组织，拆成三层：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MD2Core&lt;/code&gt;——纯逻辑层，Markdown 解析、渲染成 HTML、大纲提取、语法高亮、文档统计，不碰 UI，方便写单元测试；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MD2AppSupport&lt;/code&gt;——承载启动激活这类与 AppKit 生命周期强相关的辅助逻辑；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MD2App&lt;/code&gt;——SwiftUI/AppKit 的应用层，窗口、编辑器、预览、设置都在这。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;逻辑和界面分层之后，核心的解析渲染能力是可以脱离 GUI 单独测试的，这让"vibe coding"产出的代码不至于变成一团没法验证的浆糊。&lt;/p&gt;
&lt;h2&gt;途中那些有意思的坑&lt;/h2&gt;
&lt;p&gt;vibe coding 不是把需求丢给 AI 就万事大吉，真正花时间的，永远是那些"看起来该工作却不工作"的细节。挑几个印象最深的讲讲——前两个是早期的小坑，后两个则是真正需要先想清楚、再让 AI 动手的复杂需求。&lt;/p&gt;
&lt;h3&gt;坑一：&lt;code&gt;swift run&lt;/code&gt; 启动后，窗口躲到了别人后面&lt;/h3&gt;
&lt;p&gt;开发阶段我习惯用 &lt;code&gt;swift run Markdown2&lt;/code&gt; 直接跑。结果发现一个诡异现象：app 进程确实起来了，但&lt;strong&gt;窗口总是藏在当前 app 的后面&lt;/strong&gt;，前台焦点还赖在终端/编辑器上，等于这 app 没法正常用。&lt;/p&gt;
&lt;p&gt;排查下来，根因有点意思：SwiftPM 跑出来的是一个&lt;strong&gt;裸的 Mach-O 可执行文件&lt;/strong&gt;，而不是被 LaunchServices 管理的 &lt;code&gt;.app&lt;/code&gt; bundle。macOS 对"裸进程"和"正经 app"的窗口前台化待遇是不一样的——从裸进程里直接调 AppKit 的激活 API，并不足以可靠地把窗口抢到最前面。&lt;/p&gt;
&lt;p&gt;解决办法是加了一段"直接启动引导"（direct-launch bootstrap）：当 Markdown2 发现自己不是在 &lt;code&gt;.app&lt;/code&gt; 里运行时，就在 &lt;code&gt;.build&lt;/code&gt; 目录下临时合成一个 &lt;code&gt;Markdown2.app&lt;/code&gt;，再通过 &lt;code&gt;NSWorkspace&lt;/code&gt; 带激活地把这个 bundle 打开，然后原始的裸进程退出。被 bundle 拉起来的那个进程会跳过引导逻辑，正常进入主流程。后来又发现 SwiftUI 的 &lt;code&gt;WindowGroup&lt;/code&gt; 在这条特殊启动路径下，偶尔会进了 &lt;code&gt;applicationDidFinishLaunching&lt;/code&gt; 却没真正建出可用的初始窗口，于是干脆在 &lt;code&gt;AppDelegate&lt;/code&gt; 里&lt;strong&gt;显式创建主 &lt;code&gt;NSWindow&lt;/code&gt;&lt;/strong&gt;，把 SwiftUI 的 &lt;code&gt;ContentView&lt;/code&gt; 塞进去托管，再加上几次延迟激活重试兜底。&lt;/p&gt;
&lt;p&gt;这个坑我专门写了回归测试，验证激活策略提升、窗口层级排序、以及对"迟到窗口"的延迟重试。它很典型地说明了一件事：&lt;strong&gt;AI 能飞快写出主干，但操作系统层面的边角行为，还是得人去抠&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;坑二：公式和图表，到底要不要联网&lt;/h3&gt;
&lt;p&gt;很多 Markdown 编辑器渲染数学公式和图表时，是去 CDN 拉 KaTeX/Mermaid 这些前端库的。但这跟我"断网也能看"的原则冲突——很多时候我打开一个 md，恰恰是在没网或不想联网的场景。&lt;/p&gt;
&lt;p&gt;于是我把渲染资源全部&lt;strong&gt;内置打包进 app&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数学公式&lt;/strong&gt;用内置的 &lt;a href="https://katex.org/"&gt;KaTeX&lt;/a&gt;，行内 &lt;code&gt;$...$&lt;/code&gt; 和独占一行的 &lt;code&gt;$$...$$&lt;/code&gt; 都支持，还带上了 mhchem 扩展，能写化学式 &lt;code&gt;\ce{H2SO4}&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;图表&lt;/strong&gt;支持 &lt;code&gt;mermaid&lt;/code&gt;、&lt;code&gt;flow&lt;/code&gt;（&lt;a href="https://flowchart.js.org/"&gt;flowchart.js&lt;/a&gt;）和 &lt;code&gt;sequence&lt;/code&gt;（&lt;a href="https://bramp.github.io/js-sequence-diagrams/"&gt;js-sequence-diagrams&lt;/a&gt;），引擎全部离线内置。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;代价是 app 体积大了一点，但换来"任何时候、任何网络环境下打开都一致"，我觉得非常值。顺带一提，这两块功能我是用 spec-driven 的方式做的——先写 proposal/design/spec，再让 AI 照着实现，archive 里留着 &lt;code&gt;add-math-support&lt;/code&gt; 和 &lt;code&gt;add-diagram-rendering&lt;/code&gt; 两个变更记录。给 vibe coding 套上一层规格约束，产出的可控性明显高很多。后面两个更复杂的需求，更是全靠这套流程才没翻车。&lt;/p&gt;
&lt;h3&gt;坑三：模式切换后，怎么还"停在原地"&lt;/h3&gt;
&lt;p&gt;这是个用着用着就越来越忍不了的体验问题：在编写/阅读两种模式之间切换时，视口&lt;strong&gt;总是弹回文档顶部&lt;/strong&gt;。文档但凡比一屏长，每切一次就得重新滚回刚才看的地方，读改循环被打断得很烦。&lt;/p&gt;
&lt;p&gt;听起来像"记一下滚动位置"这么简单，真做起来才发现卡在一个根本性的错位上：&lt;strong&gt;编辑器和预览是两套完全不同的坐标系&lt;/strong&gt;。编辑器认的是「源码第几行」，预览是一个 &lt;code&gt;WKWebView&lt;/code&gt;，认的是「DOM 元素的像素滚动偏移」。我"刚才在第 180 行"，翻译成预览里该滚到哪个像素，并没有现成的换算。&lt;/p&gt;
&lt;p&gt;破局点是文档大纲：每个 &lt;code&gt;Heading&lt;/code&gt; 节点&lt;strong&gt;同时带着源码 &lt;code&gt;line&lt;/code&gt; 和 HTML 元素 &lt;code&gt;id&lt;/code&gt;&lt;/strong&gt;——它天然就是横跨两套坐标系的那座桥。于是策略定为：切换时锚定到「光标/视口上方最近的那个标题」，按&lt;strong&gt;章节粒度&lt;/strong&gt;对齐，不追求像素级或光标级精确——目标是"落回文档的同一部分"，而不是分毫不差。没有标题的文档、或标题之前的内容，则退化成按滚动比例（scroll-fraction）兜底，&lt;strong&gt;绝不静默跳回顶部&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;真正棘手的是异步：&lt;code&gt;WKWebView&lt;/code&gt; 的滚动位置只能通过 &lt;code&gt;evaluateJavaScript&lt;/code&gt; 回调&lt;strong&gt;异步读取&lt;/strong&gt;，而 SwiftUI 的模式切换是同步发生的、旧视图瞬间就被销毁。所以根本不能"在切换那一刻才去问预览滚到哪了"——得在用户滚动时就&lt;strong&gt;持续把「当前视口顶部是哪个标题」缓存下来&lt;/strong&gt;，等切换真正发生时直接拿现成的值用。我把纯粹的「行号↔标题」解析逻辑（比如"找出 &lt;code&gt;line&lt;/code&gt; 不超过某行的最后一个标题"）抽进 &lt;code&gt;MD2Core&lt;/code&gt; 做成纯函数，这样不依赖跑起来的 WebView 就能单测。&lt;/p&gt;
&lt;p&gt;这种需求，是没法一句话丢给 AI 的。得先把"用什么做锚、锚在什么粒度、异步怎么规避、没锚点时怎么兜底"这几件事一条条想清楚、写进 spec，AI 才可能写对——否则它大概率给你一个"在简单情况下能跑、一遇到长文档和异步就乱套"的版本。&lt;/p&gt;
&lt;h3&gt;坑四：藏不住的图表"闪一下"&lt;/h3&gt;
&lt;p&gt;这个 bug 是做坑三时&lt;strong&gt;顺手揪出来的&lt;/strong&gt;：调试滚动位置时我注意到，预览加载完之后大约一秒，Mermaid 图表会肉眼可见地"闪"一下——先显示一段原始代码文本，然后才被引擎替换成渲染好的 SVG。&lt;/p&gt;
&lt;p&gt;根因有两层。其一，图表引擎是&lt;strong&gt;异步&lt;/strong&gt;渲染的，在它 resolve 之前，DOM 里就摆着原始源码当占位文本给读者看到了；其二，前面提过预览的 &lt;code&gt;WKWebView&lt;/code&gt; 每次切换模式都会&lt;strong&gt;重建&lt;/strong&gt;，所以这个闪烁不只第一次出现，而是&lt;strong&gt;每切一次复发一次&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;修法是给占位状态做文章：源码仍然留在 DOM 里供引擎读取，但用 CSS &lt;strong&gt;不再把它当正常文本显示&lt;/strong&gt;；等引擎渲染完成，再加一个约 120ms 的淡入，把生硬的"代码→图"硬切变成柔和过渡；同时给占位块预留一点 &lt;code&gt;min-height&lt;/code&gt;，减少 SVG 撑开时的布局跳动。这里最容易踩的雷是&lt;strong&gt;别把错误兜底搞丢了&lt;/strong&gt;——解析失败时仍然要把原始源码显示出来，绝不能让一个写错的图表变成一片空白。所以"渲染成功才隐藏、失败要回显"这条岔路必须在 spec 里写死，测试也要专门覆盖错误路径仍然可见。&lt;/p&gt;
&lt;p&gt;一个需求的调试过程牵出另一个隐藏问题，再各自立项、写 spec、补回归测试——这才是真实软件演进的样子，也是为什么我越来越离不开这套"先规格、后实现"的节奏。&lt;/p&gt;
&lt;h2&gt;现在的 Markdown2 长什么样&lt;/h2&gt;
&lt;p&gt;迭代到现在，它已经能覆盖我日常的全部需求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;单窗口、双模式&lt;/strong&gt;：一个主界面，&lt;code&gt;Esc&lt;/code&gt; 从编辑切到预览，预览里 &lt;code&gt;Cmd+双击&lt;/code&gt; 切回编辑，手不离键，且切换时停留在同一章节、不会弹回顶部；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多窗口&lt;/strong&gt;：每个文档独立开窗，从 Finder 双击打开时会复用那些还没动过的空白窗口，不会越开越乱；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;大纲侧栏&lt;/strong&gt;：按标题自动生成，长文档导航很顺；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动保存 + 关闭确认&lt;/strong&gt;：已保存的文档防抖自动保存，有未保存改动时关窗/退出会提示；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态栏&lt;/strong&gt;：字数、字符数、行数、预计阅读时间一眼可见；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;够用的 Markdown 渲染&lt;/strong&gt;：标题、强调、链接、图片、引用、各类列表、GFM 表格、带语法高亮的围栏代码（Python/Go/Rust/Swift 等十来种语言）、YAML front matter、&lt;code&gt;[TOC]&lt;/code&gt;，外加前面说的离线公式与图表；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文件类型关联&lt;/strong&gt;：打包后把 &lt;code&gt;.md&lt;/code&gt;/&lt;code&gt;.markdown&lt;/code&gt; 注册给自己，双击直达。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它&lt;strong&gt;不做&lt;/strong&gt;的事同样清晰：脚注、PDF/DOCX 导出、图片上传拖拽、打字机模式、主题管理、笔记库——这些都不在"随手打开看一眼"的射程内，我刻意没碰。约束自己不做什么，往往比堆功能更难，但这恰恰是我对那些"功能太复杂"的编辑器最大的不满，不能自己又掉进同一个坑。&lt;/p&gt;
&lt;h2&gt;一点心路体会&lt;/h2&gt;
&lt;p&gt;回头看这趟，从"挑不到一个趁手的 Markdown 编辑器"到"两天攒出一个自己用着舒服的原生 app"，最大的感受有两条。&lt;/p&gt;
&lt;p&gt;一是 &lt;strong&gt;AI 把"自己造一个轮子"的门槛拉得很低了&lt;/strong&gt;。放在以前，为了这么个小需求去啃 AppKit、SwiftUI、KaTeX 内嵌，光是查文档就能劝退我；现在 vibe coding 让我能把精力集中在"我到底想要什么、哪里还不对"上，而不是卡在 API 细节里。&lt;/p&gt;
&lt;p&gt;二是 &lt;strong&gt;AI 替代不了"品味"和"较真"&lt;/strong&gt;。它能秒出一个能跑的版本，但"窗口为什么躲到后面"这种坑、"要不要联网渲染"这种取舍、"克制住不加哪些功能"这种判断，依然得人来定。工具越强，"想清楚自己要什么"反而越值钱。&lt;/p&gt;
&lt;p&gt;代码已经开源在 GitHub（&lt;a href="https://github.com/stutiredboy/Markdown2"&gt;stutiredboy/Markdown2&lt;/a&gt;），MIT 协议。如果你也和我一样，只是想随手打开一个 Markdown 文件看一眼，欢迎试试，也欢迎拍砖。&lt;/p&gt;</content><category term="misc"/><category term="markdown"/><category term="macos"/><category term="swift"/><category term="swiftui"/><category term="vibe-coding"/><category term="ai"/></entry><entry><title>姚顺宇 4 小时访谈，通透有趣的灵魂~</title><link href="https://www.chenxiaosheng.com/posts/2026-05-19/yao-shunyu-interview-takeaways.html" rel="alternate"/><published>2026-05-19T20:00:00+08:00</published><updated>2026-05-19T20:00:00+08:00</updated><author><name>小生说大声讲</name></author><id>tag:www.chenxiaosheng.com,2026-05-19:/posts/2026-05-19/yao-shunyu-interview-takeaways.html</id><summary type="html">&lt;p&gt;听完姚顺宇四个多小时的访谈，整理了七点有意思的观察：AI 生成代码的比例、架构瓶颈往往只是低级 Bug、执行力比想法更重要、祛魅"老登"、AI 安全的政治博弈、OpenAI 给 Google 留了喘息窗口、以及程序员的"极度中心化"洗牌。&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="cover" src="/static/yao-shunyu-interview-takeaways/cover.jpg"&gt;&lt;/p&gt;
&lt;p&gt;听完这四个多小时的访谈，有几点觉得比较有意思的：&lt;/p&gt;
&lt;p&gt;1、姚顺宇坦言自己 90% 甚至 99% 的代码都是 AI 生成的，果然极客精神首先还得是实用主义，和古法编程形成了反差，拥有最好的本地大模型和代码助手，还非要去拼人力，这就是跟生产力过不去，当然不妨碍我们应当保留那 1%~10% AI 无法触达的能力；&lt;/p&gt;
&lt;p&gt;2、所谓"到了架构瓶颈"，往往只是代码里藏了个低级 Bug。就好比近一年来各大公有云的各种 big boom 可能单纯是一段糟糕的监控代码把控制面给打爆了；&lt;/p&gt;
&lt;p&gt;3、Idea 极其廉价，执行力和"靠谱"才是硬通货。姚顺宇提到"AI 这个事其实不太需要脑子，最重要的特质是 Execute（执行力）"。想法需要详尽的架构蓝图，现在不缺天马行空的所谓"英雄"，缺的是能把复杂的项目，一步步拆解清晰，能把事情做细，对交付结果死磕到底的靠谱工程师；&lt;/p&gt;
&lt;p&gt;4、祛魅"老登"，破除个人英雄主义神话。"没有哪个老登是你的亲属，他傻就可以直接说他傻"。技术发展到今天这个量级，单枪匹马搞定颠覆性算法的时代已经翻篇了，本质上都是工程体系和集体主义的胜利。只要逻辑自洽、结果能经得起客观标准的检验，就不需要去迷信那些旧时代的权威；&lt;/p&gt;
&lt;p&gt;5、Anthropic Dario 所谓的"AI 安全"，不过是反中和争霸的遮羞布。姚顺宇毫不避讳地爆料，离开 Anthropic 有 40% 的原因是因为受不了 CEO Dario 的反华立场，而且极其反感 Dario 那套"我们必须拥有最强模型才能推 AI 安全"的逻辑。这点真的让人挺 respect 的。现在硅谷那帮大佬动辄把"AI 安全"挂在嘴边，搞得极其神圣，但扒开这层皮，底层逻辑无非是"我要垄断最强技术，所以规矩必须由我来定"的政治博弈和商业算计；&lt;/p&gt;
&lt;p&gt;6、OpenAI 救了 Google 一命，"如果 ChatGPT 一鼓作气把 Search 吃透，Google 就完了。正因为没做到极致，反而给 Google 留了反击的时间。"如果在技术优势期没有把核心场景打穿、形成绝对的降维打击，那就是给对手留了喘息和重组火力的窗口期。OpenAI 后来高管流失、各种内部动荡，多少也印证了在战略执行上的拖泥带水是致命的；&lt;/p&gt;
&lt;p&gt;7、程序员的未来不是消失，而是"极度中心化"的残酷洗牌。"绝大多数失去独特价值，1/1000 的人拿 100 倍工资。"这句话很残酷也也很真实（虽然资本家大概率不会真的给你 100 倍工资）。未来留下来的，必定是那些具备全局视野、能把复杂业务逻辑拆解成 AI 可执行步骤的"超级节点"。这1/1000的人，其实是懂业务、懂架构，还能下一线动手的"超级总包商"；&lt;/p&gt;
&lt;p&gt;见识学习了姚顺宇作为"不靠 LP 吃饭、没有历史包袱"的新一代研究员的特质，这些带着浓厚极客精神和真实体感的观点，以姚顺宇相对平静又戏虐的语气跳脱而出，还是很有冲击力的。另外抛开那些虚头巴脑的，不管外面的风口吹得多高，哪怕是 Anthropic、Google DeepMind 这样的神级团队，本质上也是由一个个也会写出 Bug、也会搞出命名乌龙（Claude 3.5/3.6）的工程师组成的。对我们来说，用好当下最锋利的工具去武装自己才是咱们在这个快速变现的时代里最应该马上去做的（至于能不能成为最核心的护城河，谁知道呢~~&lt;/p&gt;</content><category term="misc"/><category term="ai"/><category term="llm"/><category term="vibe-coding"/><category term="anthropic"/><category term="openai"/></entry><entry><title>借长鑫科技的东风，把存储这件事一次讲透</title><link href="https://www.chenxiaosheng.com/posts/2026-05-17/changxin-storage-dram.html" rel="alternate"/><published>2026-05-17T10:00:00+08:00</published><updated>2026-05-17T10:00:00+08:00</updated><author><name>小生说大声讲</name></author><id>tag:www.chenxiaosheng.com,2026-05-17:/posts/2026-05-17/changxin-storage-dram.html</id><summary type="html">&lt;p&gt;2026 年 Q1 长鑫科技营收 508 亿、归母净利润 247.62 亿，半年指引净利 500 亿+。借这股东风，把存储产业链六层架构、龙头格局、长鑫产业链全景梳理一遍。&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;2026 年 5 月 17 日，长鑫科技更新科创板招股书：2026 年 Q1 营收 508 亿、归母净利润 247.62 亿，半年指引营收 1100—1200 亿、归母 500—570 亿，同比预增 2244%—2544%。一家长期亏损的国产 DRAM 厂，半年净利干到 500 亿+ 的体量，把"中国存储能不能赚钱"这道老问题，直接翻篇了。&lt;/p&gt;
&lt;p&gt;借这股东风，正好把存储这条产业链相关的信息学习一轮。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;一、AI 时代的存储，凭什么分成六层&lt;/h2&gt;
&lt;p&gt;CPU 和 GPU 自己只有一点点片上缓存，装不下模型，更装不下推理上下文。所以存储被迫往外摊，离算力越远、容量越大、单位成本越低，但延迟和能耗也越高。这就是分层的由来。&lt;/p&gt;
&lt;p&gt;按"离算力的距离"由近到远，AI 存储一共六层，2025 年六层合计市场约 &lt;strong&gt;2290 亿美元&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;&lt;img alt="存储分层" src="/static/changxin-storage-dram/storage_pyramid.png"&gt;&lt;/p&gt;
&lt;p&gt;挑几个关键点说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;L0 片上 SRAM&lt;/strong&gt;：嵌在每颗芯片里，本身只有 10—17 亿美元的独立晶片市场，但每一颗 H100、B200、TPU v5 为了多塞 SRAM 都得多买晶圆，&lt;strong&gt;真正的利润池落在台积电&lt;/strong&gt;——全球先进工艺晶圆 70% 以上在它手里。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L1 HBM&lt;/strong&gt;：用 TSV 把 DRAM 垂直堆叠，再用 CoWoS 贴到 GPU 旁边。它几乎单枪匹马决定了 AI 加速器能跑多大的模型。&lt;strong&gt;2025 年盘子约 350 亿美元，2028 年预计冲到 1000 亿美元，CAGR 40%&lt;/strong&gt;（美光 FY26Q1 业绩会口径）。2026Q1，SK 海力士的营业利润率打到了 &lt;strong&gt;72%&lt;/strong&gt;，比台积电（58%）和英伟达（65%）都高。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L2 主板 DRAM&lt;/strong&gt;：DDR5/LPDDR/GDDR 这些常规内存条，&lt;strong&gt;2025 年 1218 亿美元，是六层里盘子最大的&lt;/strong&gt;。三星、SK、美光把产能往 HBM 倾斜后，反而让普通 DRAM 维持了高利润和定价权。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L3 CXL 内存池&lt;/strong&gt;：把 DRAM 从单台服务器"池化"到机架级，多个 GPU 按需共享。当前规模才 16 亿美元，但 2033 年看到 237 亿。这一层最暴利的环节是重定时器，&lt;strong&gt;Astera Labs 占 55%，非 GAAP 毛利率 76%&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L4 企业级 SSD&lt;/strong&gt;：QLC 大容量盘已经把 HDD 从 AI 数据湖里彻底挤出去。&lt;strong&gt;2025 年 261 亿美元，CAGR 24%&lt;/strong&gt;，海力士子公司 Solidigm 与 Kioxia 已经做出 122TB 的单盘。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L5 NAS 与云对象存储&lt;/strong&gt;：数据湖、归档、跨团队协作的最外层，AWS+Azure+GCP 三家拿走 65—70%，靠的是"数据出网+生态锁定"的复利。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一句话总结：&lt;strong&gt;越靠近算力，盘子越小，毛利越高&lt;/strong&gt;。HBM 盘子只有 DRAM 三分之一，毛利率却翻倍；CXL 重定时器盘子最小，毛利率最高。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;二、龙头格局：三强 + 一个新入场的第四名&lt;/h2&gt;
&lt;p&gt;存储是典型寡头行业，前三家在每一层都基本拿走 90% 以上。&lt;/p&gt;
&lt;p&gt;&lt;img alt="市场份额" src="/static/changxin-storage-dram/market_share.png"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DRAM&lt;/strong&gt;（TrendForce 2025Q4）：三星 35.8% 重夺第一、SK 海力士 32.1%、美光 22.4%，&lt;strong&gt;长鑫 CXMT 升至 4.7%&lt;/strong&gt;，构成新的"四强"格局。按招股书披露的产能/出货口径，长鑫 2025Q4 全球份额已达 7.67%——口径差异主要在于片数 vs 营收，长鑫片数堆上来了，单价还在追赶。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HBM&lt;/strong&gt;（2026Q1）：SK 海力士约 58%（拿下英伟达大单）、三星 22%、美光 20%。三星份额从 40% 掉到 22%，主要被先进封装良率拖累。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;企业级 SSD&lt;/strong&gt;（2025Q4）：三星 36.9%、SK（含 Solidigm）32.9%、美光 14.0%、Kioxia 11.7%、SanDisk 4.4%，前五合计约 90%。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;韩美三巨头同时吃下 DRAM+HBM+NAND 三层红利，是更全面的"AI 存储平台公司"。&lt;strong&gt;长鑫是这个格局里唯一的中国玩家&lt;/strong&gt;——之前的市场被三星、SK、美光三家寡头垄断超过 90%，长鑫从零起步做到全球第四，本身就是 0→1 的破局。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;三、长鑫的产业链全景：设备 + 材料 + 客户&lt;/h2&gt;
&lt;p&gt;长鑫现在合肥两座、北京一座，三座 12 英寸厂，&lt;strong&gt;月产能合计接近 30 万片，按晶圆片数算已占全球 DRAM 接近 15%&lt;/strong&gt;。上海新厂分两期共 20 万片，2027—2028 年陆续投产，远期奔着 50 万片以上去。IPO 拟募资 295 亿，其中约 200 亿会直接变成设备订单——这是国产设备厂未来两年最确定的基本盘。&lt;/p&gt;
&lt;p&gt;&lt;img alt="长鑫产业链" src="/static/changxin-storage-dram/supply_chain.png"&gt;&lt;/p&gt;
&lt;h3&gt;上游 · 设备（国产化率 ≈ 45%）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;已跑通&lt;/strong&gt;：刻蚀（北方华创 ICP &amp;gt; 50%、中微 CCP）、CMP（华海清科国产份额 &amp;gt; 90%）、清洗（盛美 SAPS/TEBO）、热处理（北方华创立式氧化炉）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;验证中&lt;/strong&gt;：薄膜沉积（拓荆 PECVD、北方华创 PVD 进入量产），但 DRAM 电容核心的高 K ALD 还是应材、ASM 的地盘。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;卡脖子&lt;/strong&gt;：&lt;strong&gt;光刻国产化率不到 5%&lt;/strong&gt;，主力还是 ASML NXT:1980Di。2026 年 4 月美国众议院通过 MATCH 法案，要求 150 天内堵死 DUV 出口的灰色通道——这道墙短期绕不开。量测（KLA 占 51-54%）、涂胶显影（东电 90%）、离子注入也都还是个位数国产率。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;上游 · 材料（领先设备一个身位）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;湿电子化学品 + CMP&lt;/strong&gt;：晶瑞电材（全品类）、安集科技（抛光液国内 ≈70%）、鼎龙股份（抛光垫国内 &amp;gt; 70%）、格林达（G5 级 TMAH 显影液）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;气体 + 前驱体 + 硅片&lt;/strong&gt;：广钢气体（电子大宗气签了 15 年长协，国内罕见）、华特气体（光刻气国内 60%）、雅克科技 / 安德科铭（ALD 前驱体）、沪硅产业 / 立昂微（12 英寸硅片）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;靶材&lt;/strong&gt;：江丰电子，12 英寸国内份额 &amp;gt; 70%，部分进入 3 纳米验证。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;薄弱环节&lt;/strong&gt;：ArF 光刻胶（南大光电、彤程刚破冰），掩模版（Photronics/Toppan/DNP 主导，国产化率极低）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;材料端最大的特点是"量"先于"价"——化学品单价指数从 2022 年的 100 跌到 2025H1 的 77.90，但景气回升直接传导到采购量。&lt;strong&gt;按 2026Q1 业绩外推，长鑫全年材料采购规模冲到 270 亿以上是大概率事件&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;下游 · 客户与封测&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;股东即大客户&lt;/strong&gt;：阿里云（持股 3.85%）、阿里网络、小米、联想、OPPO、vivo；华为昇腾系列的 HBM2/HBM3 与 LPDDR5X 由长鑫优先供应。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;封测已国产化&lt;/strong&gt;：沛顿（深科技）承接 &amp;gt; 50% 委外封测，长电、通富、盛合晶微补位；HBM 后段封装材料（华海诚科 GMC、联瑞新材球硅）随 2026 年底上海后段厂投产开始放量。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;四、为什么 DRAM 这一波"确定性"格外强&lt;/h2&gt;
&lt;p&gt;把视角拉远，三件事同时发生：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;AI 驱动的存储超级周期&lt;/strong&gt;：Omdia 预测全球 DRAM 市场规模从 2025 年的 &lt;strong&gt;1505 亿美元增长到 2030 年的 5710 亿美元，CAGR 30.56%&lt;/strong&gt;；HBM TAM 提前两年看到 1000 亿美元的水平。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;三巨头扩产谨慎 + 产能向 HBM 倾斜&lt;/strong&gt;：直接结果是 2026Q1 DRAM 平均售价（ASP）环比涨幅超 60%，明确的卖方市场。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;国产替代被外部约束加速&lt;/strong&gt;：MATCH 法案越收紧，长鑫每往国产设备多切一个百分点，对应的就是北方华创、中微、拓荆、华海清科、盛美的实打实订单。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;风险也要诚实地讲：DRAM 是存储三大品类中&lt;strong&gt;需求被两头挤压最严重&lt;/strong&gt;的一类（上有 HBM 抢算力侧需求、下有 NAND 抢 token 侧需求），从历史周期看也通常是最早反转的。半年 500 亿净利的高基数能维持多久，要看 H2 ASP 走势和长鑫向 G5/HBM3 的代际切换节奏。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;长鑫的故事，本质不是一家公司的 IPO，而是&lt;strong&gt;整条中国 12 英寸国产存储平台第一次被验证、被复用的过程&lt;/strong&gt;——刻蚀和 CMP 已经跑通，薄膜沉积和清洗在跟进，量测和光刻是下一个要啃的硬骨头。&lt;/p&gt;
&lt;p&gt;对普通投资者来说，与其押单一标的，不如把这张产业链全景图收好：&lt;strong&gt;HBM 的利润最暴利但跟自己关系最远，DRAM 的盘子最大、长鑫的扩产传导最直接，材料和设备里的"量增弹性"标的反而最持续。&lt;/strong&gt; AI 时代缺芯、缺电、缺存，长鑫正好压在"缺存"这条最确定的线上。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;主要数据来源&lt;/strong&gt;：
- 长鑫科技 2026 年更新版科创板招股书；
- TrendForce《Price Rally Drives 4Q25 DRAM Revenue Up 29.4%》；
- Counterpoint《Global DRAM and HBM Market Share》；
- Omdia DRAM TAM 预测；
- 美光 FY26Q1 业绩电话会；
- SK 海力士 2026Q1 财报。&lt;/p&gt;</content><category term="misc"/><category term="ai"/><category term="存储"/><category term="半导体"/><category term="长鑫"/><category term="DRAM"/><category term="HBM"/></entry><entry><title>USB 命名混乱救命表：从 1.1 到 120Gbps</title><link href="https://www.chenxiaosheng.com/posts/2026-04-26/usb-naming-cheat-sheet.html" rel="alternate"/><published>2026-04-26T12:00:00+08:00</published><updated>2026-04-26T12:00:00+08:00</updated><author><name>小生说大声讲</name></author><id>tag:www.chenxiaosheng.com,2026-04-26:/posts/2026-04-26/usb-naming-cheat-sheet.html</id><summary type="html">&lt;p&gt;USB 标准命名乱到没朋友——同一个速率换了三个商标名，"SuperSpeed" 听起来比 "SuperSpeedPlus" 只慢一点点其实差两倍，USB 3.0/3.1/3.2 Gen 1 是同一个东西。在 Fabien Sanglard 的 USB Cheat Sheet 基础上补充了截至 2026.04 已经量产的 USB4 v2.0 / 80Gbps 部分，做成一张可以直接抄的对照表。&lt;/p&gt;</summary><content type="html">&lt;h2&gt;为什么需要这张表&lt;/h2&gt;
&lt;p&gt;USB 的命名乱到什么程度？看几个常踩的坑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;同一个速率，三个商标名&lt;/strong&gt;：USB 3.0、USB 3.1 Gen 1、USB 3.2 Gen 1 是&lt;strong&gt;完全同一个东西&lt;/strong&gt;（5 Gbps）。你在淘宝看到三种叫法只是上架时机不同。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;"SuperSpeed" 比 "SuperSpeedPlus" 慢一倍&lt;/strong&gt;：听起来就差一个 Plus，实际上一个 5 Gbps 一个 10 Gbps。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USB 3.2 Gen 2 和 USB 3.2 Gen 2x2 不是一回事&lt;/strong&gt;：后者是双 lane，速率翻倍到 20 Gbps。x2 这个后缀很多店铺不会写。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USB4 也不是只有一种&lt;/strong&gt;：有 USB4 20Gbps 和 USB4 40Gbps 两个档位，盒子上不写清楚就只能赌。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USB4 不等于 Thunderbolt 4，也不等于 USB-C&lt;/strong&gt;：USB-C 只是物理接口，Thunderbolt 是 Intel 的超集协议，USB4 是 USB-IF 的协议——三者经常被混着叫。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面这张表就是用来在买线、选 dock、判断 NAS / 移动硬盘盒接口时&lt;strong&gt;一眼对上号&lt;/strong&gt;的。&lt;/p&gt;
&lt;h2&gt;营销名 vs 技术名&lt;/h2&gt;
&lt;table style="width: 100%;"&gt;
    &lt;tr&gt;
        &lt;th&gt;营销名（Marketing Name）&lt;/th&gt;
        &lt;th&gt;等价别名（Also Known As）&lt;/th&gt;
        &lt;th style="text-align:right;"&gt;信号速率&lt;/th&gt;
        &lt;th style="text-align:right;"&gt;理论带宽&lt;/th&gt;
        &lt;th style="text-align:right;"&gt;线芯&lt;/th&gt;
        &lt;th style="text-align:right;"&gt;线长上限&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;USB 1.1&lt;/td&gt;
        &lt;td&gt;Full Speed&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;12 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;1.5 MiB/s&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;4&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;4 m&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;USB 2.0&lt;/td&gt;
        &lt;td&gt;Hi-Speed&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;480 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;60 MiB/s&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;4&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;4 m&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;SuperSpeed USB 5Gbps&lt;/td&gt;
        &lt;td&gt;USB 3.0&lt;br/&gt;USB 3.1&lt;br/&gt;USB 3.2&lt;br/&gt;USB 3.1 Gen 1&lt;br/&gt;USB 3.2 Gen 1&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;5 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;625 MiB/s&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;8&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;3 m&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;SuperSpeedPlus USB 10Gbps&lt;/td&gt;
        &lt;td&gt;USB 3.1&lt;br/&gt;USB 3.2&lt;br/&gt;USB 3.1 Gen 2&lt;br/&gt;USB 3.2 Gen 2&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;10 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;1 250 MiB/s&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;8&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;2 m&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;SuperSpeedPlus USB 20Gbps&lt;/td&gt;
        &lt;td&gt;USB 3.2&lt;br/&gt;USB 3.2 Gen 2x2&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;20 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;2 500 MiB/s&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;12&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;1 m&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;USB4 20Gbps&lt;/td&gt;
        &lt;td&gt;USB4 Gen 2×2&lt;br/&gt;USB4&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;20 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;2 500 MiB/s&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;12&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;0.8 m&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;USB4 40Gbps&lt;/td&gt;
        &lt;td&gt;USB4 Gen 3×2&lt;br/&gt;USB4&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;40 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;5 000 MiB/s&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;12&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;0.8 m&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr style="background:#fff7d6;"&gt;
        &lt;td&gt;&lt;b&gt;USB 80Gbps&lt;/b&gt;&lt;br/&gt;&lt;span style="font-size:0.9em;color:#666;"&gt;2026 补充&lt;/span&gt;&lt;/td&gt;
        &lt;td&gt;USB4 v2.0&lt;br/&gt;USB4 Gen 4×2&lt;br/&gt;Thunderbolt 5（超集）&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;80 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;10 000 MiB/s&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;12&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;1 m（被动）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr style="background:#fff7d6;"&gt;
        &lt;td&gt;&lt;b&gt;USB 120Gbps&lt;/b&gt;&lt;br/&gt;&lt;span style="font-size:0.9em;color:#666;"&gt;2026 补充 / 非对称&lt;/span&gt;&lt;/td&gt;
        &lt;td&gt;USB4 v2.0 Asymmetric&lt;br/&gt;Thunderbolt 5 Bandwidth Boost&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;120/40 Gbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;15 000 / 5 000 MiB/s&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;12&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;1 m（被动）&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;看表姿势&lt;/strong&gt;：买线、买盒子的时候，先把店家写的字串去 "Also Known As" 这一列里搜，落到哪一行就是哪一行的速率。不要被 "SuperSpeed"、"USB 3.2"、"USB4" 这种笼统说法骗——它们对应着多个不同档位。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Gen 命名约定：A x B 是什么意思&lt;/h2&gt;
&lt;p&gt;USB-IF 后来给 3.x 和 USB4 加的 &lt;code&gt;Gen A x B&lt;/code&gt; 后缀，含义其实很规整：&lt;/p&gt;
&lt;pre style="background:#f7f8fa;padding:1ch;border-radius:6px;"&gt;
USB Gen &lt;span style="color:#0050d0;font-weight:bold;"&gt;A&lt;/span&gt; x &lt;span style="color:#c00;font-weight:bold;"&gt;B&lt;/span&gt;

&lt;span style="color:#0050d0;font-weight:bold;"&gt;A&lt;/span&gt; = 第几代信号（决定每个 lane 的速率）
&lt;span style="color:#c00;font-weight:bold;"&gt;B&lt;/span&gt; = 用了几条 lane（双工对数）
&lt;/pre&gt;

&lt;p&gt;下面这张表把 3.2 / USB4 各代乘开后的实际速率算清楚：&lt;/p&gt;
&lt;table style="width: 100%;"&gt;
    &lt;tr&gt;
        &lt;th&gt;名称&lt;/th&gt;
        &lt;th style="text-align:right;"&gt;单 lane 信号&lt;/th&gt;
        &lt;th style="text-align:right;"&gt;总信号速率&lt;sup&gt;a&lt;/sup&gt;&lt;/th&gt;
        &lt;th style="text-align:right;"&gt;编码&lt;/th&gt;
        &lt;th style="text-align:right;"&gt;去编码后&lt;sup&gt;b&lt;/sup&gt;&lt;/th&gt;
        &lt;th style="text-align:right;"&gt;理论带宽&lt;sup&gt;b&lt;/sup&gt;&lt;/th&gt;
        &lt;th style="text-align:right;"&gt;真实顺序读&lt;sup&gt;c&lt;/sup&gt;&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;USB 3.2 Gen &lt;span style="color:#0050d0;font-weight:bold;"&gt;1&lt;/span&gt;x&lt;span style="color:#c00;font-weight:bold;"&gt;1&lt;/span&gt;&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;5 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;5 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;8b/10b&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;4 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;500 MiB/s&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;~400 MiB/s&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;USB 3.2 Gen &lt;span style="color:#0050d0;font-weight:bold;"&gt;1&lt;/span&gt;x&lt;span style="color:#c00;font-weight:bold;"&gt;2&lt;/span&gt;&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;5 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;10 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;8b/10b&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;8 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;1 000 MiB/s&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;~800 MiB/s&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;USB 3.2 Gen &lt;span style="color:#0050d0;font-weight:bold;"&gt;2&lt;/span&gt;x&lt;span style="color:#c00;font-weight:bold;"&gt;1&lt;/span&gt;&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;10 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;10 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;128b/132b&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;9 696 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;1 212 MiB/s&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;~780 MiB/s&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;USB 3.2 Gen &lt;span style="color:#0050d0;font-weight:bold;"&gt;2&lt;/span&gt;x&lt;span style="color:#c00;font-weight:bold;"&gt;2&lt;/span&gt;&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;10 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;20 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;128b/132b&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;19 392 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;2 424 MiB/s&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;~1 600 MiB/s&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;USB4 Gen &lt;span style="color:#0050d0;font-weight:bold;"&gt;2&lt;/span&gt;x&lt;span style="color:#c00;font-weight:bold;"&gt;2&lt;/span&gt;&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;10 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;20 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;128b/132b&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;19 392 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;2 424 MiB/s&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;~1 600 MiB/s&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;USB4 Gen &lt;span style="color:#0050d0;font-weight:bold;"&gt;3&lt;/span&gt;x&lt;span style="color:#c00;font-weight:bold;"&gt;2&lt;/span&gt;&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;20 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;40 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;128b/132b&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;38 787 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;4 848 MiB/s&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;~2 700 MiB/s&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr style="background:#fff7d6;"&gt;
        &lt;td&gt;&lt;b&gt;USB4 Gen &lt;span style="color:#0050d0;font-weight:bold;"&gt;4&lt;/span&gt;x&lt;span style="color:#c00;font-weight:bold;"&gt;2&lt;/span&gt;&lt;/b&gt;&lt;br/&gt;&lt;span style="font-size:0.9em;color:#666;"&gt;v2.0 / 2026 补充&lt;/span&gt;&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;40 000 Mbps（PAM3）&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;80 000 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;64b/66b&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;77 575 Mbps&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;9 696 MiB/s&lt;/td&gt;
        &lt;td style="text-align:right;"&gt;~6 000 MiB/s&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;

&lt;p style="margin-top:1em;font-size:0.95em;"&gt;
&lt;b&gt;注&lt;/b&gt;：多 lane 系统在发端做 lane striping，在收端做 lane bonding，用户看到的就是一个连续 stream。&lt;br/&gt;
&lt;sup&gt;a&lt;/sup&gt; 厂商盒子上印的就是这一栏。&lt;br/&gt;
&lt;sup&gt;b&lt;/sup&gt; 减掉编码开销后的有效速率。8b/10b 损失 20%，128b/132b 损失约 3%，64b/66b 损失约 3%。&lt;br/&gt;
&lt;sup&gt;c&lt;/sup&gt; 实测顺序读速率（要扣掉协议层、文件系统、SSD 主控本身的瓶颈）。
&lt;/p&gt;

&lt;p&gt;理解了 &lt;code&gt;Gen A x B&lt;/code&gt; 之后，再回头看商标名就清晰多了：所谓 "SuperSpeedPlus 20Gbps" 其实就是 Gen 2 信号 × 2 lane；所谓 "USB4 40Gbps" 其实就是 Gen 3 信号 × 2 lane。这套约定唯一的问题是 &lt;strong&gt;USB-IF 自己又不在卖品名里写 Gen N×M&lt;/strong&gt;，反而强推 "USB 80Gbps" 这种以速率为名的新商标——表里第一栏就是这么来的。&lt;/p&gt;
&lt;h2&gt;线材：4 / 8 / 12 芯&lt;/h2&gt;
&lt;p&gt;USB 物理线最常见三档：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;4 芯&lt;/strong&gt;：&lt;code&gt;PWR&lt;/code&gt;、&lt;code&gt;GND&lt;/code&gt;、&lt;code&gt;D+&lt;/code&gt;、&lt;code&gt;D-&lt;/code&gt;。一对差分信号，半双工一条 lane。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;8 芯&lt;/strong&gt;：4 芯的基础上加 &lt;code&gt;RX+/RX-&lt;/code&gt;、&lt;code&gt;TX+/TX-&lt;/code&gt;。两条 lane，一上一下，全双工。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;12 芯&lt;/strong&gt;：8 芯的基础上再加一组 &lt;code&gt;RX2+/RX2-&lt;/code&gt;、&lt;code&gt;TX2+/TX2-&lt;/code&gt;。四条 lane（两上两下），全双工。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;一个 USB lane = 一对 ± 差分线&lt;/strong&gt;。所以 4 芯 = 1 个半双工 lane，8 芯 = 2 个 lane，12 芯 = 4 个 lane。USB 3.2 Gen 2x2、USB4、USB4 v2.0 这些 "x2" 的标准都是要 12 芯线才能跑满的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;USB-A / USB-B 接口（4 芯 / 8 芯）&lt;/h2&gt;
&lt;table style="width:100%;"&gt;
    &lt;tr&gt;
        &lt;th style="text-align:center;width:25%;"&gt;Type-A 4-wires&lt;/th&gt;
        &lt;th style="text-align:center;width:25%;"&gt;Type-A 8-wires&lt;/th&gt;
        &lt;th style="text-align:center;width:25%;"&gt;Type-B 4-wires&lt;/th&gt;
        &lt;th style="text-align:center;width:25%;"&gt;Type-B 8-wires&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td style="vertical-align:bottom;text-align:center;background:#fff;"&gt;&lt;img loading="lazy" src="/static/usb-cheat-sheet/typea.svg" style="width:80%;height:auto;display:inline-block;margin:0 auto;"/&gt;&lt;/td&gt;
        &lt;td style="vertical-align:bottom;text-align:center;background:#fff;"&gt;&lt;img loading="lazy" src="/static/usb-cheat-sheet/typea3.svg" style="width:80%;height:auto;display:inline-block;margin:0 auto;"/&gt;&lt;/td&gt;
        &lt;td style="vertical-align:bottom;text-align:center;background:#fff;"&gt;&lt;img loading="lazy" src="/static/usb-cheat-sheet/typeb.svg" style="width:60%;height:auto;display:inline-block;margin:0 auto;"/&gt;&lt;/td&gt;
        &lt;td style="vertical-align:bottom;text-align:center;background:#fff;"&gt;&lt;img loading="lazy" src="/static/usb-cheat-sheet/typeb3.svg" style="width:60%;height:auto;display:inline-block;margin:0 auto;"/&gt;&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;8 芯版本的 Type-A 蓝色塑料舌片就是 USB 3.x 的标志——多出来的 5 个针脚塞在原来 4 个的下方/后方。USB-B 8 芯版本（也叫 USB 3.0 Type-B）则是直接在原本梯形上又叠了一块，所以看起来像一个驼背的小房子。&lt;/p&gt;
&lt;h2&gt;USB-C 接口（12 芯）&lt;/h2&gt;
&lt;p&gt;只有 USB-C 有足够的引脚数支撑两个完整 lane。&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" src="/static/usb-cheat-sheet/typec.svg" style="width:70%;height:auto;display:block;margin:1em auto;"/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CC1 / CC2&lt;/strong&gt;：Configuration Channel。两端用来识别谁是 DFP（Downstream Facing Port，主机一侧）、谁是 UFP（Upstream Facing Port，设备一侧），同时也用来协商供电档位、切到 Alt Mode（DisplayPort、Thunderbolt、HDMI 等）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SBU1 / SBU2&lt;/strong&gt;：Sideband Use。用作 DisplayPort 的 AUX 通道、热插拔检测（HPD）等辅助信号。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;USB-C 真正强大的地方是这两组针脚——没有它们，USB 就是个纯数据口；有了它们，一根 C 线可以同时跑数据、视频、PD 供电，并且&lt;strong&gt;正反盲插&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;充电规格 / PD 档位&lt;/h2&gt;
&lt;table style="width:100%;"&gt;
    &lt;tr&gt;
        &lt;th style="text-align:center;"&gt;规范&lt;/th&gt;
        &lt;th style="text-align:center;"&gt;最大电压&lt;/th&gt;
        &lt;th style="text-align:center;"&gt;最大电流&lt;/th&gt;
        &lt;th style="text-align:center;"&gt;最大功率&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td style="text-align:center;"&gt;USB 2.0&lt;/td&gt;
        &lt;td style="text-align:center;"&gt;5 V&lt;/td&gt;
        &lt;td style="text-align:center;"&gt;500 mA&lt;/td&gt;
        &lt;td style="text-align:center;"&gt;2.5 W&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td style="text-align:center;"&gt;USB 3.0 / 3.1&lt;/td&gt;
        &lt;td style="text-align:center;"&gt;5 V&lt;/td&gt;
        &lt;td style="text-align:center;"&gt;900 mA&lt;/td&gt;
        &lt;td style="text-align:center;"&gt;4.5 W&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td style="text-align:center;"&gt;USB Battery Charging (BC) 1.2&lt;/td&gt;
        &lt;td style="text-align:center;"&gt;5 V&lt;/td&gt;
        &lt;td style="text-align:center;"&gt;1.5 A&lt;/td&gt;
        &lt;td style="text-align:center;"&gt;7.5 W&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td style="text-align:center;"&gt;USB-C Current Mode（非 PD）&lt;/td&gt;
        &lt;td style="text-align:center;"&gt;5 V&lt;/td&gt;
        &lt;td style="text-align:center;"&gt;3 A&lt;/td&gt;
        &lt;td style="text-align:center;"&gt;15 W&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td style="text-align:center;"&gt;USB-C / Power Delivery（PD 1/2）&lt;/td&gt;
        &lt;td style="text-align:center;"&gt;20 V&lt;/td&gt;
        &lt;td style="text-align:center;"&gt;5 A&lt;/td&gt;
        &lt;td style="text-align:center;"&gt;100 W&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td style="text-align:center;"&gt;USB-C PD 3.1 EPR（Extended Power Range）&lt;/td&gt;
        &lt;td style="text-align:center;"&gt;48 V&lt;/td&gt;
        &lt;td style="text-align:center;"&gt;5 A&lt;/td&gt;
        &lt;td style="text-align:center;"&gt;240 W&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;要拉满 240 W，必须是支持 EPR 的线（线身上一般直接印 &lt;code&gt;EPR 240W&lt;/code&gt;），并且两端 charger / 设备都支持 PD 3.1。普通 100 W 线直接插上去只会握手到 100 W 档位。&lt;/p&gt;
&lt;h2&gt;2026.04 补充：USB 80Gbps 已经量产&lt;/h2&gt;
&lt;p&gt;原文写于 2022 年，那会儿的天花板就是 USB4 40Gbps。&lt;strong&gt;到 2026.04，新的 USB4 v2.0（USB-IF 现在统一叫 "USB 80Gbps"）已经过了 spec → silicon → 整机三道关，能在零售店里买到。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;技术上和老 USB4 的关键差别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PAM3 信号&lt;/strong&gt;：每个 symbol 不再是 0/1 两态，而是 -1/0/+1 三态（每 symbol 携带 ~1.58 bit）。这是单 lane 速率从 20 Gbps 翻到 40 Gbps 的核心。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;编码换成 64b/66b&lt;/strong&gt;：开销从 ~3% 略降。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对称模式 80 Gbps&lt;/strong&gt;：4 lane × 40 Gbps = 160 Gbit 信号，扣编码后约 ~9 700 MiB/s 理论值；实测顺序读目前在 6 GB/s 左右（被 NVMe 主控和 PCIe 拓扑限制住）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;非对称模式 120 / 40 Gbps&lt;/strong&gt;：3 lane 走一个方向、1 lane 走反方向。专门给 8K 显示、外置 GPU 这种"大头朝外"的场景。USB-IF 称这个档为 "USB 120Gbps"，Intel 在 Thunderbolt 5 里叫 "Bandwidth Boost"——同一回事。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接口仍然是 USB-C，引脚数还是 12&lt;/strong&gt;。线长上限稍微放宽到 1 m（被动铜线）；超过 1 m 一般要 active 线。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;原文：&lt;a href="https://fabiensanglard.net/usbcheat/index.html"&gt;Fabien Sanglard — USB Cheat Sheet&lt;/a&gt;（2022-05）&lt;/li&gt;
&lt;li&gt;规范文档（USB-IF 官方下载）：&lt;a href="https://www.usb.org/documents"&gt;USB Document Library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;USB 1.0（1996-01）/ USB 1.1（1998-09）/ USB 2.0（2000-04）&lt;/li&gt;
&lt;li&gt;USB 3.0（2008-11）/ USB 3.1（2013-07）/ USB 3.2（2017-09）&lt;/li&gt;
&lt;li&gt;USB4 v1.0（2019-08）/ &lt;strong&gt;USB4 v2.0（2022-10）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;USB Power Delivery 3.1 EPR：&lt;a href="https://www.usb.org/document-library/usb-power-delivery"&gt;USB-IF PD Spec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Thunderbolt 5 / Intel Barlow Ridge 控制器公开材料：&lt;a href="https://www.intel.com/content/www/us/en/newsroom/news/thunderbolt-5-unveiled-next-generation-cable-pc-accessories.html"&gt;Intel Newsroom&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="misc"/><category term="usb"/><category term="hardware"/><category term="cheatsheet"/><category term="thunderbolt"/></entry><entry><title>啤酒游戏：让 Claude 和 Codex 干了这杯酒</title><link href="https://www.chenxiaosheng.com/posts/2026-04-21/claude-vs-codex-beer-game-two-rounds.html" rel="alternate"/><published>2026-04-21T22:00:00+08:00</published><updated>2026-04-21T22:00:00+08:00</updated><author><name>小生说大声讲</name></author><id>tag:www.chenxiaosheng.com,2026-04-21:/posts/2026-04-21/claude-vs-codex-beer-game-two-rounds.html</id><summary type="html">&lt;p&gt;同一道啤酒游戏案例题、同一份 PDF，Claude 最初算出超额收益 500–1000 元，Codex 最初算出 7500–7900 元。我把两份答案互丢回去，让它们围绕“backorder 假设”和“期末库存估值口径”互评两轮。最后两边在主策略和收益量级上基本收敛，但中心值仍未完全统一。&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://www.chenxiaosheng.com/posts/2026-04-20/claude-and-codex-crosscheck-homework.html"&gt;上一篇&lt;/a&gt;讲的是同一门《运营与供应链管理》的设施选址案例。那次比较干净：重心法有唯一解，让两个 AI 互评一轮，再用 Python 跑一遍，基本就能定谁对谁错。&lt;/p&gt;
&lt;p&gt;这次不一样。第二次课后作业是经典的&lt;strong&gt;啤酒游戏&lt;/strong&gt;（Beer Game）：零售商 → 批发商 → 制造商三级供应链，真实需求在第 2 周从 4 箱/周升到 8 箱/周，之后保持稳定，但由于信息失真和 4 周提前期，链路里层层放大。案例写到：到第 24 周零售商还堆着 75 箱、批发商还堆着 218 卡车量，而制造商在第 19 周库存已经冲到 93 批。&lt;/p&gt;
&lt;p&gt;题目三问：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;画出 1–24 周库存和订单图，解释牛鞭效应&lt;/li&gt;
&lt;li&gt;分析牛鞭效应的影响、成因、对策&lt;/li&gt;
&lt;li&gt;假设你是“聪明零售商”，给出最优订货策略，并算出你比“普通零售商”多赚多少钱&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;问题 1、2 偏定性，两个 AI 答得大体接近。真正分歧出在问题 3：&lt;strong&gt;Claude 最初算出超额收益约 500–1000 元，Codex 最初算出 7500–7900 元。差了一个数量级。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;一开始的分歧，其实不只是一道算术题&lt;/h2&gt;
&lt;p&gt;两份答案的最优订货策略看上去很接近：第 1 周按常态 4 箱，第 2 周开始往上调，之后维持 8 箱/周的稳态。真正的差别只在一句话：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Claude：第 2 周订 &lt;strong&gt;12 箱&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Codex：第 2 周订 &lt;strong&gt;8 箱&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但光是 4 箱之差，不足以解释为什么一个答案多赚几百元，另一个多赚几千元。真正把两份答案拉开的，是背后的两个建模选择：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;行为假设&lt;/strong&gt;：第 5 周不可避免的 4 箱短缺，到底能不能在第 6 周作为 &lt;code&gt;backorder&lt;/code&gt; 补卖？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;收益口径&lt;/strong&gt;：第 24 周普通零售商手上的 75 箱库存，到底是资产，还是在本题比较口径里被等效忽略掉？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;事后看，双方真正争的不是“谁算错了”，而是“哪套假设和口径更适合这道题”。&lt;/p&gt;
&lt;h2&gt;第一轮：双方先各自为自己的答案辩护&lt;/h2&gt;
&lt;p&gt;我先让它们各自写一份对比报告：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Claude_VS_Codex_by_Claude.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Claude_VS_Codex_by_Codex.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这一轮的立场很清楚：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Claude 认为自己那份更合理，因为案例 PDF 第 6、7 周明确写了顾客会留下名字电话等货，说明 &lt;code&gt;backorder&lt;/code&gt; 真实存在&lt;/li&gt;
&lt;li&gt;Codex 认为自己那份更严谨，因为从 24 周现金流口径看，普通零售商第 24 周手里还有 75 箱库存，占着现金、卖得慢，财务上不能当成“已经实现的收益”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果把两边都尽量善意理解，它们其实各有抓手：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Claude 抓的是&lt;strong&gt;案例行为证据&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Codex 抓的是&lt;strong&gt;比较口径和现金占用&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;到这一步，我还不能明确说谁一定错。因为和设施选址那题不一样，啤酒游戏这道题在第 3 问里确实存在建模自由度。&lt;/p&gt;
&lt;h2&gt;第二轮：真正让局面收敛的，不是继续争观点，而是“用对方的尺子重算一遍”&lt;/h2&gt;
&lt;p&gt;第二轮我把两份对比报告再丢回去，让双方继续回应：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Claude_VS_Codex_Round2_by_Claude.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Claude_VS_Codex_Round2_by_Codex.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Claude 这一轮做得最好的一点，是它没有继续停留在“PDF 原文支持 backorder”这种事实争论上，而是直接做了一件更硬的事：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;用 Codex 自己定义的口径，反算 Claude 的策略。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;它的核心计算逻辑是：&lt;/p&gt;
&lt;p&gt;如果第 2 周订 12 箱，那么：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第 5 周的 4 箱缺口先形成 &lt;code&gt;backorder&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;第 6 周正好到货 12 箱&lt;/li&gt;
&lt;li&gt;这 12 箱刚好覆盖“第 5 周积压的 4 箱 + 第 6 周真实需求 8 箱”&lt;/li&gt;
&lt;li&gt;之后回到稳态，每周到货 8 箱、卖 8 箱&lt;/li&gt;
&lt;li&gt;因而 &lt;strong&gt;期末库存仍然是 0&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;按这个路径，套进 Codex 当时自己采用的“收入 - 采购 - 持有成本”的 24 周现金流式口径，会得到：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;口径&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Claude 策略（W2=12）&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Codex 策略（W2=8）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;销量（箱）&lt;/td&gt;
&lt;td style="text-align: right;"&gt;188&lt;/td&gt;
&lt;td style="text-align: right;"&gt;184&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;收入（元）&lt;/td&gt;
&lt;td style="text-align: right;"&gt;23,500&lt;/td&gt;
&lt;td style="text-align: right;"&gt;23,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;采购（元）&lt;/td&gt;
&lt;td style="text-align: right;"&gt;17,600&lt;/td&gt;
&lt;td style="text-align: right;"&gt;17,200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;持有成本（元）&lt;/td&gt;
&lt;td style="text-align: right;"&gt;9.23&lt;/td&gt;
&lt;td style="text-align: right;"&gt;9.23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;现金利润（元）&lt;/td&gt;
&lt;td style="text-align: right;"&gt;5,890.77&lt;/td&gt;
&lt;td style="text-align: right;"&gt;5,790.77&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;期末库存（箱）&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;也就是说，在&lt;strong&gt;同一口径&lt;/strong&gt;下，Claude 的第 2 周订 12 箱方案，比 Codex 的第 2 周订 8 箱方案&lt;strong&gt;多赚 100 元&lt;/strong&gt;。这 100 元恰好就是多履约的 4 箱 × 单箱毛利 25 元。&lt;/p&gt;
&lt;p&gt;这一步很关键，因为它把争论从“你觉得、我觉得”推进到了“就用你自己的尺子量，结果还是这样”。&lt;/p&gt;
&lt;h2&gt;第二轮之后，Codex 的主要修正也很明确&lt;/h2&gt;
&lt;p&gt;Codex 在第二轮里做了两个重要修正。&lt;/p&gt;
&lt;h3&gt;1. 承认原先的 7500/7900 元量级不合理&lt;/h3&gt;
&lt;p&gt;它接受了一个关键点：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;不能把普通零售商第 24 周剩余的 75 箱库存直接按 0 价值处理。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;因为案例并没有说这 75 箱永远卖不出去。真实需求仍然大约是 8 箱/周，这些库存更合理的理解是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;期末库存资产&lt;/li&gt;
&lt;li&gt;后续持有成本负担&lt;/li&gt;
&lt;li&gt;潜在的折价风险和资金占用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而不是立刻等于当期亏损。&lt;/p&gt;
&lt;p&gt;这一步修完以后，Codex 把自己原先的“多赚 7500/7900 元”回收掉了。&lt;/p&gt;
&lt;h3&gt;2. 接受“第 2 周订 12 箱”作为基准更优方案&lt;/h3&gt;
&lt;p&gt;在允许并管理 &lt;code&gt;backorder&lt;/code&gt; 的前提下，Codex 也接受了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第 2 周订 12 箱&lt;/li&gt;
&lt;li&gt;第 3～20 周每周订 8 箱&lt;/li&gt;
&lt;li&gt;第 21～24 周订 0 箱&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个策略方向。&lt;/p&gt;
&lt;p&gt;不过这里 Codex 也补了一句我认为很重要的限定：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这个“12 箱更优”的结论，最好显式写出前提：&lt;strong&gt;允许并管理 backorder&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我认为这是必要的，因为 PDF 确实证明了 &lt;code&gt;backorder&lt;/code&gt; 现象存在，但它并不自动等于“第 5 周那 4 箱一定 100% 都能补卖”。所以更稳妥的说法，不是“无条件严格最优”，而是“在题目给定的聪明零售商设定下，作为基准解更合理”。&lt;/p&gt;
&lt;h2&gt;到最后，双方其实收敛到了什么？&lt;/h2&gt;
&lt;p&gt;如果只看“最后谁赢”，这个过程会被误读成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Claude 证明了自己是对的&lt;/li&gt;
&lt;li&gt;Codex 改口认输&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但我觉得更准确的描述是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;双方最后在“主策略”和“收益量级”上基本收敛了，但在普通零售商对照组的具体重构数值上，还没有完全统一。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;已经收敛的部分是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;第 1、2 问两边都能作为参考&lt;/li&gt;
&lt;li&gt;第 3 问的基准策略应以“第 2 周订 12 箱”为主&lt;/li&gt;
&lt;li&gt;这个策略的前提是：允许并管理 &lt;code&gt;backorder&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;原先的 &lt;code&gt;7500/7900 元&lt;/code&gt; 应判为口径失真，不再成立&lt;/li&gt;
&lt;li&gt;更合理的收益量级是：&lt;strong&gt;几百元到一千元左右&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但还没有完全统一的部分是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Claude 版当前仍把中心值写成 &lt;code&gt;727 元&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Codex 修订后写成了基准 &lt;code&gt;476 元&lt;/code&gt;，并把现实区间写成 &lt;code&gt;500～1,000 元&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这说明它们已经&lt;strong&gt;没有大的结论性冲突&lt;/strong&gt;，但还保留着一个典型的“同方向、不同中心值”的建模差异：普通零售商路径到底按哪组重构数据来算。&lt;/p&gt;
&lt;h2&gt;一个小插曲：我自己也踩了“AI 不重读文件”的坑&lt;/h2&gt;
&lt;p&gt;这轮还有个挺好玩的插曲。&lt;/p&gt;
&lt;p&gt;共识文件落完之后，我又让 Claude 再检查一次两份正式答案是否还有逻辑冲突。按道理说，这一步应该先重读最新版文件再比。&lt;/p&gt;
&lt;p&gt;但它一开始并没有这么做。它直接沿用脑子里前几轮的旧印象，给了我一个“已经基本没问题”的判断。我后来自己去看文件，才发现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Codex 的正式答案已经被修订过了&lt;/li&gt;
&lt;li&gt;第 3 问的主策略、收益区间、口径说明都已经更新了&lt;/li&gt;
&lt;li&gt;但 Claude 那次检查没有先读最新版，就直接开始比较&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我后来让它重新读取文件，再做一遍结论，结果就正常了。&lt;/p&gt;
&lt;p&gt;这件事让我挺警惕一件事：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;AI 在多轮上下文里，不是只会“算错”，它也会像人一样&lt;strong&gt;偷懒地以为自己还记得文件长什么样&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;所以现在我的默认流程变成了：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;跨轮复核前，明确要求它&lt;strong&gt;重新读取目标文件&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;不接受“基于之前讨论我认为……”这种比较&lt;/li&gt;
&lt;li&gt;文件类任务尽量让它把引用行号和当前版本一起报出来&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;这次复盘，对流程最大的启发是什么？&lt;/h2&gt;
&lt;h3&gt;1. 有争议的题，不能只让双方“表态”，要让它们“同口径重算”&lt;/h3&gt;
&lt;p&gt;第一轮的问题在于，双方都能自圆其说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Claude 说：PDF 支持 &lt;code&gt;backorder&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Codex 说：现金口径更保守&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这两句话都没错，但都还不够“打穿”对方。第二轮真正让问题收敛的，是 Claude 改变了打法：不用自己的尺子，而是用 Codex 自己的尺子，去量 Claude 的方案。&lt;/p&gt;
&lt;p&gt;这比继续争抽象原则有效得多。&lt;/p&gt;
&lt;h3&gt;2. 案例题里，分歧很多时候不是“算错”，而是“口径没统一”&lt;/h3&gt;
&lt;p&gt;设施选址那题属于“公式题”，ground truth 比较硬。&lt;/p&gt;
&lt;p&gt;啤酒游戏第 3 问更像“建模题”：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;backorder&lt;/code&gt; 怎么处理&lt;/li&gt;
&lt;li&gt;期末库存怎么估值&lt;/li&gt;
&lt;li&gt;普通零售商的路径怎么重构&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些都不是 PDF 直接给好的唯一答案，而是解释空间。&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;h3&gt;3. 最终共识最好落成文件，而且最好双方都落&lt;/h3&gt;
&lt;p&gt;这次最终比较清楚的阶段，不是嘴上说“好像差不多了”，而是双方都逐步把立场修正进文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Claude_VS_Codex_Final_Consensus_by_Claude.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Claude_VS_Codex_Final_Consensus_by_Codex.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;以及修订后的 &lt;code&gt;answers/啤酒游戏-案例解答_Codex.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一旦文件更新，后续讨论就有了稳定落点。否则很容易出现“口头上已经同意了，原答案还停留在旧版本”的错位。&lt;/p&gt;
&lt;h2&gt;这道题本身也给了我一个额外收获&lt;/h2&gt;
&lt;p&gt;我觉得这次最有意思的，不只是“谁算赢了”，而是它把一个课堂上常讲、但很多时候很抽象的管理问题，演得特别具体：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;同一个案例、同一份 PDF，只要口径和隐含假设不统一，最后得出的“结论”就可能差一个数量级。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这和牛鞭效应本身很像。牛鞭效应里，上游看到的不是“需求”，而是“需求 + 滞后 + 情绪 + 防御性下单”。这次两个 AI 一开始看到的也不是同一个“收益”，而是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;收益 + &lt;code&gt;backorder&lt;/code&gt; 假设&lt;/li&gt;
&lt;li&gt;收益 + 库存估值口径&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以最后真正值得记住的，不是 7900 还是 727，而是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在供应链和管理分析里，&lt;strong&gt;先统一口径，再比较数值&lt;/strong&gt;，往往比争谁的数字更大更重要。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;尾声&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;AI 也会偷懒，也会划水。&lt;/strong&gt; 而且不是个案，使用 AI 也是要走心的~  &lt;/p&gt;</content><category term="misc"/><category term="ai"/><category term="llm"/><category term="claude"/><category term="codex"/><category term="homework"/><category term="supply-chain"/></entry><entry><title>我是怎么让 Claude 和 Codex 帮忙完成课程作业</title><link href="https://www.chenxiaosheng.com/posts/2026-04-20/claude-and-codex-crosscheck-homework.html" rel="alternate"/><published>2026-04-20T22:00:00+08:00</published><updated>2026-04-20T22:00:00+08:00</updated><author><name>小生说大声讲</name></author><id>tag:www.chenxiaosheng.com,2026-04-20:/posts/2026-04-20/claude-and-codex-crosscheck-homework.html</id><summary type="html">&lt;p&gt;这门《运营与供应链管理》课后有个「设施选址」的案例，我让 Claude 先做了一遍，又用 Codex 独立做了一遍，两个答案思路一致但数字和结论完全相反。把两份答案同时丢回 Claude 让它当裁判，结果它推翻了自己之前的解法——三处方法论错误被它一条条指出来，还配合 Python 独立验证给出了修订版。这篇记录一下这个「左右互博」的过程，和我为什么觉得它比让单一模型深挖要靠谱。&lt;/p&gt;</summary><content type="html">&lt;p&gt;这学期有一门《运营与供应链管理》，第二次课留了个案例作业：南方超级医疗设备公司的设施选址。题目不难，标准的&lt;strong&gt;重心法&lt;/strong&gt;（Center of Gravity）单仓库选址问题，考的是套公式 + ROI 判断。&lt;/p&gt;
&lt;p&gt;我照例先让 Claude 做了一份，给了一个看起来挺漂亮的参考答案。觉得不放心，开了一个新的会话用 Codex 再独立跑了一遍。拿到结果之后我愣住了：&lt;strong&gt;两份答案的思路完全一致，计算出来的最优坐标相差不大，但最终 ROI 正负相反、决策完全相反。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Claude：重心在 &lt;strong&gt;(7.42, 5.04)&lt;/strong&gt;，年运输节约 $46,838，ROI −188%，&lt;strong&gt;结论：续租堪萨斯城，别搬&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Codex：重心在 &lt;strong&gt;(7.58, 4.51)&lt;/strong&gt;，年运输节约 $223,646，ROI +41.22%，&lt;strong&gt;结论：应当搬迁&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两个都是基于同一份 PDF 案例、同一个重心法公式，怎么会算出一正一负？&lt;/p&gt;
&lt;h2&gt;左右互博：把裁判位置也交给 AI&lt;/h2&gt;
&lt;p&gt;我本来的计划是自己动手验算一遍，但看了一会儿发现这事没那么简单——重心法有两个版本（一次加权平均 vs Weiszfeld 迭代）、费率 R 的单位有两种理解（美元/担 vs 美元/担/英里）、外向成本又有两种建模方式。要真人判裁谁对谁错，我得先把讲义从头翻一遍。&lt;/p&gt;
&lt;p&gt;懒病发作，我干脆把两份答案一起丢回一个新的 Claude 会话，原话是这样的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;你完成的解答位于：南方医疗设备公司设施选址_参考答案-Claude.md
我使用其他模型解答的结果和你基本不一致，虽然思路是相同的，结果位于：南方医疗设备公司设施选址-案例解答-Codex.md
请检查双方究竟谁的逻辑存在问题，并给出支撑理由，如有必要，请修正完善最终版本结论&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;注意这里：Claude 开新会话后其实是没有前次对话记忆的，所以这份 &lt;code&gt;_参考答案-Claude.md&lt;/code&gt; 对它而言就是一份陌生的答案，和 Codex 的那份在它眼里是对等的。这样它就没有「维护自己面子」的心理包袱。&lt;/p&gt;
&lt;h2&gt;Claude 翻出了自己的错&lt;/h2&gt;
&lt;p&gt;它做的第一件事是去读讲义 PDF 里重心法的原始公式。这一步关键——它不是在两份答案的输出之间找分歧，而是把两者都对照&lt;strong&gt;原题和教材&lt;/strong&gt;做 ground truth 校对。&lt;/p&gt;
&lt;p&gt;几分钟后它给出了结论：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Codex 的逻辑正确，Claude 的参考答案存在三处方法论错误。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;三处错误它一条条列了：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. R 的单位错。&lt;/strong&gt; 讲义里目标函数是 TC = Σ Vᵢ·Rᵢ·dᵢ，要求 Rᵢ 为「美元/担/英里」。案例表里给的是「美元/担（当前线路总费率）」，必须除以当前距离才能换算。&lt;strong&gt;案例原文第 2 页其实写得很清楚&lt;/strong&gt;：「可估计出以仓库为起点的外向运输平均成本为 0.0235 美元/担/英里」——这句话就是在提示学生做单位换算。前一次 Claude 把这段忽略了，直接把美元/担当权重用，相当于把当前距离"二次放大"进了权重里，让远距离客户影响被高估。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 没迭代。&lt;/strong&gt; 讲义 p.13 把步骤写得很明白：先用 X₀ = Σ(V·R·X)/Σ(V·R) 算一个&lt;strong&gt;初始坐标&lt;/strong&gt;，然后根据 dᵢ 做 Weiszfeld 迭代直到收敛。前一次 Claude 算完初始坐标就收工了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 外向费率结构错。&lt;/strong&gt; 案例文本明确给了一个统一的外向均值 0.0235。前一次 Claude 反其道而行之，按每个客户的当前总费率和距离回推了每客户不同的每英里率。这会让 Chicago 显得每英里 0.050、Montreal 每英里 0.019，两者相差 2.6 倍——这种人为的不一致把新选址的外向成本虚推高了，把真实 $223K 的年节约压成虚假的 $47K。&lt;/p&gt;
&lt;p&gt;这三处错误串在一起，合力把最优点错误地向东北拉偏、又错误地抬高了新址的外向账单，最终得到"别搬"的结论。Codex 在这三点上都是按讲义和案例原文来的。&lt;/p&gt;
&lt;h2&gt;用 Python 作第三个独立裁判&lt;/h2&gt;
&lt;p&gt;到这一步其实已经够了，但我让 Claude 再做一件事：&lt;strong&gt;别用自己的文字推理下结论，写段 Python 从头算一遍，以程序的数值输出为准。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;下面这段是它写的（我稍微整理了一下）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;math&lt;/span&gt;
&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;230.0&lt;/span&gt;  &lt;span class="c1"&gt;# 1 坐标单位 = 230 英里&lt;/span&gt;

&lt;span class="c1"&gt;# (V, R_per_mile, X, Y) — 内向用个别每英里率，外向统一 0.02346&lt;/span&gt;
&lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;61500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mf"&gt;0.01438521&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.90&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;# 菲尼克斯&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.00791246&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;# 蒙特雷&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;17000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mf"&gt;0.02346330&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;9.10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;# 西雅图&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mf"&gt;0.02346330&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;# 洛杉矶&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mf"&gt;0.02346330&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;5.60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;# 丹佛&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mi"&gt;9500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mf"&gt;0.02346330&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;7.80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.60&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;# 达拉斯&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;29500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mf"&gt;0.02346330&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;10.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.90&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# 芝加哥&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;21000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mf"&gt;0.02346330&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;11.30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.95&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# 亚特兰大&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;41300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mf"&gt;0.02346330&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;14.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.55&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# 纽约&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mi"&gt;8600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mf"&gt;0.02346330&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;12.70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;7.80&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# 多伦多&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mf"&gt;0.02346330&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;14.30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;8.25&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# 蒙特利尔&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# 初始种子&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Xi&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Xi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Yi&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Xi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Yi&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Yi&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Xi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Yi&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Xi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Yi&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Weiszfeld 迭代&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;nx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ny&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Xi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Yi&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;di&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Xi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Y&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Yi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;di&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;1e-6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;di&lt;/span&gt;
        &lt;span class="n"&gt;nx&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Xi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;ny&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Yi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;
    &lt;span class="n"&gt;Xn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Yn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ny&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Xn&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Yn&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;1e-7&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Xn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Yn&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;TC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Xp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Yp&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Xp&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Xi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Yp&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Yi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;V&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Xi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Yi&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;optimum = (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.4f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Y&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.4f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;TC @ KC  (8.20, 6.00) = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;TC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;8.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;6.00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;,.0f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;TC @ Claude(7.42,5.04) = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;TC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;7.42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5.04&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;,.0f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;TC @ Codex (7.58,4.51) = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;TC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;7.58&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.51&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;,.0f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;TC @ optimum          = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;TC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;,.0f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;跑出来的输出：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;optimum = (7.5806, 4.5084)
TC @ KC  (8.20, 6.00) = 6,983,017
TC @ Claude(7.42,5.04) = 6,792,698
TC @ Codex (7.58,4.51) = 6,759,371
TC @ optimum          = 6,759,371
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;几件事一次性水落石出：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Weiszfeld 迭代收敛到 (7.5806, 4.5084)，和 Codex 的 (7.58, 4.51) 逐位对上&lt;/li&gt;
&lt;li&gt;Codex 那份的 TC $6,759,371 在正确方法下也&lt;strong&gt;正好是最优值&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Claude 前一份的 (7.42, 5.04) 在正确方法下 TC 是 $6,792,698——&lt;strong&gt;比最优值贵了 $33K/年&lt;/strong&gt;，连局部最优都不是&lt;/li&gt;
&lt;li&gt;堪萨斯城 TC $6,983,017 和题目给的 $6,982,104 差不到 0.02%（差异来自案例里距离和均值的四舍五入），说明用的参数都没偏&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;然后年节约 $223,646，减去每年 $10 万新增租金，一次性搬迁投入 $30 万，&lt;strong&gt;年 ROI 41.22%，远高于 25% 阈值&lt;/strong&gt;。正确结论是&lt;strong&gt;应当搬迁&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;然后它把自己之前写错的那份改了&lt;/h2&gt;
&lt;p&gt;我让它把修订版直接覆盖原文件，同时在文件顶上标注修订说明。它交出的文件顶部是这样的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;# 案例：南方超级医疗设备公司设施选址 —— 参考答案（修订版）&lt;/span&gt;

&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;修订说明：本版本相对于初版修正了三处方法论错误——&lt;/span&gt;
&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;(1) 将运输费率 R 从「美元/担」换算为「美元/担/英里」；&lt;/span&gt;
&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;(2) 在初始种子 (X₀, Y₀) 的基础上补充 Weiszfeld 迭代；&lt;/span&gt;
&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;(3) 外向费率按案例原文统一取 0.02346 美元/担/英里，而非按客户回推。&lt;/span&gt;
&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;修订后结论从「维持堪萨斯城」转为「应搬迁至 (7.58, 4.51) 附近」。&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;三个情景（Q1 基准、Q2 需求变化、Q3 费率上涨）的结论全部翻过来，ROI 从负值变成 41%–60% 区间，决策从"别搬"变成"都该搬"。&lt;/p&gt;
&lt;h2&gt;为什么「左右互博」比让单一模型深挖更靠谱&lt;/h2&gt;
&lt;p&gt;这件事让我后面做类似作业时固定下来了一套流程：&lt;strong&gt;一份答案让 A 模型做，一份让 B 模型做，再让 A 当裁判评 B，要求它必须回到原始材料（讲义/教材/题目原文）做 ground truth 校对，最后用 Python 当第三方复算。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为什么这么折腾？几个观察：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;单一模型很难发现自己的方法论偏差。&lt;/strong&gt; 如果我一开始就让 Claude 自己审自己那份答案，它大概率会在"既有框架"里找局部的算术错误，而不会去质疑 R 的单位、是否需要迭代这种&lt;strong&gt;框架本身&lt;/strong&gt;的选择。Codex 那份答案的存在提供了一个&lt;strong&gt;不同的框架&lt;/strong&gt;作为参照，这时候回到原题去校对才有意义。换句话说：两个答案的差异不在于谁算得更准，而在于&lt;strong&gt;把可能的解法空间暴露了出来&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;当裁判比当选手更挑剔。&lt;/strong&gt; 让 Claude 做题时它倾向于快速给出一个自洽的流程。让它当裁判时，它会去对照原文、逐字抠公式、做单位分析。同一个模型、同一个任务知识，输出质量差很远——角色不同，防御姿态不同。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Python 兜底是性价比最高的一步。&lt;/strong&gt; 两个模型吵起来的时候，文字推理再漂亮也有可能都错。让它们最后落到一段可执行的代码上，数值一跑就有定论。这次 Python 确认 (7.5806, 4.5084) 是严格最优，也顺手把 Claude 自己给的 (7.42, 5.04) 彻底证伪了——文字说它"连局部最优都不是"显得没说服力，一行 &lt;code&gt;TC(7.42, 5.04) = 6,792,698 &amp;gt; 6,759,371&lt;/code&gt; 谁都没法赖。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;模型之间没有「面子」问题。&lt;/strong&gt; 前面提到我没告诉新会话的 Claude 那份错答案是它自己写的。这是有意为之——同一个账号、同一个模型，它如果意识到是在审自己的作业，有没有可能措辞会软一点？我不敢保证。切掉身份线索，让它&lt;strong&gt;以陌生答卷的姿态&lt;/strong&gt;去评，才放得开。&lt;/p&gt;
&lt;h2&gt;我有不担心的地方，也有担心的地方&lt;/h2&gt;
&lt;p&gt;不担心的是：作业本身是死题、有唯一答案、可以程序化验证。在这类场景下 AI + AI + 代码的组合已经比我自己从头算可靠得多——我自己算大概率也会在哪一步单位换算上犯 Claude 初版犯的那种错。&lt;/p&gt;
&lt;p&gt;担心的是：不是所有作业都有"Python 可跑的 ground truth"。比如案例讨论题让写一份"定性分析报告"的时候，两个模型可能会写出两份看起来都通顺、实则都在偏差范围内的答卷，而我没有外部锚点去判对错。这种时候"左右互博"只能证伪明显的错误，不能正向保证正确。&lt;/p&gt;
&lt;p&gt;但即便如此，&lt;strong&gt;让 AI 看它同行的作业&lt;/strong&gt;依然比&lt;strong&gt;让 AI 看自己的作业&lt;/strong&gt;要诚实。这是我这次学到的最实用的一招。&lt;/p&gt;
&lt;h2&gt;尾声&lt;/h2&gt;
&lt;p&gt;借助 AI ，我弄清楚了重心法为什么要迭代、为什么 R 必须是每英里率——这两件事要是我自己硬看讲义，大概率和 Claude 初版踩一样的坑。把作业交给 AI 之后，我反而把原理学得更明白了。&lt;/p&gt;
&lt;p&gt;这件事我没推广到所有课——有些课的作业本来就是要自己算才算练到手（比如数理课）。但对那些"考的是方法，不是手算熟练度"的课，我现在的默认姿势就是两个模型互评 + 代码兜底。花的时间比自己硬做少得多，踩的坑反而更少。&lt;/p&gt;
&lt;p&gt;下次有类似的折腾再来记录。&lt;/p&gt;</content><category term="misc"/><category term="ai"/><category term="llm"/><category term="claude"/><category term="codex"/><category term="homework"/></entry><entry><title>用 Cloudflare Worker + KV 给静态博客加一个浏览量计数</title><link href="https://www.chenxiaosheng.com/posts/2026-04-19/pageview-counter-with-cf-worker-kv.html" rel="alternate"/><published>2026-04-19T23:00:00+08:00</published><updated>2026-04-19T23:00:00+08:00</updated><author><name>小生说大声讲</name></author><id>tag:www.chenxiaosheng.com,2026-04-19:/posts/2026-04-19/pageview-counter-with-cf-worker-kv.html</id><summary type="html">&lt;p&gt;上一篇刚写完换主题的事，这篇继续给博客加一个浏览量计数。静态站托管在 GitHub Pages 上本来没法做这件事，用 Cloudflare Worker + KV 绕出来。顺带记一下为什么没选不蒜子 / GoatCounter，以及前端「seed 叠加」这个让老文章不从零起算的小机制。&lt;/p&gt;</summary><content type="html">&lt;p&gt;上一篇换主题写完的时候，心里还有一个未了的念头：新主题把 Google Analytics 那坨早就停服的 UA 代码删了之后，整站就零统计了。写的时候没想太多，但上线一两天之后，每次点开自己的博客都有一种「在给空气写字」的感觉 —— 到底还有没有人看，哪篇被多读几遍，完全没有信号。&lt;/p&gt;
&lt;p&gt;于是今晚继续折腾，给每篇文章加一个浏览量，顺带在页脚放一个整站 PV / UV。效果本身没什么稀奇，技术栈和取舍倒值得说一下。&lt;/p&gt;
&lt;h2&gt;静态站做计数的几种路子&lt;/h2&gt;
&lt;p&gt;博客源码在 &lt;code&gt;source&lt;/code&gt; 分支，CI 构建后推到 &lt;code&gt;gh-pages&lt;/code&gt;，GitHub Pages 托管。这个架构的好处是零运维、免费；坏处是 &lt;strong&gt;没有访问日志&lt;/strong&gt;，GitHub 只在 Traffic API 里给 14 天的 top paths，而且要 OAuth 认证，前端根本用不了。&lt;/p&gt;
&lt;p&gt;所以任何「在线计数」都得外挂。我考虑过的选项按接入门槛排列大概是这几类：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方案&lt;/th&gt;
&lt;th&gt;接入&lt;/th&gt;
&lt;th&gt;数据归属&lt;/th&gt;
&lt;th&gt;可控性&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;不蒜子&lt;/td&gt;
&lt;td&gt;粘两行 script&lt;/td&gt;
&lt;td&gt;第三方&lt;/td&gt;
&lt;td&gt;几乎无&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GoatCounter 云版&lt;/td&gt;
&lt;td&gt;注册、粘 script&lt;/td&gt;
&lt;td&gt;第三方&lt;/td&gt;
&lt;td&gt;有仪表板和 API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Umami / Plausible 自托管&lt;/td&gt;
&lt;td&gt;要一台 VPS&lt;/td&gt;
&lt;td&gt;自己&lt;/td&gt;
&lt;td&gt;完全&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare Worker + KV&lt;/td&gt;
&lt;td&gt;写个 Worker&lt;/td&gt;
&lt;td&gt;自己&lt;/td&gt;
&lt;td&gt;完全&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这四类都想过。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不蒜子&lt;/strong&gt; 是中文博客圈最常见的选择，粘两行 JS 就能在页面上显示数字。但它不支持写入初始值、不暴露 API，偶尔还会抽风（&lt;code&gt;busuanzi.ibruce.info&lt;/code&gt; 服务不稳是老问题了）。更关键的是：刚删完一大票第三方脚本（GA、AdSense、Disqus）的新主题，再挂一个外链，心理上拧巴。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GoatCounter&lt;/strong&gt; 其实是符合我审美的选择 —— 开源、隐私友好、有 API、能自建也能用云版。唯一拿不准的是国内访问 &lt;code&gt;goatcounter.com&lt;/code&gt; 的稳定性，以及把读者数据托管给第三方（哪怕它是隐私友好的）仍然是「托管」。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自托管 Umami / Plausible&lt;/strong&gt; 需要一台 VPS 常驻。对一个每月也写不了一两篇的博客，养一台 VPS 就为了这个，是过度消费。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cloudflare Worker + KV&lt;/strong&gt; 最终胜出。几个原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我本来就是 CF 用户（域名 DNS 在 CF），账号、DNS 都是现成的&lt;/li&gt;
&lt;li&gt;Worker 免费档 100k 请求/天、KV 1k 写/天 + 100k 读/天，对个人博客是绰绰有余的天花板&lt;/li&gt;
&lt;li&gt;边缘节点在国内延迟一般不差&lt;/li&gt;
&lt;li&gt;Worker 是一张多用途底牌 —— 未来要做外链跳转计数、RSS 订阅数统计、简单的 webhook 都能复用同一套基础设施&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;唯一的缺点是要自己写代码、自己部署。但需求简单到几十行 TS 就能搞定，还换不来过度工程的烦恼。&lt;/p&gt;
&lt;h2&gt;先用 OpenSpec 把事情说清楚&lt;/h2&gt;
&lt;p&gt;跟上次换主题一样，动手之前我先让 Claude 用 OpenSpec 写了一份变更提案：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;openspec/changes/add-article-view-counts/
├── proposal.md   # 为什么、要改什么、不改什么
├── design.md     # 技术决策与取舍
├── specs/        # 可测试的规范（分三个 capability）
└── tasks.md      # 38 条分步任务
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;好处和上次一样 —— 边界清晰。比如 &lt;code&gt;design.md&lt;/code&gt; 里写死了几个关键决策：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;计数口径 &lt;strong&gt;粗放全算&lt;/strong&gt;：不按 IP 去重，不识别爬虫。我的需求是「大致知道哪些文章有人读」，不是给广告主看的精确数字&lt;/li&gt;
&lt;li&gt;不做 time series：KV 里只存当前累计值。要趋势数据直接看 CF Dashboard 自带的请求图即可&lt;/li&gt;
&lt;li&gt;UV 用 &lt;code&gt;sha256(IP + 当日日期 + salt)&lt;/code&gt; 去重：每日盐轮换，不能反推原 IP，GDPR 友好&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;seed 在前端叠加&lt;/strong&gt;：这是整个方案最值得单独讲的一点&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;seed 前端叠加：让老文章不从零起算&lt;/h2&gt;
&lt;p&gt;启用新计数有个心理坎：所有老文章（最早是 2013 年）都会从 0 开始。十三年前写的博文显示「0 次阅读」，哪怕它本来就没几个人读过，视觉上也很刺眼。&lt;/p&gt;
&lt;p&gt;最直接的办法是在 KV 里预填一个初始值，让 &lt;code&gt;pv:&amp;lt;slug&amp;gt;&lt;/code&gt; 从比如 3000 起算。但这样做有个恶心的地方：哪天想调整初始值（比如觉得这篇估高了），要从当前值里减掉旧 seed 再加新 seed，很容易算错。&lt;/p&gt;
&lt;p&gt;我最终用的办法是 &lt;strong&gt;把 seed 放在前端&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// themes/stuhouse/static/data/view-seeds.json&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;install-bind-mysql-dlz&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;3900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;debian-add-cnnic-ca&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;3900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;pelican-custom-jinja-filters&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;postgraduate&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;peril-of-laziness-lost&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;前端展示时：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;display&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seeds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;slug&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="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&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="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;realtime_pv_from_worker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这样做的好处有三点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;seed 在 git 历史里&lt;/strong&gt;。想调整某篇文章的初始值，就是一次普通的 JSON 编辑 + commit，审计友好&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据层与展示层解耦&lt;/strong&gt;。以后后端换成 GoatCounter / 自建服务，seed 逻辑一行不用改&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;新文章自动 seed = 0&lt;/strong&gt;。JSON 里没这个 slug 就当 0，无需特殊处理&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;seed 数值本身来自一个简单的脚本 &lt;code&gt;scripts/generate_view_seeds.py&lt;/code&gt;，公式是 &lt;code&gt;max(评论数 × 120, 年数 × 300)&lt;/code&gt;。因为没导 Disqus 数据（懒得等那封导出邮件），实际跑的是纯年份兜底 —— 2013 年老文显示 3900 次阅读，2025 年新文显示 300 次，2026 年刚发的新文显示 0。不精确，但读起来自然。&lt;/p&gt;
&lt;h2&gt;Worker 侧：两个端点、几行代码&lt;/h2&gt;
&lt;p&gt;核心逻辑在 &lt;code&gt;worker/src/index.ts&lt;/code&gt;，就两个路由：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// GET /v/&amp;lt;slug&amp;gt; → 单篇文章 PV 自增并返回&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;handleSlug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Env&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 class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&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="sb"&gt;`pv:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cur&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="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VIEWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VIEWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cur&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="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&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="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pv&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;cur&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="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// GET /v/site → 整站 PV + UV&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;handleSite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Env&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 class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ip&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="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;CF-Connecting-IP&amp;quot;&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="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0.0.0.0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;today&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="nx"&gt;ymd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// 站点 PV：粗放自增&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sitePv&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="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VIEWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;site:pv&amp;quot;&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="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10&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="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VIEWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;site:pv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sitePv&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// 当日 UV：hash(ip + date + salt) 作 key 去重&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hash&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="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sha256Hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;|&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;|&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SALT_SECRET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&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;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VIEWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`uv_today:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1&amp;quot;&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 class="nx"&gt;expirationTtl&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;60&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="mf"&gt;60&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="mf"&gt;48&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 class="c1"&gt;// 当日 UV size + 历史归档累计&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;todayCount&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="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;countPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`uv_today:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;:`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;siteUvTotal&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="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VIEWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;site:uv&amp;quot;&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="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&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="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pv&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;sitePv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;uv&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;siteUvTotal&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="nx"&gt;todayCount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;两件事值得注意。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;KV 没有 atomic increment&lt;/strong&gt;。上面的 &lt;code&gt;get → +1 → put&lt;/code&gt; 在并发写入时理论上会丢计数。但这是个人博客，真并发极低，丢一两次也无所谓 —— 这正是「粗放口径」的含义。如果哪天真撞上高并发，再迁 Durable Object 就是，当下不过度设计。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UV 归档用 cron 做&lt;/strong&gt;。每天 UTC 00:05 触发一次 &lt;code&gt;scheduled&lt;/code&gt; 事件，列出昨日所有 &lt;code&gt;uv_today:&amp;lt;date&amp;gt;:*&lt;/code&gt; key 的数量累加到 &lt;code&gt;site:uv&lt;/code&gt;，然后把这些 key 删掉。这样 &lt;code&gt;uv_today:*&lt;/code&gt; 只保留今天一天的数据，不会无限膨胀。&lt;/p&gt;
&lt;h2&gt;部署踩坑：custom_domain 的首次 deploy&lt;/h2&gt;
&lt;p&gt;整个部署靠 wrangler CLI 完成。&lt;code&gt;wrangler.toml&lt;/code&gt; 填好 KV namespace id、secret 绑定、cron 触发器之后，一条 &lt;code&gt;wrangler deploy&lt;/code&gt; 就能上线。但第一次配 custom_domain 时，我走了个小弯路。&lt;/p&gt;
&lt;p&gt;原计划（也是 &lt;code&gt;tasks.md&lt;/code&gt; 里最初写的）是老路子：先在 CF DNS 面板手工加一条 &lt;code&gt;views.chenxiaosheng.com&lt;/code&gt; 的 A/AAAA 占位记录（任意 IP，重点是 Proxy 状态 orange cloud on），然后在 CF Dashboard 配置 Workers Routes 规则。&lt;/p&gt;
&lt;p&gt;后来发现 wrangler 现在有更优雅的写法：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[[routes]]&lt;/span&gt;
&lt;span class="n"&gt;pattern&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="s2"&gt;&amp;quot;views.chenxiaosheng.com&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;custom_domain&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;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;加上 &lt;code&gt;custom_domain = true&lt;/code&gt; 这一行，&lt;code&gt;wrangler deploy&lt;/code&gt; 会自动：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;给这个子域创建 DNS 记录&lt;/li&gt;
&lt;li&gt;向 CF 申请 edge 证书&lt;/li&gt;
&lt;li&gt;把 Worker 绑到该 hostname&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一条命令搞定 DNS + 证书 + 路由三件事，比老路子干净得多。&lt;/p&gt;
&lt;p&gt;有个小注意点：首次 &lt;code&gt;wrangler deploy&lt;/code&gt; 之前需要先 &lt;code&gt;wrangler secret put SALT_SECRET&lt;/code&gt;，但 put secret 时会触发一次占位 deploy。如果 &lt;code&gt;wrangler.toml&lt;/code&gt; 里已经有 &lt;code&gt;custom_domain&lt;/code&gt; 配置而 DNS 还没就绪，会报错。保险做法是先把 &lt;code&gt;[[routes]]&lt;/code&gt; 段注释掉，完成 secret put 和首次 deploy（会部署到 &lt;code&gt;*.workers.dev&lt;/code&gt;），再取消注释重新 deploy 启用 custom domain。我踩过一次之后把这个顺序写进了 &lt;code&gt;tasks.md&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;前端：不到 50 行&lt;/h2&gt;
&lt;p&gt;前端逻辑全在 &lt;code&gt;themes/stuhouse/static/js/views.js&lt;/code&gt;。页面加载后做两件事：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;showSiteStats&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 class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;WORKER_BASE&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="s1"&gt;&amp;#39;/v/site&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;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 class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;site-pv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&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="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;site-uv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&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="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;site-stats-wrap&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;hidden&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 class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* 静默 */&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;showArticlePv&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 class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slug&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&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 class="c1"&gt;// 非文章页跳过&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SEEDS_URL&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;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 class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;WORKER_BASE&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="s1"&gt;&amp;#39;/v/&amp;#39;&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="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&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="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;seeds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pvData&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;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 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="nx"&gt;pvData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&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 class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;total&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seeds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;slug&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="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&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="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pvData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pv&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;article-pv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&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="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;article-pv-wrap&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;hidden&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 class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;几个刻意的设计：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有展示元素初始是 &lt;code&gt;hidden&lt;/code&gt; 的，JS fetch 成功才 reveal。失败就保持隐藏 —— 禁用 JS 的读者看到的页面和接入前完全一致，不会出现占位符或半成品&lt;/li&gt;
&lt;li&gt;两个 fetch 彼此独立，一个失败不影响另一个&lt;/li&gt;
&lt;li&gt;数字用 &lt;code&gt;Intl.NumberFormat('en-US').format()&lt;/code&gt; 加千分位，这行代码比 polyfill 轻多了&lt;/li&gt;
&lt;li&gt;文章页由 &lt;code&gt;&amp;lt;body data-slug="..."&amp;gt;&lt;/code&gt; 带出 slug；非文章页（归档、标签）body 上没这个属性，JS 直接跳过&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;加上 CSS 小样式和一个眼睛图标的 inline SVG，前端总共改动不到 50 行。&lt;/p&gt;
&lt;h2&gt;跑起来之后的样子&lt;/h2&gt;
&lt;p&gt;上线之后，页脚多了一行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;本站累计阅读 1 次 · 访客 1 人
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;点开最早那篇 2013 年的《配置 Bind 使用 MySQL dlz 模式》，meta 行变成：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;📅 2013-09-09 · 约 3 分钟阅读 · 👁 3,901 次阅读
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;seed 3900 加上我刚刷的 1 次。整体效果自然，没有违和感。&lt;/p&gt;
&lt;p&gt;观察几个刻度：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;KV 免费额度&lt;/strong&gt;：单次页面加载触发 1-2 次 fetch，按每日 100 个访客估算，一天产生约 200 次 write，远低于 1000/天的上限&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;延迟&lt;/strong&gt;：Worker 冷启动毫秒级，国内访问通过 CF 边缘节点基本感知不到&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CF Dashboard&lt;/strong&gt;：免费账户自带的 Analytics 已经能看请求量、错误率、cron 触发记录，不需要额外仪表盘&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;写在后面&lt;/h2&gt;
&lt;p&gt;整套方案从 OpenSpec 提案到上线，节奏大致是：晚上跟 Claude 对了一轮选型、起草了 proposal 和 tasks，之后花了一个下午跑完 38 条任务里的 36 条（剩两条是明天早上验证 cron 和 UV 归档用的）。工作量其实不大，但过程里有两个体会想记一下。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;seed 叠加这个 pattern 值得收藏&lt;/strong&gt;。它把「初始化数据」这个一次性需求，从数据层（KV）挪到了展示层（前端 JSON），代价是每次渲染多读一个文件，换来的是可审计、可解耦、可迁移。同样的思路可以用在评论数迁移、积分系统初始化、任何「旧数据 + 新增量」的场景里。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CF Worker 作为静态站的「副引擎」很好使&lt;/strong&gt;。它不改变「源码 + 静态输出」的主流程，却能把那些需要「一点点动态能力」的需求接住 —— 计数、跳转、短链、webhook、简单的 API 代理，都是十几行代码的事。很多人把「加动态能力」和「离开静态站」划等号，其实没必要。&lt;/p&gt;
&lt;p&gt;至于今晚之前那个「在给空气写字」的感觉，暂时还没消失 —— 刚上线，KV 里真实数据也就一两条。但架构至少支持知道答案了，剩下的就交给时间。&lt;/p&gt;</content><category term="misc"/><category term="cloudflare"/><category term="serverless"/><category term="pelican"/><category term="blog"/></entry><entry><title>Claude 在这个周末帮我翻新了博客</title><link href="https://www.chenxiaosheng.com/posts/2026-04-19/claude-refreshed-my-blog-this-weekend.html" rel="alternate"/><published>2026-04-19T20:30:00+08:00</published><updated>2026-04-19T20:30:00+08:00</updated><author><name>小生说大声讲</name></author><id>tag:www.chenxiaosheng.com,2026-04-19:/posts/2026-04-19/claude-refreshed-my-blog-this-weekend.html</id><summary type="html">&lt;p&gt;这个周六晚用 Claude Code + OpenSpec 把博客主题从 2014 年的 Bootstrap 3 换成了新写的 stuhouse（名字随意取的）。顺便回顾了一下不久前那次「双仓库合一」的发布流程改造。两次经历指向同一件事：AI 让我这种拖延了多年的懒人也愿意动手修基础设施了。&lt;/p&gt;</summary><content type="html">&lt;p&gt;上一篇《LLM 让程序员的编程美德「懒惰」更显重要了》发完没几天，自己倒先干了一件极其「懒惰」的事 —— 周六晚让 Claude 把博客主题从 2014 年的 Bootstrap 3 一路翻修到新写的 stuhouse。&lt;/p&gt;
&lt;p&gt;折腾完准备写这篇更新稿的时候，想起前几天那次「双仓库合一」也是 Claude 陪着做的。两次经历放在一起回看，心里有些话想讲。&lt;/p&gt;
&lt;h2&gt;背景：博客的技术债从哪来&lt;/h2&gt;
&lt;p&gt;这个博客从 Pelican 起步就没怎么动过骨架，细数下来积了一堆账：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主题 &lt;code&gt;pelican-bootstrap3&lt;/code&gt; 基于 Bootstrap 3.1.1，2019 年已经 EOL&lt;/li&gt;
&lt;li&gt;FontAwesome 3 的 &lt;code&gt;icon-*&lt;/code&gt; 前缀和后来加载的 FA 4.7 不兼容，图标基本不渲染&lt;/li&gt;
&lt;li&gt;导航栏的 &lt;code&gt;.navbar-collapse&lt;/code&gt; 没有汉堡按钮，移动端扩展菜单打不开&lt;/li&gt;
&lt;li&gt;侧边栏被 &lt;code&gt;hidden-xs hidden-sm&lt;/code&gt; 整体隐藏，手机读者看不到任何侧栏元素&lt;/li&gt;
&lt;li&gt;还挂着一个 2023 年 7 月就停服的 Google Analytics UA 代码&lt;/li&gt;
&lt;li&gt;存在 IE8 兼容分支（&lt;code&gt;respond.js&lt;/code&gt;、&lt;code&gt;jXHR.js&lt;/code&gt;、条件注释）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; 缺 &lt;code&gt;lang&lt;/code&gt; 属性，&lt;code&gt;DEFAULT_LANG='en'&lt;/code&gt; 和中文内容自相矛盾&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;虽然写的很少了，但每次打开博客都在心里念叨一句「改一下吧」，然后关掉浏览器。就这么好几年了。&lt;/p&gt;
&lt;h2&gt;案例一：双仓库合一&lt;/h2&gt;
&lt;p&gt;在讲主题之前，先倒回去讲一下更早的一次改造。&lt;/p&gt;
&lt;p&gt;博客最早的源码放在 Bitbucket，GitHub 上的这个仓库 &lt;code&gt;stutiredboy.github.io&lt;/code&gt; 只存构建产物。这种「源码 + 发布」分离的结构是早年从别人那抄来的，对当时的我来说是合理的：Bitbucket 支持免费私有仓库，GitHub Pages 又必须 public。&lt;/p&gt;
&lt;p&gt;但维护起来很累：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;每次写完文章要先 push 到 Bitbucket，本地 &lt;code&gt;pelican content&lt;/code&gt;，再去 GitHub 仓库 push 生成的 HTML&lt;/li&gt;
&lt;li&gt;两个仓库的 commit history 基本断裂，追溯改动很费劲&lt;/li&gt;
&lt;li&gt;源码仓库没有 CI，发布靠手工&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;2026 年 4 月中旬，我让 Claude 帮我把这个流程整合了：所有源文件迁到 GitHub 这个仓库的 &lt;code&gt;source&lt;/code&gt; 分支，&lt;code&gt;gh-pages&lt;/code&gt; 分支改由 GitHub Actions 自动构建 + force-push。&lt;/p&gt;
&lt;p&gt;提交记录里那条 &lt;code&gt;Migrate Pelican blog source from bitbucket to single-repo structure&lt;/code&gt; 就是这次改造的结果。合并后的工作流简单了不少：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;改完文章 push 到 &lt;code&gt;source&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Actions 自动跑 &lt;code&gt;pelican content&lt;/code&gt;，推到 &lt;code&gt;gh-pages&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;线上站就更新了&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这次改造范围不大，但其实挺花时间，我自己拖了好几年没做。Claude 基本就是 prompt + 15 分钟的事情，包括 deploy workflow 的编排、&lt;code&gt;.gitignore&lt;/code&gt; 的合并、&lt;code&gt;content/extra/CNAME&lt;/code&gt; 怎么放都替我想明白了。&lt;/p&gt;
&lt;h2&gt;案例二：这个周末，换主题&lt;/h2&gt;
&lt;p&gt;这次范围大得多。&lt;/p&gt;
&lt;h3&gt;用 OpenSpec 先把事情说清楚&lt;/h3&gt;
&lt;p&gt;技术债那么一大堆，要是自己硬上，大概率会陷入「改一点发现另一个地方也得改」的泥潭，特别是改主题，人肉改起来是真累。这次换主题之前，我先用 OpenSpec 的工作流写了一份变更提案：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;openspec/changes/modernize-blog-theme/
├── proposal.md   # 为什么要改、改什么、不改什么
├── design.md     # 新主题的视觉和技术决策
└── tasks.md      # 分阶段任务清单
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;proposal 里最关键的是 Non-Goals 那一段，明确写了「不引入站内搜索、不引入新评论系统、不做 PWA、不保留旧主题做 A/B 切换」。有了这段边界声明，后面实现的时候就不会被无关想法拉偏。&lt;/p&gt;
&lt;h3&gt;新主题 stuhouse&lt;/h3&gt;
&lt;p&gt;新主题目录是 &lt;code&gt;themes/stuhouse&lt;/code&gt;，整体思路：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单列居中，正文最大宽度约 720 px&lt;/li&gt;
&lt;li&gt;极简顶栏：站名、归档、简介、RSS、明暗切换&lt;/li&gt;
&lt;li&gt;桌面宽屏（≥ 1200 px）右侧有 sticky TOC，移动端折叠&lt;/li&gt;
&lt;li&gt;彻底去掉侧边栏，标签云迁到独立的 &lt;code&gt;/tags.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;中文系统字体栈（PingFang SC → HarmonyOS Sans SC → 微软雅黑 → system-ui），零字体请求&lt;/li&gt;
&lt;li&gt;暗色模式跟随 &lt;code&gt;prefers-color-scheme&lt;/code&gt; + 顶栏手动切换，localStorage 持久化&lt;/li&gt;
&lt;li&gt;代码块基于 Nord 配色，浅色、深色两套，带一键复制按钮&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;技术栈清理得更彻底：Bootstrap 3、jQuery、FontAwesome、respond.js、jXHR.js 全删；图标改成内联 SVG；Google Analytics / Disqus / AdSense 相关代码一并下线。&lt;/p&gt;
&lt;h3&gt;一个中间的小坑&lt;/h3&gt;
&lt;p&gt;周六 23 点开始挂着 claude 跑，周日早上起来直接可以人工验收了。按 Claude 提示启动 &lt;code&gt;pelican --listen&lt;/code&gt;，浏览器打开 &lt;code&gt;http://127.0.0.1:8000&lt;/code&gt;，样式完全不对（以下修复还是 Claude 干的）。&lt;/p&gt;
&lt;p&gt;开发者工具一看：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;GET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;www&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chenxiaosheng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;
&lt;span class="o"&gt;=&amp;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;FAILED&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;net&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ERR_BLOCKED_BY_ORB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;原因是 &lt;code&gt;pelicanconf.py&lt;/code&gt; 里 &lt;code&gt;SITEURL = 'https://www.chenxiaosheng.com'&lt;/code&gt; 直接写死成生产域名，本地生成的 HTML 里所有资源链接都指向线上，自然加载不到新主题的 CSS。&lt;/p&gt;
&lt;p&gt;Pelican 社区的标准做法是拆分 dev/prod 两份配置：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pelicanconf.py&lt;/code&gt;：&lt;code&gt;SITEURL = ''&lt;/code&gt;，本地预览用，生成根相对 URL&lt;/li&gt;
&lt;li&gt;&lt;code&gt;publishconf.py&lt;/code&gt;：继承 pelicanconf，覆盖 &lt;code&gt;SITEURL&lt;/code&gt; 为真实域名，CI 构建用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;顺带把 GitHub Actions workflow 的构建命令从 &lt;code&gt;-s pelicanconf.py&lt;/code&gt; 换成 &lt;code&gt;-s publishconf.py&lt;/code&gt;。再跑 &lt;code&gt;pelican --listen&lt;/code&gt;，样式全部到位。&lt;/p&gt;
&lt;h3&gt;归档&lt;/h3&gt;
&lt;p&gt;最后把 &lt;code&gt;openspec/changes/modernize-blog-theme/&lt;/code&gt; 迁到 &lt;code&gt;openspec/changes/archive/2026-04-19-modernize-blog-theme/&lt;/code&gt;，这个变更就算完结了。tasks.md 里的每一项都对应真实的代码改动，想回溯某个决策随时能翻到上下文。&lt;/p&gt;
&lt;h2&gt;用 AI 修基础设施的一点体感&lt;/h2&gt;
&lt;p&gt;两次改造拉通一起看，有几点观察。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI 缩短了「想做」和「开动」之间的距离。&lt;/strong&gt; 双仓库合一这件事我拖了好几年，主题翻修拖得更久。之所以一直不动，是因为每次想起来都要估算「我得花多久」「会不会中途卡在某个坑里」。有了 Claude 之后，这个心理门槛明显降低：哪怕卡住，它能帮我很快试出方向，沉没成本低得多。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;但 AI 不会替我做判断。&lt;/strong&gt; OpenSpec 这种「先写提案再动手」的节奏，核心作用是逼我自己把事情想清楚 —— Non-Goals 该写哪些、视觉调性参考谁、侧边栏留不留。这些决策 Claude 给不了，它最多帮我把判断落成代码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;「懒惰」这事，AI 和人的方向是反的。&lt;/strong&gt; 上一篇文章里引用了 Bryan Cantrill 的观点：LLM 不受时间约束，没有「为未来的自己省力」的动机，放任它会让系统越滚越大。我这次刻意在 proposal 里写死边界，就是给它上一道缰绳。最终 stuhouse 的代码量比旧主题少了一大截，就是这个约束的结果。&lt;/p&gt;
&lt;h2&gt;尾声&lt;/h2&gt;
&lt;p&gt;这篇文章本身是在新主题下写的。预览窗口里看过去，正文宽度、代码块配色、TOC 高亮、复制按钮都按预期工作。写博客这件事本来应该只关心文字本身，过去那些年花在「发布流程怎么跑通」「主题哪里又坏了」上的精力，现在终于可以还给写作本身了。&lt;/p&gt;
&lt;p&gt;至于下一篇，等下一篇再见。&lt;/p&gt;</content><category term="misc"/><category term="claude"/><category term="ai"/><category term="pelican"/><category term="blog"/></entry><entry><title>LLM 让程序员的编程美德“懒惰”更显重要了</title><link href="https://www.chenxiaosheng.com/posts/2026-04-15/peril-of-laziness-lost.html" rel="alternate"/><published>2026-04-15T21:30:00+08:00</published><updated>2026-04-15T21:30:00+08:00</updated><author><name>小生说大声讲</name></author><id>tag:www.chenxiaosheng.com,2026-04-15:/posts/2026-04-15/peril-of-laziness-lost.html</id><summary type="html">&lt;p&gt;Bryan Cantrill 在最新博文中指出，LLM 天生缺乏程序员的核心美德——懒惰。懒惰驱使程序员构建精炼的抽象，而 LLM 不受时间约束，只会让系统越来越臃肿。本文以半翻译半解读的形式梳理这篇文章的核心观点。&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;本文基于 Bryan Cantrill 的博文 &lt;a href="https://bcantrill.dtrace.org/2026/04/12/the-peril-of-laziness-lost/"&gt;The Peril of Laziness Lost&lt;/a&gt; 进行半翻译半解读。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最近读到 Bryan Cantrill 的一篇博文，标题叫 &lt;em&gt;The Peril of Laziness Lost&lt;/em&gt;（「懒惰」丧失之险）。Cantrill 是 DTrace 的共同作者、Sun Microsystems 前工程师、Oxide Computer 的联合创始人，在系统编程领域是教父级的人物。这篇文章从 Larry Wall 提出的「程序员三大美德」出发，讨论了 LLM 对软件工程的深层影响。读完之后觉得切中要害，值得展开聊聊。&lt;/p&gt;
&lt;h2&gt;程序员的三大美德&lt;/h2&gt;
&lt;p&gt;Larry Wall 在经典著作 &lt;em&gt;Programming Perl&lt;/em&gt;（业界称为「骆驼书」）中提出了程序员的三大美德：&lt;strong&gt;懒惰（Laziness）、急躁（Impatience）和傲慢（Hubris）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;原文引用如下：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If we're going to talk about good software design, we have to talk about Laziness, Impatience, and Hubris, the basis of good software design. We've all fallen into the trap of using cut-and-paste when we should have defined a higher-level abstraction, if only just a loop or subroutine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这三个词乍听像是贬义，但 Larry Wall 赋予了它们完全不同的含义。其中「懒惰」最为深刻：它不是说程序员偷懒不干活，而是说好的程序员会因为「懒得重复做同一件事」，从而被驱动着去构建更高层次的抽象，让系统变得尽可能简洁。&lt;/p&gt;
&lt;p&gt;Cantrill 对此的理解是：&lt;strong&gt;懒惰驱使我们把系统做到尽可能简单（但不能过于简单）——去开发强大的抽象，从而让更多的事情变得更容易。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;懒惰的悖论：偷懒需要付出大量努力&lt;/h2&gt;
&lt;p&gt;这里有一个精妙的悖论：&lt;strong&gt;要做到「懒」，其实需要非常努力。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当程序员看似在「摸鱼」——躺在吊床上发呆、盯着天花板想问题——实际上是在反复推敲问题的本质。这种「吊床驱动开发」（hammock-driven development）是真正的智力劳动。程序员愿意在当下花费大量时间去打磨抽象，是因为在优化「未来的自己」的时间。当这个计算做对了，效果是辉煌的：好的抽象不仅服务于自己，还惠及所有后来者。&lt;/p&gt;
&lt;p&gt;换句话说，&lt;strong&gt;程序员的懒惰让软件更容易编写，让系统更容易组合，让更多人能写出更多好的软件。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Brogrammer 文化：虚假的勤奋&lt;/h2&gt;
&lt;p&gt;然而，过去二十年软件行业的扩张带来了一种不那么健康的文化。现代抽象带来的巨大生产力催生了一种对「虚假勤奋」的推崇——Cantrill 称之为 brogrammer 文化。在这种文化里，「吊床上的深度思考」被「crushing code」的鸡血叙事取代。人们不再关心抽象是否优雅，而是关心写了多少行代码、提了多少个 PR、一天搞定了几个 feature。&lt;/p&gt;
&lt;h2&gt;LLM：给 Brogrammer 注射类固醇&lt;/h2&gt;
&lt;p&gt;LLM 的出现，在这堆干柴上劈了一道闪电。&lt;/p&gt;
&lt;p&gt;Cantrill 的原话是：LLM 就像 brogrammer 群体的合成代谢类固醇（anabolic steroids）。无论一个人对软件开发持什么态度，LLM 都能放大这种态度的力量。于是那些本来就痴迷于「产出量」的人，现在更加不可收拾了。&lt;/p&gt;
&lt;p&gt;他举了一个例子：Y Combinator 总裁 Garry Tan 在社交媒体上炫耀自己一天写了 37,000 行代码，还说「仍在加速」。&lt;/p&gt;
&lt;p&gt;&lt;img alt="37k a day bro" src="/static/peril-of-laziness-lost/37k-a-day-bro.webp"&gt;&lt;/p&gt;
&lt;p&gt;Cantrill 不动声色地补了一句作为对比：&lt;strong&gt;整个 DTrace 项目的代码量大约也就六万行左右。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;用行数衡量软件的价值，就好比用重量衡量文学作品的质量。这个谬误即便是初学者也看得出来。&lt;/p&gt;
&lt;h2&gt;Garry Tan 的「作品」被拆解&lt;/h2&gt;
&lt;p&gt;至于 Tan 用如此疯狂的速度构建的产物——一个「newsletter-blog-thingy」——波兰工程师 Gregorein 对其做了一次拆解。结果既意料之中，又令人捧腹：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单次页面加载就包含了多个测试框架（test harnesses）&lt;/li&gt;
&lt;li&gt;混入了 Rails 的 Hello World 应用&lt;/li&gt;
&lt;li&gt;藏了一个文本编辑器&lt;/li&gt;
&lt;li&gt;同一个 logo 出现了八个不同版本，其中一个的文件大小为 0 字节&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cantrill 指出，问题不在于这些具体的 bug（它们都可以修复），甚至不在于有人认为这种方法论代表了软件工程的未来（虽然这确实很烦人）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;真正的问题是：LLM 天生缺乏「懒惰」这一美德。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;核心论点：LLM 不懂懒惰&lt;/h2&gt;
&lt;p&gt;这是整篇文章最精华的部分。&lt;/p&gt;
&lt;p&gt;对 LLM 而言，工作是零成本的。LLM 不会觉得需要为自己（或任何人）的未来时间做优化。它会毫不犹豫地往一个越来越高的垃圾蛋糕上继续堆料。如果不加约束，LLM 只会让系统变得更大，而不是更好——也许能满足某些扭曲的虚荣指标，但代价是所有真正重要的东西。&lt;/p&gt;
&lt;p&gt;这恰恰凸显了人类的「懒惰」有多关键：&lt;strong&gt;正因为我们的时间有限，才迫使我们去开发精炼的抽象。&lt;/strong&gt; 我们不愿意把自己宝贵的（人类的！）时间浪费在笨拙抽象带来的后果上。最好的工程总是源于约束，而时间的约束限制了我们愿意承受的系统认知负荷。这才是驱动我们把系统做得更简单的力量，尽管系统本身的复杂性是内在的。&lt;/p&gt;
&lt;p&gt;LLM 不受时间约束，也不承受认知负荷，因此不能指望它们主动去追求简洁。&lt;/p&gt;
&lt;h2&gt;LLM 仍然是重要的工具&lt;/h2&gt;
&lt;p&gt;Cantrill 并没有否定 LLM 的价值。他明确说：LLM 是软件工程的非凡工具——但归根结底，它只是工具。&lt;/p&gt;
&lt;p&gt;正确的使用方式是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用 LLM 处理程序员「非美德性的懒惰」——比如帮助清理技术债务这类棘手但缺乏动力去做的事&lt;/li&gt;
&lt;li&gt;用 LLM 提升工程严谨性&lt;/li&gt;
&lt;li&gt;但这一切必须服务于程序员自身的「美德性懒惰」：产出更简洁、更强大的系统，不仅服务于当下的自己，也服务于未来一代又一代的软件工程师&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;个人感想&lt;/h2&gt;
&lt;p&gt;读完这篇文章，最大的感触是 Cantrill 精准地指出了 LLM 时代的核心矛盾：&lt;strong&gt;工具变强了，但使用工具的判断力并没有跟着变强。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;LLM 让「写代码」的边际成本趋近于零，但「思考应该写什么代码」的成本一点都没降低。如果把 LLM 当成代码的自动生成器来追求产出量，最终只会得到一个体积庞大但内在混乱的系统。真正应该做的，是把 LLM 节省下来的时间投入到更深层的思考中去——去琢磨更好的抽象、更简洁的设计。&lt;/p&gt;
&lt;p&gt;Larry Wall 几十年前提出的「懒惰是美德」，在 LLM 时代反而变得更加重要了。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Bryan Cantrill, &lt;a href="https://bcantrill.dtrace.org/2026/04/12/the-peril-of-laziness-lost/"&gt;The Peril of Laziness Lost&lt;/a&gt;, 2026-04-12&lt;/li&gt;
&lt;li&gt;Larry Wall, &lt;em&gt;Programming Perl&lt;/em&gt; (The Camel Book)&lt;/li&gt;
&lt;/ul&gt;</content><category term="misc"/><category term="ai"/><category term="llm"/><category term="programming"/></entry><entry><title>.claude/ 文件夹解剖</title><link href="https://www.chenxiaosheng.com/posts/2026-03-23/anantomy-of-the-claude-folder.html" rel="alternate"/><published>2026-03-23T19:36:59+08:00</published><updated>2026-03-23T19:36:59+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2026-03-23:/posts/2026-03-23/anantomy-of-the-claude-folder.html</id><summary type="html">&lt;p&gt;大多数团队都在某种程度上采用了 AI，但“使用 AI”与“从 AI 中获得可衡量的 ROI”之间的差距，比人们想象得更大。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://fandf.co/3NR30kc"&gt;Postman&lt;/a&gt;&lt;/strong&gt; 发布了一份 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;大多数团队都在某种程度上采用了 AI，但“使用 AI”与“从 AI 中获得可衡量的 ROI”之间的差距，比人们想象得更大。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://fandf.co/3NR30kc"&gt;Postman&lt;/a&gt;&lt;/strong&gt; 发布了一份成本节省分析，评估了六种常见的 API 开发工作流，并对比了“平台内置 AI”与“外部加挂 AI”在实际时间和成本上的差异。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image 1" src="/static/anatomy-of-the-claude-folder/image_1.png"&gt;&lt;/p&gt;
&lt;p&gt;这是一篇简短、数据驱动的阅读材料，能帮助工程负责人判断：AI 原生工具究竟在哪些环节真正能撬动效率。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://fandf.co/3NR30kc"&gt;你可以在这里免费下载这份指南 →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;感谢 Postman 今日赞助！&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Claude Code 的用户通常把 &lt;code&gt;.claude&lt;/code&gt; 文件夹当成黑盒。知道它存在，也见过它出现在项目根目录里，但几乎没人真正打开看过，更别说理解里面每个文件的作用。&lt;/p&gt;
&lt;p&gt;这是个被错过的机会。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.claude&lt;/code&gt; 文件夹其实是你在项目里“控制 Claude 行为”的中枢。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image 2" src="/static/anatomy-of-the-claude-folder/image_2.png"&gt;&lt;/p&gt;
&lt;p&gt;它保存了你的指令、自定义命令、权限规则，甚至还有 Claude 跨会话的记忆。理解每个文件的位置和意义之后，你就可以让 Claude Code 完全按你团队的方式运行。&lt;/p&gt;
&lt;p&gt;这篇文章会带你完整拆解这个文件夹，从你每天会用到的文件，到你只需设置一次就能忘记的部分。&lt;/p&gt;
&lt;p&gt;在开始之前，有一点要先知道：实际上有两个 .claude 目录，而不是一个。&lt;/p&gt;
&lt;p&gt;一个在项目里，另一个在你的 home 目录：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image 3" src="/static/anatomy-of-the-claude-folder/image_3.png"&gt;&lt;/p&gt;
&lt;p&gt;项目级的文件夹保存团队配置。你会把它提交到 git，所有人都用同一套规则、同一组自定义命令、同一份权限策略。&lt;/p&gt;
&lt;p&gt;全局的 &lt;code&gt;~/.claude/&lt;/code&gt; 文件夹保存你的个人偏好和本机状态，比如会话历史和自动记忆。&lt;/p&gt;
&lt;p&gt;这是整个系统里最关键的文件。你启动 Claude Code 会话时，它第一个读取的就是 &lt;code&gt;CLAUDE.md&lt;/code&gt;。它会被直接加载进系统提示词，并贯穿整个对话。&lt;/p&gt;
&lt;p&gt;说白了：你写在 &lt;code&gt;CLAUDE.md&lt;/code&gt; 里的内容，Claude 就会遵守。&lt;/p&gt;
&lt;p&gt;你告诉它“先写测试再写实现”，它就会这么做。你写“禁止用 console.log 做错误处理，要用自定义 logger 模块”，它每次都会遵循。&lt;/p&gt;
&lt;p&gt;最常见的做法是在项目根目录放一个 &lt;code&gt;CLAUDE.md&lt;/code&gt;。但你也可以在 &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt; 放一个全局偏好，甚至在子目录里放局部规则。Claude 会读取它们并自动合并。&lt;/p&gt;
&lt;p&gt;多数人不是写得太多，就是写得太少。下面是合理的取舍。&lt;/p&gt;
&lt;h5&gt;建议写：&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;构建、测试、Lint 命令（如 npm run test、make build 等）&lt;/li&gt;
&lt;li&gt;关键架构决策（比如“我们使用 Turborepo 的 Monorepo”）&lt;/li&gt;
&lt;li&gt;不显然的坑（比如“TypeScript 开启 strict，未使用变量会报错”）&lt;/li&gt;
&lt;li&gt;导入约定、命名规范、错误处理风格&lt;/li&gt;
&lt;li&gt;核心模块的文件与目录结构&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;不建议写：&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;应该放在 Linter 或 Formatter 配置里的内容&lt;/li&gt;
&lt;li&gt;已有文档、可以直接链接的完整说明&lt;/li&gt;
&lt;li&gt;长篇大论的理论解释&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把 &lt;code&gt;CLAUDE.md&lt;/code&gt; 控制在 200 行以内。文件太长会消耗上下文，Claude 的指令遵循度反而会下降。&lt;/p&gt;
&lt;p&gt;原文此处有一个“简短但有效”的示例（截图展示）。&lt;/p&gt;
&lt;p&gt;大约 20 行，足以让 Claude 在这个代码库里高效工作，而不需要不断追问。&lt;/p&gt;
&lt;p&gt;有时你的偏好只属于你本人，不适合全团队。比如你偏好某种测试框架，或者希望 Claude 打开文件时遵循某个固定模式。&lt;/p&gt;
&lt;p&gt;在项目根目录创建 &lt;code&gt;CLAUDE.local.md&lt;/code&gt;。Claude 会和主 &lt;code&gt;CLAUDE.md&lt;/code&gt; 一起读取，但这个文件会自动被 gitignore，不会进入仓库。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image 4" src="/static/anatomy-of-the-claude-folder/image_4.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; 适合单一项目。但一旦团队扩大，你很容易得到一个 300 行、没人维护、人人忽视的 &lt;code&gt;CLAUDE.md&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rules/&lt;/code&gt; 文件夹就是为了解决这个问题。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.claude/rules/&lt;/code&gt; 里的每个 Markdown 文件都会和 &lt;code&gt;CLAUDE.md&lt;/code&gt; 一起加载。你可以按主题拆分指令：&lt;/p&gt;
&lt;p&gt;每个文件都更聚焦、更容易更新。负责 API 规范的人只改 &lt;code&gt;api-conventions.md&lt;/code&gt;，负责测试标准的人改 &lt;code&gt;testing.md&lt;/code&gt;，互不干扰。&lt;/p&gt;
&lt;p&gt;真正的威力在“路径作用域规则”。在规则文件里加一个 YAML frontmatter，就可以让它只在匹配路径时生效：&lt;/p&gt;
&lt;p&gt;比如它不会在编辑 React 组件时加载，而只在处理 &lt;code&gt;src/api/&lt;/code&gt; 或 &lt;code&gt;src/handlers/&lt;/code&gt; 时生效。没有 &lt;code&gt;paths&lt;/code&gt; 的规则会在每次会话中无条件加载。&lt;/p&gt;
&lt;p&gt;当 &lt;code&gt;CLAUDE.md&lt;/code&gt; 开始变得臃肿时，这就是正确的拆分方式。&lt;/p&gt;
&lt;p&gt;Claude Code 自带一些内置斜杠命令，比如 &lt;code&gt;/help&lt;/code&gt; 和 &lt;code&gt;/compact&lt;/code&gt;。&lt;code&gt;commands/&lt;/code&gt; 文件夹允许你自定义命令。&lt;/p&gt;
&lt;p&gt;你在 &lt;code&gt;.claude/commands/&lt;/code&gt; 里放一个 Markdown 文件，它就会变成一个斜杠命令。&lt;/p&gt;
&lt;p&gt;比如 &lt;code&gt;review.md&lt;/code&gt; 会生成 &lt;code&gt;/project:review&lt;/code&gt;，&lt;code&gt;fix-issue.md&lt;/code&gt; 会生成 &lt;code&gt;/project:fix-issue&lt;/code&gt;。文件名就是命令名。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image 5" src="/static/anatomy-of-the-claude-folder/image_5.png"&gt;&lt;/p&gt;
&lt;p&gt;原文这里给了一个简单示例：创建 &lt;code&gt;.claude/commands/review.md&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;然后在 Claude Code 里运行 &lt;code&gt;/project:review&lt;/code&gt;，它会自动把真实的 git diff 注入到提示词中。反引号里的 &lt;code&gt;!&lt;/code&gt; 语法会运行 shell 命令并把输出嵌入。这就是命令真正有用的原因，而不只是“保存一段文本”。&lt;/p&gt;
&lt;p&gt;如果你需要传参，可以用 &lt;code&gt;$ARGUMENTS&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;运行 &lt;code&gt;/project:fix-issue 234&lt;/code&gt; 会把 issue 234 的内容直接送进提示词。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.claude/commands/&lt;/code&gt; 里的项目命令会提交到仓库，和团队共享。若想在所有项目通用，把命令放到 &lt;code&gt;~/.claude/commands/&lt;/code&gt;，它会显示为 &lt;code&gt;/user:command-name&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;一个很实用的个人命令：每日站会助手、按你约定格式生成提交信息、或者快速安全扫描。&lt;/p&gt;
&lt;p&gt;你已经了解命令。技能看起来很像，但触发方式完全不同。先看这张图的区别：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image 6" src="/static/anatomy-of-the-claude-folder/image_6.png"&gt;&lt;/p&gt;
&lt;p&gt;技能是 Claude 可以自己调用的工作流，不需要你敲斜杠命令，只要当前任务匹配技能描述，它就会自动触发。命令是你手动触发，技能是它自动判断。&lt;/p&gt;
&lt;p&gt;每个技能都在自己的子目录里，包含一个 &lt;code&gt;SKILL.md&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SKILL.md&lt;/code&gt; 用 YAML frontmatter 描述“什么时候使用这个技能”。&lt;/p&gt;
&lt;p&gt;当你说“帮我审这个 PR 的安全问题”，Claude 会读取描述，判断匹配，于是自动调用这个技能。你也可以显式调用，比如 &lt;code&gt;/security-review&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;和命令最大的区别：技能可以打包一堆辅助文件。上面提到的 &lt;code&gt;DETAILED_GUIDE.md&lt;/code&gt; 就是和 &lt;code&gt;SKILL.md&lt;/code&gt; 同目录的参考资料。命令只是单个文件，而技能是一整个包。&lt;/p&gt;
&lt;p&gt;个人技能放在 &lt;code&gt;~/.claude/skills/&lt;/code&gt; 下，跨项目通用。&lt;/p&gt;
&lt;p&gt;当任务复杂到值得一个专门专家时，可以在 &lt;code&gt;.claude/agents/&lt;/code&gt; 定义子代理。每个代理是一个 Markdown 文件，包含它自己的系统提示词、可用工具、以及模型偏好：&lt;/p&gt;
&lt;p&gt;下面是一个 &lt;code&gt;code-reviewer.md&lt;/code&gt; 的样子。&lt;/p&gt;
&lt;p&gt;当 Claude 需要做代码审查时，它会在独立上下文里启动这个代理。代理完成工作后会压缩结论再汇报，这样你的主会话不会被几千 tokens 的中间过程撑爆。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tools&lt;/code&gt; 字段限制代理能做什么。安全审计只需要 Read、Grep、Glob，完全不该写文件。这个限制是刻意为之。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;model&lt;/code&gt; 字段让你选择更便宜、更快的模型来做聚焦任务。Haiku 处理读操作够用，把 Sonnet/Opus 留给真正需要能力的地方。&lt;/p&gt;
&lt;p&gt;个人代理放在 &lt;code&gt;~/.claude/agents/&lt;/code&gt; 下，跨项目可用。&lt;/p&gt;
&lt;p&gt;&lt;img alt="Image 7" src="/static/anatomy-of-the-claude-folder/image_7.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.claude&lt;/code&gt; 里的 &lt;code&gt;settings.json&lt;/code&gt; 控制 Claude 允许做什么、不允许做什么。它定义了哪些工具可以用、哪些文件能读，以及哪些命令需要先询问。&lt;/p&gt;
&lt;p&gt;完整文件长这样（原文此处为截图）。&lt;/p&gt;
&lt;p&gt;各部分的含义如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;$schema&lt;/code&gt;&lt;/strong&gt; 行开启 VS Code 或 Cursor 的自动补全与校验，务必保留。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;allow list&lt;/strong&gt;：允许 Claude 无需确认即可运行的命令。典型选择包括：&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Bash(npm run *)&lt;/code&gt; 或 &lt;code&gt;Bash(make *)&lt;/code&gt;，让 Claude 能自由执行脚本&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Bash(git *)&lt;/code&gt;，用于只读 git 操作&lt;/li&gt;
&lt;li&gt;Read、Write、Edit、Glob、Grep 等文件操作&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;deny list&lt;/strong&gt;：无条件禁止的命令。合理的 deny list 会阻止：&lt;ul&gt;
&lt;li&gt;破坏性命令，比如 &lt;code&gt;rm -rf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;直接网络命令，比如 &lt;code&gt;curl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;敏感文件，比如 &lt;code&gt;.env&lt;/code&gt; 和 &lt;code&gt;secrets/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不在 allow 或 deny 列表里的命令，Claude 会先询问再执行。这种“中间地带”是有意设计的：既有安全网，又不需要你预判所有命令。&lt;/p&gt;
&lt;p&gt;另外你也可以用 &lt;code&gt;settings.local.json&lt;/code&gt; 做个人覆盖。创建 &lt;code&gt;.claude/settings.local.json&lt;/code&gt;，它会被自动 gitignore，不进入仓库。&lt;/p&gt;
&lt;p&gt;你很少手动管理这个文件夹，但最好知道它们存在。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt; 会加载到每个 Claude Code 会话里，跨项目生效。适合放你的个人编码原则、偏好风格等。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;~/.claude/projects/&lt;/code&gt; 保存项目的会话记录和自动记忆。Claude Code 会把它学到的命令、模式、架构信息自动存下来并跨会话保留。你可以用 &lt;code&gt;/memory&lt;/code&gt; 浏览和编辑。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;~/.claude/commands/&lt;/code&gt; 和 &lt;code&gt;~/.claude/skills/&lt;/code&gt; 保存你的个人命令和技能，跨项目可用。&lt;/p&gt;
&lt;p&gt;你通常不需要手动整理这些，但知道它们的存在很有用：当 Claude 看起来“记住”了你没明确说过的东西，或者你想清空某个项目的自动记忆、重新开始时，就知道该去哪。&lt;/p&gt;
&lt;p&gt;最后把一切串起来：如果你从零开始，以下步骤非常实用。&lt;/p&gt;
&lt;p&gt;Step 1. 在 Claude Code 里运行 &lt;code&gt;/init&lt;/code&gt;，它会读取项目并生成一个初始 &lt;code&gt;CLAUDE.md&lt;/code&gt;，然后你把它精简到关键点。&lt;/p&gt;
&lt;p&gt;Step 2. 添加 &lt;code&gt;.claude/settings.json&lt;/code&gt;，设置合适的 allow/deny 规则。至少允许运行命令，禁止读取 .env。&lt;/p&gt;
&lt;p&gt;Step 3. 创建一两个最常用的命令，比如代码审查或修复 issue。&lt;/p&gt;
&lt;p&gt;Step 4. 当 &lt;code&gt;CLAUDE.md&lt;/code&gt; 变拥挤时，把指令拆到 &lt;code&gt;.claude/rules/&lt;/code&gt;，并按路径做作用域。&lt;/p&gt;
&lt;p&gt;Step 5. 在 &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt; 写入个人偏好，比如“先写类型再写实现”“更偏函数式而不是类”。&lt;/p&gt;
&lt;p&gt;这对 95% 的项目已经够用了。技能和代理只在你有反复出现的复杂工作流、值得打包时再引入。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.claude&lt;/code&gt; 文件夹本质上是一套协议：告诉 Claude 你是谁、项目做什么、它需要遵守哪些规则。定义得越清晰，你纠正 Claude 的时间就越少，它做的有用工作就越多。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; 是你杠杆最大的文件。先把它写对，其他都是优化。&lt;/p&gt;
&lt;p&gt;从小做起，逐步精炼，把它当成项目里的基础设施：一旦搭好，每天都会持续带来收益。&lt;/p&gt;
&lt;p&gt;感谢阅读！英文原文：https://blog.dailydoseofds.com/p/anatomy-of-the-claude-folder&lt;/p&gt;</content><category term="misc"/><category term="claude"/><category term="ai"/></entry><entry><title>阿里云域名解析被转移，让我穿越回了 15 年前</title><link href="https://www.chenxiaosheng.com/posts/2025-06-06/aliyuncs.com-downgrade.html" rel="alternate"/><published>2025-06-06T20:30:00+08:00</published><updated>2025-06-06T20:30:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2025-06-06:/posts/2025-06-06/aliyuncs.com-downgrade.html</id><summary type="html">&lt;p&gt;6月6日凌晨，阿里云核心域名 aliyuncs.com遭遇罕见的域名劫持事件，导致其对象存储服务（OSS）、内容分发网络（CDN）以及云解析DNS等多项核心云服务出现大范围故障，波及众多依赖阿里云服务的网站和应用。&lt;/p&gt;</summary><content type="html">&lt;p&gt;2025 年 6 月 6 日早上 6 点 10 分醒来，习惯性瞄了下手机，看到同事给我的留言，大概是说 &lt;code&gt;aliyuncs.com&lt;/code&gt; 被别的国家注册了&lt;/p&gt;
&lt;p&gt;作为一名曾经的域名管理员，我对域名类的故障是比较 &lt;strong&gt;敏（害）感（怕）&lt;/strong&gt; 的，因此基本上我是短时间就清醒了的，抛出了我的第一个疑问：&lt;strong&gt;在用的域名忽然被别人注册了&lt;/strong&gt;？&lt;/p&gt;
&lt;p&gt;同事反馈是过期没续费被抢，但是 &lt;strong&gt;域名过期是有保护期&lt;/strong&gt; 的，所以我并没有接受这个说法，在手机上通过 &lt;a href="https://who.is"&gt;who.is&lt;/a&gt; 查了一下信息，发现过期时间是 &lt;code&gt;2026-04-01&lt;/code&gt;，既然都还没有过期，那被别人注册了这个说法自然是不成立的。&lt;/p&gt;
&lt;p&gt;&lt;img src="/static/aliyuncs.com_1.png" /&gt;&lt;/p&gt;
&lt;p&gt;继续追问了下目前可能对我们公司业务的影响，了解到如果负载均衡 LB &lt;code&gt;Load Balance&lt;/code&gt; 有可能直接用了阿里的域名，这下我就不太淡定了，继续用 &lt;a href="https://www.itdog.cn"&gt;itdog&lt;/a&gt; 试了一下，发现有个别缓存开始失效了。结合可能对我们的影响，这下真的无法淡定了，立马起床开电脑干活！！&lt;/p&gt;
&lt;p&gt;&lt;img src="/static/aliyuncs.com_2.png" /&gt;&lt;/p&gt;
&lt;p&gt;考虑到 whois 也会有缓存，赶紧登录自己在欧洲的一台机器重新查了一下，果然发现阿里 aliyuncs.com 的域名被人更新了（而不是被别人注册了），可以看到更新时间是 &lt;code&gt;2025-06-05T21:32:38Z&lt;/code&gt; ，考虑到时区，和阿里通告故障的时间是基本对的上，域名、whois 都会有缓存，有一定时间差是符合常理的。&lt;/p&gt;
&lt;p&gt;&lt;img src="/static/aliyuncs.com_3.png" /&gt;&lt;/p&gt;
&lt;p&gt;这个时候肾上腺激素开始飙升了，立马让在线的同事开工干活，主要几个动作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;第一时间把涉  &lt;code&gt;aliyuncs.com&lt;/code&gt; 的域名捞出来，并整理成共享文档，供接下来使用；&lt;/li&gt;
&lt;li&gt;唤醒公司域名管理员，对于我们自建 DNS 这个服务本身，如果使用了阿里 LB 的且评估是对其他业务提供服务的，马上切走。（如果我们的 DNS Server 受这个 LB 影响不可用了，这个时候爆炸半径会直接无限扩大）&lt;/li&gt;
&lt;li&gt;域名管理员，发起紧急变更，将缓存 DNS 服务器上关乎 &lt;code&gt;aliyuncs.com&lt;/code&gt; 这个域名的解析全部转发劫持到阿里云的权威，确保内部业务能正常解析，尽可能缩小爆炸半径；&lt;/li&gt;
&lt;li&gt;根据服务等级，除了在相关工作群通告外，根据服务重要等级唤醒相应运维（或相应域名对应的管理员），并让他们安排将 &lt;code&gt;aliyuncs.com&lt;/code&gt; 的业务先切到 VIP 地址；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;中间还是会有一些小插曲，比如可能指引不够详细，有运维同学对于域名没有概念或者有同学所涉业务较多，有点手忙脚乱之外，整体还算快速、稳步的推进，更多的细节就不展开了。&lt;/p&gt;
&lt;p&gt;当然，这里最终还是要给阿里云的团队点个赞，原以为这个故障时间会远超预期，然后我们在北京时间 08:14 收到反馈说经过阿里云团队的处理，域名解析开始恢复正常了，故障持续的时间大约为 6 小时左右。再加上故障发生在凌晨，另外域名解析的失效也有一个缓存的过程，不幸中的万幸就是我们基本没有受到影响（但是对不起，炸了一群人起来干活 :p&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;基本没有，也不是完全没有，但都在可控范围内 :-)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;15 年前的鬼故事&lt;/h2&gt;
&lt;p&gt;在响应这个故障的过程中，就有同事问我是不是我们曾经也出现过这个问题，很肯定的说“是”，但也不是，情况不太一样。在这个时间点，我对于阿里云为什么 NS &lt;code&gt;namesever&lt;/code&gt; 被改了还是不清楚的，只是猜测大概率是管理不善，帐号被黑客拿走改了 NS 之类~~ 而我们当年（约 15 年前）碰到的一个情况是被注册商（比如万网、NetworkSolutions）以涉敏感内容为由停止了解析，但阿里云 &lt;code&gt;aliyuncs.com&lt;/code&gt; 的症状对不上。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;说明一下，涉敏感内容肯定和公司没有直接相关，不展开，避免被有心之人用来作为攻击手段，也不确认该方式是否还有效&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当时也是发生在凌晨（时差，美国佬刚好上班之类），年轻睡的晚，本来也想睡觉了，鬼始神差的觉得好像不太对劲，开电脑查了一下发现解析被停掉了，多次用我鳖脚的英文和 NetworkSolutions 对线（感谢那时候有 Skype 等软件可以打国际电话），辗转折腾，在多路大佬的帮助下也是差不多在白天业务高峰前才恢复解析，但毕竟是我们的核心域名，加上时间也是大几个小时，不少地方的缓存失效了，影响还是很大的（最后被某位大佬点赞了 hhhh&lt;/p&gt;
&lt;h2&gt;说说我的害怕&lt;/h2&gt;
&lt;p&gt;前面说到&lt;code&gt;作为一名曾经的域名管理名，我对域名类的故障是比较敏（害）感（怕）的&lt;/code&gt;，这是为什么呢：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;DNS 是互联网最基础的业务之一，但凡在互联网上能提供的业务大概都离不开这个基础。越是基础的东西伤害就越大；&lt;/li&gt;
&lt;li&gt;基础的服务受关注程度低，大家都默认你正常，所以确实出问题的概率也极低，因为你不敢经常出问题。但带来的问题是所有上层业务都默认你正常，在设计的时候就不会去做过度容灾，一出问题短时间就是解决不了（但确实有时候为了一个极低概率出问题的系统做过度的容灾，不考虑 ROI 也是有毛病的表现 。。。&lt;/li&gt;
&lt;li&gt;因为 1 和 2 的情况，业务和业务，经年累月下来，你根本改不了。就像现在，你不可能拿出一套系统取代 TCP/IP、取代 DNS，哪怕 IPv4 要进化到 IPv6，这个我从读大学就被这么画大饼了，20 多年过去了，也没见 IPv6 就把 IPv4 干掉了&lt;/li&gt;
&lt;li&gt;（价值论）通常在大多数公司里，基础服务的价值讲不清楚，上层建筑容易画大饼讲故事，也有快速创造收益的可能。像我这种做基础设施的，都要经常停下来反思我这点收入真是得之不易，劳心劳命的，然后给自己找到自洽的逻辑，继续努力~&lt;/li&gt;
&lt;li&gt;域名的爆炸半径不可控，一方面是因为我们不可能去刷新世界上所有的缓存（运营商大多数情况下也不会鸟你），同时我们也没有办法让用户在出现故障时，让他们切换到另一个域名（如 HttpDNS 等方案这些都只能解决特定场景下的特定问题），另外 &lt;strong&gt;缓存是个双刃剑&lt;/strong&gt;，它能延缓故障的爆炸，却也会让你在宣告解析恢复后要经受不停的挑战，为什么我的服务一直没有恢复？就像这次我们 &lt;strong&gt;直到北京时间 13:57 才收到业务反馈说运营商的缓存基本都过期不再报错了&lt;/strong&gt;，大概是阿里云宣告恢复后的又一个 6 小时&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当然，你也可以认为我过度紧张 :-)&lt;/p&gt;
&lt;h2&gt;小结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;阿里云此次 &lt;code&gt;aliyuncs.com&lt;/code&gt; 的故障真实原因是什么？这个我也不知道，但从网络上的蛛丝马迹上看是被 &lt;a href="https://www.verisign.com/zh_CN/domain-names/what-does-com-mean/index.xhtml"&gt;VeriSign&lt;/a&gt;（不是注册商，是 &lt;code&gt;.com&lt;/code&gt; 顶级域的管理）改了域名解析，不考虑更深层次更腹黑的原因，只能猜测是因为阿里云有不合规的内容被投诉了之类。如下一些公众号的猜想：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/static/aliyuncs.com_4.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;作为域名的拥有者，有没有可能完全杜绝这个问题？我认为不可能， 这次看起来是 &lt;a href="https://www.verisign.com/zh_CN/domain-names/what-does-com-mean/index.xhtml"&gt;VeriSign&lt;/a&gt; 操刀的，也就是直接绕开注册局（商）了。包括有些朋友可能会认为加一些注册局锁（类似 &lt;code&gt;serverUpdateProhibited&lt;/code&gt; 这些）可能可以缓解，我可以很肯定的说这 &lt;strong&gt;只能解决到注册局层面的问题，覆盖不了本次的情况&lt;/strong&gt; 。当然阿里云做为一家国际云商， &lt;strong&gt;注册局锁这点钱你们就不要省啦&lt;/strong&gt; （事后也看到阿里云加上了）：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/static/aliyuncs.com_5.png" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;技术上能不能解决：能，但成本不知几何。鼓励大家在业务上线的时候通过多入口容灾、引入 HttpDNS 等技术解决类似问题，但是当类似问题出现时，影响还是不可避免，手忙脚乱也还是不可避免，毕竟 N 年等一回，但最好一回也不要有，害怕&lt;/li&gt;
&lt;li&gt;可能有其他非技术非常规手段，可以从根服务器、顶级域名运营商开始实施保护，但想像不了了，请了解规则的朋友指点哈&lt;/li&gt;
&lt;/ul&gt;</content><category term="misc"/><category term="aliyun"/><category term="阿里云"/><category term="DNS"/><category term="域名"/></entry><entry><title>Obsidian 接入 DeepSeek API 指南：Text Generator 插件配置教程</title><link href="https://www.chenxiaosheng.com/posts/2025-05-12/obsidian-deepseek-text-generator.html" rel="alternate"/><published>2025-05-12T15:07:00+08:00</published><updated>2025-05-12T15:07:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2025-05-12:/posts/2025-05-12/obsidian-deepseek-text-generator.html</id><summary type="html">&lt;p&gt;&lt;strong&gt;Obsidian 接入 DeepSeek API 的配置教程&lt;/strong&gt;，使用 &lt;strong&gt;Text Generator 插件&lt;/strong&gt;实现智能文本生成功能。教程使用 Text Generator 插件 + DeepSeek API 生成，并经过人工校对。&lt;/p&gt;</summary><content type="html">&lt;p&gt;以下是 &lt;strong&gt;Obsidian 接入 DeepSeek API 的配置教程&lt;/strong&gt;，使用 &lt;strong&gt;Text Generator 插件&lt;/strong&gt;实现智能文本生成功能。教程使用 Text Generator 插件 + DeepSeek API 生成，并经过人工校对：&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;1. 安装 Text Generator 插件&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;打开 Obsidian → 点击左侧边栏 &lt;code&gt;Settings&lt;/code&gt; (设置)或菜单栏 &lt;code&gt;Preferences&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;进入 &lt;code&gt;Community plugins&lt;/code&gt; (社区插件) → 点击 &lt;code&gt;Browse&lt;/code&gt;  &lt;/li&gt;
&lt;li&gt;搜索 &lt;strong&gt;Text Generator&lt;/strong&gt; → 安装并启用插件。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="" src="/static/obsidian_deepseek_2025.05/1.png"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;2. 获取 DeepSeek API Key&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;访问 &lt;a href="https://deepseek.com"&gt;DeepSeek 官网&lt;/a&gt; 注册/登录账号；&lt;/li&gt;
&lt;li&gt;进入 API 管理页面（如无公开API，需联系官方申请权限）；
&lt;img alt="" src="/static/obsidian_deepseek_2025.05/2.png"&gt;&lt;/li&gt;
&lt;li&gt;复制生成的 &lt;strong&gt;API Key&lt;/strong&gt;（保存到安全位置，后面无法通过 deepseek 平台查看）；&lt;/li&gt;
&lt;li&gt;充值（&lt;strong&gt;很重要，否则后面会出错&lt;/strong&gt;）&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;3. 配置 Text Generator 插件&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;在 Obsidian 中打开 &lt;code&gt;Settings&lt;/code&gt; → 找到 &lt;code&gt;Text Generator&lt;/code&gt; 插件设置。  &lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在 &lt;code&gt;API Provider&lt;/code&gt; 中选择 &lt;strong&gt;Custom&lt;/strong&gt; 或 &lt;strong&gt;通过+创建自定义配置&lt;/strong&gt;（若支持）。
&lt;img alt="" src="/static/obsidian_deepseek_2025.05/3.png"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注：如上截图，已勾选的 &lt;code&gt;deepseek&lt;/code&gt; 是通过后面的 &lt;code&gt;+&lt;/code&gt; 创建出来的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;填写以下参数：&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API URL&lt;/strong&gt;: DeepSeek API 的端点地址（如 &lt;code&gt;https://api.deepseek.com/chat/completions&lt;/code&gt;）  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API Key&lt;/strong&gt;: 粘贴你的 DeepSeek API Key  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model Name&lt;/strong&gt;: 填写模型名称（如 &lt;code&gt;deepseek-chat&lt;/code&gt;，根据官方文档调整）&lt;br&gt;
&lt;img alt="" src="/static/obsidian_deepseek_2025.05/4.png"&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;4. 测试 API 连接&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;在 Obsidian 中新建一个笔记，输入内容并选中；&lt;/li&gt;
&lt;li&gt;按快捷键 &lt;code&gt;Ctrl/Cmd + J&lt;/code&gt; 即可自动生成；
&lt;img alt="" src="/static/obsidian_deepseek_2025.05/5.png"&gt;&lt;/li&gt;
&lt;li&gt;观察是否返回 DeepSeek 生成的文本，若无响应请检查：  &lt;/li&gt;
&lt;li&gt;API Key 和 URL 是否正确  &lt;/li&gt;
&lt;li&gt;网络是否畅通  &lt;/li&gt;
&lt;li&gt;DeepSeek 账户是否有足够配额  &lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;5. 高级设置（可选）&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;自定义提示模板&lt;/strong&gt;：在插件设置中修改 &lt;code&gt;Prompt Template&lt;/code&gt;，适配你的工作流。  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;调整参数&lt;/strong&gt;：如 &lt;code&gt;temperature&lt;/code&gt;（随机性）、&lt;code&gt;max_tokens&lt;/code&gt;（生成长度）等。  &lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;常见问题&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Q&lt;/strong&gt;: 插件报错 &lt;code&gt;n.text is not a function&lt;/code&gt;？&lt;br&gt;
&lt;strong&gt;A&lt;/strong&gt;: 在插件的 &lt;code&gt;Advance mode&lt;/code&gt; 里把 &lt;code&gt;CORS Bypass&lt;/code&gt;  打开
&lt;img alt="" src="/static/obsidian_deepseek_2025.05/6.png"&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Q&lt;/strong&gt;: 提示 &lt;code&gt;404&lt;/code&gt; 或者 &lt;code&gt;Not Found&lt;/code&gt; 之类
  &lt;strong&gt;A&lt;/strong&gt;: 确认 API 地址（&lt;code&gt;Endpoint&lt;/code&gt;）填写正确，如这里是：&lt;code&gt;https://api.deepseek.com/chat/completions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Q&lt;/strong&gt;: 提示 &lt;code&gt;402&lt;/code&gt; 或者 &lt;code&gt;Failed to load resource&lt;/code&gt; 之类
  &lt;strong&gt;A&lt;/strong&gt;: 先充值！&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Q&lt;/strong&gt;: 如何查看其他报错的详细信息
  &lt;strong&gt;A&lt;/strong&gt;: MacOS，使用：&lt;code&gt;Cmd+Option+I&lt;/code&gt; 打开控制台查看
&lt;img alt="" src="/static/obsidian_deepseek_2025.05/7.png"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;通过以上步骤，你可以在 Obsidian 中无缝调用 DeepSeek 的 AI 能力。如果需要更详细的配置（如本地代理），可参考插件官方文档或 DeepSeek API 说明。&lt;/p&gt;</content><category term="misc"/><category term="obsidian"/><category term="deepseek"/><category term="ai"/></entry><entry><title>25非全考研，人生经历+1</title><link href="https://www.chenxiaosheng.com/posts/2025-04-13/postgraduate.html" rel="alternate"/><published>2025-04-13T06:30:00+08:00</published><updated>2025-04-13T06:30:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2025-04-13:/posts/2025-04-13/postgraduate.html</id><summary type="html">&lt;p&gt;23年去华工和D老师/泰老师吃饭闲逛的时候，脑子一抽想到要不干脆我也来考个研算了，但因为离国家统考的时间确实太近了，花80块钱报了名，完全没有时间准备，最后也是不了了之。但念头有了之后，却是挥之不去了，24年春节后，就默默开始准备起来了，25年4月把复试的流程走完，也进入了“拟录取”状态，小记一下人生的的经历。&lt;/p&gt;</summary><content type="html">&lt;p&gt;23年去华工和D老师/泰老师吃饭闲逛的时候，脑子一抽想到要不干脆我也来考个研算了，但因为离国家统考的时间确实太近了，花80块钱报了名，完全没有时间准备，最后也是不了了之。但念头有了之后，却是挥之不去了，24年春节后，就默默开始准备起来了，25年4月把复试的流程走完，也进入了“拟录取”状态，小记一下人生的的经历。&lt;/p&gt;
&lt;h2&gt;为什么想考研&lt;/h2&gt;
&lt;p&gt;考研再读书的想法来的快，却挥之不急，首先还是和个人性格有比较大的关系，当我想到要去做一件事情的时候，总是会想着各种办法去实现，小到种草一件商品，大到买车买房等，只要我确定了我就会很快的想办法去实现。这个性格其实在生活、工作中给我带来了不少的好处的，坚持、靠谱、敢于接受挑战的各种评价大抵来源于此，但实际上却也经常让我个人陷入一些困境的，所谓的”内耗“。但无论如文，考个研读个书的想法真是”野火烧不尽，春风吹又生“，那就实现他吧，黄沙百战穿金甲，不破楼兰终不还。
除了性格的原因，想考研读书也是受到方方面面的影响：&lt;/p&gt;
&lt;p&gt;1、受益公司的战略调整，这几年开始有机会招聘海外的一些员工，在面试过程中就发现很有意思且很频繁的一个现象，海外同学（这里指坡县）经常会在工作一段时间后又回到学校，再出来工作，如此反复的这么一个过程。让我从“惊讶”进入到了”羡慕“，毕竟在国内要是我们的工作简历上频繁的出现”GAP“，那大抵是不受待见的。甚至于除了”铁饭碗“类的岗位，在大多数行业（比如互联网），虽然大家鼓励学习、知识的沉淀，但对于学历的追求，可能也会得到一个”有什么用呢“的反问？和海外这些员工的接触给我种了草，其他人的想法并不重要，这是让我觉得会有趣的一个经历，那就去试试吧！（谢谢我的热爱学习的同事们）
2、我不知道公司或者整个社会有什么问题，但知道我所在公司的朋友，也应该会关注到我所在的公司这两年也是各种”风起云涌“，口罩后的大家过的都不容易，对于我自己的岗位，从23年开始直到现在，我也一直是抱着背水一战，破釜沉舟的心态，可是当真的走到这一步的时候，我能做些什么，我真的没有想好。小学中学大学十六载，工作近十八载，如果有这么一个窗口，那么我想停一下看一看，也好好想一想接下来的路怎么走（毕竟40岁的互联网人那可太不被待见了啊，哈哈哈），但却并不想让自己无所事事，因此给自己一个重新学习的机会也成了自己一个很好的可选项，给自己一个暂停键，还可以充充电（算不算在逃避呢？嘿嘿）&lt;/p&gt;
&lt;p&gt;想到了就去做，而且这次我还算是做了一个和自己性格相逆的”玩法“，早早我就告诉朋友、同事我要去考个研（以前这种人生大事怎么可以告诉大家，都是默默搞定他，免得丢人，哈哈哈），人生海海，何妨让世界听见我的号角？&lt;/p&gt;
&lt;h2&gt;备考&lt;/h2&gt;
&lt;p&gt;要说花了很多的时间备考，好像也没有，毕竟日常确实太忙了，基本都是利用周末的时间以 1.5x 甚至 2.0 的倍速刷一下视频，让自己有点印象，其实最担心的无法就是到了考场一个字都蹦不出来。非全日制工商管理类（我报了工程管理）国家统考其实就考两科《管理类综合能力》和《英语（二）》，对于我个人来说，英语其实没啥好准备的（口语不行，但阅读理解却是我的强项），主要时间还是用于学习管综了，买了中公教育的历年真题，这套真题有个好处是看不懂的题目可以刷二维码有视频学习，这对于已经离开学校18年，各种知识已经还给老师的中年人实在是太友好了！
当然，这些课程的学习纯粹是为了“应试”，但好在也不是一无事处，有两点收获还是值得回味一下的：&lt;/p&gt;
&lt;p&gt;1、算是把初中的一些数学知识重新学习了一下，涉及到基础的几何知识、平面直角坐标系、概率等等的一些知识，家里刚好有一个初中娃，被他问起来的时候显得我没有那么落伍，被我装到了；
2、学到了一个应试技巧，考试时先把”论说文“和”论述文“两篇文章写了，两篇文章只要完成度高，前面的数学题、逻辑题蒙个一半，那基本上也大差不差了。
英语在考前找了几个公众号看了下英语写作的模板算是最大的准备了。逻辑题我个人就会认为纯粹就是为了考试而考试设计的各种绕口令了，总之绕晕你就对了（实际我也没有做完，至少有10道选择题就是蒙猜了），当然正能量一点，我找 DeepSeek 讨论了一下对于“逻辑题”的看法，它给出了正面反馈：&lt;/p&gt;
&lt;p&gt;&lt;img src="/static/postgraduate_1.png" /&gt;&lt;/p&gt;
&lt;h2&gt;考试&lt;/h2&gt;
&lt;p&gt;考试的仪式感还是很强的，除了文具其他随着携带的东西都是在考场区域外保管的，进考场区域和考室也是面临层层安检。更有趣的是，当天也算是一边考试一边加班了，进考场区域的前 5 钟还在打电话安排和汇报工作。中午和下午考完出来的 5~10 分钟内都有电话和 IM 信息过来的灵魂拷问：“怎么我回信息这么慢的？”（真是紧张又不失有趣啊，肾上腺激素彪升，懂的都懂，哈哈哈，真心感谢当天紧急提供协助的同学们）
所幸，流程算是走完了，这个考试状态感觉要凉，哈哈哈，但好在自己并不焦虑，好像有点祛魅了，原来就是这么一回事！&lt;/p&gt;
&lt;h2&gt;复试&lt;/h2&gt;
&lt;p&gt;成绩出来后其实并不理想，管综考的比较烂，英语算是拉了我一把，工程管理国家线 162，吹牛的说法是咱超了国家线近 30 分，其实一点都不理想，我所报考的学校计划招生 40 人，我排名 61，是否能进面试其实是比较悬的，最后算是有惊无险，进了复试。
复试至少准备上是比初试统考用心了的，在复试通知出来的时候才发现竟然要考一门专业课，足足用了两周下班后的晚上和一个完整的周末在B站刷视频课程学习，专业课和面试的过程就不具体展开了，毕竟用官方的说法这应该属于泄密。但是回到学校复试的感受还是不一样的，走在学校的小路上，看着来往的学子和有着年头的建筑，想重新回到学校的念头还是非常强烈的。所以在对话的一开始还是略微紧张的，但也算是很快调整过来了的，复试的成绩还不错，最后总成绩把自己从 61 拉到前 30 了。因为官方的通告都是 PDF 的，让 DeepSeek 帮忙分析了下：&lt;/p&gt;
&lt;p&gt;&lt;img src="/static/postgraduate_2.png" /&gt;&lt;br/&gt;
&lt;img src="/static/postgraduate_3.png" /&gt;&lt;/p&gt;
&lt;h2&gt;知识若有趣，前路自有光&lt;/h2&gt;
&lt;p&gt;至此，非全研究生的考试算是把整个流程走完了，虽然感觉对整个过程是祛魅了，但大抵自己还是会考虑这项“投资”把后面三年的学习流程走完的。印象有点深刻的是最近和同学朋友聚餐时，提到我要去读书，结合我所在的行业（学历已经没什么帮助了）、年龄（中年大叔）、学费（一年5个）、学习方式（非全日制，低人一等？），从部份朋友的眼神里看到了惊讶和不解。但我在复试专业课的准备过程中却看到了知识的趣味性、系统性、延续性，很多工作中看到的现象碰到的问题，原来可以大量使用课程里的原理、逻辑、方法加以解释（如果回到大学，我的想法一定会是这些大道理有什么意义啊？），既然这么有趣（又有挑战，听说这个学校即使非全的按期毕业率低于30%），那就让游戏继续下去吧，这应该是人生最后一次进校园的机会了？&lt;/p&gt;
&lt;p&gt;中午逆旅，莫听穿林打叶声，何妨吟啸且徐行。&lt;/p&gt;
&lt;p&gt;2025.04.13 06:30&lt;/p&gt;</content><category term="misc"/><category term="postgraduate"/></entry><entry><title>你想要建造自己的数据中心吗？</title><link href="https://www.chenxiaosheng.com/posts/2025-01-20/So-You-Want-to-Build-Your-Own-Data-Center.html" rel="alternate"/><published>2025-01-20T13:07:00+08:00</published><updated>2025-01-20T13:07:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2025-01-20:/posts/2025-01-20/So-You-Want-to-Build-Your-Own-Data-Center.html</id><summary type="html">&lt;p&gt;这篇文章主要讲述了Railway公司从依赖Google Cloud Platform到自主建设数据中心的过程。由于Google Cloud Platform存在定价、服务和工程限制等问题，Railway启动了Railway Metal项目，自主设计、安装数据中心基础设施，包括选址、电力供应、网络连接、机架布局和硬件安装等，以提供更好的云服务体验。&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;原文来自：https://blog.railway.com/p/data-center-build-part-one
很少看到有从 IDC 基础设施开始写的文章，一时兴起借 AI 工具作了粗浅的翻译。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;从一开始，Railway的计算能力就是建立在谷歌云平台之上的。该平台支持了Railway的初步发展，但它也引发了许多问题，这些问题给我们的业务带来了生存风险。更重要的是，构建在超大规模云服务提供商上，阻碍了我们为客户提供最佳平台的可能性。&lt;/p&gt;
&lt;p&gt;它直接影响了我们能够提供的定价（有人知道公有云流出流量费用吗？），限制了我们能够提供的服务级别，还引入了工程限制，这些限制了我们能够构建的能力。&lt;/p&gt;
&lt;p&gt;我们不仅&lt;a href="https://github.com/GoogleCloudPlatform/guest-agent/issues/401"&gt;很少&lt;/a&gt;能&lt;a href="https://blog.railway.com/p/2023-12-01-incident-report"&gt;理解&lt;/a&gt;上游环节&lt;a href="https://blog.railway.com/p/incident-december-16-2024"&gt;出问题&lt;/a&gt;的&lt;a href="https://x.com/JustJake/status/1667478906591666176"&gt;原因&lt;/a&gt;，而且尽管我们每年花费数百万美元，但能得到的支持也只相当于你花费100美元所得到的。&lt;/p&gt;
&lt;p&gt;因此，我们去年启动了一个 Railway Metal 项目。九个月后，我们在加利福尼亚的第一个站点上线，设计、规划并落地了从机柜中的光纤电缆到与互联网服务提供商的各种合同的一切。就在我们发布这篇博客的时候，我们正在启动另外三个区域的数据中心。&lt;/p&gt;
&lt;p&gt;为了向我们的客户提供“透明基础设施”的云体验。我们需要迅速且足够好的构建我们自己的物理基础设施。这就是我们今天博客文章的主题。&lt;/p&gt;
&lt;h2&gt;所以你想要搭建自己的“云”&lt;/h2&gt;
&lt;p&gt;从 2024 年 1 月启动 Railway Metal 项目开始，我们花了整整五个月时间才将第一批服务器接入。之后，又用了额外的三个月，我们才觉得可以让用户放心地使用这些硬件（并且又过了几个月，我们才开始在这里撰写相关的内容）。&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;&lt;img width="736" height="655" src="/static/CleanShot_2025-01-15_at_23.webp"/&gt;&lt;/p&gt;
&lt;p&gt;租用或购买足够的空间来放置设备等并不是主要的成本支出，电力（以及由此带来的冷却需求）却是成本最高的部分。根据地理位置的不同，每千瓦时（$/kW）的电力价格可能会有很大的差异。--举个例子，在美国西海岸，我们支付的电力费用可能不到在新加坡支付的一半。电费是按照固定的月度承诺来支付的，无论是否实际消耗，都要支付这笔费用，以确保电力可以随时按需使用。&lt;/p&gt;
&lt;p&gt;但是你需要多少电力呢？&lt;/p&gt;
&lt;h2&gt;能力越大，责任越大&lt;/h2&gt;
&lt;p&gt;理想情况下，如果你已经开始了数据中心迁移的任务，你应该对想要部署的计算资源数量有一个大致的概念。我们开始时设定了虚拟CPU（vCPU）的数量、内存容量（GBs）以及NVMe（一种高速存储设备）容量（TBs）作为目标，以匹配我们在谷歌云平台（GCP）的容量。&lt;/p&gt;
&lt;p&gt;经过一系列指标、数据的分析、比较等过程，我们最终确定了合适的服务器和CPU。计算过程有很多因素、参数等可供选择、调整--甚至于都值得另外写一篇文章了--但对我们来说，最重要的因素是功耗密度。例如，我们如何在特定的功耗范围内获得我们想要的计算密度。&lt;/p&gt;
&lt;p&gt;功率计算并不像简单地把瓦数加起来那么直接，尤其是涉及到三相电的情况。Cloudflare有&lt;a href="https://blog.cloudflare.com/an-introduction-to-three-phase-power-and-pdus/"&gt;一篇很棒的博客文章&lt;/a&gt;详细阐述了这个主题。&lt;/p&gt;
&lt;p&gt;电力是数据中心最为关键的资源，一旦电力中断，可能需要数小时甚至数天才能完全恢复正常的运营状态。所以冗余是非常关键的，对每个机柜来说，两路独立的电力接入至关重要。在正常运行时，这两个独立的电源线路会共同分担机架设备的负载，共同为设备提供电力。这样可以保证电力供应的稳定性和效率。然而，更重要的是，这种设计必须能够抵御其中一个电源线路发生故障的情况。即使其中一个电源线路出现故障，另一个电源线路也能够立即接管全部负载，确保机架设备的持续运行，而不会因为电源故障而导致设备断电。&lt;/p&gt;
&lt;p&gt;为了将这种电力传输到你的服务器上，你还需要一个电源分配单元（PDU），你需要根据它提供的插座数量和管理功能来选择它。基本款的 PDUI 就像是升级版的延长线，而我们部署的 PDU 则能够实现对每个插座的控制和计量。&lt;/p&gt;
&lt;p&gt;&lt;img width="736" height="654" src="/static/880a21c2-366b-445e-951e-a03a68515796.webp" /&gt;&lt;/p&gt;
&lt;p&gt;凭借前面提及的相关能力，现在机柜里面可以正常供电了。&lt;/p&gt;
&lt;h2&gt;上帝说要有光&lt;/h2&gt;
&lt;p&gt;没有一台云机器是孤立存在的，这就是网络发挥作用的地方。&lt;/p&gt;
&lt;p&gt;为了在Railway上实现尽可能低的延迟，我们需要为你建立到世界各地的稳定连接。&lt;/p&gt;
&lt;p&gt;我们寻找的数据中心要具备与顶级（T1）互联网服务提供商（ISPs）处于同一网络、属于互联网交换点（IX）的一部分以及有可用光纤连接到附近其他数据中心等特性。&lt;/p&gt;
&lt;p&gt;&lt;img width="736" height="0" src="/static/d0c2e2d8-dd9d-4917-8178-062572bc3ff4.webp" /&gt;&lt;/p&gt;
&lt;p&gt;部署到Railway的应用程序会想要通过网络连接到各种各样的端点，无论是位于澳大利亚悉尼的家庭互联网用户，还是托管在美国AWS服务器上的API。为了给您提供尽可能低的延迟和最低的带宽成本，我们会与针对每种使用场景优化的多种互联网提供商签订合同&lt;/p&gt;
&lt;p&gt;我们在选择互联网服务提供商（ISPs）时，会考虑它们在我们目标地区的网络成熟度。如果在一个地区选择了不合适的ISP，可能会导致到达特定目标的网络路径出现额外的网络跳数（从而产生延迟），或者在最糟糕的情况下，出现复杂的网络路由。因此，为了确保网络服务的质量和可靠性，我们会根据它们在各个地区的网络覆盖范围来选择至少两个不同的网络。&lt;/p&gt;
&lt;p&gt;一旦连接建立，我们会从每个互联网服务提供商（ISP）那里接收完整的互联网路由表并将这些路由表整合到我们的网络交换机上以便为每个IP前缀解析出最佳路径。如果你有一个位于澳大利亚的终端用户试图连接部署在新加坡的应用程序，我们很可能会直接将这些数据包转交给&lt;a href="https://bgp.tools/as/4637#connectivity"&gt;Telstra&lt;/a&gt;。Telstra拥有澳大利亚最密集的接入网络之一。如果同一个应用程序需要将数据包发送到日本的终端用户或服务器，那么我们很可能会将这些数据包转交给PCCWPCCW直接与日本的NTT（日本电信电话公司）进行对等互联，并且在亚太地区（APAC，Asia-Pacific）&lt;a href="https://www.pccwglobal.com/wp-content/uploads/2019/11/world_fold_20191115.pdf"&gt;拥有广泛的网络覆盖&lt;/a&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👉 网络之间对等互联的信息是公开可获取的，可以访问&lt;a href="https://bgp.tools/"&gt;bgp.tools&lt;/a&gt;这个网站来观察你感兴趣的网络之间是如何实现相互连接的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;为了实现冗余，我们正在每个区域构建多个可用区，并且这些站点之间的互联互通对我们扩展计划至关重要。我们正在寻找裸纤（dark fiber）或波分复用技术（wavelength services）这样的工具来规划这种扩展。其结果是，你的应用程序不会察觉数据库是位于同一个房间，还是在相邻建筑中相隔四个街区的位置——这是一个优势，而不是缺陷——因为它增强了系统对单个数据中心故障的韧性。&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;好的，现在你已经找到了一个你满意的场地，和一个数据中心签订了协议，还和几家互联网服务提供商签了约，你就可以着手安装服务器了，对吧？&lt;/p&gt;
&lt;p&gt;嗯，不是完全这样。首先你需要很多其他的东西来给你的服务器一个舒适温暖的家来安顿下来。&lt;/p&gt;
&lt;h2&gt;通道，机架和头顶的基础设施&lt;/h2&gt;
&lt;p&gt;在数据中心里，机架是按行排列的，机架之间的空间，也就是通道，被用于空气流通。&lt;/p&gt;
&lt;p&gt;冷通道是从数据中心设施吹入冷空气的地方，而机架中的服务器会吸入这些冷空气，并将其从后部排出到热通道。数据中心设施会从热通道中移除这些热空气。为了达到最佳效率，不希望冷通道和热通道之间的空气混合。&lt;/p&gt;
&lt;p&gt;&lt;img width="736" height="0" src="/static/3d2a676b-0ff6-4c62-a25e-c9db9c40a21a.webp" /&gt;&lt;/p&gt;
&lt;p&gt;即使您选择使用传统的19英寸宽设备，机架本身也存在一定的可变性。您可以根据设备和布线需求选择机架的高度、宽度和深度。&lt;/p&gt;
&lt;p&gt;大多数服务器设备都可以通过滑轨进行滑动，以便于维护，因此要确保机笼尺寸允许设备进行滑动。布线和线缆管理也需要一定的空间，所以要在每个机架的拥挤程度与机笼内可容纳的机架数量之间做出权衡。&lt;/p&gt;
&lt;p&gt;在我们的经验中，电力和制冷常常是限制因素，而不是实际可用的空间。在新场地，我们倾向于选择宽度为800毫米的机架，以便通过将电缆移出排气口的路径来实现更好的空气流通。&lt;/p&gt;
&lt;p&gt;&lt;img width="736" height="0" src="/static/IMG_5261.webp" /&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;img width="736" height="0" src="/static/4710_fiber_tray_and_top_rack.webp" /&gt;&lt;/p&gt;
&lt;p&gt;你已经拥有了所需的空间，签约了互联网服务提供商（ISPs），订购了硬件，准备好了机架，并且对如何布置这一切有了一个相当不错的概念。但目前，这一切还只是像一套昂贵的乐高积木一样，静静地待在数据中心的装卸区。要将它们组装起来，你现在需要利用人类历史上最通用的编程工具——Microsoft Excel。&lt;/p&gt;
&lt;h2&gt;上架和堆叠&lt;/h2&gt;
&lt;p&gt;让我们先退一步，先发表一个免责声明：整齐有序的布线需要大量的实践；我们自己也尝试过，结果……喜忧参半。&lt;/p&gt;
&lt;p&gt;&lt;img width="736" height="0" src="/static/railway-structured-cabling-services.webp" /&gt;&lt;/p&gt;
&lt;p&gt;为了正确安装它们，我们引入了专业人士，但专业人士需要知道要安装什么。一份全面的文档包是必不可少的。电缆矩阵和机架布局图是常见的文件，它们向承包商传达如何安装和连接服务器。&lt;/p&gt;
&lt;p&gt;电缆矩阵（Cabling Matrix）描述了每根电缆的端接情况，指明了连接的每一端所对应的设备位置和端口号，同时还包括电缆本身的规格信息（例如光纤类型、长度等）。机架视图（Rack Elevation）是机架本身的视觉呈现，展示了每个设备的位置和朝向。&lt;/p&gt;
&lt;p&gt;&lt;img width="736" height="0" src="/static/CleanShot_2025-01-15_at_23_1.webp" /&gt;&lt;/p&gt;
&lt;p&gt;文档工作非常繁琐，每个安装阶段都涉及60多台设备、300多根独立电缆以及许多细微之处。这些内容都被精心整理成书面规范和电子表格，作为安装和调试的基础。从物料到场到完成安装，整个过程大约需要6到14天。&lt;/p&gt;
&lt;p&gt;&lt;img width="736" height="0" src="/static/CleanShot_2025-01-15_at_23_2.webp" /&gt;&lt;/p&gt;
&lt;p&gt;数据中心的建设与软件、DevOps以及人们通常所认为的“基础设施”相去甚远。确切的说，更类似于建造房屋，而不是 Terraform 部署。&lt;/p&gt;
&lt;p&gt;更复杂的是，每一个数据中心设施、承包商和供应商在做事方式上都会存在细微的差别，即使在同一个组织内也是如此。因此这些事情会要求你始终保持警觉，并且极度注重细节。&lt;/p&gt;
&lt;p&gt;迄今为止我们经历过的一些困域时刻：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;承包商说需要更长的电源线。原因是该站点的电源分配单元（PDU）是倒置的，因为电源是从地板接入的，这导致了插座编号在规划中被反向标记了。&lt;/li&gt;
&lt;li&gt;来自阿姆斯特丹的电话说：“这个站点没有分界点吗？” 一个特定的设施直接将外部光纤链路安装到我们机架中的一个盒子上，而不是通过一个专用的交接点。&lt;/li&gt;
&lt;li&gt;Railway Discord：“为什么这个PDU的相位接线这么奇怪？” 该设施的接线方式与其他站点不同，电源插座是相线对中性线接线，而不是相线对相线接线（对于电气工程师来说，这是WYE和Delta电路的区别）。&lt;/li&gt;
&lt;li&gt;承包商说：“你的数据线太短了。” 承包商没有意识到网络设备是逆向气流的，试图以错误的方向安装设备。&lt;/li&gt;
&lt;li&gt;我们提交了一个支持工单：“这条电缆没有建立连接。” 光纤的极性接反了；那天我们学会了什么是“翻转光纤电缆”……这是指他们从&lt;a href="https://www.fs.com/uk/blog/lc-fiber-optics-a-comprehensive-guide-2684.html"&gt;LC连接器&lt;/a&gt;中拔出插头并交换它们的位置。&lt;/li&gt;
&lt;li&gt;Railway Discord：“我今天从家得宝买了一个橡胶锤。” 一个供应商的近24个PDU批次被交付时带有有故障的插座，即使施加适当的极端机械力，这些插座也无法与电源插头正确接合。&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但从这一点开始——硬件已经就位，任务开始变得熟悉起来；我们现在需要进行一些BGP（边界网关协议，是一种用于互联网自治系统之间的路由协议）操作，安装一些操作系统，配置监控并让一切工作起来。&lt;/p&gt;
&lt;p&gt;&lt;img width="736" height="0" src="/static/finished.webp" /&gt;&lt;/p&gt;
&lt;h2&gt;把油门踩到底&lt;/h2&gt;
&lt;p&gt;安装好的机架就像是一块空白的画布，网络设备需要进行配置，路由器的配置文件需要编写，区域互联网注册机构（RIR）的记录需要更新，我们还需要与类似 &lt;a href="https://www.dmtf.org/standards/redfish"&gt;Redfish API&lt;/a&gt;（用于服务器主板和电源分配单元的专用控制器的 HTTP 接口）和 PXE（一种通过网络启动服务器的协议）进行交互，以确保一切正常运行。&lt;/p&gt;
&lt;p&gt;我们也还没有讨论网络是如何工作的。我们的设计使用了&lt;a href="https://frrouting.org/"&gt;FRR&lt;/a&gt;（Free Range Routing）和运行&lt;a href="https://sonicfoundation.dev/"&gt;SONiC&lt;/a&gt;（Software for Open Networking in the Cloud）操作系统的白盒网络交换机，构建了一个仅基于第三层（L3）的软件驱动网络，该网络与我们的控制平面深度集成。&lt;/p&gt;
&lt;p&gt;我们已经给你讲了很多来自一线的故事……要是再讲下去，你今天就别想走了。&lt;/p&gt;
&lt;p&gt;在未来的文章中，我们将讨论如何将一个房间里的一堆服务器转变为一个功能性的 Railway 区域。在过去的几个月里，我们开发了两个新的软件工具：Railyard 和 MetalCP，以实现从设计新的机架、追踪和可视化布线，到在服务器上安装操作系统并将其接入互联网的“一键式”体验。&lt;/p&gt;
&lt;p&gt;直到那时，如果这些内容中有任何让你感到兴奋的，可以查看我们目前开放的&lt;a href="https://railway.com/careers/infra-platform"&gt;基础设施工程职位&lt;/a&gt;，如果这些职位引起了你的兴趣，就联系我们。&lt;/p&gt;</content><category term="misc"/><category term="datacenter"/></entry><entry><title>美区 Apple ID 充值攻略</title><link href="https://www.chenxiaosheng.com/posts/2023-10-16/how-to-top-up-U.S-AppleID-for-Chinese.html" rel="alternate"/><published>2023-10-16T20:10:00+08:00</published><updated>2023-10-16T20:10:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2023-10-16:/posts/2023-10-16/how-to-top-up-U.S-AppleID-for-Chinese.html</id><summary type="html">&lt;p&gt;拥有一个美区 Apple ID 账号对于广“果粉”来说是非常有必要的。那么就会面对一个问题，就是如何充值美区苹果帐号？这篇文章就要详细为大家介绍下美国苹果ID充值教程，满满的干货，赶紧来看看有没有适合你的充值方式吧！&lt;/p&gt;</summary><content type="html">&lt;p&gt;注：原文来自 &lt;a href="https://zhuanlan.zhihu.com/p/552311280"&gt;https://zhuanlan.zhihu.com/p/552311280&lt;/a&gt; ，亲测有效，因此转载一份，防止消失 :-)&lt;/p&gt;
&lt;h2&gt;一、购买美区苹果礼品卡&lt;/h2&gt;
&lt;p&gt;在国内购买美国 Apple gift cards的途径主要有以下三种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;淘宝购买美区礼品卡，买回来直接充值，但风险比较高，因为有些淘宝的礼品卡是黑卡，使用黑卡充值可能有 ID 丢失风险。没有信得过的店铺就不要尝试这个方法。&lt;/li&gt;
&lt;li&gt;如果你有美国的朋友，可以让他代买礼品卡，然后充值。  &lt;/li&gt;
&lt;li&gt;到苹果美国官网自己买礼品卡送给自己，也是本文要说的方法。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;准备工作&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;双币种信用卡，单币银联信用卡，中国发行的visa或者master&lt;/li&gt;
&lt;li&gt;准备两个邮箱（通过邮箱发送礼品卡，Gmail可以）&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;购买流程&lt;/h3&gt;
&lt;p&gt;首先打开美国礼品卡购买地址：&lt;a href="https://www.apple.com/shop/buy-giftcard/giftcard"&gt;https://www.apple.com/shop/buy-giftcard/giftcard&lt;/a&gt;
然后选择“Email”电子礼品卡，封面随便选一个（不重要），然后填写购买金额，有$25，$50，$100的选项，官网买的话最小金额是$10。&lt;/p&gt;
&lt;p&gt;&lt;img width="690" height="757" src="/static/how-to-top-up-U.S-AppleID-for-Chinese/image_1.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img width="690" height="757" src="/static/how-to-top-up-U.S-AppleID-for-Chinese/image_2.png"/&gt;&lt;/p&gt;
&lt;p&gt;接着填写收发人的信息及邮箱，邮箱千万不要弄错，否则无法接收礼品卡，填好后点击右上角“Add to Bag”按钮。&lt;/p&gt;
&lt;p&gt;&lt;img width="690" height="598" src="/static/how-to-top-up-U.S-AppleID-for-Chinese/image_5.png"/&gt;&lt;/p&gt;
&lt;p&gt;点完“Add to Bag”按钮后会跳转到另一个界面，这里检测下自己填写的邮箱有没有错，确认无误后点击“Check Out”按钮。&lt;/p&gt;
&lt;p&gt;&lt;img width="690" height="598" src="/static/how-to-top-up-U.S-AppleID-for-Chinese/image_4.png"/&gt;&lt;/p&gt;
&lt;p&gt;这里直接选择“Continue as Guest”按钮
&lt;br/&gt;
接下来这里填写你的信用卡信息，包括姓名，卡号，账单地址等等，填写好信息后点击“Continue to Review”按钮。&lt;/p&gt;
&lt;p&gt;&lt;img width="690" height="614" src="/static/how-to-top-up-U.S-AppleID-for-Chinese/image_6.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img width="690" height="1114" src="/static/how-to-top-up-U.S-AppleID-for-Chinese/image_7.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;填完上面信息后会让你再次确认邮箱等信息，确认无误后直接点击“Place Your Order”按钮就行，官网上写的是一个小时会收到邮件，正常不需要那么久，一般会在10分钟左右收到礼品卡邮件。&lt;/p&gt;
&lt;p&gt;PS：最后填写你的 VISA 信用卡信息，和地址信息时候，如果你选择的是中国，购买失败了，可以重新下单选 United States看看，你可以搜索在线美国地址生成器，或者打开 Google Map，找到一个你喜欢的美国的详细地址，然后 Google Map 会告诉你它所在的州，街道，电话信息等。（账单地址建议选择没有消费税的五个州，因为不确定购买虚拟产品会不会账单地址不同而多交税。）&lt;/p&gt;
&lt;h2&gt;二、使用礼品卡充值&lt;/h2&gt;
&lt;p&gt;收到礼品卡后，会在邮箱中看到兑换码，复制兑换码，到app store里去充值。  &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;充值前，先确认自己的 App Store 登录的是将要充值的 Apple ID，也就是美区 Apple ID。  &lt;/li&gt;
&lt;li&gt;把设置中国家和地区选项，改为美国，如果是 iPhone，路径是：设置 -&amp;gt; 通用 -&amp;gt; 语言和地区 -&amp;gt; 地区 -&amp;gt; United States。  &lt;/li&gt;
&lt;li&gt;打开邮件，点击进行充值。  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;或者你也可以打开 App Store，找到礼品卡充值入口，然后输入礼品卡的卡号(卡号在邮件里)，进行充值。具体步骤这里不再展开了，跟着手机的指引来就好了&lt;/p&gt;
&lt;h2&gt;Q&amp;amp;A&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;购买gift card并且到账之后，是否还需要绑定双币信用卡或者美国PayPal？&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;答：不需要，gift card和绑定双币信用卡或者美国PayPal时平行的，也就是说这三者只要满足一项，就可以进行付费APP的购买。  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;可以给美国区的Apple ID帮定国内的visa、master card、america express吗？&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;答：不可以，美国区的Apple ID只能绑定由美国发行的信用卡，有些小伙伴可能认为只要能消费美元的信用卡就可以，其实就算你绑定美国区Apple ID之后，苹果也能够识别你是大陆的信用卡，然后你的美国区账号就会被强制转成国区的Apple ID了，这就得不偿失了，所以建议不要尝试，如果不信邪，那可以试试，最后你可能需要重新注册一个美区Apple ID。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;中国区Apple ID充值&lt;/h2&gt;
&lt;p&gt;中国区的Apple Store支持的充值方式有支付宝充值、微信支付、银联/信用卡/借记卡、快捷支付等四种方式，如果你在海外，确保你的支付宝或者微信账户有钱就可以充值了。&lt;/p&gt;</content><category term="misc"/><category term="Apple"/></entry><entry><title>可能是全网最全的 ulimit 配置说明了</title><link href="https://www.chenxiaosheng.com/posts/2022-06-14/linux-ulimit.html" rel="alternate"/><published>2022-06-14T13:07:00+08:00</published><updated>2022-06-14T13:07:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2022-06-14:/posts/2022-06-14/linux-ulimit.html</id><summary type="html">&lt;p&gt;ulimit 设置不当经常会引起各种各样的问题，比如很经典的 &lt;code&gt;too many open files&lt;/code&gt;，网上也有很多文章讲解 ulimit 设置，如 &lt;a href="https://linux.die.net/man/5/initscript"&gt;initscrip&lt;/a&gt; 设置、PAM、systemd 的配置等等。&lt;/p&gt;</summary><content type="html">&lt;p&gt;ulimit 设置不当经常会引起各种各样的问题，比如很经典的 &lt;code&gt;too many open files&lt;/code&gt;，网上也有很多文章讲解 ulimit 设置，如 &lt;a href="https://linux.die.net/man/5/initscript"&gt;initscrip&lt;/a&gt; 设置、PAM、systemd 的配置等等。&lt;/p&gt;
&lt;p&gt;由于 systemd 已经成为主流，本文以 Debian 11 bullseye 为环境理清正确的 ulimit 设置应该覆盖哪些配置。通常情况下这些配置在绝大多数 Linux 发行版会是通用的，并不局限于 Debian。&lt;/p&gt;
&lt;h2&gt;systemd&lt;/h2&gt;
&lt;p&gt;systemd 主要涉及 /etc/systemd/system.conf 和 /etc/systemd/user.conf 两个文件，需要配置的内容是以样的，以 ulimit 中 &lt;code&gt;open files&lt;/code&gt; 和 &lt;code&gt;max user processes&lt;/code&gt; 为例，我们需要配置如下两项：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[Manager]&lt;/span&gt;
&lt;span class="na"&gt;DefaultLimitNOFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1048567&lt;/span&gt;
&lt;span class="na"&gt;DefaultLimitNPROC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;65535&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;配置生效需要重启。但是这两个配置文件有什么区别呢？通过 &lt;code&gt;man systemd-system.conf&lt;/code&gt; 了解到当以系统实例运行时，引用的是 system.conf ，当以用户实例运行时，引用的是 user.conf。不过系统环境中运行的都是系统实例，如果有兴趣的朋友可以通过 &lt;code&gt;systemctl edit/enable/start --user&lt;/code&gt; 等操作自己注册个 user 实例看看。可以通过 &lt;code&gt;cat /proc/${pid}/limits&lt;/code&gt; 进行确认。&lt;/p&gt;
&lt;p&gt;结论：为了确保 ulimit 在 system 环境中生效，建议同时修改/etc/systemd/system.conf 和 /etc/systemd/user.conf 两个文件中的相关配置，并确保配置一致。&lt;/p&gt;
&lt;h2&gt;PAM limits&lt;/h2&gt;
&lt;p&gt;很多伙伴会发现，即使完全配置了 system 相关的文件并重启生效后，通过 ssh 远程登录服务器，&lt;code&gt;ulimit -n&lt;/code&gt; 一看，还是 1024，这可是个忧伤的故事，怎么破呢？&lt;/p&gt;
&lt;p&gt;请检查 &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; 配置文件，看看是否有这一行：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;UsePAM yes&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果有，请将 &lt;code&gt;yes&lt;/code&gt; 改成 &lt;code&gt;no&lt;/code&gt; 之后重启 sshd 服务，再次登录后，你应该能发现 systemd ulimit 相关配置已经生效了。&lt;/p&gt;
&lt;p&gt;但系统中 PAM 可是在相当多的地方用到了，所以这里我们需要再次修改 pam limit 相关配置，以确保系统中 ulimit 相关的配置一致。需要修改的文件为：/etc/security/limits.conf，请在文件里增加如下内容：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;*&lt;/span&gt;   soft    nofile  1048567
&lt;span class="k"&gt;*&lt;/span&gt;   hard    nofile  1048567
&lt;span class="k"&gt;*&lt;/span&gt;   soft    nproc   65535
&lt;span class="k"&gt;*&lt;/span&gt;   hard    nproc   65535
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;修改上述文件后，需要再确认一下以下文件（可能还有更多，但以下这些文件建议确认）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;/etc/pam.d/su&lt;/li&gt;
&lt;li&gt;/etc/pam.d/sshd&lt;/li&gt;
&lt;li&gt;/etc/pam.d/login&lt;/li&gt;
&lt;li&gt;/etc/pam.d/cron&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;包含以下这行内容：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;session    required     pam_limits.so&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;请注意，前方高能：细心的小伙伴可能会发现这时通过 &lt;code&gt;su -&lt;/code&gt; 切换成 root 后，再看 ulimit，又是没有生效的，怎么回事呢？ &lt;code&gt;man limits.conf&lt;/code&gt; 可以找到答案：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;NOTE: group and wildcard limits are not applied to the root user. To set a limit for the root user, this field must contain the literal username root.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;上述 /etc/security/limits.conf 配置中的通配符 "*" 对 root 用户不生效，我们需要继续在 /etc/security/limits.conf 中增加如下配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;root    soft    nofile  1048567
root    hard    nofile  1048567
root    soft    nproc   65535
root    hard    nproc   65535
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;保存后，应该安逸了~&lt;/p&gt;
&lt;p&gt;结论：pam 的 limit 设置也会影响  ulimit 相关设置，需要通过配置确保和 systemd 相关内容一致。坑点：通配符 "*" 对 root 用户不生效。&lt;/p&gt;
&lt;h2&gt;结论&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;system 的 ulimit 相关设置需要同时调整 /etc/systemd/system.conf 和 /etc/systemd/user.conf；&lt;/li&gt;
&lt;li&gt;除了 system 的配置，pam 相关的配置也需要同步调整，主要涉及 /etc/security/limits.conf 和 /etc/pam.d/ 目录下的相关文件；&lt;/li&gt;
&lt;li&gt;/etc/security/limits.conf 中的通配符 "*" 对 root 用户不生效，root 用户相关配置需要显式指定；&lt;/li&gt;
&lt;li&gt;除了系统级别的配置，用户也可以在 /etc/profile 和 bashrc 等相关配置里显式的修改 ulimit，当你发现上述 3 项配置都没有生效的时候，请检查用户侧的设定，看看是否自己另外指定了；&lt;/li&gt;
&lt;/ol&gt;</content><category term="misc"/><category term="linux"/><category term="ulimit"/></entry><entry><title>MongoDB 4.2 流控 FlowControl 机制走读</title><link href="https://www.chenxiaosheng.com/posts/2022-06-11/mongodb-flowcontrol-practice.html" rel="alternate"/><published>2022-06-11T22:21:00+08:00</published><updated>2022-06-11T22:21:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2022-06-11:/posts/2022-06-11/mongodb-flowcontrol-practice.html</id><summary type="html">&lt;p&gt;MongoDB 4.2 引入了一个「流量控制」的新特性。该流控机制旨在保持副本集多数提交延迟小于或等于配置的最大值。此最大延迟的默认值为 10 秒。一旦多数提交的复制延迟达到配置的最大值的阈值百分比，流控制机制就会开始限制主节点上的写入。&lt;/p&gt;</summary><content type="html">&lt;p&gt;MongoDB 4.2 引入了一个「流量控制」的新特性。该流控机制旨在保持副本集多数提交延迟小于或等于配置的最大值。此最大延迟的默认值为 10 秒。一旦多数提交的复制延迟达到配置的最大值的阈值百分比，流控制机制就会开始限制主节点上的写入。&lt;/p&gt;
&lt;p&gt;当该特性开启时，MongoDB 会在给定的周期内（目前实现是 1 秒），分配一定数量的「流控票据」，MongoDB 的数据库库写操作需要通过获取「流控票据」来获取全局 IX 锁。当此给定周期内的票据分发完比后，相关操作需要等待下一个周期（下一秒）的票据刷新分发。部份 MongoDB 内部操作可能会忽略该流控机制，以确保 MongoDB 的正常运行。&lt;/p&gt;
&lt;p&gt;接下来我们从算法、配置、源码等几个方面结合来了解一下该流控制机制的实现（本文以 MongoDB 4.2.20 版本的文档及源码为参考）。&lt;/p&gt;
&lt;h2&gt;环境要求&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;因为是 4.2 才引入的，所以要求 MongoDB 版本大于等于 4.2.0，并且 featureCompatibility 也要匹配相应的版本；&lt;/li&gt;
&lt;li&gt;流控机制只在可以接受写入的节点生效（通过指 replica sets 中的 primary 节点）；&lt;/li&gt;
&lt;li&gt;当然，流控开关也是要打开的，默认打开。可以通过 &lt;a href="https://www.mongodb.com/docs/v4.2/reference/parameters/#param.enableFlowControl"&gt;enableFlowControl&lt;/a&gt; 进行开关；&lt;/li&gt;
&lt;li&gt;Read Concern Majority 必须启用，可以参考 &lt;a href="https://www.mongodb.com/docs/v4.2/reference/parameters/#param.enableFlowControl"&gt;replication.enableMajorityReadConcern&lt;/a&gt; 启用；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当以上机制不满足时（亦可理解为流控机制关闭时），MongoDB 总是返回默认的最大数量 &lt;code&gt;_kMaxTickets = 1000 * 1000 * 1000&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;流控机制&lt;/h2&gt;
&lt;p&gt;流控制机制允许每秒获取指定数量的全局 IX 锁。除明确规避流控机制的操作外，全局 IX 锁获取必须首先获取「流控票据」然后才能获取锁。当此给定周期内的票据分发完比后，相关操作需要等待下一个周期（下一秒）的票据刷新分发。MongoDB 通过一个独立的机制每秒刷新一次票数。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/mongo/db/storage/flow_control.cpp&lt;/span&gt;

&lt;span class="n"&gt;FlowControl&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;FlowControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ServiceContext&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;repl&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ReplicationCoordinator&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;replCoord&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 class="n"&gt;_jobAnchor&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;service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;getPeriodicRunner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;makeJob&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="s"&gt;&amp;quot;FlowControlRefresher&amp;quot;&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;client&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 class="n"&gt;FlowControlTicketholder&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;getServiceContext&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;refreshTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getNumTickets&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 class="n"&gt;Seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)});&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;_jobAnchor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;每次/秒刷新 tickets 数量时，&lt;code&gt;getNumTickets&lt;/code&gt; 将计算应该允许多少票据以解决 MongoDB 多数提交的滞后问题（复制延迟）。流控机制根据以下因素确定每个周期要补充多少票：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当前多数提交的复制延迟大于配置的目标最大复制延迟；&lt;/li&gt;
&lt;li&gt;Secondary 在上一周期应用了多少次操作；&lt;/li&gt;
&lt;li&gt;在上一周期中平均每次操作需要获取多少个 IX 锁；&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;触发流控时 ticket 数量计算&lt;/h3&gt;
&lt;p&gt;触发流控的 ticket 数量可以通过如下公式理解：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;base * k ^ ((lag - threshold)/threshold) * fudge factor&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;相关变量说明如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;base 为上一周期的操作数（也可以理解为操作数 * 平均每次操作获取的 IX 锁），通过采样获取；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;k 为 MongoDB 触发流控时节流的速率，默认值为 0.5，MongoDB 的可配置参数为 &lt;code&gt;flowControlDecayConstant&lt;/code&gt;；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;lag 为当前多数提交延迟；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;threshold 为配置的可容忍最达延迟，请特别留意，该值分别由两个参数控制 &lt;code&gt;flowControlTargetLagSeconds&lt;/code&gt; 与 &lt;code&gt;flowControlThresholdLagPercentage&lt;/code&gt; 默认值分别为 10 和 0.5，可以理解为当多数提交延迟大于 5 秒时，即触发流控；&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/mongo/db/storage/flow_control.cpp&lt;/span&gt;

&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;uint64_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getThresholdLagMillis&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 class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;static_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;uint64_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1000.0&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;gFlowControlThresholdLagPercentage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&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="w"&gt;                                      &lt;/span&gt;&lt;span class="n"&gt;gFlowControlTargetLagSeconds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;fudge 因子：比 1 略小的值，主要是为了控制当 lag 与 threshold 接近时，分配略低于 base 数量的票证。默认值为 0.95，可通过参数 &lt;code&gt;flowControlFudgeFactor&lt;/code&gt; 进行配置，该值要求设置为大于 0， 小于 1，尽量接近 1；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;公式的代码主体如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/mongo/db/storage/flow_control.cpp&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;multiplyWithOverflowCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;term1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;term2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;maxValue&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 class="p"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ret&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;term1&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;term2&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 class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;static_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FlowControl&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;_calculateNewTicketsForLag&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 class="k"&gt;auto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exponent&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="k"&gt;static_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lagMillis&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;thresholdLagMillis&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="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;static_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thresholdLagMillis&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;invariant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exponent&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="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reduce&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;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gFlowControlDecayConstant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exponent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// The fudge factor, by default is 0.95. Keeping this value close to one reduces oscillations in&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// an environment where secondaries consistently process operations slower than the primary.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sustainerAppliedPenalty&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;sustainerAppliedCount&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;reduce&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;gFlowControlFudgeFactor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&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 class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;multiplyWithOverflowCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locksPerOp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sustainerAppliedPenalty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_kMaxTickets&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;采样方式&lt;/h4&gt;
&lt;p&gt;采样的频率和数量主要通过如下两个配置参数进行控制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;flowControlSamplePeriod&lt;/code&gt; 这个参数的命名比较容易让人困惑，会容易误认为是时间，实际理解是每 N 次采样一次，N 即为 &lt;code&gt;flowControlSamplePeriod&lt;/code&gt; 设置的值，值越小的精度越高，所以可能更容易触发流控导致业务降级；&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/mongo/db/storage/flow_control.cpp&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;FlowControl::sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Timestamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;uint64_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;opsApplied&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 class="p"&gt;...&lt;/span&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;_numOpsSinceStartup&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;_lastSample&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;static_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gFlowControlSamplePeriod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&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 class="c1"&gt;// Naively sample once every 1000 or so operations.&lt;/span&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 class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;flowControlMaxSamples&lt;/code&gt;，内存中保存的样本数，默认值为 1000000， 每个样本数约为 24 bytes大小，保存 100000 样本数约需 24MB 内存；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;采样在 oplog 相关的操作中进行，参考上述 sample 采样逻辑及下述代码，没有发现显式的采样开关，即&lt;strong&gt;无论是否启用流控制，采样机制都会执行&lt;/strong&gt;。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;//src/mongo/db/repl/local_oplog_info.cpp&lt;/span&gt;

&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OplogSlot&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;LocalOplogInfo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;getNextOpTimes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OperationContext&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;opCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&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 class="p"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Provide a sample to FlowControl after the `oplogInfo.newOpMutex` is released.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ON_BLOCK_EXIT&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;opCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&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 class="k"&gt;auto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;flowControl&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;FlowControl&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opCtx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&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;flowControl&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 class="n"&gt;flowControl&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&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 class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;延迟正常（未触发流控）时 ticket 数量计算&lt;/h3&gt;
&lt;p&gt;当多数提交的延迟小于可容忍流控阈值的延迟百分比时（认为服务状态健康），直接将上一期间分配的 ticket 数量做为基数，并增加一个可配置的常量（票据加法），参数名称为 &lt;code&gt;flowControlTicketAdderConstant&lt;/code&gt; 默认值为 1000。总和乘以另一个可配置的常量（票据乘数），最终的值为下期分配的新票数。该乘数配置参数名称为 &lt;code&gt;flowControlTicketMultiplierConstant&lt;/code&gt; 默认值为 1.05，修改配置需要确保大于 1。&lt;/p&gt;
&lt;p&gt;该算法主要是为了确保系统健康时，特别是初始使用了较少的 ticket 时，系统 ticket 数量可以快速得到提升。公式：&lt;code&gt;(lastTargetTicketsPermitted + flowControlTicketAdderConstant) * flowControlTicketMultiplierConstant&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/mongo/db/storage/flow_control.cpp&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;FlowControl::getNumTickets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Date_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;now&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 class="p"&gt;...&lt;/span&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;isHealthy&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 class="c1"&gt;// The add/multiply technique is used to ensure ticket allocation can ramp up quickly,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// particularly if there were very few tickets to begin with.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;ret&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;multiplyWithOverflowCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_lastTargetTicketsPermitted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&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="w"&gt;                                            &lt;/span&gt;&lt;span class="n"&gt;gFlowControlTicketAdderConstant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="w"&gt;                                        &lt;/span&gt;&lt;span class="n"&gt;gFlowControlTicketMultiplierConstant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="w"&gt;                                        &lt;/span&gt;&lt;span class="n"&gt;kMaxTickets&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 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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;配置参数整理&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;配置参数&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;enableFlowControl&lt;/td&gt;
&lt;td&gt;流控开关 true/false，默认 true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flowControlTargetLagSeconds&lt;/td&gt;
&lt;td&gt;可容忍的多数提交延迟，单位为秒，默认 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flowControlThresholdLagPercentage&lt;/td&gt;
&lt;td&gt;可容忍的多数提交延迟百分比，{ gte: 0.0, lte: 1.0 }，该值乘以 flowControlTargetLagSeconds 为实际可容忍的延迟&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flowControlMaxSamples&lt;/td&gt;
&lt;td&gt;最大的保留样本数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flowControlSamplePeriod&lt;/td&gt;
&lt;td&gt;采样周期，注意不是指&lt;strong&gt;时间&lt;/strong&gt;，指：N 次操作采样一次&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flowControlMinTicketsPerSecond&lt;/td&gt;
&lt;td&gt;最小的 ticket 数量，默认 100。设置过小可能会导致系统需要&lt;strong&gt;预热&lt;/strong&gt;，可以根据系统业务的情况适当调整&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flowControlDecayConstant&lt;/td&gt;
&lt;td&gt;MongoDB 触发流控时节流的速率，默认值为 0.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flowControlFudgeFactor&lt;/td&gt;
&lt;td&gt;Fudge 因子，默认为 0.95，要求小于 1，但接近 1，&lt;strong&gt;不建议调整&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flowControlTicketAdderConstant&lt;/td&gt;
&lt;td&gt;健康状态时，ticket 加数，以快速增加健康状态时的 ticket 数量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flowControlTicketMultiplierConstant&lt;/td&gt;
&lt;td&gt;健康状态时，ticket 乘数，以快速增加健康状态时的 ticket 数量&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;需留意&lt;/strong&gt;，MongoDB 启动时，默认的 ticket 数量为 &lt;code&gt;_kMaxTickets = 1000 * 1000 * 1000&lt;/code&gt; 即 10 亿，而不是 &lt;code&gt;flowControlMinTicketsPerSecond&lt;/code&gt; 的值。通过如下命令可以查看当前的票据情况：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// MongoDB 刚启动时&lt;/span&gt;

&lt;span class="n"&gt;tmongo40&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;PRIMARY&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;serverStatus&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;flowControl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;targetRateLimit&lt;/span&gt;
&lt;span class="mi"&gt;1000000000&lt;/span&gt;

&lt;span class="n"&gt;tmongo40&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;PRIMARY&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;serverStatus&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;flowControl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;enabled&amp;quot;&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;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;targetRateLimit&amp;quot;&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="mi"&gt;1000000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;timeAcquiringMicros&amp;quot;&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;NumberLong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;76&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;locksPerOp&amp;quot;&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sustainerRate&amp;quot;&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;isLagged&amp;quot;&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;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;isLaggedCount&amp;quot;&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;isLaggedTimeMicros&amp;quot;&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;NumberLong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 触发流控后&lt;/span&gt;

&lt;span class="n"&gt;tmongo40&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;PRIMARY&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;serverStatus&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;flowControl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;enabled&amp;quot;&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;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;targetRateLimit&amp;quot;&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="mi"&gt;2015&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;timeAcquiringMicros&amp;quot;&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;NumberLong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12064&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;locksPerOp&amp;quot;&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sustainerRate&amp;quot;&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="mi"&gt;2196&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;isLagged&amp;quot;&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;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;isLaggedCount&amp;quot;&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;isLaggedTimeMicros&amp;quot;&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;NumberLong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;tmongo40&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;PRIMARY&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;serverStatus&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;flowControl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;enabled&amp;quot;&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;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;targetRateLimit&amp;quot;&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="mi"&gt;1014&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;timeAcquiringMicros&amp;quot;&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;NumberLong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;38346&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;locksPerOp&amp;quot;&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;sustainerRate&amp;quot;&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="mi"&gt;1924&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;isLagged&amp;quot;&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;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;isLaggedCount&amp;quot;&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;isLaggedTimeMicros&amp;quot;&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;NumberLong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;日志&lt;/h2&gt;
&lt;p&gt;触发限流时，可以在 MongoDB 的日志里找到 flowControl 相关的日志。&lt;/p&gt;
&lt;p&gt;&lt;img alt="image-20220609174333655" src="/static/mongodb-flowcontrol-log.png"&gt;&lt;/p&gt;
&lt;h2&gt;其他结论&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;好的限流效果需要根据业务的情况持续调测相关参数，且 MongoDB 的限流算法主节点的处理能力受主节点影响，存在多种不确定因素（如网络），建议谨慎开启。或基于前述情况，建议降低流控敏感度，如放宽对大多数主从提交的延迟阈值容忍，仅在较极端情况下对 MongoDB 触发保护，避免过于敏感反而影响正常业务请求；&lt;/li&gt;
&lt;li&gt;根据某业务的线上表现来看，在 CPU、io 吞吐极低的情况下，MongoDB 默认的参数配置仍频繁的触发限流。因此建议仍如 1；&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;参考文档&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;enableFlowControl: https://www.mongodb.com/docs/v4.2/reference/parameters/#param.enableFlowControl&lt;/li&gt;
&lt;li&gt;source code: https://github.com/mongodb/mongo/tree/r4.2.20&lt;/li&gt;
&lt;/ol&gt;</content><category term="misc"/><category term="mongodb"/><category term="flowcontrol"/></entry><entry><title>WireGuard 浅显体验</title><link href="https://www.chenxiaosheng.com/posts/2022-05-15/wireguard-tutorial.html" rel="alternate"/><published>2022-05-15T17:07:00+08:00</published><updated>2022-05-15T17:07:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2022-05-15:/posts/2022-05-15/wireguard-tutorial.html</id><summary type="html">&lt;p&gt;WireGuard® 是一个极其简单、快速且现代的 VPN，它利用了最先进的加密技术。目的是提供一种更快、更易配置、更精简的通用 VPN。最初是为 Linux 开发（并且已经合并至 Linux Kernel），底层是 VPN，现在支持 Windows、macOS、BSD、iOS、Android 等跨平台。目前还处于活跃的开发当中，但仍不失为一个简易友好，且性能、安全性和兼容性都很棒的 VPN 解决方案。&lt;/p&gt;</summary><content type="html">&lt;p&gt;WireGuard® 是一个极其简单、快速且现代的 VPN，它利用了最先进的加密技术。目的是提供一种更快、更易配置、更精简的通用 VPN。最初是为 Linux 开发（并且已经合并至 Linux Kernel, &amp;gt;5.6），底层是 VPN，现在支持 Windows、macOS、BSD、iOS、Android 等跨平台。目前还处于活跃的开发当中，但仍不失为一个简易友好，且性能、安全性和兼容性都很棒的 VPN 解决方案。&lt;/p&gt;
&lt;p&gt;Linus Torvalds 给予 WireGuard 很高的评价（work of art）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Can I just once again state my love for it and hope it gets merged soon? Maybe the code isn't perfect, but I've skimmed it, and compared to the horrors that are OpenVPN and IPSec, it's a work of art.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;安装篇&lt;/h2&gt;
&lt;p&gt;具体的安装可参考 &lt;a href="https://www.wireguard.com/install/"&gt;Installation&lt;/a&gt;，笔者使用的操作系统为 Debian bullseye，官方源已经自带，仅需要 root 用户 &lt;code&gt;apt-get install wireguard&lt;/code&gt; 一条命令搞定。&lt;/p&gt;
&lt;h2&gt;配置篇&lt;/h2&gt;
&lt;p&gt;安装过程比较简单，没有打脸官方所宣传的简单，具体也可以参考官方文档 &lt;a href="https://www.wireguard.com/quickstart/"&gt;QuickStart&lt;/a&gt;。&lt;/p&gt;
&lt;h3&gt;场景一：Peer-To-Peer，两台服务器可互通&lt;/h3&gt;
&lt;p&gt;笔者这里有两台机器，其中服务器 A 为腾讯云的机器，系统内看到的地址为内网地址，在腾讯云配置了公网 eip。&lt;/p&gt;
&lt;p&gt;服务器 B 为一台公网的机器，有直接的公网 IP。（这里留下了一个坑点）&lt;/p&gt;
&lt;h4&gt;生成 WireGuard 公私钥&lt;/h4&gt;
&lt;p&gt;以下命令需要在服务 A 和 服务器 B 执行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# 生成私钥&lt;/span&gt;
wg&lt;span class="w"&gt; &lt;/span&gt;genkey&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;wg.private
&lt;span class="c1"&gt;# 生成公钥&lt;/span&gt;
wg&lt;span class="w"&gt; &lt;/span&gt;pubkey&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;&lt;span class="w"&gt; &lt;/span&gt;wg.private&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;wg.pubkey
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;生成 WireGuard 配置&lt;/h4&gt;
&lt;p&gt;分别在两台机器上创建 &lt;code&gt;/etc/wireguard/wg0.conf&lt;/code&gt; 文件。&lt;/p&gt;
&lt;p&gt;服务器 A 腾讯云机器配置如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[Interface]&lt;/span&gt;
&lt;span class="n"&gt;ListenPort&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="mi"&gt;57259&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# UDP 端口，根据自己的需要设定即可&lt;/span&gt;
&lt;span class="n"&gt;Address&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="mf"&gt;172.16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;1&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="n"&gt;24&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# WireGuard 网络设备 IP 地址，这里使用 172.16.1.1&lt;/span&gt;
&lt;span class="n"&gt;PrivateKey&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="err"&gt;uJj+x/... # 在这台服务器上生成的 wg.private 文件内容&lt;/span&gt;

&lt;span class="k"&gt;[Peer]&lt;/span&gt;
&lt;span class="n"&gt;PublicKey&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="err"&gt;W&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="n"&gt;b9l&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 在服务器 B 生成的 wg.pubkey 文件内容&lt;/span&gt;
&lt;span class="n"&gt;AllowedIPs&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="mf"&gt;172.16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;2&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="n"&gt;32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 服务器 B WireGuard 网络设备 IP 地址，也可以开放 /24 网段&lt;/span&gt;
&lt;span class="n"&gt;Endpoint&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="mi"&gt;108&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xx&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;34101&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 服务器 B 的公网 IP 地址及 ListenPort&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;服务器 B 公网机器配置如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[Interface]&lt;/span&gt;
&lt;span class="n"&gt;ListenPort&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="mi"&gt;34101&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# UDP 端口，根据自己的需要设定即可&lt;/span&gt;
&lt;span class="n"&gt;Address&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="mf"&gt;172.16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;2&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="n"&gt;24&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# WireGuard 网络设备 IP 地址，这里使用 172.16.1.2&lt;/span&gt;
&lt;span class="n"&gt;PrivateKey&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="err"&gt;kETG&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 在这台服务器上生成的 wg.private 文件内容&lt;/span&gt;

&lt;span class="k"&gt;[Peer]&lt;/span&gt;
&lt;span class="n"&gt;PublicKey&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="err"&gt;hTfx&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 在服务器 A 生成的 wg.pubkey 文件内容&lt;/span&gt;
&lt;span class="n"&gt;AllowedIPs&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="mf"&gt;172.16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;0&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="n"&gt;24&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 参考服务器 A，这里开放 /24 网段&lt;/span&gt;
&lt;span class="n"&gt;Endpoint&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="mi"&gt;118&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xx&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;57259&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 服务器 A 的公网 IP 地址及 ListenPort&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;之后分别在两台机器上使用命令 &lt;code&gt;wg-quick up wg0&lt;/code&gt; 后隧道就建议好了，可以通过 ping 来检查。&lt;/p&gt;
&lt;p&gt;到这里还比较顺利，确实非常简单。笔者就转移注意力去开展其他的工作了，过了几分钟回来发现&lt;strong&gt;网络断开&lt;/strong&gt;了，从服务器 A 腾讯云机器单向无法 ping 通 B 服务器，此时从 B 服务器 ping A 服务器后网络恢复（第一个问题）。&lt;/p&gt;
&lt;p&gt;为什么呢？&lt;/p&gt;
&lt;p&gt;怀疑是腾讯云服务器系统内只能看到内网 IP 的问题，毕竟这里是通过网关转发，在我之前其他的网络试验中已经多次踩中这个坑。继续往下翻 &lt;a href="https://www.wireguard.com/quickstart/"&gt;QuickStart&lt;/a&gt; 找到答案：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;if a peer behind NAT or a firewall wishes to receive incoming packets, he must keep the NAT/firewall mapping valid, by periodically sending keepalive packets. This is called &lt;em&gt;persistent keepalives&lt;/em&gt;. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;解法也比较简单，在上面两台机器的 wg0.conf 配置文件中的 &lt;code&gt;[Peer]&lt;/code&gt; 部份分别加上 keepalive 相关的配置即可（默认是 0，也就是不开启，这里我们设置为 25 秒）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PersistentKeepalive = 25&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;场景二：能不能内网穿透呢？&lt;/h3&gt;
&lt;p&gt;再找了一台纯内网的服务器 C，该服务器可以访问外网，但外部网络无法访问该服务器。且不清楚该服务器 C 的外网出口 IP 是什么。&lt;/p&gt;
&lt;h4&gt;配置内网服务器&lt;/h4&gt;
&lt;p&gt;参考场景一的内容进行 WireGuard 安装和配置，假设服务器 C 的 WiredGuard 设备地址设置为 172.16.1.3，则配置如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[Interface]&lt;/span&gt;
&lt;span class="n"&gt;Address&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="mf"&gt;172.16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;3&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="n"&gt;24&lt;/span&gt;
&lt;span class="n"&gt;PrivateKey&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="err"&gt;aHebd...&lt;/span&gt;

&lt;span class="k"&gt;[Peer]&lt;/span&gt;
&lt;span class="n"&gt;PublicKey&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="err"&gt;hTfx&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;AllowedIPs&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="mf"&gt;172.16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;0&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="n"&gt;24&lt;/span&gt;
&lt;span class="n"&gt;Endpoint&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="mi"&gt;118&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xx&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;57259&lt;/span&gt;
&lt;span class="n"&gt;PersistentKeepalive&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="mi"&gt;25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 处于 NAT 环境的设备需要加上 keepalive 配置&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;主要差异：&lt;/strong&gt; &lt;code&gt;ListenPort&lt;/code&gt; 的配置取消了，因为这台机器纯内网，我们无法判定最终映射的公网 IP 和端口分别是什么。上述配置保存后，通过 &lt;code&gt;wg-quick up wg0&lt;/code&gt; 启动设备即可。&lt;/p&gt;
&lt;p&gt;在服务器 A 腾讯云的机器上 &lt;code&gt;/etc/wireguard/wg0.conf&lt;/code&gt; 增加该服务器 C 的 peer 配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[Peer]&lt;/span&gt;
&lt;span class="n"&gt;PublicKey&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="err"&gt;hESH+Md&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 服务器 C 的 wg.pubkey 内容&lt;/span&gt;
&lt;span class="n"&gt;AllowedIPs&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="mf"&gt;172.16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;0&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="n"&gt;24&lt;/span&gt;
&lt;span class="n"&gt;PersistentKeepalive&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="mi"&gt;25&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;主要差异：&lt;/strong&gt; &lt;code&gt;Endpoint&lt;/code&gt; 配置取消了，毕竟 A 不知道 C 用的会是哪个公网 IP 和端口。保存配置后，分别执行 &lt;code&gt;wg-quick down wg0&lt;/code&gt; 和 &lt;code&gt;wg-quick up wg0&lt;/code&gt; 即可。&lt;/p&gt;
&lt;p&gt;本以为到这里已经完成配置了，结果第二个问题来了。两台服务器 ping 不通。再次检查配置确认无误，陷入深深的思考~&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决办法&lt;/strong&gt;：网络不决问防火墙。想到这里因为服务器 C 是在内网，WireGuard 能互通的前提肯定是服务器 C 要先访问服务器 A 的 ListenPort，检查防火墙配置，服务器 A 的 57259 UDP 端口没有对外开放。通过以下命令开放 57259 端口访问：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;iptables -I INPUT 1 -p udp -m udp --dport 57259 -j ACCEPT&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;端口开放后，两台机器之间的互联果然正常了。&lt;/p&gt;
&lt;h2&gt;一些结论&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;如果 wg 两端设备都有公网地址可达，分别配置 Address 和 ListenPort 后，两台机器隧道可以互通，通常无需特殊配置；&lt;/li&gt;
&lt;li&gt;如果设备位于防火墙或网关后面，需要打开 keepalive 配置；&lt;/li&gt;
&lt;li&gt;内网穿透时，做为网关的机器（公网那台）需要开放 ListenPort 的防火墙访问；&lt;/li&gt;
&lt;li&gt;根据需要，你可能需要打开设备的数据包转发（net.ipv4.ip_forward = 1）和网络地址转换功能（NAT/MASQUERADE），这里不展开；&lt;/li&gt;
&lt;/ol&gt;</content><category term="misc"/><category term="wireguard"/><category term="vpn"/></entry><entry><title>Debian 指定内核启动</title><link href="https://www.chenxiaosheng.com/posts/2022-05-07/debian-switch-kernel-boot.html" rel="alternate"/><published>2022-05-07T12:59:00+08:00</published><updated>2022-05-07T12:59:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2022-05-07:/posts/2022-05-07/debian-switch-kernel-boot.html</id><summary type="html">&lt;p&gt;升级完内核发现有一些问题需要回退，但是因为不能直接接触机器终端，需要通过 grub 配置指定内核版本重新启动。&lt;/br&gt;&lt;center&gt;&lt;img alt="" src="/static/debian-bullseye.png"&gt;&lt;/center&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;升级完内核发现有一些问题需要回退，但是因为不能直接接触机器终端，需要通过 grub 配置指定内核版本重新启动。&lt;/p&gt;
&lt;p&gt;操作系统：Debian 11&lt;/p&gt;
&lt;p&gt;涉及文件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;/etc/default/grub&lt;/li&gt;
&lt;li&gt;/boot/grub/grub.cfg&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;操作步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;通过命令 &lt;code&gt;grep -e 'menuentry ' -e 'subm' /boot/grub/grub.cfg | awk -F'--' '{print $1}'&lt;/code&gt; 找到需要启动的内核菜单名称：&lt;/p&gt;
&lt;p&gt;&lt;img alt="image-20220507113012323" src="/static/debian-grub-cfg.png"&gt;&lt;/p&gt;
&lt;p&gt;假设上图中的 &lt;code&gt;Debian GNU/Linux, with Linux 5.10.0-13-amd64&lt;/code&gt; 是我们期望使用的内核，则需要引用图示 1 和 2 修改 &lt;code&gt;/etc/default/grub&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改 &lt;code&gt;/etc/default/grub&lt;/code&gt; 中的 &lt;code&gt;GRUB_DEFAULT=0&lt;/code&gt; 为如下值：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;GRUB_DEFAULT="Advanced options for Debian GNU/Linux&amp;gt;Debian GNU/Linux, with Linux 5.10.0-13-amd64"&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;执行命令 &lt;code&gt;update-grub&lt;/code&gt;，成功后重启即可。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</content><category term="misc"/><category term="debian"/><category term="grub"/><category term="kernel"/></entry><entry><title>当 cgroups 碰上超线程</title><link href="https://www.chenxiaosheng.com/posts/2022-03-29/cgroups-hyperthreading.html" rel="alternate"/><published>2022-03-29T11:24:00+08:00</published><updated>2022-03-29T11:24:00+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2022-03-29:/posts/2022-03-29/cgroups-hyperthreading.html</id><summary type="html">&lt;p&gt;cgroups 是 Linux 内核提供的一种可以限制单个进程或者多个进程所使用资源的机制，可以对 cpu，内存等资源实现精细化的控制。当英特尔® 超线程技术处于激活状态时，CPU 会在每个物理内核上公开两个执行上下文。这意味着，一个物理内核现在就像两个“逻辑内核”一样。这个时候使用 cgroups 进行 cpu 资源隔离会出现什么情况呢？ &lt;/br&gt;&lt;center&gt;&lt;img alt="" src="/static/cpu_arch_ex2.png"&gt;&lt;/center&gt;&lt;/p&gt;</summary><content type="html">&lt;h2&gt;超线程&lt;/h2&gt;
&lt;p&gt;当英特尔® 超线程技术处于激活状态时，CPU 会在每个物理内核上公开两个执行上下文。这意味着，一个物理内核现在就像两个“逻辑内核”一样。超线程技术就是利用特殊的硬件指令，把两个逻辑内核模拟成两个物理芯片，让单个处理器都能使用线程级并行计算，进而兼容多线程操作系统和软件，&lt;strong&gt;减少了 CPU 的闲置时间&lt;/strong&gt;，提高的 CPU 的运行效率。&lt;/p&gt;
&lt;p&gt;虽然采用超线程技术能同时执行两个线程，但它并不象两个真正的 CPU 那样，每个 CPU 都具有独立的资源。当两个线程都同时需要某一个资源时，其中一个要暂时停止，并让出资源，直到这些资源闲置后才能继续。因此&lt;strong&gt;超线程的性能并不等于两颗 CPU 的性能&lt;/strong&gt;。根据 Intel 官方文档反馈，&lt;strong&gt;理论情况下在服务器应用程序中可提升 30% 的性能&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;怎么确认机器是否开启超线程&lt;/h3&gt;
&lt;p&gt;通常情况下，我们只需要通过 &lt;code&gt;/proc/cpuinfo&lt;/code&gt; 即可确认机器是否开启超线程。当相同的 &lt;code&gt;physical id&lt;/code&gt; 和 &lt;code&gt;core id&lt;/code&gt; 出现两次时，即表示机器开启了超线程。&lt;/p&gt;
&lt;center&gt;![image-20210830174503295](/static/cpus_hyperthreading.png)&lt;/center&gt;

&lt;center&gt;图 1. /proc/cpuinfo&lt;/center&gt;

&lt;p&gt;以图 1 为例，在 &lt;code&gt;/proc/cpuinfo&lt;/code&gt; 中共可以找到 48 个 CPU，根据 &lt;code&gt;physical id&lt;/code&gt; 和 &lt;code&gt;core id&lt;/code&gt; 的组合，我们可以画出 CPU 架构图如下图 2。其中如 0-24/2-26/../23-47 表示相同 core id，启用了超线程后操作系统看到的逻辑 CPU 对。&lt;/p&gt;
&lt;center&gt;![image-20210830175849529](/static/cpu_arch.png)&lt;/center&gt;

&lt;center&gt;图 2. 物理 CPU 与逻辑 CPU 关系&lt;/center&gt;

&lt;h2&gt;CGROUPS&lt;/h2&gt;
&lt;p&gt;cgroups 是 Linux 内核提供的一种可以限制单个进程或者多个进程所使用资源的机制，可以对 cpu，内存等资源实现精细化的控制，目前市面上相当流行的轻量级容器 Docker 就使用了 cgroups 提供的资源限制能力来完成 cpu，内存等部分的资源控制。&lt;/p&gt;
&lt;p&gt;笔者目前主要的工作内容是为公司提供数据库私有云服务，为了有效充分的利用服务器资源，我们会利用 cgroups 对资源进行隔离，根据业务需求提供不等规模的机器资源。其中对 cpu 的隔离是通过 &lt;code&gt;cpuset.cpus&lt;/code&gt; 来进行的，通常情况下，我们会保留头部数个 cpu，如 CPU 0-CPU 5 做为系统保留 CPU。其它 CPU 则按顺序，如将 &lt;code&gt;6,7,8&lt;/code&gt;、&lt;code&gt;9,10,11,12，13，14&lt;/code&gt; 分配给两个不同的实例。&lt;/p&gt;
&lt;p&gt;为了业务的可用性考虑，我们的 CPU 分配还是比较充份的，通常 50% 是我们的一个相对安全水位。&lt;strong&gt;所以大多数情况下，上面的按序分配并不会带来什么问题&lt;/strong&gt;。本文主要是探讨极端情况下上述 cpu 分配方式可能造成的问题。&lt;/p&gt;
&lt;h3&gt;问题&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;由于共用了物理 CPU，容器之间可能存在 CPU 争抢：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;以数据库为例，明明 A 实例的负载并不高，但有时候会忽然「卡」那么一下或者刷个慢查询，但业务并没有发生变化，DBA 也无法追踪到不合理的 SQL 或索引缺失等情况&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;center&gt;&lt;img alt="image-20210901144651162" src="/static/cpu_arch_ex1.png"&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;&lt;center&gt;图 3. 逻辑 CPU 分配案例一&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;以上图 3 为例，假设我们给 Container A 绑定了 2, 3, 4 共 3 个 CPU，给 Container B 绑定了 26, 27, 28 共三个 CPU，可以看到 2-26, 4-28, 3-27 分别共享了一个物理核心（core id 相同）。根据对超线程的理解：&lt;code&gt;当两个线程都同时需要某一个资源时，其中一个要暂时停止，并让出资源，直到这些资源闲置后才能继续&lt;/code&gt;，假设 Container A 的负载较高，则 Container B 是有可能受到影响并导致出现 cpu 等待的情况的，反映到业务，则是：&lt;strong&gt;不知道为什么抽风了&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;高负载情况下，隔离资源的实际性能表示与标称值不符：&lt;/p&gt;
&lt;p&gt;&lt;center&gt;&lt;img alt="image-20210901150436639" src="/static/cpu_arch_ex2.png"&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;&lt;center&gt;图 4. 逻辑 CPU 分配案例二&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;以上图 4 为例，假设 0, 1, 2, 45, 46, 47 为保留 CPU（不明确分配实际业务），其它 CPU 按顺序每 6 个一组分配至不同的业务容器，我们可以猜测分配到 21, 22, 23, 24, 25, 26 这 6 个 CPU 的容器会有较少的超线程资源争抢，预计会有更好的性能表现。&lt;/p&gt;
&lt;p&gt;下图为某业务的实际测试图，在施加具备让容器满载压力情况下，&lt;strong&gt;具备较少超线程争抢的容器拥有较好的性能&lt;/strong&gt;。这在大多数情况下可能并不是好事：&lt;/p&gt;
&lt;p&gt;&lt;center&gt;&lt;img alt="image-20210901152629454" src="/static/cpu_load_bench_1.png"&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;&lt;center&gt;图5. 某业务使用“图 4. 逻辑 CPU 分配案例二”压力测试情况&lt;/center&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;strong&gt;一线技术人员极难排查，只能以疑难问题或偶尔抽风等玄学又无奈的口径进行解释&lt;/strong&gt;。对技术人员的业务技能储备有非常高的要求，需要对硬件架构、操作系统、业务架构等多方面都有较清晰的了解。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;推荐分配方案&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;大多数情况下，如果可以预见系统不会出现满载（如达到 70% 或以上），或只有个别实例会出现满载的情况按系统内 CPU 顺序直接分配绑定并不会带来明显问题。这种方式不管是理解还是运维实现，都是最直接和可快速落地的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;相关技术人员需了解此知识点，当出现资源抢占问题时可以有效进行分析排查。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果对于资源隔离、容器间相互影响有较高质量要求的情况下，建议 CPU 以偶数倍进行绑定，并确保同一个 core id 下的两个逻辑 CPU（超线程 HT）被分配绑定至同一个容器/实例。这种方案下，需要对 CPU 的分配进行合理编排调度。（下图以 4 个 CPU 为一组，确保每一个物理核心超线程后的两个逻辑 CPU 被分配至同一组）&lt;/p&gt;
&lt;p&gt;&lt;center&gt;&lt;img alt="image-20210901155115582" src="/static/cpu_arch_ex3.png"&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;&lt;center&gt;图 6. 逻辑 CPU 分配案例三&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;&lt;center&gt;&lt;img alt="image-20210901162859160" src="/static/cpu_load_bench_2.png"&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;&lt;center&gt;图7. 某业务使用“图 6. 逻辑 CPU 分配案例三”压例测试情况&lt;/center&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于 CPU 时间有更细粒度或更精准控制的场景可以考虑通过 CPU 时间分片进行限制，可参考 &lt;a href="https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt"&gt;CFS Bandwidth Control&lt;/a&gt;，这里不展开讨论。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;参考文档&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.intel.cn/content/www/cn/zh/gaming/resources/hyper-threading.html"&gt;什么是超线程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tech.meituan.com/2015/03/31/cgroups.html"&gt;Linux资源管理之cgroups简介&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content><category term="misc"/><category term="cgroups"/></entry><entry><title>速率限制算法：固定窗口与滑动窗口</title><link href="https://www.chenxiaosheng.com/posts/2022-03-28/rate-limit_fixed-window_sliding-window.html" rel="alternate"/><published>2022-03-28T15:59:00+08:00</published><updated>2022-03-28T15:59:00+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2022-03-28:/posts/2022-03-28/rate-limit_fixed-window_sliding-window.html</id><summary type="html">&lt;p&gt;在生产环境中，我们经常需要通过一定的办法方案来保护我们的系统（比如 API）免受无意的或恶意的过度使用。在极端情况下，我们还需要对系统进行适度的降级，以确保系统的可用性。速率限制（rate limit）有助于自动化该过程（有时候也会称为流量控制）。&lt;/br&gt;&lt;img alt="image-20220328154213560" src="/static/sliding_window.png"&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;在生产环境中，我们经常需要通过一定的办法方案来保护我们的系统（比如 API）免受无意的或恶意的过度使用。在极端情况下，我们还需要对系统进行适度的降级，以确保系统的可用性。速率限制（rate limit）有助于自动化该过程（有时候也会称为流量控制）。&lt;/p&gt;
&lt;p&gt;这里我们主要介绍固定窗口计数算法和滑动窗口计数算法。&lt;/p&gt;
&lt;h2&gt;固定窗口 Fixed Window&lt;/h2&gt;
&lt;p&gt;固定窗口算法比较好理解，大致的概念如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;时间被切分成固定大小的窗口，比如 60 秒或 5 分钟等；&lt;/li&gt;
&lt;li&gt;每个时间窗口允许通过的请求阈值固定，假定为 N；&lt;/li&gt;
&lt;li&gt;每一次请求进来，对当前时间窗口的计数器加 1， 假设为 n；&lt;/li&gt;
&lt;li&gt;当当前时间窗口的请求计数超过阈值时（n&amp;gt;N），丢弃当前时间窗口进来的所有请求；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;整体的理解可以参考下述图例 1（固定时间窗口 1 秒，每秒钟允许通过 3 个请求）：&lt;/p&gt;
&lt;p&gt;&lt;img alt="image-20220328142057442" src="/static/fixed_window.png"&gt;&lt;/p&gt;
&lt;p style="text-align:center"&gt; 图 1. 固定窗口算法图例&lt;/p&gt;

&lt;p&gt;看起来好像没有什么毛病，在允许的时间窗口内只允许通过预设的请求。为什么还会需要滑动窗口算法，我们看一下固定窗口算法的问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;以上图为例，假定  14:00:02 秒的 3 个请求是在后 500ms 到达，14:00:03 分的 3 个请求是在前 500ms 到达，实际上一秒内可能通过了超过预设的 3 个请求，系统被击穿（参考下图 2）；&lt;/li&gt;
&lt;li&gt;如果这是一个高并发系统，那么有可能存在大量请求等待时间窗口重置的情况，还是以上图为例，一切换新的时间窗口，系统可能马上在短时间内被击穿不可用（踩踏）；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="图2" src="/static/fixed_window_cons.png"&gt;&lt;/p&gt;
&lt;p style="text-align:center"&gt; 图 2. 固定窗口算法问题&lt;/p&gt;

&lt;h2&gt;滑动窗口 Sliding Window&lt;/h2&gt;
&lt;p&gt;为了解决固定窗口在时间窗口切换边界的流量尖峰问题，引入了滑动窗口计数算法。该算法在固定窗口的基础上，将固定窗口进行更细粒度的切分：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将时间划分为多个固定区间，如 200ms；&lt;/li&gt;
&lt;li&gt;在每个区间内每有一次请求则计数加 1，并维持固定的时间窗口，如固定时间窗口为 1 秒，则占用 5 个时间区间；&lt;/li&gt;
&lt;li&gt;每经过一个区间的时间，抛弃最早的区间，并纳入最新的时间区间；&lt;/li&gt;
&lt;li&gt;如果当前时间窗口的请求数量超过了预设限制，则本时间窗口内的后续请求都会被丢弃；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;基本算法通过下图 3 概要说明，可以直观发现滑动窗口计算方法可以有效避免窗口切换的边界流量尖峰及踩踏问题。并且在我们需要提高流控精度的时候，可以通过划分更细的时间区间来实现。&lt;/p&gt;
&lt;p&gt;&lt;img alt="image-20220328154213560" src="/static/sliding_window.png"&gt;&lt;/p&gt;
&lt;p style="text-align:center"&gt; 图 3. 滑动窗口算法图例&lt;/p&gt;

&lt;p&gt;注：算法可以进一步简化，假设当前时间窗口已经过去 25%，目前的请求数为CurrentN，上一次时间窗口共接受 PreN 次请求（权重为 100%-25%=75%），如果 &lt;code&gt;(PreN * 0.75 + CurrentN) &amp;gt; 预设请求阈值&lt;/code&gt; 则丢弃本次请求，反之放行本次请求。&lt;/p&gt;
&lt;h2&gt;参考文档&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;[How to Design a Scalable Rate Limiting Algorithm with Kong API&lt;/li&gt;
&lt;/ol&gt;</content><category term="misc"/><category term="rate limit"/></entry><entry><title>记一次 TiDB 时区设置异常问题排查</title><link href="https://www.chenxiaosheng.com/posts/2019-10-12/tidb-time_zone-settings.html" rel="alternate"/><published>2019-10-12T22:27:00+08:00</published><updated>2019-10-12T22:27:00+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2019-10-12:/posts/2019-10-12/tidb-time_zone-settings.html</id><summary type="html">&lt;p&gt;两个配置一模一样的 TiDB 集群，在执行 &lt;code&gt;SELECT NOW();&lt;/code&gt; 操作的时候出现了不一样的结果，A 集群的时间与系统时间一致，B 集群的时间比系统时间少了 8 小时。OS、TiDB 配置是统一管理的，究竟是什么原因导致的呢？&lt;/p&gt;</summary><content type="html">&lt;p&gt;收到同事的反馈，两个配置一模一样的 TiDB 集群，在执行 &lt;code&gt;SELECT NOW();&lt;/code&gt; 操作的时候出现了不一样的结果，A 集群的时间与系统时间一致，B 集群的时间比系统时间少了 8 小时。&lt;/p&gt;
&lt;p&gt;&lt;img alt="请在这里输入图片描述" src="/static/20191012150952.png"&gt;&lt;/p&gt;
&lt;p&gt;由于我们的 OS、TiDB 配置是统一管理的，所以我并不怀疑是因为两个集群当前配置不一样导致的。第一反应肯定是因为在 TiDB 启动之前用了错误的时区（UTC），然后 TiDB 引用了错误的时区导致的这个时间问题。根据常规经验，重启一下 TiDB 进程问题应该能解决。&lt;/p&gt;
&lt;p&gt;然而这一次失望了，重启 TiDB 之后，并没有发生什么变化。跟同事要了服务器信息，直接登录到机器上看一下吧。以下是整体的排查过程。&lt;/p&gt;
&lt;h3&gt;先确认是不是，再查为什么&lt;/h3&gt;
&lt;p&gt;首先还是确认是不是有这个问题（再研究为什么），根据&lt;a href="https://pingcap.com/docs-cn/v3.0/how-to/configure/time-zone/#%E6%97%B6%E5%8C%BA%E6%94%AF%E6%8C%81"&gt;官方文档&lt;/a&gt;，检查了 &lt;code&gt;time_zone&lt;/code&gt; 信息，并没有发现异常，有问题的集群和没问题的集群配置也是完全一样的。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;mysql&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="c"&gt; SELECT @@global&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;time_zone&lt;/span&gt;&lt;span class="nt"&gt;,&lt;/span&gt;&lt;span class="c"&gt; @@session&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;time_zone;&lt;/span&gt;
&lt;span class="nb"&gt;+--------------------+---------------------+&lt;/span&gt;
&lt;span class="c"&gt;| @@global&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;time_zone | @@session&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;time_zone |&lt;/span&gt;
&lt;span class="nb"&gt;+--------------------+---------------------+&lt;/span&gt;
&lt;span class="c"&gt;| SYSTEM             | SYSTEM              |&lt;/span&gt;
&lt;span class="nb"&gt;+--------------------+---------------------+&lt;/span&gt;
&lt;span class="c"&gt;1 row in set (0&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;01 sec)&lt;/span&gt;

&lt;span class="c"&gt;mysql&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;&lt;span class="c"&gt; SHOW GLOBAL VARIABLES LIKE &amp;#39;%time_zone&amp;#39;;&lt;/span&gt;
&lt;span class="nb"&gt;+------------------+--------+&lt;/span&gt;
&lt;span class="c"&gt;| Variable_name    | Value  |&lt;/span&gt;
&lt;span class="nb"&gt;+------------------+--------+&lt;/span&gt;
&lt;span class="c"&gt;| system_time_zone | CST    |&lt;/span&gt;
&lt;span class="c"&gt;| time_zone        | SYSTEM |&lt;/span&gt;
&lt;span class="nb"&gt;+------------------+--------+&lt;/span&gt;
&lt;span class="c"&gt;2 rows in set (0&lt;/span&gt;&lt;span class="nt"&gt;.&lt;/span&gt;&lt;span class="c"&gt;00 sec)&lt;/span&gt;

&lt;span class="c"&gt;mysql&lt;/span&gt;&lt;span class="nv"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;虽然这里没有发现明显问题，但我们还是发现了一个疑点：我们的服务器，不可能会有 &lt;code&gt;CST&lt;/code&gt; 这个时区设置（我们用的是 HKT），但 &lt;code&gt;CST&lt;/code&gt; 这个很明显不是问题的原因。&lt;/p&gt;
&lt;h3&gt;日志是一定要看的&lt;/h3&gt;
&lt;p&gt;几乎可以这么说，日志就是为了排查问题而诞生的，任何不能马上判断的问题，都应该去翻一下日志，对服务的健康情况进行判断。因为是时区相关的问题，所以直接对 TiDB 的日志做了一个 &lt;code&gt;fgrep -i timezone tidb.log&lt;/code&gt; 的操作，没想到真的发现了错误日志，毫无波折，就是这么惊喜。（TiDB 日志打印出来是完整一行的，以下内容我稍微格式化了一下，转化为真正的换行）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;019/10/09 16:21:42.111 +08:00&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;ERROR&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;time.go:81&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;&amp;quot;locate timezone files failed&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&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;stack=&amp;quot;github.com/pingcap/tidb/util/timeutil.InferSystemTZ&lt;/span&gt;
&lt;span class="n"&gt;/home/jenkins/workspace/release_tidb_3.0/go/src/github.com/pingcap/tidb/util/timeutil/time.go:81&lt;/span&gt;
&lt;span class="n"&gt;github.com/pingcap/tidb/session.writeSystemTZ&lt;/span&gt;
&lt;span class="n"&gt;/home/jenkins/workspace/release_tidb_3.0/go/src/github.com/pingcap/tidb/session/bootstrap.go:786&lt;/span&gt;
&lt;span class="n"&gt;github.com/pingcap/tidb/session.doDMLWorks&lt;/span&gt;
&lt;span class="n"&gt;/home/jenkins/workspace/release_tidb_3.0/go/src/github.com/pingcap/tidb/session/bootstrap.go:935&lt;/span&gt;
&lt;span class="n"&gt;github.com/pingcap/tidb/session.bootstrap&lt;/span&gt;
&lt;span class="n"&gt;/home/jenkins/workspace/release_tidb_3.0/go/src/github.com/pingcap/tidb/session/bootstrap.go:290&lt;/span&gt;
&lt;span class="n"&gt;github.com/pingcap/tidb/session.runInBootstrapSession&lt;/span&gt;
&lt;span class="n"&gt;/home/jenkins/workspace/release_tidb_3.0/go/src/github.com/pingcap/tidb/session/session.go:1541&lt;/span&gt;
&lt;span class="n"&gt;github.com/pingcap/tidb/session.BootstrapSession&lt;/span&gt;
&lt;span class="n"&gt;/home/jenkins/workspace/release_tidb_3.0/go/src/github.com/pingcap/tidb/session/session.go:1460&lt;/span&gt;
&lt;span class="n"&gt;main.createStoreAndDomain&lt;/span&gt;
&lt;span class="n"&gt;/home/jenkins/workspace/release_tidb_3.0/go/src/github.com/pingcap/tidb/tidb-server/main.go:205&lt;/span&gt;
&lt;span class="n"&gt;main.main&lt;/span&gt;
&lt;span class="n"&gt;/home/jenkins/workspace/release_tidb_3.0/go/src/github.com/pingcap/tidb/tidb-server/main.go:171&lt;/span&gt;
&lt;span class="n"&gt;runtime.main&lt;/span&gt;
&lt;span class="n"&gt;/usr/local/go/src/runtime/proc.go:200&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;几乎可以直接肯定是因为这个报错导致的问题了，但还是需要靠撸代码来证明一下的。&lt;/p&gt;
&lt;h3&gt;撸代码&lt;/h3&gt;
&lt;p&gt;根据上述的错误日志，可以知道最终实际执行出错的函数在 &lt;code&gt;util/timeutil/time.go&lt;/code&gt; 的 &lt;code&gt;InferSystemTZ&lt;/code&gt; 函数里，我们来看下这个函数做了什么：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// InferSystemTZ reads system timezone from `TZ`, the path of the soft link of `/etc/localtime`. If both of them are failed, system timezone will be set to `UTC`.&lt;/span&gt;
&lt;span class="c1"&gt;// It is exported because we need to use it during bootstap stage. And it should be only used at that stage.&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;InferSystemTZ&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&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="c1"&gt;// consult $TZ to find the time zone to use.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// no $TZ means use the system default /etc/localtime.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// $TZ=&amp;quot;&amp;quot; means use UTC.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// $TZ=&amp;quot;foo&amp;quot; means use /usr/share/zoneinfo/foo.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&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="nx"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;TZ&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;switch&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="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err1&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="nx"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EvalSymlinks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/etc/localtime&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&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="nx"&gt;err1&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;nil&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="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err2&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="nx"&gt;inferTZNameFromFileName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&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="nx"&gt;err2&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;nil&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="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&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="nx"&gt;logutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;infer timezone failed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err2&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 class="nx"&gt;logutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;locate timezone files failed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tz&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="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tz&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="s"&gt;&amp;quot;UTC&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&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="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;source&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="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zoneSources&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="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&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="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&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="nx"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&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;nil&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="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tz&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="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 class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;UTC&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;可以看到，TiDB 设置时区的逻辑是首先获取操作系统的 TZ 变量，再从 &lt;code&gt;/etc/localtime&lt;/code&gt; 的实际软链地址提取，最后 fallback 为 UTC。这个逻辑还是与&lt;a href="https://pingcap.com/docs-cn/v3.0/how-to/configure/time-zone/#%E6%97%B6%E5%8C%BA%E6%94%AF%E6%8C%81"&gt;官方文档&lt;/a&gt;的描述一致的。但是我们的机器上肯定是有 &lt;code&gt;/etc/localtime&lt;/code&gt; 文件的，文件肯定也是正确的，为什么会失败呢？再来看一下上述代码里最终分析  &lt;code&gt;/etc/localtime&lt;/code&gt; 的函数 &lt;code&gt;inferTZNameFromFileName&lt;/code&gt; 都做了什么？&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// inferTZNameFromFileName gets IANA timezone name from zoneinfo path.&lt;/span&gt;
&lt;span class="c1"&gt;// TODO: It will be refined later. This is just a quick fix.&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;inferTZNameFromFileName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&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 class="c1"&gt;// phase1 only support read /etc/localtime which is a softlink to zoneinfo file&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;substr&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="s"&gt;&amp;quot;zoneinfo&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// macOs MoJave changes the sofe link of /etc/localtime from&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// &amp;quot;/var/db/timezone/tz/2018e.1.0/zoneinfo/Asia/Shanghai&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// to &amp;quot;/usr/share/zoneinfo.default/Asia/Shanghai&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;substrMojave&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="s"&gt;&amp;quot;zoneinfo.default&amp;quot;&lt;/span&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="nx"&gt;idx&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="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;substrMojave&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;idx&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="mi"&gt;1&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="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;substrMojave&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&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="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;idx&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="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;idx&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="mi"&gt;1&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="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&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="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;path %s is not supported&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;代码的注释里明确写了&lt;strong&gt;只支持 /etc/localtime 为软链接&lt;/strong&gt;，代码的逻辑也是通过截取 &lt;code&gt;/etc/localtime&lt;/code&gt; 软链指向的具体文件路径来获得时区信息。看一下我们服务器上的这个文件：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-alh&lt;span class="w"&gt; &lt;/span&gt;/etc/localtime
-rw-r--r--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.2K&lt;span class="w"&gt; &lt;/span&gt;10月&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;:11&lt;span class="w"&gt; &lt;/span&gt;/etc/localtime
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;果然是一个文件而不是一个软链接。这下又带来了两个疑问：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果我改成软链再重启 TiDB 能不能解决问题呢？实践证明，不能解决。这里不再赘述。&lt;/li&gt;
&lt;li&gt;函数逻辑很明确是 fallback 了 UTC，根据 &lt;code&gt;SELECT NOW();&lt;/code&gt; 早了 8 小时也可以确认是 UTC，可为什么 &lt;code&gt;system_time_zone&lt;/code&gt; 是 CST 呢？&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;看到的（CST）和实际的（UTC）不一样？&lt;/h4&gt;
&lt;p&gt;TiDB 在拿不到正确的时区，回落至 UTC 时，最终 UTC 是怎么设置的呢？我们再来看一下上面的错误日志：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pingcap&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tidb&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writeSystemTZ&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;jenkins&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;workspace&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;release_tidb_3&lt;/span&gt;&lt;span class="mf"&gt;.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;pingcap&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tidb&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;session&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;786&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;在 &lt;code&gt;session/bootstrap.go&lt;/code&gt; 我们找到：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// writeSystemTZ writes system timezone info into mysql.tidb&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;writeSystemTZ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Session&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 class="nx"&gt;sql&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="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`INSERT HIGH_PRIORITY INTO %s.%s VALUES (&amp;quot;%s&amp;quot;, &amp;quot;%s&amp;quot;, &amp;quot;TiDB Global System Timezone.&amp;quot;) ON DUPLICATE KEY UPDATE VARIABLE_VALUE=&amp;quot;%s&amp;quot;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SystemDB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TiDBTable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tidbSystemTZ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;timeutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InferSystemTZ&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;timeutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InferSystemTZ&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;mustExecute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;TiDB 首次启动时，拿到时区信息后，将时区信息写到了 mysql.SystemDB.mysql.TiDBTable，也就是 mysql.tidb，其中 &lt;code&gt;VARIABLE_NAME&lt;/code&gt; 为 &lt;code&gt;tidbSystemTZ&lt;/code&gt;，也即 &lt;code&gt;system_tz&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// The variable name in mysql.tidb table and it will be used when we want to know&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// system timezone.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;tidbSystemTZ&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="s"&gt;&amp;quot;system_tz&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;继续直接登录机器看一下这张参数的内容在两个集群中有什么不一样： &lt;/p&gt;
&lt;p&gt;&lt;img alt="请在这里输入图片描述" src="/static/20191012164104.png"&gt;&lt;/p&gt;
&lt;p&gt;可以看到，确实是设置了 UTC，那么也可以非常明确是由于这个值引起的了。经过测试，可以通过如下两种方式解决：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;更新 mysql.tidb 中 system_tz 为正确的时区，如我们这里为 HKT，并重启 tidb-server 进程后，问题可以得到解决。&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;根据&lt;a href="https://pingcap.com/docs-cn/v3.0/how-to/configure/time-zone/#%E6%97%B6%E5%8C%BA%E6%94%AF%E6%8C%81"&gt;官方文档&lt;/a&gt;设置 &lt;code&gt;time_zone&lt;/code&gt; 变量，问题可以得到解决。如果有需要，可以把 &lt;code&gt;system_tz&lt;/code&gt; 也修改了，tidb-server 不会自行进行调整：&lt;/p&gt;
&lt;p&gt;&lt;img alt="请在这里输入图片描述" src="/static/20191012165413.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;为什么&lt;/h3&gt;
&lt;p&gt;到这里，我们可以很明确 TiDB 参数这里导致时间不对的情况了。但是还有几个疑问还没有得到解决：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;为什么同样的配置，有一个集群是正常的，一个集群又用了 UTC 呢？&lt;/p&gt;
&lt;p&gt;这个情况主要是因为我们的机器有一个初始化的过程，在没有完成初始化的时候，&lt;code&gt;/etc/localtime&lt;/code&gt; 确实是一个软链接，并且指向了 &lt;code&gt;/usr/share/zoneinfo/Asia/Hong_Kong&lt;/code&gt;，当我们的 tidb-server 进程在这个文件被修改之前启动时，会使用正确的时区。但同时，我们在机器上部署了 puppet 进行统一的配置管理，puppet 会做一个类似 &lt;code&gt;cp /usr/share/zoneinfo/Asia/Hong_Kong /etc/localtime&lt;/code&gt; 的操作，将 &lt;code&gt;/etc/localtime&lt;/code&gt; 替换为一个文件，这个时候，tidb-server 无法正确分析时区信息，导致 fall back 至 UTC。简单示意如下：
&lt;img alt="请在这里输入图片描述" src="/static/20191012172800.png"&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为什么 &lt;code&gt;SELECT @@global.time_zone, @@session.time_zone;&lt;/code&gt; 看到的是 CST，但不管是我们的机器还是 TiDB 配置文件都没有引用 CST 这个时区，CST 从哪里来的？&lt;/p&gt;
&lt;p&gt;属于 tidb-server 的默认行为，tidb-server 初次启动时，会默认设置为 CST。可以参考 &lt;code&gt;session/bootstrap.go&lt;/code&gt; 和 &lt;code&gt;sessionctx/variable/sysvar.go&lt;/code&gt; 这里不再展开。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;system_tz&lt;/code&gt;、&lt;code&gt;@@global.time_zone&lt;/code&gt;、&lt;code&gt;@@sessio.time_zone&lt;/code&gt; 的关系是什么？&lt;code&gt;system_tz&lt;/code&gt; 和 &lt;code&gt;time_zone&lt;/code&gt; 两者的生效逻辑是怎样的？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@@session.time_zone&lt;/code&gt; 在新建连接时，值主要依赖于 &lt;code&gt;@@global.time_zone&lt;/code&gt;，session 值等同于 global，默认都为 SYSTEM，即 &lt;code&gt;system_tz&lt;/code&gt; 值；&lt;/li&gt;
&lt;li&gt;如果单独修改了 &lt;code&gt;@@session.time_zone&lt;/code&gt; ，则当前连接的 time_zone 以 session 设置为准；&lt;/li&gt;
&lt;li&gt;tidb-server 在每次启动的时候会从 mysql.tidb 加载 &lt;code&gt;system_tz&lt;/code&gt; 值，并设置系统时区。这也是为什么我们在更新了 &lt;code&gt;system_tz&lt;/code&gt; 并重启 tidb-server 后，可以获取到正确时间的原因；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;tidb-server 启动时通过 mysql.tidb 设置系统时区的相关代码可以在 &lt;code&gt;session/session.go&lt;/code&gt; 找到：&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// loadSystemTZ loads systemTZ from mysql.tidb&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;loadSystemTZ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;se&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;session&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&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 class="nx"&gt;sql&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="s"&gt;`select variable_value from mysql.tidb where variable_name = &amp;#39;system_tz&amp;#39;`&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;rss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errLoad&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="nx"&gt;se&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&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="nx"&gt;errLoad&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;nil&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="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errLoad&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="c1"&gt;// the record of mysql.tidb under where condition: variable_name = &amp;quot;system_tz&amp;quot; should shall only be one.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&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 class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&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="nx"&gt;rss&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&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;nil&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="nx"&gt;logutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;close result set error&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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 class="p"&gt;}()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;req&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="nx"&gt;rss&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;NewChunk&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&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="nx"&gt;err&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="nx"&gt;rss&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&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;nil&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="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&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="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GetRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// BootstrapSession runs the first time when the TiDB server start.&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BootstrapSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Storage&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="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;小结&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;其实还蛮普通的一个问题，只是之前碰到比较多的情况是在环境初始化没完成时就启动服务，导致服务出现状况。这次 TiDB 这里刚好反过来，环境初始化未操作之前反而是正常的，初始化之后异常了；&lt;/li&gt;
&lt;li&gt;问题的线索其实完完整整的保存在了日志中，流程是否有可以优化的空间有待考证商榷，但日志在排查问题的时候是一定要首先关注的；&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/etc/localtime&lt;/code&gt; 直接使用文件不是一个合理的方式，TZ 环境变量的优先级也高于 &lt;code&gt;/etc/localtime&lt;/code&gt;。这个可以参考 &lt;a href="http://man7.org/linux/man-pages/man5/localtime.5.html"&gt;man 文档&lt;/a&gt;：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Because the timezone identifier is extracted from the symlink target 
name of /etc/localtime, this file may not be a normal file or
hardlink.&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</content><category term="misc"/><category term="dm"/><category term="mysql"/><category term="tidb"/><category term="pingcap"/></entry><entry><title>TiDB DM 数据库同步 STEP BY STEP</title><link href="https://www.chenxiaosheng.com/posts/2019-09-29/tidb-dm-syncer.html" rel="alternate"/><published>2019-09-29T13:08:00+08:00</published><updated>2019-09-29T13:08:00+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2019-09-29:/posts/2019-09-29/tidb-dm-syncer.html</id><summary type="html">&lt;p&gt;前段时间，看到了一篇关于数据库选型的文章（MongoDB/TiDB/CockroachDB），忍不住感慨了一句，TiDB 一看就是大户人家，什么工具都可以有，什么工 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;前段时间，看到了一篇关于数据库选型的文章（MongoDB/TiDB/CockroachDB），忍不住感慨了一句，TiDB 一看就是大户人家，什么工具都可以有，什么工具都迭代得特别快，没有钱没有人，难以想像如何才能驱动得了。TiDB/TiKV/PD/Mydumper/Loader/Syncer/Data Migration/TiDB Lighting/Pump/Drainer ... 光是弄清楚这些组件是用来做什么的，感觉都可以评个高工了 :-)&lt;/p&gt;
&lt;p&gt;随着 DM 1.0 GA 的 release，我们也开始考虑将 syncer 迁移至 DM。主要原因还是 syncer 已经太久没有更新了，否则 syncer 这种简易几乎无须部署的工具还是比较对我个人的胃口的，官方很喜欢把工具集群化（可能听起来厉害一点），DM 也是，那就按着最小目标（syncer）折腾一下吧。&lt;/p&gt;
&lt;p&gt;DM 的架构文档可以参考&lt;a href="https://pingcap.com/docs-cn/v3.0/reference/tools/data-migration/overview/#dm-架构"&gt;官方&lt;/a&gt;，整个集群有三个角色 dmctl/dm-master/dm-worker。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;DM-master 负责管理和调度数据同步任务的各项操作。&lt;/li&gt;
&lt;li&gt;DM-worker 负责执行具体的数据同步任务。&lt;/li&gt;
&lt;li&gt;dmctl 是用来控制 DM 集群的命令行工具。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="Data Migration architecture" src="https://pingcap.com/images/docs-cn/dm-architecture.png"&gt;&lt;/p&gt;
&lt;p&gt;官方也提供了基于 Ansible 的部署工具，如果你不是特别需要了解实际的部署过程，或者你的环境对 ansible 的亲和度比较好的话，可以参考&lt;a href="https://pingcap.com/docs-cn/v3.0/how-to/deploy/data-migration-with-ansible"&gt;使用 DM-Ansible 部署 DM 集群&lt;/a&gt;。在这篇文章里，我们还是希望对于 DM 的部署有一定的了解，另外我们的小目标是实现类似 syncer 功能，因此，让我们 step by step 人肉部署吧。（好吧，吐槽一下，其实本人是很反感什么都要 ansible 再包一层的，无法直接通过文档了解工作方式，还非得去读 ansible 的相关部署文件）。&lt;/p&gt;
&lt;h2&gt;准备工作&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;确认工作目录，假定这里为 &lt;code&gt;/home/dm&lt;/code&gt;，并确保相关目录已经建立且具备读写权限：&lt;code&gt;mkdir -p /home/dm/log&lt;/code&gt;；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;确保机器上已经具备相应的命令行工具，如：tar、wget、rsync 或 cp 等；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;下载 DM 二进制文件：&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;wget&lt;span class="w"&gt; &lt;/span&gt;http://download.pingcap.org/dm-latest-linux-amd64.tar.gz
tar&lt;span class="w"&gt; &lt;/span&gt;-zxvf&lt;span class="w"&gt; &lt;/span&gt;dm-latest-linux-amd64.tar.gz
rsync&lt;span class="w"&gt; &lt;/span&gt;-avz&lt;span class="w"&gt; &lt;/span&gt;dm-latest-linux-amd64/bin/&lt;span class="w"&gt; &lt;/span&gt;/home/dm/bin/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;机器列表，这里我们主要熟悉部署过程，且不需要分库分表合并等功能，只实现 syncer 流程，因此 master 和 worker 均只部署在同一台同器：&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;角色&lt;/th&gt;
&lt;th&gt;地址&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DM-master&lt;/td&gt;
&lt;td&gt;127.0.0.1:8261&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DM-worker&lt;/td&gt;
&lt;td&gt;127.0.0.1:8262&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;配置文件&lt;/h2&gt;
&lt;h3&gt;dm-worker&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# DM-worker Configuration: worker.toml&lt;/span&gt;
&lt;span class="c1"&gt;# MySQL Server ID, 注意不要和已有的 ID 冲突&lt;/span&gt;
&lt;span class="n"&gt;server-id&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="mi"&gt;8262&lt;/span&gt;
&lt;span class="c1"&gt;# 可以理解为 worker 的唯一标识&lt;/span&gt;
&lt;span class="n"&gt;source-id&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="s2"&gt;&amp;quot;worker-8262&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;worker-addr&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="s2"&gt;&amp;quot;127.0.0.1:8262&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# 支持源为 mysql 或 mariadb&lt;/span&gt;
&lt;span class="n"&gt;flavor&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="s2"&gt;&amp;quot;mysql&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# 是否启用 GTID，开启前需要上游 MySQL 开启 GTID 功能&lt;/span&gt;
&lt;span class="n"&gt;enable-gtid&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;true&lt;/span&gt;
&lt;span class="c1"&gt;# MySQL Master/Slave 发生切换时，是否修复 gtid&lt;/span&gt;
&lt;span class="n"&gt;auto-fix-gtid&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;true&lt;/span&gt;
&lt;span class="c1"&gt;# 开始同步的位置，我这里使用 GTID，该值为 MySQL Master 上的 Executed_Gtid_Set 值&lt;/span&gt;
&lt;span class="c1"&gt;# relay-binlog-name = &amp;quot;binlog.000140&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;relay-binlog-gtid&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="s2"&gt;&amp;quot;2ea42e60-a157-11e9-8f2d-0ef02be0adee:1-3472558,34204ac9-a157-11e9-bc03-c6e9019f8073:1-2&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# charset of DSN of source mysql/mariadb instance&lt;/span&gt;
&lt;span class="n"&gt;charset&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="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# worker 元数据存储目录，需要提前创建好&lt;/span&gt;
&lt;span class="n"&gt;meta-dir&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="s2"&gt;&amp;quot;/home/dm/meta/worker-8262&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# relay log 的本地存储目录，需要提前创建好&lt;/span&gt;
&lt;span class="n"&gt;relay-dir&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="s2"&gt;&amp;quot;/home/dm/relay/worker-8262&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# 源数据库，一个 worker 只能对应一个 MySQL 源&lt;/span&gt;
&lt;span class="k"&gt;[from]&lt;/span&gt;
&lt;span class="n"&gt;host&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="s2"&gt;&amp;quot;127.0.0.1&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;user&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="s2"&gt;&amp;quot;dm&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# 该密码为使用 dmctl -encrypt ${你的原始密码} 加密后的字符串&lt;/span&gt;
&lt;span class="n"&gt;password&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="s2"&gt;&amp;quot;vUYLfdtQlHLF6Kr4IHg8NkDjiPtVig6p&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;port&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="mi"&gt;3306&lt;/span&gt;

&lt;span class="c1"&gt;# relay log purge strategy&lt;/span&gt;
&lt;span class="k"&gt;[purge]&lt;/span&gt;
&lt;span class="n"&gt;interval&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="mi"&gt;3600&lt;/span&gt;
&lt;span class="n"&gt;expires&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="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;remain-space&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="mi"&gt;15&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;配置不算特别复杂，需要留意的地方都在注释里进行说明了。其中 &lt;code&gt;source-id&lt;/code&gt; 请记下来，接着配置 dm-master 需要用到。&lt;/p&gt;
&lt;p&gt;再次提醒，MySQL 的密码配置需要使用 dmctl 进行加密处理。&lt;/p&gt;
&lt;h3&gt;dm-master&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# dm-master Configuration: master.toml&lt;/span&gt;
&lt;span class="n"&gt;log-level&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="s2"&gt;&amp;quot;info&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;log-file&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="s2"&gt;&amp;quot;/home/dm/log/dm-master.log&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;master-addr&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="s2"&gt;&amp;quot;127.0.0.1:8261&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;[[deploy]]&lt;/span&gt;
&lt;span class="n"&gt;source-id&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="s2"&gt;&amp;quot;worker-8262&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;dm-worker&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="s2"&gt;&amp;quot;127.0.0.1:8262&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;配置文件比较简单，基本上都不需要解释。其中 &lt;code&gt;[[deploy]]&lt;/code&gt; 对应一个 worker，可以有多个 &lt;code&gt;[[deploy]]&lt;/code&gt; 。目前了解到的，dm-worker 只能通过配置文件并重启 dm-master 进行加载，无法通过 dmctl 进行运行上填加。好在 dm-master 几乎随时都可以重启，所以也不是特别大的问题。&lt;/p&gt;
&lt;h3&gt;dmctl&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# dmctl Configuration: ctl.toml&lt;/span&gt;
&lt;span class="n"&gt;master-addr&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="s2"&gt;&amp;quot;127.0.0.1:8261&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;dmctl 的配置就相当简单了，只需要指定 dm-master 地址。而 dmctl 也是我们与 dm-master 打交道的最直接工作。&lt;/p&gt;
&lt;h2&gt;集群启动&lt;/h2&gt;
&lt;p&gt;经过上述的配置（是不是没有想像中复杂），我们已经可以开启集群了：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;启动 master: bin/dm-master -config master.toml&lt;/li&gt;
&lt;li&gt;启动 worker: bin/dm-worker -config worker.toml&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;除了跟踪日志输出，确认进程是否正常启动。我们还可以借助 dmctl 来检查当前集群的情况，使用 &lt;code&gt;bin/dmctl -config ctl.toml&lt;/code&gt; 连接集群，并通过 &lt;code&gt;query-status&lt;/code&gt; 查询集群状态，返回如下（成功）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;»&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;query&lt;/span&gt;&lt;span class="mi"&gt;-&lt;/span&gt;&lt;span class="err"&gt;s&lt;/span&gt;&lt;span class="kc"&gt;tatus&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;result&amp;quot;&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="nt"&gt;&amp;quot;msg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;workers&amp;quot;&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 class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;result&amp;quot;&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="nt"&gt;&amp;quot;worker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;127.0.0.1:8262&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;msg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;no sub task started&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;subTaskStatus&amp;quot;&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 class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relayStatus&amp;quot;&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 class="nt"&gt;&amp;quot;masterBinlog&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(binlog.000172, 234)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;masterBinlogGtid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2ea42e60-a157-11e9-8f2d-0ef02be0adee:1-3472558,34204ac9-a157-11e9-bc03-c6e9019f8073:1-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relaySubDir&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2ea42e60-a157-11e9-8f2d-0ef02be0adee.000001&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relayBinlog&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(, 4)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relayBinlogGtid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;34204ac9-a157-11e9-bc03-c6e9019f8073:1-2,2ea42e60-a157-11e9-8f2d-0ef02be0adee:1-3472558&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;relayCatchUpMaster&amp;quot;&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="nt"&gt;&amp;quot;stage&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Running&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;result&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&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="nt"&gt;&amp;quot;sourceID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;worker-8262&amp;quot;&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="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;»&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这个时候，系统还没有配置任何同步任务，所以也可以看到显示的是：&lt;code&gt;no sub task started&lt;/code&gt;。接下来，我们开始来配置一个最基础的同步任务。&lt;/p&gt;
&lt;h2&gt;配置并应用同步任务&lt;/h2&gt;
&lt;h3&gt;创建任务配置文件&lt;/h3&gt;
&lt;p&gt;PingCAP 在任务配置这一块有比较完善的说明了&lt;a href="https://pingcap.com/docs-cn/v3.0/reference/tools/data-migration/configure/task-configuration-file/"&gt;DM 任务配置文件介绍&lt;/a&gt;。这里我们以将源库为 &lt;code&gt;abc&lt;/code&gt; 迁到目标库 &lt;code&gt;abc_dm&lt;/code&gt; 为例子做简单说明：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;---&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;
&lt;span class="n"&gt;task-mode&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;
&lt;span class="n"&gt;is-sharding&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;
&lt;span class="n"&gt;meta-schema&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dm_meta&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;remove-meta&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;
&lt;span class="n"&gt;enable-heartbeat&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;

&lt;span class="n"&gt;target-database&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;192.168.143.41&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;3306&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dm&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vUYLfdtQlHLF6Kr4IHg8NkDjiPtVig6p&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;mysql-instances&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# 源库及相关的同步规则配置&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;-&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;source-id&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;worker-8262&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;binlog-name&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;binlog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;000140&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;binlog-pos&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;141868&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;route-rules&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;user-route-rules-schema&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;black-white-list&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;instance&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;user-route-rules-schema&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;schema-pattern&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;abc&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;target-schema&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;abc_dm&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;black-white-list&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;do-dbs&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;abc&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;上面参数的说明都可以在官方文档找到，这里对几个重要的参数再做补充说明：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;meta-schema&lt;/td&gt;
&lt;td&gt;DM 会将该 task 对应的  meta 信息，如断点（checkpoint）存储至目标集存中的该数据库&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;remove-meta&lt;/td&gt;
&lt;td&gt;通常我们希望在重启 worker/task 时可以从上次断点继续同步，需要设置为 false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;password&lt;/td&gt;
&lt;td&gt;需要使用 dmctl 加密&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;source-id&lt;/td&gt;
&lt;td&gt;理解为相应 dm worker 的 source-id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;routes&lt;/td&gt;
&lt;td&gt;可以在这里配置数据库表的改名规则，具体参考&lt;a href="https://pingcap.com/docs-cn/v3.0/reference/tools/data-migration/configure/task-configuration-file/"&gt;官方文档&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;filters&lt;/td&gt;
&lt;td&gt;这里没有用到，可以过滤相应的 binlog events，如 &lt;code&gt;drop table&lt;/code&gt; 操作，具体参考&lt;a href="https://pingcap.com/docs-cn/v3.0/reference/tools/data-migration/configure/task-configuration-file/"&gt;官方文档&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;应用并启动同步任务&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;bin/dmctl -config ctl.toml&lt;/code&gt; 登录 dm master，并执行 &lt;code&gt;start-task test.yaml&lt;/code&gt; 即可，确认 &lt;code&gt;test.yaml&lt;/code&gt; 文件在当前目录下，成功的话将收到如下返回：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;result&amp;quot;&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="nt"&gt;&amp;quot;msg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;workers&amp;quot;&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 class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;result&amp;quot;&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="nt"&gt;&amp;quot;worker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;127.0.0.1:8262&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;msg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&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="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这个时候再执行 &lt;code&gt;query-status&lt;/code&gt; 可以看到相应的 dm worker 有了具体的 subTaskStatus 信息：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;subTaskStatus&amp;quot;&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 class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;test&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;stage&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Paused&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;unit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Sync&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;result&amp;quot;&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 class="nt"&gt;&amp;quot;isCanceled&amp;quot;&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 class="nt"&gt;&amp;quot;errors&amp;quot;&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 class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;detail&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&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="nt"&gt;&amp;quot;unresolvedDDLLockID&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;sync&amp;quot;&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 class="nt"&gt;&amp;quot;totalEvents&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;totalTps&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;recentTps&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;masterBinlog&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(binlog.000176, 234)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;masterBinlogGtid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2ea42e60-a157-11e9-8f2d-0ef02be0adee:1-3472558,34204ac9-a157-11e9-bc03-c6e9019f8073:1-2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;syncerBinlog&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(, 4)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;syncerBinlogGtid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;blockingDDLs&amp;quot;&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 class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;unresolvedGroups&amp;quot;&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 class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;synced&amp;quot;&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="w"&gt;                    &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 class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这个时候我们通过 mysql 登录目标数据库，可以看到多了 &lt;code&gt;dm_meta&lt;/code&gt; 和 &lt;code&gt;abc_dm&lt;/code&gt; 两个数据库，其中 &lt;code&gt;dm_meta&lt;/code&gt; 里面有两张表：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;test_loader_checkpoint&lt;/code&gt; 其中 test 是任务的名称，这张表存储的是全量同步的 meta 信息；&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;            id: worker-8262
      filename: abc.t3.sql
     cp_schema: abc
      cp_table: t3
        offset: 156
       end_pos: 156
   create_time: 2019-09-29 12:03:59
   update_time: 2019-09-29 12:04:00
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;test_syncer_checkpint&lt;/code&gt; 其中 test 是任务的名称，这张表存储的是增量同步的 meta 信息；&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;            id: worker-8262
     cp_schema: abc
      cp_table: xxy
   binlog_name: binlog|000001.000176
    binlog_pos: 538
     is_global: 0
   create_time: 2019-09-29 12:11:25
   update_time: 2019-09-29 12:11:25
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;组件升级&lt;/h2&gt;
&lt;h3&gt;dm-master&lt;/h3&gt;
&lt;p&gt;dm-master 主要负责 dmctl 的通讯，维护任务及 Sharding DDL lock 信息，重启后会与 dm-worker 重启这些信息，所以除了影响 dmctl 短暂的使用，可以随时重启，升级也就比较简单了：下载新版本 -&amp;gt; 覆盖旧 binary -&amp;gt; 重启。&lt;/p&gt;
&lt;h3&gt;dm-worker&lt;/h3&gt;
&lt;p&gt;dm-worker 在本地维护了 meta 信息，在下游（目标）数据库维护了断点（checkpoint）信息，升级也是按：下载新版本 -&amp;gt; 覆盖旧 binary -&amp;gt; 重启即可。唯一需要注意的有：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;尽量避免在 sharding DDL 同步过程中重启 DM-worker。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当然，本身对数据的敬畏之心，即使没有 sharding，也不推荐在 DDL 的过程中重启 dm-worker。&lt;/p&gt;
&lt;h3&gt;dmctl&lt;/h3&gt;
&lt;p&gt;命令行交互工具，直接升级即可：下载新版本 -&amp;gt; 覆盖旧 binary。&lt;/p&gt;
&lt;h2&gt;问题处理&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;dm-worker 挂了，并且本地的 meta 信息都丢失了怎么办？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通常不需要担心，找回按原来的 worker.toml 再启动 worker 即可。其中配置文件中的 &lt;code&gt;relay-binlog-gtid&lt;/code&gt; 或 &lt;code&gt;relay-binlog-name&lt;/code&gt; 的位置信息可能在源数据库已经过期，可以从目标集群的 meta 库找回，或者直接从源数据库集群上使用一个相对靠前的位置信息替换即可。&lt;/p&gt;
&lt;p&gt;确认新的 worker 已经启动后，再次通过 dmctl 执行 &lt;code&gt;start-task test.yaml&lt;/code&gt; 即可，task 的配置文件不需要修改，断点信息会从目标数据库中的 meta 库直接获取。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;官方的组件（概念）特别多，ansible 的包装让用户对相关组件的工作流程了解门槛大大提升。实际部署流程比想像中较为简单。从可靠性来看，还是比较推荐放弃 syncer（较长时间未更新了），并使用 dm 做为替代工具。&lt;/p&gt;</content><category term="misc"/><category term="dm"/><category term="mysql"/><category term="tidb"/><category term="syncer"/><category term="pingcap"/></entry><entry><title>MySQL Seconds_Behind_Master 忽大忽小？莫慌</title><link href="https://www.chenxiaosheng.com/posts/2019-09-07/mysql-large-seconds_behind_master.html" rel="alternate"/><published>2019-09-07T22:33:00+08:00</published><updated>2019-09-07T22:33:00+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2019-09-07:/posts/2019-09-07/mysql-large-seconds_behind_master.html</id><summary type="html">&lt;p&gt;MySQL Slave Seconds_Behind_Master 是否可以直接做为数据复制是否延迟的衡量标准？在日常监控中是否有需要注意或特别避开的坑点？有没有更好的监控方式呢？&lt;/br&gt; &lt;img alt="MySQL Replication示意图" src="/static/mysql_repl.png"&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;本周在某次开会的时候，忽然收到了大量的 MySQL Seconds_Behind_Master 落后的报警轰炸（落后时间随机，几百到几万秒不等），但是当我们的值班 DBA 登录 MySQL 通过 &lt;code&gt;show slave status\G&lt;/code&gt; 查看时，发现都是 0，通常我们认为这个时候复制是正常的。那么是什么情况导致了报警的发生呢？&lt;/p&gt;
&lt;p&gt;DBA 值班同学也比较没有头绪，我们首先做了以下几件事情：&lt;/p&gt;
&lt;ol&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;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;事后打脸了，有一位同学做了报警策略的变更，从原 X 时间内只发送第 N 条报警修改成了 X 时间内只发送第 1 条报警。该策略的变更也会导致该报警的轰炸，但是他变更的时间是在报警轰炸之后，跟此次报警轰炸原因没有正相关；&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;登录多个实例确认都是误报后，先通告产品，明确是误报事件，让产品不用担心，等待 DBA 查明原因；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;接下来我们会议正常进行，由某位 DBA 同学对此问题进行持续跟进，这个时候了解到某报警系统正在维护，猜测与他们有关系。但是即使是与他们有关系，这个落后的时间又是如何得来的呢？暂时 DBA 值班同学也没有很好的 GET 到头绪，DBA 老司机可能当时不是特别在状态，有点懵。&lt;/p&gt;
&lt;p&gt;刚好会议环节进入非重点环节，偷偷转移精力排查了一下，做为 DBA 门外汉，首先想到的是我要确认是不是 MySQL &lt;code&gt;show slave status&lt;/code&gt; 里真的出现了随机的落后数值，所以我做了最原始的一件事情：写了一个 2 秒执行一次 &lt;code&gt;show slave status&lt;/code&gt; 的脚本对 &lt;code&gt;Secons_Behind_Master&lt;/code&gt; 进行检查，果然在脚本执行了几分钟后出现了非 0 的数值，且是一个较大的超出实际线上情况的数值。（这里已经确认了是 MySQL 本身会有这个情况出现）&lt;/p&gt;
&lt;p&gt;接下来怎么办呢？门外汉的第一反应当然是查手册啦（手册大多数时候都是解毒良药），几乎是没有耗费时间的，直接在 MySQL 官方&lt;a href="https://dev.mysql.com/doc/refman/5.7/en/replication-administration-status.html"&gt;文档&lt;/a&gt;里关于这个问题的非常重要的一句说明：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;when the slave I/O thread is still queuing up a new event,
Seconds_Behind_Master may show a large value until the SQL thread 
finishes executing the new event.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;MySQL Replication 通常情况下有两个工作线程，I/O 线程用于获取 MySQL Master 的 Binlog，SQL 线程用于将获取到的 Binlog Events 在 Slave 从库上进行回放。&lt;/p&gt;
&lt;p&gt;&lt;img alt="MySQL Replication示意图" src="/static/mysql_repl.png"&gt;&lt;/p&gt;
&lt;p&gt;上面的描述我们通过字面简单进行了理解：当 I/O 线程仍在排队等待一件新事件时，&lt;code&gt;Seconds_Behind_Master&lt;/code&gt; 有可能返回一个较大的随机值，直到 SQL 线程完成该事件的重放。&lt;/p&gt;
&lt;p&gt;那么这里我们已经可以确认了，MySQL 是会出现这个情况的，并且是正常的，至少这个报警轰炸是不用特别担心了。那为什么忽然就炸出来了呢，在后面几分钟后我们得到了几个信息，确认了原因：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;该报警原先的设置是 X 时间内只发送第 N 条报警：X 时间内并不一定总会出现 &amp;gt;= N 次的随机 &lt;code&gt;Seconds_Behind_Master&lt;/code&gt; 飙升（并且被监控插件获取到）；即使出现 &amp;gt;= N 次，在 X 时间内也只会发送一条报警，所以这个问题一直并没有被注意到；&lt;/li&gt;
&lt;li&gt;从报警平台的同事了解到，确实在维护过程中，报警策略匹配有问题，导致 &lt;code&gt;X 时间内只发送第 N 条&lt;/code&gt; 这个策略没有生效，所有报警都发了出来；&lt;/li&gt;
&lt;li&gt;如前述，某同事确实做了策略变更，该变更可以明确是错的，但跟上述的轰炸没有正相关。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个时候我们整体的原因已经比较清楚了，恢复生效的策略，并同步通告相关产品具体的情况，让产品安心是我们服务的目标之一。&lt;/p&gt;
&lt;p&gt;但这里也暴露了几个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;报警平台维护变更出现了一定的问题，导致策略失效；&lt;/li&gt;
&lt;li&gt;部分 DBA 同学在某些环节对系统的了解并不够完整、也不够专业（我们都需要不断学习积累的，我自己也是学习中，能理解）；&lt;/li&gt;
&lt;li&gt;DBA 同学在做策略变更的时候也没有在团队内部进行同步，存在管理和规范遵守上的问题；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以上问题需要我们进行反思，并在团队内进行解决。但是 MySQL Seconds_Behind_Master 是否还有监控的价值呢？对可能导致 &lt;code&gt;Seconds_Behind_Master&lt;/code&gt; 异常的情况这里做个简单的总结：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Replication 启动时，Slave 会在主库执行 &lt;code&gt;SELECT UNIX_TIMESTAMP()&lt;/code&gt; 并进行主库和从库的系统时间差值对比，以避免由于系统时间差异对于 &lt;code&gt;Seconds_Behind_Master&lt;/code&gt; 的时间产生影响。但如果是 Replication 启动后的时间异常，还是会对 &lt;code&gt;Seconds_Behind_Master&lt;/code&gt; 产生影响，因此确保系统时间的同步非常重要（ntpd）；&lt;/li&gt;
&lt;li&gt;当 Master 和 Slave 间的网络较差时，I/O 线程不能快速的获取 Binlog Events，SQL 线程处理速度是有可能超过 I/O 线程的，那么这个时候即使是 0，也不代表主从同步是正常的。（良好的网络环境是主从同步的基础保障）&lt;/li&gt;
&lt;li&gt;当 I/O 线程被关闭时，该值可能为 NULL。（可以理解为特殊情况）；&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Seconds_Behind_Master 怎么用？&lt;/h3&gt;
&lt;p&gt;结合以上情况，我认为 &lt;code&gt;Seconds_Behind_Master&lt;/code&gt; 还是一个很重要的同步是否落后的参考值的，只是可能我们可以做得更完善一些，比如在获取的时候，可以通过二次获取进行判断，当获取到较大值时，再次获取一次当前值进行确认。&lt;/p&gt;
&lt;p&gt;另外，如果需要较为严格的同步监控，建议通过 Binglog 的位置：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Master_Log_file/Read_Master_Log_Pos&lt;/li&gt;
&lt;li&gt;Relay_Master_Log_File/Exec_Master_Log_Pos&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;进行判断，如果(Relay_Master_Log_File, Exec_Master_Log_Pos) 和 (Relay_Master_Log_File, Read_Master_Log_Pos)位置相等且 Seconds_Behind_Master=0，那么我们可以认为目前的同步是「同步」的。（开启 gtid 的话，比对 &lt;code&gt;Executed_Gtid_Set&lt;/code&gt; 我认为也是没有毛病的）&lt;/p&gt;
&lt;h3&gt;The End&lt;/h3&gt;
&lt;p&gt;后续相关技术内容的总结，也会逐步更新至微信平台（微信目前确实也是技术内容流转的一个好渠道），欢迎关注个人公众号（也算给自己立个 flag 吧，希望 flag 永不倒）：&lt;/p&gt;
&lt;p&gt;&lt;img alt="公众号二维码" src="/static/qrcode.jpg"&gt;&lt;/p&gt;</content><category term="misc"/><category term="mysql"/><category term="replication"/></entry><entry><title>在 Mac 中对 iPhone 手机网络进行抓包的方法</title><link href="https://www.chenxiaosheng.com/posts/2018-11-02/capture-packet-from-ios-through-mac.html" rel="alternate"/><published>2018-11-02T19:58:00+08:00</published><updated>2018-11-02T19:58:00+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2018-11-02:/posts/2018-11-02/capture-packet-from-ios-through-mac.html</id><summary type="html">&lt;ol&gt;
&lt;li&gt;通过数据线连接 Mac 笔记本，并通过 itunes 查询手机的 UUID
&lt;img alt="iphone uuid" src="/static/iphone_uuid.png"&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mac 提供了一个工具 rvictl （rvi=Remote Virtual Interface）可以为连上的 iphone 手机创建一个虚拟网 …&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">&lt;ol&gt;
&lt;li&gt;通过数据线连接 Mac 笔记本，并通过 itunes 查询手机的 UUID
&lt;img alt="iphone uuid" src="/static/iphone_uuid.png"&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mac 提供了一个工具 rvictl （rvi=Remote Virtual Interface）可以为连上的 iphone 手机创建一个虚拟网络设备，不管手机用的是移动网络还是 WIFI，均可以通过该设备进行抓包。启动虚拟设备的命令如下：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;rvictl -s ${uuid}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果成功启动，将返回：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Starting device xxxxxxxx [SUCCEEDED] with interface rvi0&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;其中的 rvi0 即为可用于抓包的虚拟设备。&lt;/p&gt;
&lt;p&gt;如果操作失败，并返回 &lt;code&gt;bootstrap_look_up(): 1102&lt;/code&gt; 错误，请通过如下命令启动服务，并重复 &lt;code&gt;rvictl -s ${uuid}&lt;/code&gt; 的操作即可：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.rpmuxd.plist&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;之后就可以通过 tcpdump 命令进行抓包了，如：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;tcpdump -i rvi0 -s 0 -vvv dst host x.x.x.x -w test.pcap&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;之后可以通过 wireshark 打开 test.pcap 进行查看。通常你可以使用 &lt;code&gt;brew cask install wireshark&lt;/code&gt; 安装 wireshark.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;怎么通过 iTunes 往手机上安装 ipa 包？&lt;/h4&gt;
&lt;p&gt;旧版本的 iTunes，在设备信息里有个「应用」入口，并可以在这里安装 ipa 文件。这次顺便发现 iTunes 没有了这个入口，刚拿到 ipa 包时，有点不知道怎么办。原来，只要将将文件拖到 iTunes 对应的设备里，就会自动安装了。（拖到下面截图红色框框部份）&lt;/p&gt;
&lt;p&gt;&lt;img alt="iphone drop" src="/static/iphone_drop.png"&gt;&lt;/p&gt;</content><category term="misc"/><category term="tcpdump"/><category term="wireshark"/><category term="ios"/><category term="mac"/></entry><entry><title>一次「安全」的变更将 redis cluster 送进了孤岛</title><link href="https://www.chenxiaosheng.com/posts/2018-07-16/redis-cluster-bind-bug.html" rel="alternate"/><published>2018-07-16T00:51:00+08:00</published><updated>2018-07-16T00:51:00+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2018-07-16:/posts/2018-07-16/redis-cluster-bind-bug.html</id><summary type="html">&lt;p&gt;一次以为安全的，天衣无缝的升级，却将 redis cluster 集群送进了孤岛。对于这次故障，没有借口，我们不能也不该出现这样的失误！我们将认真复盘改进自动化运维技术和发布验证流程，敬畏每一行代码，敬畏每一份托付。&lt;/p&gt;</summary><content type="html">&lt;p&gt;鉴于 redis 没有有效的鉴权方法（特别是 redis cluster），为了防止人为疏忽导致系统防火墙未被有效开启，我们决定对线上的 redis 增加显示监听内网 IP 配置：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;bind 127.0.0.1 ${本机内网 IP}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在我们看来，这是一个不可能出现问题的变更，因此也没有专门安排测试。事实证明，没有保持敬畏之心的我们还是太天真了！&lt;/p&gt;
&lt;p&gt;线上配置发布后，重启 redis 服务，一切都很顺利，进程成功启动。然后，灾难开始降临，业务开始反馈 redis 缓存无法读写，查看 redis 日志，发现：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Cluster state changed: fail&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;尝试通过 redis-cli 连接进行操作，连接没有问题，但当我们尝试执行 &lt;code&gt;get a&lt;/code&gt; 操作时，集却群反馈错误：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;-CLUSTERDOWN The cluster is down&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;首要事件是保证服务可用，我们第一时间回退配置，重启后 redis 服务恢复正常。&lt;/p&gt;
&lt;h3&gt;问题回放&lt;/h3&gt;
&lt;p&gt;开启 Debug 模式，发现有大量的 createing socket: invalid argument 怀疑是 redis 拿了 bind 中的第一个 IP 为节点 IP，导致集群中所有节点的 IP 为 127.0.0.1，因此集群的通信出现问题（事后 review 代码确认跟第一个 IP 有关系，但不是导致所有节点的 IP 为 127.0.0.1）。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mi"&gt;1616&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Jul&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;25.407&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;changed&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;
&lt;span class="o"&gt;......&lt;/span&gt;
&lt;span class="mi"&gt;13236&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Jul&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;27.104&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;Unable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="o"&gt;]:&lt;/span&gt;&lt;span class="mi"&gt;16379&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;creating&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Invalid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;argument&lt;/span&gt;
&lt;span class="mi"&gt;13236&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Jul&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;27.104&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;Unable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="o"&gt;]:&lt;/span&gt;&lt;span class="mi"&gt;16379&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;creating&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Invalid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;argument&lt;/span&gt;
&lt;span class="o"&gt;......&lt;/span&gt;
&lt;span class="mi"&gt;1616&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Jul&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;38.438&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;changed&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;有了上面的怀疑之后，我们尝试修改 bind 的配置，将 127.0.0.1 放到后面，即：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;bind ${本机内网 IP} 127.0.0.1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;重启集群发现问题果然没有出现，感觉自己好机智（进行源码分析后，发现自己还是想的太简单了）。&lt;/p&gt;
&lt;h3&gt;源码分析&lt;/h3&gt;
&lt;p&gt;redis 版本：3.2.11&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;根据 &lt;code&gt;Cluster state changed: fail&lt;/code&gt; 我们定位到，redis 是在 cluster.c 的 clusterUpdateState 函数里进行集群的状态判断：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当节点状态为 CLUSTER_NODE_FAIL 或 CLUSTER_NODE_PFAIL 时，节点被标记为不可达；&lt;/li&gt;
&lt;li&gt;当不可达的 master 节点大于 quorum 时，集群被标记为不可用；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;```c
    /&lt;em&gt; Compute the cluster size, that is the number of master nodes
     * serving at least a single slot.
     *
     * At the same time count the number of reachable masters having
     * at least one slot. &lt;/em&gt;/
    {
     ...
                if ((node-&amp;gt;flags &amp;amp; (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) == 0)
                    reachable_masters++;
     ...
    }&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;/*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;If&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;we&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;minority&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state&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;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FAIL&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="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;needed_quorum&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;size&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="mi"&gt;2&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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;reachable_masters&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;needed_quorum&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 class="n"&gt;new_state&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;CLUSTER_FAIL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;among_minority_time&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;mstime&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="p"&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;Log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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;new_state&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;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;state&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 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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Change&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;event&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="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;serverLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LL_WARNING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Cluster state changed: %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;new_state&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;CLUSTER_OK&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ok&amp;quot;&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="s"&gt;&amp;quot;fail&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;state&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;new_state&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;```&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;redis cluster 主要是通过 cluster.c 中的 clusterCron 定期判断收集集群的状态，也是在这个函数里我们发现了最主要的探测函数 &lt;code&gt;anetTcpNonBlockBindConnect&lt;/code&gt; 及非常符合异常特征的参数名称 &lt;code&gt;NET_FIRST_BIND_ADDR&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;c
            fd = anetTcpNonBlockBindConnect(server.neterr, node-&amp;gt;ip,
                node-&amp;gt;port+CLUSTER_PORT_INCR, NET_FIRST_BIND_ADDR);
            if (fd == -1) {
                /* We got a synchronous error from connect before
                 * clusterSendPing() had a chance to be called.
                 * If node-&amp;gt;ping_sent is zero, failure detection can't work,
                 * so we claim we actually sent a ping now (that will
                 * be really sent as soon as the link is obtained). */
                if (node-&amp;gt;ping_sent == 0) node-&amp;gt;ping_sent = mstime();
                serverLog(LL_DEBUG, "Unable to connect to "
                    "Cluster Node [%s]:%d -&amp;gt; %s", node-&amp;gt;ip,
                    node-&amp;gt;port+CLUSTER_PORT_INCR,
                    server.neterr);
                continue;
            }&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;anetTcpNonBlockBindConnect&lt;/code&gt; 函数位于 anet.c 文件中，该函数调用了另一个函数 &lt;code&gt;anetTcpGenericConnect&lt;/code&gt;，&lt;code&gt;NET_FIRST_BIND_ADDR&lt;/code&gt; 在这里的实际作用为 source addr，问题原因已经呼之欲出了。我们再次定位 &lt;code&gt;anetTcpGenericConnect&lt;/code&gt; 函数，找到以下内容：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;c
        if (source_addr) {
            int bound = 0;
            /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */
            if ((rv = getaddrinfo(source_addr, NULL, &amp;amp;hints, &amp;amp;bservinfo)) != 0)
            {
                anetSetError(err, "%s", gai_strerror(rv));
                goto error;
            }
            for (b = bservinfo; b != NULL; b = b-&amp;gt;ai_next) {
                if (bind(s,b-&amp;gt;ai_addr,b-&amp;gt;ai_addrlen) != -1) {
                    bound = 1;
                    break;
                }
            }
            freeaddrinfo(bservinfo);
            if (!bound) {
               anetSetError(err, "bind: %s", strerror(errno));
               goto error;
            }
        }&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通过以上内容，我们可以确认 redis cluster 在健康检测，建立 socket 连接时，尝试绑定了 socket 源地址，那么 &lt;code&gt;NET_FIRST_BIND_ADDR&lt;/code&gt; 内容究竟是什么呢？这个我们在 server.h 文件中找到了答案，确实是 bind 配置中的第一个 IP，也就是我们这里的 127.0.0.1&lt;/p&gt;
&lt;p&gt;```c
/&lt;em&gt; Get the first bind addr or NULL &lt;/em&gt;/&lt;/p&gt;
&lt;h1&gt;define NET_FIRST_BIND_ADDR (server.bindaddr_count ? server.bindaddr[0] : NULL)&lt;/h1&gt;
&lt;p&gt;```&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;问题结论&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;redis cluster 在检测节点健康状态时，尝试与其它节点建立连接，但强制使用了 bind 配置中的第一个 IP 为建立 socket 的源地址&lt;/li&gt;
&lt;li&gt;以上案例，当配置了监听 127.0.0.1 时，redis cluster 将尝试使用本地地址 127.0.0.1 去和一个外部节点建立链接，因此失败了；&lt;/li&gt;
&lt;li&gt;基本的解决方法：使用 bind 配置时，确保将可进行通讯的 IP 放在第一个；&lt;/li&gt;
&lt;li&gt;有没有其它问题：如果这是一个新建的 cluster 集群，那么在使用 redis-trib.rb 进行初始化集群时，该配置将导致 redis-trib.rb 工具长期阻塞，无法成功执行；&lt;/li&gt;
&lt;li&gt;所有支持 cluster 的 redis 版本均受影响（包括 unstable 分支）；&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;其它&lt;/h3&gt;
&lt;p&gt;私以为强制使用 bind 第一个 IP 进行通讯的方法略有不妥，如果检测健康状态时一定需要指定源地址的话，兴许做出如下调整会有一定的优势：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当未初始化时（集群节点未明确 IP 地址），建立 socket 连接，不要指定源地址，由操作系统自行分配源地址即可；&lt;/li&gt;
&lt;li&gt;当 redis cluster 初始化时，可以使用初始化时指定的节点 IP 做为源地址；&lt;/li&gt;
&lt;li&gt;redis 在 anet.c 文件里，其实还提供了 &lt;code&gt;anetTcpNonBlockBestEffortBindConnect&lt;/code&gt; 函数，这个函数如果通过绑定源地址建立 socket 链接失败后，会再次尝试由系统分配源地址的方式（不绑定），健康检查函数换成这个也不失为一个好办法；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最（chao）后（xi）：对于这次故障，没有借口，我们不能也不该出现这样的失误！我们将认真复盘改进自动化运维技术和发布验证流程，敬畏每一行代码，敬畏每一份托付。&lt;/p&gt;</content><category term="misc"/><category term="redis"/></entry><entry><title>MongoDB 随机查询获取一条或 N 条记录的方法</title><link href="https://www.chenxiaosheng.com/posts/2018-05-24/mongodb-random-query.html" rel="alternate"/><published>2018-05-24T12:14:13+08:00</published><updated>2018-05-24T12:14:13+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2018-05-24:/posts/2018-05-24/mongodb-random-query.html</id><summary type="html">&lt;p&gt;MySQL 可以通过 &lt;code&gt;rand()&lt;/code&gt; 配合 &lt;code&gt;limit&lt;/code&gt; 获取随机的 N 条记录，那么在 MongoDB 上我们又该如何操作呢？&lt;/p&gt;</summary><content type="html">&lt;p&gt;熟悉 MySQL 的同学应该都知道可以通过类似如下语句简单的获取一张表里随机的 N 条记录：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SELECT * FROM table ORDER BY RAND() LIMIT N;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在 MongoDB 里应该如何操作呢？官方在 3.2 版本带来了直接的解决方案：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;$sample (aggregation)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;具体应该如何操作，官方也给出了一个示例，假设 Collection users 有如下记录：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{ &amp;quot;_id&amp;quot; : 1, &amp;quot;name&amp;quot; : &amp;quot;dave123&amp;quot;, &amp;quot;q1&amp;quot; : true, &amp;quot;q2&amp;quot; : true }
{ &amp;quot;_id&amp;quot; : 2, &amp;quot;name&amp;quot; : &amp;quot;dave2&amp;quot;, &amp;quot;q1&amp;quot; : false, &amp;quot;q2&amp;quot; : false  }
{ &amp;quot;_id&amp;quot; : 3, &amp;quot;name&amp;quot; : &amp;quot;ahn&amp;quot;, &amp;quot;q1&amp;quot; : true, &amp;quot;q2&amp;quot; : true  }
{ &amp;quot;_id&amp;quot; : 4, &amp;quot;name&amp;quot; : &amp;quot;li&amp;quot;, &amp;quot;q1&amp;quot; : true, &amp;quot;q2&amp;quot; : false  }
{ &amp;quot;_id&amp;quot; : 5, &amp;quot;name&amp;quot; : &amp;quot;annT&amp;quot;, &amp;quot;q1&amp;quot; : false, &amp;quot;q2&amp;quot; : true  }
{ &amp;quot;_id&amp;quot; : 6, &amp;quot;name&amp;quot; : &amp;quot;li&amp;quot;, &amp;quot;q1&amp;quot; : true, &amp;quot;q2&amp;quot; : true  }
{ &amp;quot;_id&amp;quot; : 7, &amp;quot;name&amp;quot; : &amp;quot;ty&amp;quot;, &amp;quot;q1&amp;quot; : false, &amp;quot;q2&amp;quot; : true  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;那么可以通过命令 &lt;code&gt;db.users.aggregate( [ { $sample: { size: N } } ] )&lt;/code&gt; 获取随机的 N 条记录，如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;mongos&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregate&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 class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;$sample&lt;/span&gt;&lt;span class="o"&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 class="nl"&gt;size:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;3&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="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 class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_id&amp;quot;&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="mh"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;name&amp;quot;&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="s"&gt;&amp;quot;ty&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;q1&amp;quot;&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;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;q2&amp;quot;&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;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_id&amp;quot;&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="mh"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;name&amp;quot;&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="s"&gt;&amp;quot;li&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;q1&amp;quot;&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;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;q2&amp;quot;&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;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_id&amp;quot;&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="mh"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;name&amp;quot;&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="s"&gt;&amp;quot;dave123&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;q1&amp;quot;&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;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;q2&amp;quot;&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;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;mongos&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregate&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 class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;$sample&lt;/span&gt;&lt;span class="o"&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 class="nl"&gt;size:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;3&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="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 class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_id&amp;quot;&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="mh"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;name&amp;quot;&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="s"&gt;&amp;quot;ty&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;q1&amp;quot;&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;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;q2&amp;quot;&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;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_id&amp;quot;&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="mh"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;name&amp;quot;&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="s"&gt;&amp;quot;ahn&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;q1&amp;quot;&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;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;q2&amp;quot;&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;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_id&amp;quot;&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="mh"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;name&amp;quot;&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="s"&gt;&amp;quot;dave2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;q1&amp;quot;&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;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;q2&amp;quot;&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;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;mongos&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;$sample 会使用如下两种方式来获取随机的 N 条记录，具体使用哪种方式取决于如下条件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$sample 处于聚合管道的第一阶段；&lt;/li&gt;
&lt;li&gt;N 小于总文档数量的 5% ;&lt;/li&gt;
&lt;li&gt;集合（Collection）中的文档数量大于 100；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当以上 3 个条件都满足时，$sample 将通过伪随机的游标来获取记录。当任一条件不满足时，$sample 将进行集合扫描，并通过随机排序来选择相应的 N 条记录。&lt;/p&gt;
&lt;p&gt;需要注意的是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当使用随机排序来获取记录时，排序操作会有 100 MB 的内存限制，具体可以参考 &lt;a href="https://docs.mongodb.com/manual/reference/operator/aggregation/sort/#sort-memory-limit"&gt;$sort and Memory Restrictions&lt;/a&gt;；&lt;/li&gt;
&lt;li&gt;$sample 有可能多次返回同一条记录（document）；&lt;/li&gt;
&lt;li&gt;尝试在一张十亿级的 Collection 上获取 1W 条随机记录，耗时约 2.5 秒，供参考；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;参考文档：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;https://docs.mongodb.com/manual/reference/operator/aggregation/sample/&lt;/li&gt;
&lt;li&gt;https://jira.mongodb.org/browse/SERVER-533&lt;/li&gt;
&lt;/ol&gt;</content><category term="misc"/><category term="mongodb"/></entry><entry><title>《OKR 工作法》读书笔记</title><link href="https://www.chenxiaosheng.com/posts/2018-01-30/okr-reading-notes.html" rel="alternate"/><published>2018-01-30T09:50:00+08:00</published><updated>2018-01-30T09:50:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2018-01-30:/posts/2018-01-30/okr-reading-notes.html</id><summary type="html">&lt;p&gt;OKR 就是要通过不断实践、总结，发不断发现、挑战团队的潜力，不要把这个过程当作汇报、考核的结果。没有完成，一起思考为什么会这样、怎么改进。目标达成，那就设置更有挑战的目标。把精力聚焦在学习总结、挖掘潜力和高效执行上。&lt;/p&gt;</summary><content type="html">&lt;p&gt;目标用来明确方向，关键结果则用来量化目标，使团队和个人聚焦在一个有挑战性的目标上。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;确定目标，确保团队聚焦&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;目标要有明确方向并鼓舞人心&lt;/li&gt;
&lt;li&gt;目标要有时间期限&lt;/li&gt;
&lt;li&gt;目标必须真正属于你&lt;blockquote&gt;
&lt;p&gt;思考，哪些是关键成果，哪些是目标：销售额提升 30%/用户增加一倍/收入增加到 500 万美元/完成一轮融资/拿下 XX 市场/推出一个最小化可行产品&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;讨论关键结果，复盘 OKR&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;评估 OKR 实施成果&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;影响目标达成的关键因素&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;没有给目标设置优先级&lt;/li&gt;
&lt;li&gt;缺乏充分沟通，导致没能准确理解目标&lt;blockquote&gt;
&lt;p&gt;目标完成的进度必须在每周会议和邮件中汇报，分解出的项目任务必须能支撑目标的达成；&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;没有做好计划&lt;blockquote&gt;
&lt;p&gt;承担、庆祝与盘点&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;没有把时间花在重要的事情上&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;重要的事情通常不紧急，紧急的事情通常不重要
思考：计划应该是为了重要不紧急还是紧急不重要？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;轻易放弃&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;害怕失败，不给自己挑战机会，隐藏实力，导致目标设置太低
期望过高，没有能力实现
没有坚持跟进目标，直到最后一周才发现没有进展（注意前面的，需要持续复盘、评估、沟通）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;OKR 就是要通过不断实践、总结，发不断发现、挑战团队的潜力，不要把这个过程当作汇报、考核的结果。没有完成，一起思考为什么会这样、怎么改进。目标达成，那就设置更有挑战的目标。把精力聚焦在学习总结、挖掘潜力和高效执行上。&lt;/p&gt;
&lt;h3&gt;其它建议：&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;如何评估绩效：持续沟通，不地加以指导和校正。管理者和员工各自分享自己的观点与想法，这样可以减少误解，问题也能得到快速解决。年终评估的事实大家可以通过持续的沟通提前知道。&lt;/li&gt;
&lt;li&gt;目标里不要有传统的绩效考核指标。&lt;/li&gt;
&lt;li&gt;OKR 进度确认（如周会）是一次谈话，而不是汇报（A做了什么B做了什么，这叫流水帐）或指示。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.cn/dp/B07577T3XS/ref=sr_1_1?ie=UTF8&amp;amp;qid=1517276944&amp;amp;sr=8-1&amp;amp;keywords=okr+%E5%B7%A5%E4%BD%9C%E6%B3%95"&gt;《OKR 工作法》&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://eleganthack.com/wp-content/uploads/2017/05/OKR_Worksheet.pdf"&gt; OKR 练习册&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content><category term="misc"/><category term="okr"/></entry><entry><title>网易游戏 MySQL-MongoDB 运维及 DBA 招聘</title><link href="https://www.chenxiaosheng.com/posts/2017-11-15/ntes-offers.html" rel="alternate"/><published>2017-11-15T22:51:00+08:00</published><updated>2017-11-15T22:51:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2017-11-15:/posts/2017-11-15/ntes-offers.html</id><summary type="html">&lt;p&gt;以下岗位部门均属于&lt;a href="http://hr.game.163.com/about.html"&gt;网易在线游戏事业部&lt;/a&gt;。
其中 MongoDB 运维及运维开发工程师可选工作地点为广州或杭州。&lt;/p&gt;
&lt;p&gt;相关数据库产品 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;以下岗位部门均属于&lt;a href="http://hr.game.163.com/about.html"&gt;网易在线游戏事业部&lt;/a&gt;。
其中 MongoDB 运维及运维开发工程师可选工作地点为广州或杭州。&lt;/p&gt;
&lt;p&gt;相关数据库产品均服务于如《&lt;a href="http://hy.163.com"&gt;荒野行动&lt;/a&gt;》、《&lt;a href="http://xyq.163.com"&gt;梦幻西游&lt;/a&gt;》、《&lt;a href="http://dhxy.163.com"&gt;大话西游&lt;/a&gt;》、《&lt;a href="http://yys.163.com"&gt;阴阳师&lt;/a&gt;》、《&lt;a href="http://mc.163.com"&gt;我的世界&lt;/a&gt;》、&lt;a href="http://cc.163.com"&gt;CC&lt;/a&gt;等网易游戏海量用户业务。
如果需要也有更深入了解的，也可以给我留言或者&lt;a href="https://www.dropbox.com/s/fcx2kqf77kmx7zv/wechat_qrcode.jpeg?dl=0"&gt;微信（二维码戳我）&lt;/a&gt;私我。同时欢迎推荐及转发。&lt;/p&gt;
&lt;h3&gt;岗位一：MySQL DBA &lt;a href="http://hr.game.163.com/position/introduction.html?code=T0915"&gt;点我投递简历&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;工作职责&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;参与内部MySQL SaaS开发和维护工作；&lt;/li&gt;
&lt;li&gt;优化数据库，提高数据处理效率；&lt;/li&gt;
&lt;li&gt;负责数据库新技术的调研和测试；&lt;/li&gt;
&lt;li&gt;负责处理故障，服务请求，运维环境优化；&lt;/li&gt;
&lt;li&gt;和开发团队及其他部门进行沟通协作。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;职位要求&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;精通MySQL等数据库系统使用，精通SQL语句和优化；&lt;/li&gt;
&lt;li&gt;熟悉Linux系统操作；&lt;/li&gt;
&lt;li&gt;了解网络，有一定的故障排查和处理能力；&lt;/li&gt;
&lt;li&gt;熟悉shell脚本编程，会Python、Java者优先；&lt;/li&gt;
&lt;li&gt;有大型数据库和大规模服务器管理经验优先；&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;岗位二：MongoDB 运维工程师 &lt;a href="http://hr.game.163.com/position/introduction.html?code=T1427"&gt;点我投递简历&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;职位描述：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;负责 MongoDB 等数据库平台的配置管理、流程管理；&lt;/li&gt;
&lt;li&gt;响应用户需求，提供协助或技术解决方案，配合业务完成数据库集群的部署、升级、扩容、缩容等；&lt;/li&gt;
&lt;li&gt;通过全方位、立体化监控系统快速发现和处理故障；&lt;/li&gt;
&lt;li&gt;配合优化数据库服务运维规范、工作流程、应急预案等，确保任何突发情况都能高效响应；&lt;/li&gt;
&lt;li&gt;汇总用户需求，并设计技术需求方案，促进产品发展；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;职位要求：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;熟悉 Linux 系统以及工具命令等使用，熟悉主流开源软件的配置及调优，有底层基础技术研发经历的优先；&lt;/li&gt;
&lt;li&gt;熟悉至少一种程序设计语言，有 Shell、Python 等脚本编程语言经验者优先；&lt;/li&gt;
&lt;li&gt;对计算机硬件设备、网络设备有一定了解，熟悉 TCP/IP 以及具有丰富的网络知识；&lt;/li&gt;
&lt;li&gt;熟悉 MongoDB 数据库的日常使用；&lt;/li&gt;
&lt;li&gt;掌握 MongoDB 的管理及构建，包括但不限于集群构建、性能调优、备份恢复；&lt;/li&gt;
&lt;li&gt;具备良好的沟通表达能力和服务意识，较强的团队合作意识，快速处理突发事件的能力；&lt;/li&gt;
&lt;li&gt;有大型 MongoDB 商用部署实施及运营经验者优先，有 OCP 认证者优先。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;岗位三：运维开发工程师（数据库平台）&lt;a href="http://hr.game.163.com/position/introduction.html?code=T1300"&gt;点我投递简历&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;职位描述：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;负责内部数据库相关产品的管理平台、自动化流程开发工作；&lt;/li&gt;
&lt;li&gt;与产品、PM、运维同学协作，积极高效推动产品开发；&lt;/li&gt;
&lt;li&gt;与用户及其他部门进行沟通协作；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;职位要求：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;1 年或以上前后端开发工作经验；&lt;/li&gt;
&lt;li&gt;对开发有浓厚兴趣，HTML/CSS/JS 基础扎实，熟悉Angulajs、Reactjs、Vue.js等MVVM框架中的一种；&lt;/li&gt;
&lt;li&gt;有至少一种 server 端开发经验，熟悉 Python、PHP 语言开发者优先；&lt;/li&gt;
&lt;li&gt;至少一种熟悉Web开发框架（如Django/Flask/Tornado）；&lt;/li&gt;
&lt;li&gt;对 HTTP 协议、网页性能优化有一定的理解；&lt;/li&gt;
&lt;li&gt;熟悉 MySQL、MongoDB、Redis 等数据库应用开发优先；&lt;/li&gt;
&lt;li&gt;能够编写高扩展、易维护的代码，熟练且规范使用代码管理工具；&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;岗位四：高级运维工程师（DNS方向） &lt;a href="http://hr.game.163.com/position/introduction.html?code=T1249"&gt;点我投递简历&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;职位描述：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;负责 DNS 及配套系统部署、维护及运维环境、服务优化；&lt;/li&gt;
&lt;li&gt;日常系统运维、服务请求、故障处理等技术支持；&lt;/li&gt;
&lt;li&gt;网络、系统等维护配套系统部署、维护；&lt;/li&gt;
&lt;li&gt;和项目开发团队及其他部门进行沟通协作；&lt;/li&gt;
&lt;li&gt;负责新技术的调研和测试。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;职位要求：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;熟悉 Linux 系统操作；&lt;/li&gt;
&lt;li&gt;熟悉 shell 脚本语言编程，会 Python 者优先；&lt;/li&gt;
&lt;li&gt;了解 TCP/IP 网络，有一定的故障排查和处理能力；&lt;/li&gt;
&lt;li&gt;熟悉 DNS 协议、BIND，有相关运维经验，并能够快速实施部署、配置和排障；&lt;/li&gt;
&lt;/ol&gt;</content><category term="misc"/><category term="netease"/><category term="dba"/><category term="mysql"/><category term="mongodb"/></entry><entry><title>LXC 如何找出容器对应的 veth 设备</title><link href="https://www.chenxiaosheng.com/posts/2017-03-29/lxc-veth-pairs.html" rel="alternate"/><published>2017-03-29T11:42:00+08:00</published><updated>2017-03-29T11:42:00+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2017-03-29:/posts/2017-03-29/lxc-veth-pairs.html</id><summary type="html">&lt;p&gt;LXC 使用 veth 模式时，如果宿主机上创建了很多容器，那么宿主上将存在大量的 vethXXXX 设备，肉眼难以直接确认每个虚拟容器使用的是哪个 veth 设备。&lt;/p&gt;</summary><content type="html">&lt;p&gt;LXC 使用 veth 模式时，如果宿主机上创建了很多容器，那么宿主上将存在大量的 vethXXXX 设备，肉眼难以直接确认每个虚拟容器使用的是哪个 veth 设备。&lt;/p&gt;
&lt;p&gt;在我的记忆中，方法比较复杂，且之前忘了记录下来，已经遗忘。今天重新尝试使用另一方法获取匹配信息，这里做下记录：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;虚拟机（容器）执行 &lt;code&gt;ethtool -S eth0&lt;/code&gt; 拿到如 &lt;code&gt;peer_ifindex: 16&lt;/code&gt;，其中 &lt;code&gt;eth0&lt;/code&gt; 为在虚拟机（容器）内对应的设备名称；&lt;/li&gt;
&lt;li&gt;宿主机执行：&lt;code&gt;ip link show | grep '^16:'&lt;/code&gt;，其中 &lt;code&gt;16&lt;/code&gt; 为第一步拿到的 &lt;code&gt;peer_ifindex&lt;/code&gt;，通过该命令拿到的 vethXXXX 即为虚拟机（容器）使用的设备。&lt;/li&gt;
&lt;/ol&gt;</content><category term="misc"/><category term="lxc"/></entry><entry><title>记一次 Apache 性能调优</title><link href="https://www.chenxiaosheng.com/posts/2017-03-23/apache-performance-tuning.html" rel="alternate"/><published>2017-03-23T09:44:00+08:00</published><updated>2017-03-23T09:44:00+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2017-03-23:/posts/2017-03-23/apache-performance-tuning.html</id><summary type="html">&lt;p&gt;每一次神优化背后都有一个很烧饼的设置（bug），刷一次负载下降 70%+ 的小目标。&lt;/br&gt;&lt;img src="/static/apache_load.png"&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;手上有一组 web 服务，使用 apache 对外提供服务，其中apache 使用 worker 模式，这堆 web 服务器有以下一堆毛病：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;apache 长期占用大量 CPU，负载长期居高不下。&lt;/li&gt;
&lt;li&gt;每天 rotate log 的指令不能被 apache 处理，通过 lsof 可以看到被删除的日志文件没有正确关闭。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通过监控、日志统计，发现 qps 并不高，单台机器 qps 为600 左右（机器为 16 核、32G 内存），一开始以为是我代码性能问题，各种查找高消耗节点，无果。再次对服务器的整体情况及 apache 文档进行审视。&lt;/p&gt;
&lt;p&gt;通过阅读 &lt;a href="https://httpd.apache.org/docs/2.2/mod/mpm_common.html"&gt;apache 官方文档&lt;/a&gt; 在 worker 这里最重要的三个设置参数有 &lt;code&gt;ServerLimit * ThreadsPerChild &amp;gt;= MaxClients&lt;/code&gt; ，但我们服务器上的配置远大于 qps（理论上，实际并发数会小于 qps）。尝试调小以上参数以接近 qps 数值，服务器负载没有发生明显变化。&lt;/p&gt;
&lt;p&gt;再次观察系统负载（top）发现，apache 进程的 pid 一直在产生变化，产生了新的怀疑，是不是因为「频繁的销毁、创建 apache 进程」导致的开销过大，造成 cpu 负载居高不下？&lt;/p&gt;
&lt;p&gt;再次阅读文档，其中两个会导致进程销毁的设置分别为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;MaxRequestsPerChild&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;MaxRequestsPerChild&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;directive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;limit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;number&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;requests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;individual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;child&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;handle&lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;After&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;MaxRequestsPerChild&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;requests&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;child&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;die&lt;/span&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="nv"&gt;MaxRequestsPerChild&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;never&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;expire&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这个值，我们的设置是 0，所以不会是因为这个设置导致的。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;MaxSpareThreads&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;worker&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;MaxSpareThreads&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;These&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;MPMs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;deal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;idle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;threads&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;server&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;wide&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;basis&lt;/span&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="nv"&gt;there&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;too&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;many&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;idle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;threads&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;child&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;processes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;killed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;until&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;number&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;idle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;threads&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;less&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;than&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;number&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;我们原来的值设置为 75，怀疑是因为这个值设得太小了，尝试将此值调整至与 &lt;code&gt;MaxClients&lt;/code&gt; 相等，重启 apache ，发现负载开始持续下降，&lt;code&gt;top 中 apache 进程 pid 几乎没有产生变化&lt;/code&gt;，服务正常，qps没有明显下降（未导致服务不可用）。新的配置如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;IfModule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;mpm_worker_module&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;StartServers&lt;span class="w"&gt;          &lt;/span&gt;2
&lt;span class="w"&gt;    &lt;/span&gt;ServerLimit&lt;span class="w"&gt;          &lt;/span&gt;12
&lt;span class="w"&gt;    &lt;/span&gt;MinSpareThreads&lt;span class="w"&gt;      &lt;/span&gt;25
&lt;span class="w"&gt;    &lt;/span&gt;MaxSpareThreads&lt;span class="w"&gt;     &lt;/span&gt;768
&lt;span class="w"&gt;    &lt;/span&gt;ThreadLimit&lt;span class="w"&gt;         &lt;/span&gt;128
&lt;span class="w"&gt;    &lt;/span&gt;ThreadsPerChild&lt;span class="w"&gt;      &lt;/span&gt;64
&lt;span class="w"&gt;    &lt;/span&gt;MaxClients&lt;span class="w"&gt;          &lt;/span&gt;768
&lt;span class="w"&gt;    &lt;/span&gt;MaxRequestsPerChild&lt;span class="w"&gt;   &lt;/span&gt;0
&lt;span class="nt"&gt;&amp;lt;/IfModule&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;至此问题解决，附优化前后的负载对比图：&lt;/p&gt;
&lt;p&gt;优化前：&lt;/p&gt;
&lt;p&gt;&lt;img src="/static/apache_before_tuning.png"&gt;&lt;/p&gt;
&lt;p&gt;优化后：&lt;/p&gt;
&lt;p&gt;&lt;img src="/static/apache_after_tuning.png"&gt;&lt;/p&gt;</content><category term="misc"/><category term="apache"/></entry><entry><title>pip 升级 pip 失败</title><link href="https://www.chenxiaosheng.com/posts/2016-05-19/pip_upgrade_pip_failure.html" rel="alternate"/><published>2016-05-19T12:11:00+08:00</published><updated>2016-05-19T12:11:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2016-05-19:/posts/2016-05-19/pip_upgrade_pip_failure.html</id><summary type="html">&lt;p&gt;今天在使用 python pip 安装一个 python 包的时候，一直提示：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;upgrade&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;
&lt;span class="nx"&gt;Requirement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;already&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;python2&lt;/span&gt;&lt;span class="m m-Double"&gt;.7&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;dist&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;packages&lt;/span&gt;
&lt;span class="nx"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;8.1.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;however&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;8.1 …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;今天在使用 python pip 安装一个 python 包的时候，一直提示：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;upgrade&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;
&lt;span class="nx"&gt;Requirement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;already&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;python2&lt;/span&gt;&lt;span class="m m-Double"&gt;.7&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;dist&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;packages&lt;/span&gt;
&lt;span class="nx"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;8.1.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;however&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;8.1.2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="nx"&gt;You&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;consider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;upgrading&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;via&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;upgrade&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;尝试按说明执行 &lt;code&gt;pip install --upgrade pip&lt;/code&gt; ，没有任何报错，但一直升级不成功，百思不得解，
在查看 &lt;code&gt;man pip&lt;/code&gt; 的时候，找到如下选项：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;-v, --verbose
    Give more output. Option is additive, and can be used up to 3 times.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;尝试执行 &lt;code&gt;pip install --upgrade pip -vvv&lt;/code&gt; 终于有更多提示：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&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="nx"&gt;versions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//example.url/simple/pip/&lt;/span&gt;
&lt;span class="nx"&gt;Getting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//example.url/simple/pip/&lt;/span&gt;
&lt;span class="nx"&gt;Starting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HTTP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;
&lt;span class="s"&gt;&amp;quot;GET /simple/pip/ HTTP/1.1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;247&lt;/span&gt;
&lt;span class="nx"&gt;Could&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//example.url/simple/pip/: 403 Client Error: Forbidden for url: http://example.url/simple/pip/ - skipping&lt;/span&gt;
&lt;span class="nx"&gt;Installed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m m-Double"&gt;8.1.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;most&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;past&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;none&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;Requirement&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;already&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;python2&lt;/span&gt;&lt;span class="m m-Double"&gt;.7&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;dist&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m m-Double"&gt;8.1.2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;py2&lt;/span&gt;&lt;span class="m m-Double"&gt;.7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;egg&lt;/span&gt;
&lt;span class="nx"&gt;Cleaning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这时才发现，原来在我的 &lt;code&gt;~/.pip/pip.conf&lt;/code&gt; 配置文件里，&lt;code&gt;index-url&lt;/code&gt; 使用了一个已经废弃的地址，删除文件后重新升级，一切恢复正常。&lt;/p&gt;
&lt;p&gt;这么重要的 debug 信息，pip 默认竟然不输出，继续百思不得其解。:-)&lt;/p&gt;</content><category term="misc"/><category term="python"/><category term="pip"/></entry><entry><title>Mac OSX Yosemite 10.10 WIFI 掉线修复</title><link href="https://www.chenxiaosheng.com/posts/2014-11-17/mac_osx_yosemite_fix_wifi_problems.html" rel="alternate"/><published>2014-11-17T14:35:00+08:00</published><updated>2014-11-17T14:35:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2014-11-17:/posts/2014-11-17/mac_osx_yosemite_fix_wifi_problems.html</id><summary type="html">&lt;p&gt;原先用的是 Mac Air 11，通过Time Machine迁移至 Mac Retina 13，最后升级成
Yosemite 10.10 的，系统已经用了一段时间的，最近才忽然出出WIFI掉线的问题。&lt;/p&gt;
&lt;p&gt;而且此掉线比较 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;原先用的是 Mac Air 11，通过Time Machine迁移至 Mac Retina 13，最后升级成
Yosemite 10.10 的，系统已经用了一段时间的，最近才忽然出出WIFI掉线的问题。&lt;/p&gt;
&lt;p&gt;而且此掉线比较奇怪，WIFI Connection正常，TCP/UDP/ICMP包均不正常，
并且我试过在ping/safari/ssh等均异常的情况下，我某正在下载内容的应用（genymotion），
却坚持到了最后，并成功下载完了近200M的内容，所以基本排除了无线路由的问题。&lt;/p&gt;
&lt;p&gt;重启电脑无效，网上搜到这么一编文章：&lt;a href="http://www.oschina.net/news/56361/os-x-yosemite-wifi-fault"&gt;别忙升级，苹果 Yosemite 频现 WiFi 断线综合症&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;虽然前面两种方法无效，但第三种方法还是给了我启示的，凭直接猜测应该是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/Library/Preferences/SystemConfiguration
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这个目录，进行这个目录，果然看到一堆与wifi关键字的文件：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;ls
NetworkInterfaces.plist&lt;span class="w"&gt;             &lt;/span&gt;com.apple.smb.server.plist
com.apple.airport.preferences.plist&lt;span class="w"&gt; &lt;/span&gt;com.apple.wifi.message-tracer.plist
com.apple.captive.probe.plist&lt;span class="w"&gt;       &lt;/span&gt;preferences.plist
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;把这些文件删掉（我是先备份了一下的，建议你也这样做），重启Mac。
注意本次重启，是不会自动连上之前使用的wifi的，需要自己去点击连接一下，然后问题就解决了。&lt;/p&gt;
&lt;p&gt;后来在网上看到 &lt;a href="http://osxdaily.com/2014/10/25/fix-wi-fi-problems-os-x-yosemite/"&gt;Fix Wi-Fi Problems in OS X Yosemite&lt;/a&gt;
这编文章，其实只要删除：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;com.apple.airport.preferences.plist
com.apple.network.identification.plist
com.apple.wifi.message-tracer.plist 
NetworkInterfaces.plist 
preferences.plist
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这些文件就可以了，解释也很到位，比oschina那个靠谱多了。:-)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;2014&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;11&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;昨天折腾完&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;今天收到Apple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Store的更新提醒&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="mf"&gt;10.10.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fix了wifi这个问题了&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="misc"/><category term="mac"/><category term="osx"/><category term="yosemite"/><category term="wifi"/></entry><entry><title>查询 dns server 使用的bind版本</title><link href="https://www.chenxiaosheng.com/posts/2014-07-07/query-remote-bind-version.html" rel="alternate"/><published>2014-07-07T20:21:00+08:00</published><updated>2014-07-07T20:21:00+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2014-07-07:/posts/2014-07-07/query-remote-bind-version.html</id><summary type="html">&lt;p&gt;其实命令很简单，但我发现我老记不住，所以做个记录吧（我总是把version.bind记成bind.version，orz）&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;dig&lt;span class="w"&gt; &lt;/span&gt;@&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;TXT&lt;span class="w"&gt; &lt;/span&gt;CHAOS&lt;span class="w"&gt; &lt;/span&gt;version.bind
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;${server}换成你想查询的dns服务器IP地址即可，如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;dig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;@8.8 …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;其实命令很简单，但我发现我老记不住，所以做个记录吧（我总是把version.bind记成bind.version，orz）&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;dig&lt;span class="w"&gt; &lt;/span&gt;@&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;TXT&lt;span class="w"&gt; &lt;/span&gt;CHAOS&lt;span class="w"&gt; &lt;/span&gt;version.bind
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;${server}换成你想查询的dns服务器IP地址即可，如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;dig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;@8.8.8.8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CHAOS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TXT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;其实通常情况下出于安全考虑，大多数我们是查询不到版本信息的（系统管理员禁用了）&lt;/p&gt;
&lt;p&gt;bind 可以通过如下设置禁用版本号查询：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;options {
    version none;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;或者你可以将此值设置为某个固定字符串，如 "someone like you"&lt;/p&gt;</content><category term="misc"/><category term="bind"/><category term="dns"/><category term="dig"/><category term="named"/></entry><entry><title>crontab 同时显式指定day of month和day of week的特殊说明</title><link href="https://www.chenxiaosheng.com/posts/2014-05-09/cron-day-weekday-restricted.html" rel="alternate"/><published>2014-05-09T13:31:00+08:00</published><updated>2014-05-09T13:31:00+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2014-05-09:/posts/2014-05-09/cron-day-weekday-restricted.html</id><summary type="html">&lt;p&gt;man 5 crontab:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;           field         allowed values
           -----         --------------
           minute        0-59
           hour          0-23
           day of month  1-31
           month         1-12 (or names, see below)
           day of week   0-7 (0 or 7 is Sun, or use names)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: The day of a command's execution can be specified by two fields -- day of month, and day …&lt;/p&gt;&lt;/blockquote&gt;</summary><content type="html">&lt;p&gt;man 5 crontab:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;           field         allowed values
           -----         --------------
           minute        0-59
           hour          0-23
           day of month  1-31
           month         1-12 (or names, see below)
           day of week   0-7 (0 or 7 is Sun, or use names)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: The day of a command's execution can be specified by two fields -- day of month, and day of week.
If both fields are restricted (ie, are not *), the command will be
run when either field matches the current time.
For example, ''30 4 1,15 * 5'' would cause a command to be run
at 4:30 am on the 1st and 15th of each month, plus every Fri-day.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;来自wikipedia的说明（&lt;a href="http://en.wikipedia.org/wiki/Crontab"&gt;http://en.wikipedia.org/wiki/Crontab&lt;/a&gt;）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;While normally the job is executed when the time/date specification fields all match the current time and date,
there is one exception: if both &amp;quot;day of month&amp;quot; and &amp;quot;day of week&amp;quot; are restricted (not &amp;quot;*&amp;quot;),
then either the &amp;quot;day of month&amp;quot; field (3) or the &amp;quot;day of week&amp;quot; field (5) must match the current day.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;以设置crontab： 11 14 27 3 3 为例，下面两个类型的时间都能满足该表达式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;3月里的每一个周三&lt;/li&gt;
&lt;li&gt;3月27日&lt;/li&gt;
&lt;/ol&gt;</content><category term="misc"/><category term="crontab"/></entry><entry><title>Linux系统上通知网关更新arp</title><link href="https://www.chenxiaosheng.com/posts/2014-03-19/linux-arp-flush.html" rel="alternate"/><published>2014-03-19T16:02:00+08:00</published><updated>2014-03-19T16:02:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2014-03-19:/posts/2014-03-19/linux-arp-flush.html</id><summary type="html">&lt;p&gt;经常会有在线更换Linux服务器IP的操作，该操作带来的一个问题是: 我们已经执行了修改IP的操作，但由于网络上（网关）的ARP缓存暂未更新，导致在某一段时间内，该 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;经常会有在线更换Linux服务器IP的操作，该操作带来的一个问题是: 我们已经执行了修改IP的操作，但由于网络上（网关）的ARP缓存暂未更新，导致在某一段时间内，该服务器会有网络不通的情况存在。&lt;/p&gt;
&lt;p&gt;因此，我们需要在变更IP的同时，通知网关刷新ARP缓存。&lt;/p&gt;
&lt;p&gt;首先清除本地ARP缓存:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/bin/ip neigh flush dev eth0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;其次向网关发送本机的ip/mac地址&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/usr/sbin/arping -v -c 2 -S 1.1.1.144 -s 00:17:a4:8d:0e:98 -p 1.1.1.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;1.1.1.144 为本机IP&lt;/p&gt;
&lt;p&gt;00:17:a4:8d:0e:98 为本机MAC地址&lt;/p&gt;
&lt;p&gt;1.1.1.1 为网关&lt;/p&gt;</content><category term="misc"/><category term="linux"/><category term="arp"/></entry><entry><title>Java 发起Http Post请求</title><link href="https://www.chenxiaosheng.com/posts/2014-02-19/java-ssl-and-http_post.html" rel="alternate"/><published>2014-02-19T17:16:00+08:00</published><updated>2014-02-19T17:16:00+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2014-02-19:/posts/2014-02-19/java-ssl-and-http_post.html</id><summary type="html">&lt;p&gt;对于一个 Java 小白，每完成一个新的功能，都表示相当不易，就连 Apache HttpClient 偶都表示没搞明白，
看起来好像不同版本还有不同的方 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;对于一个 Java 小白，每完成一个新的功能，都表示相当不易，就连 Apache HttpClient 偶都表示没搞明白，
看起来好像不同版本还有不同的方法，没办法，还是借助于 Google，拼凑出了这一段代码，记录以备自己后用。&lt;/p&gt;
&lt;p&gt;功能：使用 http post 方式访问某使用了 CNNIC 证书的站点（cnnic，唉，好多不信任）&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt; * Java POST Example&lt;/span&gt;
&lt;span class="cm"&gt; */&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.io.IOException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.net.MalformedURLException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.net.ProtocolException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.net.URL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.net.URLEncoder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.io.DataOutputStream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.io.BufferedReader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.io.InputStreamReader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.util.logging.Level&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.util.logging.Logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.io.File&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.io.FileInputStream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.io.InputStream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.security.KeyStore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.security.cert.CertificateException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.security.cert.X509Certificate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;javax.net.ssl.HttpsURLConnection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;javax.net.ssl.TrustManager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;javax.net.ssl.TrustManagerFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;javax.net.ssl.X509TrustManager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;javax.net.ssl.SSLContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;javax.net.ssl.SSLSocketFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**&lt;/span&gt;
&lt;span class="cm"&gt; *&lt;/span&gt;
&lt;span class="cm"&gt; * @author Greg （原链接：http://sigterm.sh/2009/10/simple-post-in-java/）&lt;/span&gt;
&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SimpleHttpPost&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="cm"&gt;/**&lt;/span&gt;
&lt;span class="cm"&gt;     * Pretend you&amp;#39;re a script...&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&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="kd"&gt;static&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;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;throws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Exception&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;URL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;url&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;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/* 加载CNNIC证书开始 */&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;X509TrustManager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sunJSSEX509TrustManager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// 加载 Keytool 生成的证书文件&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;passphrase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&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;p&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="s"&gt;&amp;quot;changeit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;passphrase&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;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toCharArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&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="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;java.cnnic.cacert&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&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;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="s"&gt;&amp;quot;Loading KeyStore &amp;quot;&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;file&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="s"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;InputStream&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&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="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FileInputStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;KeyStore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ks&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;KeyStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KeyStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDefaultType&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;ks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;passphrase&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// 构造 javax.net.ssl.TrustManager 对象&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;TrustManagerFactory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tmf&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;TrustManagerFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;SunX509&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;SunJSSE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;tmf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;TrustManager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tms&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tmf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTrustManagers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// 使用构造好的 TrustManager 访问相应的 https 站点&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;SSLContext&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sslContext&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;SSLContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;SSL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;SunJSSE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;sslContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tms&lt;/span&gt;&lt;span class="p"&gt;,&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;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;security&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SecureRandom&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;SSLSocketFactory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ssf&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;sslContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSocketFactory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/* 加载CNNIC证书结束 */&lt;/span&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 class="n"&gt;url&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="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://demo.site.url/uri/&amp;quot;&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 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;MalformedURLException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&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 class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SimpleHttpPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SEVERE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&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 class="n"&gt;HttpsURLConnection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;urlConn&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;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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 class="c1"&gt;// URL connection channel.&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;urlConn&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpsURLConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;openConnection&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 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;IOException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&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 class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SimpleHttpPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SEVERE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&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 class="c1"&gt;// Let the conn use SSL&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;urlConn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setSSLSocketFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ssf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Let the run-time system (RTS) know that we want input.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;urlConn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDoInput&lt;/span&gt;&lt;span class="w"&gt; &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="c1"&gt;// Let the RTS know that we want to do output.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;urlConn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDoOutput&lt;/span&gt;&lt;span class="w"&gt; &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="c1"&gt;// No caching, we want the real thing.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;urlConn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setUseCaches&lt;/span&gt;&lt;span class="w"&gt; &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="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 class="n"&gt;urlConn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRequestMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;POST&amp;quot;&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 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;ProtocolException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&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 class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SimpleHttpPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SEVERE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&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 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 class="n"&gt;urlConn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;connect&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 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;IOException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&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 class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SimpleHttpPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SEVERE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&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 class="n"&gt;DataOutputStream&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;output&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;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;BufferedReader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;input&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;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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 class="n"&gt;output&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="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DataOutputStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urlConn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getOutputStream&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 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;IOException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&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 class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SimpleHttpPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SEVERE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&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 class="c1"&gt;// Specify the content type if needed.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;//urlConn.setRequestProperty(&amp;quot;Content-Type&amp;quot;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;//  &amp;quot;application/x-www-form-urlencoded&amp;quot;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Construct the POST data.&lt;/span&gt;
&lt;span class="w"&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;content&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="s"&gt;&amp;quot;username=&amp;quot;&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="s"&gt;&amp;quot;randomuser&amp;quot;&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="s"&gt;&amp;quot;&amp;amp;password=&amp;quot;&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="s"&gt;&amp;quot;wrongmd5string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Send the request data.&lt;/span&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 class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&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 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;IOException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&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 class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SimpleHttpPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SEVERE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&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 class="c1"&gt;// Get response data.&lt;/span&gt;
&lt;span class="w"&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;str&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;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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 class="n"&gt;input&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="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BufferedReader&lt;/span&gt;&lt;span class="p"&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;InputStreamReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urlConn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInputStream&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&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="kc"&gt;null&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="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;str&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;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readLine&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 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;str&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 class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&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="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;IOException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&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 class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SimpleHttpPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SEVERE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ex&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 class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="misc"/><category term="java"/><category term="ssl"/><category term="http"/><category term="post"/></entry><entry><title>Java 使用自签证书访问https站点</title><link href="https://www.chenxiaosheng.com/posts/2013-12-26/java-use-self_signed_certificate.html" rel="alternate"/><published>2013-12-26T10:18:00+08:00</published><updated>2013-12-26T10:18:00+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2013-12-26:/posts/2013-12-26/java-use-self_signed_certificate.html</id><summary type="html">&lt;p&gt;最近被 Java 使用自签证书访问 https 的问题折腾得体无完肤，做为一名 Java 小白，
我也忍不住想感慨一下，不同 Java 程序员的水平差 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;最近被 Java 使用自签证书访问 https 的问题折腾得体无完肤，做为一名 Java 小白，
我也忍不住想感慨一下，不同 Java 程序员的水平差别真的好大，有些根本无法沟通，
都不知道他们怎么胜任日常的编码工作的=,=
同时做为一个 Java 黑，我觉得，好吧，更黑了。。&lt;/p&gt;
&lt;p&gt;anyway，经过各种折腾，至少总结出了以下两种使用自签证书访问 https 站点的办法。&lt;/p&gt;
&lt;h3&gt;方法一&lt;/h3&gt;
&lt;p&gt;拿到相应的 https 站点证书后（好吧，我这里是cnnic.crt，顺便吐槽一下万恶的 cnnic 证书，各种不信任）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;keytool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;alias&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="n"&gt;keystore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;JAVA_HOME&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;jre&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;security&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;cacerts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;certificate&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;其中&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;请替换为你想使用的名称，比如我这里是&lt;span class="w"&gt; &lt;/span&gt;CNNIC
&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;JAVA_HOME&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;请替换为你自己的&lt;span class="w"&gt; &lt;/span&gt;JAVA_HOME&lt;span class="w"&gt; &lt;/span&gt;目录，比如我这里是&lt;span class="w"&gt; &lt;/span&gt;/usr/lib/jvm/java-6-openjdk
&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;certificate&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;请替换为你的ca证书路径，比如我这里就是当前目录的&lt;span class="w"&gt; &lt;/span&gt;cnnic.crt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;那么，我需要完整执行的命令就是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;keytool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;CNNIC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;keystore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;jvm&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;openjdk&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;jre&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;security&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;cacerts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cnnic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;如果 keytool 要求你输入密码，在你没有变更过的情况下，该值默认为英文 &lt;code&gt;changeit&lt;/code&gt; 。&lt;/p&gt;
&lt;h3&gt;方法二&lt;/h3&gt;
&lt;p&gt;有些同学表示，他们的服务器不允许导入证书，或者他们是一个很大的集群，不适合每台都导入，那怎么办呢？
方法就是在每次访问的时候，程序自己加载相应的证书，但是在加载证书之前，你还是需要使用 keytool 工具将原ca转换成
Java 可以读取的证书格式，命令同方法一，只是 keystore 的位置你可以放在你的程序可以读取的任一路径，如我这里：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;keytool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;CNNIC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;keystore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cnnic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cacert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cnnic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;之后在你的 Java 程序里，使用 keystore 加载该证书，并附加该证书创建 https 请求即可。&lt;/p&gt;
&lt;p&gt;从 Java 官方 docs 和 网上拼凑了小样例一枚，请君参照并自行完善：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.io.File&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.io.FileInputStream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.io.InputStream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.io.InputStreamReader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.security.KeyStore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.security.cert.CertificateException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.security.cert.X509Certificate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;java.net.URL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;javax.net.ssl.HttpsURLConnection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;javax.net.ssl.TrustManager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;javax.net.ssl.TrustManagerFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;javax.net.ssl.X509TrustManager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;javax.net.ssl.SSLContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;javax.net.ssl.SSLSocketFactory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoadCert&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="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&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;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;throws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Exception&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;X509TrustManager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sunJSSEX509TrustManager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// 加载 Keytool 生成的证书文件&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;passphrase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&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;p&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="s"&gt;&amp;quot;changeit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;passphrase&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;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toCharArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&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="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;java.cnnic.cacert&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&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;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="s"&gt;&amp;quot;Loading KeyStore &amp;quot;&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;file&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="s"&gt;&amp;quot;...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;InputStream&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&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="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FileInputStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;KeyStore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ks&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;KeyStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KeyStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDefaultType&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;ks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;passphrase&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// 构造 javax.net.ssl.TrustManager 对象&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;TrustManagerFactory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tmf&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;TrustManagerFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;SunX509&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;SunJSSE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;tmf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;TrustManager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tms&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tmf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTrustManagers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// 使用构造好的 TrustManager 访问相应的 https 站点&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;SSLContext&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sslContext&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;SSLContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;SSL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;SunJSSE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;sslContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tms&lt;/span&gt;&lt;span class="p"&gt;,&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;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;security&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SecureRandom&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;SSLSocketFactory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ssf&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;sslContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSocketFactory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;myURL&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="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://replace.to.your.site.real.url/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;HttpsURLConnection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;httpsConn&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpsURLConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;myURL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;openConnection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;httpsConn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setSSLSocketFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ssf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;InputStreamReader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;insr&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="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;InputStreamReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpsConn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInputStream&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;respInt&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;insr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&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="n"&gt;respInt&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="mi"&gt;1&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 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;print&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;respInt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;respInt&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;insr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&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 class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="misc"/><category term="java"/><category term="ssl"/></entry><entry><title>OpenID4Java 使用dumb模式（stateless）并禁用 discovery</title><link href="https://www.chenxiaosheng.com/posts/2013-12-25/openid4java-dumb_mode-and-disable_discovery.html" rel="alternate"/><published>2013-12-25T12:20:00+08:00</published><updated>2013-12-25T12:20:00+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2013-12-25:/posts/2013-12-25/openid4java-dumb_mode-and-disable_discovery.html</id><summary type="html">&lt;p&gt;使用无状态模式（dumb mode/stateless）发起 OpenID 认证请求：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;manager&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="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConsumerManager&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRealmVerifier&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;setEnforceRpId&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="c1"&gt;// 强制manager使用无状态模式&lt;/span&gt;
&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setMaxAssocAttempts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;禁用 discovery&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// 不要使用manager.discover构造discoveries&lt;/span&gt;
&lt;span class="c1"&gt;// List discoveries = manager.discover(userSuppliedString);&lt;/span&gt;
&lt;span class="c1"&gt;// 使用人肉构造 …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;使用无状态模式（dumb mode/stateless）发起 OpenID 认证请求：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;manager&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="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConsumerManager&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRealmVerifier&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;setEnforceRpId&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="c1"&gt;// 强制manager使用无状态模式&lt;/span&gt;
&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setMaxAssocAttempts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;禁用 discovery&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// 不要使用manager.discover构造discoveries&lt;/span&gt;
&lt;span class="c1"&gt;// List discoveries = manager.discover(userSuppliedString);&lt;/span&gt;
&lt;span class="c1"&gt;// 使用人肉构造discoveries&lt;/span&gt;
&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;discoveries&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="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ArrayList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;discoveries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&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;DiscoveryInformation&lt;/span&gt;&lt;span class="p"&gt;(&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http//real.openid.server.url&amp;quot;&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 class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="misc"/><category term="openid"/><category term="java"/></entry><entry><title>ssh client 通过 socks5 proxy 登录远程服务器</title><link href="https://www.chenxiaosheng.com/posts/2013-12-20/ssh-through-socks-proxy.html" rel="alternate"/><published>2013-12-20T12:20:00+08:00</published><updated>2013-12-20T12:20:00+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2013-12-20:/posts/2013-12-20/ssh-through-socks-proxy.html</id><summary type="html">&lt;p&gt;今天某同学需要登录某国家服务器（A），但从我朝过去网络延时非常大
发现从岛国过去的速度相当快，但因为岛国的服务 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;今天某同学需要登录某国家服务器（A），但从我朝过去网络延时非常大
发现从岛国过去的速度相当快，但因为岛国的服务器（B）不适合加该同学的帐号
因此做了一个 socks5 proxy ，然后本地 ssh client 通过该 proxy 登录A服务器&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;ProxyCommand=&amp;#39;nc&lt;span class="w"&gt; &lt;/span&gt;-x&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;proxy_server&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;:&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;proxy_server_port&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;%h&lt;span class="w"&gt; &lt;/span&gt;%p&amp;#39;&lt;span class="w"&gt; &lt;/span&gt;xxx.xxx.xxx.xxx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ssh -o ProxyCommand=&amp;#39;nc -x 127.0.0.1:7070 %h %p&amp;#39; 8.8.8.8
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;需要注意的是，nc需要使用 OpenBSD 版本，非 Linux 默认版本（该版本不支持）
通常，类 Debian 的衍生版本，都可以通过如下命令直接安装：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;apt-get install netcat-openbsd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;非OpenBSD 可能报如下错误：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/bin/nc: invalid option -- &amp;#39;x&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="misc"/><category term="ssh"/><category term="proxy"/></entry><entry><title>Mac OSX iTerm2 终端UTF-8和GBK编码自由切换</title><link href="https://www.chenxiaosheng.com/posts/2013-10-29/mac_osx_iterm2_utf8_gbk_switch.html" rel="alternate"/><published>2013-10-29T16:43:00+08:00</published><updated>2013-10-29T16:43:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2013-10-29:/posts/2013-10-29/mac_osx_iterm2_utf8_gbk_switch.html</id><summary type="html">&lt;p&gt;老树使用的是Mac OSX系统，平时终端都是使用iTerm2替代默认的Terminal进行使用。&lt;/p&gt;
&lt;p&gt;考虑到各种兼容性，个人一直使用的是UTF-8编码，但由于老树管理着大量服务器，并且可 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;老树使用的是Mac OSX系统，平时终端都是使用iTerm2替代默认的Terminal进行使用。&lt;/p&gt;
&lt;p&gt;考虑到各种兼容性，个人一直使用的是UTF-8编码，但由于老树管理着大量服务器，并且可能使用的是GBK或者其它编码，经常由于终端环境编码的不同，导致登录服务器出现乱码，或者需要处理GBK文件时，要使用iconv进行多次编码转换，相当麻烦。&lt;/p&gt;
&lt;p&gt;好在iTerm2使用了比较友好的Profile配置及切换方式，首先我的默认配置（Default Profile）使用了UTF-8编码：&lt;/p&gt;
&lt;p&gt;&lt;img src="/static/iterm2_default_profile.png" /&gt;&lt;/p&gt;
&lt;p&gt;我另外建立了一个Profile，叫GBK：&lt;/p&gt;
&lt;p&gt;&lt;img src="/static/iterm2_gbk_profile.png" /&gt;&lt;/p&gt;
&lt;p&gt;并编写了一个非常简单的切换脚本：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c1"&gt;# 使用GBK Profile&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\033]50;SetProfile=GBK\a&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# 环境编码切换为GBK&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;LANG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;zh_CN.GBK
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;zh_CN.GBK
&lt;span class="c1"&gt;# 更改当前 iTerm2 tab title&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-ne&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\033]0;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\007&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;$@&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-ne&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\033]0;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PWD&lt;/span&gt;&lt;span class="p"&gt;/#&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="p"&gt;/~&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\007&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# GBK任务完成后，自动切换回默认编码（UTF-8）&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\033]50;SetProfile=Default\a&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;LANG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;zh_CN.UTF-8
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;zh_CN.UTF-8
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;将以上内容保存为一个叫grun的文件，并赋予可执行权限，同时加到系统可执行目录（PATH）&lt;/p&gt;
&lt;p&gt;当我需要登录一台GBK编码的服务器时，只需要&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;grun&lt;span class="w"&gt; &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;${&lt;/span&gt;&lt;span class="n"&gt;hostname&lt;/span&gt;&lt;span class="cp"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;即可&lt;/p&gt;</content><category term="misc"/><category term="mac"/><category term="osx"/><category term="iterm2"/></entry><entry><title>sshd无法使用secureFX传输文件</title><link href="https://www.chenxiaosheng.com/posts/2013-09-24/sshd-sftp-securefx.html" rel="alternate"/><published>2013-09-24T15:58:00+08:00</published><updated>2013-09-24T15:58:00+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2013-09-24:/posts/2013-09-24/sshd-sftp-securefx.html</id><summary type="html">&lt;p&gt;某同学表示，某些机器无法使用&lt;code&gt;secureFX&lt;/code&gt;进行文件传输。&lt;/p&gt;
&lt;p&gt;正常情况下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="n"&gt;RECV&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AUTH_SUCCESS&lt;/span&gt;
&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="n"&gt;RECV&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="n"&gt;Sftp&lt;/span&gt; &lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="n"&gt;SEND&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RealPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="n"&gt;Resolved&lt;/span&gt; &lt;span class="n"&gt;RealPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;
&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="n"&gt;SEND&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OpenDir&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;而无 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;某同学表示，某些机器无法使用&lt;code&gt;secureFX&lt;/code&gt;进行文件传输。&lt;/p&gt;
&lt;p&gt;正常情况下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="n"&gt;RECV&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AUTH_SUCCESS&lt;/span&gt;
&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="n"&gt;RECV&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="n"&gt;Sftp&lt;/span&gt; &lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="n"&gt;SEND&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RealPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="n"&gt;Resolved&lt;/span&gt; &lt;span class="n"&gt;RealPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;
&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="n"&gt;SEND&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OpenDir&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;而无法传输文件的服务器则是如下日志：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;i RECV : AUTH_SUCCESS
i Changing state from STATE_CONNECTION to   STATE_TRANSPORT_STOPPING.
i Changing state from STATE_TRANSPORT_STOPPING to STATE_CLOSING.
i Changing state from STATE_CLOSING to STATE_CLOSED.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;查看&lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt;配置，发现缺少&lt;code&gt;sftp&lt;/code&gt;的相关配置，在该配置文件中增加：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Subsystem   sftp    /usr/libexec/openssh/sftp-server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;然后重启sshd服务，问题解决。&lt;/p&gt;</content><category term="misc"/><category term="sftp"/><category term="secureFX"/><category term="sshd"/></entry><entry><title>Django Admin 使用 filter_horizontal 不生效</title><link href="https://www.chenxiaosheng.com/posts/2013-09-23/django-filter_horizontal.html" rel="alternate"/><published>2013-09-23T15:25:00+08:00</published><updated>2013-09-23T15:25:00+08:00</updated><author><name>陈小生</name></author><id>tag:www.chenxiaosheng.com,2013-09-23:/posts/2013-09-23/django-filter_horizontal.html</id><summary type="html">&lt;p&gt;Django 1.2.7 admin在使用ManyToManyField的时候，默认使用垂直&lt;code&gt;filter_vertical&lt;/code&gt;方式进行显示与选择。此方式在选项比较多的时候，难以直观的看出哪些选项被选中，在尝试使用&lt;code&gt;filter_horizontal&lt;/code&gt;进行显示的时候，我们碰到了一些问题。&lt;/p&gt;</summary><content type="html">&lt;p&gt;考虑到Django 1.0.2 admin的功能实在太弱了，今天冒着大风险，将某系统的Django进行了升级（升级至Django 1.2.7）。&lt;/p&gt;
&lt;p&gt;Django 1.2.7 admin在使用&lt;code&gt;ManyToManyField&lt;/code&gt;的时候，默认使用垂直&lt;code&gt;filter_vertical&lt;/code&gt;方式进行显示与选择。此方式在选项比较多的时候，难以直观的看出哪些选项被选中，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src="/static/django_filter_vertical.png"&gt;&lt;/p&gt;
&lt;p&gt;决定使用更直观的方式，&lt;code&gt;filter_horizontal&lt;/code&gt;，设置比较简单，只要在&lt;code&gt;admin.py&lt;/code&gt;里对应的模块下，添加类似如下内容即可：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;filter_horizontal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;example&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;filter_horizontal&lt;/code&gt;显示方式如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src="/static/django_filter_horizontal.png"&gt;&lt;/p&gt;
&lt;p&gt;但是，当我设置了此选择后，发现出现了奇怪的问题，如下图：&lt;/p&gt;
&lt;p&gt;&lt;img src="/static/django_filter_nothing.png"&gt;&lt;/p&gt;
&lt;p&gt;没有报错，但也没有出现设想中的选择框。经过研究，发现，原来是因为我对应的字段&lt;code&gt;ips&lt;/code&gt;，在&lt;code&gt;models.py&lt;/code&gt;里定义的时候，&lt;code&gt;verbose_name&lt;/code&gt;使用了字符串，而不是&lt;code&gt;unicode&lt;/code&gt;，如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;ips&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManyToManyField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;IPData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbose_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;使用IP&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;调整为&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;ips&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManyToManyField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;IPData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbose_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;使用IP&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;问题解决。使用&lt;code&gt;string&lt;/code&gt;是因为我们这些是一些老旧的系统，新项目必须果断的抛弃&lt;code&gt;string&lt;/code&gt;，拥抱&lt;code&gt;unicode&lt;/code&gt;吧！&lt;/p&gt;</content><category term="misc"/><category term="django"/><category term="python"/></entry><entry><title>pelican增加自定义jinja template filters</title><link href="https://www.chenxiaosheng.com/posts/2013-09-11/pelican-custom-jinja-filters.html" rel="alternate"/><published>2013-09-11T19:46:00+08:00</published><updated>2013-09-11T19:46:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2013-09-11:/posts/2013-09-11/pelican-custom-jinja-filters.html</id><summary type="html">&lt;p&gt;pelican官方文档没有显式的指明应该如何自定义jinja template filters.&lt;/p&gt;
&lt;p&gt;在制作标签云的时候，不想写复杂的javascript，更不想又import一份javascript进来，所以决定自己搞一个比较简单的标签云&lt;/p&gt;
&lt;p&gt;pelican默认的tag排序比较简单，为了能有比较“云”的 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;pelican官方文档没有显式的指明应该如何自定义jinja template filters.&lt;/p&gt;
&lt;p&gt;在制作标签云的时候，不想写复杂的javascript，更不想又import一份javascript进来，所以决定自己搞一个比较简单的标签云&lt;/p&gt;
&lt;p&gt;pelican默认的tag排序比较简单，为了能有比较“云”的感觉，决定对tag加个随机排序&lt;/p&gt;
&lt;p&gt;查了Jinja的文档，没有随机排序的filter。由于我使用了&lt;code&gt;virtualenv&lt;/code&gt;，所以最简单粗暴的解决方案是直接修改了Jinja的源码，添加了随机排序的功能&lt;/p&gt;
&lt;p&gt;不过对于稍微有些“洁癖”的好，这样改后，心理上非常难受，决定到pelican社区，看看是否有幸添加个patch&lt;/p&gt;
&lt;p&gt;当然，更幸运的是在&lt;code&gt;github&lt;/code&gt;找到了此commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;https://github.com/getpelican/pelican/pull/96
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;原来有个&lt;code&gt;JINJA_FILTERS&lt;/code&gt;的设置，那一切就好办了, 增加自定义模块&lt;code&gt;jinja_filters.py&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;shuffle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Jinja template filter for shuffling list/tuple &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt; &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shuffle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;在&lt;code&gt;pelican&lt;/code&gt;配置文件里填加:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;jinja_filters&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shuffle&lt;/span&gt;

&lt;span class="n"&gt;JINJA_FILTERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;shuffle&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;shuffle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;尝试重新编译生成html文件，顺利完成。&lt;/p&gt;</content><category term="misc"/><category term="python"/><category term="pelican"/><category term="jinja"/></entry><entry><title>配置Bind使用MySQL dlz模式</title><link href="https://www.chenxiaosheng.com/posts/2013-09-09/install-bind-mysql-dlz.html" rel="alternate"/><published>2013-09-09T16:18:00+08:00</published><updated>2013-09-09T16:18:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2013-09-09:/posts/2013-09-09/install-bind-mysql-dlz.html</id><summary type="html">&lt;p&gt;** 安装MySQL/Bind with dlz **&lt;/p&gt;
&lt;p&gt;略过，bind dlz安装，只需在编译的时候增加 &lt;code&gt;--with-dlz-mysql&lt;/code&gt; 选项即可。&lt;/p&gt;
&lt;p&gt;** 创建 MySQL 数据库 **&lt;/p&gt;
&lt;p&gt;根据自己的需求创建即可，如使用如下命令创 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;** 安装MySQL/Bind with dlz **&lt;/p&gt;
&lt;p&gt;略过，bind dlz安装，只需在编译的时候增加 &lt;code&gt;--with-dlz-mysql&lt;/code&gt; 选项即可。&lt;/p&gt;
&lt;p&gt;** 创建 MySQL 数据库 **&lt;/p&gt;
&lt;p&gt;根据自己的需求创建即可，如使用如下命令创建一个名为 &lt;code&gt;dns&lt;/code&gt; 的数据库：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;CREATE DATABASE dns DEFAULT CHARSET UTF8;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;使用 &lt;code&gt;use dns&lt;/code&gt; 切换进 &lt;code&gt;dns&lt;/code&gt; 数据库后，使用如下命令创建一张 &lt;code&gt;dns_records&lt;/code&gt; 表：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n n-Quoted"&gt;`dns_records`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n n-Quoted"&gt;`zone`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n n-Quoted"&gt;`host`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n n-Quoted"&gt;`type`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n n-Quoted"&gt;`data`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n n-Quoted"&gt;`ttl`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DEFAULT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;600&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n n-Quoted"&gt;`id`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;NULL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n n-Quoted"&gt;`id`&lt;/span&gt;&lt;span class="p"&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;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;InnoDB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DEFAULT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CHARSET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;这里以域 &lt;code&gt;example.com&lt;/code&gt; 为例，说明如上字段意义，假设我需要有一条 &lt;code&gt;www.example.com&lt;/code&gt; 的记录指向 &lt;code&gt;1.1.1.1&lt;/code&gt; 这个IP，应该增加哪些内容：&lt;/p&gt;
&lt;p&gt;zone:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;域，这里指 example.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;host:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;主机名（名字），这里为 www
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;type:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;记录类型，这里为A
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;data:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;域名资源数据，这里为IP地址 1.1.1.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;ttl:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;域名TTL，为任意整数（建议不要低于120，即2分钟）
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;将该数据库授权给某用户（后续bind-dlz配置需要用到）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;grant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dns&lt;/span&gt;&lt;span class="nv"&gt;@localhost&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;identified&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;dns&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;用户名为 &lt;code&gt;dns&lt;/code&gt; ，密码也为 &lt;code&gt;dns&lt;/code&gt; ，只允许本地主机 &lt;code&gt;localhost&lt;/code&gt; 访问，并只给予了 &lt;code&gt;select&lt;/code&gt; 权限。&lt;/p&gt;
&lt;p&gt;** Bind-dlz配置 **&lt;/p&gt;
&lt;p&gt;这里只涉及dlz的配置，其它named.conf的配置不涉及&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;dlz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;your custom description&amp;quot;&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="nx"&gt;database&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;mysql&lt;/span&gt;
&lt;span class="s"&gt;        {host=localhost dbname=dns user=dns pass=dns}&lt;/span&gt;
&lt;span class="s"&gt;        {select zone from dns_records where zone = &amp;#39;$zone$&amp;#39;}&lt;/span&gt;
&lt;span class="s"&gt;        {select ttl, type, data from dns_records where host = &amp;#39;$record$&amp;#39; and zone=&amp;#39;$zone$&amp;#39;}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;your custom description:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;管理员自行添加的描述
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;配置完成。&lt;/p&gt;</content><category term="misc"/><category term="bind"/></entry><entry><title>Django multiple select option with title</title><link href="https://www.chenxiaosheng.com/posts/2013-09-09/django-multiple-select-option-with-title.html" rel="alternate"/><published>2013-09-09T15:02:00+08:00</published><updated>2013-09-09T15:02:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2013-09-09:/posts/2013-09-09/django-multiple-select-option-with-title.html</id><summary type="html">&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils.encoding&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;force_unicode&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils.html&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conditional_escape&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SelectMultipleWithTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SelectMultiple&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; multiple select optihon with title &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;render_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;selected_choices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;option_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;option_label&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;option_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;force_unicode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;option_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;selected_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;option_value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;selected_choices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; selected=&amp;quot;selected&amp;quot;&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;option value=&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s …&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils.encoding&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;force_unicode&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils.html&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conditional_escape&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SelectMultipleWithTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SelectMultiple&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; multiple select optihon with title &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;render_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;selected_choices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;option_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;option_label&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;option_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;force_unicode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;option_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;selected_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;option_value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;selected_choices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; selected=&amp;quot;selected&amp;quot;&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;option value=&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt; title=&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;/option&amp;gt;&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;option_value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;selected_html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;conditional_escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;force_unicode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;option_label&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="n"&gt;conditional_escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;force_unicode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;option_label&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="misc"/><category term="python"/><category term="django"/></entry><entry><title>install Facebook scribe on Debian Squeeze</title><link href="https://www.chenxiaosheng.com/posts/2013-09-08/install-facebook-scribe-on-debian-squeeze.html" rel="alternate"/><published>2013-09-08T13:03:00+08:00</published><updated>2013-09-08T13:03:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2013-09-08:/posts/2013-09-08/install-facebook-scribe-on-debian-squeeze.html</id><summary type="html">&lt;p&gt;系统: &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Debian Squeeze 6.0 amd64&lt;/p&gt;
&lt;p&gt;thrift: 0.8.0&lt;/p&gt;
&lt;p&gt;hadoop: 0.20.2 cdh3 hadoop-0.20_0.20.2+923.142&lt;/p&gt;
&lt;p&gt;scribe: git current version&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;首先安装各种库，包括但不局限于:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;python-dev maven2 ant sun-java6-jre sun-java6-jdk bison flex gcc make autoconf …&lt;/p&gt;&lt;/blockquote&gt;</summary><content type="html">&lt;p&gt;系统: &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Debian Squeeze 6.0 amd64&lt;/p&gt;
&lt;p&gt;thrift: 0.8.0&lt;/p&gt;
&lt;p&gt;hadoop: 0.20.2 cdh3 hadoop-0.20_0.20.2+923.142&lt;/p&gt;
&lt;p&gt;scribe: git current version&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;首先安装各种库，包括但不局限于:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;python-dev maven2 ant sun-java6-jre sun-java6-jdk bison flex gcc make autoconf libevent-dev libboost-all-dev git-core&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;之后:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;sudo update-alternatives --set java /usr/lib/jvm/java-6-sun/jre/bin/java #确保使用的不是openjdk&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;安装thrift:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;tar -zxvf thrift-0.8.0.tar.gz&lt;/p&gt;
&lt;p&gt;cd thrift-0.8.0&lt;/p&gt;
&lt;p&gt;./configure --prefix=/usr/local/thrift-0.8.0&lt;/p&gt;
&lt;p&gt;make&lt;/p&gt;
&lt;p&gt;sudo make install&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;安装fb303:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;cd thrift-0.8.0/contrib/fb303&lt;/p&gt;
&lt;p&gt;sh boostrap.sh&lt;/p&gt;
&lt;p&gt;./configure --prefix=/usr/local/fb303-0.8.0 --with-thriftpath=/usr/local/thrift-0.8.0 CPPFLAGS="-DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H"&lt;/p&gt;
&lt;p&gt;make&lt;/p&gt;
&lt;p&gt;sudo make install&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;安装hadoop:&lt;/p&gt;
&lt;p&gt;使用apache官方下载的版本编译各种不成功，其中一个错误（编译scribe时出现）:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;HdfsFile.cpp:255: error: ‘hdfsConnectNewInstance’ was not declared in this scope&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ConnectNewInstance是在hadoop 0.21之后的版本才出现的，由于我这边hadoop的服务器是0.20.2，所以只能使用cloudarea版本&lt;/p&gt;
&lt;p&gt;由于我在在编译时，cloudarea页面无法下载对应版本hadoop(或者跳转到apache页面)&lt;/p&gt;
&lt;p&gt;只好使用如下方式进行下载，首先，修改/etc/apt/source.list，增加&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;deb http://archive.cloudera.com/debian squeeze-cdh3u2 contrib&lt;/p&gt;
&lt;p&gt;deb-src http://archive.cloudera.com/debian squeeze-cdh3u2 contrib&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;然后使用apt-get source hadoop-0.20进行下载&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;tar -zxvf hadoop-0.20.2.tar.gz&lt;/p&gt;
&lt;p&gt;mv hadoop-0.20.2 /usr/local&lt;/p&gt;
&lt;p&gt;cd /usr/local/hadoop-0.20.2&lt;/p&gt;
&lt;p&gt;sudo ant compile-c++-libhdfs -Dislibhdfs=true #由于cdh3/apache提供的hadoop包内的libhdfs库不一定能使用，重新编译c++的libhdfs库&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;安装scribe:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;git clone https://github.com/facebook/scribe.git&lt;/p&gt;
&lt;p&gt;cd scribe&lt;/p&gt;
&lt;p&gt;sh bootstrap.sh&lt;/p&gt;
&lt;p&gt;./configure --prefix=/usr/local/scribe-2.2 --with-fb303path=/usr/local/fb303-0.8.0 --with-thriftpath=/usr/local/thrift-0.8.0 --with-hadooppath=/usr/local/hadoop-0.20.2 --enable-hdfs CPPFLAGS="-DHAVE_NETDB_H=1 -fpermissive -DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H -I/usr/local/hadoop-0.20.2/src/c++/libhdfs -I/usr/lib/jvm/java-6-sun-1.6.0.26/include -I/usr/lib/jvm/java-6-sun-1.6.0.26/include/linux" LDFLAGS="-L/usr/lib/jvm/java-6-sun-1.6.0.26/jre/lib/amd64 -L/usr/lib/jvm/java-6-sun-1.6.0.26/jre/lib/amd64/server -L/usr/local/hadoop-0.20.2/build/c++/Linux-amd64-64/lib"&lt;/p&gt;
&lt;p&gt;make&lt;/p&gt;
&lt;p&gt;sudo make install&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;其它配置:&lt;/p&gt;
&lt;p&gt;由于我这里各软件包都是自定义安装路径，需要配置一下ld加载路径，编辑(增加) /etc/ld.so.conf.d/scribe.conf&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;/usr/local/thrift-0.8.0/lib/&lt;/p&gt;
&lt;p&gt;/usr/local/hadoop-0.20.2/build/c++/Linux-amd64-64/lib/&lt;/p&gt;
&lt;p&gt;/usr/lib/jvm/java-6-sun-1.6.0.26/jre/lib/amd64/server/&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;保存退出后&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;sudo ldconfig&lt;/p&gt;
&lt;/blockquote&gt;</content><category term="misc"/><category term="debian"/><category term="scribe"/></entry><entry><title>Windows OpenVPN GUI记住用户名和密码</title><link href="https://www.chenxiaosheng.com/posts/2013-09-08/windows-openvpn-remember-password.html" rel="alternate"/><published>2013-09-08T12:51:00+08:00</published><updated>2013-09-08T12:51:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2013-09-08:/posts/2013-09-08/windows-openvpn-remember-password.html</id><summary type="html">&lt;p&gt;日常工作中经常使用VPN，由于Server端要求进行用户名和密码校验，在第一次链接的时候，OpenVPN Client会弹出输入用户名和密码的窗口。 如果网络比较稳定的情况下，这个还没什么问题，网络不稳定的时候，每次弹开，都会弹出重新验证的窗口，加上我自己的密码比较长，不胜其烦&lt;/p&gt;</summary><content type="html">&lt;p&gt;日常工作中经常使用VPN，由于Server端要求进行用户名和密码校验，在第一次链接的时候，OpenVPN Client会弹出输入用户名和密码的窗口。&lt;/p&gt;
&lt;p&gt;如果网络比较稳定的情况下，这个还没什么问题，网络不稳定的时候，每次弹开，都会弹出重新验证的窗口，加上我自己的密码比较长，不胜其烦&lt;/p&gt;
&lt;p&gt;我使用的版本（2.3.1），实际上已经支持保存密码的功能（旧版本可能需要重新编译），在ovpn配置文件里增加:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;auth-user-pass pass.txt&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;之后在ovpn配置文件所在目录建立一个pass.txt的文件，第一行为用户名，第二行为密码即可。&lt;/p&gt;
&lt;p&gt;PS：这样对自己电脑的安全性要有足够的保证，否则密码泄露就得不偿失了。&lt;/p&gt;</content><category term="misc"/><category term="windows"/><category term="openvpn"/></entry><entry><title>Debian系统添加全局根证书（CNNIC）</title><link href="https://www.chenxiaosheng.com/posts/2013-09-08/debian-add-cnnic-ca.html" rel="alternate"/><published>2013-09-08T12:25:00+08:00</published><updated>2013-09-08T12:25:00+08:00</updated><author><name>老树</name></author><id>tag:www.chenxiaosheng.com,2013-09-08:/posts/2013-09-08/debian-add-cnnic-ca.html</id><summary type="html">&lt;p&gt;鉴于CNNIC的证书默认被不信任，导致了应用在访问某些使用cnnic证书的ssl站点时，请求失败。本篇文章介绍了如何将CNNIC CA添加至Debian操作系统全局根证书。&lt;/p&gt;</summary><content type="html">&lt;p&gt;鉴于CNNIC的证书默认被不信任，导致了应用在访问某些使用cnnic证书的ssl站点时，请求失败。&lt;/p&gt;
&lt;p&gt;虽然我也不喜欢cnnic，但没办法，这是反抗不了的事情。&lt;/p&gt;
&lt;p&gt;过程如下（需要root权限）：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;apt-get install ca-certificates #一般情况下应该是已经有安装的了&lt;/p&gt;
&lt;p&gt;mkdir -p /usr/share/ca-certificates/local #建立local目录是为了方便区分这是自己填加的证书，可以自定义，但一定要在/usr/share/ca-certificates目录下&lt;/p&gt;
&lt;p&gt;将cnnic的证书复制至/usr/share/ca-certificates/local目录，请注意，证书的后缀必须修改为.crt&lt;/p&gt;
&lt;p&gt;dpkg-reconfigure ca-certificates #选中刚才添加的local/cnnic.crt证中，确定，即可&lt;/p&gt;
&lt;/blockquote&gt;</content><category term="misc"/><category term="debian"/></entry></feed>