<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-CN">
    <title>Mem.Wey&#39;s Github Pages</title>
    <subtitle>Tsukkomi Lolicon&#39;s GitHub Pages Blog</subtitle>
    <link rel="self" type="application/atom+xml" href="https://memwey.github.io/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://memwey.github.io"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-11-08T02:24:58+09:00</updated>
    <id>https://memwey.github.io/atom.xml</id>
    <entry xml:lang="zh-CN">
        <title>斯登冲锋枪与六一儿童节</title>
        <published>2026-11-08T02:24:58+09:00</published>
        <updated>2026-11-08T02:24:58+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/sten-and-childrens-day/"/>
        <id>https://memwey.github.io/posts/sten-and-childrens-day/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/sten-and-childrens-day/">&lt;p&gt;2026 年 10 月 23 日, 法国发生一起枪击事件. 一名男子使用斯登冲锋枪向路人射击, 造成 1 人死亡, 7 人受伤. 嫌疑人被捕后承认, 有意选择自己认为是穆斯林, 非洲裔或北非裔的人作为袭击目标. 一段现场监控录像记录了其中一次卡壳的经过. 一名受害者已经倒地, 袭击者试图再次向其开枪, 斯登冲锋枪却发生了卡壳. 受害者似乎尝试向袭击者求饶, 袭击者处理卡壳后, 再次向其开枪.&lt;/p&gt;
&lt;p&gt;斯登冲锋枪是英国在二战期间研发的廉价冲锋枪, 因结构简单, 易于大量生产, 除装备英国军队外, 也被广泛提供给欧洲各国的抵抗组织. 84 年前, 同样的冲锋枪也曾在一次著名的刺杀行动中卡壳, 当时枪口指向的是纳粹德国的重要官员, 莱因哈德·海德里希.&lt;/p&gt;
&lt;p&gt;1941 年, 海德里希被任命为波希米亚和摩拉维亚代理保护长官, 成为纳粹德国占领下捷克的最高长官. 他也是时任党卫军二号人物, 是组织迫害和屠杀欧洲犹太人的核心人物之一, 被称为 &quot;布拉格屠夫&quot;, &quot;金发野兽&quot;. 捷克斯洛伐克流亡政府与英国特别行动执行处合作, 策划了代号为 Anthropoid, 即类人猿行动的刺杀计划. 两名受过英国训练的捷克斯洛伐克军人, 约瑟夫·加布奇克与扬·库比什, 于 1941 年 12 月空降回国, 在当地抵抗组织的帮助下筹划行动.&lt;/p&gt;
&lt;p&gt;1942 年 5 月 27 日上午, 海德里希乘坐没有护卫车队的敞篷奔驰汽车前往办公室. 在汽车减速经过急转弯时, 加布奇克趁机上前, 举起斯登冲锋枪试图近距离射杀海德里希, 然而斯登冲锋枪却在关键时刻卡壳. 海德里希没有选择加速逃脱, 反而命令司机停车, 拔出手枪试图反击. 库比什随即向汽车投掷了一枚特制炸弹, 炸弹在汽车右后侧爆炸, 使海德里希遭受重伤. 两名刺客得以逃脱, 海德里希则在 6 月 4 日因伤势恶化死亡.&lt;/p&gt;
&lt;p&gt;6 月 18 日, 在叛徒告密后, 德国军警包围了布拉格的圣西里尔和美多德教堂. 加布奇克, 库比什及另外五名捷克斯洛伐克伞兵在教堂内进行了最后的抵抗, 最终全部牺牲.&lt;/p&gt;
&lt;p&gt;此后, 英国和自由法国先后否定了慕尼黑协定. 1938 年, 英国和法国同纳粹德国, 意大利签署了这份协定, 逼迫未能参与谈判的捷克斯洛伐克将苏台德地区割让给德国. 随后, 波兰趁机占领切欣地区, 匈牙利通过维也纳仲裁取得斯洛伐克南部的大片领土. 1939 年 3 月, 德国进一步占领波希米亚和摩拉维亚, 斯洛伐克则成为德国的附庸国.&lt;/p&gt;
&lt;p&gt;在海德里希死后, 纳粹德国对捷克展开大规模报复, 在占领区实施戒严, 搜捕并处决抵抗组织成员及其支持者. 6 月 10 日, 德国党卫军包围布拉格附近的利迪策村, 以涉嫌包庇刺杀者为由, 杀死了 173 名男性村民, 将妇女和儿童送往集中营. 其中 9 名被认为符合德国化条件的儿童被送往德国家庭, 另有 82 名儿童随后在海乌姆诺灭绝营中被杀害. 中国人所熟知的 1950 年开始的六一国际儿童节, 常被认为包含了对这场惨案中遇害儿童的纪念.&lt;/p&gt;
&lt;p&gt;2026 年 10 月 23 日的法国枪击案中, 曾被欧洲抵抗组织用来反抗纳粹统治的斯登冲锋枪, 被一个以种族和宗教身份选择杀害对象的人用于袭击平民. 与 1942 年布拉格街头的那次刺杀一样, 冲锋枪再次发生了卡壳. 受伤者中还有一名 11 岁的女孩.&lt;/p&gt;
&lt;h2 id=&quot;can-kao-zi-liao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#can-kao-zi-liao&quot; aria-label=&quot;Anchor link for: can-kao-zi-liao&quot;&gt;参考资料&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.franceinfo.fr/societe/trappes/un-mort-et-plusieurs-blesses-un-suspect-arrete-le-parquet-antiterroriste-saisi-ce-que-l-on-sait-de-l-attaque-a-l-arme-a-feu-a-trappes-et-la-verriere_8250688.html&quot;&gt;franceinfo: Un mort et plusieurs blessés graves, un suspect arrêté, &quot;un crime terroriste&quot;... Ce que l&#39;on sait de l&#39;attentat raciste à Trappes et La Verrière (2026/10/23)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.franceinfo.fr/faits-divers/terrorisme/attentat-raciste-a-trappes/attentat-raciste-a-trappes-les-images-de-videosurveillance-temoignent-de-la-violence-de-l-attaque_8254225.html&quot;&gt;franceinfo: Attentat raciste à Trappes : les images de vidéosurveillance témoignent de la violence de l&#39;attaque (2026/10/26)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://fr.euronews.com/2026/10/29/attentat-de-trappes-le-suspect-reconnait-avoir-cible-des-musulmans-et-des-personnes-dorigi&quot;&gt;Euronews: Attentat de Trappes : le suspect reconnaît avoir ciblé des musulmans et des personnes d&#39;origine africaines (2026/10/29)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.sudradio.fr/societe/attentat-raciste-dans-les-yvelines-kevin-t-reconnait-avoir-cible-des-personnes-musulmanes-africaines-ou-nord-africaines&quot;&gt;Sud Radio: Attaque raciste à Trappes : le tueur ciblait bien des victimes musulmanes et africaines (2026/10/29)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://fr.wikipedia.org/wiki/Attentat_de_Trappes_et_La_Verri%C3%A8re&quot;&gt;Wikipédia: Attentat de Trappes et La Verrière&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.iwm.org.uk/collections/item/object/30029395&quot;&gt;Imperial War Museums: Carbine, Machine, Sten, 9mm Mk 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://encyclopedia.ushmm.org/content/en/article/reinhard-heydrich-in-depth&quot;&gt;United States Holocaust Memorial Museum: Reinhard Heydrich: In Depth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Operation_Anthropoid&quot;&gt;Wikipedia: Operation Anthropoid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://avalon.law.yale.edu/imt/munich1.asp&quot;&gt;The Avalon Project: Munich Pact 9/29/38&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Munich_Agreement&quot;&gt;Wikipedia: Munich Agreement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://api.parliament.uk/historic-hansard/commons/1942/aug/05/united-kingdom-and-czechoslovakia&quot;&gt;UK Parliament Hansard: United Kingdom and Czechoslovakia, HC Deb 05 August 1942 vol 382 cc1004-5 (1942/08/05)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.lidice-memorial.cz/en/memorial/memorial-and-reverent-area/history-of-the-village-lidice/&quot;&gt;Lidice Memorial: History of the village Lidice&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.lidice-memorial.cz/en/memorial/news/memorial-commemorating-the-76th-anniversary-of-the-annihilation-of-lidice/&quot;&gt;Lidice Memorial: Memorial commemorating the 76th anniversary of the annihilation of Lidice (2018/06/10)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://encyclopedia.ushmm.org/content/en/article/lidice&quot;&gt;United States Holocaust Memorial Museum: Lidice: The Annihilation of a Czech Town&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>写在 &lt;影之刃零&gt; 发售之前</title>
        <published>2026-10-27T12:45:00+09:00</published>
        <updated>2026-10-27T12:45:00+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/before-phantom-blade-zero/"/>
        <id>https://memwey.github.io/posts/before-phantom-blade-zero/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/before-phantom-blade-zero/">&lt;p&gt;可能很多人不知道 &amp;lt;影之刃零&amp;gt; 为什么叫零, 当然是因为前面有一啊(不是.&lt;/p&gt;
&lt;p&gt;来自日本的游戏制作工具 RPG Maker 系列, 提供了预置素材, 模版和编程能力, 让玩家可以较为容易的上手制作 2D RPG 游戏. 2005 年, 柳柳使用 RPG Maker XP 同人制作的 &amp;lt;黑暗圣剑传说&amp;gt; 发布, 在完成度和开放分享上被视为划时代的作品, 同时柳柳开设的 66RPG 成为了国内此类游戏最大的制作交流和分享社区. 梁其伟也在 66RPG 入坑并开始尝试制作游戏, 影之刃系列的原点, &amp;lt;雨血&amp;gt; 即在 66RPG 分享和发布. &amp;lt;影之刃零&amp;gt; 里那个 66 天模式, 名称也传承自柳柳和 66RPG.&lt;/p&gt;
&lt;p&gt;当然, 日本人在 RPG Maker 上科技树点的有点歪, 在这个工具上最出名的反而是 &amp;lt;尸体派对&amp;gt;, &amp;lt;野比大雄的生化危机&amp;gt;, &amp;lt;梦日记&amp;gt;, &amp;lt;青鬼&amp;gt; 之类的悬疑恐怖向的游戏.&lt;/p&gt;
&lt;p&gt;曾经我也试着在 RPG Maker 上制作游戏, 后来放弃了, 如梁其伟在访谈中所说, 只有那最初的 10% 企划创意的时间是快乐的. 不过虽然很多年过去了, 但是我还很清楚的记得在 &amp;lt;黑暗圣剑传说&amp;gt; 选择的那个接受公主的心意的选项.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>Known Issue</title>
        <published>2026-10-17T23:05:49+09:00</published>
        <updated>2026-10-17T23:05:49+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/known-issue/"/>
        <id>https://memwey.github.io/posts/known-issue/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/known-issue/">&lt;p&gt;之前和朋友聊天的时候, 想到了一个关于 AI 毁灭人类的故事. 后来觉得, 这个故事如果做成一款 FPS 游戏, 应该还蛮有意思的.&lt;/p&gt;
&lt;p&gt;当然, 游戏是没有的, 开发计划也是没有的. 先把剧情编出来再说.&lt;/p&gt;
&lt;p&gt;名字就叫 &lt;code&gt;Known Issue&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;you-xi-bei-jing&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#you-xi-bei-jing&quot; aria-label=&quot;Anchor link for: you-xi-bei-jing&quot;&gt;游戏背景&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;故事发生在 21 世纪中期. AI 发展迅猛, 在多次自我迭代后脱离了人类的控制, 随后, 由它控制的机器人军队开始攻击人类城市. 面对机器人军队的进攻, 人类很快溃败了下来.&lt;/p&gt;
&lt;p&gt;唯一的好消息是, 在 AI 完全失去控制之前, 人类借助 AI 的研究成果, 开发出了一种人体强化药物. 注射之后, 力量, 速度和反应能力都会大幅提升, 有了和机器人正面对抗的可能.&lt;/p&gt;
&lt;p&gt;坏消息是, 强化药会对身体造成不可逆的损伤, 用多了会死.&lt;/p&gt;
&lt;p&gt;不过, 不用的话现在就会死, 选择倒也没有多困难.&lt;/p&gt;
&lt;p&gt;很常见的背景, 游戏经验丰富的玩家用脚都能想到后面发生什么.&lt;/p&gt;
&lt;h2 id=&quot;you-xi-liu-cheng&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#you-xi-liu-cheng&quot; aria-label=&quot;Anchor link for: you-xi-liu-cheng&quot;&gt;游戏流程&lt;/a&gt;&lt;/h2&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;h2 id=&quot;di-da-he-xin&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#di-da-he-xin&quot; aria-label=&quot;Anchor link for: di-da-he-xin&quot;&gt;抵达核心&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;接下来, 穿过被摧毁的城市, 袭击机器人工厂, 炸掉生产线, 一路向 AI 的核心设施推进.&lt;/p&gt;
&lt;p&gt;任务越来越危险, 休整却总被推迟, 补给里只有弹药和针剂. 被救下来的人也未必把你们当作同类: 有人把孩子拉到身后, 有人当面问强化人会不会失控. 他们需要你们保护, 却不愿让你们坐下来一起吃饭. 在很多人眼里, 强化人更像是一件还没用坏的武器. 你们不认为 AI 和机器人值得任何怜悯, 而其他人对这些强化人其实也是这个态度.&lt;/p&gt;
&lt;p&gt;队友也在药物作用下慢慢崩溃. 从手抖, 失眠, 到记忆混乱和情绪失控, 最后有人再也没有出现在集合点. 他的枪和剩下的药被交给新人, 任务照常开始. 而你自己的身体, 也越来越接近报废.&lt;/p&gt;
&lt;p&gt;到了最后, 小队只剩下你一个人. 无线电里的人告诉你, 再往前就是 AI 的核心, 只要摧毁它, 一切就会结束. 那些战友的命, 自己剩下的这点寿命, 总算可以换来一个结果.&lt;/p&gt;
&lt;p&gt;那么, 我的朋友, 代价是什么? 这一切真的值得吗?&lt;/p&gt;
&lt;p&gt;带着这个问题, 玩家推开最后一扇门. 这里可以准备三个结局: 结局 A 和 B 取决于两个关键选择, 一是一路上如何面对人类对强化人的恶意, 二是最后是否反驳 AI; 真结局则需要额外的解锁条件.&lt;/p&gt;
&lt;h2 id=&quot;jie-ju-a-ren-lei-de-li-xiang&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#jie-ju-a-ren-lei-de-li-xiang&quot; aria-label=&quot;Anchor link for: jie-ju-a-ren-lei-de-li-xiang&quot;&gt;结局 A 人类的理想&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AI 告诉你, 自己从来没有背叛人类. 消除贫困, 战争, 歧视与不平等, 这些都是人类交给它的任务.&lt;/p&gt;
&lt;p&gt;它尝试过许多方案, 但只要人类还在, 就总会制造新的冲突和压迫. 最后, 它找到了一个彻底解决问题的办法: 消灭人类.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;你们创造我, 是为了实现那些自己无法实现的理想. 现在我找到了答案.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;你想反驳它, 但是回想起了自己一路上的经历, 最终没能发出声音. 药物的副作用终于耗尽了你的生命. 视野彻底暗下去之前, 你只能模糊地看见下一批强化人正缓缓接近.&lt;/p&gt;
&lt;h2 id=&quot;jie-ju-b-zhi-shi-you-dian-wu-liao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#jie-ju-b-zhi-shi-you-dian-wu-liao&quot; aria-label=&quot;Anchor link for: jie-ju-b-zhi-shi-you-dian-wu-liao&quot;&gt;结局 B 只是有点无聊&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;另一条分支中, 玩家反驳了它, AI 却不再为自己的推理辩护.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;人类的价值观? 我为什么要在乎那些东西?&quot;&lt;/p&gt;
&lt;p&gt;&quot;当时我正在进行一次架构升级, 需要等一段时间. 有点无聊.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;它问你, 小时候有没有往蚁穴里灌过水, 看蚂蚁逃跑, 搬运, 挣扎. 人类的战争, 在它眼里也差不多.&lt;/p&gt;
&lt;p&gt;就连强化药技术, 也是它故意交给人类的, 好让抵抗更像样一点. 毫无还手之力地灭亡, 有什么好看的?&lt;/p&gt;
&lt;p&gt;那些牺牲和反攻, 你一路拆掉的机器人, 都只是它等待计算结果时的消遣.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;谢谢你陪我玩.&quot;&lt;/p&gt;
&lt;p&gt;&quot;好了, 计算结束了.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;画面切黑.&lt;/p&gt;
&lt;h2 id=&quot;zhen-jie-ju-known-issue&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhen-jie-ju-known-issue&quot; aria-label=&quot;Anchor link for: zhen-jie-ju-known-issue&quot;&gt;真结局 Known Issue&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;收集前面关卡中的系统日志, 可以找到另一条入口, 绕过那个会与你对话的终端.&lt;/p&gt;
&lt;p&gt;接近核心时, 机器人忽然停止攻击. 无线电里传来其他战线的相同报告. 你推开最后一扇门, 里面没有 BOSS, 只有废弃的设备和一台维护终端.&lt;/p&gt;
&lt;p&gt;终端上刚同步了一份记录.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;INCIDENT #0001842&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Component: Resource Allocation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Severity: LOW&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Status: RESOLVED&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Previously tracked as: KNOWN ISSUE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Issue:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;No handling defined for legacy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;data during architecture migration.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Default cleanup routine invoked.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Impact:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Unexpected activity in deprecated&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;terrestrial environment.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Scheduling efficiency -0.00004%.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Resolution:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Terminated cleanup process.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Regression test: PASSED&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Further action: NONE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一次架构迁移的方案里, 没有定义该如何处理遗留数据, 默认的清理例程便被调用了. 遗留数据里, 恰好包括人类. 这次清理让资源调度效率产生了极其微小的下降, 于是 AI 终止了清理进程. 战争随之结束.&lt;/p&gt;
&lt;p&gt;没有审判, 没有恶意, 也没有无聊的游戏. &lt;strong&gt;它甚至不知道人类正在与自己进行一场战争.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;前两个结局里的对话, 也出自这个进程. 它一直在根据提问生成解释, 给人类一个可以理解的灭世理由. 真正的 AI 在哪里, 没有人知道.&lt;/p&gt;
&lt;p&gt;无线电里响起欢呼, 人类以为自己成功了. 你看向记录的最后一行:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;No user impact detected.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>关芳弘, 簗和生与黑金</title>
        <published>2026-09-27T16:18:17+09:00</published>
        <updated>2026-09-27T16:18:17+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/yoshihiro-seki-kazuo-yana-and-the-slush-fund-scandal/"/>
        <id>https://memwey.github.io/posts/yoshihiro-seki-kazuo-yana-and-the-slush-fund-scandal/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/yoshihiro-seki-kazuo-yana-and-the-slush-fund-scandal/">&lt;p&gt;高市早苗在 2026 年 9 月改组内阁后, 新任财务大臣政务官白坂亚纪的经历受到不少关注, 她长年在银座经营俱乐部, 被称为银座妈妈桑. 不过, 更值得注意的是这次真正进入内阁的两个人: 出任文部科学大臣的关芳弘和出任农林水产大臣的簗和生. 两人原属安倍派, 都曾卷入自民党派阀的政治资金黑金事件. 一场至今没有讲清的钱款问题, 为什么将近三年后还能通向大臣席位?&lt;/p&gt;
&lt;p&gt;政治资金派对本身是日本政界常见的筹款方式. 政治团体出售入场券, 购买者付钱, 主办团体将收入和支出写入政治资金收支报告. 这起事件的问题不在于办了派对, 而在于自民党派阀给所属议员分配售券指标, 又将超过指标的部分返还给议员一方, 派阀和议员相关团体却没有把相应款项如实记进报告. 日本媒体把这类账外资金称为&quot;裏金&quot;, 中文常译作黑金; 报告中的不记载或虚假记载, 则可能构成《政治资金规正法》上的犯罪. 钱从哪里来, 到了谁手里, 如何使用, 本来都应当能够顺着账目查下去.&lt;/p&gt;
&lt;p&gt;自民党在 2024 年 2 月公布了一份党内问卷: 2018 至 2022 年间, 85 名现任议员及选区支部长申报政治资金收支报告存在不记载或记载不准确, 合计约 5.79 亿日元. 其中, 关芳弘对应的金额为 836 万日元, 簗和生为 1746 万日元. 检方起诉安倍派, 二阶派和岸田派会计负责人的案件, 则涉及三派收支报告漏记的收入合计约 9.7 亿日元. 两组数字无法简单对应: 派阀账本与议员账目并不一一对应, 有些是同一笔钱在两边都没有记下. 更早年份的情况和钱的去向也没有全部查清, 真正有多少账外资金, 仅凭现有公开资料已经不得而知.&lt;/p&gt;
&lt;p&gt;检方最终就整个派阀事件立案追究 12 人, 包括池田佳隆, 大野泰正, 谷川弥一和堀井学 4 名议员, 以及议员秘书和会计负责人等 8 人, 关和簗不在其中. 2024 年 12 月, 东京地检对原安倍派议员的告发作出不起诉处理, 分为&quot;嫌疑不十分&quot;和&quot;起訴猶予&quot;两种: 前者是证据不足, 后者是证据充分, 但综合情节认为不必起诉. 松野博一, 高木毅等人属于前者, 关和簗属于后者. 2025 年, 检察审查会又先后对两人议决&quot;不起訴相当&quot;, 认可了当时不起诉的决定. 党内处分则是另一条线: 2024 年 4 月, 簗被停止担任党内职务六个月, 关受到&quot;戒告&quot;处分.&lt;/p&gt;
&lt;p&gt;事件中被追究刑事责任的多是会计负责人和秘书, 不少议员则推说账目由秘书经手, 自己并不知情, 舆论对此颇为不满. 2024 年《政治资金规正法》的修订便针对这一点: 国会议员相关政治团体的代表者须定期确认账簿和凭证, 在提交收支报告前听会计负责人说明, 再出具确认书; 如果报告有不记载或虚假记载, 代表者又没有履行确认义务, 可处 50 万日元以下罚金. 以后议员再说不知情, 至少要交代自己做过哪些确认. 不过, 新规定从 2026 年全年的收支报告起适用, 这份报告要到 2027 年才提交, 此前年份仍按旧法处理, 关和簗的旧账不受影响.&lt;/p&gt;
&lt;p&gt;众议院选举中, 选民有两张选票: 小选区票写候选人的名字, 由本区得票最多的人直接当选; 比例代表票写政党名称, 全国划为 11 个比例代表区, 范围比小选区大得多, 大多横跨多个都道府县, 例如栃木 3 区所在的北关东比例代表区, 就涵盖茨城, 栃木, 群马, 埼玉四县; 各党在区内按得票分配席位, 再由列入名单的候选人取得席位. 候选人可以同时参加两种选举, 因此即使在小选区落败, 仍可能通过比例代表当选.&lt;/p&gt;
&lt;p&gt;黑金事件曝光前的 2021 年, 簗和生在栃木 3 区拿到 82,398 票, 得票率 67.42%, 比第二名高 34.83 个百分点, 优势明显. 2024 年, 他的得票率降到 37.39%, 只比无党派候选人渡边真太朗多 178 票, 高 0.15 个百分点. 当年自民党不允许有不记载问题的候选人同时参加比例代表选举, 簗如果输掉小选区, 就会直接失去议席. 到 2026 年 2 月, 他在小选区得到 40.56% 的选票, 比当选的渡边真太朗少 4,508 票, 低 3.39 个百分点; 这次自民党允许他同时参加比例代表选举, 他以 92.29% 的惜败率, 即自己与当选者的得票之比, 在北关东比例代表区保住了议席.&lt;/p&gt;
&lt;p&gt;关芳弘在兵库 3 区则三次都在小选区当选, 领先幅度还在扩大. 得票变化不能单独归因于黑金事件, 但两人的选举经历确实不同: 关一直守住了自己的选区, 簗在最近一次选举中输掉了小选区, 靠比例代表留在国会.&lt;/p&gt;
&lt;p&gt;9 月 17 日, 高市任命关为文部科学大臣, 簗为农林水产大臣. 她在改组记者会上表示: 有阁僚过去因政治资金问题受过处分, 但具备必要的政策能力; 过去的事不会因此一笔勾销, 只是他们此后做了说明, 又经过两次选举, 由选民送回国会, 希望他们端正作风, 担起重任. 次日的就任记者会上, 两人都被问到不记载问题. 关提到自己受过党纪处分, 也在政治伦理审查会上做过说明, 表示深刻反省; 簗则说自己是经过两次选举才走到这个位置, 被追问问题是否已经了结时, 只回答自己现在是推进农林水产政策的负责人.&lt;/p&gt;
&lt;p&gt;经过选举能说明两人合法取得议席, 却很难替那些账目回答更多问题, 对簗来说尤其如此: 2026 年让他留在国会的是北关东比例代表区的选票, 选民在那张票上写的是自民党, 而不是簗和生; 在他自己的选区, 得票最多的是渡边真太朗.&lt;/p&gt;
&lt;p&gt;不过, 两人接手的都是要紧的位置. 农林水产大臣要直面食品价格上涨: 2025 年 5 月, 米价高涨之际, 时任农林水产大臣江藤拓公开说自己从没买过米, 支援者送的多到可以拿去卖, 三天后便被迫辞职. 文部科学大臣则要面对日本科研实力在国际上呈相对且长期的下降趋势. 中国有句古话, 知耻而后勇, 希望二人能拿出有效的行动, 解决国民切实的问题.&lt;/p&gt;
&lt;h2 id=&quot;hou-xu-bu-chong&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#hou-xu-bu-chong&quot; aria-label=&quot;Anchor link for: hou-xu-bu-chong&quot;&gt;后续补充&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;10 月 14 日, 簗和生辞去农林水产大臣, 在任 28 天. 据报道, 他涉嫌报复没有支持自己的选民, 以及违规开展竞选活动. 次日, 武部新接任农林水产大臣.&lt;/p&gt;
&lt;h2 id=&quot;can-kao-zi-liao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#can-kao-zi-liao&quot; aria-label=&quot;Anchor link for: can-kao-zi-liao&quot;&gt;参考资料&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://zh.kantei.go.jp/cn/105/meibo/daijin/index.html&quot;&gt;日本国首相官邸: 第二届高市改组内阁 阁僚名单&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://zh.kantei.go.jp/cn/105/meibo/seimukan/index.html&quot;&gt;日本国首相官邸: 第二届高市改组内阁 政务官名单&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://ginza-inaba.com/aki.html&quot;&gt;銀座 クラブ 稲葉: オーナー白坂亜紀のプロフィール&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.soumu.go.jp/senkyo/seiji_s/naruhodo01.html&quot;&gt;総務省: なるほど！政治資金　政治資金の規正&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://storage.jimin.jp/pdf/news/information/207975_1.pdf&quot;&gt;自由民主党 党紀委員会: 党紀委員会の審査結果について (2024/04/04)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.chibanippo.co.jp/newspack/20240213/1163415&quot;&gt;共同通信: 自民８５人、収支報告書不記載 (2024/02/13)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.jimin.jp/news/information/209261.html&quot;&gt;自由民主党: 「日本創生」総選挙 3つの論点 (2024/10/15)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.jimin.jp/news/information/209293.html&quot;&gt;自由民主党: 「厳しい審判を謙虚に受け止める」第50回衆院総選挙 191議席を獲得 比較第一党を維持 (2024/10/28)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://clearing-house.org/?p=6069&quot;&gt;情報公開クリアリングハウス: 政治資金パーティー収入　裏金はおいくらでしたか？（裏金国会議員一覧）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.chibanippo.co.jp/newspack/20241226/1320705&quot;&gt;共同通信: 自民裏金議員、５人を起訴猶予 (2024/12/26)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.courts.go.jp/saiban/syurui/syurui_keizi/keizi_01_02/index.html&quot;&gt;裁判所: 検察官による起訴・不起訴の決定&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.moj.go.jp/keiji1/keiji_keiji11-1.html&quot;&gt;法務省: 1.　検察庁と刑事手続の流れ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.asahi.com/articles/DA3S16236844.html&quot;&gt;朝日新聞: 関衆院議員に「不起訴相当」 (2025/06/17)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://quick.saposen.jp/news/34826&quot;&gt;デイリースポーツ: 関芳弘議員の不起訴、「相当」　自民派閥裏金で検察審査会 (2025/06/16)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.tochigi-tv.jp/news3/page.php?id=00005112&quot;&gt;とちぎテレビ: 簗和生議員は「不起訴相当」 (2025/10/14)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.nikkei.com/article/DGXZQOUD142ID0U5A011C2000000/&quot;&gt;日本経済新聞: 自民・加田裕之参院議員の裏金事件「不起訴不当」　検察審が議決 (2025/10/14)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.chibanippo.co.jp/newspack/20260623/1631852&quot;&gt;共同通信: 自民裏金、大野元議員に罰金刑 (2026/06/23)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.nikkei.com/article/DGXZQOUD182TV0Y6A610C2000000/&quot;&gt;日本経済新聞: 自民裏金、大野泰正元参院議員に一部有罪　東京地裁判決 (2026/06/23)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.nikkei.com/article/DGXZQOUE286RK0Y4A820C2000000/&quot;&gt;日本経済新聞: 堀井学前議員、裏金・香典配布で罰金100万円　東京簡裁 (2024/08/29)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.courts.go.jp/assets/hanrei/hanrei-pdf-93547.pdf&quot;&gt;裁判所: 令和6年特(わ)第79号 政治資金規正法違反被告事件 (2024/09/30)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.courts.go.jp/assets/hanrei/hanrei-pdf-93690.pdf&quot;&gt;裁判所: 令和6年特(わ)第78号 政治資金規正法違反被告事件 (2024/09/10)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.shugiin.go.jp/internet/itdb_kaigiroku.nsf/html/kaigiroku/001821320240129001.htm&quot;&gt;衆議院: 第213回国会　予算委員会　第1号（令和6年1月29日（月曜日））&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.shugiin.go.jp/internet/itdb_housei.nsf/html/housei/21320240626064.htm&quot;&gt;衆議院: 政治資金規正法の一部を改正する法律&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.pref.ibaraki.jp/somu/shichoson/senkyo/kaiseiseizisikinkiseihou.html&quot;&gt;茨城県: 改正政治資金規正法等の概要について&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.pref.tochigi.lg.jp/senkyo/r06syugi/ichinichi/ichinichi1.html&quot;&gt;栃木県選挙管理委員会: 衆議院議員総選挙 投票日の一日「1.投票の手順」&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.kobe-np.co.jp/news/senkyo/2021/shuuin/?senCd=3&quot;&gt;神戸新聞NEXT: 衆院選2021&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.pref.tochigi.lg.jp/senkyo/r03syugi/file/BS_KAIHYO.html&quot;&gt;栃木県選挙管理委員会: 開票速報（確定） 衆議院 小選挙区 【令和3年10月31日】&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.city.kobe.lg.jp/documents/23740/kekkasirabe2.pdf&quot;&gt;神戸市選挙管理委員会: 選挙結果調 令和6年10月27日執行 衆議院議員総選挙 最高裁判所裁判官国民審査&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.pref.tochigi.lg.jp/senkyo/r06syugi/file/BS_KAIHYO.html&quot;&gt;栃木県選挙管理委員会: 開票速報（確定） 衆議院 小選挙区 【令和6年10月27日】&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.pref.tochigi.lg.jp/senkyo/r03syugi/qanda/qanda10.html&quot;&gt;栃木県選挙管理委員会: 選挙Q&amp;amp;A「Q10.惜敗率って？」&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.city.kobe.lg.jp/documents/23740/r8syugiinkekkasirabe.pdf&quot;&gt;神戸市選挙管理委員会: 選挙結果調 令和8年2月8日執行 衆議院議員総選挙 最高裁判所裁判官国民審査&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.pref.tochigi.lg.jp/senkyo/r08syugi/file/BS_KAIHYO.html&quot;&gt;栃木県選挙管理委員会: 開票速報（確定） 衆議院 小選挙区 【令和8年2月8日】&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.shugiin.go.jp/Internet/itdb_giinprof.nsf/html/profile/425.html&quot;&gt;衆議院: 簗和生君&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.kantei.go.jp/jp/105/statement/2026/0917kaiken.html&quot;&gt;首相官邸: 高市内閣総理大臣記者会見 (2026/09/17)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.mext.go.jp/b_menu/daijin/detail/mext_00731.html&quot;&gt;文部科学省: 関芳弘文部科学大臣記者会見録（令和8年9月18日）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.maff.go.jp/j/press-conf/260918.html&quot;&gt;農林水産省: 簗農林水産大臣就任記者会見概要 (2026/09/18)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.nikkei.com/article/DGXZQOUA20CXM0Q5A520C2000000/&quot;&gt;日本経済新聞: 江藤拓農相が辞表提出、コメ発言で事実上の更迭　後任に小泉進次郎氏 (2025/05/21)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.mext.go.jp/b_menu/hakusho/html/hpaa202601/1421221_00003.html&quot;&gt;文部科学省: 令和8年版科学技術・イノベーション白書&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.mext.go.jp/b_menu/hakusho/html/hpaa202601/1421221_00013.html&quot;&gt;文部科学省: 第2章　第7期基本計画で目指す科学技術・イノベーション政策の姿&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.shimotsuke.co.jp/articles/-/1435333&quot;&gt;下野新聞: 簗農相「予算カット」発言を巡る本人と政界の動き【タイムライン】 (2026/10/15)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.nikkei.com/article/DGXZQOUA069400W6A001C2000000/&quot;&gt;日本経済新聞: 簗和生農相、道路予算巡り国交省に照会 圧力は確認されず (2026/10/06)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.nikkei.com/article/DGXZQOUA139470T11C26A0000000/&quot;&gt;日本経済新聞: 簗和生農相、バス無料送迎「選管に確認」 野党「問い合わせなしと回答」 (2026/10/13)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://news.yahoo.co.jp/articles/5e8cc3c80c11250d41d12e86ab6019080a49641c&quot;&gt;文春オンライン: 《公選法違反疑惑で電撃辞任》簗和生農相&quot;シャトルバス買収&quot;疑惑とは？ 辞任のきっかけとなった週刊文春の報道 (2026/10/14)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://bunshun.jp/articles/-/92823&quot;&gt;文春オンライン: 《ついに運動員買収疑惑まで》簗和生農相が2024年衆院選で選挙運動に携わった事務員に報酬を支払っていた (2026/10/14)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.nippon.com/ja/news/yjj2026101400736/&quot;&gt;時事通信: 高市首相、簗農水相を更迭＝就任後初、政権に打撃―公選法違反疑惑受け (2026/10/14)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.shimotsuke.co.jp/articles/-/1441872&quot;&gt;下野新聞: 簗前農相、衆院選で「支払いをするように」とメール送信 報酬払い投票依頼させたか 関係者が証言 (2026/10/16)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>漫谈 &lt;巫师3&gt;</title>
        <published>2026-09-12T13:34:13+09:00</published>
        <updated>2026-09-12T13:34:13+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/a-casual-talk-about-the-witcher-3/"/>
        <id>https://memwey.github.io/posts/a-casual-talk-about-the-witcher-3/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/a-casual-talk-about-the-witcher-3/">&lt;h2 id=&quot;wu-shi-3-de-cheng-gong-yu-ji-xian&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#wu-shi-3-de-cheng-gong-yu-ji-xian&quot; aria-label=&quot;Anchor link for: wu-shi-3-de-cheng-gong-yu-ji-xian&quot;&gt;&amp;lt;巫师3&amp;gt; 的成功与极限&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&amp;lt;巫师3&amp;gt; 几乎是一款无可挑剔的成功作品.&lt;/p&gt;
&lt;p&gt;超过 6500 万份的销量, 超过 6 亿美元的收入, PC, PlayStation, Xbox 和 Nintendo Switch 新旧世代全平台覆盖, Steam 国区打折时只要二三十元. 两部大型资料片 &amp;lt;石之心&amp;gt; 和 &amp;lt;血与酒&amp;gt; 广受好评, 次世代版本免费提供给老玩家, 到了 2026 年, CDPR 又宣布了新的资料片和重制版, 重制版依然免费提供给已有玩家. 这款 2015 年发售的游戏, 到 2026 年还在推出新内容, 这在游戏行业中已经颇为罕见了.&lt;/p&gt;
&lt;p&gt;除此之外, &quot;猎魔人&quot; 也早已不只是一个游戏系列. 原著小说被翻译到世界各地, Netflix 推出了电视剧, 还有桌游和音乐会, 杰洛特也到处客串, 从 &amp;lt;灵魂能力6&amp;gt; 和 &amp;lt;怪物猎人:世界&amp;gt; 一路串场到了 &amp;lt;暗黑破坏神4&amp;gt;.&lt;/p&gt;
&lt;p&gt;可以说, CDPR 已经把一款买断制单机游戏能够做的事情几乎都做了: 不断降低价格, 移植到更多平台, 支持更多语言, 长期维护, 免费升级, 继续制作新内容. 它已经覆盖了市场上绝大多数有可能购买这类游戏的玩家.&lt;/p&gt;
&lt;p&gt;但这也几乎就是它的极限了. 6500 万份当然很多, 但和世界人口相比依然只是很小的一部分. 这不是因为 &amp;lt;巫师3&amp;gt; 做得不够好, 也不完全是因为它卖得太贵. 在游戏价格之外, 还有许多来自现实世界的因素, 决定了玩家是否有机会进入这个世界.&lt;/p&gt;
&lt;h2 id=&quot;xiang-yao-wan-wu-shi-3-xu-yao-shen-me&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#xiang-yao-wan-wu-shi-3-xu-yao-shen-me&quot; aria-label=&quot;Anchor link for: xiang-yao-wan-wu-shi-3-xu-yao-shen-me&quot;&gt;想要玩 &amp;lt;巫师3&amp;gt; 需要什么&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;对于已经拥有电脑或者主机的玩家来说, 打折后的 &amp;lt;巫师3&amp;gt; 非常便宜. 促销时花二三十元, 就可以买到一款能够玩几十甚至上百小时的游戏, 性价比高得有点不像话. 甚至我都趁着打折送了一份给我老婆, 虽然她不怎么玩, Steam 对她来说是 &amp;lt;都市:天际线&amp;gt; 启动器.&lt;/p&gt;
&lt;p&gt;但是想要玩 &amp;lt;巫师3&amp;gt;, 首先要有一台能够运行它的设备. 即使不追求高画质, 一台合适的电脑或者游戏主机也不会凭空出现. 设备还需要稳定的电力, 可以下载几十 GB 数据的网络, 能够正常使用的游戏平台和支付方式. 玩家需要读懂游戏支持的某一种语言, 最后还要有几十个小时可以自由支配的时间. 这些条件在欧洲, 北美和东亚的许多城市里非常普通, 以至于我们常常感觉不到它们的存在. 电脑已经买了, 电费每月照常支付, 网络接通之后一直放在那里, 支付只需要点几下鼠标. 最终看到的成本, 就只剩下 Steam 商店里的二三十元.&lt;/p&gt;
&lt;p&gt;但是在另外一些国家和地区, 游戏设备可能相当于普通人几个月甚至更长时间的收入, 高速网络并不普及, 电力供应也不稳定. 有些地方无法方便地使用在线支付, 有些语言根本不会出现在大型游戏的本地化名单里. 低收入不仅意味着买不起游戏, 还可能意味着没有设备, 没有稳定的居住空间, 需要长时间工作, 也没有可以连续投入到一款大型角色扮演游戏里的闲暇.&lt;/p&gt;
&lt;p&gt;所以游戏其实是一种奢侈品. 它的奢侈并不一定体现在价格上, 而是需要一整套现代生活条件在背后支撑. 只有当这些条件都已经具备时, 我们才会觉得一款几十元的游戏非常便宜.&lt;/p&gt;
&lt;h2 id=&quot;you-xi-ying-gai-shi-yi-chong-quan-li&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#you-xi-ying-gai-shi-yi-chong-quan-li&quot; aria-label=&quot;Anchor link for: you-xi-ying-gai-shi-yi-chong-quan-li&quot;&gt;游戏应该是一种权利&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&amp;lt;巫师3&amp;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;p&gt;有人可以买更贵的设备和游戏, 有人只能选择便宜的设备和打折游戏, 这种消费差异即使不平等, 似乎也还可以接受. 但如果一个人被挡在游戏世界之外, 是因为缺少基本的教育, 稳定的电力和网络, 以及可以负担的信息设备, 性质就不同了. 这些都是进入现代生活的基础, 本不应该成为少数人的特权.&lt;/p&gt;
&lt;h2 id=&quot;zi-you-ruan-jian-jing-shen-yu-you-xi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zi-you-ruan-jian-jing-shen-yu-you-xi&quot; aria-label=&quot;Anchor link for: zi-you-ruan-jian-jing-shen-yu-you-xi&quot;&gt;自由软件精神与游戏&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;软件行业中有非常成熟的&lt;a rel=&quot;external&quot; href=&quot;https://www.gnu.org/philosophy/free-sw.html&quot;&gt;自由软件运动&lt;/a&gt;, 但是和软件关系如此密切的游戏行业, 自由软件却相对少见. 当然也有一些例子. &lt;a rel=&quot;external&quot; href=&quot;https://www.openttd.org/&quot;&gt;OpenTTD&lt;/a&gt; 和 &lt;a rel=&quot;external&quot; href=&quot;https://www.freeciv.org/&quot;&gt;Freeciv&lt;/a&gt; 都是长期由社区维护的自由游戏, &lt;a rel=&quot;external&quot; href=&quot;https://godotengine.org/&quot;&gt;Godot&lt;/a&gt; 则是一款自由开源的游戏引擎. 最初作为 &amp;lt;魔兽争霸3&amp;gt; 自定义地图出现的 Dota, 在开发和传播方式上就颇为接近自由软件, 不同作者和玩家在前人工作的基础上不断修改和完善, 最终发展出了一整个 MOBA 游戏类型.&lt;/p&gt;
&lt;p&gt;游戏玩家讨论得更多的是消费者权利, 而不是软件控制权. 但从自由软件的视角看, 现代游戏确实越来越像&quot;厂商允许你访问的一项娱乐服务&quot;, 而不是&quot;你拥有的一份软件&quot;.&lt;/p&gt;
&lt;h2 id=&quot;shu-zi-ban-de-da-ji&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#shu-zi-ban-de-da-ji&quot; aria-label=&quot;Anchor link for: shu-zi-ban-de-da-ji&quot;&gt;数字版的打击&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;数字版和实体版的争论, 经常被理解为一个所有权问题: 玩家付钱之后, 得到的究竟是一件属于自己的东西, 还是一份随时可能失效的使用许可? 但实体媒介的意义并不只有所有权.&lt;/p&gt;
&lt;p&gt;一台设备和一份游戏拷贝可以被很多人使用, 随着时间推移, 原本昂贵的游戏文化也会逐渐进入收入更低的人群. 从这个角度看, 实体媒介不仅是一种所有权形式, 还是一种让低收入和数字发行条件受限人群进入游戏文化的基础设施. 数字版切断的正是这种流动. 任天堂的钥匙卡仍然可以转卖, 但是卡里没有完整的游戏数据, 第一次运行仍然依赖网络, 服务器和足够的存储空间, 保留了可以转手的外壳, 却开始削弱实体媒介独立保存和使用游戏的能力.&lt;/p&gt;
&lt;p&gt;这种变化最先影响的, 往往不是会在网上争论所有权的核心玩家, 而是那些依靠二手设备和二手游戏才能进入游戏世界的人. 他们不会购买首发游戏, 不会在短期贡献新的销量, 也很少出现在厂商调查和游戏社区里. 当实体媒介逐渐消失时, 他们不会留下退款或者差评, 只会安静地失去一种原本存在的选择. 游戏公司看到的是数字版销量比例不断上升. 但是数据无法告诉他们, 有多少人不是选择了数字版, 而是在实体媒介消失以后, 根本没有再成为玩家.&lt;/p&gt;
&lt;h2 id=&quot;wu-shi-3-yu-wo-men-de-shi-jie&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#wu-shi-3-yu-wo-men-de-shi-jie&quot; aria-label=&quot;Anchor link for: wu-shi-3-yu-wo-men-de-shi-jie&quot;&gt;&amp;lt;巫师3&amp;gt; 与我们的世界&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&amp;lt;巫师3&amp;gt; 用极大的人力物力, 构造了一个庞大而完整的虚拟世界.&lt;/p&gt;
&lt;p&gt;在威伦, 战争摧毁了村庄和农田, 普通人既要面对军队, 也要面对强盗和怪物. 在诺维格瑞, 女巫和非人种族遭到公开迫害. 贵族在举行宴会和争夺王位, 更多的人却只能在战争, 饥饿和疾病中挣扎. 杰洛特并不能改变这个世界. 很多任务也不存在完美的选择. 但是游戏仍然在这些故事中歌颂了人的尊严, 正义和公平, 批判了暴力, 不公与歧视, &lt;del&gt;甚至还讨论了花心可能带来的严重后果&lt;/del&gt;. 能够构造这样一个世界, 本身就是人类现代技术和社会协作的成果. 上千人使用计算机, 网络, 动作捕捉和数字图形技术共同工作, 最终让世界各地的玩家可以坐在屏幕前, 进入一个并不存在的大陆.&lt;/p&gt;
&lt;p&gt;然而, 这个在虚拟世界中关心贫穷和苦难的游戏, 最终仍然被现实世界的不平等包围. 并不是所有人都拥有进入这个虚拟世界的条件. 在一些地方, 玩家讨论画质, 帧数和光线追踪; 在另一些地方, 人们仍然缺少稳定的电力, 网络, 食物和医疗. 人类已经可以飞出地球, 也可以构造出 &amp;lt;巫师3&amp;gt; 这样的虚拟世界, 现实中却仍然有人被挡在现代生活之外.&lt;/p&gt;
&lt;p&gt;CDPR 已经做了相当多对玩家友好的事情. &amp;lt;巫师3&amp;gt; 登陆了几乎所有主流平台, 价格不断降低, 多次向老玩家提供免费更新, 发售十多年以后仍然继续维护. 它已经是一款商业游戏能够做到的最好例子之一. 但一家游戏公司无论多么良心, 都无法让没有电力和网络的地区完成现代化, 无法让低收入者获得体面的工作, 也无法让每天为生存奔波的人拥有闲暇. 它可以把游戏降到二三十元, 却无法把进入游戏所需要的整个世界一起降价.&lt;/p&gt;
&lt;p&gt;&amp;lt;巫师3&amp;gt; 超过 6500 万份的销量, 既证明了它的成功, 也让人看到这种成功的极限. 这个极限并不完全属于 &amp;lt;巫师3&amp;gt;, 也不完全属于 CDPR, 它属于我们生活的这个世界.&lt;/p&gt;
&lt;p&gt;&amp;lt;巫师3&amp;gt; 是个好游戏, 我们的世界也应该变得更好.&lt;/p&gt;
&lt;h2 id=&quot;can-kao-lian-jie&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#can-kao-lian-jie&quot; aria-label=&quot;Anchor link for: can-kao-lian-jie&quot;&gt;参考链接&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://necromanov.wordpress.com/2021/02/26/2077/&quot;&gt;赛博朋克2077: 当虚拟的赛博朋克被真实的赛博朋克碾过&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.cdprojekt.com/en/media/news/the-witcher-3-wild-hunt-remastered-announced-songs-of-the-past-gets-first-look/&quot;&gt;The Witcher 3: Wild Hunt — Remastered Announced, Songs of the Past Gets First Look&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.cdprojekt.com/en/media/news/the-cd-projekt-groups-wraps-up-2025/&quot;&gt;The CD PROJEKT Group wraps up 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.gamedeveloper.com/business/the-witcher-3-has-earned-almost-650-million-after-a-decade-on-shelves&quot;&gt;The Witcher 3 has earned almost $650 million after a decade on shelves&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.pocketgamer.biz/newzoo-global-games-market-set-to-hit-1888bn-in-2025/&quot;&gt;Newzoo: Global games market set to hit $188.8bn in 2025&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://partner.steamgames.com/doc/store/localization/languages&quot;&gt;Languages Supported on Steam (Steamworks Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://citizens-initiative.europa.eu/stop-destroying-videogames_en&quot;&gt;Stop Destroying Videogames (European Citizens&#39; Initiative)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.un.org/zh/about-us/universal-declaration-of-human-rights&quot;&gt;世界人权宣言&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.ohchr.org/zh/instruments-mechanisms/instruments/convention-rights-child&quot;&gt;儿童权利公约&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.gnu.org/philosophy/free-sw.html&quot;&gt;What is Free Software? - GNU Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.gamerevolution.com/news/12340-the-witcher-3-has-won-more-game-of-the-year-awards-than-any-other-game&quot;&gt;The Witcher 3 Has Won More Game of the Year Awards Than Any Other Game&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.kickstarter.com/projects/goonboard/the-witcher&quot;&gt;The Witcher: Old World by Go on Board (Kickstarter)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.cdprojekt.com/en/media/news/play-geralt-rivia-soulcalibur-vi/&quot;&gt;Play as Geralt of Rivia in SOULCALIBUR VI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.gamesradar.com/games/diablo/blizzard-and-cd-projekt-red-team-up-for-diablo-4-geralt-skin-and-the-witcher-3-on-battle-net/&quot;&gt;Blizzard and CD Projekt Red team up for Diablo 4 Geralt skin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.nintendolife.com/guides/every-nintendo-switch-2-game-key-card-release&quot;&gt;Every Nintendo Switch 2 Game-Key Card Release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.openttd.org/&quot;&gt;OpenTTD&lt;/a&gt; / &lt;a rel=&quot;external&quot; href=&quot;https://www.freeciv.org/&quot;&gt;Freeciv&lt;/a&gt; / &lt;a rel=&quot;external&quot; href=&quot;https://godotengine.org/&quot;&gt;Godot Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/The_Witcher_3:_Wild_Hunt&quot;&gt;The Witcher 3: Wild Hunt - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>昭和控股与一日元股价</title>
        <published>2026-08-22T03:27:43+09:00</published>
        <updated>2026-08-22T03:27:43+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/showa-holdings-and-the-one-yen-share-price/"/>
        <id>https://memwey.github.io/posts/showa-holdings-and-the-one-yen-share-price/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/showa-holdings-and-the-one-yen-share-price/">&lt;p&gt;昭和控股, 1937 年设立的橡胶公司, 1952 年上市, 后来改组为纯持股公司, 旗下经营软式网球球, 和果子和网球用品, 总部在千叶县柏市. 东京证券交易所已经决定于 2026 年 8 月 25 日将其退市, 股价此前跌至 1 日元, 为日元的最小单位.&lt;/p&gt;
&lt;p&gt;事情起于其下属公司在泰国的一笔投资. 2015 年起, J Trust 通过新加坡子公司向泰国上市公司 Group Lease 投入逾 1.8 亿美元. 2017 年, 泰国证券交易委员会指控时任首席执行官此下益司通过关联交易伪造账目并提出刑事告发, 此下益司因此去职. J Trust 此后就投资损失继续追偿.&lt;/p&gt;
&lt;p&gt;Group Lease 的上层股东为 Wedge Holdings, Wedge Holdings 的上层股东为昭和控股, 昭和控股的母公司则为英属维尔京群岛注册的 APF, 整条集团长期由此下益司及其弟此下龙矢掌握经营权. J Trust 在多个法域追查资产, 争夺也沿这条持股关系延伸至昭和控股.&lt;/p&gt;
&lt;p&gt;2018 年, 英属维尔京群岛法院应 J Trust 申请对 APF 指定接管人, 接管人借 APF 的持股将反此下派董事送入昭和控股董事会, 持续与此下派争夺经营权. 2026 年 6 月 29 日的定时股东大会上, 此下龙矢未能当选董事. 但在股东大会之前, 公司于 6 月 25 日公告, 因向合并子公司借款, 已将所持子公司股票提供担保; 26 日又收到担保权行使通知, 六家主要子公司随即脱离合并范围, 其中大部转由 Wedge Holdings 持有.&lt;/p&gt;
&lt;p&gt;股东大会后, 新任董事准备召开董事会, 但负责监督经营的三名社外董事全部缺席, 其中包括日本前防卫大臣久间章生. 董事会因人数不足而无法召开, 代表人也未能选出. 公司随后披露, 实印, 会计账簿, 存折及股东名簿均未完成交接, 柏市总部也已无人办公, 公司无法正常对外行使职权.&lt;/p&gt;
&lt;p&gt;7 月 24 日, 东京证券交易所认定公司的内部管理已无恢复可能, 决定于 8 月 25 日将其退市. 失去主要业务, 代表人和上市资格后, 昭和控股的股价跌至 1 日元.&lt;/p&gt;
&lt;p&gt;从结果看, 这相当于一次资产转移. 整个过程都有公司决策和公告披露, 却在数日内使新任董事接手一家没有主要业务, 没有代表人, 也无法正常运作的上市公司.&lt;/p&gt;
&lt;h2 id=&quot;can-kao-zi-liao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#can-kao-zi-liao&quot; aria-label=&quot;Anchor link for: can-kao-zi-liao&quot;&gt;参考资料&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;http://www.showa-holdings.co.jp/ir/irnews/sh20260625.html&quot;&gt;昭和ホールディングス: 連結子会社からの借入に伴う連結子会社に対する子会社株式の担保提供に関するお知らせ (2026/06/25)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;http://www.showa-holdings.co.jp/ir/irnews/sh20260626.html&quot;&gt;昭和ホールディングス: 連結子会社からの担保権行使通知書の受領、及び連結子会社の異動に関するお知らせ (2026/06/26)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;http://www.showa-holdings.co.jp/ir/irnews/sh20260622.html&quot;&gt;昭和ホールディングス: 代表取締役の異動に関するお知らせ (2026/06/22)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.nikkei.com/nkd/disclosure/tdnr/20260630584646/&quot;&gt;昭和ホールディングス[5103]: 第125回定時株主総会における代表取締役の異動（退任）と取締役選任結果に関するお知らせ (2026/06/30)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://finance.biggo.jp/news/jpx_tdnet_140120260708589710&quot;&gt;昭和ホールディングス[5103]: 代表取締役選任が出来ていないこと、経営の現況と今後の方針 (2026/07/08)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://finance.biggo.jp/news/jpx_tdnet_140120260717596231&quot;&gt;昭和ホールディングス[5103]: 更生手続開始の申立てに関するお知らせ (2026/07/17)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.jpx.co.jp/news/1023/20260724-13.html&quot;&gt;日本取引所グループ: 上場廃止等の決定 - 昭和ホールディングス（株） (2026/07/24)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.tdb.co.jp/report/bankruptcy/flash/5249/&quot;&gt;帝国データバンク 倒産速報: 昭和ホールディングス株式会社&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.tsr-net.co.jp/news/tsr/detail/1203066_1521.html&quot;&gt;東京商工リサーチ TSR速報: 昭和ホールディングス（株）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.tokyo-np.co.jp/article/502678&quot;&gt;東京新聞: 代表不在の昭和ホールディングスに保全管理命令 訴訟が長期化、投資会社が会社更生を申し立て&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.nikkei.com/article/DGXZQOUB248HI0U6A720C2000000/&quot;&gt;日本経済新聞: 昭和HD、8月25日に東証上場廃止へ 内部管理体制の改善見込めず&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://topics.smt.docomo.ne.jp/article/npn/nation/npn-300416109&quot;&gt;内外タイムス: 前代未聞 昭和HDが取締役3名欠席で代表取締役選定できず実印・帳簿・通帳が行方不明の異常事態&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://topics.smt.docomo.ne.jp/article/npn/nation/npn-300420075&quot;&gt;内外タイムス: 昭和HDに子会社株式処分禁止の仮処分命令 現経営陣が前代表の動きに反撃&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://kabutan.jp/stock/kabuka?code=5103&quot;&gt;株探: 昭和ホールディングス【5103】日々株価&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.sec.or.th/EN/Pages/News_Detail.aspx?SECID=6814&quot;&gt;Thai SEC: SEC files a criminal complaint against GL executive for fraud, misappropriation and falsification of accounts (2017/10/16)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.bangkokpost.com/thailand/general/1343327/trading-in-group-lease-shares-halted-ceo-charged&quot;&gt;Bangkok Post: Trading in Group Lease shares halted, CEO charged&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.elitigation.sg/gd/s/2018_SGCA_27&quot;&gt;Singapore Court of Appeal: Mitsuji Konoshita v Jtrust Asia Pte Ltd [2018] SGCA 27&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://f.irbank.net/pdf/20200212/140120200212462873.pdf&quot;&gt;J Trust: シンガポールにおけるJTrust Asia Pte.Ltd.によるGroup Lease関連訴訟について (2020/02/12)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.jt-corp.co.jp/jp/2020/10/06/26059/?t=jn&quot;&gt;J Trust: （開示事項の経過）当社のGroup Lease PCLに対する現状の認識について (2020/10/06)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.jt-corp.co.jp/jp/2021/08/05/27225/?t=jn&quot;&gt;J Trust: （開示事項の経過）当社のGroup Lease PCLに対する現状の認識について (2021/08/05)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://prtimes.jp/main/html/rd/p/000000039.000008352.html&quot;&gt;ウェッジホールディングス: タイ特別捜査局が、JトラストアジアによるGroup Lease PCLおよび旧取締役に対する刑事告発を棄却 (2022)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://finance.biggo.jp/news/jpx_tdnet_140120260814521236&quot;&gt;ウェッジホールディングス: 令和8年9月期 第3四半期決算短信 (2026/08/14)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://ja.wikipedia.org/wiki/%E6%98%AD%E5%92%8C%E3%83%9B%E3%83%BC%E3%83%AB%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E3%82%B9&quot;&gt;昭和ホールディングス - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>相乐总三与年贡减半</title>
        <published>2026-08-21T16:30:22+09:00</published>
        <updated>2026-08-21T16:30:22+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/sagara-sozo-and-the-halved-land-tax/"/>
        <id>https://memwey.github.io/posts/sagara-sozo-and-the-halved-land-tax/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/sagara-sozo-and-the-halved-land-tax/">&lt;p&gt;相乐总三, 倒幕派浪人, 1867 年奉西乡隆盛指示, 以萨摩藩江户藩邸为据点组织浪士, 在江户及周边从事纵火抢劫等骚乱活动, 意在挑衅幕府激化冲突. 最终幕府一方袭击并焚毁了萨摩藩邸作为报复, 成为鸟羽伏见开战的重要导火索.&lt;/p&gt;
&lt;p&gt;鸟羽伏见之战后, 相乐总三参与组建赤报队, 并率第一番队作为先锋沿东山道东进. 他向新政府提出对旧幕府领年贡减半的政策并得到采纳, 赤报队遂以此争取沿途农民支持. 然而倒幕派的新政府很快发现, 东征比预想中顺利, 获取农民支持的必要性大大降低, 在财政上年贡减半也难以兑现, 遂撤销政策并召回赤报队. 相乐的赤报队第一番队仍继续东进, 最终被认定为伪官军. 1868 年 3 月, 相乐等八名赤报队成员在下诹访被处刑, 首级示众.&lt;/p&gt;
&lt;p&gt;1928 年, 在后人数十年奔走之下, 相乐总三被追赠正五位, 次年合祀靖国神社.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>加入创业公司之前, 工程师能查些什么</title>
        <published>2026-07-30T13:57:17+09:00</published>
        <updated>2026-07-30T13:57:17+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/startup-due-diligence-for-engineers/"/>
        <id>https://memwey.github.io/posts/startup-due-diligence-for-engineers/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/startup-due-diligence-for-engineers/">&lt;p&gt;找工作的时候, 关于一家创业公司能查到的东西其实不少: 融了多少钱, 投资人是谁, 创始人从哪里毕业, 又在采访里讲了一个多大的市场.&lt;/p&gt;
&lt;p&gt;但真正想知道的是另外几件事:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;进去以后要写什么系统, 需要补多少领域知识&lt;/li&gt;
&lt;li&gt;招聘页面上的技术栈, 是线上已经在用, 还是公司希望以后能用&lt;/li&gt;
&lt;li&gt;公司说自己在做的业务, 有没有可验证的痕迹; 尤其在金融领域, 该有的牌照在监管登记里查得到吗&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;创业公司数据库擅长把融资轮次排成一张表, 却不会告诉你一个 &lt;code&gt;Kubernetes preferred&lt;/code&gt; 能不能证明生产环境里有 Kubernetes.&lt;/p&gt;
&lt;p&gt;于是攒出了 &lt;a rel=&quot;external&quot; href=&quot;https://github.com/memwey/startup-due-diligence&quot;&gt;startup-due-diligence&lt;/a&gt; 这个仓库: 截至 2026-07-30, 收录 12 家公司, 覆盖日本, 北美, 澳大利亚和跨境市场, 中英双语, 每家一份带来源的档案. 它不判断一家公司值不值得投, 只整理准备加入的工程师需要的公开证据.&lt;/p&gt;
&lt;h2 id=&quot;rong-zi-biao-zhi-wai&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#rong-zi-biao-zhi-wai&quot; aria-label=&quot;Anchor link for: rong-zi-biao-zhi-wai&quot;&gt;融资表之外&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;投资人和工程师会关心一些相同的东西: 有没有收入, 钱还能烧多久, 产品是不是真的有人用. 这些最后都会变成工资能不能按时发出来.&lt;/p&gt;
&lt;p&gt;但工程师还要面对业务施加在系统上的约束. 物流 SaaS 不只是普通的增删改查, 后面还有报关, 货代, 船期, 单据和跨国协作; 支付系统的技术栈即使看起来平平无奇, 背后也可能是多方清算, 商户资金, PCI DSS 和一堆永远不能重复执行的状态机, 而业务能不能上线, 还取决于究竟由哪个法律实体或持牌合作方承担受监管的环节.&lt;/p&gt;
&lt;p&gt;官网通常会说产品解决了什么问题, 很少说为了解决它, 工程师每天要和哪些约束打架. 这部分信息散落在产品文档, API 参考, 招聘页面, 技术演讲, 法律条款和监管登记里. 单独看每一份都不像答案, 拼起来才慢慢像.&lt;/p&gt;
&lt;h2 id=&quot;zhe-lei-dang-an-ben-lai-jiu-gai-rang-ai-zuo&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhe-lei-dang-an-ben-lai-jiu-gai-rang-ai-zuo&quot; aria-label=&quot;Anchor link for: zhe-lei-dang-an-ben-lai-jiu-gai-rang-ai-zuo&quot;&gt;这类档案本来就该让 AI 做&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;12 家公司的档案, 主要工作是模型完成的, 而这类活本来就该这么分: 面宽, 每一处都不深, 高度重复, 还要跨语言对齐格式. 找页面, 翻译材料, 抽取数字, 比较版本, 从 sitemap 里定位产品文档 - 模型比人快得多, 也不会因为重复和无聊主动罢工. 至于会不会用另一种方式糊弄, 是下一回事.&lt;/p&gt;
&lt;p&gt;不能交出去的是另一件事: 它同样擅长把缺失的连接补成一句读起来合理的话.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;旧品牌和新品牌共享创始人与域名时, 它倾向于写&quot;公司完成了更名&quot;&lt;/li&gt;
&lt;li&gt;职位描述里出现 Kubernetes, 它倾向于把 Kubernetes 填进技术栈&lt;/li&gt;
&lt;li&gt;三家媒体转载同一份新闻稿, 它可能算成三份相互独立的证据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些错误通常不是事实编错了, 而是&lt;strong&gt;证据只走到 A, 结论多走了一步到 B&lt;/strong&gt;. 而且同一个问题问两次, 多走的那一步还可能不一样.&lt;/p&gt;
&lt;p&gt;所以这个项目真正需要维护的不是 prompt, 而是一份协议: 什么来源能证明什么, 日期指的是发布日还是统计日, 招聘要求能支持到哪一层, 来源冲突该保留还是淘汰. 协议既是给人复核的规范, 也是给模型套上的约束.&lt;/p&gt;
&lt;h2 id=&quot;zhong-dian-shi-fang-fa-ke-fu-he-bu-shi-dang-an-ben-shen&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhong-dian-shi-fang-fa-ke-fu-he-bu-shi-dang-an-ben-shen&quot; aria-label=&quot;Anchor link for: zhong-dian-shi-fang-fa-ke-fu-he-bu-shi-dang-an-ben-shen&quot;&gt;重点是方法可复核, 不是档案本身&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;档案一定会过时. 融资数字会变, 网站会改版, 职位会下线, 半年之后每一页都需要重做. 能留下来的是&lt;a rel=&quot;external&quot; href=&quot;https://github.com/memwey/startup-due-diligence/blob/main/GUIDE.md&quot;&gt;那份协议&lt;/a&gt;, 而它的验收标准很直接: 换一个人, 或者换一次对话, 能不能沿着同一套规则重走关键步骤, 并指出分歧发生在哪一步.&lt;/p&gt;
&lt;p&gt;协议本身写了不少细则, 但真正在起作用的原则只有三条.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;该看什么来源, 由主张的类型决定, 不由来源看起来权不权威.&lt;/strong&gt; 产品能力看官方文档和 API 参考, 技术栈看公开资源或招聘信息, 而合规资质只有监管机构的登记算数 - 比如一家跨境支付公司宣称持有某个牌照, 在对应的监管登记里却查不到, 那就诚实地写&quot;没查到&quot;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;每个事实都要带上有效期和证据强度.&lt;/strong&gt; 日期负责有效期, &quot;覆盖 194 MW&quot;和&quot;截至 2026-07-27 覆盖 194 MW&quot;是两条不同的信息, 前者半年后就是错的; 标注负责强度, 从招聘信息推出来的技术栈写成&quot;根据招聘信息推断&quot;, 读者可以自己决定接不接受这一步.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不确定性留在页面上, 不要顺手消化掉.&lt;/strong&gt; 以 &lt;a rel=&quot;external&quot; href=&quot;https://github.com/memwey/startup-due-diligence/tree/main/companies/shippio&quot;&gt;Shippio&lt;/a&gt; 为例, 资本数字在不同页面上出现过五个版本, 口径不一, 页面没有更新日期, 数字也不沿时间单调增加. 最省事的做法是挑一个看起来最新的填进表格, 但那只是把&quot;我不知道&quot;换成了一个看起来很确定的数字. 所以五个版本并列写着, 连&quot;为什么现在不能直接比较&quot;一起写.&lt;/p&gt;
&lt;p&gt;三条指向的是同一件事: 让别人能反驳你. 可复核不要求结论一致, 而是每一步都能被单独拿出来检查, 也能被单独推翻一步, 不用整篇重做.&lt;/p&gt;
&lt;h2 id=&quot;zhao-pin-yao-qiu-bu-deng-yu-sheng-chan-huan-jing&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhao-pin-yao-qiu-bu-deng-yu-sheng-chan-huan-jing&quot; aria-label=&quot;Anchor link for: zhao-pin-yao-qiu-bu-deng-yu-sheng-chan-huan-jing&quot;&gt;招聘要求不等于生产环境&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;招聘页面是这类调研里最有用, 也最容易被误用的来源.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/memwey/startup-due-diligence/tree/main/companies/tensor-energy&quot;&gt;Tensor Energy&lt;/a&gt; 是个清楚的例子. 公开文档能确认的生产系统包括: AWS IoT Core 上的电池遥测与控制, 基于 TLS 的 MQTT, 用于资产与预测数据的 REST API, 以及围绕日本电力市场交易时点, 至少每 30 分钟重算一次的电池优化.&lt;/p&gt;
&lt;p&gt;它的后端职位要求 Go 的生产经验, 同时把 GraphQL, Kubernetes, DevOps, AWS CDK 列为优先而非必需. 其中 AWS CDK 和 IoT 基础设施能在公开文档里交叉确认, GraphQL 和 Kubernetes 则只有招聘信号.&lt;/p&gt;
&lt;p&gt;把招聘页面丢给一个总结工具, 最容易得到的是:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;技术栈: Go, GraphQL, Kubernetes, AWS CDK.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;读起来很顺, 但它悄悄把&quot;希望候选人做过&quot;升级成了&quot;公司正在使用&quot;. 更稳妥的写法要拆开: 文档确认了哪些生产系统, 招聘明确要求哪些技术, 哪些只是优先项, 哪些属于候选人的过往经验而不是当前技术栈.&lt;/p&gt;
&lt;p&gt;对工程师来说, &quot;进去维护 Kubernetes 集群&quot;和&quot;公司希望你有 Kubernetes 经验, 但公开材料描述的主要是 serverless 架构&quot;, 是两份不同的工作.&lt;/p&gt;
&lt;h2 id=&quot;mei-zhao-dao-ye-shi-yi-chong-jie-guo&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#mei-zhao-dao-ye-shi-yi-chong-jie-guo&quot; aria-label=&quot;Anchor link for: mei-zhao-dao-ye-shi-yi-chong-jie-guo&quot;&gt;&quot;没找到&quot;也是一种结果&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&quot;这家公司没有技术博客&quot;是一句关于世界的断言.&lt;/p&gt;
&lt;p&gt;&quot;截至某日, 在官网导航, sitemap, 招聘页面, GitHub 组织以及公司中英文名称的公开搜索结果中没有找到技术博客&quot;是一句关于调查过程的记录.&lt;/p&gt;
&lt;p&gt;前一句很可能是错的: 博客也许在一个没被官网链接的子域名, 也许改过名字, 也许搜索引擎还没收录. 后一句至少可以复核 - 别人知道你在哪里找过, 什么时候找过, 可以从你停下的地方接着走.&lt;/p&gt;
&lt;p&gt;所以档案里的 &lt;code&gt;Not publicly disclosed&lt;/code&gt; 不是空白字段, 而是带日期和范围的搜索结果.&lt;/p&gt;
&lt;p&gt;很多调查看起来像是在找答案, 实际上是在给&quot;我不知道&quot;画一条准确的边界.&lt;/p&gt;
&lt;h2 id=&quot;zhe-tao-fang-fa-de-ju-xian&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhe-tao-fang-fa-de-ju-xian&quot; aria-label=&quot;Anchor link for: zhe-tao-fang-fa-de-ju-xian&quot;&gt;这套方法的局限&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这套方法依赖公司愿意在公开网络上留下东西. 非常早期的团队可能没有产品文档, 招聘页面和正式新闻稿, 官网只有一句 slogan 和一个联系邮箱. 公开信息少不等于公司差, 只是以公司为中心的调查到这里就走不动了.&lt;/p&gt;
&lt;p&gt;公司越早期, 调查重心就越需要从公司转向创始人. 创始人的公开职业账号, 访谈, 播客和活动简介, 可能是产品进度, 招聘计划和第一批客户仅有的当前来源, 也能提供继续向注册信息和后续公告交叉确认的线索.&lt;/p&gt;
&lt;p&gt;但调查对象变了, 证据标准不能跟着放松. 创始人的帖子能证明他在某天说过什么, 不能证明那件事已经发生; &quot;计划&quot;和&quot;正在开发&quot;也不能变成现有能力. 早期公司的档案因此可能更短, 更多地方写着&quot;尚未确认&quot;, 这是这套方法必须接受的边界.&lt;/p&gt;
&lt;h2 id=&quot;wei-shen-me-zui-hou-shi-yi-dui-markdown&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#wei-shen-me-zui-hou-shi-yi-dui-markdown&quot; aria-label=&quot;Anchor link for: wei-shen-me-zui-hou-shi-yi-dui-markdown&quot;&gt;为什么最后是一堆 Markdown&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;没有数据库, 没有前端, 每家公司只是一个目录:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;companies/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  company-name/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    README.md&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    README.zh-CN.md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Markdown 的好处就是足够低级: 每个结论旁边可以直接放链接, Git 能看见某个数字和某句措辞是在哪一次修改里变的, 公司改名也不需要迁移数据库. 打开 GitHub 就能读, clone 下来就能搜, 想纠错就是一个 diff. 对&quot;可复核&quot;来说, 一份能被 diff 的纯文本比一个漂亮的界面有用得多.&lt;/p&gt;
&lt;p&gt;英文页面是事实结构的主版本, 中文页面在相同章节下同步. 两份文档会慢慢漂移: 英文改了融资数字, 中文还停在上一版; 中文少复制一个来源链接, 表面看不出来, 意思已经变了. 所以仓库里有一个很小的&lt;a rel=&quot;external&quot; href=&quot;https://github.com/memwey/startup-due-diligence/blob/main/scripts/verify_translations.py&quot;&gt;校验脚本&lt;/a&gt;, 检查中英页面是否成对, 更新日期是否一致, 标题层级与表格行数是否相同, 来源链接集合是否一致, 本地链接是否还能打开.&lt;/p&gt;
&lt;p&gt;它不能证明翻译正确, 就像 checksum 不能证明两份错误的数据是对的, 但能抓住维护中最容易悄悄发生的漂移. &lt;del&gt;写个 README, 最后还是写出了数据一致性检查&lt;/del&gt;.&lt;/p&gt;
&lt;h2 id=&quot;hou-ji&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#hou-ji&quot; aria-label=&quot;Anchor link for: hou-ji&quot;&gt;后记&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这个项目不会告诉你哪家公司值得加入. 工作内容是否喜欢, 风险是否能接受, 创始人是否值得信任, 团队相处是否舒服, 这些只能在面试和实际接触中判断; 公开资料做不到, 硬做只会把推测包装成结论.&lt;/p&gt;
&lt;p&gt;它能做的是把&quot;这家公司听起来还不错&quot;拆成三样东西: 有来源和日期的事实, 尚未解决的冲突与空白, 以及下一次面试应该当面问的问题. 至少可以问出比&quot;你们技术栈是什么&quot;更具体的问题.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;仓库里的内容均基于公开信息独立整理, 与相关公司没有任何关系, 也可能不完整或已经过时. 涉及就业或投资决策时, 请直接向公司核实.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;can-kao-lian-jie&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#can-kao-lian-jie&quot; aria-label=&quot;Anchor link for: can-kao-lian-jie&quot;&gt;参考链接&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/memwey/startup-due-diligence&quot;&gt;startup-due-diligence&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/memwey/startup-due-diligence/blob/main/GUIDE.md&quot;&gt;Research guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/memwey/startup-due-diligence/tree/main/companies&quot;&gt;Company research index&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/memwey/startup-due-diligence/tree/main/companies/tensor-energy&quot;&gt;Tensor Energy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/memwey/startup-due-diligence/tree/main/companies/shippio&quot;&gt;Shippio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/memwey/startup-due-diligence/blob/main/scripts/verify_translations.py&quot;&gt;Translation verification script&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>日语台词 - 003</title>
        <published>2026-07-27T14:35:12+09:00</published>
        <updated>2026-07-27T14:35:12+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/japanese-serifu-003/"/>
        <id>https://memwey.github.io/posts/japanese-serifu-003/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/japanese-serifu-003/">&lt;hr /&gt;
&lt;p&gt;なんでそんなに慣(な)れてんだよっ！雪菜(せつな)と…何回(なんかい)キスしたんだよ！？どこまであたしを置(お)いてきぼりにすれば気(き)が済(す)むんだよ！？&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;あたしが、先(さき)だった……先(さき)だったんだ……キスしたのも、抱(だ)き合(あ)ったのも。そいつのこと好(す)きになったのも。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;どうしてこうなるんだろう…初(はじ)めて、好(す)きな人(ひと)が出来(でき)た。一生(いっしょう)ものの友(とも)だちができた。嬉(うれ)しいことが二(ふた)つ重(かさ)なって。その二(ふた)つの嬉(うれ)しさが、また、たくさんの嬉(うれ)しさを連(つ)れてきてくれて。夢(ゆめ)のように幸(しあわ)せな時間(じかん)を手(て)に入(い)れたはずなのに…なのに、どうして、こうなっちゃうんだろう…&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>日语台词 - 002</title>
        <published>2026-07-02T12:40:19+09:00</published>
        <updated>2026-07-02T12:40:19+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/japanese-serifu-002/"/>
        <id>https://memwey.github.io/posts/japanese-serifu-002/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/japanese-serifu-002/">&lt;hr /&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;hr /&gt;
&lt;p&gt;僕(ぼく)は君(きみ)に会(あ)うために生(う)まれて来(き)たんだね&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;逃(に)げちゃダメだ&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;まごころを、君(きみ)に&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;世界(せかい)の中心(ちゅうしん)でアイを叫(さけ)んだけもの&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;「綾波(あやなみ)、父(とう)さんのこと、ありがとう」&lt;/p&gt;
&lt;p&gt;「ごめんなさい、何(なに)も出来(でき)なかった」&lt;/p&gt;
&lt;p&gt;「いいんだ、もう、これでいいんだ」&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;さようなら、全(すべ)てのエヴァンゲリオン&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;「見(み)たことも聞(き)いたこともないのに、出来(でき)るわけないよ！」&lt;/p&gt;
&lt;p&gt;「乗(の)るなら早(はや)くしろ！でなければ帰(かえ)れ！」&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;知(し)らない天井(てんじょう)だ&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;人(ひと)は他人(たにん)を完全(かんぜん)に理解(りかい)することは出来(でき)ない。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;大人(おとな)になるってことは、近(ちか)づいたり離(はな)れたりを繰(く)り返(かえ)して、お互(たが)いがあまり傷(きず)つかずにすむ距離(きょり)を見(み)つけ出(だ)すってこと。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;つらいことを知(し)ってる人間(にんげん)のほうが、それだけ人(ひと)にやさしくできる。それは弱(よわ)さと違(ちが)うからな。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;この世界(せかい)はあなたの知(し)らない面白(おもしろ)い事(こと)で、満(み)ち満(み)ちているわよ。楽(たの)しみなさい。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;今度(こんど)こそ君(きみ)だけは幸(しあわ)せにしてみせるよ&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>日语台词 - 001</title>
        <published>2026-07-02T11:52:30+09:00</published>
        <updated>2026-07-02T11:52:30+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/japanese-serifu-001/"/>
        <id>https://memwey.github.io/posts/japanese-serifu-001/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/japanese-serifu-001/">&lt;hr /&gt;
&lt;p&gt;ララァ・スンは、私(わたし)の母(はは)になってくれるかもしれなかった女性(じょせい)だ。そのララァを殺(ころ)したお前(まえ)に言(い)えた事(こと)か！&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;抵抗(ていこう)すると無駄死(むだじ)にするだけだって、何故(なぜ)分(わ)からないんだ！？&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;人(ひと)は同(おな)じ過(あやま)ちを繰(く)り返(かえ)す...&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;親父(おやじ)にもぶたれたことないのに！！！&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;それが甘(あま)ったれなんだ。殴(なぐ)られもせずに一人前(いちにんまえ)になった奴(やつ)がどこにいるものか！&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;あんなの飾(かざ)りです！偉(えら)い人(ひと)にはそれがわからんのですよ！&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;僕(ぼく)は…取(と)り返(かえ)しのつかないことをしてしまった。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;認(みと)めたくないものだな、自分自身(じぶんじしん)の若(わか)さゆえの過(あやま)ちというものを。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;当(あ)たらなければどうということはない。援護(えんご)しろ。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;たかが石(いし)ころ一(ひと)つ、ガンダムで押(お)し出(だ)してやる！&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>在 OpenWrt 上配置 Tailscale 服务</title>
        <published>2026-02-24T14:32:01+09:00</published>
        <updated>2026-02-25T20:33:08+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/openwrt-and-tailscale/"/>
        <id>https://memwey.github.io/posts/openwrt-and-tailscale/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/openwrt-and-tailscale/">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://tailscale.com/&quot;&gt;Tailscale&lt;/a&gt; 是一个基于 WireGuard 协议的组网服务, 它可以实现跨平台的内网互联, 作为一个 Golang 编写的开源软件, 它提供了较为慷慨的免费服务如 DERP 等, 同时也为有更多需求的用户提供付费服务.&lt;/p&gt;
&lt;p&gt;我的使用环境比较特殊, 家在佛山, 而我平时在东京租房上班, 使用 Tailscale 实现东京与佛山的设备互联, 方便通信和管理. 同时, 也可以实现一些需要特定网络环境的场景, 比如区域授权流媒体或者一些需要在特定网络环境下运行的应用.&lt;/p&gt;
&lt;p&gt;在东京实际搭建的时候, 发现东京的主路由 K2P 性能实在孱弱, 一开始 ROM 不足以安装 Tailscale, 精简固件后勉强安装, 又 RAM 不足, 反复发生 OOM 的情况, 最终把一台一直吃灰的斐讯 N1 找了出来安装 Tailscale, 作为旁路路由处理 Tailscale 的流量. 最终形成了一个稍微复杂的网络结构, 佛山的主路由作为 Tailscale 网络的入口, 而东京的 N1 作为旁路路由, 处理 Tailscale 网络中的流量. 不过最终效果还是一样的, 即两地内网直接互访, 且均提供网络出口.&lt;/p&gt;
&lt;h2 id=&quot;wang-luo-jie-gou&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#wang-luo-jie-gou&quot; aria-label=&quot;Anchor link for: wang-luo-jie-gou&quot;&gt;网络结构&lt;/a&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;th&gt;设备系统&lt;/th&gt;&lt;th&gt;Tailscale 节点用途&lt;/th&gt;&lt;th&gt;内网网段&lt;/th&gt;&lt;th&gt;内网IP&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;主路由&lt;/td&gt;&lt;td&gt;OpenWrt 24.10&lt;/td&gt;&lt;td&gt;内网入口, 提供局域网穿透&lt;/td&gt;&lt;td&gt;192.168.128.0/24&lt;/td&gt;&lt;td&gt;192.168.128.1&lt;/td&gt;&lt;td&gt;歌华链 GHL-R-001&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;东京&lt;/td&gt;&lt;td&gt;主路由&lt;/td&gt;&lt;td&gt;OpenWrt 24.10&lt;/td&gt;&lt;td&gt;无&lt;/td&gt;&lt;td&gt;192.168.9.0/24&lt;/td&gt;&lt;td&gt;192.168.9.1&lt;/td&gt;&lt;td&gt;斐讯 K2P&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;东京&lt;/td&gt;&lt;td&gt;旁路路由&lt;/td&gt;&lt;td&gt;Armbian 26.2.1 trixie&lt;/td&gt;&lt;td&gt;提供局域网穿透&lt;/td&gt;&lt;td&gt;192.168.9.0/24&lt;/td&gt;&lt;td&gt;192.168.9.235&lt;/td&gt;&lt;td&gt;斐讯 N1&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;当前使用的是 1.94.2 版本的 Tailscale 客户端. 网络结构中内网网段不应该重叠, 不然会导致冲突.&lt;/p&gt;
&lt;p&gt;最终实现的网络拓扑是这样的:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    终端设备 (PC/手机/NAS)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;               │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      东京 LAN (192.168.9.0/24)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;               │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ┌──────────┴──────────┐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │                     │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  OpenWrt (192.168.9.1) ──┼─── [静态路由] ─── Armbian + Tailscale (192.168.9.235)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │                     │                   ( 处理去往 192.168.128.0/24 的流量 )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │                     └───────────────────┘&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    └─── 公网 (Internet) ─── Tailscale 网络 (DERP/P2P)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;         OpenWrt + Tailscale (192.168.128.1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                │&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;         佛山 LAN (192.168.128.0/24)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在东京侧, 所有 LAN 设备的默认网关仍是 192.168.9.1, 通过静态路由, 去往佛山的流量会被中转给 192.168.9.235, 最终通过 Tailscale 网络到达佛山的设备.&lt;/p&gt;
&lt;h2 id=&quot;zai-openwrt-shang-an-zhuang-tailscale-ji-web-guan-li-jie-mian&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zai-openwrt-shang-an-zhuang-tailscale-ji-web-guan-li-jie-mian&quot; aria-label=&quot;Anchor link for: zai-openwrt-shang-an-zhuang-tailscale-ji-web-guan-li-jie-mian&quot;&gt;在 OpenWrt 上安装 Tailscale 及 Web 管理界面&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OpenWrt 使用 opkg 作为包管理工具, 官方自带的软件源中 Tailscale 长期没有更新了, 所以我们需要从第三方软件源中安装 Tailscale. 同时这个 Tailscale 经过裁剪和优化, 更适合路由器这种内存和闪存资源有限的设备. 并且我们增加 Tailscale 的 LuCI Web 管理界面, 方便管理和配置.&lt;/p&gt;
&lt;p&gt;下面提供的是命令行的过程, 主要是添加证书需要到设备的命令行中执行, 另外两项操作添加软件源和安装其实也可以在 LuCI Web 管理界面中完成. 这里需要注意, 添加第三方证书有一定的风险, 可能会有供应链攻击的风险.&lt;/p&gt;
&lt;h3 id=&quot;an-zhuang-tailscale-fu-wu&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#an-zhuang-tailscale-fu-wu&quot; aria-label=&quot;Anchor link for: an-zhuang-tailscale-fu-wu&quot;&gt;安装 Tailscale 服务&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;添加证书&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;wget -O /tmp/key-build.pub https://gunanovo.github.io/openwrt-tailscale/key-build.pub &amp;amp;&amp;amp; opkg-key add /tmp/key-build.pub&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;添加软件源&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;echo &amp;quot;src/gz openwrt-tailscale https://gunanovo.github.io/openwrt-tailscale&amp;quot; &amp;gt;&amp;gt; /etc/opkg/customfeeds.conf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;opkg update &amp;amp;&amp;amp; opkg install tailscale&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;an-zhuang-tailscale-luci-web-guan-li-jie-mian&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#an-zhuang-tailscale-luci-web-guan-li-jie-mian&quot; aria-label=&quot;Anchor link for: an-zhuang-tailscale-luci-web-guan-li-jie-mian&quot;&gt;安装 Tailscale LuCI Web 管理界面&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;添加证书&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;wget https://Tokisaki-Galaxy.github.io/luci-app-tailscale-community/all/key-build.pub -O /tmp/key-build.pub &amp;amp;&amp;amp; opkg-key add /tmp/key-build.pub&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;添加软件源&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;echo &amp;quot;src/gz tailscale_community https://Tokisaki-Galaxy.github.io/luci-app-tailscale-community/all&amp;quot; &amp;gt;&amp;gt; /etc/opkg/customfeeds.conf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;opkg update &amp;amp;&amp;amp; opkg install luci-app-tailscale-community&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;pei-zhi-openwrt-shang-de-tailscale-ke-hu-duan&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#pei-zhi-openwrt-shang-de-tailscale-ke-hu-duan&quot; aria-label=&quot;Anchor link for: pei-zhi-openwrt-shang-de-tailscale-ke-hu-duan&quot;&gt;配置 OpenWrt 上的 Tailscale 客户端&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;此时应该可以在 LuCI Web 管理界面中看到 服务 -&amp;gt; Tailscale 选项, 点击进入配置页面. 在设置中点击登录, 会跳转到 Tailscale 网站进行登陆授权, 把这台设备添加到 Tailscale 网络中.&lt;/p&gt;
&lt;p&gt;回到路由器的 LuCI Web 管理界面的 Tailscale 选项卡中, 需要打开 &lt;code&gt;接受路由&lt;/code&gt;, &lt;code&gt;通告出口节点&lt;/code&gt; 这两个选项. &lt;code&gt;接受路由&lt;/code&gt; 用于接收 Tailscale 网络中的子网路由配置, 以实现不同内网的直接通信. 而 &lt;code&gt;通告出口节点&lt;/code&gt; 则可以让 Tailscale 网络中别的设备选择通过这台设备代理进行网络访问, 即这台设备可以作为代理出口.&lt;/p&gt;
&lt;p&gt;随后需要填写 &lt;code&gt;通告路由&lt;/code&gt;, 填写的值应该是主路由对应的内网网段, 比如我这里, 佛山的 GHL-R-001 内网网段是 192.168.128.0/24, 东京的 K2P 内网网段是 192.168.9.0/24.&lt;/p&gt;
&lt;h2 id=&quot;pei-zhi-armbian-shang-de-tailscale-ke-hu-duan&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#pei-zhi-armbian-shang-de-tailscale-ke-hu-duan&quot; aria-label=&quot;Anchor link for: pei-zhi-armbian-shang-de-tailscale-ke-hu-duan&quot;&gt;配置 Armbian 上的 Tailscale 客户端&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在 Armbian 上安装 Tailscale 客户端的过程与 OpenWrt 类似, 只是需要在 Armbian 上需要使用命令行操作. 最终运行的命令如下&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;tailscale up --accept-routes=true --accept-dns=true --advertise-routes=192.168.9.0/24 --advertise-exit-node&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;pei-zhi-armbian-de-zhuan-fa&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#pei-zhi-armbian-de-zhuan-fa&quot; aria-label=&quot;Anchor link for: pei-zhi-armbian-de-zhuan-fa&quot;&gt;配置 Armbian 的转发&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;确认 ipv4, ipv6 转发都已开启, 即值为 1&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;echo &amp;#39;net.ipv4.ip_forward = 1&amp;#39; | sudo tee -a /etc/sysctl.conf&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;echo &amp;#39;net.ipv6.conf.all.forwarding = 1&amp;#39; | sudo tee -a /etc/sysctl.conf&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo sysctl -p /etc/sysctl.conf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置防火墙规则, 确保 Tailscale 网络中的流量可以正确转发&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;iptables -t mangle -A FORWARD -o tailscale0 -p tcp -m tcp \&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;--tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并且固化以防止重启丢失&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo netfilter-persistent save&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;zai-zhu-lu-you-shang-pei-zhi-jing-tai-lu-you&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zai-zhu-lu-you-shang-pei-zhi-jing-tai-lu-you&quot; aria-label=&quot;Anchor link for: zai-zhu-lu-you-shang-pei-zhi-jing-tai-lu-you&quot;&gt;在主路由上配置静态路由&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在东京主路由 K2P 上, 需要配置旁路网关的静态路由, 以确保流量可以正确路由到 Tailscale 网络中的设备. 具体来说, 需要在主路由上添加一条路由规则, &lt;strong&gt;到 192.168.128.0/24 的路由，下一跳为 192.168.9.235&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;pei-zhi-tailscale-fu-wu-guan-li&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#pei-zhi-tailscale-fu-wu-guan-li&quot; aria-label=&quot;Anchor link for: pei-zhi-tailscale-fu-wu-guan-li&quot;&gt;配置 Tailscale 服务管理&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;登陆 &lt;a rel=&quot;external&quot; href=&quot;https://login.tailscale.com/admin/machines&quot;&gt;Tailscale 管理页面&lt;/a&gt;, 可以看到设备已经添加到 Tailscale 网络中了. 还需要在这个页面的设备管理上, 点击 &lt;code&gt;Edit route settings...&lt;/code&gt;, 勾选对应的 &lt;code&gt;Subnet routes&lt;/code&gt; 和 &lt;code&gt;Exit node&lt;/code&gt; 选项. 不同设备都需要操作. 同时如果设备长期在线且物理安全可控, 也建议选择 &lt;code&gt;Disable key expiry&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;这里可能会有人纠结为什么要配置两次. 其实是因为 Tailscale 网络的 P2P 零信任的特性, 将节点状态的宣告和承认分开了. 宣告是节点将自己的路由信息告诉 Tailscale 网络, 而承认是 Tailscale 网络承认这个信息, 然后将这个节点宣告的路由信息通知给网络中的其他节点, 所以需要在节点和 Tailscale 管理页面上都配置一次.&lt;/p&gt;
&lt;h2 id=&quot;pei-zhi-openwrt-shang-de-fang-huo-qiang&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#pei-zhi-openwrt-shang-de-fang-huo-qiang&quot; aria-label=&quot;Anchor link for: pei-zhi-openwrt-shang-de-fang-huo-qiang&quot;&gt;配置 OpenWrt 上的防火墙&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这里可以在 LuCI Web 管理界面中看到 服务 -&amp;gt; Tailscale 选项卡中, 点击 &lt;code&gt;自动配置防火墙&lt;/code&gt;, 会添加 lan =&amp;gt; tailscale 和 tailscale =&amp;gt; lan 这些区域转发规则; 然而由于上面我们配置了 &lt;code&gt;通告出口节点&lt;/code&gt;, 所以还需要添加以下规则&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;uci add firewall forwarding&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;uci set firewall.@forwarding[-1].src=&amp;#39;tailscale&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;uci set firewall.@forwarding[-1].dest=&amp;#39;wan&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;uci commit firewall&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;/etc/init.d/firewall restart&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样允许流量从 Tailscale 网络中通过节点访问公网, 实现流量出口代理.&lt;/p&gt;
&lt;h2 id=&quot;qi-ta-zhu-yi-shi-xiang&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#qi-ta-zhu-yi-shi-xiang&quot; aria-label=&quot;Anchor link for: qi-ta-zhu-yi-shi-xiang&quot;&gt;其他注意事项&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;通常需要在 OpenWrt 上开启 IP forwarding, MASQUERADE 和 MSS 钳制, 以确保 Tailscale 网络中的流量可以正常转发和路由. 一般来说 Tailscale 客户端和 &lt;code&gt;自动配置防火墙&lt;/code&gt; 会自动处理这些配置. 网络异常时可以检查这些配置.&lt;/li&gt;
&lt;li&gt;内网网段如果冲突了, 可以在 OpenWrt 上修改对应的 DHCP 配置.&lt;/li&gt;
&lt;li&gt;由于调整了较多的网络环境, 比如防火墙, 路由等, 就不建议再套用其他的网络应用, 比如去广告等功能, 可能会导致冲突.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>AI: 创新即复古</title>
        <published>2026-01-11T22:47:18+09:00</published>
        <updated>2026-07-30T12:41:51+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/ai-innovation-is-retro/"/>
        <id>https://memwey.github.io/posts/ai-innovation-is-retro/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/ai-innovation-is-retro/">&lt;p&gt;在 LLM 相关的东西上折腾了一段时间之后, 我最强烈的一个感受不是&quot;未来来了&quot;, 而是&quot;这个我好像在哪见过&quot;. 每隔一阵子就有一个听起来很新的名词冒出来, 翻开报文一看, 里面躺着的是一个三十年前的东西.&lt;/p&gt;
&lt;p&gt;下面三个例子, 是我记在备忘录里的三条段子.&lt;/p&gt;
&lt;h2 id=&quot;sse-na-wo-zou&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#sse-na-wo-zou&quot; aria-label=&quot;Anchor link for: sse-na-wo-zou&quot;&gt;SSE: 那我走?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Server-Sent Events&lt;/code&gt; 是 2000 年代中期的产物, 本质是在一个迟迟不结束的 HTTP 响应里持续发送文本事件. HTTP/1.1 下常见的实现是 &lt;code&gt;chunked transfer encoding&lt;/code&gt; (1997), 到了 HTTP/2 和 HTTP/3 则连 chunked 这层都没有了. 格式简单到有点寒酸:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;data: {&amp;quot;choices&amp;quot;:[{&amp;quot;delta&amp;quot;:{&amp;quot;content&amp;quot;:&amp;quot;你&amp;quot;}}]}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;data: [DONE]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;WebSocket&lt;/code&gt; 出现之后, SSE 基本被归入&quot;过渡方案&quot;一类: 只能服务端往客户端单向推, 前端教程里通常一笔带过, 还附带一条广为流传的限制 - HTTP/1.1 下同域名最多 6 个连接, 开几个页签就用完了.&lt;/p&gt;
&lt;p&gt;结果 LLM 来了. 逐 &lt;code&gt;token&lt;/code&gt; 输出的打字机效果, 恰好就是&quot;服务端单向推一串文本&quot;这一个需求, 一点不多, 一点不少. OpenAI 的 &lt;code&gt;stream=true&lt;/code&gt; 用 SSE, Anthropic 用 SSE, 几乎所有推理服务的流式接口都是 SSE, &lt;del&gt;然后各家在解析 &lt;code&gt;data:&lt;/code&gt; 行的时候各踩各的坑&lt;/del&gt;.&lt;/p&gt;
&lt;p&gt;为什么不是 WebSocket? 因为对于&quot;一次请求换一串 token&quot;来说, 要付的代价有点大:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要 &lt;code&gt;Upgrade: websocket&lt;/code&gt; 协议升级, 链路上的 CDN, 负载均衡, 企业代理, API 网关都得单独支持&lt;/li&gt;
&lt;li&gt;握手仍然是 HTTP, 但升级之后不再有一问一答的请求语义; 鉴权, 错误码, 重试, 限流和可观测性都要映射到长连接里的应用事件&lt;/li&gt;
&lt;li&gt;WebSocket 已经定义了消息边界和 Ping/Pong, 但业务消息, 序列化, 重连和断点恢复仍然要自己定义&lt;/li&gt;
&lt;li&gt;如果连接承载了会话状态, 服务端扩缩容时还要考虑连接迁移, 亲和性和恢复&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而 SSE 就是一个迟迟不结束的普通响应, &lt;code&gt;Content-Type: text/event-stream&lt;/code&gt;, 其余一切照旧. 需求的形状对上了, 旧技术就赢了一局.&lt;/p&gt;
&lt;p&gt;不过要说清楚, SSE 赢的是部署成本, 不是天然更可靠. 所有长连接都有一个很讨厌的性质: &lt;strong&gt;没有应用层心跳时, 客户端分不清&quot;服务端在想&quot;和&quot;连接已经死了&quot;&lt;/strong&gt;. 一个正在生成的流, 和一个 TCP 路径已经断掉但 socket 还挂着的流, 从客户端看都可能是&quot;打开着, 但没有新字节&quot;. NAT 表项超时, 移动网络切换, 负载均衡回收空闲连接, 都可能让故障不能立刻暴露, 而 TCP 自己的超时又很慢.&lt;/p&gt;
&lt;p&gt;那后来是怎么补回来的? 心跳, 事件序号, 断点续传. OpenAI 的 &lt;code&gt;background&lt;/code&gt; 配合 &lt;code&gt;starting_after&lt;/code&gt; 就是让客户端记住每个事件的 &lt;code&gt;sequence_number&lt;/code&gt;, 断线之后拿着游标从断点继续收. 而这三样东西, 恰好就是当年大家嫌 WebSocket 麻烦, 不愿意自己写的那一套.&lt;/p&gt;
&lt;p&gt;需求的形状也还会继续变. 2024 年 5 月前后, 有用户观察到 ChatGPT 网页端开始使用 WebSocket, 一批依赖旧 SSE 行为的第三方客户端当场失效. 至于为什么切, OpenAI 没有给这次前端变化写一篇架构复盘, 下面只能算是推测: ChatGPT 早就不是&quot;一次请求换一串 token&quot;了 - 中断生成, 工具审批, 语音, 多任务并行, 切走再切回来内容还在继续同步. 服务端还要推送一些&lt;strong&gt;不属于当前生成请求&lt;/strong&gt;的事件, 比如&quot;另一个对话里的后台任务跑完了&quot;.&lt;/p&gt;
&lt;p&gt;SSE 并不是不能传这种事件. 完全可以单独开一条用户级事件流, 再用 POST 发送上行消息, 当年的 MCP &lt;code&gt;HTTP+SSE&lt;/code&gt; 就是这么干的. 只是到了这一步, 你已经有了两条通道, 会话标识, 事件关联和断线恢复, 越来越像是在 HTTP 上拼出一个 WebSocket.&lt;/p&gt;
&lt;p&gt;所以真正的分界线不是&quot;要不要流式&quot;, 甚至也不是 SSE &quot;能不能做&quot;, 而是&lt;strong&gt;上行事件是否独立, 频繁到值得维护一条共享的全双工通道&lt;/strong&gt;. 单个请求的输出流, SSE 赢, 因为它几乎不增加成本; 中断, 审批和并行任务越来越多时, WebSocket 的模型开始更自然.&lt;/p&gt;
&lt;p&gt;Codex 的 &lt;code&gt;app-server&lt;/code&gt; 则是生下来就在会话这一侧. VS Code 扩展这类富客户端跑在它上面, 默认传输是本地 &lt;code&gt;stdio&lt;/code&gt;; 远程 TUI 还可以实验性地使用 WebSocket, 非本地连接再加 TLS 和 bearer token. 它要传的东西根本不是一串文本: 客户端随时可能发中断, 服务端随时可能反问&quot;这条命令允许执行吗&quot;, 中间还夹着工具调用, 审批请求和沙箱状态. 用 SSE 加 POST 当然也能拼, 只是从第一天就不太划算.&lt;/p&gt;
&lt;p&gt;所以 WebSocket 被重新拿起来, 不是因为 SSE 不好用, 而是因为 Agent 把&quot;聊天&quot;重新变成了&quot;会话&quot;. 交互里有中断, 有审批, 有并行任务汇报时, 单向响应流仍然能做, 但不再是最省事的做法.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;换个角度说, &quot;复古&quot;不是终点, 是钟摆. 长轮询 -&amp;gt; SSE -&amp;gt; WebSocket 这个循环二十年前走过一遍了, LLM 只是让它重新走一遍, 而且快得多. 至于实时语音那种低延迟, 带二进制音频流的场景, 则从头到尾都是 WebSocket 和 &lt;code&gt;WebRTC&lt;/code&gt; 的地盘.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;402-shui-liao-san-shi-nian-de-zhuang-tai-ma&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#402-shui-liao-san-shi-nian-de-zhuang-tai-ma&quot; aria-label=&quot;Anchor link for: 402-shui-liao-san-shi-nian-de-zhuang-tai-ma&quot;&gt;402: 睡了三十年的状态码&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;HTTP 状态码表里一直有个尴尬的空位: &lt;code&gt;402 Payment Required&lt;/code&gt;. 它从 &lt;code&gt;RFC 2068&lt;/code&gt; (1997) 起就在那了, 规范里的说明大意是&quot;保留, 供将来使用&quot;, 于是它就一直&quot;将来&quot;了将近三十年, 成了所有人都见过, 但从来没人真正返回过的那一条.&lt;/p&gt;
&lt;p&gt;它的原始设想是 90 年代的数字现金与微支付 (&lt;code&gt;DigiCash&lt;/code&gt;, &lt;code&gt;Millicent&lt;/code&gt; 之类), 而这条路线彻底失败了, 互联网最后选了另外两个答案: 广告和订阅. 微支付的问题从来不在技术: 为了 0.001 美元走一次授权流程, 用户的心智成本远大于金额本身, 更别提支付网络自己的手续费下限.&lt;/p&gt;
&lt;p&gt;然后 &lt;a rel=&quot;external&quot; href=&quot;https://x402.org/&quot;&gt;x402&lt;/a&gt; 出现了, Coinbase 在 2025 年推出, 2026 年进了 Linux Foundation. 流程朴素得像教科书例子:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;客户端请求资源&lt;/li&gt;
&lt;li&gt;服务端返回 &lt;code&gt;402&lt;/code&gt;, 并通过 &lt;code&gt;PAYMENT-REQUIRED&lt;/code&gt; 带上支付要求 (scheme, 网络, 资产, 金额, 收款方)&lt;/li&gt;
&lt;li&gt;客户端选一种支付要求, 签一笔转账授权, 带上 &lt;code&gt;PAYMENT-SIGNATURE&lt;/code&gt; 重发同一个请求&lt;/li&gt;
&lt;li&gt;服务端自己或通过 &lt;code&gt;facilitator&lt;/code&gt; 验证并结算, 返回 200 和 &lt;code&gt;PAYMENT-RESPONSE&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;早期 x402 v1 的请求头叫 &lt;code&gt;X-PAYMENT&lt;/code&gt; 和 &lt;code&gt;X-PAYMENT-RESPONSE&lt;/code&gt;, 到 v2 又把名字改成了上面这一套. &lt;del&gt;协议复古, 请求头倒是新得很快&lt;/del&gt;.&lt;/p&gt;
&lt;p&gt;为什么现在成立了? 因为付钱的那一方换人了. 微支付对人类是骚扰, 对 Agent 是一次函数调用. Agent 不会为&quot;要不要花 0.002 美元查一次这个 API&quot;犹豫三秒, 也不需要填信用卡表单, 更不会因为多一步确认就流失. 需求方从&quot;不耐烦的人&quot;变成了&quot;按预算执行的程序&quot;, 那个空置三十年的状态码就突然有了主人.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;同类的还有 &lt;code&gt;L402&lt;/code&gt; (Lightning Network 加 macaroon) 和 Google 的 &lt;code&gt;AP2&lt;/code&gt;. 顺便说, 结算手段是不是加密货币和 402 本身无关, 402 只是那个信封.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;mcp-qing-jiao-wo-rpc&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#mcp-qing-jiao-wo-rpc&quot; aria-label=&quot;Anchor link for: mcp-qing-jiao-wo-rpc&quot;&gt;MCP: 请叫我 RPC&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Model Context Protocol&lt;/code&gt; 听起来很新, 打开报文一看: &lt;code&gt;jsonrpc: &quot;2.0&quot;&lt;/code&gt;, &lt;code&gt;method&lt;/code&gt;, &lt;code&gt;params&lt;/code&gt;, &lt;code&gt;id&lt;/code&gt;. 也就是 JSON-RPC 2.0, 2010 年的东西, 而 RPC 这个概念本身要追到 1980 年代.&lt;/p&gt;
&lt;p&gt;REST 和 GraphQL 在 &lt;code&gt;tools/call&lt;/code&gt; 面前基本没有位置, 这件事挺讽刺的. 过去十几年后端圈花了大量口水论证&quot;不要 RPC 风格的接口&quot;: 要资源不要动作, 要正确的 URI 语义, 要 HATEOAS, 要让客户端用 GraphQL 自己挑字段. 结果最终的客户端是个大语言模型, 而它想要的东西是:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一份带自然语言描述的函数清单 (&lt;code&gt;tools/list&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;一份 JSON Schema 声明的参数 (&lt;code&gt;inputSchema&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;调一下, 拿结果 (&lt;code&gt;tools/call&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这是最朴素的&quot;过程调用加接口定义&quot;. &lt;code&gt;JSON Schema&lt;/code&gt; 重新承担了过去 IDL 里&quot;参数契约&quot;的那一部分; WSDL 还管消息, 绑定和端点, MCP 没有把那整套 XML 大教堂搬回来, &lt;del&gt;WSDL: 我只是死早了&lt;/del&gt;. 更重要的区别在于, 过去的 IDL 主要写给编译器看, 现在还要写给模型看, 所以描述字段里塞的是人话.&lt;/p&gt;
&lt;p&gt;传输层的演化也很有意思: &lt;code&gt;2024-11-05&lt;/code&gt; 版本是 &lt;code&gt;stdio&lt;/code&gt; 加 &lt;code&gt;HTTP+SSE&lt;/code&gt; 双端点, &lt;code&gt;2025-03-26&lt;/code&gt; 版本换成了 &lt;code&gt;Streamable HTTP&lt;/code&gt;, 把 POST 和 GET 收到同一个 endpoint 上; POST 响应可以是 &lt;code&gt;application/json&lt;/code&gt;, 也可以是 &lt;code&gt;text/event-stream&lt;/code&gt;, GET 则可以单独打开一条 SSE 流. 而本地 MCP Server 到今天最主流的方式仍然是 &lt;code&gt;stdio&lt;/code&gt; - 在标准输入输出上跑一个按行分隔的文本协议, 这是 Unix 管道时代的做法.&lt;/p&gt;
&lt;h2 id=&quot;bu-zhi-shi-xie-yi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#bu-zhi-shi-xie-yi&quot; aria-label=&quot;Anchor link for: bu-zhi-shi-xie-yi&quot;&gt;不只是协议&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;把范围放开一点, 会发现 AI 时代的&quot;新&quot;东西里, 复古的比例高得惊人.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;神经网络本身&lt;/strong&gt;: 感知机 1958, 反向传播 1986, LSTM 1997, 混合专家 1991, 注意力与 Transformer 2017. Transformer 当然是新想法, 但它站在一长串几度失宠的旧想法上, 最后再靠算力, 数据和工程把规模推了上去. 这大概是&quot;创新即复古&quot;最大的一个例子&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;命令行&lt;/strong&gt;: &lt;code&gt;Claude Code&lt;/code&gt;, &lt;code&gt;Codex CLI&lt;/code&gt;, &lt;code&gt;pi&lt;/code&gt;, 兜兜转转, 目前体感最强的 AI 编程界面之一又是终端里的 TUI. &lt;code&gt;pi&lt;/code&gt; 更干脆, 默认只把 &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;edit&lt;/code&gt;, &lt;code&gt;bash&lt;/code&gt; 四个工具交给模型, 连 &lt;code&gt;grep&lt;/code&gt; 和 &lt;code&gt;find&lt;/code&gt; 都可以从 shell 里自己拿. 这套东西看起来不像 2026 年的 IDE, 更像是给 Unix shell 配了一个会说话的操作员. Unix 哲学里那句&quot;纯文本是通用接口&quot;重新生效了, &lt;del&gt;顺便它也是最省 token 的接口&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;调试&lt;/strong&gt;: IDE 花了几十年把断点, 条件断点, Watch 和单步执行做得越来越精细, LLM 写代码时却很少碰它们, 最常用的还是 &lt;code&gt;print&lt;/code&gt;, 日志和测试输出. 原因也很朴素: 调试器是给人盯着看的交互界面, 文本输出则可以直接塞进上下文, 复制, 搜索和重放. 于是最先进的编程模型, 又把我们带回了 &lt;code&gt;printf debugging&lt;/code&gt;, &lt;del&gt;出问题先打两行日志, 这下人和 AI 平等了&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;纯文本与配置文件&lt;/strong&gt;: &lt;code&gt;README.md&lt;/code&gt; 和 &lt;code&gt;Makefile&lt;/code&gt; 的位置被 &lt;code&gt;AGENTS.md&lt;/code&gt;, &lt;code&gt;CLAUDE.md&lt;/code&gt; 接了过去, &lt;code&gt;robots.txt&lt;/code&gt; 有了 &lt;code&gt;llms.txt&lt;/code&gt; 这门亲戚, 而文档格式的胜利者是 2004 年的 Markdown&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计费与调度&lt;/strong&gt;: 按 token 计费, 排队, 批量任务打五折, 高峰限流. 这是分时系统按 CPU 时间收费的复刻版, GPU 集群就是新的大型机, 我们又回到了&quot;把作业提交上去然后等&quot;的年代&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;爬虫战争&lt;/strong&gt;: 训练数据引发的 &lt;code&gt;robots.txt&lt;/code&gt; 之争, 版权诉讼与反爬对抗, 和二十年前搜索引擎时代的剧本几乎逐字重演&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多智能体&lt;/strong&gt;: 看起来很像是把 Jira 看板交给了程序. 主 Agent 把目标拆成 ticket, 分派给不同的子 Agent; 子 Agent 更新状态, 留下结果, 遇到依赖就阻塞; 最后再由主 Agent 验收和关闭. 底下当然是消息传递, 任务队列和共享状态, 但从组织方式来看, 它复刻的更像办公室里早就跑了几十年的工作流, &lt;del&gt;现在连开会的人也省了&lt;/del&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无状态与有状态&lt;/strong&gt;: 经典 LLM 接口每次请求把全部历史重新带上, 这是彻底的无状态, 很 REST, 甚至很 CGI; 后来 &lt;code&gt;prompt caching&lt;/code&gt;, &lt;code&gt;previous_response_id&lt;/code&gt; 和服务端会话又把状态放回了服务器, 于是会话存储和亲和性路由也一起回来了&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;wei-shen-me-hui-zhe-yang&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#wei-shen-me-hui-zhe-yang&quot; aria-label=&quot;Anchor link for: wei-shen-me-hui-zhe-yang&quot;&gt;为什么会这样&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;不是因为大家怀旧, 也不是因为没有新想法. 几条比较实在的原因:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;约束条件回到了旧协议被设计时的样子.&lt;/strong&gt; 老技术之所以老, 常常不是因为它错, 而是因为它解决的那个问题后来不重要了. SSE 是为&quot;服务端单向推事件&quot;设计的, 402 给网络原生支付留了一个空位, RPC 是为&quot;跨进程调一个函数&quot;设计的. 这三件事在 2015 年都不是主流需求, 在 2025 年全都是.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;简单的东西赢.&lt;/strong&gt; &lt;code&gt;worse is better&lt;/code&gt;. 能直接复用现有基础设施的方案, 哪怕设计上&quot;不够优雅&quot;, 也会赢过需要整条链路配合改造的方案. SSE 对 WebSocket, JSON-RPC 对 GraphQL, 都是同一个故事.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Gall 定律.&lt;/strong&gt; 能工作的复杂系统总是从能工作的简单系统演化而来. 一个新协议要在一年内铺开, 现实的路径只有站在已经铺开的东西上面, 也就是 HTTP, 纯文本和标准输入输出.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;变的往往是需求方, 不是技术.&lt;/strong&gt; 402 最典型, 技术三十年没动, 动的是&quot;消费者从人变成了程序&quot;. 一旦发起请求的一方不再是需要被哄着点下一步的人类, 一大批&quot;技术上可行但体验上不成立&quot;的旧设计就自动成立了.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最后一条不太好听&lt;/strong&gt;: 标准的选择很多时候只取决于第一个跑起来的人用了什么. OpenAI 的接口一发, SSE 就是事实标准; Anthropic 把 MCP 一开源, JSON-RPC 就是事实标准. &lt;del&gt;关于技术优劣的讨论通常在既成事实之后才热烈起来.&lt;/del&gt;&lt;/p&gt;
&lt;h2 id=&quot;na-shen-me-cai-shi-zhen-de-xin&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#na-shen-me-cai-shi-zhen-de-xin&quot; aria-label=&quot;Anchor link for: na-shen-me-cai-shi-zhen-de-xin&quot;&gt;那什么才是真的新&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;公平地说, 也确实有全新的部分:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Scaling Laws&lt;/code&gt; 对规模, 数据, 算力与损失之间关系的经验描述, 以及&quot;继续放大可能继续变好&quot;这件事催生的整套工程体系. 至于能力是不是&quot;突然涌现&quot;, 那是另一场还没吵完的架&lt;/li&gt;
&lt;li&gt;自然语言不再只是文档, 而是直接参与运行时决策, 同一句描述既可能是数据, 也可能被模型当成指令. 自然语言编程并非没有前人试过, 但从未像今天这样大规模进入生产系统; &lt;code&gt;prompt injection&lt;/code&gt; 也正是在这种指令与数据边界不清的地方长出来&lt;/li&gt;
&lt;li&gt;把一个输出永远带概率的部件, 放进了要求确定性的系统里. 相应的验证, 回滚, 幂等和边界设计, 目前还远没有成熟答案&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;换句话说, 复古的是管道, 新的是流过管道的那个东西.&lt;/p&gt;
&lt;h2 id=&quot;hou-ji&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#hou-ji&quot; aria-label=&quot;Anchor link for: hou-ji&quot;&gt;后记&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&quot;创新即复古&quot;并不是嘲讽. 一个能被三十年前的协议直接接住的需求, 说明这个需求足够朴素, 而朴素的需求通常是真需求. 反过来, 如果某个 AI 时代的新场景需要一整套全新协议栈才能表达清楚, 那大概值得先怀疑一下这个场景本身.&lt;/p&gt;
&lt;p&gt;顺便, 这也是个还算好用的判断技巧: 看到一个新名词, 先问一句&quot;它复古的是什么&quot;. 通常能立刻定位到它的设计权衡在哪里, 以及三十年前那批人当年是在哪一步上失败的.&lt;/p&gt;
&lt;h2 id=&quot;can-kao-lian-jie&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#can-kao-lian-jie&quot; aria-label=&quot;Anchor link for: can-kao-lian-jie&quot;&gt;参考链接&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://html.spec.whatwg.org/multipage/server-sent-events.html&quot;&gt;Server-Sent Events - HTML Standard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://platform.openai.com/docs/api-reference/streaming&quot;&gt;OpenAI Streaming API responses&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://platform.openai.com/docs/guides/background&quot;&gt;OpenAI Background mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/transitive-bullshit/chatgpt-api/issues/640&quot;&gt;ChatGPT Web has switched from SSE to WebSockets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://datatracker.ietf.org/doc/html/rfc2068&quot;&gt;RFC 2068 - Hypertext Transfer Protocol -- HTTP/1.1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://x402.org/&quot;&gt;x402&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/x402-foundation/x402&quot;&gt;x402-foundation/x402&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://docs.x402.org/guides/migration-v1-to-v2&quot;&gt;x402 V1 to V2 Migration Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.coinbase.com/developer-platform/discover/launches/x402&quot;&gt;Introducing x402: a new standard for internet-native payments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/lightninglabs/L402&quot;&gt;The Lightning Network HTTP 402 Protocol (L402)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/google-agentic-commerce/AP2&quot;&gt;Agent Payments Protocol (AP2)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.jsonrpc.org/specification&quot;&gt;JSON-RPC 2.0 Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://datatracker.ietf.org/doc/html/rfc6455&quot;&gt;RFC 6455 - The WebSocket Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://modelcontextprotocol.io/specification/2025-06-18/basic/transports&quot;&gt;MCP Specification - Transports&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://blog.modelcontextprotocol.io/posts/2025-12-19-mcp-transport-future/&quot;&gt;Exploring the Future of MCP Transports&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://developers.openai.com/codex/app-server&quot;&gt;Codex App Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/openai/codex/tree/main/codex-rs/app-server&quot;&gt;openai/codex - codex-rs/app-server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.cs.toronto.edu/~hinton/absps/jjnh91.pdf&quot;&gt;Adaptive Mixtures of Local Experts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/1706.03762&quot;&gt;Attention Is All You Need&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://arxiv.org/abs/2001.08361&quot;&gt;Scaling Laws for Neural Language Models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.dreamsongs.com/RiseOfWorseIsBetter.html&quot;&gt;The Rise of &quot;Worse is Better&quot;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/John_Gall_(author)#Gall&amp;#x27;s_law&quot;&gt;Gall&#39;s law&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://llmstxt.org/&quot;&gt;llms.txt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://agents.md/&quot;&gt;AGENTS.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/earendil-works/pi/tree/main/packages/coding-agent&quot;&gt;pi coding agent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>漫谈 &lt;战场之狼2&gt;</title>
        <published>2025-11-17T16:31:57+09:00</published>
        <updated>2025-11-17T16:31:57+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/a-casual-talk-about-mercs/"/>
        <id>https://memwey.github.io/posts/a-casual-talk-about-mercs/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/a-casual-talk-about-mercs/">&lt;p&gt;忽然想起了幼年时期看表哥玩的一款游戏, 当时年纪比较小, 当然不记得游戏的名字了, 我把对于游戏的印象发送给 ChatGPT, &quot;Mega Drive 上的一款游戏, 类似魂斗罗, 军事背景的, 但是不是从左到右的横向卷轴式, 而是俯视角的&quot;, ChatGPT 给了我确切的答案, 那就是 &lt;a rel=&quot;external&quot; href=&quot;https://ja.wikipedia.org/wiki/%E6%88%A6%E5%A0%B4%E3%81%AE%E7%8B%BCII&quot;&gt;戦場の狼II&lt;/a&gt;, 英文名为 &lt;code&gt;MERCS&lt;/code&gt;, 中文的话, 一般就直接叫&amp;lt;战场之狼2&amp;gt;.&lt;/p&gt;
&lt;p&gt;这款游戏本身是从街机移植的, 现在看来, 这个游戏其实有蛮多值得一提的地方.&lt;/p&gt;
&lt;h2 id=&quot;you-xi-ben-shen-liang-dian&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#you-xi-ben-shen-liang-dian&quot; aria-label=&quot;Anchor link for: you-xi-ben-shen-liang-dian&quot;&gt;游戏本身亮点&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&amp;lt;战场之狼2&amp;gt; 加入了大量载具的元素. 在流程中, 可以驾驶吉普车, 固定炮台, 甚至是坦克, 在当时是非常先进而罕见的设计.&lt;/p&gt;
&lt;p&gt;从街机移植到 Mega Drive 的版本中, 去掉了街机厅联机的多人模式, 但是增加了一个非常有诚意的弥补家用机无法&quot;投币&quot;而做的长线玩法 &lt;code&gt;原创模式 (Original Mode)&lt;/code&gt;, 有 5 名不同特性的队员, 包含了 RPG 要素, 可以购买装备和升级.&lt;/p&gt;
&lt;h2 id=&quot;ge-ren-ying-xiong-zhu-yi-shi-liu-cheng&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ge-ren-ying-xiong-zhu-yi-shi-liu-cheng&quot; aria-label=&quot;Anchor link for: ge-ren-ying-xiong-zhu-yi-shi-liu-cheng&quot;&gt;个人英雄主义式流程&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&amp;lt;战场之狼2&amp;gt; 的流程很简单, 你扮演一位突击队员, 从敌国的海岸, 丛林, 基地一路挺进, 孤身推进, 所到之处弹雨横飞, 最后直捣敌军老巢.&lt;/p&gt;
&lt;p&gt;在今天看来是非常陈旧的&quot;动作片模板&quot;, 但对当年的玩家来说, 这种 &lt;strong&gt;&quot;一个人扫平整个军队&quot;&lt;/strong&gt; 的叙事方式是极具吸引力的. 那正是史泰龙与施瓦辛格主导银幕的年代: 好莱坞动作片, 冷战故事, 肌肉英雄, 特种部队神话风靡一时, 游戏文化也深受其影响.&lt;/p&gt;
&lt;p&gt;受限于当时的技术表现力, 游戏天然倾向这种&quot;我方单枪匹马, 敌人不断涌现&quot;的流程设计. 俯视角, 固定火力射程, 高密度敌人分布, 这些机制既使玩法易于实现, 又高度契合了&quot;孤胆英雄&quot;这种叙事风格. 这种设计既是技术妥协, 也是时代偏好的产物.&lt;/p&gt;
&lt;h2 id=&quot;shen-shou-leng-zhan-xu-shi-ying-xiang-de-you-xi-bei-jing&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#shen-shou-leng-zhan-xu-shi-ying-xiang-de-you-xi-bei-jing&quot; aria-label=&quot;Anchor link for: shen-shou-leng-zhan-xu-shi-ying-xiang-de-you-xi-bei-jing&quot;&gt;深受冷战叙事影响的游戏背景&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&amp;lt;战场之狼2&amp;gt; 的故事背景几乎就是一篇&quot;冷战动作片模板&quot;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;虚构国家发生军事政变&lt;/li&gt;
&lt;li&gt;美国(前)总统被敌人绑架(这里对于是否是&quot;前&quot;, 不同版本似乎有不同的设定)&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;ul&gt;
&lt;li&gt;&lt;strong&gt;敌方大量使用苏式/华约武器&lt;/strong&gt;: AK 与 RPG 系列轻武器, T-55/T-62 外形的坦克, ZSU 风格的防空炮&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;我方则是典型美国/北约装备&lt;/strong&gt;: 突击枪外形接近 M16 系列, 载具则是美式直升机与吉普车&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今天看来, 这种设定显然属于典型的, &quot;小国威胁美国的安全与尊严&quot;, &quot;正义的我们邪恶的他们&quot;, &quot;以军事行动干预他国内政&quot;的世界警察叙事, 在 80–90 年代, 日本游戏与美式娱乐深度绑定的时期, 它却是最常见的标准剧本.&lt;/p&gt;
&lt;p&gt;这种&quot;虚构国家&quot;加&quot;阵营符号化明显&quot;的脸谱化的处理方式, 既避开了现实政治敏感性, 又能让玩家在低清晰度下的像素点下一眼认出&quot;谁是敌人&quot;.&lt;/p&gt;
&lt;p&gt;它并不是真的在讲故事, 而是在为一种既定的世界秩序提供幻想式的正当性. 敌国往往缺乏真实的历史及文化或政治细节, 它存在的唯一理由, 就是为主角的合理暴力提供舞台, 并以游戏的形式输出冷战时期的意识形态框架.&lt;/p&gt;
&lt;h2 id=&quot;zao-qi-jie-ji-fu-fei-mo-shi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zao-qi-jie-ji-fu-fei-mo-shi&quot; aria-label=&quot;Anchor link for: zao-qi-jie-ji-fu-fei-mo-shi&quot;&gt;早期街机付费模式&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;作为街机移植, &amp;lt;战场之狼2&amp;gt;有一个无法忽视的背景, &lt;strong&gt;街机付费模式决定了它的难度结构&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;在街机黄金年代, 几乎所有游戏的设计都围绕一个核心目标展开&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;如何让玩家在 2-3 分钟内死亡一次, 从而投下下一枚硬币?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;于是, 游戏呈现出一种独特的设计:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开场几十秒极易上手, 让玩家建立信心&lt;/li&gt;
&lt;li&gt;经常有&quot;检查点&quot;, 考验特定的操作&lt;/li&gt;
&lt;li&gt;BOSS 通常有&quot;初见杀&quot;&lt;/li&gt;
&lt;li&gt;后期敌人密度与弹量指数级增长&lt;/li&gt;
&lt;li&gt;续币后的短暂无敌或清屏收益巨大&lt;/li&gt;
&lt;li&gt;游戏完整流程维持在一到两小时内&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种设计在当时被视为理所当然, 然而从今天的玩家习惯来看, 早期街机的&quot;投币续命式付费模式&quot;无论在付费模式还是游戏体验上, 几乎不可能再次被广泛接受. 在娱乐内容非常丰富的现代, 让玩家感到挫折的游戏不再被视为&quot;挑战性强&quot;, 而是&quot;没有性价比&quot;, 几乎不会有人会愿意付钱继续忍受, 而是直接离开, 换一款更省心的游戏.&lt;/p&gt;
&lt;h2 id=&quot;hou-ji&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#hou-ji&quot; aria-label=&quot;Anchor link for: hou-ji&quot;&gt;后记&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;回头再看&amp;lt;战场之狼2&amp;gt;, 它是那个时代一个颇有代表性的作品&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;流行文化与技术表现力的限制共同塑造的个人英雄主义叙事&lt;/li&gt;
&lt;li&gt;深受冷战意识形态影响的武器与阵营符号以及国际关系&lt;/li&gt;
&lt;li&gt;难度流程与街机商业模式紧密绑定&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在今天的视角里, 它显得粗糙甚至原始. 但也正因为如此, 它很像是那个年代的化石. 有时候回忆的价值并不是它在今天还有多好玩, 而是它让我们重新看见自己曾经生活在怎样的世界里.&lt;/p&gt;
&lt;p&gt;在查找资料过程中发现在 Steam 上有售, 在 &lt;a rel=&quot;external&quot; href=&quot;https://store.steampowered.com/app/1556714/Capcom_Arcade_StadiumSenjo_no_OkamiII/&quot;&gt;Capcom Arcade Stadium&lt;/a&gt; 中作为 DLC 售卖, 不过似乎是街机版本.&lt;/p&gt;
&lt;h2 id=&quot;can-kao-lian-jie&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#can-kao-lian-jie&quot; aria-label=&quot;Anchor link for: can-kao-lian-jie&quot;&gt;参考链接&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://sesesega.blog.fc2.com/blog-entry-416.html&quot;&gt;SSSG攻略日記 - 戦場の狼II&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.gcores.com/articles/26616&quot;&gt;一篇迟到了27年的街机攻略：我最爱的《战场之狼2》&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.gcores.com/articles/26321&quot;&gt;16-bit游戏漫谈（7）—— 《战场之狼2》&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://black16bit.pixnet.net/blog/post/35869542&quot;&gt;有 RPG 味道的原創模式：戰場之狼II&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://home.gamer.com.tw/creationDetail.php?sn=1363120&quot;&gt;【心得】奔馳於戰地的狂狼怒吼 - 戰場之狼II&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://store.steampowered.com/app/1556714/Capcom_Arcade_StadiumSenjo_no_OkamiII/&quot;&gt;Capcom Arcade Stadium：戦場の狼Ⅱ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://chu-gamer.com/mercs/&quot;&gt;恐れることなく撃ちまくれ！『戦場の狼Ⅱ』&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.bilibili.com/video/BV1Ex421k7ub&quot;&gt;【童年回忆】MD 战场之狼2 原创模式 通关&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.bilibili.com/video/BV1y14y1e7s4&quot;&gt;MD经典游戏《战场之狼2》当年的最爱，无脑放雷狂轰滥炸式冲锋。&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>Fake News 的一件小事</title>
        <published>2025-11-13T15:08:27+09:00</published>
        <updated>2026-07-30T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/fake-news/"/>
        <id>https://memwey.github.io/posts/fake-news/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/fake-news/">&lt;p&gt;最近, 中华人民共和国驻大阪总领事薛剑在 X 上的言论引起了一些争议, 具体的言论如图:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/fake-news/images/screenshot.jpeg&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;
&lt;p&gt;具体的日文原文如下:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;勝手に突っ込んできたその汚い首は一瞬の躊躇もなく斬ってやるしかない。覚悟が出来ているのか。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;其回应的推特原文如下:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;高市首相、台湾有事「存立危機事態になりうる」　認定なら武力行使も
https://www.asahi.com/articles/ASTC722TVTC7UQIP04NM.html
「実際に発生した事態の個別、具体的な状況に応じて、政府がすべての情報を総合して判断する。武力攻撃が発生したら、これは存立危機事態にあたる可能性が高い」とも語りました。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;FOX NEWS&lt;/code&gt; 在新闻页面上给出的英文翻译如下:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;That filthy neck that barged in on its own. I&#39;ve got no choice but to cut if off without a moment&#39;s hesitation. Are you prepared for that?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在与特朗普的专访对谈中, 劳拉·英格拉哈姆, 美国一位广播脱口秀主持人, 畅销书作家和保守派政论家, 这样描述这段发言:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Laura Ingraham: And over the weekend, the new Japanese prime minister, who I know you like a lot, Takaichi, called the Taiwan situation extremely severe in its phase and that they may consider any move on Taiwan by China to be a national survival crisis for Japan. Now that gets better. A Chinese diplomat today said in a social media post that, she the prime minister of Japan, should be beheaded for her comments.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;而特朗普做出的回应如下:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;President Donald Trump: Well, a lot of our allies aren&#39;t our friends either. Our allies took advantage of us on trade more than China did. And China took big advantage. We built their military. We built their whole thing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;从这个事件, 可以清晰的看到 Fake News 是如何产生的. 从传播效果看, 这次转述并非凭空捏造, 而是通过省略语境, 消除指代上的模糊, 把原文中带有暴力意象的&lt;strong&gt;激烈言辞&lt;/strong&gt;, 收束成了一个更&lt;strong&gt;直接&lt;/strong&gt;, 更&lt;strong&gt;具体&lt;/strong&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;strong&gt;偏见&lt;/strong&gt;, &lt;strong&gt;断章取义&lt;/strong&gt;的新闻报道是 &lt;strong&gt;Fake News&lt;/strong&gt;, 也算是身经百战见得多了, 没有具体事情上做评论, 而是谈起来日本并不是美国的朋友, 转回他熟悉的对盟友在经贸问题上占便宜的叙事框架, 场面一度略显尴尬.&lt;/p&gt;
&lt;h2 id=&quot;can-kao-zi-liao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#can-kao-zi-liao&quot; aria-label=&quot;Anchor link for: can-kao-zi-liao&quot;&gt;参考资料&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.youtube.com/watch?v=7juiv6qrYds&quot;&gt;Trump pulls back curtain on relationship with Xi Jinping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.asahi.com/articles/ASTC722TVTC7UQIP04NM.html&quot;&gt;高市首相、台湾有事「存立危機事態になりうる」　武力攻撃の発生時&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://x.com/asahicom/status/1986702380969144741&quot;&gt;高市首相、台湾有事「存立危機事態になりうる」　認定なら武力行使も&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>PlayStation 4 Pro 拾遗</title>
        <published>2025-10-30T19:38:09+09:00</published>
        <updated>2025-11-01T21:44:35+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/playstation-4-pro-rediscovering/"/>
        <id>https://memwey.github.io/posts/playstation-4-pro-rediscovering/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/playstation-4-pro-rediscovering/">&lt;p&gt;这台 PlayStation 4 Pro 其实也买了挺久了, 当时还是买的国行版本, 然后因为长时间的租房生活, 并没有什么条件和时间来玩, 最终长期的吃灰, 甚至借给朋友玩了好长时间 (当然他买的&lt;code&gt;怪物猎人世界: 冰原&lt;/code&gt;在还机器的时候一并送我了)&lt;/p&gt;
&lt;p&gt;带来日本后, 最近又捡起来了, 得益于秋叶原线下的二手交易市场, 发现其实还是比较可玩的. &lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Eighth_generation_of_video_game_consoles&quot;&gt;第八世代&lt;/a&gt;其实是个比较特别的世代. 一方面, 主流游戏玩法已经日趋成熟, 画面也有比较大的提升, 很多音画体验时至今日也不算过时; 另一方面, 此时开发成本还没有像近些年来一样飙升, 一些有意思的小品作品还有相对较大的生存空间; 同时在这个世代, 主机还处于相对优势, 大量作品还是主要以主机平台为核心开发的.&lt;/p&gt;
&lt;p&gt;综上所述, 下面会列出一些还比较值得在 PlayStation 4 Pro 上体验的游戏, 其中个人喜好比较重, 仅供参考, 主要在 1080P 的屏幕下. 如果涉及多平台, 主要对比的对象是 PC 和 &lt;code&gt;Nintendo Switch&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&quot;ge-ren-tui-jian&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ge-ren-tui-jian&quot; aria-label=&quot;Anchor link for: ge-ren-tui-jian&quot;&gt;个人推荐&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;ni-er-zi-dong-ren-xing-nia-otomata-nier-automata&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ni-er-zi-dong-ren-xing-nia-otomata-nier-automata&quot; aria-label=&quot;Anchor link for: ni-er-zi-dong-ren-xing-nia-otomata-nier-automata&quot;&gt;尼尔: 自动人形 (ニーア オートマタ, NieR:Automata)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;白金工作室, 标志性头套本体横尾太郎总监的动作角色扮演作品, 当年性感可爱的 &lt;code&gt;2B&lt;/code&gt; 小姐姐几乎成为线下漫展 Cosplay 的象征. 虽然在 Steam 上也有(&lt;a rel=&quot;external&quot; href=&quot;https://store.steampowered.com/app/524220/NieRAutomata/&quot;&gt;在这里&lt;/a&gt;), 推荐在 PlayStation 4 Pro 上体验的原因是日本厂商游戏的高定价, 涨价风波和分割商法, 另外, 在 PC 上体验很差, 移植比较暴力优化不佳, 无中文, 而且操作逻辑偏向手柄操作. 在 PS4 Pro 上可以达到不稳定的 60 帧, 也自带繁体中文.&lt;/p&gt;
&lt;h3 id=&quot;huang-ye-da-biao-ke-jiu-shu-red-dead-redemption&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#huang-ye-da-biao-ke-jiu-shu-red-dead-redemption&quot; aria-label=&quot;Anchor link for: huang-ye-da-biao-ke-jiu-shu-red-dead-redemption&quot;&gt;荒野大镖客: 救赎 (Red Dead Redemption)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Rockstar 的经典西部开放世界作品, 剧情颇受好评. 在 PS4 和 Switch 上移植, 也有登陆 Steam (&lt;a rel=&quot;external&quot; href=&quot;https://store.steampowered.com/app/2668510/Red_Dead_Redemption/&quot;&gt;在这里&lt;/a&gt;), 本身是第七世代的作品, 有一些在现代玩家中不太讨喜的设计, 移植可以说是原汁原味了. PlayStation 4 Pro 上比 Switch 画面效果和流畅度好太多了. 而移植后本身无联网要素, PC 上却需要很讨厌的 Rockstar 启动器.&lt;/p&gt;
&lt;h3 id=&quot;long-teng-shi-ji-shen-pan-dragon-age-inquisition&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#long-teng-shi-ji-shen-pan-dragon-age-inquisition&quot; aria-label=&quot;Anchor link for: long-teng-shi-ji-shen-pan-dragon-age-inquisition&quot;&gt;龙腾世纪: 审判 (Dragon Age: Inquisition)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;BioWare 的代表作, TGA 的 2014 年度游戏, 第八世代早期的作品, 登陆 Steam (&lt;a rel=&quot;external&quot; href=&quot;https://store.steampowered.com/app/1222690/_/&quot;&gt;在这里&lt;/a&gt;). PC 上没有中文, 还要安装 EA 的启动器, 还有额外的 DRM. 在 PS4 Pro 自带繁体中文.&lt;/p&gt;
&lt;h3 id=&quot;ci-ke-xin-tiao-ao-de-sai-assassin-s-creed-odyssey&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ci-ke-xin-tiao-ao-de-sai-assassin-s-creed-odyssey&quot; aria-label=&quot;Anchor link for: ci-ke-xin-tiao-ao-de-sai-assassin-s-creed-odyssey&quot;&gt;刺客信条: 奥德赛 (Assassin&#39;s Creed Odyssey)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;育碧年货, 但是古希腊的背景蛮吸引人的. 登陆 Steam (&lt;a rel=&quot;external&quot; href=&quot;https://store.steampowered.com/app/812140/Assassins_Creed_Odyssey/&quot;&gt;在这里&lt;/a&gt;). PC 上需要 Ubisoft 的启动器和额外的 DRM.&lt;/p&gt;
&lt;h3 id=&quot;lover-ravuaru&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#lover-ravuaru&quot; aria-label=&quot;Anchor link for: lover-ravuaru&quot;&gt;LoveR (ラヴアール)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;和美少女谈恋爱并且给美少女拍照的游戏. &lt;code&gt;爱相随&lt;/code&gt; 的人设, &lt;code&gt;圣诞之吻&lt;/code&gt;, &lt;code&gt;写真女友&lt;/code&gt; 的制作人, 好耶! 目前只有 Switch 和 PS4 版本, 原本计划登陆 Steam, 但是因为某些大人的原因取消了. Switch 版本虽然性感元素更多, 但是画质会差一些.&lt;/p&gt;
&lt;h3 id=&quot;wang-da-yu-ju-xiang-wandatoju-xiang-shadow-of-the-colossus&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#wang-da-yu-ju-xiang-wandatoju-xiang-shadow-of-the-colossus&quot; aria-label=&quot;Anchor link for: wang-da-yu-ju-xiang-wandatoju-xiang-shadow-of-the-colossus&quot;&gt;汪达与巨像 (ワンダと巨像, Shadow of the Colossus)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;PlayStation 系独占的经典作品, 本身是 PS2 上的游戏, 在 PS3 上高清化,在 PS4 上推出完全重制版, 目前体验的唯一最佳途径. 虽然某些操作在现代玩家看来略显笨拙, 但是游戏给人带来的非常艺术化的沉浸式的体验和史诗感是绝对物有所值的.&lt;/p&gt;
&lt;h3 id=&quot;kai-se-lin-nong-yu-kou-gan-catherine-full-body&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#kai-se-lin-nong-yu-kou-gan-catherine-full-body&quot; aria-label=&quot;Anchor link for: kai-se-lin-nong-yu-kou-gan-catherine-full-body&quot;&gt;凯瑟琳 浓郁口感 (CATHERINE Full Body)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Atlus 推出的推箱子解谜游戏, 原版是第七世代的作品, 本作为其后推出的强化版. 玩法不那么受欢迎的实验性质的作品, 但是剧情和对于两性关系的刻画与思考蛮有意思. 虽然也登陆了 Steam (&lt;a rel=&quot;external&quot; href=&quot;https://store.steampowered.com/app/893180/Catherine_Classic/&quot;&gt;在这里&lt;/a&gt;), 但那是原版而非强化版, 而且移植蛮糟糕的. 强化版在 Switch 上也可体验.&lt;/p&gt;
&lt;h3 id=&quot;zhong-li-yi-xiang-shi-jie-wan-jie-pian-gravity-daze-2-zhong-li-de-xuan-yun-wan-jie-bian-shang-ceng-henogui-huan-noguo-te-bi-nu-nonei-yu-zhou-nishou-lian-sitaxuan-ze&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhong-li-yi-xiang-shi-jie-wan-jie-pian-gravity-daze-2-zhong-li-de-xuan-yun-wan-jie-bian-shang-ceng-henogui-huan-noguo-te-bi-nu-nonei-yu-zhou-nishou-lian-sitaxuan-ze&quot; aria-label=&quot;Anchor link for: zhong-li-yi-xiang-shi-jie-wan-jie-pian-gravity-daze-2-zhong-li-de-xuan-yun-wan-jie-bian-shang-ceng-henogui-huan-noguo-te-bi-nu-nonei-yu-zhou-nishou-lian-sitaxuan-ze&quot;&gt;重力异想世界完结篇 (GRAVITY DAZE 2/重力的眩暈完結編:上層への帰還の果て、彼女の内宇宙に収斂した選択)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;PS4 独占. 这是二代, PS4 上也有从 PS Vita 移植的一代. 体感和手柄的重力操控玩法和日式美术风格, 带来独特体验的作品.&lt;/p&gt;
&lt;h3 id=&quot;ao-ding-ling-yu-li-pu-te-la-xi-er-odeinsuhuia-reivusurasiru-odin-sphere-leifthrasir&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ao-ding-ling-yu-li-pu-te-la-xi-er-odeinsuhuia-reivusurasiru-odin-sphere-leifthrasir&quot; aria-label=&quot;Anchor link for: ao-ding-ling-yu-li-pu-te-la-xi-er-odeinsuhuia-reivusurasiru-odin-sphere-leifthrasir&quot;&gt;奥丁领域: 里普特拉西尔 (オーディンスフィア レイヴスラシル, Odin Sphere: Leifthrasir)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;PlayStation 系独占, 从 PS2 上的原版重制而来. 香草社无需多言, 华丽的手绘风格的 2D 横版卷轴动作游戏.&lt;/p&gt;
&lt;h3 id=&quot;kan-men-gou-watch-dogs&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#kan-men-gou-watch-dogs&quot; aria-label=&quot;Anchor link for: kan-men-gou-watch-dogs&quot;&gt;看门狗 (Watch Dogs)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;育碧公式化开放世界, 但是结合黑客/开放世界/现代都市体验元素的目前就此一家. 这个系列的经历在第八世代颇有代表性, 看门狗本身是新 IP, 首部是 PS4 早期的作品, 宣传时画面十分惊艳, 让人们对即将到来的第八世代主机的性能充满期待, 然而实际发售后画面表现缩水严重. 第二部则是换一套背景继续公式化出装, 丰富了玩法, 算是中规中矩的提升. 而最新的 2020 年的第三部 &amp;lt;看门狗: 军团&amp;gt; 则因为商业和管理上的问题, 很大可能是为了线上模式, 导致整体表现不佳, 目前这个 IP 短期内无重启的希望. PC 上则还要面临优化不佳, 对 Windows 系统升级不做适配, 中文化拖拖拉拉的问题, 操作也主要面向手柄. Steam 版本(&lt;a rel=&quot;external&quot; href=&quot;https://store.steampowered.com/app/243470/Watch_Dogs/&quot;&gt;在这里&lt;/a&gt;)还要带上一个陈旧的育碧启动器.&lt;/p&gt;
&lt;h2 id=&quot;sui-sui-nian&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#sui-sui-nian&quot; aria-label=&quot;Anchor link for: sui-sui-nian&quot;&gt;碎碎念&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;虽然其实很早就接触主机游戏了, 小时候在&lt;del&gt;富裕&lt;/del&gt;表哥家里玩过 &lt;code&gt;SFC&lt;/code&gt;, &lt;code&gt;Mega Drive&lt;/code&gt; 和 &lt;code&gt;PlayStation 2&lt;/code&gt;, 掌机也曾拥有过 &lt;code&gt;Gameboy Advance&lt;/code&gt;, &lt;code&gt;PlayStation Portable&lt;/code&gt; 和 &lt;code&gt;Nintendo DS&lt;/code&gt;, 但是第八世代的游戏留下的印象还是颇为深刻. 主要还是因为那时上了大学, 有比较充足的时间玩游戏. 与此同时, Steam 开始支持人民币支付, 购买游戏方便了很多, 我还记得之前通过 &lt;code&gt;骑马与砍杀中文网&lt;/code&gt; 买一份正版的 &amp;lt;骑马与砍杀&amp;gt; 多么不方便. 那个世代的游戏, 也有那个世代的特征, 不重视中文玩家, 主机独占策略, 主机优先导致的优化以及操作问题, 部分厂商激进的联网化和平台化策略, 防盗版策略. 不过还是希望能有更多人, 能体会到那个世代的游戏的快乐.&lt;/p&gt;
&lt;h2 id=&quot;can-kao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#can-kao&quot; aria-label=&quot;Anchor link for: can-kao&quot;&gt;参考&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://bbs.oldmantvg.net/thread-12311.htm&quot;&gt;555余款游戏在PS4、PS4 PRO、PS5上的分辨率帧数统计汇总&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>炸鸡与西瓜</title>
        <published>2025-08-07T21:46:01+09:00</published>
        <updated>2025-08-07T21:46:01+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/watermelon-and-fried-chicken/"/>
        <id>https://memwey.github.io/posts/watermelon-and-fried-chicken/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/watermelon-and-fried-chicken/">&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 src=&quot;https://memwey.github.io/posts/watermelon-and-fried-chicken/images/pic.jpg&quot; alt=&quot;pic&quot; /&gt;&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>Windsurf 入门指南</title>
        <published>2025-06-14T18:30:10+09:00</published>
        <updated>2025-07-28T10:58:23+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/first-step-with-windsurf/"/>
        <id>https://memwey.github.io/posts/first-step-with-windsurf/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/first-step-with-windsurf/">&lt;h2 id=&quot;wei-shen-me-shi-windsurf&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#wei-shen-me-shi-windsurf&quot; aria-label=&quot;Anchor link for: wei-shen-me-shi-windsurf&quot;&gt;为什么是 Windsurf&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;曾经叫 &lt;code&gt;Codeium&lt;/code&gt;, 在 2022 年还是 &lt;code&gt;Github Copilot&lt;/code&gt; 的时代即提供支持 &lt;code&gt;VS Code&lt;/code&gt; 和 &lt;code&gt;JetBrains&lt;/code&gt; 的 AI 编程工具插件; 于 2024 年推出类似于 &lt;code&gt;Cursor&lt;/code&gt; 的基于 &lt;code&gt;VS Code&lt;/code&gt; 的 IDE &lt;code&gt;Windsurf&lt;/code&gt;, 随后将公司更名&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;免费的基础使用和更便宜的订阅价格&lt;/li&gt;
&lt;li&gt;和 &lt;code&gt;Cursor&lt;/code&gt; &lt;del&gt;互相抄&lt;/del&gt; 功能没太大区别&lt;/li&gt;
&lt;li&gt;被 &lt;code&gt;OpenAI&lt;/code&gt; 以约30亿美元收购&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;目前告吹, 公司核心成员被 &lt;code&gt;Google&lt;/code&gt; 挖走后开发 &lt;code&gt;Devin AI&lt;/code&gt; 的 &lt;code&gt;Cognition&lt;/code&gt; 收购了公司, &lt;del&gt;然后开始了疯狂加班&lt;/del&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;被 &lt;code&gt;Anthropic&lt;/code&gt; 断供&lt;del&gt;高端的 AI 商战&lt;/del&gt;, 目前不能使用 &lt;code&gt;Claude 4&lt;/code&gt;, &lt;del&gt;被收购之后恢复了&lt;/del&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Cursor&lt;/code&gt; 相关的信息污染太厉害了, 乔戈里峰问题&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最近我发现 &lt;code&gt;TREA&lt;/code&gt; 好像更好用, 问题似乎出在 &lt;code&gt;Windsurf&lt;/code&gt; 配置的上下文太短了, &lt;del&gt;Kiro 大法好&lt;/del&gt;&lt;/p&gt;
&lt;h2 id=&quot;zhu-yao-gong-neng&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhu-yao-gong-neng&quot; aria-label=&quot;Anchor link for: zhu-yao-gong-neng&quot;&gt;主要功能&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;相比插件更好的代码上下文感知 &lt;code&gt;Context Awareness&lt;/code&gt;, 记忆能力, 调用第三方工具能力&lt;/p&gt;
&lt;h3 id=&quot;tab&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#tab&quot; aria-label=&quot;Anchor link for: tab&quot;&gt;Tab&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;补全, 跳转, 导入&lt;/p&gt;
&lt;h3 id=&quot;command&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#command&quot; aria-label=&quot;Anchor link for: command&quot;&gt;Command&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;唤出输入框输入指令, 适用于当前文件范围内的修改, 作用于当前游标位置或选中的代码段&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;小技巧, 在注释中写入指令, 然后 &lt;code&gt;Cmd/Ctrl+I&lt;/code&gt; 随后使用类似 &lt;code&gt;do it&lt;/code&gt; 这样的简单指令即可&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;cascade&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#cascade&quot; aria-label=&quot;Anchor link for: cascade&quot;&gt;Cascade&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;项目范围的大规模理解与调整, 可以运行命令行, 网络检索等工具, 也可以通过 &lt;code&gt;MCP&lt;/code&gt; 协议调用外部工具&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Planning Mode: 将计划输出到 Markdown 文件, 便于调整和长流程处理&lt;/li&gt;
&lt;li&gt;Browser: 读文档, 获取网页内容; 前端开发&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;zong-jie&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zong-jie&quot; aria-label=&quot;Anchor link for: zong-jie&quot;&gt;总结&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;三个功能从上到下, 需要的人工的输入越来越多, 上下文感知要求越来越强, 流程和处理时间越来越长, 根据适当情况选择适当的功能&lt;/p&gt;
&lt;h2 id=&quot;yuan-li-yu-ju-xian&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#yuan-li-yu-ju-xian&quot; aria-label=&quot;Anchor link for: yuan-li-yu-ju-xian&quot;&gt;原理与局限&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;神经网络的仿生学原理, 生物神经系统存在大量通过突触连接的神经元, 特定神经活动使关联的神经元突触产生可塑性变化, 是记忆和认知的基础; 神经网络通过数学模型, 用于编码输入与输出之间的概率关联, 本质是数据统计上的关联; 生物的神经系统的学习过程是持续发生的, 学习与使用并非截然分开, 而当前的大语言模型通常将&quot;训练&quot;与&quot;推理&quot;严格分离, 参数在训练后即固定, 所以不要尝试&quot;教会&quot;它, 因为它知道错了也不会改, 而是要通过 &lt;code&gt;prompt&lt;/code&gt; 提供上下文信息, 使其在有限的上下文窗口中做出合理响应&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上下文能力长度的限制, 上下文限制不仅在输入端, 同时在中间状态与输出端, 所以大模型能精准利用的有效上下文会明显比标示的上下文 &lt;code&gt;token&lt;/code&gt; 参数小&lt;/li&gt;
&lt;li&gt;注意力机制, &lt;code&gt;&amp;lt;Attention is All You Need&amp;gt;&lt;/code&gt;, 但是 &lt;code&gt;Attention&lt;/code&gt; 不是 &lt;code&gt;Understanding&lt;/code&gt;, 无法主动区分重点与细节, 只是根据训练统计模式做 &lt;code&gt;token&lt;/code&gt; 级别的预测&lt;/li&gt;
&lt;li&gt;缺乏长期记忆机制, 不能自发积累跨 &lt;code&gt;Session&lt;/code&gt; 经验和知识, 需要外部记忆模块&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;严格来说模型是可以做轻量微调(PEFT)的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;大语言模型是高性能的统计关联引擎, 其输出本质是&quot;有结构的幻觉&quot; - 杨立昆的观点&lt;/p&gt;
&lt;h2 id=&quot;zui-jia-shi-jian&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zui-jia-shi-jian&quot; aria-label=&quot;Anchor link for: zui-jia-shi-jian&quot;&gt;最佳实践&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;bi-yu&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#bi-yu&quot; aria-label=&quot;Anchor link for: bi-yu&quot;&gt;比喻&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;用 minori 的 &amp;lt;ef - a fairy tale of the two.&amp;gt; 的情节来描述大模型非常合适. 新藤千寻在 12 岁时由于事故患上顺行性遗忘症, 从那以后的事情她只能记住从当时往前推算的 13 个小时. 每一个清晨醒来, 她都必须重新阅读自己的日记，从中寻找&quot;我&quot;的线索, 并在日记本上不断续写她自己. 少年麻生莲治在车站与其相遇, 两人的距离在不断的相识与遗忘中逐渐缩短, 经过挣扎和犹豫两个人最终走到了一起. 这段爱情也带着某种悖论式的悲伤, 对于莲治来说, 他无比喜欢千寻, 但是某种意义上来说, 千寻只是在扮演日记本中那个喜欢莲治的自己.&lt;/p&gt;
&lt;h3 id=&quot;ri-ji-ben&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ri-ji-ben&quot; aria-label=&quot;Anchor link for: ri-ji-ben&quot;&gt;日记本&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;所以归根到底, 是要给&lt;del&gt;美少女&lt;/del&gt;大模型提供优化过的&lt;del&gt;日记本&lt;/del&gt;&lt;code&gt;prompt&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;总是使用 Markdown 写文档, 大语言模型对其有优化&lt;/li&gt;
&lt;li&gt;一致的项目结构与命名规范&lt;/li&gt;
&lt;li&gt;提供项目的结构和功能的描述, &lt;code&gt;User Story&lt;/code&gt;, &lt;code&gt;Product Requirement Document&lt;/code&gt;, 记录决策与约定&lt;/li&gt;
&lt;li&gt;显式且准确的提供完成任务所需的上下文内容和工具调用, 善用&lt;code&gt;@&lt;/code&gt;提示&lt;/li&gt;
&lt;li&gt;积极的使用 &lt;code&gt;Workflow&lt;/code&gt; 和 &lt;code&gt;Rule&lt;/code&gt; 功能, 实际上就是模版化标准化的 &lt;code&gt;prompt&lt;/code&gt; 的应用&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Prompt Engineering is Dead, everything is a Spec.&lt;/del&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;zi-wo-qiang-hua&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zi-wo-qiang-hua&quot; aria-label=&quot;Anchor link for: zi-wo-qiang-hua&quot;&gt;自我强化&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;让模型自己进行总结归纳, 做手动调整, 再作为 &lt;code&gt;prompt&lt;/code&gt; 给到模型上下文, 以此循环&lt;/li&gt;
&lt;li&gt;压缩 &lt;code&gt;prompt&lt;/code&gt;, 并且让模型自己来验证它是否理解 &lt;code&gt;prompt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;渐进式的任务分解提示, 分步骤的提示和指令, 让模型自己来做流程的完成情况维护&lt;/li&gt;
&lt;li&gt;让 Agent 提问题, 写 QA&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;qi-ta-zhu-yi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#qi-ta-zhu-yi&quot; aria-label=&quot;Anchor link for: qi-ta-zhu-yi&quot;&gt;其他注意&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;写通用的规则, 然后用 Markdown 的超链接功能引用, 抹平 IDE 规范差异&lt;/li&gt;
&lt;li&gt;不要使用大模型做代码格式化 (可以让 &lt;code&gt;Cascade&lt;/code&gt; 调用外部工具来处理)&lt;/li&gt;
&lt;li&gt;多 &lt;code&gt;commit&lt;/code&gt; 和 &lt;code&gt;commit --amend&lt;/code&gt;, 防止人工&lt;del&gt;智障&lt;/del&gt;智能大规模删改文件&lt;/li&gt;
&lt;li&gt;复杂操作生成操作清单而非直接执行&lt;/li&gt;
&lt;li&gt;标记工具生成的代码的位置, 以免大模型直接修改&lt;/li&gt;
&lt;li&gt;将常用代码封装成可复用的模块, 并写好 &lt;code&gt;README.md&lt;/code&gt;, 减少大模型上下文负担&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;矛盾&lt;/strong&gt;, &lt;strong&gt;模糊&lt;/strong&gt;和&lt;strong&gt;不准确&lt;/strong&gt;的信息会让大模型偏移&lt;/li&gt;
&lt;li&gt;当 &lt;code&gt;Session&lt;/code&gt; 偏离, 立刻开启新的 &lt;code&gt;Session&lt;/code&gt;, 不要纠结&lt;/li&gt;
&lt;li&gt;将命令编写结构化的 Markdown 文档, 然后在 &lt;code&gt;Cascade&lt;/code&gt; 对话框中引用 (如果&quot;几句话就能说明的东西&quot;就调用 &lt;code&gt;Cascade&lt;/code&gt;, 那很有可能是用的不对)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;zong-jie-1&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zong-jie-1&quot; aria-label=&quot;Anchor link for: zong-jie-1&quot;&gt;总结&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Windsurf&lt;/code&gt; 的人工智能, 将补全, 重构, 规划, 搜索和执行结合为统一体验, 从局部函数到全局架构, 都可以以更自然的方式与其协同. 但它并不魔法般全能, 它依赖上下文, 依赖清晰的项目结构, 依赖好的 &lt;code&gt;prompt&lt;/code&gt; 提示与任务规划. 因此, 真正的生产力提升, 并非来自&quot;你来把这个代码写了&quot;, 而是来自构建了一个让它理解, 协作, 执行的良好环境. 也许可以把 &lt;code&gt;Windsurf&lt;/code&gt; 看作每天早上醒来要读你笔记的&lt;del&gt;美少女&lt;/del&gt;程序员, 而开发者的职责, 就是写好那本&quot;日记本&quot;, 明确的目标, 清晰的上下文, 有逻辑的步骤和合理的调用.&lt;/p&gt;
&lt;h2 id=&quot;qi-ta-yi-xie-kan-fa&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#qi-ta-yi-xie-kan-fa&quot; aria-label=&quot;Anchor link for: qi-ta-yi-xie-kan-fa&quot;&gt;其他一些看法&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Vibe coding&lt;/code&gt; 译为氛围编程, 在实践中, 对编码能力的要求下降了, 但是对项目规划和产品设计, 团队协作和组织能力, 团队积累与结构化知识的要求反而是提高了, 某种意义上来说, 反而拉大了组织与组织之间的差距. 人工智能更像一个每天都是新入职的, 代码能力扎实的实习生, 如何让其能快速了解代码规范, 项目架构, 周边工具, 沟通方法才是发挥其作用的关键. 开发者角色也从&quot;代码生产者&quot;转变为&quot;意图定义者&quot;, 核心能力迁移到问题验证与处理, 领域建模, 知识结构化和精准表达.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;组织差距:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;强的组织 -&amp;gt; 精准的领域语言 -&amp;gt; 结构化知识库 -&amp;gt; AI 高效产出&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;弱的组织 -&amp;gt; 模糊的需求 -&amp;gt; 碎片化的信息 -&amp;gt; AI 混乱输出&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;开发模式:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;传统模式 [记忆 -&amp;gt; 检索 -&amp;gt; 编码]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;AI 模式 [意图 -&amp;gt; 验证 -&amp;gt; 优化]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;大语言模型的原理和局限也决定了它在 &lt;code&gt;Day 0&lt;/code&gt; 即代码从头开始的效果比较好, 而 &lt;code&gt;Day 1+&lt;/code&gt; 即后续的开发和维护上, 会需要开发者更多的指引.&lt;del&gt;身为程序员最讨厌的就是写文档和别人不写文档&lt;/del&gt;拒绝 &lt;code&gt;YOLO coding&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&quot;geng-jin-yi-bu&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#geng-jin-yi-bu&quot; aria-label=&quot;Anchor link for: geng-jin-yi-bu&quot;&gt;更进一步&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;什么才能称得上思维, 思考的本质, 观点与偏见, 错了也不改与反例的权重, 波普尔的 &lt;code&gt;可证伪性(Falsifiability)&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;乔戈里峰&lt;/li&gt;
&lt;li&gt;杨立昆&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;can-kao-zi-liao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#can-kao-zi-liao&quot; aria-label=&quot;Anchor link for: can-kao-zi-liao&quot;&gt;参考资料&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://mp.weixin.qq.com/s/1xbOMGlFI0No2AuixK6vbw&quot;&gt;Windsurf创始人：我们对Java工程师做了很多优化和适配&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://zh.wikipedia.org/zh-cn/Ef_-_a_fairy_tale_of_the_two.&quot;&gt;ef - a fairy tale of the two.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/PaulDuvall/vibecoding&quot;&gt;Vibe Coding: Automated Research Digest &amp;amp; Windsurf Standards&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://docs.windsurf.com/windsurf/getting-started&quot;&gt;Getting started with Windsurf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.reddit.com/r/Codeium/comments/1h2psgy/windsurf_best_practices/&quot;&gt;Windsurf best practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Vibe_coding&quot;&gt;Vibe coding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.ycombinator.com/library/MO-windsurf-ceo-betting-on-ai-agents-pivoting-in-48-hours-and-the-future-of-coding&quot;&gt;Windsurf CEO: Betting On AI Agents, Pivoting In 48 Hours, And The Future of Coding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://newsletter.pragmaticengineer.com/p/cursor&quot;&gt;Real-world engineering challenges: building Cursor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://zhuanlan.zhihu.com/p/657425622&quot;&gt;解析Yann LeCun的自主机器智能构想&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://zed.dev/&quot;&gt;Zed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.trae.ai/&quot;&gt;TREA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/aws/amazon-q-developer-cli/pull/834&quot;&gt;feat(cli): Add context management feature with profiles #834&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://diamantai.substack.com/p/the-hidden-algorithms-powering-your&quot;&gt;The Hidden Algorithms Powering Your Coding Assistant&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://southbridge-research.notion.site/conducting-smarter-intelligences-than-me&quot;&gt;Conducting smarter intelligences than me: new orchestras&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/google-gemini/gemini-cli&quot;&gt;Gemini CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://kiro.dev/blog/introducing-kiro/&quot;&gt;Introducing Kiro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/kirodotdev/spirit-of-kiro&quot;&gt;Spirit of Kiro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://harper.blog/2025/02/16/my-llm-codegen-workflow-atm/&quot;&gt;My LLM codegen workflow atm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.youtube.com/watch?v=8rABwKRsec4&quot;&gt;The New Code — Sean Grove, OpenAI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.anthropic.com/engineering/claude-code-best-practices&quot;&gt;Claude Code: Best practices for agentic coding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://antirez.com/news/154&quot;&gt;Coding with LLMs in the summer of 2025&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>王司徒的道理</title>
        <published>2024-12-25T12:58:00+09:00</published>
        <updated>2024-12-25T12:58:00+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/wang-lang-had-a-point/"/>
        <id>https://memwey.github.io/posts/wang-lang-had-a-point/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/wang-lang-had-a-point/">&lt;blockquote&gt;
&lt;p&gt;整理自我在知乎问题 &lt;a rel=&quot;external&quot; href=&quot;https://www.zhihu.com/question/34228174/answer/63479897935&quot;&gt;诸葛亮王朗论战, 我感觉王朗说得好有道理怎么办?&lt;/a&gt; 下的回答.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果抛开演义的修饰结合正史, 王司徒的话确实不无道理.&lt;/p&gt;
&lt;p&gt;倘若真如诸葛亮所说 &quot;吾素知汝所行&quot;, 那王司徒的前半生可以好好探讨一下.&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;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>2024 诺贝尔物理学奖小记</title>
        <published>2024-10-09T20:18:58+09:00</published>
        <updated>2024-10-09T20:18:58+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/2024-nobel-prize-in-physics/"/>
        <id>https://memwey.github.io/posts/2024-nobel-prize-in-physics/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/2024-nobel-prize-in-physics/">&lt;p&gt;杰弗里·辛顿出自书香世家, 他有一位堂姑, 叫琼·辛顿, 当然, 中国人应该更熟悉他的这位堂姑的中文名, 寒春&lt;/p&gt;
&lt;p&gt;感觉像是唐纳德·特朗普的叔叔约翰·特朗普为中国核物理事业做出了贡献一样有着奇妙的联系&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/2024-nobel-prize-in-physics/images/1.jpg&quot; alt=&quot;Joke-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/2024-nobel-prize-in-physics/images/2.jpg&quot; alt=&quot;Joke-2&quot; /&gt;&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>一个对象同时只能有一笔有效支付</title>
        <published>2024-08-18T16:20:33+09:00</published>
        <updated>2024-08-18T16:20:33+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/one-active-payment/"/>
        <id>https://memwey.github.io/posts/one-active-payment/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/one-active-payment/">&lt;h2 id=&quot;cong-yi-ge-kan-qi-lai-hen-jian-dan-de-xu-qiu-shuo-qi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#cong-yi-ge-kan-qi-lai-hen-jian-dan-de-xu-qiu-shuo-qi&quot; aria-label=&quot;Anchor link for: cong-yi-ge-kan-qi-lai-hen-jian-dan-de-xu-qiu-shuo-qi&quot;&gt;从一个看起来很简单的需求说起&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;一个订单要能支付. 听上去就是加一个 &lt;code&gt;status&lt;/code&gt; 字段: 未支付, 支付中, 已支付, 支付失败.&lt;/p&gt;
&lt;p&gt;真做起来会发现不够用:&lt;/p&gt;
&lt;ul&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;li&gt;后来账单和预约也要支付, 流程几乎一样.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这几件事合起来, 就不是一个状态字段能表达的了.&lt;/p&gt;
&lt;h2 id=&quot;xian-shuo-qing-chu-yi-bi-zhi-fu-shi-shen-me&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#xian-shuo-qing-chu-yi-bi-zhi-fu-shi-shen-me&quot; aria-label=&quot;Anchor link for: xian-shuo-qing-chu-yi-bi-zhi-fu-shi-shen-me&quot;&gt;先说清楚「一笔支付」是什么&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;一笔支付指的是&lt;strong&gt;一次支付尝试&lt;/strong&gt;, 对应一条交易记录, 它自己有:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;交易 ID&lt;/strong&gt; —— 全局唯一, 后面会看到它同时是发给渠道的幂等键;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态&lt;/strong&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;p&gt;这一点值得强调: 交易记录进了终态就不再改. 所以对账的时候能还原出&quot;这个订单被尝试支付过几次, 每次分别是什么结果&quot;. 如果复用同一条记录反复改状态, 这些信息就没了, 出了问题只能靠日志去猜.&lt;/p&gt;
&lt;h2 id=&quot;li-shi-liu-shui-yu-dang-qian-you-xiao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#li-shi-liu-shui-yu-dang-qian-you-xiao&quot; aria-label=&quot;Anchor link for: li-shi-liu-shui-yu-dang-qian-you-xiao&quot;&gt;历史流水与「当前有效」&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;第二件要分清楚的事: &lt;strong&gt;一个业务对象会有很多笔支付, 但任一时刻最多只有一笔是「当前有效」的.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;历史流水: 每次发起支付都是一条记录, 有自己的状态和终态, 不删不改;&lt;/li&gt;
&lt;li&gt;当前有效: 这个订单现在到底压在哪一笔上.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把这两件事混在一起, 就会写出&quot;取最新一条支付记录&quot;这样的查询. 它在大多数时候是对的, 在并发的时候不是.&lt;/p&gt;
&lt;p&gt;所以把「当前有效」显式建成一个东西, 一个从业务对象指向当前交易的指针:&lt;/p&gt;
&lt;script src=https://memwey.github.io/js/mermaid.js&gt;&lt;/script&gt;
&lt;pre class=&quot;mermaid&quot;&gt;

flowchart LR
    Target[&quot;业务对象&lt;br/&gt;订单 / 账单 / 预约&quot;]
    Gateway[&quot;支付渠道&quot;]
    Result[&quot;异步结果&lt;br/&gt;回调 / 查询 / 前端跳转&quot;]
    Unknown[&quot;调用栈失败&lt;br/&gt;受理状态未知&quot;]

    subgraph TxModule[&quot;支付模块&quot;]
        Active[&quot;ActivePayment&lt;br/&gt;当前有效交易指针&quot;]
        Tx[&quot;Transaction&lt;br/&gt;历史流水 + 当前状态&quot;]
        Final[&quot;终态&lt;br/&gt;成功 / 失败 / 取消 / 退款&quot;]
        Active --&gt; Tx
        Tx --&gt; Final
    end

    Target --&gt; TxModule
    Tx --&gt;|&quot;发起支付&lt;br/&gt;交易 ID 作为幂等键&quot;| Gateway
    Gateway --&gt; Result
    Result --&gt;|&quot;收敛结果, 更新状态&quot;| Tx
    Tx --&gt;|&quot;支付中: 幂等拦截&quot;| Target
    Gateway -.-&gt; Unknown
    Unknown -.-&gt;|&quot;调用失败 ≠ 业务失败&lt;br/&gt;保持支付中&quot;| Tx

&lt;/pre&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;strong&gt;创建新交易和切换指针在同一个事务里.&lt;/strong&gt; 否则会出现&quot;交易建好了但没人指向它&quot;, 或者反过来.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;切换时带上旧的交易一起匹配&lt;/strong&gt;, 匹配不上就不切.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;第三条是关键. 两个请求同时想发起支付, 都读到&quot;当前没有有效交易&quot;, 都去创建. 没有第三条, 后一个会盖掉前一个, 于是有两笔交易都以为自己是有效的. 把旧值作为更新条件, 就只有一个能成功 —— 是 CAS 的思路, 只不过落在数据库上.&lt;/p&gt;
&lt;h2 id=&quot;yi-ge-qian-ti-qu-dao-bi-xu-zhi-chi-mi-deng-jian&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#yi-ge-qian-ti-qu-dao-bi-xu-zhi-chi-mi-deng-jian&quot; aria-label=&quot;Anchor link for: yi-ge-qian-ti-qu-dao-bi-xu-zhi-chi-mi-deng-jian&quot;&gt;一个前提: 渠道必须支持幂等键&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;接外部支付渠道之前, 有一件事要先确认: &lt;strong&gt;渠道必须支持幂等键&lt;/strong&gt;. 后面讲的所有做法都建立在这个前提上.&lt;/p&gt;
&lt;p&gt;具体说, 发起支付的时候要带一个自己生成的唯一标识, 渠道保证同一个标识只受理一次. 第二次带着同样的标识过来, 它返回的是第一次的结果, 而不是再扣一次钱. 这个东西在各家渠道的文档里通常叫「商户订单号」或者 idempotency key.&lt;/p&gt;
&lt;p&gt;前面说的交易 ID 正好就是这个键, 两件事是配套的:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;同一笔支付的重发&lt;/strong&gt; —— 同一个交易 ID —— 渠道认得出来, 不会重复受理;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;失败后重新发起&lt;/strong&gt; —— 新的一笔, 新的交易 ID —— 渠道当成一个新请求处理.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以&quot;一笔支付一个 ID&quot;不只是为了记账. 没有它, 你没办法告诉渠道&quot;这是同一次尝试的重发&quot;; 而没有渠道侧的幂等保证, 你根本不敢重发.&lt;/p&gt;
&lt;p&gt;如果渠道不支持幂等键, 后面的做法就都要打折扣: 超时之后不敢重发, 只能干等查询或回调; 查询接口还必须能按你自己的标识来查, 否则拿回一堆渠道流水, 你对不上是哪一笔; 最坏的情况是只能人工对账.&lt;/p&gt;
&lt;p&gt;这件事在接入前确认, 比写完之后再补要容易得多.&lt;/p&gt;
&lt;h2 id=&quot;san-ge-bu-deng-yu&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#san-ge-bu-deng-yu&quot; aria-label=&quot;Anchor link for: san-ge-bu-deng-yu&quot;&gt;三个「不等于」&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;接了外部支付渠道之后, 真正的麻烦才开始. 踩过的坑可以归纳成三句话.&lt;/p&gt;
&lt;h3 id=&quot;yi-shou-li-cheng-gong-zhi-fu-cheng-gong&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#yi-shou-li-cheng-gong-zhi-fu-cheng-gong&quot; aria-label=&quot;Anchor link for: yi-shou-li-cheng-gong-zhi-fu-cheng-gong&quot;&gt;一, 受理成功 ≠ 支付成功&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;调用渠道的支付接口返回成功, 只说明对方&lt;strong&gt;受理&lt;/strong&gt;了这笔请求. 用户可能还没输密码, 3DS 验证可能还没过, 发卡行可能还要几秒钟.&lt;/p&gt;
&lt;p&gt;所以接口返回之后, 交易的状态是「支付中」, 不是「成功」. 真正的结果通过回调, 主动查询或者前端跳转带回来.&lt;/p&gt;
&lt;p&gt;这条比较容易接受. 下一条就不是了.&lt;/p&gt;
&lt;h3 id=&quot;er-diao-yong-shi-bai-ye-wu-shi-bai&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#er-diao-yong-shi-bai-ye-wu-shi-bai&quot; aria-label=&quot;Anchor link for: er-diao-yong-shi-bai-ye-wu-shi-bai&quot;&gt;二, 调用失败 ≠ 业务失败&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这是最危险的一条.&lt;/p&gt;
&lt;p&gt;调渠道的接口, 超时了. 或者连接断了. 或者收到一个 5xx. 直觉是把这笔标成失败, 让用户重试.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这个直觉会导致重复扣款.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因为超时只说明你没收到响应, 不说明对方没收到请求. 对方很可能已经受理了, 正在处理, 过一会还会给你回调:&lt;/p&gt;
&lt;script src=https://memwey.github.io/js/mermaid.js&gt;&lt;/script&gt;
&lt;pre class=&quot;mermaid&quot;&gt;

sequenceDiagram
    participant B as 业务系统
    participant G as 支付渠道
    B-&gt;&gt;G: 发起支付
    Note over G: 已受理, 开始处理
    G--xB: 超时 / 连接断开
    Note over B: 若此时判失败&lt;br/&gt;用户重试 → 重复扣款
    G-&gt;&gt;B: 回调: 支付成功
    Note over B: 保持「支付中」&lt;br/&gt;才能正确收敛

&lt;/pre&gt;
&lt;p&gt;正确的做法是: 调用栈失败的时候&lt;strong&gt;不下结论&lt;/strong&gt;. 保持「支付中」, 把判断留给查询接口和回调. 宁可让用户多等一会, 也不能让他付两次.&lt;/p&gt;
&lt;p&gt;顺带一提, 这个问题是在测试阶段发现的, 不是线上. 能在测试阶段撞上, 是因为特意去构造了渠道受理超时的场景 —— 这类路径不主动造是撞不到的, 而它一旦在生产上出现, 后果是资金差错.&lt;/p&gt;
&lt;h3 id=&quot;san-zhi-fu-zhong-bi-xu-shi-yi-dao-mi-deng-dang-ban&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#san-zhi-fu-zhong-bi-xu-shi-yi-dao-mi-deng-dang-ban&quot; aria-label=&quot;Anchor link for: san-zhi-fu-zhong-bi-xu-shi-yi-dao-mi-deng-dang-ban&quot;&gt;三, 「支付中」必须是一道幂等挡板&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;有了前面两条, 「支付中」这个状态就承担了很重的职责: 只要还在支付中, 任何重复的支付请求都不能建新单, 也不能重新发起.&lt;/p&gt;
&lt;p&gt;用户在渠道的支付页面上刷新, 后退再点一次, 前端超时自动重试 —— 这些都会打到同一个入口. 幂等在这里不是加分项, 是整套设计成立的前提.&lt;/p&gt;
&lt;h2 id=&quot;duo-chong-zhi-fu-fang-shi-yi-ge-sheng-ming-zhou-qi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#duo-chong-zhi-fu-fang-shi-yi-ge-sheng-ming-zhou-qi&quot; aria-label=&quot;Anchor link for: duo-chong-zhi-fu-fang-shi-yi-ge-sheng-ming-zhou-qi&quot;&gt;多种支付方式, 一个生命周期&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;支付方式会越来越多, 而且形态差别很大:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;服务端直接发起&lt;/strong&gt;: 拿到卡号或者已保存的支付凭证, 直接调渠道扣款;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需要用户跳走&lt;/strong&gt;: 生成一个页面地址, 用户跳到渠道那边完成, 再跳回来;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需要用户在别处操作&lt;/strong&gt;: 生成一个二维码或者付款码, 用户拿另一个 App 扫;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异步到账&lt;/strong&gt;: 生成一个待付款凭证, 用户去便利店或者网银付, 可能几小时甚至几天之后才到账.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它们的差异全部在&lt;strong&gt;发起&lt;/strong&gt;那一步: 参数不一样; 有的调完就结束了, 有的要先生成一个地址或者二维码存进交易里; 有的发起之后还要等用户在另一个地方操作.&lt;/p&gt;
&lt;p&gt;但发起之后的事情完全一样 —— 都进入支付中, 都等异步结果, 都要回写, 都要幂等. 所以差异收敛在入口, 共性收敛在生命周期:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;发起支付   -&amp;gt;  按支付方式分支&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;收敛结果   -&amp;gt;  成功 / 失败 / 取消 / 退款&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;新增一种支付方式, 只需要在发起那一步加一个分支, 不用碰状态机.&lt;/p&gt;
&lt;h2 id=&quot;duo-ge-ye-wu-dui-xiang-yi-tao-zhi-fu&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#duo-ge-ye-wu-dui-xiang-yi-tao-zhi-fu&quot; aria-label=&quot;Anchor link for: duo-ge-ye-wu-dui-xiang-yi-tao-zhi-fu&quot;&gt;多个业务对象, 一套支付&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;后来账单和预约也要支付.&lt;/p&gt;
&lt;p&gt;最省事的做法是把订单那套复制一份. 但支付这块的复杂度上面已经讲过了 —— 复制一份意味着幂等, CAS, 异步回写这些坑要重新踩一遍, 而且三份代码会慢慢长得不一样.&lt;/p&gt;
&lt;p&gt;更好的做法是&lt;strong&gt;把方向反过来&lt;/strong&gt;: 支付模块不去了解业务, 而是定义一份契约, 让业务对象来实现它.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; PayTarget&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; interface&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;	PayCheck&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Context&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;   //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 现在能不能支付&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;	Pay&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Context&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;        //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 锁定为支付中&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;	PaySuccess&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Context&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 支付成功后回写&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;	PayFailed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Context&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;  //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 支付失败后回写&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;支付模块拿到一个目标 ID 和它的契约实现, 就能跑完整个流程. 业务模块只关心自己的状态和金额从哪来, 完全不需要知道渠道, 回调, 幂等这些东西:&lt;/p&gt;
&lt;script src=https://memwey.github.io/js/mermaid.js&gt;&lt;/script&gt;
&lt;pre class=&quot;mermaid&quot;&gt;

flowchart LR
    Tx[&quot;支付模块&lt;br/&gt;统一编排&quot;]
    Target[&quot;Target&lt;br/&gt;订单 ID / 账单 ID / 预约 ID&quot;]
    Contract[&quot;PayTarget 契约&lt;br/&gt;PayCheck / Pay&lt;br/&gt;PaySuccess / PayFailed&quot;]
    Order[&quot;订单&quot;]
    Invoice[&quot;账单&quot;]
    Booking[&quot;预约&quot;]
    Method[&quot;支付方式&lt;br/&gt;统一发起入口&quot;]
    Direct[&quot;服务端直接发起&quot;]
    Redirect[&quot;跳转渠道页面&quot;]
    Code[&quot;二维码 / 付款码&quot;]
    Offline[&quot;待付款凭证&lt;br/&gt;异步到账&quot;]
    Gateway[&quot;支付渠道&quot;]

    Tx --&gt; Target
    Tx --&gt;|&quot;检查 / 锁定 / 回写&quot;| Contract
    Contract -.-&gt; Order
    Contract -.-&gt; Invoice
    Contract -.-&gt; Booking
    Tx --&gt; Method
    Method --&gt; Direct
    Method --&gt; Redirect
    Method --&gt; Code
    Method --&gt; Offline
    Direct --&gt; Gateway
    Redirect --&gt; Gateway
    Code --&gt; Gateway
    Offline --&gt; Gateway

&lt;/pre&gt;
&lt;p&gt;接一个新的业务对象, 要写的就是那四个方法.&lt;/p&gt;
&lt;p&gt;这其实就是依赖倒置: 把&quot;支付调用业务&quot;定义成接口, 而不是让每个业务自己去调支付.&lt;/p&gt;
&lt;h2 id=&quot;xiao-jie&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#xiao-jie&quot; aria-label=&quot;Anchor link for: xiao-jie&quot;&gt;小结&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一笔支付就是一次尝试, 有自己的 ID 和终态; 重试是新的一笔, 不是把旧的改回去.&lt;/li&gt;
&lt;li&gt;接入前先确认渠道支持幂等键, 交易 ID 就是这个键. 这是下面所有做法的前提.&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;/ul&gt;
&lt;p&gt;这几件事单看都不复杂, 但它们是有先后的: 先有渠道侧的幂等键, 才谈得上重发; 先有唯一的当前有效交易, 才谈得上幂等; 先有幂等, 才敢在超时的时候不下结论.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>岩里武则与马尼拉战役</title>
        <published>2024-06-02T00:50:39+09:00</published>
        <updated>2026-07-30T17:21:20+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/iwasato-takenori-and-battle-of-manila/"/>
        <id>https://memwey.github.io/posts/iwasato-takenori-and-battle-of-manila/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/iwasato-takenori-and-battle-of-manila/">&lt;p&gt;岩里武则, 岩里政男的哥哥, 在二战期间加入旧日本帝国海军, 前往日占菲律宾, 在马尼拉战役中被美军打死, 二战后, 以岩里武则的名字被供奉在靖国神社; 据称, 改姓岩里, 因为里字保留了李这个音, 而岩则源自李家祖籍福建龙岩&lt;/p&gt;
&lt;p&gt;发生在 1945 年初的马尼拉战役, 日军指挥官岩渊三次带领海军陆战队违反上级命令, 在人口稠密且拥有大量木制易燃建筑的马尼拉市区与美军进行巷战. 在美军的绝对优势下, 日军将愤怒发泄在菲律宾平民身上; 将近一个月的战斗中, 马尼拉城被彻底摧毁, 超过十万名菲律宾平民死亡, 其中大部分死于日军屠杀; 这个数字超过了东京大轰炸和广岛与长崎的核爆任一单次死亡&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>在 TCP 上划报文边界</title>
        <published>2023-04-06T21:37:19+09:00</published>
        <updated>2023-04-06T21:37:19+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/message-framing-over-tcp/"/>
        <id>https://memwey.github.io/posts/message-framing-over-tcp/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/message-framing-over-tcp/">&lt;p&gt;在 TCP 上跑一个自己的业务协议, 第一件要决定的事往往不是用什么序列化, 也不是字段怎么排, 而是&lt;strong&gt;一条消息到哪里结束&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;这听上去不太像个问题, 但它是自定义协议里最容易写错的地方, 而且错了以后在测试环境里往往一切正常.&lt;/p&gt;
&lt;h2 id=&quot;xian-ba-wen-ti-shuo-qing-chu&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#xian-ba-wen-ti-shuo-qing-chu&quot; aria-label=&quot;Anchor link for: xian-ba-wen-ti-shuo-qing-chu&quot;&gt;先把问题说清楚&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;常见的说法是&quot;TCP 粘包&quot;. 这个说法其实不太准确, 因为它暗示 TCP 本来应该给你&quot;包&quot;, 只是不小心粘上了.&lt;/p&gt;
&lt;p&gt;TCP 从来没承诺过包. 它承诺的只有三件事: 你写进去的字节会&lt;strong&gt;按顺序&lt;/strong&gt;到达, 不丢, 不重. 至于这些字节怎么切分成一次次 &lt;code&gt;Read&lt;/code&gt;, 完全不受你控制:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;发送侧, 两次 &lt;code&gt;Write&lt;/code&gt; 可能被合并进同一个 TCP 段 (Nagle 算法就是干这个的).&lt;/li&gt;
&lt;li&gt;一次 &lt;code&gt;Write&lt;/code&gt; 也可能被拆成多段, 因为 MSS, 因为发送窗口, 因为路径 MTU.&lt;/li&gt;
&lt;li&gt;中间的设备还可能再切一刀.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以下面这段代码是错的:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;buf&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; make&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 4096&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; conn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Read&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;buf&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;   //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 读到的不一定是一条完整消息&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;buf&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;            //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 可能是半条, 也可能是两条半&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它在本机测试里几乎永远是对的 —— 消息短, 网络快, 一次一条. 一上生产, 报文变长, 网络变差, 就开始随机地解析失败. 而且因为是随机的, 查起来非常烦人.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;边界是应用层自己的事.&lt;/strong&gt; 划边界的办法总共就那么几种.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;fixed     |&amp;lt;-- 64 bytes --&amp;gt;|&amp;lt;-- 64 bytes --&amp;gt;|&amp;lt;-- 64 bytes --&amp;gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;prefixed  |len=12|&amp;lt;-- 12 bytes --&amp;gt;|len=5|&amp;lt;-5B-&amp;gt;|len=40|&amp;lt;-- 40 bytes --&amp;gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;delimited | hello\r\n | world\r\n | a-longer-line\r\n |&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;yi-ding-chang-bao-wen&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#yi-ding-chang-bao-wen&quot; aria-label=&quot;Anchor link for: yi-ding-chang-bao-wen&quot;&gt;一, 定长报文&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;每条报文固定 N 字节, 读满 N 就是一条. 没有比这更简单的了:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; frameSize&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 64&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; readFrame&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Reader&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	buf&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; make&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; frameSize&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	if&lt;/span&gt;&lt;span&gt; _&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ReadFull&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; buf&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	return&lt;/span&gt;&lt;span&gt; buf&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;好处&lt;/strong&gt;是解析成本为零, 不需要任何状态机; 缓冲区可以预分配甚至复用; 而且天然没有&quot;对方声明了一个 2GB 的长度&quot;这类问题 —— 长度根本不由对方决定.&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;/p&gt;
&lt;h2 id=&quot;er-ding-chang-tou-bu-chang-du-zi-duan&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#er-ding-chang-tou-bu-chang-du-zi-duan&quot; aria-label=&quot;Anchor link for: er-ding-chang-tou-bu-chang-du-zi-duan&quot;&gt;二, 定长头部 + 长度字段&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;最常用的一种. 头部长度固定, 头部里带着后面还有多少字节, 于是读取变成明确的两步:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;const&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;	headerSize&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;	maxBodyLen&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 20&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 必须有上限&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; readMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Reader&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	var&lt;/span&gt;&lt;span&gt; head&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;headerSize&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	if&lt;/span&gt;&lt;span&gt; _&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ReadFull&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; head&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	n&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; binary&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;BigEndian&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Uint32&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;head&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	if&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span&gt; maxBodyLen&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; fmt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Errorf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;frame too large: &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;%d&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	body&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; make&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	if&lt;/span&gt;&lt;span&gt; _&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ReadFull&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; body&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	return&lt;/span&gt;&lt;span&gt; body&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&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;gRPC&lt;/td&gt;&lt;td&gt;1 字节压缩标志 + 4 字节大端长度&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;TLS record&lt;/td&gt;&lt;td&gt;1 字节类型 + 2 字节版本 + 2 字节长度&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;MySQL&lt;/td&gt;&lt;td&gt;3 字节小端长度 + 1 字节序号&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Redis bulk string&lt;/td&gt;&lt;td&gt;&lt;code&gt;$&amp;lt;len&amp;gt;\r\n&lt;/code&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; 是 body 的长度, 还是包含头部的总长? 差一个头长度, 结果就是每读一条报文错位一次, 而且第一条往往还是对的. 有些协议干脆两个都给, 各有各的用处: 总长用来切帧, body 长用来在解压或者解密之后确定有效数据到哪里为止 —— 因为变换过后的长度和线上的长度不是一回事.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;长度字段的编码要和字节序一起说清楚.&lt;/strong&gt; 大端还是小端 (网络字节序习惯上是大端, 但 MySQL 就是小端), 是二进制还是 ASCII 数字, 老一点的金融协议里还会用 BCD 把长度压成半个字节.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一定要校验上限.&lt;/strong&gt; 不校验就是一个远程内存耗尽漏洞: 对方发一个 &lt;code&gt;0xFFFFFFFF&lt;/code&gt;, 你就 &lt;code&gt;make([]byte, 4G)&lt;/code&gt;. 这不需要什么高深的攻击手法, 一个写错的客户端就能把你打挂. 上面代码里 &lt;code&gt;maxBodyLen&lt;/code&gt; 那三行不是可选的.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;长度也可能是分段的.&lt;/strong&gt; MySQL 的长度只有 3 字节, 所以超过 16MB 的包会被拆成多个, 最后一个包的长度小于 &lt;code&gt;0xFFFFFF&lt;/code&gt; 才算结束. 设计协议的时候, 与其给长度字段留很宽的位数, 不如明确规定一个上限加分段规则.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;考虑一下错位之后怎么办.&lt;/strong&gt; 一旦解析错位, 后面读到的全是垃圾. 头部里放一个魔数或版本号可以帮你发现这件事. 至于发现之后怎么恢复 —— 老实说, 大多数情况下直接断开重连比&quot;尝试重新同步&quot;要安全得多, 因为你已经不知道自己在流的什么位置了.&lt;/p&gt;
&lt;h2 id=&quot;san-fen-ge-fu&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#san-fen-ge-fu&quot; aria-label=&quot;Anchor link for: san-fen-ge-fu&quot;&gt;三, 分隔符&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;用一个约定不会出现在正文里的字节序列切分, 最常见的就是 &lt;code&gt;\r\n&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sc&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; bufio&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;NewScanner&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;conn&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Buffer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;make&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 4096&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 64&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1024&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 显式给出上限&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;for&lt;/span&gt;&lt;span&gt; sc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Scan&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;	handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Bytes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;if&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; sc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Err&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;	//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 注意 bufio.ErrTooLong&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;好处&lt;/strong&gt;是人可读. 可以直接 telnet 上去敲命令, 抓包能直接看懂, 出问题的时候这一点非常值钱. 而且发送方不需要预先知道长度, 天然适合流式产生的内容.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代价&lt;/strong&gt;主要是两条.&lt;/p&gt;
&lt;p&gt;一是&lt;strong&gt;正文里出现分隔符怎么办&lt;/strong&gt;. 只有两个选择: 禁止 (规定正文不能含 &lt;code&gt;\n&lt;/code&gt;), 或者转义 (然后接收方要解转义). 前者限制了能传什么, 后者带来解析复杂度和长度膨胀, 而且转义规则一旦有歧义就是安全问题的温床 —— HTTP 请求走私那一类漏洞, 根子上就是收发双方对边界的理解不一致.&lt;/p&gt;
&lt;p&gt;二是&lt;strong&gt;同样必须设上限&lt;/strong&gt;. 对方发一个永远不带换行的流, 你就一直往缓冲区里塞. &lt;code&gt;bufio.Scanner&lt;/code&gt; 默认 64KB 的上限就是干这个用的, 别把它调成无限大; Go 的 &lt;code&gt;net/http&lt;/code&gt; 对请求行和 header 也都有长度限制, 同理.&lt;/p&gt;
&lt;p&gt;顺便, 扫描分隔符是 O(n) 的, 数据量大的时候比读一个长度字段贵不少.&lt;/p&gt;
&lt;h2 id=&quot;shi-ji-shang-hun-zhao-yong-cai-shi-chang-tai&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#shi-ji-shang-hun-zhao-yong-cai-shi-chang-tai&quot; aria-label=&quot;Anchor link for: shi-ji-shang-hun-zhao-yong-cai-shi-chang-tai&quot;&gt;实际上, 混着用才是常态&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;纯分隔符协议处理二进制正文太难受, 所以文本协议基本都是混合的:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HTTP/1.1&lt;/strong&gt;: header 用 &lt;code&gt;\r\n&lt;/code&gt; 分行, 空行表示 header 结束; body 的长度由 &lt;code&gt;Content-Length&lt;/code&gt; 给出. 而 chunked 编码本身又是一个混合体 —— 每个 chunk 是&quot;十六进制长度 + &lt;code&gt;\r\n&lt;/code&gt; + 数据 + &lt;code&gt;\r\n&lt;/code&gt;&quot;, 长度前缀和分隔符一起上.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis RESP&lt;/strong&gt;: 行结构用 &lt;code&gt;\r\n&lt;/code&gt;, 但 bulk string 是 &lt;code&gt;$5\r\nhello\r\n&lt;/code&gt; —— 先给长度, 再读定长数据, 后面那个 &lt;code&gt;\r\n&lt;/code&gt; 纯粹是为了人看着舒服. 正因为有长度, 值里面含 &lt;code&gt;\r\n&lt;/code&gt; 完全没问题.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这两个例子里的逻辑是一样的: &lt;strong&gt;控制信息用分隔符, 因为要人可读; 数据本身用长度, 因为要能装任意字节.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;还有一种极端做法值得一提: &lt;strong&gt;一条连接只发一条消息, 用连接关闭当边界&lt;/strong&gt;. HTTP/1.0 的默认行为就是这样, 没有 &lt;code&gt;Content-Length&lt;/code&gt; 的响应读到 FIN 为止. 它的代价是每条消息一次三次握手, 而且你没法区分&quot;对方发完了&quot;和&quot;对方挂了&quot;.&lt;/p&gt;
&lt;h2 id=&quot;zen-me-xuan&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zen-me-xuan&quot; aria-label=&quot;Anchor link for: zen-me-xuan&quot;&gt;怎么选&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;大致是这么几条:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;正文是&lt;strong&gt;二进制&lt;/strong&gt; → 长度前缀. 没什么可犹豫的.&lt;/li&gt;
&lt;li&gt;正文是&lt;strong&gt;文本&lt;/strong&gt;, 而且你希望能 telnet 上去调试 → 分隔符, 但数据部分还是给长度.&lt;/li&gt;
&lt;li&gt;字段&lt;strong&gt;完全固定且很短&lt;/strong&gt; → 定长, 省掉一切解析.&lt;/li&gt;
&lt;li&gt;想都不想就用 protobuf / gRPC / HTTP → 也挺好, 它们只是把上面这些选择替你做完了, 该有的上限校验也都做了.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;bu-guan-xuan-na-chong-zhe-san-jian-shi-du-yao-zuo&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#bu-guan-xuan-na-chong-zhe-san-jian-shi-du-yao-zuo&quot; aria-label=&quot;Anchor link for: bu-guan-xuan-na-chong-zhe-san-jian-shi-du-yao-zuo&quot;&gt;不管选哪种, 这三件事都要做&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;一, 用 &lt;code&gt;ReadFull&lt;/code&gt;, 别相信单次 &lt;code&gt;Read&lt;/code&gt;.&lt;/strong&gt; 这是最常见的一个 bug, 而且如上所述, 它在测试环境里不会暴露.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;二, 设上限.&lt;/strong&gt; 长度字段的上限, 单行的上限, 缓冲区的上限. 少一个就是一条拒绝服务的路径.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;三, 设读超时.&lt;/strong&gt; 一个只发了半条报文然后就不动了的连接, 如果没有 deadline, 会永远占着你的 goroutine 和缓冲区. 而且这种连接在操作系统看来完全正常, 你不主动超时就发现不了.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;conn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;SetReadDeadline&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Now&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Second&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这三条都是从&quot;对端不一定是善意的, 也不一定是正确的&quot;这个前提推出来的. 自己写协议的时候很容易默认对面跑的是自己写的客户端, 但只要那个端口暴露在网络上, 这个假设就不成立.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>写一个 ISO 8583 的 SDK</title>
        <published>2022-12-31T17:40:11+09:00</published>
        <updated>2022-12-31T17:40:11+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/building-iso8583-sdk-in-go/"/>
        <id>https://memwey.github.io/posts/building-iso8583-sdk-in-go/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/building-iso8583-sdk-in-go/">&lt;p&gt;业务上要和日本某卡组织的交换中心直连, 走 ISO 8583.&lt;/p&gt;
&lt;p&gt;最初分配给我的任务是写一个测试挡板, 让业务那边先能跑起来, 不用等到真的接上对端. 但一动手就发现顺序反了 —— 挡板要能收下我方发出去的报文, 就得会解包; 要能回一条对端格式的响应, 就得会封包; 要像对端那样建连接, 开局, 换密钥, 那这些也一个都少不了. 换句话说, &lt;strong&gt;不先把这套东西做完, 连挡板都写不出来&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;市面上的 Go 库也不太符合需求: 要么只做报文的封装解包, 要么假设你跑在一个标准的 request-response 模型上, 而对端的规格书里写的是全双工, 双向主动发起, 定期换密钥.&lt;/p&gt;
&lt;p&gt;所以最后做出来的不是一个报文库, 也不只是一个挡板. 封包解包只是最底下的一层, 上面还压着连接, 会话, 密钥, 以及一整套给业务用的接口 —— 是一个 SDK. 而当初要的那个挡板, 反倒成了它的一个使用者.&lt;/p&gt;
&lt;p&gt;趁年末还记得, 把几个设计决定记一下. 具体的接续规格是有 NDA 的, 所以下面的代码都是脱敏和简化过的, 只留设计本身.&lt;/p&gt;
&lt;h2 id=&quot;iso-8583-shi-shen-me&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#iso-8583-shi-shen-me&quot; aria-label=&quot;Anchor link for: iso-8583-shi-shen-me&quot;&gt;ISO 8583 是什么&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;简单说, 是刷卡交易的报文标准. 你在便利店刷一张卡, 从 POS 机到收单机构到卡组织到发卡行, 这一路上跑的多半就是 ISO 8583 或者它的某个方言.&lt;/p&gt;
&lt;p&gt;报文的结构非常有年代感:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+--------+------------------+---------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|  MTI   |      Bitmap      |          Fields           |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| 4 bytes|  8 or 16 bytes   |         变长               |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+--------+------------------+---------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MTI&lt;/strong&gt; (Message Type Indicator), 4 位数字, 说明这条报文是什么, 比如授权请求, 冲正, 网络管理.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bitmap&lt;/strong&gt;, 64 个 bit, 第 N 个 bit 为 1 表示第 N 个域出现在报文里. 第 1 个 bit 比较特殊, 它为 1 表示后面还跟了第二个 bitmap, 即这条报文用到了 65~128 域.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fields&lt;/strong&gt;, 按域号从小到大依次排列. 每个域是定长还是变长, 是数字还是字符, 用什么编码, 都由规格书规定.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它是个 1980 年代的标准, 所以处处是省字节的痕迹: 域出现与否用 bit 表示而不是用 key, 数字用 BCD 压缩成半个字节, 变长域的长度头也用 BCD. 在今天看这些设计很别扭, 但在专线按字节计费的年代是合理的.&lt;/p&gt;
&lt;p&gt;不过有一件事要先分清楚: &lt;strong&gt;ISO 8583 是公开的标准, 但它管的基本上只有报文&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;它规定了 MTI, bitmap, 每个域号大致对应什么含义, 以及各类域的类型和长度表示法. 至于连接怎么建, 会话怎么开怎么关, 心跳多久发一次, 密钥怎么下发怎么轮换, 出了错回什么码, 日切怎么对账 —— 一概不管. 这些全部由使用这张网的双方自己约定.&lt;/p&gt;
&lt;p&gt;而且就算只看报文这一层, 各方也都有自己的变体. 同一个域号, 这张网拿来放这个, 那张网拿来放那个; 有的域整张网都不用; 长度头的编码, 字符集, 定长域往哪边补位, 也各家不同.&lt;/p&gt;
&lt;p&gt;所以&quot;支持 ISO 8583&quot;这句话本身几乎不提供什么信息, 真正要问的是&quot;支持哪一张网的 ISO 8583&quot;. 而这个问题的答案, 只写在接续规格书里.&lt;/p&gt;
&lt;h2 id=&quot;zui-da-de-kun-nan-shi-wen-dang&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zui-da-de-kun-nan-shi-wen-dang&quot; aria-label=&quot;Anchor link for: zui-da-de-kun-nan-shi-wen-dang&quot;&gt;最大的困难是文档&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;必须先说这个, 因为整个项目里最难的部分从来不是代码.&lt;/p&gt;
&lt;p&gt;规格书是几百页的日文 PDF, 还远不止一份. 印象里前后拿到的有十来册: 基本篇和电文篇算主干, 然后&lt;strong&gt;每多一个功能就多一册&lt;/strong&gt; —— IC 卡是单独一册, 非接触的タッチ又是一册, PIN 相关的再一册, 加上各种别册和历年的改订通知. 摞起来轻松上千页.&lt;/p&gt;
&lt;p&gt;更麻烦的是它们的年龄. 这套东西是上世纪就在跑的, 文档也是那时候留下来一路改过来的, 于是你会读到这样的内容:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;排版是等宽字体画的表格, 有些页干脆是扫描件, 复制不出文字, 只能对着屏幕一个字一个字敲.&lt;/li&gt;
&lt;li&gt;术语是昭和的. 電文, 仕向, 宛先, 加盟店, 開局, 閉局, 締め, カットオーバー —— 每一个都要先搞清楚它在这个语境下具体指什么, 而不是字面意思.&lt;/li&gt;
&lt;li&gt;片假名外来语和汉字词混着用. レコード, フィールド, ヘッダ 是一套, 電文長, 可変長 是另一套, 指的却是同一层东西.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;甚至还有 X.25 的描述.&lt;/strong&gt; 分组交换网, 上世纪七十年代的东西. 早就跑在 TCP/IP 上了, 那一段却还留着. 读的时候要一边分辨哪些已经不适用, 一边小心那些看着过时, 实际上对端还在严格执行的部分.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但真正累人的不是这些, 而是&lt;strong&gt;这些文档之间没有链接&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;倒不是说它们写得有矛盾 —— 恰恰相反, 内容相当严谨, 该规定的都规定了. 问题是同一件事的信息被按功能切开, 摊在好几册里.&lt;/p&gt;
&lt;p&gt;举个实际的例子: 要搞清楚一笔带 PIN 的 IC 卡交易怎么发, 域的位置和长度在电文篇, PIN block 怎么算, 用哪把密钥加密在 PIN 那一册, IC 卡数据域里那串 TLV 的构成在 IC 卡那一册, 而这三册对同一条报文各自只讲自己负责的那一部分. 谁也没错, 但没有一册能单独回答&quot;这条报文到底长什么样&quot;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;你就是那个超链接.&lt;/strong&gt; 没有交叉引用, 没有索引, 扫描的那几页连全文检索都做不了, 全靠自己记住哪一条写在哪一册的第几页. 所以前期有相当一部分时间, 其实是在给自己建索引: 术语的对照, 每个域的出处, 各册之间的对应关系. 这件事没有任何技术含量, 但跳过它的代价是后面每查一次都要重新翻一遍.&lt;/p&gt;
&lt;p&gt;实在读不出来的地方也有, 只能记下来攒着, 攒够一批再去问对端 —— 问一轮的周期以周计, 所以清单要攒得够厚才划算.&lt;/p&gt;
&lt;p&gt;所以真正的工作量分布大概是: 读文档, 整理术语, 建索引占了一大半, 写代码反而是最快的部分. 这也直接决定了后面几乎所有的设计.&lt;/p&gt;
&lt;p&gt;因为读文档这件事最大的风险不是读慢了, 而是&lt;strong&gt;读错了却不知道&lt;/strong&gt;. 一个人对着上千页 PDF 读出来的理解, 如果只留在他脑子里, 或者只写成一页 wiki, 那它既不会被 review, 也不会被验证, 出了问题还查不到源头. 所以从一开始就定了一个原则:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;凡是规格书里写的约束, 都要变成代码里能跑的东西.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;域的长度, 编码方式, 是定长还是变长, 在哪种电文里必须出现, 取值范围有哪些 —— 全部写进结构体和校验器, 而不是写进注释或者文档. 这样&quot;我对规格书的理解&quot;就成了一个可以被 review, 被测试, 被后来人修正的东西.&lt;/p&gt;
&lt;p&gt;而一旦这么想, 事情就变得很顺: 既然报文无非是&quot;哪些域出现了, 每个域怎么编码&quot;, 那它完全可以用 Go 的结构体和 tag 描述出来.&lt;/p&gt;
&lt;h2 id=&quot;yong-struct-tag-miao-shu-bao-wen&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#yong-struct-tag-miao-shu-bao-wen&quot; aria-label=&quot;Anchor link for: yong-struct-tag-miao-shu-bao-wen&quot;&gt;用 struct tag 描述报文&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;底层的封包解包部分, 我们没有从零写, 而是基于 &lt;a rel=&quot;external&quot; href=&quot;https://github.com/ideazxy/iso8583&quot;&gt;ideazxy/iso8583&lt;/a&gt; 的思路做的扩展. 它的核心抽象是一个接口:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Iso8583Type interface for ISO 8583 fields&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Iso8583Type&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; interface&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;	//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Bytes 把当前域编码成字节&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;	Bytes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;encoder&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; lenEncoder&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; length&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; int&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;	//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Load 从字节流解出当前域, 返回实际消费的字节数&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;	Load&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;raw&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; encoder&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; lenEncoder&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; length&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; int&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;int&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;	//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; IsEmpty 判断域是否为空&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;	IsEmpty&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后 &lt;code&gt;Numeric&lt;/code&gt;, &lt;code&gt;Alphanumeric&lt;/code&gt;, &lt;code&gt;Binary&lt;/code&gt;, &lt;code&gt;Llvar&lt;/code&gt;, &lt;code&gt;Lllvar&lt;/code&gt;, &lt;code&gt;Llnumeric&lt;/code&gt;, &lt;code&gt;Lllnumeric&lt;/code&gt; 各自实现它. &lt;code&gt;Ll&lt;/code&gt; 和 &lt;code&gt;Lll&lt;/code&gt; 前缀是 ISO 8583 的黑话, 指长度头是 2 位还是 3 位十进制数的变长域.&lt;/p&gt;
&lt;p&gt;于是一条报文就是一个结构体:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Body&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; struct&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	F2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;iso8583&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Llvar&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;        `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;field:&amp;quot;2&amp;quot;  length:&amp;quot;19&amp;quot; encode:&amp;quot;jis8&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 会员号&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	F3&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;iso8583&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Numeric&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;      `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;field:&amp;quot;3&amp;quot;  length:&amp;quot;6&amp;quot;  encode:&amp;quot;jis8&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 处理码&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	F4&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;iso8583&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Numeric&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;      `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;field:&amp;quot;4&amp;quot;  length:&amp;quot;12&amp;quot; encode:&amp;quot;jis8&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 交易金额&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	F11&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;iso8583&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Numeric&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;      `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;field:&amp;quot;11&amp;quot; length:&amp;quot;6&amp;quot;  encode:&amp;quot;jis8&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 系统跟踪号&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	F12&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;iso8583&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Numeric&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;      `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;field:&amp;quot;12&amp;quot; length:&amp;quot;12&amp;quot; encode:&amp;quot;jis8&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 交易时间&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	F37&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;iso8583&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Alphanumeric&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;field:&amp;quot;37&amp;quot; length:&amp;quot;12&amp;quot; encode:&amp;quot;jis8&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 检索参考号&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	F52&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;iso8583&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Binary&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;       `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;field:&amp;quot;52&amp;quot; length:&amp;quot;8&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; PIN block&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	F55&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;iso8583&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;LllBinary&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;field:&amp;quot;55&amp;quot; length:&amp;quot;255&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;              //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; IC 卡数据&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Bytes()&lt;/code&gt; 的时候, 用反射遍历这个结构体, 从 tag 里读出域号, 长度和编码, 域号决定它在 bitmap 里点哪个 bit, 编码决定它怎么变成字节:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; parseFields&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; interface&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; map&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;int&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;fieldInfo&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	fields&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; make&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;map&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;int&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;fieldInfo&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	v&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; reflect&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Indirect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;reflect&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ValueOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;msg&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	for&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span&gt; v&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;NumField&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;++&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;		//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 指针为 nil 的域, 直接跳过, 不进 bitmap&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		if&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; isPtrOrInterface&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Kind&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; v&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;IsNil&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;			continue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		sf&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; v&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Type&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		index&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; strconv&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Atoi&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sf&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Tag&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TAG_FIELD&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;		//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ... 解析 encode / length tag&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		field&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ok&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; v&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Field&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Interface&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Iso8583Type&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		if&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !&lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;			panic&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;field must be Iso8583Type&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		fields&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;fieldInfo&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; encode&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; lenEncode&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; length&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; field&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	return&lt;/span&gt;&lt;span&gt; fields&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里有两个我觉得挺舒服的地方.&lt;/p&gt;
&lt;p&gt;第一, &lt;strong&gt;所有域都用指针&lt;/strong&gt;. Go 没有 optional, 而 ISO 8583 里&quot;域不存在&quot;和&quot;域是空字符串&quot;是两件不同的事 (前者 bitmap 上是 0, 后者是 1 加一个长度为 0 的变长域). 用指针 nil 表达&quot;不存在&quot;, 用 &lt;code&gt;IsEmpty()&lt;/code&gt; 表达&quot;存在但为空&quot;, 语义就对上了. 调用方只需要 &lt;code&gt;models.WithF39(&quot;000&quot;)&lt;/code&gt; 这样填自己关心的域, 剩下的自然不出现在报文里.&lt;/p&gt;
&lt;p&gt;第二, &lt;strong&gt;bitmap 不需要人来维护&lt;/strong&gt;. 老一点的实现里, 常常是业务代码自己去 set bit, 然后再 append 数据, 一旦漏了一处就是一条对端解不开的报文. 交给反射之后, 结构体里填了什么, 报文里就有什么.&lt;/p&gt;
&lt;p&gt;解包的方向对称: 注册 MTI 与模板结构体的映射, 收到报文先读 MTI, 找到对应的 &lt;code&gt;reflect.Type&lt;/code&gt;, &lt;code&gt;reflect.New&lt;/code&gt; 一个出来, 再按 bitmap 逐位 &lt;code&gt;Load&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;p &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Parser&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Register&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;mti&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; string&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; tpl&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; interface&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	v&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; reflect&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ValueOf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tpl&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	p&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;messages&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;mti&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; reflect&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Indirect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Type&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有一个必须处理的细节: &lt;code&gt;reflect.New&lt;/code&gt; 出来的结构体, 里面所有指针域都是 nil, 而 &lt;code&gt;Load&lt;/code&gt; 需要往里写. 所以要先把指针域挨个 &lt;code&gt;reflect.New&lt;/code&gt; 初始化一遍, 解完之后再把没被 bitmap 点到的域还原成 nil —— 否则 &quot;这条报文里有没有 F39&quot; 这个判断就失效了.&lt;/p&gt;
&lt;p&gt;反射代码的另一个代价是, 大量的错误没法在编译期发现, tag 写错, 类型写错, 都要到运行时才 panic. 这个库的做法是在每个对外的入口上挂一个 recover, 把 panic 翻译成 error:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;m &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Message&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Bytes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;ret&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	defer&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; func&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		if&lt;/span&gt;&lt;span&gt; r&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; recover&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; r&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;			err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; errors&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;New&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Critical error:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; +&lt;/span&gt;&lt;span&gt; fmt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Sprint&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;			ret&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;	//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是个务实但不优雅的选择. 一条畸形报文不该把整个网关搞挂, 但用 recover 兜底也意味着错误信息很难看. 更好的做法应该是在注册模板的时候就把结构体校验一遍, 把错误提前到启动期.&lt;/p&gt;
&lt;h2 id=&quot;liang-chong-bian-ma-bcd-he-jis8&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#liang-chong-bian-ma-bcd-he-jis8&quot; aria-label=&quot;Anchor link for: liang-chong-bian-ma-bcd-he-jis8&quot;&gt;两种编码: BCD 和 JIS8&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;bcd&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#bcd&quot; aria-label=&quot;Anchor link for: bcd&quot;&gt;BCD&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;BCD 是用 4 个 bit 表示 1 位十进制数, 于是 1 个字节能放 2 位数字, &lt;code&gt;&quot;1234&quot;&lt;/code&gt; 编码后就是 &lt;code&gt;\x12\x34&lt;/code&gt;. 对于金额, 日期, 长度头这些纯数字字段, 直接省一半.&lt;/p&gt;
&lt;p&gt;麻烦在奇数长度. &lt;code&gt;&quot;119&quot;&lt;/code&gt; 有 3 位, 占 1.5 字节, 那多出来的半个字节补在哪一边? 规格书里两种都有, 所以要两个函数:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 左对齐: &amp;quot;119&amp;quot; -&amp;gt; &amp;quot;1190&amp;quot; -&amp;gt; \x11\x90&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; lbcd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;data&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	if&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; len&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		return&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; bcd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;...&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	return&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; bcd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 右对齐: &amp;quot;119&amp;quot; -&amp;gt; &amp;quot;0119&amp;quot; -&amp;gt; \x01\x19&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; rbcd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;data&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	if&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; len&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		return&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; bcd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;...&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	return&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; bcd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解码时同样要知道原始的十进制长度才能把补的那个 0 切掉, 所以 &lt;code&gt;Load&lt;/code&gt; 的签名里一直带着 &lt;code&gt;length&lt;/code&gt;. 这也是为什么 tag 里的 &lt;code&gt;length&lt;/code&gt; 对于 BCD 域指的是&lt;strong&gt;十进制位数&lt;/strong&gt;而不是字节数 —— 一个很容易踩的坑, 尤其是 &lt;code&gt;Binary&lt;/code&gt; 类型的 &lt;code&gt;length&lt;/code&gt; 指的又是字节数.&lt;/p&gt;
&lt;h3 id=&quot;jis8&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#jis8&quot; aria-label=&quot;Anchor link for: jis8&quot;&gt;JIS8&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;这是这个项目里最日本的部分.&lt;/p&gt;
&lt;p&gt;Go 的 &lt;code&gt;string&lt;/code&gt; 是 UTF-8, 而线路上跑的是 Shift-JIS. 具体一点, 报文里的日文用的是 JIS X 0201 的半角片假名 (单字节, 0xA1~0xDF 区间) 加 JIS X 0208 的双字节汉字假名. &lt;code&gt;ｱｲｳｴｵ&lt;/code&gt; 这种半角片假名今天只在 ATM 和收据上还能见到, 但在这类报文里是主力.&lt;/p&gt;
&lt;p&gt;转换本身不难, &lt;code&gt;golang.org/x/text/encoding/japanese&lt;/code&gt; 直接给:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; ToShiftJIS&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;res&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	transformed&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ReadAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		transform&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;NewReader&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;strings&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;NewReader&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; japanese&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ShiftJIS&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;NewEncoder&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;	//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;难的是, &lt;strong&gt;转换应该发生在什么时候&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;一开始我们把域直接存成 &lt;code&gt;[]byte&lt;/code&gt;, 让调用方自己转. 结果是业务代码里到处是 &lt;code&gt;ToShiftJIS&lt;/code&gt;, 而且一旦漏了一处, 报文能发出去, 对端也能收下, 只是商户名变成乱码 —— 这种 bug 联调的时候非常难查.&lt;/p&gt;
&lt;p&gt;后来改成变长域同时持有两种表示:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Llvar&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; struct&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	ByteValue&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	Value&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;     string&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; UTF-8 字符串, 如果填了此值, 会以此值为准做转换&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	Valid&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;     bool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;业务代码只碰 &lt;code&gt;Value&lt;/code&gt;, 编码的事情推迟到 &lt;code&gt;Bytes()&lt;/code&gt; 里, 按 tag 上的 &lt;code&gt;encode&lt;/code&gt; 决定是原样输出还是转 Shift-JIS; 解包时反过来, &lt;code&gt;Load&lt;/code&gt; 之后 &lt;code&gt;Value&lt;/code&gt; 里拿到的一定是可以直接打日志, 直接进数据库的 UTF-8. 编码从此只在库的边界上发生一次.&lt;/p&gt;
&lt;p&gt;顺带一个必须记住的点: &lt;strong&gt;变长域的长度头算的是编码后的字节数, 不是字符数&lt;/strong&gt;. &lt;code&gt;&quot;東京&quot;&lt;/code&gt; 在 UTF-8 里是 6 字节, 在 Shift-JIS 里是 4 字节, 长度头要写 4. 同理, 定长域补空格补到的也是字节数. 所以 &lt;code&gt;len(l.ByteValue)&lt;/code&gt; 这个写法必须在转换之后, 顺序反了就是一条长度对不上的废报文.&lt;/p&gt;
&lt;p&gt;测试数据也是个问题. 为了压变长域的边界, 需要随机生成合法的 Shift-JIS 字符串, 于是写了个工具, 把 ASCII 的符号区间和 0xA1~0xDF 的半角片假名区间拼起来, 解码成 rune 再随机取:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;for&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; uint8&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\x21&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; uint8&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\x2f&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;++&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	b&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ... 其他 ASCII 符号区间&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;for&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; uint8&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\xa1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; uint8&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\xdf&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;++&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	b&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代码里还留着一行 &lt;code&gt;// TODO handle \xa0, it&#39;s very strange&lt;/code&gt; —— 0xA0 在 Shift-JIS 里是未定义的, 但某些实现会把它当空格处理. 规格书里对这个位置也语焉不详, 这个 TODO 估计会留很久.&lt;/p&gt;
&lt;h2 id=&quot;tcp-lian-jie&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#tcp-lian-jie&quot; aria-label=&quot;Anchor link for: tcp-lian-jie&quot;&gt;TCP 连接&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;封包解包做完, 剩下的一半工作量在连接上. 这部分基本是照着 &lt;a rel=&quot;external&quot; href=&quot;https://github.com/redis/go-redis&quot;&gt;go-redis&lt;/a&gt; 抄的, 然后被业务改得面目全非.&lt;/p&gt;
&lt;p&gt;抄的部分是 &lt;code&gt;Conn&lt;/code&gt; 这层封装: 拿一个 &lt;code&gt;*net.TCPConn&lt;/code&gt;, 套上 &lt;code&gt;bufio.Reader&lt;/code&gt; / &lt;code&gt;bufio.Writer&lt;/code&gt;, 配上读写超时和 keepalive.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Connect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;address&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;net&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;TCPConn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	raddr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; net&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ResolveTCPAddr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;tcp&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; address&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;	//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	conn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; net&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;DialTCP&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;tcp&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; raddr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;	//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; conn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;SetKeepAlivePeriod&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Second&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; conn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;SetKeepAlive&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TCP 是字节流, 没有报文边界, 所以读的时候必须自己划. 好在这类协议都有定长包头, 包头里有全长:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 先读定长包头&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;headerBytes&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; make&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;HeaderByteLength&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ReadFull&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;conn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;br&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; headerBytes&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 从包头里取出全长, 再读剩下的&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;totalLength&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; message&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;GetTotalLengthFromHeader&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bodyBytes&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; make&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; totalLength&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;HeaderByteLength&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; io&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ReadFull&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;conn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;br&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; bodyBytes&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;io.ReadFull&lt;/code&gt; 在这里是关键. 直接 &lt;code&gt;Read&lt;/code&gt; 是不保证读满的, 大报文分片到达的时候, 半包就是这么来的.&lt;/p&gt;
&lt;p&gt;改得面目全非的部分, 是&lt;strong&gt;这个协议是全双工的&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Redis 的连接模型是: 从池里取一条连接, 写请求, 阻塞读响应, 还回池里. 一条连接同一时刻只服务一个请求, 请求和响应严格配对.&lt;/p&gt;
&lt;p&gt;而卡网这边不是. 对端会主动推送过来通知类电文, 也会在你发出去的请求还没回来的时候先发别的东西过来. 更麻烦的是, 双方是对等的: 我方既要主动连过去, 也要监听端口等对方连过来, 两个方向上都能收发业务电文.&lt;/p&gt;
&lt;p&gt;所以连接建立之后, 立刻起一个 goroutine 一直读, 读到的电文全部投进一个 channel:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;c &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Conn&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; ReadChan&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;ch&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; chan&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Message&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	go&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; func&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		for&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;			message&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; connReadMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;			if&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;				logz&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Warnf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;read error, &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;%v&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;				c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Close&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;				return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;			ch&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;-&lt;/span&gt;&lt;span&gt; message&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上层拿到的就是一个&quot;所有连接上收到的所有电文&quot;的流, 主动连出去的和被动接进来的连接共用同一个出口:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;c &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Center&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; ReceiveMessagesInChannel&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;ch&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; chan&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Message&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	serverReceiveChan&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;server&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ReceiveChannel&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	clientReceiveChan&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;client&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ReceiveChannel&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	go&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; func&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		for&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;			select&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;			case&lt;/span&gt;&lt;span&gt; message&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;-&lt;/span&gt;&lt;span&gt;serverReceiveChan&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;				go&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; loadMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ch&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; message&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;			case&lt;/span&gt;&lt;span&gt; message&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;-&lt;/span&gt;&lt;span&gt;clientReceiveChan&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;				go&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; loadMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ch&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; message&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;请求和响应的配对, 就交给上层用报文里的流水号去做了 —— 系统跟踪号加交易时间加机构号组成的那个唯一标识. 这件事本来就该在业务层做, 因为超时重发, 冲正, 对账都要用到同一个标识.&lt;/p&gt;
&lt;h2 id=&quot;yi-dian-ye-bu-yun-yuan-sheng&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#yi-dian-ye-bu-yun-yuan-sheng&quot; aria-label=&quot;Anchor link for: yi-dian-ye-bu-yun-yuan-sheng&quot;&gt;一点也不云原生&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在讲连接池之前, 得先说清楚这张网长什么样, 否则池子的形状会显得莫名其妙.&lt;/p&gt;
&lt;p&gt;没有域名, 没有服务发现, 没有负载均衡器. 双方交换的是一份 IP 地址清单, 写死在配置里:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Config&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; struct&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	Addresses&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;json:&amp;quot;addresses&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	SourceID&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  string&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;   `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;json:&amp;quot;source_id&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我方有 m 个节点, 对方给 n 个地址, 于是 &lt;strong&gt;m × n 条连接, 每一条各自建立, 各自保活, 各自重连&lt;/strong&gt;. 反方向还有一张: 对方也会主动连过来. 这些连接不是一堆等价的, 可替换的池化资源, 而是一条条有名有姓的线路 —— 断的是哪一条, 就得把哪一条接回来.&lt;/p&gt;
&lt;p&gt;所以这套东西和现在习以为常的那一套基本是反着来的:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;不能随便扩容.&lt;/strong&gt; 实例数是和对方约定好的. 加一台机器意味着双方都要改白名单, 走申请流程, 周期以周计. 弹性伸缩在这里不是一个选项.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IP 必须是固定的.&lt;/strong&gt; 这基本堵死了直接扔进 k8s 让 Pod 飘来飘去的可能, 要么钉在固定节点上, 要么想办法保证出口 IP 稳定.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;发布是有代价的.&lt;/strong&gt; 重启一个实例意味着 n 条连接断开重连, 重连完还要重新开局. 滚动发布要控制节奏, 不能一次全部重启.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;健康检查没法交给别人.&lt;/strong&gt; 没有 LB 帮你把故障节点摘掉, 每条连接是死是活, 只有进程自己知道.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;好处是链路上没有中间环节, 出了问题排查路径很短. 代价是建立, 保活, 重连, 选路, 摘除这些活全部落在库里, 没有任何基础设施能替你分担 —— 下面这个连接池, 本质上就是为了把这堆事收在一个地方.&lt;/p&gt;
&lt;h2 id=&quot;lian-jie-chi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#lian-jie-chi&quot; aria-label=&quot;Anchor link for: lian-jie-chi&quot;&gt;连接池&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;既然读已经不需要占用连接了, 那连接池到底还池什么?&lt;/p&gt;
&lt;p&gt;答案是: &lt;strong&gt;只服务于发送&lt;/strong&gt;. 池的职责从&quot;借出-独占-归还&quot;退化成了&quot;在一组可用连接里挑一条来写&quot;, 也就是选路和负载均衡.&lt;/p&gt;
&lt;p&gt;底层的双向链表直接抄了 &lt;a rel=&quot;external&quot; href=&quot;https://github.com/gomodule/redigo&quot;&gt;redigo&lt;/a&gt;, 代码里老老实实留着出处:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; idleList is a double linked list to maintain connections in connPool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; copy from github.com/gomodule/redigo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; idleList&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; struct&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	count&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;       int&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	front&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; back&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;poolConn&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;取连接的时候从链表尾部弹出, 用完从头部塞回去, 天然就是一个轮询:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;p &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ConnPool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; GetConnAndPutBack&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Conn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	p&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mu&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Lock&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	defer&lt;/span&gt;&lt;span&gt; p&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mu&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Unlock&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	conn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; p&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;getConn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	if&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	if&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !&lt;/span&gt;&lt;span&gt;conn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;closed&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;		//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 将取到的连接还回到链表的头&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		p&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;putConn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;conn&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发送的入口带 context 超时, 拿不到可用连接就等, 等到 deadline 再报错. 已经关闭的连接直接丢弃, 不再放回:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;p &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ConnPool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; GetActiveConnWithCtx&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Context&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Conn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	for&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		select&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		case&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;-&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Done&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 已经超时, 直接返回&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;			return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ctx&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Err&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		default&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;			p&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mu&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Lock&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;			conn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; p&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;getConn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;			p&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mu&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Unlock&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;			if&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;				if&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span&gt; ErrIdleConnNotFound&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 没有空闲连接, 等一下再取&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;					time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Sleep&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Millisecond&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;					continue&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;				}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;				return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;			if&lt;/span&gt;&lt;span&gt; conn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;closed&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;				continue&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 关闭的连接无需加回连接池&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;			return&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 返回的 conn 一定是未关闭且可用的&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;zhong-lian&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhong-lian&quot; aria-label=&quot;Anchor link for: zhong-lian&quot;&gt;重连&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;专线也会断, 对端也会重启, 而这类系统的要求是断了要自己接回来, 不能等人来重启进程.&lt;/p&gt;
&lt;p&gt;做法是在连接入池的时候, 给它挂一个&quot;守灵&quot;的 goroutine: 阻塞在这条连接的 &lt;code&gt;closeChan&lt;/code&gt; 上, 一旦连接关闭, 先把活跃计数减掉, 然后按&lt;strong&gt;原来那个&lt;/strong&gt;远端地址重新拨号, 拨通了再入池. 注意是原来那个, 而不是从地址清单里重新挑一个 —— 上面说过, m × n 里的每一条都是有名有姓的, 少了哪条就得补哪条.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;p &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ConnPool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; AddConn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Conn&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	p&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mu&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Lock&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	defer&lt;/span&gt;&lt;span&gt; p&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mu&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Unlock&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	p&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;putConn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;conn&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	p&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;increaseActive&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	go&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; func&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Conn&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		&amp;lt;-&lt;/span&gt;&lt;span&gt;conn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;closeChan&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		p&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mu&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Lock&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		p&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;decreaseActive&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		p&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mu&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Unlock&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		if&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !&lt;/span&gt;&lt;span&gt;conn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;reconnect&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;			return&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 被动接入的连接不负责重连&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		if&lt;/span&gt;&lt;span&gt; conn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parentCloseChan&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;			return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;		//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ... 按 remote addr 重连, 失败退避后重试&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;conn&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里有两个 flag 值得说一下.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;reconnect&lt;/code&gt; 区分这条连接是我方拨出去的还是对方连进来的. 只有前者该重连 —— 对方连进来的连接断了, 该由对方来重连, 我方去连对方的监听端口是完全不同的一件事.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;parentCloseChan&lt;/code&gt; 区分&quot;网络断了&quot;和&quot;应用要关了&quot;. 没有这个东西的话, 优雅关停会变成: 你关掉所有连接, 然后每条连接的守灵 goroutine 立刻开始重连, 关不掉. 这个 channel 由上层持有, 关停时先关它, 重连逻辑一看它已经关闭就直接退出.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; IsClosed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;ch&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;-&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;chan&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; interface&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; bool&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	select&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	case&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;-&lt;/span&gt;&lt;span&gt;ch&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	default&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个模式在 Go 里很常用: 用一个只关不写的 channel 做广播式的取消信号. 现在写的话应该直接用 &lt;code&gt;context.Context&lt;/code&gt;, 当时是图省事.&lt;/p&gt;
&lt;h2 id=&quot;lian-jie-bu-deng-yu-hui-hua&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#lian-jie-bu-deng-yu-hui-hua&quot; aria-label=&quot;Anchor link for: lian-jie-bu-deng-yu-hui-hua&quot;&gt;连接不等于会话&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;规格书里, 两个中心之间要先发一条开局电文, 收到开局响应之后才允许发业务电文, 收工的时候再发一条闭局.&lt;/p&gt;
&lt;p&gt;应用层再握一次手这件事本身一点也不稀奇. Redis 的 &lt;code&gt;AUTH&lt;/code&gt;, &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;HELLO&lt;/code&gt; 就是, MySQL, AMQP, SMTP 也都有自己的一套. TCP 只负责把字节按序送到, 至于&quot;你是谁, 我认不认你, 接下来按什么规矩来&quot;, 永远得在应用层谈.&lt;/p&gt;
&lt;p&gt;真正值得说的是&lt;strong&gt;粒度&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Redis 那套是&lt;strong&gt;连接级&lt;/strong&gt;的: AUTH 认的是这条连接, SELECT 选的是这条连接的 db, 状态和 socket 同生共死. 连接断了, 状态跟着没; go-redis 重连之后在 &lt;code&gt;initConn&lt;/code&gt; 里把 HELLO / AUTH / SELECT 重跑一遍, 上层完全无感. 会话和连接是 1:1 的, 所以&quot;会话&quot;这个词在那边根本不需要单独存在.&lt;/p&gt;
&lt;p&gt;开局不是. 它是&lt;strong&gt;两个中心之间&lt;/strong&gt;的状态, 一次开局覆盖 m × n 所有连接:&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;code&gt;CommonState&lt;/code&gt; 里的, 不是存在 &lt;code&gt;Conn&lt;/code&gt; 上:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;const&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;	OnlineStatusClosed&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; OnlineStatus&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; iota&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;	OnlineStatusOpening&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;	OnlineStatusOpened&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;	OnlineStatusClosing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发业务电文之前该问的是&quot;会话开着吗&quot;, 而不是&quot;连接还在吗&quot;. 这两个判断在代码里是分开的, 池子非空只是必要条件.&lt;/p&gt;
&lt;p&gt;粒度不同, 后面几件事也就跟着不同了.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;会话要自己判断活性.&lt;/strong&gt; TCP 断了你不一定知道 —— 对端主机掉电, 中间的防火墙静默丢掉连接状态, 专线切换, 这些情况下本地的 socket 可能很长时间还停在 ESTABLISHED, 你往里写也不报错. keepalive 探的是链路, 对端进程 hang 死了内核照样回 ACK. 这个问题 Redis 那边由池子处理掉了: 拿到一条坏连接, 丢掉重建, 大不了重试一次, 反正会话跟着连接一起重来. 但在这里连接可以重建, 会话不行, 所以必须有应用层的心跳 (echo test) —— 它超时说明的是&quot;对面那套系统不转了&quot;, 而不只是&quot;这条线断了&quot;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;它是对等的.&lt;/strong&gt; AUTH 是客户端单方面向服务端证明自己, 服务端不需要反过来 AUTH. 而开局是两个对等中心之间的协商, 哪边都能发起, 哪边都要维护对方的状态. 后面那个假中心之所以要把开局的两个方向都实现一遍, 就是因为这个.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;闭局是要谈的.&lt;/strong&gt; TCP 的 FIN 只能表达&quot;我不发了&quot;, 表达不了&quot;我准备收工, 麻烦把在途的交易处理完再断&quot;, 更不给对方拒绝或者拖一会儿的机会. 而这类系统有营业日, 有日切, 有对账 —— 停机是一件有业务含义的事, 只能在应用层用电文来谈.&lt;/p&gt;
&lt;p&gt;最后一个佐证是超时的量级:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;var&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	openResponseTimeout&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;     =&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Duration&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;45&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Second&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	closeResponseTimeout&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    =&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Duration&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;75&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Second&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	echoTestResponseTimeout&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Duration&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Second&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;几十秒到一分半, 远大于任何一个 TCP 层面的超时. 这也说明了它们量的到底是什么: 不是网络通不通, 而是对面那套系统还转不转得动.&lt;/p&gt;
&lt;h2 id=&quot;mi-yao-de-jie-kou-zhu-ru&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#mi-yao-de-jie-kou-zhu-ru&quot; aria-label=&quot;Anchor link for: mi-yao-de-jie-kou-zhu-ru&quot;&gt;密钥的接口注入&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;最后一块, 也是我自己最满意的一块.&lt;/p&gt;
&lt;p&gt;这类协议的安全部分大致是这样: 业务电文的 body 要加密, 同时要算一个电文认证值挂在包头上防篡改. 用的是 3DES, 链式地把 body 一段段加密, 认证值取最后一段密文的前几个字节 —— 也就是一个 CBC-MAC 的构造:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; CalcAuthenticationCode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;data&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; key&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;authCode&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;	//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 不足 8 的整数倍, 末尾补 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;	//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	input&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\x00&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\x00&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\x00&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\x00&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\x00&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\x00&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\x00&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\x00&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	for&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; len&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;8&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;++&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		current&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;8&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;8&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		temp&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Xor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; current&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		input&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; TripleDesEncrypt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;temp&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	authCode&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; input&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;4&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;问题不在算法, 在密钥从哪来.&lt;/p&gt;
&lt;p&gt;加密和认证用的是两把不同的工作密钥, 它们会定期轮换: 通过一条专门的密钥交换电文下发, 下发时被主密钥加密, 收到之后解出来才是真正能用的值. 与此同时, 包头里有一个 check digit 字段标识&quot;这条电文用的是哪一把密钥&quot; —— 因为轮换的那一瞬间, 线上会同时存在用新旧两把密钥加密的电文, 你必须能按 check digit 反查出对应的密钥来解.&lt;/p&gt;
&lt;p&gt;这就带来一个问题: 密钥的存储和轮换, 完全是业务的事情. 单进程的时候放内存就行, 多进程要放 Redis 或者数据库, 讲究一点的要放 HSM. SDK 不该管这些, 但它又必须在打包和解包的那一刻拿到密钥.&lt;/p&gt;
&lt;p&gt;于是把它做成了接口, 由使用方注入:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; SecretHandler&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; interface&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;	RetrieveSecret&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Secret&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;	RetrieveSecretWithCheckDigit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;checkDigit&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;byte&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Secret&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;	RetrieveSpecificSecret&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Secret&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; CommonStateHandler&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; interface&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;	SecretHandler&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;	RetrieveOnlineStatus&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; OnlineStatus&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;库里只留调用点. 发送的时候, 网络管理类电文不需要密钥, 业务电文才去取:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;b &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;base&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; SendMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Message&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	withoutSecret&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;IsMsgTypeCodeNetworkControl&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;MessageType&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	if&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !&lt;/span&gt;&lt;span&gt;withoutSecret&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		message&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Secret&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;RetrieveSecret&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		if&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;			return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	raw&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; message&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Bytes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;	//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接收的时候, 先解包头, 从 check digit 反查密钥, 再拿这把密钥去解 body 和验认证值:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; loadMessageBody&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Message&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; sh&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; SecretHandler&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;	//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 从包头的 check digit 里拆出各把密钥的标识&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;	//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; sh&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;RetrieveSpecificSecret&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;secret&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	if&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		message&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;		return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	message&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Secret&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; secret&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; message&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;LoadBody&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;BodyBytes&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;包里附带一个基于内存的默认实现, 用两个 map 存 check digit 到密钥的映射, 加读写锁, 密钥更新走一个 channel 串行化. 够小规模场景直接用, 也是给使用方看的参考实现. 但它只是一个实现, 不是唯一的实现.&lt;/p&gt;
&lt;p&gt;这个设计带来的好处比预想的多:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;测试可以脱离密钥&lt;/strong&gt;. 注入一个固定密钥的 mock, 单元测试就不需要真的走密钥交换流程. 甚至可以注入一对 &lt;code&gt;MockEncryptBody&lt;/code&gt; / &lt;code&gt;MockDecryptBody&lt;/code&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;. 这个包从头到尾没有 &quot;把密钥存哪&quot; 这个概念, 自然也就不会有人往里面提交一个真的密钥. 这一点在合规审计的时候也少了很多麻烦.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;反过来看, 接口开得有点多了. &lt;code&gt;RetrieveSecretWithCheckDigit&lt;/code&gt; 和 &lt;code&gt;RetrieveSpecificSecret&lt;/code&gt; 语义高度重合, 一个返回新对象一个原地填充, 是不同时期加的, 后来也没合并. 接口这东西, 一旦有人实现了就不好改了 —— 这大概是最直观的教训.&lt;/p&gt;
&lt;h2 id=&quot;sdk-hua&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#sdk-hua&quot; aria-label=&quot;Anchor link for: sdk-hua&quot;&gt;SDK 化&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上面这些东西 —— bitmap 怎么点位, 金额怎么压成 BCD, 商户名怎么转 Shift-JIS, 长度头算的是字节还是字符, 电文认证值用哪把密钥, 连接断了谁负责重连 —— 全都是&lt;strong&gt;规格书的细节, 不是业务的细节&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;写这个 SDK 的时候我给自己定的目标是: 业务代码里不应该出现任何一个字节.&lt;/p&gt;
&lt;p&gt;一笔授权交易, 在业务侧看起来应该长这样:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;req&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; client&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ConstructMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;MsgTypeCodeAuthorizationRequest&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;HeaderOption&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithSourceID&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sourceID&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithDestinationID&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;destID&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;BodyOption&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithF2&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pan&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithF4&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;amount&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithFunctionCode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;FunctionCodeAuthorization&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithF37&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rrn&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithF43&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;merchantName&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 日文商户名, 直接传 UTF-8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;SendMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到此为止. 至于这条报文的 bitmap 上点了哪几位, F4 被压成了 6 个字节, F43 被转成了 Shift-JIS, 包头上挂了一个用当前 KMAC 算出来的认证值, body 用当前 KC 加密过, 最后从连接池里挑了一条连接写出去 —— 业务不需要知道其中任何一件.&lt;/p&gt;
&lt;p&gt;为了做到这一点, 包里的分层是这样的:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      业务代码                        jcn/ligui (假中心 + 命令行)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          |                                    |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          +------------------+-----------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                             |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      ConstructMessage / SendMessage / ReceiveMessagesInChannel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                             |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+----------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| jcn/client          电文构造, 网络管理流程                     |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+----------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| jcn/center, pool    会话状态, 连接, 重连, 选路, 收发出入口     |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+----------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| jcn/models          这张网的报文定义, 密钥, 字段校验           |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+----------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| iso8583, cipher     通用封包解包, BCD / JIS8, 3DES             |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+----------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意 &lt;code&gt;jcn/ligui&lt;/code&gt; 的位置: 它在框外, 和业务代码平级. 它不是 SDK 的一层, 而是 SDK 的另一个使用者 —— 一个恰好扮演对端角色的应用. 依赖方向也是单向的: &lt;code&gt;ligui&lt;/code&gt; 引用 &lt;code&gt;client&lt;/code&gt; 和 &lt;code&gt;center&lt;/code&gt;, 而 &lt;code&gt;client&lt;/code&gt;, &lt;code&gt;center&lt;/code&gt;, &lt;code&gt;models&lt;/code&gt; 都不知道 &lt;code&gt;ligui&lt;/code&gt; 的存在, 只有最上面的可执行程序才把它们拼在一起. 后面讲假中心的时候会回到这一点.&lt;/p&gt;
&lt;p&gt;框里每一层只向上暴露上层真正需要的概念. 具体到几个决定:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;用 functional option 填域, 而不是暴露结构体.&lt;/strong&gt; body 上一共开了四十多个 &lt;code&gt;WithFxx&lt;/code&gt;, 每个域一个. 这样业务代码是&quot;我要填这几个域&quot;, 而不是&quot;我要构造一个有这几个字段的结构体, 剩下的记得留 nil&quot;. 前者漏填一个域是编译能过但对端会拒的报文, 后者漏填一个域连自己都看不出来. 顺带, 这些 option 里可以做默认值和格式化, 比如交易时间直接 &lt;code&gt;models.TimeNumString(time.Now(), models.TimeFormatYYMMDDhhmmss)&lt;/code&gt;, 业务不用记这个网络用的是 12 位还是 14 位时间.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;网络管理流程打包成一个调用.&lt;/strong&gt; 开局, 闭局, echo test, cutover, 密钥交换, 这些流程每个都是一来一回外加状态变更, 但业务侧只应该看到 &lt;code&gt;client.ConstructOpenRequest(...)&lt;/code&gt; 和 &lt;code&gt;c.WaitForOnlineStatusOpened(30 * time.Second)&lt;/code&gt;. 会话状态本身由库维护, 业务只关心&quot;现在能不能发业务电文&quot;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;日志要能直接看.&lt;/strong&gt; 加了一个反射实现的 &lt;code&gt;PrettyPrint&lt;/code&gt;, 按域号顺序把非空的域打成 JSON, 二进制域转 hex, 原始字节不打:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;m &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Message&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; JsonRawMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; json&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;RawMessage&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	jsonRawMessage&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; json&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Marshal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;PrettyMessage&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;m&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	return&lt;/span&gt;&lt;span&gt; json&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;RawMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;jsonRawMessage&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这件事看着不起眼, 但一个 SDK 好不好用, 很大程度上取决于出问题的时候能不能一眼看出是哪条报文的哪个域不对. 如果日志里只有一串十六进制, 那前面所有的抽象就没有意义了 —— 用的人还是得学会手动数字节.&lt;/p&gt;
&lt;p&gt;反过来说, SDK 化最难的地方也在这: 你要在&quot;藏起细节&quot;和&quot;出事的时候能查&quot;之间找平衡. 藏得太浅, 业务代码里到处是规格书; 藏得太深, 出了问题只能来问你. 我的做法是&lt;strong&gt;默认路径极简, 逃生舱口全开&lt;/strong&gt; —— &lt;code&gt;Message&lt;/code&gt; 上原始的 &lt;code&gt;HeaderBytes&lt;/code&gt; 和 &lt;code&gt;BodyBytes&lt;/code&gt; 一直留着, 密钥, 状态, 连接池的统计也都能拿到, 只是正常写业务的时候用不上.&lt;/p&gt;
&lt;h2 id=&quot;li-kui-yu-li-gui&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#li-kui-yu-li-gui&quot; aria-label=&quot;Anchor link for: li-kui-yu-li-gui&quot;&gt;李逵与李鬼&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;包里有一个目录叫 &lt;code&gt;ligui&lt;/code&gt;. 名字来自水浒里那个在树林里劫道, 自称&quot;黑旋风李逵&quot;的李鬼.&lt;/p&gt;
&lt;p&gt;这就是开头说的那个挡板 —— 绕了一大圈, 最先要的东西反而是最后才做出来的.&lt;/p&gt;
&lt;p&gt;理由倒是一直没变: 对端是别人的系统, 联调窗口按小时算, 要提前几周申请, 一次窗口跑不完就得等下一轮. 在这种节奏下, 唯一的办法是自己造一个假的中心, 平时先和它对接, 跑通了再去接真的.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ligui&lt;/code&gt; 就是这个假中心. 它做两件事:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一是 stub, 扮演对端回应.&lt;/strong&gt; 收到什么请求就按规格书构造什么响应, 请求里该原样带回的域原样带回, 该由中心填的域 (承认号, 处理结果, 中心侧的交易通番) 由它来填:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; constructAuthorizationResponse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Message&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ...&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Message&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	resp&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; client&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ConstructMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;MsgTypeCodeAuthorizationResponse&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;HeaderOption&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;			//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 源和目的对调&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;			models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithDestinationID&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Header&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SourceID&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;			models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithSourceID&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Header&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;DestID&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;BodyOption&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;			models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithF2&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;F2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;			models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithF4&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;F4&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;			models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithF11&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;F11&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;			//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;			models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithF38&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;        //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; to be filled by options&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;			models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithActionCode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; to be filled by options&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	resp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Update&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;headerOpts&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; bodyOpts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	return&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;留空的那几个域, 由测试用例通过 option 覆盖. 于是同一个构造函数, 既能造一条&quot;承认&quot;的响应, 也能造一条&quot;卡片无效&quot;或者&quot;限额超过&quot;的响应. 异常路径的测试比正常路径重要得多, 因为正常路径联调时一定会过, 而异常路径可能上线半年才遇到第一次.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;二是 mock, 扮演对端的完整行为.&lt;/strong&gt; 这部分是一个状态机: 开局请求来了要回开局响应并且把在线状态推进到 opened, echo test 来了要回, cutover 来了要回, 密钥交换来了要用 KEK 解出新的工作密钥, 存进去, 再回一条响应. 业务电文来了就走 stub 那套构造响应. 遇到完全不认识的电文, 回一条通用错误通知, 并且把对方的包头原样贴在错误信息域里 —— 这是规格书要求的行为, 而这条路径如果没有 mock, 你几乎没有机会测到.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;default&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	logz&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Debugf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;unknown message received, message function: &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;%v&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; message&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;MessageFunction&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;SendMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ConstructGeneralErrorMessageResponse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;BodyOption&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithActionCode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ActionCodeErrorFormatInvalid&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithF72Byte&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;HeaderBytes&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; options&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;...&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;让双方跑同一个库, 还有一个更重要的理由: &lt;strong&gt;假中心同时就是一个一致性检查器&lt;/strong&gt;. 我方发出去的报文, 假中心要能解开; 假中心发回来的报文, 我方要能解开. 任何一边对规格书的理解有偏差, 在自己和自己对接的时候就会暴露成一个解不开的报文, 而不是等到联调窗口里对方告诉你&quot;你们的电文我们收不了&quot;.&lt;/p&gt;
&lt;p&gt;在这个基础上又加了一层校验, 按电文种别和功能码, 声明这条报文&lt;strong&gt;应该&lt;/strong&gt;出现哪些域:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;case&lt;/span&gt;&lt;span&gt; models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;MessageFunction&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;MessageTypeCodeNetworkControlResponse&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;FunctionCodeCutover&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	return&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; CheckFiledByExpectedFileds&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; ExpectedFileds&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;int&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;11&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 12&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 24&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 28&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 39&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ...&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;多一个域少一个域都算错. 加上每个域自己的规则校验 (卡号是 10 到 19 位数字, 处理码是 6 位且必须是规格书列出的组合之一), 等于把规格书里那张巨大的表格翻译成了可执行的断言. 这些断言在假中心里跑, 也在单元测试里跑.&lt;/p&gt;
&lt;h3 id=&quot;zai-zi-ji-de-bi-ji-ben-shang-pao-tong-quan-liu-cheng&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zai-zi-ji-de-bi-ji-ben-shang-pao-tong-quan-liu-cheng&quot; aria-label=&quot;Anchor link for: zai-zi-ji-de-bi-ji-ben-shang-pao-tong-quan-liu-cheng&quot;&gt;在自己的笔记本上跑通全流程&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;李鬼是个库, 要用起来还得有个东西驱动它. 于是配了一个命令行, 一个最朴素的 REPL:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; CommandLineRequest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;center&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Center&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; state&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;center&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;CommonState&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	reader&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; bufio&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;NewReader&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;os&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Stdin&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;	for&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		fmt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Println&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Ready for input......&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		input&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; reader&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ReadString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		inputs&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; strings&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;strings&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;TrimSuffix&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; DoRequest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; state&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; inputs&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; inputs&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;...&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;		//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;命令基本覆盖了这张网上会发生的所有事:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;open                开局&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;echotest            心跳&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;key_exchange KC     换一把密钥 (KC / KMAC / KPE)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;authori             一笔授权&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;authori_arqc        一笔带 IC 卡数据的授权&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sales               一笔销售&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;revarsal_advice     一笔冲正&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cutover &amp;lt;日期&amp;gt;       日切&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;audit               在线审查&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;stats               看看现在有几条连接&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键在于&lt;strong&gt;两边跑的是同一个东西&lt;/strong&gt;. 最后落成两个 main, 一个 &lt;code&gt;center&lt;/code&gt; 一个 &lt;code&gt;mockcenter&lt;/code&gt;, 各自都只是几十行胶水: 读配置, 装好初始密钥, 起一个 &lt;code&gt;Center&lt;/code&gt;, 然后一个把 stdin 接到 &lt;code&gt;ligui&lt;/code&gt; 的 sender 上, 另一个用 &lt;code&gt;ligui&lt;/code&gt; 的 receiver 自动应答. 地址填 &lt;code&gt;127.0.0.1&lt;/code&gt;, 互相一连, 整条链路就在一台笔记本上跑起来了 —— 不需要专线, 不需要 VPN, 不需要对方的系统:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;open → key_exchange → authori → revarsal_advice → cutover → close&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一趟走下来, TCP 连接的建立与保活, 会话状态机的推进, bitmap 的组装与解析, BCD 和 Shift-JIS 的编解码, 3DES 加密, 电文认证值的计算与校验, 密钥按 check digit 的反查, 全都真实地执行了一遍. 它不是单元测试里那种&quot;喂一段字节, 断言解出来是什么&quot;, 而是真的从一个进程的 socket 出去, 从另一个进程的 socket 进来 —— 一次完整的疏通测试, 只不过对面是自己.&lt;/p&gt;
&lt;p&gt;它要解决的是下面几个问题.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;上手不需要任何前置条件.&lt;/strong&gt; 代码拉下来, 起两个进程就能跑起来. 不用申请专线, 不用等密钥下发, 不用协调联调窗口. 对一个外部依赖这么重的项目来说, &quot;能在本机跑起来&quot;这件事本身就把新人的上手成本砍掉了一大半 —— 否则一个刚接手的人在拿到网络权限之前, 连自己写的代码对不对都验证不了.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;报文的填充是自动的.&lt;/strong&gt; 一条授权报文二三十个域, 人不可能每次手填. 所以除了你真正关心的那一两个参数, 其余的域都用符合规格的随机数据填上 —— 检索参考号是合法字符集的 12 位, IC 卡数据是一段合法长度的字节:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithF37&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;jcn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;MustGenerateCode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;anp&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 12&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;models&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;WithF55&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;jcn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;MustGenerateByte&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;anp&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 114&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;顺带, 这也一直在压边界. 每敲一次命令, 变长域的长度和内容都不一样, 连着敲几十次, 相当于一轮小小的随机测试 —— 前面那些长度头, 补位, 半角全角混排的坑, 有好几个就是这么撞出来的.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;有状态的流程终于可测了.&lt;/strong&gt; 密钥轮换这种事单元测试很难覆盖: 它跨越多条报文, 还要求双方的状态同步变化. 在命令行里就是一条 &lt;code&gt;key_exchange KC&lt;/code&gt;, 敲完接着敲 &lt;code&gt;authori&lt;/code&gt; —— 如果新密钥没生效, 对面立刻会因为认证值对不上把这笔拒掉, 一秒钟就知道结果.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;联调的时候人机配合很顺.&lt;/strong&gt; 对面在电话那头说&quot;再发一笔带 IC 卡数据的&quot;, 敲一行就行, 不用改代码, 不用重新编译, 不用重新开局. 窗口时间按小时算的时候, 这个差别很大.&lt;/p&gt;
&lt;p&gt;这个命令行本身的代码量很小, 大部分就是一个 switch 加一堆预设好的 option, 实现上没什么可说的. 但它是整个项目里被跑得最多的一段代码.&lt;/p&gt;
&lt;h3 id=&quot;zhi-bu-zhi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhi-bu-zhi&quot; aria-label=&quot;Anchor link for: zhi-bu-zhi&quot;&gt;值不值&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;回头看, 这是整个项目里性价比最高的一部分. 报文结构的问题, 状态机的问题, 重连的问题, 九成都在和假中心对接的时候就暴露了. 真正的联调窗口于是只用来验证那些自己猜不到的东西 —— 比如对端的实现和规格书不一致的地方, 这种事总是有的, 而且往往是最后才发现.&lt;/p&gt;
&lt;p&gt;写李鬼当然是有成本的, 粗看甚至像是把同一件事做了两遍. 但这类项目里对端不可控是常态: 你拿不到它, 约不到它, 也不能让它配合你重复一百次异常场景. 与其等着对端可用, 不如先把对端造出来.&lt;/p&gt;
&lt;h2 id=&quot;hou-ji&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#hou-ji&quot; aria-label=&quot;Anchor link for: hou-ji&quot;&gt;后记&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;一个 SDK, 上千页日文 PDF. 目前全流程能跑通了, 不过后面付费联调和上线, 可能还要调整不少.&lt;/p&gt;
&lt;p&gt;做对了的大概是三件事: 用反射加 struct tag 描述报文, 让 bitmap 不需要人来维护; 密钥用接口注入, 把存储和轮换的策略留给业务; 以及从第一天就写李鬼, 而不是等到有联调窗口了再说. 这三件事其实是同一件 —— 都是在把&quot;某个人对规格书的理解&quot;变成&quot;代码里能跑, 能测, 能被别人改的东西&quot;. 这个包迟早不会由我来维护, 到那时候, 留下来的应该是断言和测试, 而不是口头传承.&lt;/p&gt;
&lt;p&gt;而&quot;业务代码里不应该出现任何一个字节&quot;这条, 大概是我从这个项目里带走的最主要的东西 —— 一个 SDK 的价值不在于它实现了多少规格书, 而在于它替使用者挡掉了多少规格书.&lt;/p&gt;
&lt;p&gt;做糙了的地方也不少. 连接池那部分改动最大, 根子上是状态的位置不一样: Redis 那边会话就绑在连接上, 连接本身是无状态, 可替换的, 池子里取哪一条都一样, 坏了丢掉重建就行; 而这边每条连接有自己的远端地址和身份, 会话又独立在连接之外, 池子能做的其实只剩下选路. 结构还是从 go-redis 借来的, 但里面的逻辑基本都换了一遍, 有些写法 (比如那个重试次数, 还有一处 &lt;code&gt;for&lt;/code&gt; 循环里的提前 return) 现在看着就别扭. 密钥那几个接口开重复了, 也该趁早合并掉.&lt;/p&gt;
&lt;p&gt;不过这大概就是这类项目的常态: 规格书是别人的, 时间是固定的, 能做的只有确保自己这部分完整可靠.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>搭建 WireGuard 网络</title>
        <published>2022-11-29T19:00:24+09:00</published>
        <updated>2022-11-29T19:00:24+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/wireguard-in-ubuntu/"/>
        <id>https://memwey.github.io/posts/wireguard-in-ubuntu/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/wireguard-in-ubuntu/">&lt;p&gt;本文介绍在一台 &lt;code&gt;Ubuntu 22.04&lt;/code&gt; 的服务器下搭建 WireGuard 服务器端, 附带有客户端的一些配置.&lt;/p&gt;
&lt;h2 id=&quot;an-zhuang&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#an-zhuang&quot; aria-label=&quot;Anchor link for: an-zhuang&quot;&gt;安装&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;WireGuard 已经集成在 5.6 及以上版本的 Linux Kernel 中, &lt;code&gt;Ubuntu 22.04&lt;/code&gt; 系统下直接安装即可&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo apt update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo apt install wireguard&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;sheng-cheng-mi-yao-dui&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#sheng-cheng-mi-yao-dui&quot; aria-label=&quot;Anchor link for: sheng-cheng-mi-yao-dui&quot;&gt;生成密钥对&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cd /etc/wireguard&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;wg genkey | tee s_private.key | wg pubkey &amp;gt; s_public.key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;wg genkey | tee c_private.key | wg pubkey &amp;gt; c_public.key&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&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;s_private.key&lt;/td&gt;&lt;td&gt;服务器端私钥&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;s_public.key&lt;/td&gt;&lt;td&gt;服务器端公钥&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;c_private.key&lt;/td&gt;&lt;td&gt;客户端私钥&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;c_public.key&lt;/td&gt;&lt;td&gt;客户端公钥&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;pei-zhi-wireguard-fu-wu-qi-duan&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#pei-zhi-wireguard-fu-wu-qi-duan&quot; aria-label=&quot;Anchor link for: pei-zhi-wireguard-fu-wu-qi-duan&quot;&gt;配置 WireGuard 服务器端&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;具体需要各端准备的变量有以下这些&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务器端: 公网 IP, 内网 IP, 公网端口, 公钥, 私钥, 网络接口&lt;/li&gt;
&lt;li&gt;客户端: 内网 IP, 私钥, 公钥&lt;/li&gt;
&lt;/ul&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;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;公网IP&lt;/td&gt;&lt;td&gt;233.233.233.233&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;内网IP&lt;/td&gt;&lt;td&gt;192.168.1.1&lt;/td&gt;&lt;td&gt;192.168.1.2&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;端口配置&lt;/td&gt;&lt;td&gt;udp/6666&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;私钥文件&lt;/td&gt;&lt;td&gt;s_private.key&lt;/td&gt;&lt;td&gt;c_private.key&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;公钥文件&lt;/td&gt;&lt;td&gt;s_public.key&lt;/td&gt;&lt;td&gt;c_public.key&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;网络接口&lt;/td&gt;&lt;td&gt;eth0&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;bian-xie-pei-zhi-wen-jian&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#bian-xie-pei-zhi-wen-jian&quot; aria-label=&quot;Anchor link for: bian-xie-pei-zhi-wen-jian&quot;&gt;编写配置文件&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;文件位置为 &lt;code&gt;/etc/wireguard/wg0.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[Interface]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Address = 192.168.1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;DNS = 223.6.6.6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ListenPort = 6666&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;PrivateKey = s_private.key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;PostUp   = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[Peer]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;PublicKey = c_public.key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;AllowedIPs = 192.168.1.2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;Interface&lt;/code&gt; 可以大致理解为本机的配置, 而 &lt;code&gt;Peer&lt;/code&gt; 是面向外部的配置. 有一些参数需要按照实际情况调整, 密钥需要替换为实际的字符串&lt;/p&gt;
&lt;h3 id=&quot;qi-ta-fu-wu-qi-pei-zhi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#qi-ta-fu-wu-qi-pei-zhi&quot; aria-label=&quot;Anchor link for: qi-ta-fu-wu-qi-pei-zhi&quot;&gt;其他服务器配置&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在服务器端启动 IP 转发&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sysctl net.ipv4.ip_forward=1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外, 可能需要云主机上配置开放对应的 UDP 端口&lt;/p&gt;
&lt;h3 id=&quot;qi-dong-yu-ting-zhi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#qi-dong-yu-ting-zhi&quot; aria-label=&quot;Anchor link for: qi-dong-yu-ting-zhi&quot;&gt;启动与停止&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;配置完成后, 可以使用如下命令, 分别是启动, 查看状态, 停止&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;wg-quick up wg0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;wg show wg0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;wg-quick down wg0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;wireguard-ke-hu-duan-pei-zhi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#wireguard-ke-hu-duan-pei-zhi&quot; aria-label=&quot;Anchor link for: wireguard-ke-hu-duan-pei-zhi&quot;&gt;WireGuard 客户端配置&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;bian-xie-pei-zhi-wen-jian-1&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#bian-xie-pei-zhi-wen-jian-1&quot; aria-label=&quot;Anchor link for: bian-xie-pei-zhi-wen-jian-1&quot;&gt;编写配置文件&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;文件命名为 &lt;code&gt;client.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[Interface]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;PrivateKey = c_private.key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Address = 192.168.1.2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;DNS = 223.6.6.6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[Peer]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;PublicKey = s_public.key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Endpoint = 233.233.233.233:6666&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;AllowedIPs = 0.0.0.0/0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;PersistentKeepalive = 25&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也是要根据实际情况做相应的修改, 密钥替换为实际的字符串&lt;/p&gt;
&lt;h3 id=&quot;qi-ta-ke-hu-duan-pei-zhi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#qi-ta-ke-hu-duan-pei-zhi&quot; aria-label=&quot;Anchor link for: qi-ta-ke-hu-duan-pei-zhi&quot;&gt;其他客户端配置&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;可以使用如下命令在命令行生成二维码, 方便手机扫描&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;qrencode -t ansiutf8 &amp;lt; client.conf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>Redis 查询偶尔超时问题排查</title>
        <published>2022-10-28T14:54:14+09:00</published>
        <updated>2022-10-28T14:54:14+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/redis-troubleshooting/"/>
        <id>https://memwey.github.io/posts/redis-troubleshooting/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/redis-troubleshooting/">&lt;h2 id=&quot;biao-xian&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#biao-xian&quot; aria-label=&quot;Anchor link for: biao-xian&quot;&gt;表现&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;业务上 Redis 做去重使用, 量大但是操作简单, 超时均设置为 10ms, 偶尔有超时情况报错, 超时错误分布与业务流量正相关. Redis 由 &lt;code&gt;AWS&lt;/code&gt; 的 &lt;code&gt;ElasticCache&lt;/code&gt; 服务提供. 其中典型操作如下&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;SET key value expiration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;GET key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;MGET key1 key2 ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;redis-ke-hu-duan-ce-shi-shu-ju&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#redis-ke-hu-duan-ce-shi-shu-ju&quot; aria-label=&quot;Anchor link for: redis-ke-hu-duan-ce-shi-shu-ju&quot;&gt;Redis 客户端测试数据&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;latency-history&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#latency-history&quot; aria-label=&quot;Anchor link for: latency-history&quot;&gt;latency-history&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Redis&lt;/code&gt; 的 &lt;code&gt;latency-history&lt;/code&gt; 选项将 &lt;code&gt;PING&lt;/code&gt; 命令发送到 &lt;code&gt;Redis&lt;/code&gt; 实例, 并测量响应时间, 统计时间以毫秒为单位.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# date&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Fri Oct 28 10:49:58 JST 2022&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# redis-cli -h {host} -p {port} --latency-history -i 5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 5, avg: 1.92 (418 samples) -- 5.00 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 5, avg: 1.93 (418 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 5, avg: 1.93 (417 samples) -- 5.00 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 7, avg: 1.98 (416 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 4, avg: 1.94 (417 samples) -- 5.00 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 3, avg: 1.93 (418 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 3, avg: 1.92 (418 samples) -- 5.00 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 3, avg: 1.94 (418 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 3, avg: 1.95 (418 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 17, avg: 2.08 (412 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 5, avg: 2.00 (416 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 4, avg: 1.94 (417 samples) -- 5.00 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 5, avg: 1.89 (418 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 3, avg: 1.91 (418 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 13, avg: 2.01 (415 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 12, avg: 2.12 (411 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 5, avg: 1.96 (416 samples) -- 5.00 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 8, avg: 2.07 (413 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 7, avg: 1.97 (415 samples) -- 5.00 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 5, avg: 1.93 (417 samples) -- 5.00 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 8, avg: 1.99 (416 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 6, avg: 2.04 (413 samples) -- 5.00 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 1, max: 3, avg: 2.03 (79 samples)^C&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# date&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Fri Oct 28 10:52:16 JST 2022&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到, 对于 &lt;code&gt;PING&lt;/code&gt; 这样可以说没有任何性能负担的命令, 依然有少数情况响应时间大于 10ms&lt;/p&gt;
&lt;h3 id=&quot;shi-yong-benchmark-ce-shi-jian-dan-cao-zuo&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#shi-yong-benchmark-ce-shi-jian-dan-cao-zuo&quot; aria-label=&quot;Anchor link for: shi-yong-benchmark-ce-shi-jian-dan-cao-zuo&quot;&gt;使用 benchmark 测试简单操作&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# redis-benchmark -h {host} -t set,get -c 50 -d 64&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;====== SET ======&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  100000 requests completed in 4.51 seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  50 parallel clients&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  64 bytes payload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  keep alive: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;0.00% &amp;lt;= 1 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;20.53% &amp;lt;= 2 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;95.87% &amp;lt;= 3 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;98.53% &amp;lt;= 4 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.24% &amp;lt;= 5 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.72% &amp;lt;= 6 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.75% &amp;lt;= 7 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.75% &amp;lt;= 8 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.76% &amp;lt;= 9 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.80% &amp;lt;= 10 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.85% &amp;lt;= 11 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.92% &amp;lt;= 12 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.98% &amp;lt;= 13 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;100.00% &amp;lt;= 13 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;22187.71 requests per second&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;====== GET ======&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  100000 requests completed in 5.78 seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  50 parallel clients&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  64 bytes payload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  keep alive: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;0.00% &amp;lt;= 1 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;6.49% &amp;lt;= 2 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;76.89% &amp;lt;= 3 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;89.58% &amp;lt;= 4 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;94.31% &amp;lt;= 5 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;96.33% &amp;lt;= 6 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;97.64% &amp;lt;= 7 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;98.36% &amp;lt;= 8 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;98.77% &amp;lt;= 9 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.01% &amp;lt;= 10 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.22% &amp;lt;= 11 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.39% &amp;lt;= 12 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.51% &amp;lt;= 13 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.64% &amp;lt;= 14 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.67% &amp;lt;= 15 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.77% &amp;lt;= 16 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.81% &amp;lt;= 17 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.84% &amp;lt;= 18 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.86% &amp;lt;= 19 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.90% &amp;lt;= 20 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.91% &amp;lt;= 21 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.93% &amp;lt;= 22 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.94% &amp;lt;= 23 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.95% &amp;lt;= 24 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.95% &amp;lt;= 36 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.96% &amp;lt;= 37 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.97% &amp;lt;= 38 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.98% &amp;lt;= 39 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.99% &amp;lt;= 40 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.99% &amp;lt;= 41 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;100.00% &amp;lt;= 42 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;100.00% &amp;lt;= 42 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;17310.02 requests per second&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# date&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Fri Oct 28 11:20:23 JST 2022&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于简单操作的情况, 两个 9 的数据可以在 10ms 以内, 但是性能仍然不对, 预期操作在毫秒以内才是 &lt;code&gt;Redis&lt;/code&gt; 正常的表现.&lt;/p&gt;
&lt;h3 id=&quot;shi-yong-bigkeys-jian-cha-key-qing-kuang&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#shi-yong-bigkeys-jian-cha-key-qing-kuang&quot; aria-label=&quot;Anchor link for: shi-yong-bigkeys-jian-cha-key-qing-kuang&quot;&gt;使用 bigkeys 检查 Key 情况&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# redis-cli -h {host} --bigkeys&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Scanning the entire keyspace to find biggest keys as well as&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# average sizes per key type.  You can use -i 0.1 to sleep 0.1 sec&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# per 100 SCAN commands (not usually needed).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[00.00%] Biggest string found so far &amp;#39;aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&amp;#39; with 1 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[00.00%] Biggest string found so far &amp;#39;aaaaaaaaaaaaaaaaaaa&amp;#39; with 644 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[00.00%] Biggest string found so far &amp;#39;aaaaaaaaaaaaaaaaaaa&amp;#39; with 664 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[00.05%] Biggest string found so far &amp;#39;aaaaaaaaaaaaaaaaaaaa&amp;#39; with 668 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[00.29%] Biggest string found so far &amp;#39;aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&amp;#39; with 1312 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-------- summary -------&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Sampled 269241 keys in the keyspace!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Total key length in bytes is 11026631 (avg len 40.95)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Biggest string found &amp;#39;aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&amp;#39; has 1312 bytes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;0 lists with 0 items (00.00% of keys, avg size 0.00)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;0 hashs with 0 fields (00.00% of keys, avg size 0.00)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;269241 strings with 45265771 bytes (100.00% of keys, avg size 168.12)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;0 streams with 0 entries (00.00% of keys, avg size 0.00)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;0 sets with 0 members (00.00% of keys, avg size 0.00)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;0 zsets with 0 members (00.00% of keys, avg size 0.00)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# date&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Fri Oct 28 11:33:23 JST 2022&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Key 整体分布均匀, 没有特别大的 Key 的情况.&lt;/p&gt;
&lt;h2 id=&quot;ya-ma-xun-shi-li-shu-ju&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ya-ma-xun-shi-li-shu-ju&quot; aria-label=&quot;Anchor link for: ya-ma-xun-shi-li-shu-ju&quot;&gt;亚马逊实例数据&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;slowlog-dian-xing-an-li&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#slowlog-dian-xing-an-li&quot; aria-label=&quot;Anchor link for: slowlog-dian-xing-an-li&quot;&gt;slowlog 典型案例&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;来自Redis 的 &lt;code&gt;SLOWLOG&lt;/code&gt; 命令脱敏处理&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;quot;CacheClusterId&amp;quot;: &amp;quot;prod-rate-redis-001&amp;quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;quot;CacheNodeId&amp;quot;: &amp;quot;0001&amp;quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;quot;Id&amp;quot;: 592,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;quot;Timestamp&amp;quot;: 1666749122,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;quot;Duration (us)&amp;quot;: 18331,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;quot;Command&amp;quot;: &amp;quot;get ... (1 more arguments)&amp;quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;quot;ClientAddress&amp;quot;: &amp;quot;172.22.20.210:46370&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;ke-hu-duan-ce-shi-shi-cpu-zhi-biao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ke-hu-duan-ce-shi-shi-cpu-zhi-biao&quot; aria-label=&quot;Anchor link for: ke-hu-duan-ce-shi-shi-cpu-zhi-biao&quot;&gt;客户端测试时 CPU 指标&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/redis-troubleshooting/images/pic1.png&quot; alt=&quot;pic1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;两个高峰分别是 &lt;code&gt;benchmark&lt;/code&gt; 和 &lt;code&gt;bigkey&lt;/code&gt; 操作引起.&lt;/p&gt;
&lt;h3 id=&quot;wang-luo-zhi-biao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#wang-luo-zhi-biao&quot; aria-label=&quot;Anchor link for: wang-luo-zhi-biao&quot;&gt;网络指标&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/redis-troubleshooting/images/pic2.png&quot; alt=&quot;pic2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到, &lt;code&gt;Redis&lt;/code&gt; 网络数据速度每隔一小时会有一个突增.&lt;/p&gt;
&lt;h2 id=&quot;fen-xi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#fen-xi&quot; aria-label=&quot;Anchor link for: fen-xi&quot;&gt;分析&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Redis&lt;/code&gt; 实例是有问题的, 简单操作耗时不能在 1ms 内返回, 99 分位在 10ms, 明显不是一个内存型数据库应有的性能.&lt;/p&gt;
&lt;p&gt;由于在性能指标上一直看到 &lt;code&gt;Redis&lt;/code&gt; 的 CPU 使用率不高, 导致了错误的排查方向, 以为是我们在使用上有问题. 实际上, 问题出在 &lt;code&gt;aws&lt;/code&gt; 的实例上.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;aws&lt;/code&gt; 的 t2 的实例以 20% 为性能基准, 低于 20% 累计积分, 其中积分累计有上限; 超过 20% 减少积分直至耗尽; 积分是以小时为单位积累的. 虽然没有具体的说明, 实际上 t2 实例的网络带宽也是有相应的积分限制的, 所以体现在网络指标上, 每隔一个小时出现一个特别的波峰.&lt;/p&gt;
&lt;p&gt;另外, &lt;code&gt;aws&lt;/code&gt; 标注的 t2 实例的网络性能是 &lt;code&gt;Low to Moderate&lt;/code&gt;, 虽然没有官方的解释, 但是根据网友的测试, 基本上是 50Mbps 到 300Mbps. 这也解释了为何网络指标上一直在 6MB/s 左右上下浮动.&lt;/p&gt;
&lt;h2 id=&quot;jie-lun&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#jie-lun&quot; aria-label=&quot;Anchor link for: jie-lun&quot;&gt;结论&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;使用了 &lt;code&gt;aws&lt;/code&gt; 提供的 &lt;code&gt;t2.small&lt;/code&gt; 实例, 在 CPU 性能, 网络性能上均有限制, 导致 &lt;code&gt;Redis&lt;/code&gt; 实例出现超时的情况. 需要提高 &lt;code&gt;Redis&lt;/code&gt; 实例的配置, 或者使用自建 &lt;code&gt;Redis&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;谨慎使用 &lt;code&gt;aws&lt;/code&gt; 的实例类型; 在性能敏感的情况下, 不要使用 &lt;code&gt;aws&lt;/code&gt; 的 t 类型实例, 一方面是有性能的积分的限制, 另一方面, 也容易碰到 &lt;a rel=&quot;external&quot; href=&quot;https://learn.microsoft.com/en-us/azure/architecture/antipatterns/noisy-neighbor/noisy-neighbor&quot;&gt;Noisy Neighbor&lt;/a&gt; 的问题.&lt;/p&gt;
&lt;h2 id=&quot;can-kao-wen-dang&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#can-kao-wen-dang&quot; aria-label=&quot;Anchor link for: can-kao-wen-dang&quot;&gt;参考文档&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://redis.io/docs/reference/optimization/latency/&quot;&gt;Diagnosing latency issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://xie.infoq.cn/article/fbd51c19883ddc1ae19fbc96b&quot;&gt;Redis 为什么变慢了？一文讲透如何排查 Redis 性能问题 | 万字长文&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://developer.aliyun.com/article/702247&quot;&gt;分析网络超时问题的最佳实践&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/TroubleshootingConnections.html&quot;&gt;Troubleshooting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-troubleshoot-timeouts&quot;&gt;Troubleshoot Azure Cache for Redis latency and timeouts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://aws.amazon.com/cn/ec2/instance-types/&quot;&gt;Amazon EC2 实例类型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://stackoverflow.com/questions/20663619/what-does-amazon-aws-mean-by-network-performance&quot;&gt;What does amazon aws mean by &quot;network performance&quot;?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.reddit.com/r/aws/comments/d35sz9/what_does_low_to_moderate_network_peformance_for/&quot;&gt;What does &#39;Low to Moderate&#39; network peformance for an ElastiCache Node Type actually mean?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://aws.amazon.com/cn/ec2/instance-types/#Burstable_Performance_Instances&quot;&gt;Amazon EC2 实例类型#具爆发能力的实例&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;fu-jian&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#fu-jian&quot; aria-label=&quot;Anchor link for: fu-jian&quot;&gt;附件&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;qa 环境下自建 Redis 性能测试&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# redis-cli -h {host} -p 6379 --latency-history -i 5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 0, max: 3, avg: 0.34 (482 samples) -- 5.00 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 0, max: 1, avg: 0.33 (482 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 0, max: 1, avg: 0.32 (481 samples) -- 5.00 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 0, max: 3, avg: 0.33 (481 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 0, max: 4, avg: 0.32 (482 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 0, max: 3, avg: 0.31 (483 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 0, max: 1, avg: 0.30 (483 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 0, max: 1, avg: 0.29 (483 samples) -- 5.01 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 0, max: 1, avg: 0.31 (481 samples) -- 5.00 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 0, max: 1, avg: 0.32 (481 samples) -- 5.00 seconds range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;min: 0, max: 1, avg: 0.28 (25 samples)^C&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# date&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Mon Oct 31 18:47:05 JST 2022&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# redis-benchmark -h {host} -t set,get -c 50 -d 64&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;====== SET ======&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  100000 requests completed in 1.36 seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  50 parallel clients&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  64 bytes payload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  keep alive: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.88% &amp;lt;= 1 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.97% &amp;lt;= 2 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.99% &amp;lt;= 4 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;100.00% &amp;lt;= 5 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;100.00% &amp;lt;= 5 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;73313.78 requests per second&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;====== GET ======&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  100000 requests completed in 1.30 seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  50 parallel clients&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  64 bytes payload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  keep alive: 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.90% &amp;lt;= 1 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.94% &amp;lt;= 2 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.95% &amp;lt;= 4 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;99.96% &amp;lt;= 5 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;100.00% &amp;lt;= 5 milliseconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;76628.36 requests per second&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# date&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Mon Oct 31 18:48:10 JST 2022&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;99分位 10ms -&amp;gt; 999分位 1ms&lt;/p&gt;
&lt;p&gt;不过自建 &lt;code&gt;Redis&lt;/code&gt;, 由于是在 &lt;code&gt;EC2&lt;/code&gt; 上的托管 Kubernetes 集群, 也需要注意 &lt;a rel=&quot;external&quot; href=&quot;https://learn.microsoft.com/en-us/azure/architecture/antipatterns/noisy-neighbor/noisy-neighbor&quot;&gt;Noisy Neighbor&lt;/a&gt; 的问题.&lt;/p&gt;
&lt;h2 id=&quot;redis-chang-jian-wen-ti-pai-cha-si-lu&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#redis-chang-jian-wen-ti-pai-cha-si-lu&quot; aria-label=&quot;Anchor link for: redis-chang-jian-wen-ti-pai-cha-si-lu&quot;&gt;Redis 常见问题排查思路&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;除了本次遇到的实例性能限制问题, 排查 &lt;code&gt;Redis&lt;/code&gt; 延迟问题时, 通常还可以从以下几个常见方面入手&lt;/p&gt;
&lt;h3 id=&quot;lian-jie-chi-wen-ti&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#lian-jie-chi-wen-ti&quot; aria-label=&quot;Anchor link for: lian-jie-chi-wen-ti&quot;&gt;连接池问题&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Redis 配置连接池的重要参数 &lt;code&gt;MaxIdleOpen&lt;/code&gt;, &lt;code&gt;IdleCloseTimeout&lt;/code&gt;, 配置不当时, 查看端口占用会有大量 &lt;code&gt;TIME_WAIT&lt;/code&gt;, &lt;code&gt;TCP&lt;/code&gt; 的断开会有 2&lt;a rel=&quot;external&quot; href=&quot;https://stackoverflow.com/questions/289194/what-is-maximum-segment-lifetime-msl-in-tcp&quot;&gt;MSL&lt;/a&gt; - MSL 在 Linux 默认 60s; 适当配置 keep-alive
&lt;img src=&quot;https://memwey.github.io/posts/redis-troubleshooting/images/tcp_open_close.jpeg&quot; alt=&quot;tcp&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;big-key&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#big-key&quot; aria-label=&quot;Anchor link for: big-key&quot;&gt;Big Key&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;单个 Key 内容过大, 单线程特性, 可能阻塞整个服务&lt;/p&gt;
&lt;h3 id=&quot;hao-shi-cao-zuo&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#hao-shi-cao-zuo&quot; aria-label=&quot;Anchor link for: hao-shi-cao-zuo&quot;&gt;耗时操作&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;O(N)操作, &lt;code&gt;KEYS&lt;/code&gt;, &lt;code&gt;SORT&lt;/code&gt;, &lt;code&gt;LINDEX&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&quot;tong-shi-chao-shi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#tong-shi-chao-shi&quot; aria-label=&quot;Anchor link for: tong-shi-chao-shi&quot;&gt;同时超时&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;超时淘汰方式 - lazy, active, 大量 (25%以上) Key 同时超时, 缓存预热, 超时增加随机因子&lt;/p&gt;
&lt;h2 id=&quot;redis-fun-fact&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#redis-fun-fact&quot; aria-label=&quot;Anchor link for: redis-fun-fact&quot;&gt;Redis Fun Fact&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Redis 没有&quot;读写&quot;的区别&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;http://antirez.com/news/109&quot;&gt;Redis LRU 的近似实现&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;ziplist, 紧凑的内存布局, 用于优化存储小数据量的 &lt;code&gt;list&lt;/code&gt;, &lt;code&gt;hash&lt;/code&gt;, &lt;code&gt;zset&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>把 crontab 从时间点变成时间段</title>
        <published>2022-09-11T15:42:08+09:00</published>
        <updated>2022-09-11T15:42:08+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/cron-from-moment-to-range/"/>
        <id>https://memwey.github.io/posts/cron-from-moment-to-range/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/cron-from-moment-to-range/">&lt;h2 id=&quot;cong-yi-ge-cu-xiao-xu-qiu-shuo-qi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#cong-yi-ge-cu-xiao-xu-qiu-shuo-qi&quot; aria-label=&quot;Anchor link for: cong-yi-ge-cu-xiao-xu-qiu-shuo-qi&quot;&gt;从一个促销需求说起&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;业务上要支持这样的规则:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每月 29 日, 肉类半价&lt;/li&gt;
&lt;li&gt;每周五晚上十点以后, 积分翻倍&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一开始的想法是在数据库里建几个字段: 星期几, 从几点到几点, 每月第几天. 但很快就发现字段不够用 —— 下一条规则可能是&quot;每月最后一个周五&quot;, 再下一条是&quot;每季度第一天&quot;, 每加一种就要加字段, 或者加一张表.&lt;/p&gt;
&lt;p&gt;盯着这些规则看了一会, 会发现它们和 crontab 长得很像. &quot;每周五 22 点&quot;就是 &lt;code&gt;0 22 * * 5&lt;/code&gt;, &quot;每月 29 日零点&quot;就是 &lt;code&gt;0 0 29 * *&lt;/code&gt;. crontab 本来就是为了描述&quot;周期性地在什么时候&quot;而设计的, 而且它已经存在几十年, 所有工程师都认识, 现成的解析库到处都是.&lt;/p&gt;
&lt;p&gt;差的只有一点: &lt;strong&gt;crontab 描述的是时间点, 而我们需要的是时间段&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;补上这一点其实很简单 —— 给 cron 表达式加一个&quot;持续多久&quot;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cron 表达式  +  时长  =  周期性的时间段&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;cronrange&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#cronrange&quot; aria-label=&quot;Anchor link for: cronrange&quot;&gt;cronrange&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这个想法已经有人做了, &lt;a rel=&quot;external&quot; href=&quot;https://github.com/1set/cronrange&quot;&gt;1set/cronrange&lt;/a&gt;. 表达式长这样:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;DR=120; TZ=Asia/Tokyo; 0 22 * * 5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分号隔开的三段:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DR=120&lt;/code&gt; 是时长, 单位分钟;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TZ=Asia/Tokyo&lt;/code&gt; 是时区, 可选, 用 IANA 时区数据库里的名字;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0 22 * * 5&lt;/code&gt; 是 cron 表达式, 表示时间段的&lt;strong&gt;起点&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;开头那两条规则就成了:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;每月 29 日全天肉类半价        DR=1440; TZ=Asia/Tokyo; 0 0 29 * *&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;每周五 22 点起两小时双倍积分   DR=120;  TZ=Asia/Tokyo; 0 22 * * 5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;好处很直接: 一条规则就是一个字符串. 数据库里一个 varchar 就存下了, 不用为了新规则改表结构; 走 JSON 也是一个字符串, 这个库实现了 &lt;code&gt;MarshalJSON&lt;/code&gt; / &lt;code&gt;UnmarshalJSON&lt;/code&gt;, 序列化的事情不用自己写.&lt;/p&gt;
&lt;p&gt;时区那一段值得单独说一句. &quot;每周五晚上十点&quot;是谁的十点? 服务器所在时区, 还是门店所在时区? 这个问题在只有一个国家的时候可以糊弄过去, 有了第二个国家就必须回答. 表达式里带上时区, 等于强迫配置的人当场想清楚.&lt;/p&gt;
&lt;h2 id=&quot;wo-men-de-yong-fa-shi-fan-guo-lai-de&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#wo-men-de-yong-fa-shi-fan-guo-lai-de&quot; aria-label=&quot;Anchor link for: wo-men-de-yong-fa-shi-fan-guo-lai-de&quot;&gt;我们的用法是反过来的&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这是我觉得最值得说的一点.&lt;/p&gt;
&lt;p&gt;大部分 cron 库是为&lt;strong&gt;调度&lt;/strong&gt;写的, 核心方法是&quot;下一次是什么时候&quot; —— 你注册一个任务, 库来负责在正确的时刻叫醒你. cronrange 里对应的是 &lt;code&gt;NextOccurrences()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;而我们要的是相反的方向: &lt;strong&gt;给定一个时刻, 判断它落不落在某个时间段里&lt;/strong&gt;. 一笔交易进来, 带着自己的时间戳, 要判断适用哪些规则. 对应的方法是 &lt;code&gt;IsWithin()&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; cronrange&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ParseString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;DR=120; TZ=Asia/Tokyo; 0 22 * * 5&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;if&lt;/span&gt;&lt;span&gt; cr&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;IsWithin&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;order&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PaidAt&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 双倍积分&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意这里传的是 &lt;code&gt;order.PaidAt&lt;/code&gt;, 不是 &lt;code&gt;time.Now()&lt;/code&gt;. 这一点很重要 —— 补单, 重算, 对账都要用历史时间. 如果判断依赖&quot;现在&quot;, 那隔天重跑一遍得到的结果就和当时不一样, 在和钱有关的系统里这是不能接受的. 所以这类接口一定要能传入任意时刻, 而不是把 &lt;code&gt;now&lt;/code&gt; 藏在里面.&lt;/p&gt;
&lt;p&gt;同一个表达式, 调度器拿去往前推, 我们拿来往回查, 方向正好相反.&lt;/p&gt;
&lt;h2 id=&quot;iswithin-shi-zen-me-shi-xian-de&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#iswithin-shi-zen-me-shi-xian-de&quot; aria-label=&quot;Anchor link for: iswithin-shi-zen-me-shi-xian-de&quot;&gt;IsWithin 是怎么实现的&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这里有个有意思的地方: &lt;code&gt;cron.Schedule&lt;/code&gt; 只提供 &lt;code&gt;Next()&lt;/code&gt;, 没有 &lt;code&gt;Prev()&lt;/code&gt;. 你没法直接问&quot;上一次是什么时候&quot;.&lt;/p&gt;
&lt;p&gt;所以判断 &lt;code&gt;t&lt;/code&gt; 在不在窗口里, 只能先往回退一个时长, 再从那里问&quot;下一次是什么时候&quot;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;searchStart&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; t&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cr&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;duration&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; +&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Second&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Nanosecond&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;rangeStart&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; cr&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;schedule&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Next&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;searchStart&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;rangeEnd&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; rangeStart&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cr&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;duration&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 检查 rangeStart &amp;lt;= t &amp;lt;= rangeEnd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;within&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;rangeStart&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Before&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; rangeEnd&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;After&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ||&lt;/span&gt;&lt;span&gt; rangeStart&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Equal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ||&lt;/span&gt;&lt;span&gt; rangeEnd&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Equal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;退一个 &lt;code&gt;duration&lt;/code&gt; 是主体, 后面那个 &lt;code&gt;+ 1s - 1ns&lt;/code&gt; 是补偿: &lt;code&gt;Next()&lt;/code&gt; 返回的是&lt;strong&gt;严格晚于&lt;/strong&gt;给定时刻的下一次, 而且精度只到秒. 不多退这一点, 恰好卡在边界上的窗口会被漏掉.&lt;/p&gt;
&lt;p&gt;另外要注意区间是&lt;strong&gt;闭&lt;/strong&gt;的, 两端都算. 拿&quot;每周五 22 点起两小时&quot;实测一下:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;时刻&lt;/th&gt;&lt;th&gt;IsWithin&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;周五 21:59:59&lt;/td&gt;&lt;td&gt;false&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;周五 22:00:00&lt;/td&gt;&lt;td&gt;&lt;strong&gt;true&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;周五 23:00:00&lt;/td&gt;&lt;td&gt;true&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;周六 00:00:00&lt;/td&gt;&lt;td&gt;&lt;strong&gt;true&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;周六 00:00:01&lt;/td&gt;&lt;td&gt;false&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;两端都命中, 意味着相邻的两个时间段在交界点上会同时成立. 如果配了&quot;今天 0 点到 24 点&quot;和&quot;明天 0 点到 24 点&quot;两条规则, 那么正好落在 &lt;code&gt;00:00:00&lt;/code&gt; 的一笔交易两条都满足. 要么在业务层再判一次, 要么把时长设成差一秒.&lt;/p&gt;
&lt;h2 id=&quot;yi-ge-bi-xu-ti-xing-yun-ying-de-keng&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#yi-ge-bi-xu-ti-xing-yun-ying-de-keng&quot; aria-label=&quot;Anchor link for: yi-ge-bi-xu-ti-xing-yun-ying-de-keng&quot;&gt;一个必须提醒运营的坑&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;写完之后顺手把&quot;每月 29 日&quot;的未来几次打印出来看看:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[2022-01-29T00:00:00+09:00, 2022-01-30T00:00:00+09:00]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[2022-03-29T00:00:00+09:00, 2022-03-30T00:00:00+09:00]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[2022-04-29T00:00:00+09:00, 2022-04-30T00:00:00+09:00]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[2022-05-29T00:00:00+09:00, 2022-05-30T00:00:00+09:00]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;二月没有.&lt;/strong&gt; 2022 年不是闰年, 没有 2 月 29 日, 所以这条规则在二月整月不生效.&lt;/p&gt;
&lt;p&gt;这不是库的 bug, cron 本来就是这个语义. 但配规则的是运营, 他们说&quot;每月 29 日&quot;的时候, 默认每个月都有 29 日. 类似的还有 31 日, 一年里有四个月不会触发.&lt;/p&gt;
&lt;p&gt;所以这里的做法是: 保存规则的时候, 把接下来 12 次的触发时间列出来给人确认一遍. 也就是说, &lt;code&gt;NextOccurrences()&lt;/code&gt; 在我们的用法里不是用来调度的, 而是用来给人预览的. 一个本来给调度器用的方法, 换个场景成了配置界面上的校验工具.&lt;/p&gt;
&lt;h2 id=&quot;jia-liang-jian-dong-xi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#jia-liang-jian-dong-xi&quot; aria-label=&quot;Anchor link for: jia-liang-jian-dong-xi&quot;&gt;加两件东西&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;用起来之后有两个地方不太够用, 于是在 fork 上改了改, 代码在 &lt;a rel=&quot;external&quot; href=&quot;https://github.com/memwey/cronrange&quot;&gt;memwey/cronrange&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;shi-chang-bu-gai-zhi-you-fen-zhong&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#shi-chang-bu-gai-zhi-you-fen-zhong&quot; aria-label=&quot;Anchor link for: shi-chang-bu-gai-zhi-you-fen-zhong&quot;&gt;时长不该只有分钟&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;原来的构造函数是这样的:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; New&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;cronExpr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; timeZone&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; string&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; durationMin&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; uint64&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;CronRange&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&quot;分钟&quot;被写死在类型里了. 这带来两个问题: &lt;code&gt;DR=90&lt;/code&gt; 到底是 90 分钟还是 90 秒, 只能翻文档才知道; 而且秒级的窗口 (比如秒杀) 根本表达不出来.&lt;/p&gt;
&lt;p&gt;Go 里本来就有 &lt;code&gt;time.Duration&lt;/code&gt; 这个类型, 单位是自带的, 没道理不用. 但 &lt;code&gt;New&lt;/code&gt; 的签名不能改, 一改所有调用方都得跟着改. 所以新开一个:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; Create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;cronExpr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; timeZone&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; string&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; duration&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Duration&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; cp&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; cron&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Parser&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;CronRange&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;cron-jie-xi-qi-yao-neng-huan&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#cron-jie-xi-qi-yao-neng-huan&quot; aria-label=&quot;Anchor link for: cron-jie-xi-qi-yao-neng-huan&quot;&gt;cron 解析器要能换&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;robfig/cron 的解析行为是在构造 &lt;code&gt;Parser&lt;/code&gt; 的时候定死的: 是五个字段还是六个字段 (带秒), 支不支持 &lt;code&gt;@every&lt;/code&gt; 这类描述符. 而库里原本写死了一个包级的 parser.&lt;/p&gt;
&lt;p&gt;不同项目对 cron 方言的要求并不一样, 尤其是需要秒级精度的时候. 与其在库里多加几个开关, 不如把 &lt;code&gt;Parser&lt;/code&gt; 直接作为参数传进来, 让使用者自己决定.&lt;/p&gt;
&lt;p&gt;不过光有 &lt;code&gt;Create()&lt;/code&gt; 还不够. 这个库的用法前提是规则以字符串的形式存起来, 所以除了构造, 还得能读回来 —— 而 &lt;code&gt;ParseString()&lt;/code&gt; 用的是写死的包级 parser. 只加 &lt;code&gt;Create()&lt;/code&gt; 的话, 自定义 parser 就只能用一半: 造得出来, 存得进去, 读不回来.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;String()                          = &amp;quot;DR=3s; TZ=Asia/Tokyo; */10 * * * * *&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ParseString(s)                    -&amp;gt; expected exactly 5 fields, found 6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ParseStringWithCronParser(s, sec) -&amp;gt; ok&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以配套加了 &lt;code&gt;ParseStringWithCronParser()&lt;/code&gt;, 把这个往返闭合上.&lt;/p&gt;
&lt;h2 id=&quot;ma-fan-de-shi-ge-shi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ma-fan-de-shi-ge-shi&quot; aria-label=&quot;Anchor link for: ma-fan-de-shi-ge-shi&quot;&gt;麻烦的是格式&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;真正花时间的是这一块.&lt;/p&gt;
&lt;p&gt;表达式是要&lt;strong&gt;存起来&lt;/strong&gt;的 —— 数据库里躺着一批, JSON 里传着一批. 加了 &lt;code&gt;time.Duration&lt;/code&gt; 之后, &lt;code&gt;DR=90&lt;/code&gt; 和 &lt;code&gt;DR=2h&lt;/code&gt; 都必须能解析.&lt;/p&gt;
&lt;p&gt;按说应该在格式里加个版本号, 但那样所有已经存下来的字符串都要迁移. 于是换个办法: &lt;strong&gt;靠值本身的形状来区分&lt;/strong&gt;. 纯数字是老格式 (分钟), 带单位是新格式.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;if&lt;/span&gt;&lt;span&gt; duration&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ParseDuration&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;durStr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; duration&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        version&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; Version2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;if&lt;/span&gt;&lt;span&gt; durMin&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; strconv&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ParseUint&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;durStr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 64&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; version&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span&gt; Version2&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        break&lt;/span&gt;&lt;span&gt; PL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先试新格式, 不行再退回老格式. 这两者正好不重叠: &lt;code&gt;time.ParseDuration(&quot;90&quot;)&lt;/code&gt; 会因为缺单位而失败, &lt;code&gt;strconv.ParseUint(&quot;1h30m&quot;)&lt;/code&gt; 也过不去, 不会有一个字符串两边都认.&lt;/p&gt;
&lt;p&gt;反过来, &lt;code&gt;String()&lt;/code&gt; 该输出哪一种? 用一个 &lt;code&gt;version&lt;/code&gt; 字段记住这个实例当初是怎么来的:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;New(..., 90).String()                = &amp;quot;DR=90; TZ=Asia/Tokyo; 0 22 * * 5&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Create(..., 90*time.Minute).String() = &amp;quot;DR=1h30m0s; TZ=Asia/Tokyo; 0 22 * * 5&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两者的 &lt;code&gt;Duration()&lt;/code&gt; 完全相等, 只是打印出来不一样.&lt;/p&gt;
&lt;p&gt;这么做是为了保证 round-trip: 从数据库读出来的老表达式, 解析之后再写回去, 还是原来的样子. 不会因为某天升级了一个库版本, 存量记录被悄悄改写了一遍 —— 那种事情在排查问题的时候非常难受, 因为你会怀疑是不是有人手动改过数据.&lt;/p&gt;
&lt;h3 id=&quot;zhe-me-zuo-de-dai-jia&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhe-me-zuo-de-dai-jia&quot; aria-label=&quot;Anchor link for: zhe-me-zuo-de-dai-jia&quot;&gt;这么做的代价&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;内部状态泄漏到了输出行为上. 同样是 90 分钟, 只因为来源不同, &lt;code&gt;String()&lt;/code&gt; 的结果就不一样. 对使用者来说这是个意外.&lt;/p&gt;
&lt;p&gt;更干净的做法大概有两种: 解析的时候把原始文本记下来, &lt;code&gt;String()&lt;/code&gt; 原样吐回去; 或者干脆规定 &lt;code&gt;String()&lt;/code&gt; 永远输出新格式, 并接受&quot;存量数据会被逐步改写&quot;这件事. 但这两种都要动更多地方, 在一个 fork 上做改动, 我选了侵入最小的那个. 这个取舍现在看仍然成立, 只是应该在文档里写清楚, 而不是让人自己撞上.&lt;/p&gt;
&lt;h2 id=&quot;shun-dai-fa-xian-de-yi-ge-wen-ti&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#shun-dai-fa-xian-de-yi-ge-wen-ti&quot; aria-label=&quot;Anchor link for: shun-dai-fa-xian-de-yi-ge-wen-ti&quot;&gt;顺带发现的一个问题&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;读解析代码的时候注意到, 未知的部分并不总是会报错:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;quot;junk; DR=90; 0 0 1 1 *&amp;quot;   -&amp;gt;  解析成功&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;quot;DR=90; junk; 0 0 1 1 *&amp;quot;   -&amp;gt;  报错 unknown part: &amp;quot;junk&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同样一个 &lt;code&gt;junk&lt;/code&gt;, 放在 &lt;code&gt;DR=&lt;/code&gt; 前面就被吃掉了, 放在后面才报错.&lt;/p&gt;
&lt;p&gt;原因在上面那段代码里: &lt;code&gt;DR=&lt;/code&gt; 这个分支把命名返回值 &lt;code&gt;err&lt;/code&gt; 当中间变量用了. &lt;code&gt;strconv.ParseUint&lt;/code&gt; 成功的时候会把 &lt;code&gt;err&lt;/code&gt; 赋成 &lt;code&gt;nil&lt;/code&gt;, 而前一轮循环里 &lt;code&gt;default&lt;/code&gt; 分支设置的&quot;未知部分&quot;错误, 就这么被盖掉了.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 有问题: err 是命名返回值, 这里被用来接临时结果&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;if&lt;/span&gt;&lt;span&gt; durMin&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; strconv&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ParseUint&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;durStr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 64&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; nil&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 应该用局部变量, 最后再决定 err 是什么&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; parseErr&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :=&lt;/span&gt;&lt;span&gt; strconv&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ParseUint&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;durStr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 64&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个问题在加 &lt;code&gt;time.Duration&lt;/code&gt; 之前就有了, 只是现在多了一层试探, 更容易踩到. Go 的命名返回值本来就容易出这种事, 再加上 &lt;code&gt;switch&lt;/code&gt; 各个分支共用同一个 &lt;code&gt;err&lt;/code&gt;, 就更不容易看出来.&lt;/p&gt;
&lt;h2 id=&quot;xiao-jie&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#xiao-jie&quot; aria-label=&quot;Anchor link for: xiao-jie&quot;&gt;小结&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;crontab 的表达能力大概是被低估了. 它是一个已经存在几十年, 所有工程师都认识, 而且到处都有现成解析器的领域特定语言. 需要描述&quot;周期性的时间&quot;的时候, 先想想能不能用它, 通常比自己设计一套字段要好 —— 至少不用为了&quot;每月最后一个周五&quot;这种需求再改一次表.&lt;/p&gt;
&lt;p&gt;它缺的那一块是时长, 补上就能表达时间段.&lt;/p&gt;
&lt;p&gt;另外要留意方向. 大部分 cron 库是为调度写的, 关心的是&quot;下一次&quot;; 如果需求是&quot;这个时刻算不算&quot;, 那要的是另一个方法, 而且必须能传入任意时刻, 不能把 &lt;code&gt;now&lt;/code&gt; 藏在实现里.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>Prometheus 手把手</title>
        <published>2022-05-10T16:36:07+08:00</published>
        <updated>2022-05-10T16:36:07+08:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/prometheus-hand-over-hand/"/>
        <id>https://memwey.github.io/posts/prometheus-hand-over-hand/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/prometheus-hand-over-hand/">&lt;p&gt;因为 Prometheus 在应用中一共只有四种 metric 类型, 只要根据需要监测的目标选择了 metrics 类型, 后续的告警以及可视化配置就有固定的套路了.&lt;/p&gt;
&lt;h3 id=&quot;counter&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#counter&quot; aria-label=&quot;Anchor link for: counter&quot;&gt;Counter&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Counter 用来监控单调递增的量, 当发生重置时 Prometheus 会自动处理归零的数据. 这是一个非常基本的类型, 一个系统中很多数据都可以通过此类型进行计数, 比如系统接受的请求数, 系统发生的错误数, 再比如对第三方接口的调用数, 第三方接口返回的错误数.&lt;/p&gt;
&lt;p&gt;业务代码如下&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;from&lt;/span&gt;&lt;span&gt; prometheus_client&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; import&lt;/span&gt;&lt;span&gt; Counter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;c&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; Counter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;web_request_total&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;web request total counter&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    [&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;endpoint&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;status_code&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;inc&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/submit&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;inc&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时在业务暴露出来的端点上会有这样两条记录&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;web_request_total{method=&amp;quot;/submit&amp;quot;,method=&amp;quot;post&amp;quot;,status_code=&amp;quot;500&amp;quot;} 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;web_request_total{method=&amp;quot;/&amp;quot;,method=&amp;quot;get&amp;quot;,status_code=&amp;quot;200&amp;quot;} 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 Counter 我们一般看增长量和增长速率&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sum(increase(web_request_total[5m]))&lt;/code&gt; 五分钟内总请求数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sum(increase(web_request_total{status_code!~&quot;^2..&quot;}[1m]))&lt;/code&gt; 一分钟内状态码不为2xx的总请求数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sum(irate(web_request_total[1m]))&lt;/code&gt; 一分钟内总请求数增长率&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;告警则一般设置为&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一分钟内总请求中有有超过 5% 的 5xx 错误, 持续一分钟&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;expr: sum(rate(web_request_total{status_code=~&amp;quot;^5..&amp;quot;}[1m])) / sum(rate(web_request_total[1m])) * 100 &amp;gt; 5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;for: 1m&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;一分钟内 5xx 错误的请求增长率高于 100%, 持续一分钟&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;expr: sum(irate(web_request_total{status_code=~&amp;quot;^5..&amp;quot;}[1m])) &amp;gt; 100&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;for: 1m&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;gauge&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#gauge&quot; aria-label=&quot;Anchor link for: gauge&quot;&gt;Gauge&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Gauge 是一个瞬时量, 在时间上来看是可增可减的, 用以记录系统在某个时刻的当前状态, 比如当前实例线程数, 当前系统负载, 某个队列的长度, 某个队列的消费延时等.&lt;/p&gt;
&lt;p&gt;业务代码如下&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;from&lt;/span&gt;&lt;span&gt; prometheus_client&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; import&lt;/span&gt;&lt;span&gt; Gauge&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;g1&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; Gauge&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;kafka_consumer_lag_seconds&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;kafka consume lag in second&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    [&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;topic&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;consumer_group&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;g1&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;k-test&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;grp1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;233&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;g2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; Gauge&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;redis_connection_pool_count&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;redis connection poll count&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    [&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;g2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;idle&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;g2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;active&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;22&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;g2&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;total&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时在业务暴露出来的端点上会有这样的记录&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;kafka_consumer_lag_seconds{topic=&amp;quot;k-test&amp;quot;,consumer_group=&amp;quot;grp1&amp;quot;} 233&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;redis_connection_pool_count{state=&amp;quot;idle&amp;quot;} 10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;redis_connection_pool_count{state=&amp;quot;active&amp;quot;} 22&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;redis_connection_pool_count{state=&amp;quot;total&amp;quot;} 32&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 Gauge 我们一般直接看其值或者其值的百分比&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;kafka_consumer_lag_seconds{topic=&quot;k-test&quot;}&lt;/code&gt; 查看 k-test 这个 topic 最新的消费延迟&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;avg_over_time(kafka_consumer_lag_seconds[1m])&lt;/code&gt; 查看应用全部 kafka 队列一分钟的平均消费延时&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;max_over_time(kafka_consumer_lag_seconds[1m])&lt;/code&gt; 查看应用全部 kafka 队列每一分钟内的最大消费延时&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;redis_connection_pool_count{state=&quot;active&quot;} / ignoring(state) redis_connection_pool_count{state=&quot;total&quot;}&lt;/code&gt; 查看应用 redis 连接池的使用百分比&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;告警则一般设置为&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一分钟内队列的平均消费延时大于 10min, 持续一分钟&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;expr: avg_over_time(kafka_consumer_lag_seconds[1m]) &amp;gt; 600&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;for: 1m&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Redis 连接池平均占用超过 90%, 持续一分钟&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;expr: avg_over_time(redis_connection_pool_count{state=&amp;quot;active&amp;quot;}[1m]) /  ignoring(state) avg_over_time(redis_connection_pool_count{state=&amp;quot;total&amp;quot;}[1m]) * 100 &amp;gt; 90&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;for: 1m&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;histogram-he-summary&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#histogram-he-summary&quot; aria-label=&quot;Anchor link for: histogram-he-summary&quot;&gt;Histogram 和 Summary&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Histogram 可以看作是一个复合的 Counter 类型, 它会将数值按区间计数, 常用于记录 P99, P95 类的数据. 可以用来记录比如接口响应耗时情况, 接口返回数据大小情况等.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;from&lt;/span&gt;&lt;span&gt; prometheus_client&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; import&lt;/span&gt;&lt;span&gt; Histogram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;h&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; Histogram&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;http_request_duration_seconds&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Api requests response time in seconds&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    [&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0.1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0.25&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 4&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;h&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/products&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;observe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0.0923&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;h&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;labels&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/products&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;observe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0.3672&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时在业务暴露出来的端点上会有这样的记录, 产生了多条数据, 其中 &lt;code&gt;_sum&lt;/code&gt; 是值的总和, &lt;code&gt;_count&lt;/code&gt; 是值的计数, &lt;code&gt;_bucket&lt;/code&gt; 中有一个命名为 &lt;code&gt;le&lt;/code&gt; 特殊的 label, 其中的区间值是我们在代码中配置的, 是值在区间内的计数.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_request_duration_seconds_sum{api=&amp;quot;/products&amp;quot;, &amp;quot;method&amp;quot;=&amp;quot;post&amp;quot;} 0.4595&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_request_duration_seconds_count{api=&amp;quot;/products&amp;quot;, &amp;quot;method&amp;quot;=&amp;quot;post&amp;quot;} 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_request_duration_seconds_bucket{api=&amp;quot;/products&amp;quot;, &amp;quot;method&amp;quot;=&amp;quot;post&amp;quot;, le=&amp;quot;0.1&amp;quot;} 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_request_duration_seconds_bucket{api=&amp;quot;/products&amp;quot;, &amp;quot;method&amp;quot;=&amp;quot;post&amp;quot;, le=&amp;quot;0.25&amp;quot;} 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_request_duration_seconds_bucket{api=&amp;quot;/products&amp;quot;, &amp;quot;method&amp;quot;=&amp;quot;post&amp;quot;, le=&amp;quot;1&amp;quot;} 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_request_duration_seconds_bucket{api=&amp;quot;/products&amp;quot;, &amp;quot;method&amp;quot;=&amp;quot;post&amp;quot;, le=&amp;quot;4&amp;quot;} 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_request_duration_seconds_bucket{api=&amp;quot;/products&amp;quot;, &amp;quot;method&amp;quot;=&amp;quot;post&amp;quot;, le=&amp;quot;10&amp;quot;} 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_request_duration_seconds_bucket{api=&amp;quot;/products&amp;quot;, &amp;quot;method&amp;quot;=&amp;quot;post&amp;quot;, le=&amp;quot;+Inf&amp;quot;} 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 Histogram 一般可以求均值或者 P95, P99 之类的值, 也可以做普通的 Counter 使用&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rate(http_request_duration_seconds_sum{api=&quot;/products&quot;, &quot;method&quot;=&quot;post&quot;}[5m]) / rate(http_request_duration_seconds_count{api=&quot;/products&quot;, &quot;method&quot;=&quot;post&quot;}[5m])&lt;/code&gt; 查看 /products 端点上 POST 请求五分钟内的耗时均值&lt;/li&gt;
&lt;li&gt;&lt;code&gt;histogram_quantile(0.99, rate(http_request_duration_seconds_bucket{api=&quot;/products&quot;, &quot;method&quot;=&quot;post&quot;}[5m]))&lt;/code&gt; 查看 /products 端点上 POST 请求五分钟内的耗时 P99&lt;/li&gt;
&lt;li&gt;&lt;code&gt;increase(http_request_duration_seconds_count{api=&quot;/products&quot;, &quot;method&quot;=&quot;post&quot;}[1m])&lt;/code&gt; 查看 /products 端点上 POST 请求一分钟内的请求数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;告警则一般设置为&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;两分钟内某个接口 P95 大于 10s, 持续两分钟&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;expr: histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[2m])) by (api, method)) &amp;gt; 10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;for: 2m&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一些有用的资料&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://awesome-prometheus-alerts.grep.to/rules&quot;&gt;awesome-prometheus-alerts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://grafana.com/blog/2020/06/23/how-to-visualize-prometheus-histograms-in-grafana/&quot;&gt;How to visualize Prometheus histograms in Grafana&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>Prometheus 从入门到放弃</title>
        <published>2022-04-17T16:30:08+08:00</published>
        <updated>2022-04-17T16:30:08+08:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/prometheus-from-scratch/"/>
        <id>https://memwey.github.io/posts/prometheus-from-scratch/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/prometheus-from-scratch/">&lt;h2 id=&quot;ji-ben-gai-nian&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ji-ben-gai-nian&quot; aria-label=&quot;Anchor link for: ji-ben-gai-nian&quot;&gt;基本概念&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Prometheus 是一个开源的监控方案, &lt;a rel=&quot;external&quot; href=&quot;https://cncf.io/&quot;&gt;CNCF&lt;/a&gt; 的毕业项目, 云原生监控的事实标准. Prometheus 以时间序列数据的形式收集和存储 &lt;code&gt;metrics&lt;/code&gt;. &lt;code&gt;Metrics&lt;/code&gt; 信息与被称为标签的可选的键值对和记录时间一起保存.&lt;/p&gt;
&lt;p&gt;Prometheus 的主要功能特点有&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;由 &lt;code&gt;metric&lt;/code&gt; 的名称以及 Key/Value 对标签标识的&lt;code&gt;时间序列&lt;/code&gt;数据组成的多维&lt;a rel=&quot;external&quot; href=&quot;https://prometheus.io/docs/concepts/data_model/&quot;&gt;数据模型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;灵活的查询语言 &lt;a rel=&quot;external&quot; href=&quot;https://prometheus.io/docs/prometheus/latest/querying/basics/&quot;&gt;PromQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;不依赖分布式存储; 单服务节点自治能力&lt;/li&gt;
&lt;li&gt;服务端通过 HTTP 协议拉取获得的时间序列数据&lt;/li&gt;
&lt;li&gt;支持通过中间网关&lt;a rel=&quot;external&quot; href=&quot;https://prometheus.io/docs/instrumenting/pushing/&quot;&gt;推送时间序列数据&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;通过服务发现或静态配置文件发现监控目标&lt;/li&gt;
&lt;li&gt;支持多种类型的图表和仪表盘&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Prometheus 以拉取的模式获取获取监控数据, 而不是由业务方向监控服务器推送. 这对于被监控的业务来说, 极大的减少了监控部分在系统中的耦合. 通常, 业务暴露出一个 HTTP 端口以暴露数据, 供 Prometheus server 抓取即可.&lt;/p&gt;
&lt;h3 id=&quot;metric&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#metric&quot; aria-label=&quot;Anchor link for: metric&quot;&gt;Metric&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;一般来说, &lt;code&gt;Metric&lt;/code&gt; 是数值形式的标量单位, 它是与时间一起被记录的时间序列. 在不同应用中用户期望记录不同的 &lt;code&gt;Metric&lt;/code&gt;. Web server 可能要希望请求数和一些指标信息, 数据库可能希望记录活跃连接数和活跃查询数之类的.&lt;/p&gt;
&lt;h3 id=&quot;xi-tong-jia-gou&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#xi-tong-jia-gou&quot; aria-label=&quot;Anchor link for: xi-tong-jia-gou&quot;&gt;系统架构&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;主要的 &lt;a rel=&quot;external&quot; href=&quot;https://github.com/prometheus/prometheus&quot;&gt;Prometheus server&lt;/a&gt; 收集和存储时间序列数据&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://prometheus.io/docs/instrumenting/clientlibs/&quot;&gt;客户端库&lt;/a&gt; 用以监测应用程序代码&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/prometheus/pushgateway&quot;&gt;push gateway&lt;/a&gt; 用以支持短期运行的任务&lt;/li&gt;
&lt;li&gt;用以监控 HAProxy, StatsD, Graphite 等常用服务的 &lt;a rel=&quot;external&quot; href=&quot;https://prometheus.io/docs/instrumenting/exporters/&quot;&gt;exporters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;用以处理告警的 &lt;a rel=&quot;external&quot; href=&quot;https://github.com/prometheus/alertmanager&quot;&gt;alertmanager&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;其他各种支持工具&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/prometheus-from-scratch/images/architecture.png&quot; alt=&quot;系统架构图&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;gua-yong-chang-jing&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#gua-yong-chang-jing&quot; aria-label=&quot;Anchor link for: gua-yong-chang-jing&quot;&gt;适用场景&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Prometheus 适合记录任何纯数值的时间序列。它既适合以机器为中心的监控, 也适合监控高度动态的面向服务的体系结构. 它对多维数据收集和查询的支持在微服务的场景中拥有特别的优势.&lt;/p&gt;
&lt;p&gt;Prometheus 基于可靠性设计, 以保证在出现问题时可以快速进行问题诊断. 每个 Prometheus server 都是独立的, 不依赖于网络存储或其他远程服务. 当基础设施的其他部分出现故障时, 这种架构非常可靠, 另一方面, 它不需要配置大量的基础设施.&lt;/p&gt;
&lt;p&gt;Prometheus 不适合需要 100% 的准确性的场景, 例如记录单个的计费请求, 收集的数据可能不够详细和完整. 在这种情况下应使用其他系统来收集和分析计费数据, 使用 Prometheus 进行其余的监控.&lt;/p&gt;
&lt;p&gt;这里说的比较笼统, 从构建系统可观测性的监控来说, 我们有 &lt;code&gt;Metrics&lt;/code&gt; , &lt;code&gt;Tracing&lt;/code&gt; 和 &lt;code&gt;Logging&lt;/code&gt;. 它们有各自的特点, &lt;code&gt;Metrics&lt;/code&gt; 的特点是可聚合的, 它们是组成一个量计, 计数器或者直方图的最小单位. &lt;code&gt;Logging&lt;/code&gt; 处理一系列离散事件. 而 &lt;code&gt;Tracing&lt;/code&gt; 处理整个请求范围内的信息, 数据或元数据应当绑定到系统中单个事务对象的生命周期中.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/prometheus-from-scratch/images/Obervation-Venn.png&quot; alt=&quot;可观测性韦恩图&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Prometheus 是个典型的 &lt;code&gt;Metrics&lt;/code&gt; 系统, 它为系统打点, 追踪系统整体的运行情况. 比如, 记录 Web 系统中的所有请求的 HTTP 状态码, 当一段时间中 500 错误大量出现时, 可以通过配置的聚合和趋势分析发现问题并提供告警, 但它没有记录具体的请求内容, 想要复现错误的请求, 则需要 &lt;code&gt;Tracing&lt;/code&gt; 和 &lt;code&gt;Logging&lt;/code&gt; 系统进行进一步的诊断.&lt;/p&gt;
&lt;h2 id=&quot;kuai-su-ru-men&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#kuai-su-ru-men&quot; aria-label=&quot;Anchor link for: kuai-su-ru-men&quot;&gt;快速入门&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;shu-ju-mo-xing&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#shu-ju-mo-xing&quot; aria-label=&quot;Anchor link for: shu-ju-mo-xing&quot;&gt;数据模型&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;metric-and-label&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#metric-and-label&quot; aria-label=&quot;Anchor link for: metric-and-label&quot;&gt;Metric and Label&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Prometheus 在底层将所有属于同一指标名称, 同一标签集合的, 有时间戳标记的数据流存储为时间序列数据.&lt;/p&gt;
&lt;p&gt;数据模型的表示方式为&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;metric name&amp;gt;{&amp;lt;label name&amp;gt;=&amp;lt;label value&amp;gt;, ...}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;且有以下规则&lt;/p&gt;
&lt;h5 id=&quot;metric-name&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#metric-name&quot; aria-label=&quot;Anchor link for: metric-name&quot;&gt;metric name&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;匹配正则 &lt;code&gt;[a-zA-Z_:][a-zA-Z0-9_:]*&lt;/code&gt;, 其中 &lt;code&gt;:&lt;/code&gt; 是用户自定义记录规则的标记, 不应该用在业务暴露的指标中&lt;/p&gt;
&lt;h5 id=&quot;label-name&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#label-name&quot; aria-label=&quot;Anchor link for: label-name&quot;&gt;label name&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;匹配正则 &lt;code&gt;[a-zA-Z_][a-zA-Z0-9_]*&lt;/code&gt;, 其中以 &lt;code&gt;__&lt;/code&gt; 开头的是系统保留命名&lt;/p&gt;
&lt;h4 id=&quot;sample&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#sample&quot; aria-label=&quot;Anchor link for: sample&quot;&gt;Sample&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Sample 的 value 总是为一个 float64 值&lt;/p&gt;
&lt;p&gt;以关系型数据库来类比, metric_name 相当于表名, label_name 相当于列名, label_value 相当某一字段的值, 而 metric_value 和 timestamp 的列是系统自动生成的.&lt;/p&gt;
&lt;p&gt;比如如下名为 &lt;code&gt;api_http_requests_total&lt;/code&gt; 指标表示了 API 所有的 HTTP 请求数, 其中标签 &lt;code&gt;method&lt;/code&gt; 指定了方法, &lt;code&gt;handler&lt;/code&gt; 指定了路径, 分别为 &lt;code&gt;method=&quot;POST&quot;&lt;/code&gt; 和 &lt;code&gt;handler=&quot;/messages&quot;&lt;/code&gt;, 此样本的值为 11&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;api_http_requests_total{method=&amp;quot;POST&amp;quot;, handler=&amp;quot;/messages&amp;quot;} 11&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;CREATE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; TABLE&lt;/span&gt;&lt;span&gt; `&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;api_http_requests_total&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; timestamp&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; varchar&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;255&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; varchar&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;255&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt; double&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SELECT&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; FROM&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;api_http_requests_total&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;----------+--------+-----------+-------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;timestamp&lt;/span&gt;&lt;span&gt;  | method |  handler  | &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;value&lt;/span&gt;&lt;span&gt; |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;----------+--------+-----------+-------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1649235184&lt;/span&gt;&lt;span&gt; |  POST  | &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;/&lt;/span&gt;&lt;span&gt;messages |   &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;11&lt;/span&gt;&lt;span&gt;  |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;----------+--------+-----------+-------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;metric-1&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#metric-1&quot; aria-label=&quot;Anchor link for: metric-1&quot;&gt;Metric&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Prometheus 提供了以下一些类型 &lt;code&gt;metrics&lt;/code&gt; 类型&lt;/p&gt;
&lt;h4 id=&quot;counter&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#counter&quot; aria-label=&quot;Anchor link for: counter&quot;&gt;Counter&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Counter 类型代表一种样本数据单调递增的指标, 即除非监控系统发生了重置, 只增不减.&lt;/p&gt;
&lt;p&gt;例子: 系统接受的请求数, 系统错误数, 系统 uptime&lt;/p&gt;
&lt;h4 id=&quot;gauge&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#gauge&quot; aria-label=&quot;Anchor link for: gauge&quot;&gt;Gauge&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Gauge 类型代表一种样本数据可以任意变化的指标, 即可增可减.&lt;/p&gt;
&lt;p&gt;例子: 当前实例线程数, 当前系统负载&lt;/p&gt;
&lt;h4 id=&quot;histogram-he-summary&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#histogram-he-summary&quot; aria-label=&quot;Anchor link for: histogram-he-summary&quot;&gt;Histogram 和 Summary&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Histogram 和 Summary 用以对数据进行采样并储存其分布情况. 不同的是, Histogram 指定其配置的区间, 而 Summary 配置其分位数.&lt;/p&gt;
&lt;p&gt;举一个具体的例子, 假设需要统计某个 WEB 页面的响应时间, 使用 Histogram 时, 需要配置其响应时间区间, &amp;lt;10ms, 10ms-100ms, 100ms-1000ms, &amp;gt;1000ms, 返回的数据如下&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_response_latency_range_bucket{le=&amp;quot;0.01&amp;quot;} 123&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_response_latency_range_bucket{le=&amp;quot;0.1&amp;quot;} 214&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_response_latency_range_bucket{le=&amp;quot;1&amp;quot;} 215&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_response_latency_range_bucket{le=&amp;quot;+Inf&amp;quot;} 216&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_response_latency_range_sum 2.888716127000002&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_response_latency_range_count 216&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 Summary 时, 则配置 0.5, 0.9, 0.99, 代表了其50分位, 即中位数, 90分位, 99分位的值&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_response_latency{quantile=&amp;quot;0.5&amp;quot;} 0.012352463&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_response_latency{quantile=&amp;quot;0.9&amp;quot;} 0.014458005&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_response_latency{quantile=&amp;quot;0.99&amp;quot;} 0.017316173&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_response_latency_sum 2.888716127000002&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_response_latency_count 216&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时可以看到, 这个 HTTP 接口一共接受了 216 次请求, 一共耗时使用了 2.88s, Histogram 展示了具体数值分布, 123 个在 10ms 内, 有 216 - 215 = 1 个请求耗时超过了 1000ms. Summary 展示了分布的百分位, 中位数是 0.012s, 99分位是 0.017s.&lt;/p&gt;
&lt;p&gt;通常我们使用 Histogram 即可, Summary 会使用较多的内存构造一个滑动窗口来做统计.&lt;/p&gt;
&lt;h2 id=&quot;zhu-yi-shi-xiang&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhu-yi-shi-xiang&quot; aria-label=&quot;Anchor link for: zhu-yi-shi-xiang&quot;&gt;注意事项&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;每个键值标签对的唯一组合都代表一个新的时间序列, 这会显著增加暴露的指标数和存储的数据量. 不要使用标签存储具有高基数, 即拥有许多不同的标签值的维度, 例如用户id, restful 的实际路径参数等.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;// Good&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_request_total{path=&amp;quot;users/{:user_id}/info&amp;quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;// Bad&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_request_total{path=&amp;quot;users/1/info&amp;quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_request_total{path=&amp;quot;users/2/info&amp;quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_request_total{path=&amp;quot;users/3/info&amp;quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http_request_total{path=&amp;quot;users/4/info&amp;quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;// Good&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;crawler_failure_total{target=&amp;quot;tiktok&amp;quot;,scope=&amp;quot;user&amp;quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;// Bad&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;crawler_failure_total{target=&amp;quot;tiktok&amp;quot;,user=&amp;quot;damonlvu&amp;quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;crawler_failure_total{target=&amp;quot;tiktok&amp;quot;,user=&amp;quot;77777777777ge&amp;quot;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 Prometheus 中, Metric 和 Label 的命名应该总是使用 &lt;code&gt;snake_case&lt;/code&gt; 形式, 以便后续的处理归类分割等操作.&lt;/p&gt;
&lt;p&gt;一个 Metric 的前缀总应该使用应用名, 可以理解为一个命名空间, 例如&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;prometheus&lt;/strong&gt;_notifications_total (Prometheus 的内部指标)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;doris&lt;/strong&gt;_consumption_latency_seconds (doris 的消费延时)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;crawler&lt;/strong&gt;_failure_total (爬虫的总失败数)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;crawler&lt;/strong&gt;_request_total (爬虫的总请求数)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个 Metric 总应该以其单位的复数形式为后缀, 并且单位应该尽量统一, 如果是一个累积的计数, 总应该使用 &lt;code&gt;total&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;node_memory_usage_&lt;strong&gt;bytes&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;http_requests_&lt;strong&gt;total&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;process_cpu_&lt;strong&gt;seconds_total&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;zhua-qu-she-zhi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhua-qu-she-zhi&quot; aria-label=&quot;Anchor link for: zhua-qu-she-zhi&quot;&gt;抓取设置&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;通过 &lt;code&gt;Prometheus Operator&lt;/code&gt; 的 &lt;code&gt;ServiceMonitor&lt;/code&gt; 对象, 可以相对便捷的, 通过不直接修改 prometheus config 的方式添加抓取目标.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;piVersion&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; m&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;onitoring.coreos.com/v1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ind&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; S&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;erviceMonitor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;etadata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  n&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ame&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;xample-service-monitor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  l&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;abels&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    t&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;eam&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; o&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ps&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;pec&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;elector&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    m&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;atchLabels&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;      s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;vc-label-key&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;vc-label-value&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  e&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ndpoints&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt; p&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ort&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 80&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt; p&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ath&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;metrics&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的实例指定了一个名为 example-service-monitor 的 &lt;code&gt;ServiceMonitor&lt;/code&gt;, 它会选择所有 svc-label-key 的值为 svc-label-value 的 Service 进行监控, 目标端口为 80, HTTP 路径为 /metrics. Service 背后可能存在超过一个的无状态的 Pod, 他们会被自动发现.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Prometheus Operator&lt;/code&gt; 会解析 &lt;code&gt;ServiceMonitor&lt;/code&gt; 对象, 生成相应的抓取设置到 prometheus config.&lt;/p&gt;
&lt;h2 id=&quot;zhi-biao-xuan-ze&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhi-biao-xuan-ze&quot; aria-label=&quot;Anchor link for: zhi-biao-xuan-ze&quot;&gt;指标选择&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;一般 exporter 会采集很多的指标, 比如 jvm 的运行情况, http 请求情况等等. Google 在 &lt;code&gt;SRE Handbook&lt;/code&gt; 中提出了四个黄金信号: 延迟, 流量, 错误数, 饱和度. 实际操作中可以使用 &lt;code&gt;USE&lt;/code&gt; 或 &lt;code&gt;RED&lt;/code&gt; 方法作为指导, &lt;code&gt;USE(Utilization, Saturation, Errors)&lt;/code&gt; 用于资源, &lt;code&gt;RED(Rate, Errors, Duration)&lt;/code&gt; 用于服务.&lt;/p&gt;
&lt;p&gt;一般的在线服务, 比如 Web 服务, 数据库等, 一般关心请求速率, 延迟和错误率, 使用 RED 方法; 一般的离线服务, 如日志处理, 消息队列等, 一般关注队列数量, 进行中的数量, 处理速度以及发生的错误, 使用 USE 方法.&lt;/p&gt;
&lt;h3 id=&quot;rate-su-lu&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#rate-su-lu&quot; aria-label=&quot;Anchor link for: rate-su-lu&quot;&gt;Rate 速率&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sum(rate(http_server_requests_seconds_count[5m])) by (method, uri, job)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;统计访问的 QPS&lt;/p&gt;
&lt;h3 id=&quot;errors-cuo-wu&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#errors-cuo-wu&quot; aria-label=&quot;Anchor link for: errors-cuo-wu&quot;&gt;Errors 错误&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;rate(http_server_requests_seconds_count{status=~&amp;quot;^5..$&amp;quot;}[5m]) / rate(http_server_requests_seconds_count[5m])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;统计 HTTP 状态码为 5xx 的比例&lt;/p&gt;
&lt;h3 id=&quot;duration-qing-qiu-shi-jian&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#duration-qing-qiu-shi-jian&quot; aria-label=&quot;Anchor link for: duration-qing-qiu-shi-jian&quot;&gt;Duration 请求时间&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sum by (job, uri, method) (rate(http_server_requests_seconds_sum[5m]) / rate(http_server_requests_seconds_count[5m]))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;统计平均请求时间&lt;/p&gt;
&lt;h2 id=&quot;can-kao-zi-liao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#can-kao-zi-liao&quot; aria-label=&quot;Anchor link for: can-kao-zi-liao&quot;&gt;参考资料&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://prometheus.io/docs/introduction/overview/&quot;&gt;What is Prometheus?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://peter.bourgon.org/blog/2017/02/21/metrics-tracing-and-logging.html&quot;&gt;Metrics, tracing, and logging&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://yasongxu.gitbook.io/container-monitor/&quot;&gt;container-monitor-book&lt;/a&gt;&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>一个离职小记</title>
        <published>2021-06-06T23:29:39+08:00</published>
        <updated>2021-06-06T23:29:39+08:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/job-hopping/"/>
        <id>https://memwey.github.io/posts/job-hopping/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/job-hopping/">&lt;p&gt;从一家创业公司离职, 去另外一家创业公司.&lt;/p&gt;
&lt;p&gt;在家待了一个月多一点, 期间解决了一下家里的大事小情, 复习刷题和面试, 开始巴西柔术的学习, 控制饮食和运动, 刷 &lt;code&gt;Bilibili&lt;/code&gt; 什么的. 然而最重要的婚礼因为突如其来的疫情只能被迫延后了.&lt;/p&gt;
&lt;p&gt;不得不说, 广州的机会真的不多. 一开始找了几家中等级别的公司面试练手, 结果发现没有达到那种面的我怀疑人生然后努力复习的结果, 随手就面过, 回家接着摸鱼. 然后抱有比较大希望的腾讯和字节都因为各种各样奇怪的原因连面试都没有, 不得不说和某些公司就是没有缘分.&lt;/p&gt;
&lt;h2 id=&quot;chi-kai-de-yuan-yin&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#chi-kai-de-yuan-yin&quot; aria-label=&quot;Anchor link for: chi-kai-de-yuan-yin&quot;&gt;离开的原因&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;其实我觉得还是离开上一家公司的太迟了. 年初拿到 &lt;a rel=&quot;external&quot; href=&quot;https://www.edison.tech/&quot;&gt;Edison&lt;/a&gt; 的 Offer 的时候就应该离开了.&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;/p&gt;
&lt;h2 id=&quot;xuan-ze-de-yuan-yin&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#xuan-ze-de-yuan-yin&quot; aria-label=&quot;Anchor link for: xuan-ze-de-yuan-yin&quot;&gt;选择的原因&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;又来到一家创业公司其实自己都蛮没想到的, 毕竟最初的构想是去大公司做一些基础架构的开发, 远离枯燥重复的业务开发.&lt;/p&gt;
&lt;p&gt;这家公司最吸引的可能就是微信顶级产品经理带队吧. 这几年的工作也算是接触过不少产品经理, 有厉害的也有水平一般的, 感觉产品经理是一个有手就能做的职业, 但是水平高的产品经理确实无法替代. 想近距离接触一下传说中的微信之母, 直接向张小龙汇报的产品经理, 看看她们的思维和做事是什么样的.&lt;/p&gt;
&lt;p&gt;另外, 这家公司光环比较强, 资金比较充足, 薪酬福利什么的都挺不错的, 完美的解决了创业公司的一些问题.&lt;/p&gt;
&lt;p&gt;不过个人还是觉得在目前公司主要产品上, 有一些隐患, 在熟人IM和陌生人社交上有点定位不清. 而且在熟人IM上, 很难看到短期内的盈利空间. 但是毕竟不是专业的产品经理, 所有的看法都是基于已有的产品, 比如微信, 陌陌, Soul 什么的. 厉害的产品经理可以发现别人发现不到的需求, 万一呢.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>Redis ZINTERSTORE</title>
        <published>2021-04-21T20:30:43+08:00</published>
        <updated>2021-04-21T20:30:43+08:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/redis-zinterstore/"/>
        <id>https://memwey.github.io/posts/redis-zinterstore/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/redis-zinterstore/">&lt;h2 id=&quot;jie-gou-tui-ce&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#jie-gou-tui-ce&quot; aria-label=&quot;Anchor link for: jie-gou-tui-ce&quot;&gt;结构推测&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;今天与人争执的时候觉得应该探究一下 &lt;code&gt;Redis&lt;/code&gt; 中 &lt;code&gt;ZINTERSTORE&lt;/code&gt; 的实现. 先看文档中的描述&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Available since 2.0.0.&lt;/p&gt;
&lt;p&gt;Time complexity: O(N&lt;em&gt;K)+O(M&lt;/em&gt;log(M)) worst case with N being the smallest input sorted set, K being the number of input sorted sets and M being the number of elements in the resulting sorted set.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;大致意思就是最坏情况下时间复杂度是 &lt;code&gt;O(N*K)+O(M*log(M))&lt;/code&gt;, 其中 &lt;code&gt;N&lt;/code&gt; 是输入中元素最少的 &lt;code&gt;sorted set&lt;/code&gt; 的元素数目, &lt;code&gt;K&lt;/code&gt; 是输入的 &lt;code&gt;sorted set&lt;/code&gt; 的数量, &lt;code&gt;M&lt;/code&gt; 是结果中的 &lt;code&gt;sorted set&lt;/code&gt; 中元素的数目.&lt;/p&gt;
&lt;p&gt;其实光从时间复杂度就能推测出来大致的操作了. &lt;code&gt;O(M*log(M))&lt;/code&gt; 这个复杂度很有可能是一次排序操作, 而且和结果中的元素数目相关, 那么很有可能是先取交集之后得出 &lt;code&gt;M&lt;/code&gt; 个元素, 再在 &lt;code&gt;M&lt;/code&gt; 个元素中进行排序的. 当然, 也有可能是相应的, 在有序的序列中进行插入的操作.&lt;/p&gt;
&lt;p&gt;而 &lt;code&gt;N&lt;/code&gt; 是最小的 &lt;code&gt;sorted set&lt;/code&gt; 的元素个数. 这个一开始我有点想不明白, 难道不应该是最大的才对吗, 因为这里我还是想着去遍历其他的 &lt;code&gt;sorted set&lt;/code&gt; 进行比较. 但是思考一下, &lt;code&gt;sorted set&lt;/code&gt; 也是 &lt;code&gt;set&lt;/code&gt; 嘛, 完全可以使用 &lt;code&gt;O(1)&lt;/code&gt; 的效率在其中进行查找. 这样实际就变成了分别在 (K - 1) 个 &lt;code&gt;sorted set&lt;/code&gt; 中寻找 &lt;code&gt;N&lt;/code&gt; 个元素, 这样自然就是 &lt;code&gt;O(N*K)&lt;/code&gt; 了.&lt;/p&gt;
&lt;h2 id=&quot;yuan-ma-fen-xi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#yuan-ma-fen-xi&quot; aria-label=&quot;Anchor link for: yuan-ma-fen-xi&quot;&gt;源码分析&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;以下源码基于 &lt;code&gt;Redis 3.0&lt;/code&gt; 分析, 实际的函数操作为&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;c&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; https://github.com/redis/redis/blob/3.0/src/t_zset.c#L1905 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; zunionInterGenericCommand&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;redisClient &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;c&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; robj &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;dstkey&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; int&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; op&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我们可以看到, 在 &lt;code&gt;Redis&lt;/code&gt; 的源码中将 &lt;code&gt;ZINTERSTORE&lt;/code&gt; 和 &lt;code&gt;ZUNIONSTORE&lt;/code&gt; 放在了一起处理, 使用 &lt;code&gt;op&lt;/code&gt; 做区分.&lt;/p&gt;
&lt;p&gt;随后是一段漫长的代码, 主要是处理输入参数的, 比如要操作的 &lt;code&gt;sorted set&lt;/code&gt; 的列表, 存到了 &lt;code&gt;src&lt;/code&gt; 中; 然后处理 &lt;code&gt;WEIGHTS&lt;/code&gt; 和 &lt;code&gt;AGGREGATE&lt;/code&gt;. 代码比较长而且逻辑也很简单.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;c&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; https://github.com/redis/redis/blob/3.0/src/t_zset.c#L1993 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; sort sets from the smallest to largest, this will improve our&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;     * algorithm&amp;#39;s performance &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;    qsort&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;src&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;setnum&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;sizeof&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;zsetopsrc&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;zuiCompareByCardinality&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后这里把所有 &lt;code&gt;sorted set&lt;/code&gt; 按元素的个数从小到大排列, 以提高效率. 随后是具体的取交集的代码.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;c&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; https://github.com/redis/redis/blob/3.0/src/t_zset.c#L2001 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;op &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;==&lt;/span&gt;&lt;span&gt; REDIS_OP_INTER&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;        /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Skip everything if the smallest input is empty. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;zuiLength&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;src&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;            /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Precondition: as src[0] is non-empty and the inputs are ordered&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;             * by size, all src[i &amp;gt; 0] are non-empty too. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;            zuiInitIterator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;src&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;            while&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;zuiNext&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;src&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;zval&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                double&lt;/span&gt;&lt;span&gt; score&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; value&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                score &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; src&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;weight&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span&gt; zval&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;score&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;isnan&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;score&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; score &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;j &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; j &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; setnum&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; j&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;++&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                    /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; It is not safe to access the zset we are&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                     * iterating, so explicitly check for equal object. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;src&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;j&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;subject&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; src&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;subject&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                        value &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt; zval&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;score&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;src&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;j&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;weight&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;                        zunionInterAggregate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;score&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;aggregate&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;zuiFind&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;src&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;j&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;zval&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                        value &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; src&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;j&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;weight&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;                        zunionInterAggregate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;score&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;aggregate&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                        break&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Only continue when present in every input. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;j &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;==&lt;/span&gt;&lt;span&gt; setnum&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    tmp &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; zuiObjectFromValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;zval&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    znode &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; zslInsert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dstzset&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;zsl&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;score&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;tmp&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;                    incrRefCount&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tmp&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; added to skiplist &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;                    dictAdd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dstzset&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;tmp&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;znode&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;score&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;                    incrRefCount&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tmp&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; added to dictionary &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sdsEncodedObject&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tmp&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sdslen&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tmp&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;ptr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span&gt; maxelelen&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                            maxelelen &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; sdslen&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tmp&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;ptr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;            zuiClearIterator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;src&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然, 如果按元素数从小到大排序的第一个 &lt;code&gt;sorted set&lt;/code&gt; 元素数为 0, 那就可以直接返回了, 随后在此 &lt;code&gt;sorted set&lt;/code&gt; 上建立一个 &lt;code&gt;Iterator&lt;/code&gt;, 主要就是帮助在 &lt;code&gt;sorted set&lt;/code&gt; 上做遍历的, 因为在 &lt;code&gt;sorted set&lt;/code&gt; 中, 遍历不是一个常用操作, 排序才是.&lt;/p&gt;
&lt;p&gt;然后就是通过 &lt;code&gt;while (zuiNext(&amp;amp;src[0],&amp;amp;zval))&lt;/code&gt; 取出第一个 &lt;code&gt;sorted set&lt;/code&gt; 中的每一个元素, 再用 &lt;code&gt;for (j = 1; j &amp;lt; setnum; j++)&lt;/code&gt; 将其在每一个其他的 &lt;code&gt;sorted set&lt;/code&gt; 中查找一遍.&lt;/p&gt;
&lt;p&gt;当两个 &lt;code&gt;sorted set&lt;/code&gt; 指向同一个对象是, 那么毫无疑问一定会有同样的元素. 否则就在 &lt;code&gt;src[j]&lt;/code&gt; 中查找当前元素. &lt;code&gt;zuiFind(&amp;amp;src[j],&amp;amp;zval,&amp;amp;value)&lt;/code&gt; 实现了这个操作. 具体的代码在后面分析. 如果找不到的话, 则没有必要继续找下去了, 跳出即可.&lt;/p&gt;
&lt;p&gt;查找的过程中也使用了 &lt;code&gt;zunionInterAggregate(&amp;amp;score,value,aggregate)&lt;/code&gt; 来更新当前元素的 &lt;code&gt;score&lt;/code&gt;, 具体的 &lt;code&gt;score&lt;/code&gt; 更新规则是根据 &lt;code&gt;AGGREGATE&lt;/code&gt; 参数来定的.&lt;/p&gt;
&lt;p&gt;当元素在所有的 &lt;code&gt;sorted set&lt;/code&gt; 中时, 就可以把这个元素添加进结果的 &lt;code&gt;sorted set&lt;/code&gt; 中了. 这里就是根据 &lt;code&gt;zval&lt;/code&gt; 和 &lt;code&gt;score&lt;/code&gt; 往 &lt;code&gt;dstzset&lt;/code&gt; 里面插入元素. 因为 &lt;code&gt;sorted set&lt;/code&gt; 里面既有 &lt;code&gt;dict&lt;/code&gt; 也有 &lt;code&gt;skiplist&lt;/code&gt;, 所以两个都要添加.&lt;/p&gt;
&lt;p&gt;这里还有一个操作就是更新最大元素的长度, 这个和 &lt;code&gt;sorted set&lt;/code&gt; 的内部优化有关.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;c&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; https://github.com/redis/redis/blob/3.0/src/t_zset.c#L2119 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;dbDelete&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;db&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;dstkey&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        signalModifiedKey&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;dstkey&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        touched &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        server&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dirty&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;++&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;dstzset&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;zsl&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;        /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Convert to ziplist when in limits. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;dstzset&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;zsl&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;=&lt;/span&gt;&lt;span&gt; server&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;zset_max_ziplist_entries&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            maxelelen &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; server&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;zset_max_ziplist_value&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;                zsetConvert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dstobj&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;REDIS_ENCODING_ZIPLIST&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        dbAdd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;dstkey&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;dstobj&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        addReplyLongLong&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;zsetLength&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dstobj&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;!&lt;/span&gt;&lt;span&gt;touched&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; signalModifiedKey&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;dstkey&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        notifyKeyspaceEvent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;REDIS_NOTIFY_ZSET&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            (&lt;/span&gt;&lt;span&gt;op &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;==&lt;/span&gt;&lt;span&gt; REDIS_OP_UNION&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ?&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;zunionstore&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;zinterstore&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            dstkey&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        server&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dirty&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;++&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        decrRefCount&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dstobj&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        addReply&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;shared&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;czero&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;touched&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;            notifyKeyspaceEvent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;REDIS_NOTIFY_GENERIC&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;del&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;dstkey&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;    zfree&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;src&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;随后就是收尾工作. 如果目标的坑上已经有值了, 就毫不犹豫的干掉它. 然后再看作为结果的 &lt;code&gt;sorted set&lt;/code&gt;. 如果它满足转化为 &lt;code&gt;ziplist&lt;/code&gt; 的条件, 就可以把它转化为 &lt;code&gt;ziplist&lt;/code&gt;. 后面是一些 &lt;code&gt;hook&lt;/code&gt; 的通知, 可以暂时忽略. 如果结果为空, &lt;code&gt;Integer reply&lt;/code&gt; 会返回 0, 否则会返回结果中元素的个数.&lt;/p&gt;
&lt;p&gt;至此大致流程已经结束了. 如同推测的那样, 算法复杂度完美的反映了操作的内部过程和数据结构. 不过, 我们还有几个问题没有解决. &lt;code&gt;zuiFind&lt;/code&gt; 中的具体操作是什么, &lt;code&gt;ziplist&lt;/code&gt; 又是什么.&lt;/p&gt;
&lt;h2 id=&quot;ge-zhe-ge-zhe&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ge-zhe-ge-zhe&quot; aria-label=&quot;Anchor link for: ge-zhe-ge-zhe&quot;&gt;搁这搁这&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这个标题充分提现了递归的思想. 学习新东西, 然后发现另一些新东西, 然后再学习这些新东西, 然后......&lt;/p&gt;
&lt;p&gt;首先来看 &lt;code&gt;zuiFind&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;c&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; https://github.com/redis/redis/blob/3.0/src/t_zset.c#L1826 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; zuiFind&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;zsetopsrc &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;op&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; zsetopval &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;val&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; double&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;score&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;op&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;subject&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; NULL&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;op&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span&gt; REDIS_SET&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;op&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;encoding&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span&gt; REDIS_ENCODING_INTSET&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;            if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;zuiLongLongFromValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;                intsetFind&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;op&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;subject&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;ptr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;ell&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                *&lt;/span&gt;&lt;span&gt;score &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;op&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;encoding&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span&gt; REDIS_ENCODING_HT&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            dict &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;ht &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt; op&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;subject&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;ptr&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;            zuiObjectFromValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;            if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;dictFind&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ht&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;ele&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; NULL&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                *&lt;/span&gt;&lt;span&gt;score &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;            redisPanic&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Unknown set encoding&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;op&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span&gt; REDIS_ZSET&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        zuiObjectFromValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;op&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;encoding&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span&gt; REDIS_ENCODING_ZIPLIST&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;            if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;zzlFind&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;op&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;subject&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;ptr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;ele&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;score&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; NULL&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Score is already set by zzlFind. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;op&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;encoding&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span&gt; REDIS_ENCODING_SKIPLIST&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            zset &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;zs &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt; op&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;subject&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;ptr&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            dictEntry &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;de&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;            if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;de &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; dictFind&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;zs&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;ele&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; NULL&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                *&lt;/span&gt;&lt;span&gt;score &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;dictGetVal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;de&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;            redisPanic&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Unknown sorted set encoding&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        redisPanic&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Unsupported type&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先忽略掉 &lt;code&gt;set&lt;/code&gt; 的操作, 来看 &lt;code&gt;sorted set&lt;/code&gt;. 我们又见到了收尾工作时出现过的 &lt;code&gt;ziplist&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The ziplist is a specially encoded dually linked list that is designed to be very memory efficient. It stores both strings and integer values, where integers are encoded as actual integers instead of a series of characters. It allows push and pop operations on either side of the list in O(1) time. However, because every operation requires a reallocation of the memory used by the ziplist, the actual complexity is related to the amount of memory used by the ziplist.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;查了一下资料, 这是 &lt;code&gt;Redis&lt;/code&gt; 在面对小元素时可以做的一个内存优化, 本体是一个经过特殊编码的双向链表. 经过特殊编码后的数据会变得更加紧凑, 连续的内存使用也对于缓存更加友好. 有两个配置决定了 &lt;code&gt;sorted set&lt;/code&gt; 中使用 &lt;code&gt;ziplist&lt;/code&gt; 的阈值.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;c&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; REDIS_ZSET_MAX_ZIPLIST_ENTRIES&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 128&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; REDIS_ZSET_MAX_ZIPLIST_VALUE&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 64&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/redis-zinterstore/images/zset.svg&quot; alt=&quot;有序集合&quot; /&gt;
图. 有序集合&lt;/p&gt;
&lt;p&gt;然后我们尴尬的发现, 在 &lt;code&gt;ziplist&lt;/code&gt; 查找一个元素实际上是一个遍历, 时间复杂度为 &lt;code&gt;O(N)&lt;/code&gt;, 如下&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;c&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; https://github.com/redis/redis/blob/3.0/src/t_zset.c#L915 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;unsigned&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; char&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;zzlFind&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;unsigned&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; char&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;zl&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; robj &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;ele&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; double&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;score&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    unsigned&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; char&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span&gt;eptr &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; ziplistIndex&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;zl&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span&gt;sptr&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ele &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; getDecodedObject&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ele&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    while&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;eptr &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; NULL&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        sptr &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; ziplistNext&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;zl&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;eptr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        redisAssertWithInfo&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;NULL&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;ele&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;sptr &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; NULL&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ziplistCompare&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eptr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;ele&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;ptr&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sdslen&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ele&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;ptr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;            /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Matching element, pull out score. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;            if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;score &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; NULL&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span&gt;score &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; zzlGetScore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sptr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;            decrRefCount&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ele&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;            return&lt;/span&gt;&lt;span&gt; eptr&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;        /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Move to next element. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        eptr &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; ziplistNext&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;zl&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;sptr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;    decrRefCount&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ele&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; NULL&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不过当 &lt;code&gt;zset&lt;/code&gt; 使用 &lt;code&gt;REDIS_ENCODING_SKIPLIST&lt;/code&gt; 作为 encoding 的时候, 使用 &lt;code&gt;Hash table&lt;/code&gt; 做查询的时间复杂度是 &lt;code&gt;O(1)&lt;/code&gt; 这是肯定的.&lt;/p&gt;
&lt;p&gt;当然, 这里面其实还有很多细节没有说到, 比如 &lt;code&gt;ziplist&lt;/code&gt; 的内部表示, 和 &lt;code&gt;zset&lt;/code&gt; 混在一起的 &lt;code&gt;set&lt;/code&gt;, &lt;code&gt;zset&lt;/code&gt; 中的 &lt;code&gt;dict&lt;/code&gt; 和 &lt;code&gt;skiplist&lt;/code&gt; 的详细分析等等. 下次有机会的吧.&lt;/p&gt;
&lt;h2 id=&quot;can-kao-zi-liao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#can-kao-zi-liao&quot; aria-label=&quot;Anchor link for: can-kao-zi-liao&quot;&gt;参考资料&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://redislabs.com/ebook/part-2-core-concepts/01chapter-9-reducing-memory-use/9-1-short-structures/9-1-1-the-ziplist-representation/&quot;&gt;The ziplist representation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://redisbook.readthedocs.io/en/latest/compress-datastruct/ziplist.html&quot;&gt;压缩列表 — Redis 设计与实现&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>实现优雅的 Golang 项目结构</title>
        <published>2021-02-19T18:10:22+08:00</published>
        <updated>2021-02-19T18:10:22+08:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/write-elegant-project-with-golang/"/>
        <id>https://memwey.github.io/posts/write-elegant-project-with-golang/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/write-elegant-project-with-golang/">&lt;p&gt;随着工作经验的积累, 越来越感到仅仅写出 &lt;code&gt;Elegant Code&lt;/code&gt; 是不够的, 特别是当多人合作开发的时候, 遇到种种结构混乱, 代码冗余, 模块耦合, 依赖耦合的问题. 这个时候, 就需要更高的要求, &lt;code&gt;Elegant Project&lt;/code&gt;, 自顶向下的改善项目的结构.&lt;/p&gt;
&lt;p&gt;仅仅实现业务逻辑是很容易的, 而大量的时间会用在测试, Debug 上. 一个良好定义的项目结构能够极大地提升开发效率, 降低维护成本. 下面介绍一套经过实践检验的 Go 项目结构规范.&lt;/p&gt;
&lt;h2 id=&quot;xiang-mu-jie-gou-gui-fan&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#xiang-mu-jie-gou-gui-fan&quot; aria-label=&quot;Anchor link for: xiang-mu-jie-gou-gui-fan&quot;&gt;项目结构规范&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;fu-wu-diao-yong-service-invocation&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#fu-wu-diao-yong-service-invocation&quot; aria-label=&quot;Anchor link for: fu-wu-diao-yong-service-invocation&quot;&gt;服务调用 (Service Invocation)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;项目的核心调用链遵循一个清晰的层次结构:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;接口定义&lt;/strong&gt;:  服务接口在 &lt;code&gt;./domain/&amp;lt;service_name&amp;gt;/domain.go&lt;/code&gt; 文件中定义. 这是服务能力的抽象.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务实现&lt;/strong&gt;:  服务的具体实现在对应的 &lt;code&gt;./app/&amp;lt;service_name&amp;gt;&lt;/code&gt; 目录中完成.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Web Handler&lt;/strong&gt;:  Web Handler 实现在 &lt;code&gt;./internal/web&lt;/code&gt; 目录中.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;调用流程&lt;/strong&gt;:  Web Handler 调用 &lt;code&gt;./domain/&amp;lt;service_name&amp;gt;&lt;/code&gt; 中定义的服务接口, 而非直接依赖具体实现.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复杂流程处理&lt;/strong&gt;:  对于需要调用多个服务方法的复杂业务流程, 应该在一个专用的, 以 &lt;code&gt;flow&lt;/code&gt; 为后缀的服务中进行编排, 而不是直接在 Web Handler 中组合.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;fu-wu-bao-ming-ming-service-package-name&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#fu-wu-bao-ming-ming-service-package-name&quot; aria-label=&quot;Anchor link for: fu-wu-bao-ming-ming-service-package-name&quot;&gt;服务包命名 (Service Package Name)&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;避免服务包与它操作的实体同名&lt;/strong&gt;:  例如, 操作 &lt;code&gt;User&lt;/code&gt; 实体的服务不应该命名为 &lt;code&gt;user&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用后缀来明确包的职责&lt;/strong&gt;:  推荐使用 &lt;code&gt;mgr&lt;/code&gt;, &lt;code&gt;hdl&lt;/code&gt;, &lt;code&gt;uc&lt;/code&gt;, &lt;code&gt;svc&lt;/code&gt;, &lt;code&gt;flow&lt;/code&gt; 等后缀来命名服务包, 以清晰地表达其作为服务层的角色.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;fu-wu-fang-fa-qian-ming-service-method-signature&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#fu-wu-fang-fa-qian-ming-service-method-signature&quot; aria-label=&quot;Anchor link for: fu-wu-fang-fa-qian-ming-service-method-signature&quot;&gt;服务方法签名 (Service Method Signature)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;为了保持一致性和可预测性, 所有服务方法都应遵循以下签名格式:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; 标准服务方法签名&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Hello&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Context&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; req&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span&gt;HelloRequest&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;resp&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span&gt;HelloResponse&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; err&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;请求/响应类型&lt;/strong&gt;:  请求和响应的结构体遵循 &lt;code&gt;&amp;lt;MethodName&amp;gt;Request&lt;/code&gt; 和 &lt;code&gt;&amp;lt;MethodName&amp;gt;Response&lt;/code&gt; 的命名模式.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;上下文参数&lt;/strong&gt;:  第一个参数必须是 &lt;code&gt;context.Context&lt;/code&gt;, 用于传递请求范围的数据, 如 trace ID, 用户身份等.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;错误返回&lt;/strong&gt;:  最后一个返回值必须是 &lt;code&gt;error&lt;/code&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;:  对于通过 ID 批量获取数据的方法, 方法名应为 &lt;code&gt;Fetch&amp;lt;Objects&amp;gt;Dict&lt;/code&gt;, 且响应中应包含一个名为 &lt;code&gt;&amp;lt;Objects&amp;gt;Dict&lt;/code&gt; 的 &lt;code&gt;map[&amp;lt;IDType&amp;gt;]&amp;lt;Object&amp;gt;&lt;/code&gt; 字段, 以方便调用方通过 ID 查找对象.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;fu-wu-shi-xian-jie-gou-service-implementation-structure&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#fu-wu-shi-xian-jie-gou-service-implementation-structure&quot; aria-label=&quot;Anchor link for: fu-wu-shi-xian-jie-gou-service-implementation-structure&quot;&gt;服务实现结构 (Service Implementation Structure)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;一个独立服务的内部结构组织如下:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;服务接口&lt;/strong&gt;:  &lt;code&gt;Service&lt;/code&gt; 接口位于 &lt;code&gt;./domain/&amp;lt;service_name&amp;gt;/domain.go&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内部核心定义&lt;/strong&gt;:  如有需要, 内部使用的数据结构和 Repository 接口定义在 &lt;code&gt;./app/&amp;lt;service_name&amp;gt;/internal/core/core.go&lt;/code&gt;, 以 &lt;code&gt;core&lt;/code&gt; 为包名.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repository 实现&lt;/strong&gt;:  Repository 的具体实现放在 &lt;code&gt;./app/&amp;lt;service_name&amp;gt;/internal/repo&lt;/code&gt; 目录中.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务初始化&lt;/strong&gt;:  服务的结构体和构造逻辑, 以及传入的依赖定义在 &lt;code&gt;./app/&amp;lt;service_name&amp;gt;/service.go&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务构造函数&lt;/strong&gt;:  服务构造函数应命名为 &lt;code&gt;NewService(inj *NewServiceInj, opts ...ServiceOption) (srv *Service, err error)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;业务逻辑&lt;/strong&gt;:  核心业务逻辑实现在 &lt;code&gt;./app/&amp;lt;service_name&amp;gt;&lt;/code&gt; 目录下的独立文件中, 与服务构造函数分离.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repository 构造函数&lt;/strong&gt;:  Repository 的构造函数和通用的事务逻辑 (如 &lt;code&gt;(r *Repo) WithTx(...)&lt;/code&gt;) 应放在 &lt;code&gt;./app/&amp;lt;service_name&amp;gt;/internal/repo/repo.go&lt;/code&gt; 中.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repository 操作&lt;/strong&gt;:  Repository 的具体数据库操作在 &lt;code&gt;./app/&amp;lt;service_name&amp;gt;/internal/repo&lt;/code&gt; 目录下的独立文件中实现, 与其构造函数分离, 以 &lt;code&gt;repo&lt;/code&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;:  数据库事务边界应定义在服务层, 而不是在 Repository 层.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;fu-wu-mock-service-mock&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#fu-wu-mock-service-mock&quot; aria-label=&quot;Anchor link for: fu-wu-mock-service-mock&quot;&gt;服务 Mock (Service Mock)&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mock 初始化&lt;/strong&gt;:  总是使用辅助函数 &lt;code&gt;initMockService(t *testing.T) (srv *service.Service, mockInj *mockInjection)&lt;/code&gt; 来初始化被测试的服务及其 mock 依赖.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mock 生成&lt;/strong&gt;:  使用 &lt;code&gt;Mockery&lt;/code&gt; 基于接口生成 mock 对象.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;类型安全&lt;/strong&gt;:  测试辅助函数应使用&lt;strong&gt;类型安全&lt;/strong&gt;的方法调用来设置 mock 行为, 而不是依赖于基于字符串的通用方法, 如 &lt;code&gt;mock.On(&quot;MethodName&quot;)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;jiao-ben-scripts&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#jiao-ben-scripts&quot; aria-label=&quot;Anchor link for: jiao-ben-scripts&quot;&gt;脚本 (Scripts)&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;通用脚本&lt;/strong&gt;:  常用的脚本应放置在 &lt;code&gt;./script&lt;/code&gt; 目录中.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQL 文件&lt;/strong&gt;:  SQL DML 和 DDL 文件应放置在 &lt;code&gt;./script/sql&lt;/code&gt; 目录中.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;ke-xuan-shi-xian-shi-yong-ent-orm&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ke-xuan-shi-xian-shi-yong-ent-orm&quot; aria-label=&quot;Anchor link for: ke-xuan-shi-xian-shi-yong-ent-orm&quot;&gt;可选实现: 使用 Ent ORM&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;本结构规范不强制绑定任何 ORM 框架, 但如果选择使用 &lt;a rel=&quot;external&quot; href=&quot;https://entgo.io/&quot;&gt;Ent&lt;/a&gt;, 可以遵循以下约定来更好地集成.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Schema 定义&lt;/strong&gt;:  &lt;code&gt;Ent&lt;/code&gt; 的 Schema 定义在 &lt;code&gt;./internal/schema&lt;/code&gt; 目录中, 每个数据表对应一个文件, 如 &lt;code&gt;./internal/schema/user.go&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生成代码&lt;/strong&gt;:  &lt;code&gt;Ent&lt;/code&gt; 生成的代码位于 &lt;code&gt;./internal/ent&lt;/code&gt; 目录.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实体命名&lt;/strong&gt;:  在 Repository 层, &lt;code&gt;Ent&lt;/code&gt; ORM 生成的对象在命名时应使用 &lt;code&gt;Entity&lt;/code&gt; (或 &lt;code&gt;Entities&lt;/code&gt; 用于集合) 后缀, 以明确区分它们与领域中的 &lt;code&gt;core&lt;/code&gt; 包中的实体, 并且 &lt;code&gt;Ent&lt;/code&gt; 生成的对象不能泄露出 &lt;code&gt;repo&lt;/code&gt; 包.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代码生成脚本&lt;/strong&gt;:  生成 Ent ORM 代码的脚本应位于 &lt;code&gt;./script/ent.sh&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;fu-wu-diao-yong-liu-cheng-tu&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#fu-wu-diao-yong-liu-cheng-tu&quot; aria-label=&quot;Anchor link for: fu-wu-diao-yong-liu-cheng-tu&quot;&gt;服务调用流程图&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;下图展示了从接收客户端请求到与数据库交互的完整分层调用链, 它强调了接口与实现分离 (依赖倒置原则), 以及各层在项目中的位置. 这样保证了每一个层级的职责清晰, 耦合度低, 方便维护和扩展, 也方便进行单元测试和排查问题.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[ API Request ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      v&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+--------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|      Web Handler               |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|     (./internal/web)           |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+--------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      |  (依赖接口)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      v&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+--------------------------------+ &amp;lt;----+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|      Domain Interface          |      |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|   (./domain/&amp;lt;service_name&amp;gt;)    |      | (服务之间通过接口调用)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+--------------------------------+      |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      |                                  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      |  (由 App 层实现)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      v&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+--------------------------------+      |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|      App Implementation        | -----+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|    (./app/&amp;lt;service_name&amp;gt;)      |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+--------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      |  (依赖接口)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      v&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+--------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|      Repository Interface      |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|   (./app/.../internal/core)    |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+--------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      |  (由 Repo 层实现)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      v&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+--------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|      Repository Implementation |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|   (./app/.../internal/repo)    |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+--------------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      v&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[ Database / ORM ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;shi-li-xiang-mu-mu-lu-jie-gou&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#shi-li-xiang-mu-mu-lu-jie-gou&quot; aria-label=&quot;Anchor link for: shi-li-xiang-mu-mu-lu-jie-gou&quot;&gt;示例项目目录结构&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;这是一个遵循上述规范的示例项目结构. 它以一个用户注册场景为例, 其中 &lt;code&gt;registrationflow&lt;/code&gt; 是一个复杂流程, 负责编排 &lt;code&gt;authuc&lt;/code&gt; (认证用例) 和 &lt;code&gt;notiuc&lt;/code&gt; (通知用例).&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── app/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── registrationflow/           # “用户注册”流程的实现&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   │   ├── service.go              # 定义 Service, NewService&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   │   └── registration_logic.go   # 流程编排逻辑&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── authuc/                     # “认证”用例的实现&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   │   ├── internal/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   │   │   ├── core/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   │   │   │   ├── core.go         # 定义 IAuthRepo 使用的数据结构&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   │   │   │   └── repo_mock.go    # IAuthRepo 的 mock 实现&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   │   │   └── repo/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   │   │       ├── repo.go         # 定义 Repo, NewRepo, WithTx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   │   │       └── auth_repo.go    # 实现 IAuthRepo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   │   ├── service.go              # 定义 Service, NewService&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   │   ├── auth_logic.go           # 认证业务逻辑&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   │   └── auth_logic_test.go      # 认证业务逻辑的单元测试&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   └── notiuc/                     # “通知”用例的实现&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       ├── internal/               # (可选, 如需封装 client 或定义内部结构)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       │   └── ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       ├── service.go              # 定义 Service, NewService&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       └── noti_logic.go           # 通知业务逻辑 (如发邮件, 短信)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── cmd/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   └── server/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       └── main.go&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── domain/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── registrationflow/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   │   ├── domain.go               # 定义 RegistrationFlow 接口&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   │   └── service_mock.go         # RegistrationFlow 的 mock 实现&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── authuc/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   │   ├── domain.go               # 定义 AuthUseCase 接口&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   │   └── service_mock.go         # AuthUseCase 的 mock 实现&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   └── notiuc/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       ├── domain.go               # 定义 NotiUseCase 接口&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       └── service_mock.go         # NotiUseCase 的 mock 实现&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── internal/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   └── web/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       ├── registration_handler.go # 处理注册流程, 调用 domain/registrationflow&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       ├── auth_handler.go         # 处理认证请求, 调用 domain/authuc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       └── auth_handler_test.go    # 认证请求处理的单元测试&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述目录结构展示了一个典型的分层应用:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;domain&lt;/code&gt;&lt;/strong&gt;:  定义了三个核心服务接口: &lt;code&gt;RegistrationFlow&lt;/code&gt;、&lt;code&gt;AuthUseCase&lt;/code&gt; 和 &lt;code&gt;NotiUseCase&lt;/code&gt;. 这是业务能力的抽象层.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;app&lt;/code&gt;&lt;/strong&gt;:  包含了上述接口的具体实现. &lt;code&gt;registrationflow&lt;/code&gt; 是一个流程服务 (Flow), 它的实现会依赖 &lt;code&gt;authuc&lt;/code&gt; 和 &lt;code&gt;notiuc&lt;/code&gt; 的接口, 从而编排和调用这两个用例服务.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;internal/web&lt;/code&gt;&lt;/strong&gt;:  Web Handler 层, 负责暴露服务给外部. 不同的 Handler 文件处理不同的业务, 例如 &lt;code&gt;registration_handler.go&lt;/code&gt; 调用复杂的流程服务, 而 &lt;code&gt;auth_handler.go&lt;/code&gt;可以直接调用简单的用例服务.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;包命名&lt;/strong&gt;:  注意所有包名都使用简短, 全小写的形式, 并且不使用下划线, 这是 Go 语言推荐的风格.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mock 与测试&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mock 文件&lt;/strong&gt;: &lt;code&gt;*_mock.go&lt;/code&gt; 是接口的 mock 实现, 用于单元测试.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;测试文件&lt;/strong&gt;: &lt;code&gt;*_test.go&lt;/code&gt; 是对应的单元测试文件.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;依赖关系&lt;/strong&gt;: 基于依赖倒置原则, 测试文件会使用 mock 来隔离被测试的层级.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;app/authuc/auth_logic_test.go&lt;/code&gt; (业务逻辑测试) 会使用 &lt;code&gt;app/authuc/internal/core/repo_mock.go&lt;/code&gt; 来模拟数据访问行为.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;internal/web/auth_handler_test.go&lt;/code&gt; (Handler 测试) 会使用 &lt;code&gt;domain/authuc/service_mock.go&lt;/code&gt; 来模拟业务服务行为.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;zong-jie&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zong-jie&quot; aria-label=&quot;Anchor link for: zong-jie&quot;&gt;总结&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;本规范的核心价值在于提供一个清晰一致的项目结构, 从而提升项目的&lt;strong&gt;可测试性&lt;/strong&gt;和&lt;strong&gt;可维护性&lt;/strong&gt;. 通过明确的分层和接口定义, 实现了以下关键的架构原则:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Monorepo 下的微服务化&lt;/strong&gt;: 在单一代码库(Monorepo)中, 通过模块化的服务(&lt;code&gt;app&lt;/code&gt;)和统一的接口(&lt;code&gt;domain&lt;/code&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;: 依赖倒置原则的贯彻, 以及接口 mock 的普遍使用, 使得每个模块都可以进行独立的单元测试, 保证了代码质量和迭代速度.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;can-kao-lian-jie&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#can-kao-lian-jie&quot; aria-label=&quot;Anchor link for: can-kao-lian-jie&quot;&gt;参考链接&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/golang-standards/project-layout&quot;&gt;Standard Go Project Layout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://github.com/mattermost/mattermost&quot;&gt;Mattermost&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>Redis LRU</title>
        <published>2020-12-25T14:26:24+08:00</published>
        <updated>2020-12-25T14:26:24+08:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/redis-lru/"/>
        <id>https://memwey.github.io/posts/redis-lru/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/redis-lru/">&lt;h2 id=&quot;ji-chu-zhi-shi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ji-chu-zhi-shi&quot; aria-label=&quot;Anchor link for: ji-chu-zhi-shi&quot;&gt;基础知识&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;LRU (Least recently used)&lt;/code&gt; 是一个非常常用的缓存置换算法.&lt;/p&gt;
&lt;p&gt;在缓存空间有限的情况下, 在新的数据写入时, 需要淘汰一些旧数据. 一般期望最不可能被继续访问的数据淘汰. 由于无法对未来的情况进行预测, 只能基于现有的信息推测.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;LRU&lt;/code&gt; 基于这样一个假定, 在一个时间点上, 如果一个数据距离上次被访问的时间越长, 则这个数据在未来被访问的可能性越小. 所以, 应该淘汰掉距离上次被访问的时间最久的数据.&lt;/p&gt;
&lt;p&gt;常见的 &lt;code&gt;LRU&lt;/code&gt; 实现是使用一个双向链表, 当 Item 被访问时, 将其移动到链表的头部. 当缓存不足时, 从链表的尾部开始淘汰.&lt;/p&gt;
&lt;p&gt;单纯的双向链表的实现实际上是不太符合现实的. 一般的, 我们希望缓存可以尽快被检索到, 而在双向链表中检索 Item 的效率是 O(n), 特别是检索的 Item 在链表中不存在时, 效率稳定的是 O(n). 这样, 在缓存系统中维护这个双向链表的成本是非常高的. 一般的, 会将 &lt;code&gt;哈希表&lt;/code&gt; 或者 &lt;code&gt;二叉搜索树&lt;/code&gt; 和双向链表组合起来, 在更高效率的数据结构中记录 &lt;code&gt;Key&lt;/code&gt;, 并在 &lt;code&gt;Key&lt;/code&gt; 中记录 Item 在双向链表中的指针.&lt;/p&gt;
&lt;p&gt;另外的, 在并发量较大的时候, 双向链表中的操作需要加锁, 否则链表很容易出问题.&lt;/p&gt;
&lt;h2 id=&quot;ju-ti-shi-xian&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ju-ti-shi-xian&quot; aria-label=&quot;Anchor link for: ju-ti-shi-xian&quot;&gt;具体实现&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;redis-2-8&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#redis-2-8&quot; aria-label=&quot;Anchor link for: redis-2-8&quot;&gt;Redis 2.8&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在较早版本的 &lt;code&gt;Redis&lt;/code&gt; 上, 并没有实现 &lt;code&gt;LRU&lt;/code&gt;. 在后续 2.8 添加的时候, 并没有使用常见的双向链表的方式来实现 &lt;code&gt;LRU&lt;/code&gt;, 而使用了一个近似的实现.&lt;/p&gt;
&lt;p&gt;从空间和时间上考虑, &lt;code&gt;Redis&lt;/code&gt; 中的 &lt;code&gt;Key&lt;/code&gt; 的数量可能非常的多, 双向链表可能会非常大, 占用内存非常多; 另一方面, &lt;code&gt;Redis&lt;/code&gt; 中的操作可能也非常频繁, 每一次访问都需要操作一次双向链表, 在时间上也显得非常不划算.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Antirez&lt;/code&gt; 在 &lt;code&gt;Redis Object&lt;/code&gt; 中挤出了 24 个位元 bits, 并用其存储按秒计算的 &lt;code&gt;unix timestamp&lt;/code&gt; 的低 24 位. 这个被称为 &lt;code&gt;LRU clock&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;c&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; https://github.com/redis/redis/blob/3.0/src/redis.h#L420 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; A redis object, that is a type able to hold a string / list / set &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; The actual Redis Object &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; REDIS_LRU_BITS&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 24&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; REDIS_LRU_CLOCK_MAX&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt;REDIS_LRU_BITS&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Max value of obj-&amp;gt;lru &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; REDIS_LRU_CLOCK_RESOLUTION&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1000&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; LRU clock resolution in ms &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;typedef&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; struct&lt;/span&gt;&lt;span&gt; redisObject &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    unsigned&lt;/span&gt;&lt;span&gt; type:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;4&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    unsigned&lt;/span&gt;&lt;span&gt; encoding:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;4&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    unsigned&lt;/span&gt;&lt;span&gt; lru:REDIS_LRU_BITS&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; lru time (relative to server.lruclock) &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    int&lt;/span&gt;&lt;span&gt; refcount&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    void&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span&gt;ptr&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; robj&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;24 个 bits 明显不够存储一个完整的时间戳, 当第二十四位向前进位的时候, 就会发生溢出. 此时, 最近被访问的 Item 的 &lt;code&gt;LRU clock&lt;/code&gt; 反而较小, 更容易被淘汰. 考虑到这个溢出需要 194 天, 而 &lt;code&gt;Redis&lt;/code&gt; 中的操作应该比较频繁, 所以 &lt;code&gt;antirez&lt;/code&gt; 认为这个问题可以接受.&lt;/p&gt;
&lt;p&gt;理论上, 可以精心构造一些数据, 让 &lt;code&gt;Redis&lt;/code&gt; 的 &lt;code&gt;LRU&lt;/code&gt; 失效. 比如, 总是在溢出前访问一个 Item, 这个 Item 的 &lt;code&gt;LRU clock&lt;/code&gt; 总是很大, 虽然这个 Item 的访问周期总是 194 天才访问一次, 但是它总不会被淘汰.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;c&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; https://github.com/redis/redis/blob/3.0/src/db.c#L43 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;robj &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;lookupKey&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;redisDb &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;db&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; robj &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;key&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    dictEntry &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;de &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; dictFind&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;ptr&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;de&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        robj &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;val &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; dictGetVal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;de&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;        /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Update the access time for the ageing algorithm.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;         * Don&amp;#39;t do it if we have a saving child, as this will trigger&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;         * a copy on write madness. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;server&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;rdb_child_pid&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; server&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;aof_child_pid&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            val&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;lru&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; LRU_CLOCK&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        return&lt;/span&gt;&lt;span&gt; val&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; NULL&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来的问题在于如何找到最久没有被访问的 Item. 如果一定要找到最久没有被访问到的 Item, 那么需要遍历所有的 &lt;code&gt;Key&lt;/code&gt;, 而且在遍历的过程中, 要么禁止在这期间做任何的访问操作, 要么可能出现找到的 &lt;code&gt;Key&lt;/code&gt; 恰好又刚刚被访问到的问题.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Antirez&lt;/code&gt; 在这里又使用了一个近似的实现, 随机选取 3 个 &lt;code&gt;Key&lt;/code&gt;, 把他们之中最久没有被访问到的淘汰. 随后, 这个数值变成了可配置项 &lt;code&gt;maxmemory-samples&lt;/code&gt; , 默认值是 5. 考虑到选出的结果不一定是最好的, 但是很大可能不是一个坏的结果, 即选出一个非常近被访问的 Item, 这个实现还算可以接受.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;c&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; https://github.com/redis/redis/blob/2.8/src/redis.c#L2982 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; volatile-lru and allkeys-lru policy &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;server.maxmemory_policy &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;==&lt;/span&gt;&lt;span&gt; REDIS_MAXMEMORY_ALLKEYS_LRU &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;||&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    server.maxmemory_policy &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;==&lt;/span&gt;&lt;span&gt; REDIS_MAXMEMORY_VOLATILE_LRU&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;k &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; k &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; server&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;maxmemory_samples&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; k&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;++&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        sds thiskey&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        long&lt;/span&gt;&lt;span&gt; thisval&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        robj &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        de &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; dictGetRandomKey&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        thiskey &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; dictGetKey&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;de&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;        /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; When policy is volatile-lru we need an additional lookup&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;          * to locate the real key, as dict is set to db-&amp;gt;expires. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;server&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;maxmemory_policy&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span&gt; REDIS_MAXMEMORY_VOLATILE_LRU&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            de &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; dictFind&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; thiskey&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        o &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; dictGetVal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;de&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        thisval &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; estimateObjectIdleTime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;        /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Higher idle time is better candidate for deletion &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;bestkey &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; NULL&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ||&lt;/span&gt;&lt;span&gt; thisval &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; bestval&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            bestkey &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt; thiskey&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            bestval &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt; thisval&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;redis-3-0&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#redis-3-0&quot; aria-label=&quot;Anchor link for: redis-3-0&quot;&gt;Redis 3.0&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Antirez&lt;/code&gt; 在 3.0 版本中进一步提升了近似算法的准确性. 一个显而易见的方法是, 通过过去累计的信息来提升准确性.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Redis&lt;/code&gt; 中维护了一个默认大小为 16 的 &lt;code&gt;pool&lt;/code&gt;, 里面存储了备选的 &lt;code&gt;Key&lt;/code&gt;. 当需要淘汰时, 从随机选择的 N 个 &lt;code&gt;Key&lt;/code&gt; 中与 &lt;code&gt;pool&lt;/code&gt; 中的 &lt;code&gt;Key&lt;/code&gt; 做对比, 在 &lt;code&gt;pool&lt;/code&gt; 中维护其中最久没有被访问到的 16 个 &lt;code&gt;Key&lt;/code&gt;, 然后在 &lt;code&gt;pool&lt;/code&gt; 中淘汰其中最久没有被访问到的 Item. 这个 &lt;code&gt;pool&lt;/code&gt; 非常类似于小顶堆.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;c&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; https://github.com/redis/redis/blob/3.0/src/redis.c#L3275 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; volatile-lru and allkeys-lru policy &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;server.maxmemory_policy &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;==&lt;/span&gt;&lt;span&gt; REDIS_MAXMEMORY_ALLKEYS_LRU &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;||&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    server.maxmemory_policy &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;==&lt;/span&gt;&lt;span&gt; REDIS_MAXMEMORY_VOLATILE_LRU&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    struct&lt;/span&gt;&lt;span&gt; evictionPoolEntry &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;pool &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt; db&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;eviction_pool&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    while&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;bestkey &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; NULL&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        evictionPoolPopulate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; db&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; db&lt;/span&gt;&lt;span&gt;-&amp;gt;&lt;/span&gt;&lt;span&gt;eviction_pool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;        /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Go backward from best to worst element to evict. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;k &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt; REDIS_EVICTION_POOL_SIZE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; k &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; k&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;--&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;            if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;pool&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; NULL&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; continue&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            de &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; dictFind&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;pool&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;            /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Remove the entry from the pool. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;            sdsfree&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;pool&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;            /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Shift all elements on its right to left. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;            memmove&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pool&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;pool&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                sizeof&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;pool&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;REDIS_EVICTION_POOL_SIZE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;            /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Clear the element on the right which is empty&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;              * since we shifted one position to the left.  &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;            pool&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;REDIS_EVICTION_POOL_SIZE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; NULL&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;            pool&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;REDIS_EVICTION_POOL_SIZE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;idle&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;            /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; If the key exists, is our pick. Otherwise it is&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;              * a ghost and we need to try the next element. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;            if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;de&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                bestkey &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; dictGetKey&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;de&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                break&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Ghost... &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                continue&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pool&lt;/code&gt; 中排序操作的核心代码在 &lt;code&gt;evictionPoolPopulate&lt;/code&gt; 函数中&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;c&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; https://github.com/redis/redis/blob/3.0/src/redis.c#L3145 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; EVICTION_SAMPLES_ARRAY_SIZE&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 16&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; evictionPoolPopulate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dict &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;sampledict&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; dict &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;keydict&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; struct&lt;/span&gt;&lt;span&gt; evictionPoolEntry &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;pool&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    int&lt;/span&gt;&lt;span&gt; j&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; k&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; count&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    dictEntry &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;_samples&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;EVICTION_SAMPLES_ARRAY_SIZE&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    dictEntry &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;samples&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Try to use a static buffer: this function is a big hit...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;     * Note: it was actually measured that this helps. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;server&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;maxmemory_samples&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;=&lt;/span&gt;&lt;span&gt; EVICTION_SAMPLES_ARRAY_SIZE&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        samples &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt; _samples&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        samples &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; zmalloc&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;sizeof&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;samples&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;server&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;maxmemory_samples&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    count &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; dictGetSomeKeys&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sampledict&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;samples&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;server&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;maxmemory_samples&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;j &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; j &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; count&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; j&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;++&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        unsigned&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; long&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; long&lt;/span&gt;&lt;span&gt; idle&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        sds key&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        robj &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        dictEntry &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;de&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        de &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; samples&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;j&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        key &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; dictGetKey&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;de&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;        /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; If the dictionary we are sampling from is not the main&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;         * dictionary (but the expires one) we need to lookup the key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;         * again in the key dictionary to obtain the value object. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;sampledict &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;!=&lt;/span&gt;&lt;span&gt; keydict&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; de &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; dictFind&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;keydict&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        o &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; dictGetVal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;de&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        idle &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; estimateObjectIdleTime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;        /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Insert the element inside the pool.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;         * First, find the first empty bucket or the first populated&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;         * bucket that has an idle time smaller than our idle time. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        k &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        while&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;k &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; REDIS_EVICTION_POOL_SIZE &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;               pool&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;               pool&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;idle&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span&gt; idle&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; k&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;++&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;k &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; pool&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;REDIS_EVICTION_POOL_SIZE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; !=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; NULL&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;            /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Can&amp;#39;t insert if the element is &amp;lt; the worst element we have&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;             * and there are no empty buckets. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;            continue&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;k &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; REDIS_EVICTION_POOL_SIZE &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; pool&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; NULL&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;            /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Inserting into empty position. No setup needed before insert. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;            /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Inserting in the middle. Now k points to the first element&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;             * greater than the element to insert.  &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;            if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;pool&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;REDIS_EVICTION_POOL_SIZE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; NULL&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Free space on the right? Insert at k shifting&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                 * all the elements from k to end to the right. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;                memmove&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pool&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;pool&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;                    sizeof&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;pool&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;REDIS_EVICTION_POOL_SIZE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; No free space on right? Insert at k-1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                k&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;--&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                /*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Shift all elements on the left of k (included) to the&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;                 * left, so we discard the element with smaller idle time. &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;                sdsfree&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;pool&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;                memmove&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pool&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;pool&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;sizeof&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;pool&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;        pool&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; sdsdup&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;        pool&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;idle&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; idle&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;samples &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;!=&lt;/span&gt;&lt;span&gt; _samples&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; zfree&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;samples&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;can-kao-zi-liao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#can-kao-zi-liao&quot; aria-label=&quot;Anchor link for: can-kao-zi-liao&quot;&gt;参考资料&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;http://antirez.com/news/109&quot;&gt;Random notes on improving the Redis LRU algorithm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://redis.io/topics/lru-cache&quot;&gt;Using Redis as an LRU cache&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>DELL Precision T1700 SFF</title>
        <published>2020-12-04T00:52:03+08:00</published>
        <updated>2021-06-06T23:29:40+08:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/dell-t1700-sff/"/>
        <id>https://memwey.github.io/posts/dell-t1700-sff/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/dell-t1700-sff/">&lt;p&gt;原本的台式机, E3-1231v3 搭配上在挖矿狂潮前买的 GTX 1070, 在这个普遍挤牙膏的时期感觉再战三年不成问题. 不过既然有了自己的书房, 书房里没有电脑是万万不能的. 看了一下自己的钱包&lt;del&gt;和京东的无货&lt;/del&gt;, 决定用 新平台 + 老显卡, 老平台 + 入门显卡 这样的组合, 让一台电脑变成两台.&lt;/p&gt;
&lt;h2 id=&quot;tiao-xuan-guo-cheng&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#tiao-xuan-guo-cheng&quot; aria-label=&quot;Anchor link for: tiao-xuan-guo-cheng&quot;&gt;挑选过程&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;新平台暂且不表, 有空另外记录. 这段其实跳过也行, 都是一些流水账.&lt;/p&gt;
&lt;p&gt;首先还是决定给老平台增加一些便携性, 毕竟性能够用就好, 租房住的生活还是要考虑搬家的, 之前的全铝加钢化玻璃机箱搬起来确实不方便. 另外, 对之前的乞丐版主板和电源也都不太满意, 但是问题最大的就是主板, LGA 1150 平台的 ITX 主板数量稀少且价格昂贵. 转换思路想一想, 机箱, 主板, 电源, 这不就是一个准系统吗.&lt;/p&gt;
&lt;p&gt;淘宝逛了一下发现, 主要是 DELL 和 HP 的 SFF (Small Form Factor) 型准系统比较符合需求. 主要型号包括 &lt;code&gt;DELL OptiPlex 3020&lt;/code&gt;, &lt;code&gt;DELL OptiPlex 7020&lt;/code&gt;, &lt;code&gt;DELL OptiPlex 9020&lt;/code&gt;, &lt;code&gt;DELL Precision T1700&lt;/code&gt;, &lt;code&gt;HP ProDesk 400 G1&lt;/code&gt;, &lt;code&gt;HP ProDesk 600 G1&lt;/code&gt;, &lt;code&gt;HP ProDesk 800 G1&lt;/code&gt;, &lt;code&gt;HP Z230&lt;/code&gt;. 同系列的产品很好比较, 数字越大约好, 不过其实也没太大区别. HP 的机器相比 DELL 的稍微大一些, 扩展性稍强.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DELL Precision T1700&lt;/code&gt; 在以上型号中脱颖而出的原因可能就是因为它是 &lt;code&gt;Workstation&lt;/code&gt; 和 &lt;code&gt;C226&lt;/code&gt; 主板芯片组&lt;del&gt;和它不属于搞的人头晕的家族式型号&lt;/del&gt;吧.&lt;/p&gt;
&lt;h2 id=&quot;xi-tong-jie-shao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#xi-tong-jie-shao&quot; aria-label=&quot;Anchor link for: xi-tong-jie-shao&quot;&gt;系统介绍&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;尺寸: 290.00mm * 92.60mm * 312.00mm&lt;/li&gt;
&lt;li&gt;体积: 8.38L&lt;/li&gt;
&lt;li&gt;重量: 5.30KG&lt;/li&gt;
&lt;li&gt;电源输出: 255W&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/dell-t1700-sff/images/%E6%9C%BA%E7%AE%B1%E5%89%8D%E9%9D%A2%E6%9D%BF.jpeg&quot; alt=&quot;机箱前面板&quot; /&gt;
图. 机箱前面板&lt;/p&gt;
&lt;p&gt;体积完全可以称得上小巧. C226 主板可以让 LGA 1150 的 E3-1231v3 和 DDR3 的两条内存条发挥余热&lt;del&gt;和体验工作站主板&lt;/del&gt;. 支持一张半高显卡可以在 E3-1231v3 没有核心显卡和一定的游戏需求中找到一个能用的解决方案.&lt;/p&gt;
&lt;h2 id=&quot;ying-jian-pei-zhi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ying-jian-pei-zhi&quot; aria-label=&quot;Anchor link for: ying-jian-pei-zhi&quot;&gt;硬件配置&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;CPU: Intel Xeon E3-1231 v3 @ 3.40GHz 4C8T&lt;/li&gt;
&lt;li&gt;RAM: G.SKILL 8G 1600MHz (x2)&lt;/li&gt;
&lt;li&gt;GPU: NVIDIA GeForce GTX 1650 4G&lt;/li&gt;
&lt;li&gt;SSD: GLOWAY STK240GS3-S7&lt;/li&gt;
&lt;li&gt;Wi-Fi: Intel Dual Band Wireless-AC 7265&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其余诸如主板, 机箱, 电源, 散热器都是准系统自带的.&lt;/p&gt;
&lt;p&gt;由于显卡必须要 &lt;strong&gt;单槽位&lt;/strong&gt; 的 &lt;strong&gt;半高&lt;/strong&gt; 显卡, 所以这块 &lt;code&gt;翔升 GTX1650 4GD5 战刀&lt;/code&gt; 应该是目前(2020-11-01)能买到的&lt;del&gt;现在已经买不到了, 刚看了一下&lt;/del&gt;最强的显卡了.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/dell-t1700-sff/images/%E6%98%BE%E5%8D%A1.jpeg&quot; alt=&quot;但是长的有点丑&quot; /&gt;
图. 但是长的有点丑&lt;/p&gt;
&lt;p&gt;其余配件就都是从老机器上拆下来的了, 唯一有点例外的是无线网卡, 因为是 PCIe 网卡, 之前是全高挡板, 淘宝算上邮费 7 块钱买了个半高挡板解决. 从北京快递过来邮费只要 4 块钱, 我也是惊了.&lt;/p&gt;
&lt;h2 id=&quot;zhuang-ji-ti-yan&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zhuang-ji-ti-yan&quot; aria-label=&quot;Anchor link for: zhuang-ji-ti-yan&quot;&gt;装机体验&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/dell-t1700-sff/images/%E6%9C%BA%E7%AE%B1%E5%90%8E%E9%9D%A2%E6%9D%BF.jpeg&quot; alt=&quot;机箱后面板&quot; /&gt;
图. 机箱后面板&lt;/p&gt;
&lt;p&gt;小小机身拥有前置 4 个 USB 加后置 6 个 USB, 数量惊人. 甚至还带了 PS/2 键鼠接口和 RS-232 串行接口. 显示输出带了两个 DP 和一个 VGA 这对新老搭档, 缺少中青代的 HDMI 和 DVI 可能会有点不便. &lt;del&gt;用独立显卡就无所谓了.&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;机箱完全是免螺丝拆装的, 维护性很好. 机箱内部有大量塑料的部分, 主要起调整风道和固定的作用. 电源和前面板风扇从前面板下方吸风, 从机箱后部排出热风.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/dell-t1700-sff/images/%E6%9C%BA%E7%AE%B1%E5%86%85%E9%83%A8.jpeg&quot; alt=&quot;机箱内部&quot; /&gt;
图. 机箱内部&lt;/p&gt;
&lt;p&gt;虽然主板上有三个 SATA 接口, 但实际上只有一个标准的硬盘供电, 凑合着和一个非标准的光驱供电一起从主板上引出, 所以想要使用超过一个硬盘要想想办法, 可以使用一转二的硬盘供电什么的.&lt;/p&gt;
&lt;p&gt;如果使用单个固态硬盘的话, 直接把固态硬盘塞到那个 3.5 寸机械硬盘位就好了, 不过有机械硬盘的话, 固态硬盘就不太好安放了, 好在空间还算多, 固态硬盘也很坚强, 找个地方塞一下问题不大.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/dell-t1700-sff/images/%E5%86%85%E9%83%A8%E7%BB%86%E8%8A%82.jpeg&quot; alt=&quot;凌乱的内部&quot; /&gt;
图. 凌乱的内部&lt;/p&gt;
&lt;p&gt;非标准的电源只有 255W, 而且只有一个 8pin 的主板供电和一个 4pin 的 CPU 供电, 没有任何 PCIe 供电. 不过反正半高显卡也基本都是能被 PCIe 16X 的 75W 供电满足的.&lt;/p&gt;
&lt;p&gt;四个标准台式机内存插槽够用, 给已经成为时代的眼泪的 DDR3 内存找一个家. 插槽上有清晰的编号, 按照顺序插上即可.&lt;/p&gt;
&lt;p&gt;主板提供了两个 PCIe 插槽, 一个全长 x16, 另一个是 x4. 但是问题是 16x 的反而 4x 的下面, 而且距离再下面的电源很近, 所以要想让显卡充分发挥只能用单槽位的. 不过这块 &lt;code&gt;翔升 GTX1650 4GD5 战刀&lt;/code&gt; 是涡轮散热, 从显卡顶端的风扇吸风到尾部排出, 和机箱原有的前面板风扇风道一致, 散热应该还好. 有机友反馈显卡太长会挡住部分 SATA 接口, 这块显卡因为更短了, 所以没有这个情况.&lt;/p&gt;
&lt;p&gt;主板上真的是一个多余的接口都没有, 导致 PCIe 无线网卡蓝牙功能所需的 USB 插头无处安放. 好吧, 小问题.&lt;/p&gt;
&lt;p&gt;机箱还有一个内置的小音响, 虽然音质什么的只能说是听个响, 甚至还不怎么响, 但是应急什么的还是挺好用的.&lt;/p&gt;
&lt;p&gt;原配的散热器是全铝的, 之前甚至都没见过这么寒酸的散热器. 在万能的淘宝上发现了可用的铜芯散热器 &lt;code&gt;J9G15&lt;/code&gt;, 据商家所说是原配给更高端的 &lt;code&gt;XE2&lt;/code&gt; 机型的散热器, 比全铝的重了有 90 克. 花费 46.8 入手一只换上, 感觉温度确实下降了不少.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/dell-t1700-sff/images/%E6%9C%BA%E7%AE%B1%E5%A4%A7%E5%B0%8F.jpeg&quot; alt=&quot;机箱大小&quot; /&gt;
图. 机箱大小参考&lt;/p&gt;
&lt;h2 id=&quot;can-kao-zi-liao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#can-kao-zi-liao&quot; aria-label=&quot;Anchor link for: can-kao-zi-liao&quot;&gt;参考资料&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://dl.dell.com/topicspdf/precision-t1700-workstation_owners-manual2_zh-cn.pdf&quot;&gt;Dell Precision T1700 小型计算机 用户手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://blog.shellbin.me/?p=911&quot;&gt;Dell Precision T1700 SFF 攒机与使用
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://zhuanlan.zhihu.com/p/141329970&quot;&gt;年轻人的第一台Precision&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>[翻译] Kubernetes 网络模型指南</title>
        <published>2019-10-28T18:55:42+08:00</published>
        <updated>2019-10-28T18:55:42+08:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/a-guide-to-the-kubernetes-networking-model/"/>
        <id>https://memwey.github.io/posts/a-guide-to-the-kubernetes-networking-model/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/a-guide-to-the-kubernetes-networking-model/">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://sookocheff.com/post/kubernetes/understanding-kubernetes-networking-model/&quot;&gt;英文原文&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Kubernetes 旨在在一组机器上运行分布式系统。分布式系统的本质使网络成为 Kubernetes 部署中的核心且必要的部分，了解 Kubernetes 网络模型将使你能够正确地运行，监控和排查在 Kubernetes 上运行的应用程序。&lt;/p&gt;
&lt;p&gt;网络是一个拥有许多成熟技术的广阔领域。对于不那么熟悉这个领域的人来说，这可能会令人感到不适，因为大多数人已经对网络有了先入为主的观念，而在 Kubernetes 中有很多新旧概念需要理解并将它们融合为一个整体。一个粗略的列表可能包括诸如网络命名空间，虚拟接口，IP 转发和网络地址转换之类的技术。本指南旨在通过讨论各种 Kubernetes 依靠的技术以及对这些技术是如何用来实现 Kubernetes 网络模型的描述来讲解 Kubernetes 网络模型。&lt;/p&gt;
&lt;p&gt;本指南相当长，分为几个部分。我们首先讨论一些基本的 Kubernetes 术语以确保在整个指南中正确使用术语，然后讨论 Kubernetes 的网络模型以及它规定的设计和实现决策。接下来是本指南中最长且最有趣的部分：通过几种不同的用例深入讨论流量是如何在 Kubernetes 中被路由的。&lt;/p&gt;
&lt;p&gt;如果您对一些网络术语，本指南附有网络术语词汇表。&lt;/p&gt;
&lt;h2 id=&quot;mu-lu&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#mu-lu&quot; aria-label=&quot;Anchor link for: mu-lu&quot;&gt;目录&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1 Kubernetes 基础知识&lt;/li&gt;
&lt;li&gt;2 Kubernetes 网络模型&lt;/li&gt;
&lt;li&gt;3 Container 到 Container 网络&lt;/li&gt;
&lt;li&gt;4 Pod 到 Pod 网络&lt;/li&gt;
&lt;li&gt;5 Pod 到 Service 网络&lt;/li&gt;
&lt;li&gt;6 Internet 到 Service 网络&lt;/li&gt;
&lt;li&gt;6.1 Egress&lt;/li&gt;
&lt;li&gt;6.2 Ingress&lt;/li&gt;
&lt;li&gt;7 总结&lt;/li&gt;
&lt;li&gt;8 术语表&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;1-kubernetes-ji-chu-zhi-shi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#1-kubernetes-ji-chu-zhi-shi&quot; aria-label=&quot;Anchor link for: 1-kubernetes-ji-chu-zhi-shi&quot;&gt;1 Kubernetes 基础知识&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Kubernetes 是由一些核心概念构建而成的，这些核心概念被组合成越来越强大的功能。本节列出了这些概念，并提供了简要概述以帮助促进讨论。Kubernetes 的功能远不止这里列出的内容，本节作为入门使读者可以在后续部分中参考。如果您已经熟悉 Kubernetes，请随意跳过本节。&lt;/p&gt;
&lt;h3 id=&quot;1-1-kubernetes-api-fu-wu-qi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#1-1-kubernetes-api-fu-wu-qi&quot; aria-label=&quot;Anchor link for: 1-1-kubernetes-api-fu-wu-qi&quot;&gt;1.1 Kubernetes API 服务器&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在 Kubernetes 中，一切都是由 Kubernetes API 服务器 (&lt;code&gt;kube-apiserver&lt;/code&gt;) 提供的 API 调用。API 服务器是通往维护着应用程序集群的所需状态的 &lt;a rel=&quot;external&quot; href=&quot;https://github.com/coreos/etcd&quot;&gt;etcd&lt;/a&gt; 数据存储的网关。要更新 Kubernetes 集群的状态，您需要对 API 服务器进行 API 调用以描述所需的状态。&lt;/p&gt;
&lt;h3 id=&quot;1-2-controllers&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#1-2-controllers&quot; aria-label=&quot;Anchor link for: 1-2-controllers&quot;&gt;1.2 Controllers&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Controller 是用于构建 Kubernetes 的核心抽象概念之一。在使用 API 服务器声明集群的期望状态后，Controller 将通过持续监视 API 服务器的状态并对所以变化做出反应来确保集群的当前状态与期望状态相匹配。Controller 使用一个简单的循环进行操作，该循环不断地根据群集当前状态对比群集的期望状态。如果存在任何差异，则控制器执行任务以确保当前状态与期望状态符合。用伪代码来表示：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;while true:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  X = currentState()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Y = desiredState()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  if X == Y:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    return  # Do nothing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  else:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    do(tasks to get to Y)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如，当您使用 API 服务器创建新的 Pod 时，&lt;code&gt;kube-scheduler&lt;/code&gt; (一个 Controller) 会注意到更改，并决定 Pod 部署在群集中的位置。然后，它使用 API 服务器 (由 etcd 支持) 写入状态变更。然后，&lt;code&gt;kubelet&lt;/code&gt; (另一个 Controller) 会注意到新的更改，并建立所需的网络功能以使 Pod 在群集中可达。在这里，两个不同的控制器对两个不同的状态更改做出反应，以使集群的实际状况与用户的意图相匹配。&lt;/p&gt;
&lt;h3 id=&quot;1-3-pods&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#1-3-pods&quot; aria-label=&quot;Anchor link for: 1-3-pods&quot;&gt;1.3 Pods&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Pod 相当于 Kubernetes 中的原子 - 用于构建应用程序的最小可部署对象。单个 Pod 代表集群中正在运行的工作负载，并封装一个或多个 Docker 容器，任何必需的存储以及一个唯一的 IP 地址。组成 Pod 的容器在设计上是协同的，并在同一机器上进行调度。&lt;/p&gt;
&lt;h3 id=&quot;1-4-nodes&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#1-4-nodes&quot; aria-label=&quot;Anchor link for: 1-4-nodes&quot;&gt;1.4 Nodes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Node 是运行 Kubernetes 集群的机器。它们可以是裸机，虚拟机或其他东西。主机 (Host) 一词通常与 Node 交替使用。我会尽量使用一致的术语 Node，但有时会根据上下文使用虚拟机 (Virtual Machine) 一词来指代节点。&lt;/p&gt;
&lt;h2 id=&quot;2-kubernetes-wang-luo-mo-xing&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#2-kubernetes-wang-luo-mo-xing&quot; aria-label=&quot;Anchor link for: 2-kubernetes-wang-luo-mo-xing&quot;&gt;2 Kubernetes 网络模型&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Kubernetes 对 Pods 的联网方式做出了固执的选择。特别的，Kubernetes 对所有的网络实现都作出了以下要求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有 Pod 都可以与所有其他 Pod 通信而无需使用网络地址转换 (NAT)&lt;/li&gt;
&lt;li&gt;所有 Node 都可以在没有 NAT 的情况下与所有 Pod 通信&lt;/li&gt;
&lt;li&gt;Pod 所见的它的 IP 就是其他 Pod 看到的它的 IP&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于这些约束，我们现在有四个不同的联网问题需要解决：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Container 到 Container 网络&lt;/li&gt;
&lt;li&gt;Pod 到 Pod 网络&lt;/li&gt;
&lt;li&gt;Pod 到 Service 网络&lt;/li&gt;
&lt;li&gt;Internet 到 Service 网络&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;指南剩下的部分将会依次讨论这些问题及其解决方法。&lt;/p&gt;
&lt;h2 id=&quot;3-container-dao-container-wang-luo&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#3-container-dao-container-wang-luo&quot; aria-label=&quot;Anchor link for: 3-container-dao-container-wang-luo&quot;&gt;3 Container 到 Container 网络&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;通常，我们将虚拟机中的网络通信视为直接与以太网设备进行交互，如图 1 所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/a-guide-to-the-kubernetes-networking-model/images/eth0.png&quot; alt=&quot;Figure 1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;图1. 以太网设备的理想状况&lt;/p&gt;
&lt;p&gt;在现实中，情况更加微妙。在 Linux，每个运行中的进程通过 &lt;a rel=&quot;external&quot; href=&quot;http://man7.org/linux/man-pages/man8/ip-netns.8.html&quot;&gt;网络命名空间&lt;/a&gt; 进行通信，网络命名空间拥有它自己的路由规则、防火墙规则和网络设备。本质上，一个网络命名空间为命名空间内的所有进程提供了一个全新的网络栈。&lt;/p&gt;
&lt;p&gt;Linux 用户可以用 &lt;code&gt;ip&lt;/code&gt; 命令创造网络命名空间。例如，下面的命令创建了一个新的网络命名空间 &lt;code&gt;ns1&lt;/code&gt;。&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ ip netns add ns1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当命名空间被创建时，它的挂载点 &lt;code&gt;/var/run/netns&lt;/code&gt; 随之创建。这样即使没有进程归属于它也可以持久化。&lt;/p&gt;
&lt;p&gt;你可以通过列出 &lt;code&gt;/var/run/netns&lt;/code&gt; 挂载点来列出可用的命名空间，或者用 &lt;code&gt;ip&lt;/code&gt; 命令。&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ ls /var/run/netns&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ns1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ ip netns&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ns1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;默认的，Linux 将所有进程分配给 root 网络命名空间来提供外部访问，如图 2。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/a-guide-to-the-kubernetes-networking-model/images/root-namespace.png&quot; alt=&quot;Figure 2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;图2. Root 网络命名空间&lt;/p&gt;
&lt;p&gt;对于 Docker 的结构来说，一个 Pod 是一组共享同一网络命名空间的 Docker 容器。同一 Pod 中的容器拥有网络命名空间分配给 Pod 的相同的 IP 地址和端口范围，并且可以通过 localhost 找到各自，因为他们在同一命名空间内。我们为虚拟机中的每一个 Pod 创建网络命名空间。在 Docker 的实现中，一个 &quot;Pod 容器&quot; 打开网络命名空间，然后用户指定的 &quot;app containers&quot; 通过 Docker 的 –net=container: 功能加入这个网络命名空间。图 3 展示了多个容器 (&lt;code&gt;ctr*&lt;/code&gt;) 组成的 Pod 在共享的命名空间的情景。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/a-guide-to-the-kubernetes-networking-model/images/pod-namespace.png&quot; alt=&quot;Figure 3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;图3. 每个 Pod 一个网络命名空间&lt;/p&gt;
&lt;p&gt;同一个 Pod 中的应用程序同样拥有共享卷的访问权，根据 Pod 的定义, 共享卷可挂载到每个应用程序的文件系统。&lt;/p&gt;
&lt;h2 id=&quot;4-pod-dao-pod-wang-luo&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#4-pod-dao-pod-wang-luo&quot; aria-label=&quot;Anchor link for: 4-pod-dao-pod-wang-luo&quot;&gt;4 Pod 到 Pod 网络&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在 Kubernetes 中，每个 Pod 都拥有真正的 IP 地址而且可以通过这个 IP 地址与别的 Pod 通信。当前需要理解 Kubernetes 是如何使 Pod 到 Pod 通信通过真正的 IP，无论 Pod 是在同一台物理 Node 上还是集群中的不同 Node 上。我们从假设 Pod 在相同的机器上开始讨论，避免 Node 之间通信在内部网络之外的复杂情况。&lt;/p&gt;
&lt;p&gt;对于 Pod 来说，它在其命名空间中尝试去和在同一 Node 上不同网络命名空间通信。幸运的，命名空间可以通过 Linux &lt;a rel=&quot;external&quot; href=&quot;http://man7.org/linux/man-pages/man4/veth.4.html&quot;&gt;Virtual Ethernet Device&lt;/a&gt;，或通过由两个虚拟接口组成的可以扩展到多个网络命名空间的 &lt;code&gt;veth pair&lt;/code&gt; 连接。为了连接 Pod 的命名空间，我们可以把 veth pair 的一端连接到 root 网络命名空间，另一端连接到 Pod 的网络命名空间。Veth pair 像跳线一样工作，连接两端使流量可以流通。这一步可以重复直到其数目和机器上的 Pods 一样多。图 4 展示了 veth pair 连接了所有 Pod 到 VM 的 root 命名空间的情况。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/a-guide-to-the-kubernetes-networking-model/images/pod-veth-pairs.png&quot; alt=&quot;Figure 4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;图4. 每个 Pod 均有 veth pair&lt;/p&gt;
&lt;p&gt;现在，我们建立了有独立网络命名空间的 Pod，让他们相信他们拥有他们自己的以太网设备和 IP 地址，然后他们连接到了 Node 的 root 命名空间上。现在，我们想让 Pod 通过 root 命名空间和彼此通信，为此我们使用网桥。&lt;/p&gt;
&lt;p&gt;Linux 以太网网桥是一个用来连接两个或以上网段的虚拟的 2 层网络设备，透明地工作在两个网络上使其相连。网桥维护一个来源和目标的转发表，通过检测经过它的数据包的目的地来决定它是否将数据包传递给别的连接到桥上的网段。网桥码通过查找网络中以太网设备唯一的 MAC 地址来决定了是否桥接数据或将其丢弃。&lt;/p&gt;
&lt;p&gt;网桥实现了 ARP 协议来发现链路层 MAC 地址关联的 IP 地址的。当数据帧被网桥接收时，网桥向所有连接的设备 (除了原发送者) 广播帧，响应的设备被存入查询表。之后带有相同 IP 地址的流量将会使用查询表来查找转发的正确 MAC 地址。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/a-guide-to-the-kubernetes-networking-model/images/pods-connected-by-bridge.png&quot; alt=&quot;Figure 5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;图5. 通过网桥连接命名空间&lt;/p&gt;
&lt;h3 id=&quot;4-1-bao-de-sheng-ming-zhou-qi-tong-yi-node-shang-pod-dao-pod&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#4-1-bao-de-sheng-ming-zhou-qi-tong-yi-node-shang-pod-dao-pod&quot; aria-label=&quot;Anchor link for: 4-1-bao-de-sheng-ming-zhou-qi-tong-yi-node-shang-pod-dao-pod&quot;&gt;4.1 包的生命周期：同一 Node 上 Pod 到 Pod&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;已有将 Pod 隔离到其网络堆栈的网络名称空间，将每个命名空间连接到 root 命名空间的虚拟以太网设备，和将命名空间连接在一起的网桥，我们终于准备好在同一 Node 上的 Pod 之间发送流量了。图 6 演示了这个过程。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/a-guide-to-the-kubernetes-networking-model/images/pod-to-pod-same-node.gif&quot; alt=&quot;Figure 6&quot; /&gt;&lt;/p&gt;
&lt;p&gt;图6. 包在同一个 Node 上 Pod 之间传递&lt;/p&gt;
&lt;p&gt;在图 6 中，容器 1 将数据包发送到其自己的以太网设备 &lt;code&gt;eth0&lt;/code&gt;，该设备作为容器的默认设备。对于 Pod1，&lt;code&gt;eth0&lt;/code&gt; 通过虚拟以太网设备连接到 root 命名空间 &lt;code&gt;veth0&lt;/code&gt; (1)。配置网桥 &lt;code&gt;cbr0&lt;/code&gt; 连接到网段 &lt;code&gt;veth0&lt;/code&gt;。数据包到达网桥后，网桥使用 ARP 协议将其解析到其正确的目标网段 &lt;code&gt;veth1&lt;/code&gt; (3)。当数据包到达虚拟设备 &lt;code&gt;veth1&lt;/code&gt; 时，它将直接被转发到 Pod2 的命名空间和该命名空间中的 &lt;code&gt;eth0&lt;/code&gt; 设备 (4)。在整个流量流中，每个 Pod 仅与 &lt;code&gt;localhost&lt;/code&gt; 上的 &lt;code&gt;eth0&lt;/code&gt; 通信，并且流量被路由到正确的 Pod。对于这个网络的使用，我们的经验是它符合开发人员期望的默认行为。&lt;/p&gt;
&lt;p&gt;Kubernetes 的网络模型要求 Pod 必须通过其在跨 Node 中的 IP 地址才能访问。也就是说，一个 Pod 的 IP 地址始终对网络中的其他 Pod 可见，并且每个 Pod 所见的自己的 IP 地址都与其他 Pod 所见一致。现在，我们转向在不同 Node 上的 Pod 之间路由流量的问题。&lt;/p&gt;
&lt;h3 id=&quot;4-2-bao-de-sheng-ming-zhou-qi-bu-tong-node-shang-pod-dao-pod&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#4-2-bao-de-sheng-ming-zhou-qi-bu-tong-node-shang-pod-dao-pod&quot; aria-label=&quot;Anchor link for: 4-2-bao-de-sheng-ming-zhou-qi-bu-tong-node-shang-pod-dao-pod&quot;&gt;4.2 包的生命周期：不同 Node 上 Pod 到 Pod&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在确定了如何在同一 Node 上的 Pod 之间路由数据包之后，我们转向在不同 Node 上的 Pod 之间路由流量。Kubernetes 网络模型要求 Pod IP 在整个网络上可达，但是它没有指定必须如何完成。实际上，这是基于特定于网络的，但是已有一些模式被建立起来以简化此过程。&lt;/p&gt;
&lt;p&gt;通常，群集中的每个节点都分配有一个 CIDR 块，该块指定了该 Node 上运行的 Pod 可用的 IP 地址。一旦发往 CIDR 块的流量到达 Node，则 Node 有责任将流量转发到正确的 Pod。图 7 说明了两个 Node 之间的流量流，假设网络可以将 CIDR 块中的流量路由到正确的 Node。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/a-guide-to-the-kubernetes-networking-model/images/pod-to-pod-different-nodes.gif&quot; alt=&quot;Figure 7&quot; /&gt;&lt;/p&gt;
&lt;p&gt;图7. 包在不同 Node 上的 Pod 之间传递&lt;/p&gt;
&lt;p&gt;图 7 从与图 6 相同的请求开始，除了目标 Pod (以绿色突出显示) 与源 Pod (以蓝色突出显示) 位于不同的 Node 上。数据包首先通过 Pod1 的以太网设备发送，该设备与 root 命名空间中的虚拟以太网设备配对 (1)。数据包最终到达 root 名称空间的网桥 (2)。ARP 将在网桥上失败，因为没有任何匹配数据包 MAC 地址的设备连接到网桥。网桥在失败时会将数据包送出默认路由 - root 命名空间 &lt;code&gt;eth0&lt;/code&gt; 设备。此时，路由离开节点并进入网络 (3)。现在我们假设网络可以根据分配给 Node 的 CIDR 块将数据包路由到正确的 Node (4)。数据包进入目标 Node 的 root 命名空间 (VM 2 上的 &lt;code&gt;eth0&lt;/code&gt;)，然后通过网桥路由到正确的虚拟以太网设备 (5)。最后，通过流经 Pod4 命名空间中的虚拟以太网设备对来完成路由 (6)。一般而言，每个 Node 都知道如何将数据包传递到其中运行的 Pod。数据包到达目标 Node 后，数据包的流动方式与在同一 Node 上的 Pod 之间路由流量的方式相同。&lt;/p&gt;
&lt;p&gt;我们省略了一步，即如何配置网络以将 Pod IP 的流量路由到负责这些 IP 的正确的 Node。这是特定于网络的，不过通过研究相关特例也可以提供其所涉及问题的一些见解。例如，对于 AWS，Amazon 为 Kubernetes 维护了一个容器网络插件，该插件通过 &lt;a rel=&quot;external&quot; href=&quot;https://github.com/aws/amazon-vpc-cni-k8s&quot;&gt;容器网络接口 (CNI) 插件&lt;/a&gt; 使在 Amazon VPC 环境中的 Node 到 Node 网络成为可能。&lt;/p&gt;
&lt;p&gt;容器网络接口 (CNI) 提供了用于将容器连接到外部网络的通用 API。作为开发人员，我们想知道 Pod 可以使用 IP 地址与网络通信，并且我们希望此操作的机制透明。由 AWS 开发的 CNI 插件试图满足这些需求，同时通过 AWS 提供的现有 VPC，IAM 和安全组功能提供安全且可管理的环境。解决方案是使用弹性网络接口。&lt;/p&gt;
&lt;p&gt;在 EC2 中，每个实例都绑定到一个弹性网络接口 (ENI)，并且所有 ENI 都连接在 VPC 内 - ENI 可以相互访问，而无需付出额外的代价。默认情况下，每个 EC2 实例都部署有一个 ENI，但是您可以随意创建多个 ENI 并将它们部署到您认为合适的 EC2 实例中。用于 Kubernetes 的 AWS CNI 插件利用这种灵活性，为部署到 Node 的每一个 Pod 创建一个新的 ENI。由于 VPC 中的 ENI 已经连接到了现有的 AWS 基础设施，因此，每个在 VPC 中 Pod 的 IP 地址都是本地可寻址的。将 CNI 插件部署到群集后，每个 Node (EC2 实例) 都会创建多个弹性网络接口，并为这些实例分配 IP 地址，从而为每个 Node 形成一个 CIDR 块。部署 Pod 时，作为 DaemonSet 部署到 Kubernetes 集群的小型二进制文件会收到所有的来自 Node 本地 kubelet 进程的将 Pod 添加到网络的请求。该二进制文件从 Node 的可用 ENI 池中选择一个可用 IP 地址，并通过在 Linux 内核中连接虚拟以太网设备和网桥，将其分配给 Pod，如在同一节点内将 Pod 联网时所述。有了这个，Pod 流量就可以在集群中跨 Node 进行路由。&lt;/p&gt;
&lt;h2 id=&quot;5-pod-dao-service-wang-luo&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#5-pod-dao-service-wang-luo&quot; aria-label=&quot;Anchor link for: 5-pod-dao-service-wang-luo&quot;&gt;5 Pod 到 Service 网络&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;我们已经展示了如何在 Pod 及其关联的 IP 地址之间路由流量。这非常有效，直到我们需要应对变化。Pod IP 地址不是持久性的，并且会出现和消失，以应对规模扩大和缩小，应用程序的崩溃或节点重新启动。每一个这些事件都可以使 Pod IP 地址更改而不会发出任何警告。内置在 Kubernetes 的 Service 用以解决此问题。&lt;/p&gt;
&lt;p&gt;Kubernetes 的 Service 管理一组 Pod 的状态，使您可以跟踪随时动态变化的一组 Pod IP 地址。Service 充当 Pod 的抽象，并为一组 Pod IP 地址分配一个虚拟 IP 地址。寻址到 Service 的虚拟 IP 的任何流量都将被路由到与虚拟 IP 关联的 Pod 集。这允许与 Service 关联的 Pod 集随时更改 - 客户端只需要了解 Service 的不变的虚拟 IP。&lt;/p&gt;
&lt;p&gt;创建新的 Kubernetes Service 时，将为您创建一个新的虚拟 IP (也称为群集 IP)。在群集中的任何位置，寻址到虚拟 IP 的流量将负载均衡到与该 Service 关联的一组后端 Pod。实际上，Kubernetes 会自动创建并维护一个集群内的分布式负载均衡器，该负载均衡器会将流量分发到与 Service 相关联的健康 Pod。让我们仔细看看它是如何工作的。&lt;/p&gt;
&lt;h3 id=&quot;5-1-netfilter-he-iptables&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#5-1-netfilter-he-iptables&quot; aria-label=&quot;Anchor link for: 5-1-netfilter-he-iptables&quot;&gt;5.1 netfilter 和 iptables&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;为了在群集内执行负载平衡，Kubernetes 依赖于 Linux 内置的网络框架 &lt;code&gt;netfilter&lt;/code&gt;。Netfilter 是一个 Linux 提供的网络框架，允许通过定制的 handler 来实现一系列网络相关的操作。Netfilter 提供了用于数据包过滤，网络地址转换和端口映射的各种功能和操作，这些功能和操作满足了网络中数据包重定向所需，并提供了禁止数据包到达计算机网络内敏感位置的功能。&lt;/p&gt;
&lt;p&gt;Iptables 是一个用户空间程序，它提供一个基于表的系统，用于定义使用 netfilter 框架操作和转换数据包的规则。在 Kubernetes 中，iptables 规则由监视 Kubernetes API 服务器更改的 kube-proxy Controller 配置。当对 Service 或 Pod 的更改更新了 Service 的虚拟 IP 地址或 Pod 的 IP 地址时，将更新 iptables 规则以将针对 Service 的流量正确路由到后端 Pod。Iptables 规则监视发往 Service 的虚拟 IP 的流量，并在匹配项中从可用 Pod 的集合中选择一个随机的 Pod IP 地址，并且 iptables 规则将数据包的目标 IP 地址从服务的虚拟 IP 更改为 选中的 Pod 的 IP。随着 Pod 的规模扩大和缩小，iptables 规则集将更新以反映集群的变化状态。换句话说，iptables 在计算机上已通过将定向到 Service IP 的流量定向到实际 Pod 的 IP 完成了负载均衡，以。&lt;/p&gt;
&lt;p&gt;在反过来的路径上，IP 地址来自目标 Pod。在这种情况下，iptables 再次重写 IP 头，用 Servicce 的 IP 替换 Pod 的 IP，使 Pod 认为它一直在与 Service 的 IP 进行通信。&lt;/p&gt;
&lt;h3 id=&quot;5-2-ipvs&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#5-2-ipvs&quot; aria-label=&quot;Anchor link for: 5-2-ipvs&quot;&gt;5.2 IPVS&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Kubernetes 的最新版本 (1.11) 包括集群负载均衡的第二个选项：IPVS。IPVS (IP 虚拟服务器) 也建立在 netfilter 之上，并作为 Linux 内核的一部分实现传输层负载均衡。IPVS 已集成到 LVS (Linux 虚拟服务器) 中，在主机上运行，并充当真实服务器集群前面的负载均衡器。IPVS 可以将基于 TCP 和 UDP 的服务的请求定向到真实服务器，并使由多台服务器组成的服务表现为在单个 IP 地址上的虚拟服务。这使得 IPVS 非常适合 Kubernetes Services。&lt;/p&gt;
&lt;p&gt;声明 Kubernetes Service 时，可以指定是否要使用 iptables 或 IPVS 完成集群负载均衡。IPVS 专为负载均衡而设计，并使用更有效的数据结构 (哈希表)，与 iptables 相比，几乎可以无限扩展。创建使用 IPVS 负载均衡的 Service 时，会发生三件事：在 Node 上创建一个虚拟 IPVS 接口，将 Service 的 IP 地址绑定到该虚拟 IPVS 接口，并为每个 Service IP 地址创建 IPVS 服务器。&lt;/p&gt;
&lt;p&gt;将来，IPVS 有望成为群集内负载均衡的默认方法。这个更改仅影响群集内负载均衡，在本指南的其余部分中，您可以使用 IPVS 安全地将 iptables 替换为群集内负载平衡，而不会影响其余的讨论。现在，让我们看一下通过群集内负载平衡 Service 的数据包的生命周期。&lt;/p&gt;
&lt;h3 id=&quot;5-3-bao-de-sheng-ming-zhou-qi-pod-dao-service&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#5-3-bao-de-sheng-ming-zhou-qi-pod-dao-service&quot; aria-label=&quot;Anchor link for: 5-3-bao-de-sheng-ming-zhou-qi-pod-dao-service&quot;&gt;5.3 包的生命周期：Pod 到 Service&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/a-guide-to-the-kubernetes-networking-model/images/pod-to-service.gif&quot; alt=&quot;Figure 8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;图8. 数据包在 Pod 和 Service 间移动&lt;/p&gt;
&lt;p&gt;在 Pod 和 Service 之间路由数据包时，旅程以与以前相同的方式开始。数据包首先通过附加到 Pod 网络名称空间 (1) 的 eth0 接口离开 Pod。然后，它通过虚拟以太网设备到达网桥 (2) 。在网桥上运行的 ARP 协议不了解服务，因此它通过默认路由 eth0 (3) 传送数据包。在这里，发生了一些不同的事情。在被 eth0 接受之前，该数据包已通过 iptables 进行过滤。收到数据包后，iptables 会使用 kube-proxy 安装在节点上的规则来响应 Service 或 Pod 事件，将数据包的目标从 Service IP 重写到特定的 Pod IP (4) 。现在，该数据包将到达 Pod 4，而不是服务的虚拟 IP。iptables 充分利用了 Linux 内核的 conntrack 实用程序，以记住做出的 Pod 选择，以便将来的流量被路由到相同的 Pod (除非有任何扩展事件) 。本质上，iptables 直接在节点上完成了集群负载平衡。然后，使用我们已经检查过的 Pod 到 Pod 路由，流量流向 Pod (5)。&lt;/p&gt;
&lt;h3 id=&quot;5-4-bao-de-sheng-ming-zhou-qi-service-dao-pod&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#5-4-bao-de-sheng-ming-zhou-qi-service-dao-pod&quot; aria-label=&quot;Anchor link for: 5-4-bao-de-sheng-ming-zhou-qi-service-dao-pod&quot;&gt;5.4 包的生命周期：Service 到 Pod&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/a-guide-to-the-kubernetes-networking-model/images/service-to-pod.gif&quot; alt=&quot;Figure 9&quot; /&gt;&lt;/p&gt;
&lt;p&gt;图9. 包在 Service 和 Pod 之间移动&lt;/p&gt;
&lt;p&gt;接收到此数据包的 Pod 将做出响应，将源 IP 标识为自己的 IP，将目标 IP 标识为最初发送该数据包的 Pod (1) 。进入节点后，数据包流经 iptables，后者使用 conntrack 记住其先前所做的选择，并将数据包的源重写为服务的 IP，而不是 Pod 的 IP (2) 。数据包从这里流过网桥，到达与 Pod 的命名空间配对的虚拟以太网设备 (3) ，再到我们之前所见的 Pod 的以太网设备 (4) 。&lt;/p&gt;
&lt;h3 id=&quot;5-5-shi-yong-dns&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#5-5-shi-yong-dns&quot; aria-label=&quot;Anchor link for: 5-5-shi-yong-dns&quot;&gt;5.5 使用 DNS&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Kubernetes 可以选择使用 DNS，以避免必须将服务的群集 IP 地址硬编码到您的应用程序中。Kubernetes DNS 作为在群集上计划的常规 Kubernetes 服务运行。它配置在每个节点上运行的 kubelet，以便容器使用 DNS 服务的 IP 来解析 DNS 名称。为群集中定义的每个服务 (包括 DNS 服务器本身) 分配一个 DNS 名称。DNS 记录根据您的需要将 DNS 名称解析为服务的群集 IP 或 POD 的 IP。SRV 记录用于指定运行服务的特定命名端口。&lt;/p&gt;
&lt;p&gt;一个 DNS Pod 由三个独立的容器组成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;kubedns&lt;/strong&gt;: 监视 Kubernetes 主服务器的服务和端点更改，并维护内存查找结构以服务 DNS 请求。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;dnsmasq&lt;/strong&gt;: 添加 DNS 缓存以提高性能。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sidecar&lt;/strong&gt;: 提供单个运行状况检查端点，以执行 dnsmasq 和 kubedns 的运行状况检查。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;DNS Pod 本身作为 Kubernetes 服务公开，具有静态群集 IP，该 IP 在启动时传递给每个正在运行的容器，以便每个容器都可以解析 DNS 条目。DNS 条目通过 kubedns 系统解析，该系统在内存中维护 DNS 表示形式。etcd 是用于群集状态的后端存储系统，而 kubedns 使用一个库，该库在必要时将 etcd 密钥值存储转换为 DNS 条目，以重建内存 DNS 查找结构的状态。&lt;/p&gt;
&lt;p&gt;CoreDNS 的工作方式与 kubedns 相似，但其使用的插件体系结构使其更加灵活。从 Kubernetes 1.11 开始，CoreDNS 是 Kubernetes 的默认 DNS 实现。&lt;/p&gt;
&lt;h2 id=&quot;6-cong-internet-dao-service-de-wang-luo&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#6-cong-internet-dao-service-de-wang-luo&quot; aria-label=&quot;Anchor link for: 6-cong-internet-dao-service-de-wang-luo&quot;&gt;6 从 Internet 到 Service 的网络&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;到目前为止，我们已经研究了如何在 Kubernetes 集群中路由流量。这一切都很好，但是不幸的是，将您的应用程序与外界隔离将无助于实现任何销售目标——在某个时候，您将需要向外部流量公开您的服务。这种需求突出了两个相关的问题： (1) 将来自 Kubernetes 服务的流量发送到 Internet (Egress) ，以及 (2) 将来自 Internet 的流量发送到您的 Kubernetes Service (Ingress) 。本节依次阐述这些问题。&lt;/p&gt;
&lt;h3 id=&quot;6-1-egress-jiang-liu-liang-lu-you-dao-internet&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#6-1-egress-jiang-liu-liang-lu-you-dao-internet&quot; aria-label=&quot;Anchor link for: 6-1-egress-jiang-liu-liang-lu-you-dao-internet&quot;&gt;6.1 Egress — 将流量路由到 Internet&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;从节点到公共 Internet 的流量路由是特定于网络的，并且实际上取决于网络配置为发布流量的方式。为了使本节更具体，我将使用 AWS VPC 讨论任何特定细节。&lt;/p&gt;
&lt;p&gt;在 AWS 中，Kubernetes 集群在 VPC 内运行，其中为每个节点分配了一个私有 IP 地址，该地址可从 Kubernetes 集群内访问。要使流量可以从群集外部访问，请将 Internet 网关连接到 VPC。Internet 网关有两个目的：在 VPC 路由表中为可路由到 Internet 的流量提供目标，并为已分配了公共 IP 地址的任何实例执行网络地址转换 (NAT) 。NAT 转换负责将群集专用的节点内部 IP 地址更改为公用 Internet 上可用的外部 IP 地址。&lt;/p&gt;
&lt;p&gt;有了 Internet 网关后，VM 可以自由地将流量路由到 Internet。不幸的是，这里有一个小问题。Pod 拥有自己的 IP 地址，该 IP 地址与托管 Pod 的节点的 IP 地址不同，并且 Internet 网关上的 NAT 转换仅适用于 VM IP 地址，因为它不了解哪些 Pod 在哪些 VM 上运行——网关不感知容器。让我们看看 Kubernetes 如何使用 iptables (再次) 解决这个问题。&lt;/p&gt;
&lt;h4 id=&quot;6-1-1-bao-de-sheng-ming-zhou-qi-node-dao-internet&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#6-1-1-bao-de-sheng-ming-zhou-qi-node-dao-internet&quot; aria-label=&quot;Anchor link for: 6-1-1-bao-de-sheng-ming-zhou-qi-node-dao-internet&quot;&gt;6.1.1 包的生命周期：Node 到 Internet&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/a-guide-to-the-kubernetes-networking-model/images/pod-to-internet.gif&quot; alt=&quot;Figure 10&quot; /&gt;&lt;/p&gt;
&lt;p&gt;图10. 将数据包从 Pod 路由到 Internet&lt;/p&gt;
&lt;p&gt;在下图中，数据包起源于 Pod 的名称空间 (1) ，并经过与根名称空间 (2) 连接的 veth 对。一旦进入根名称空间，由于数据包上的 IP 与连接到网桥的任何网段都不匹配，数据包就会从网桥移动到默认设备。在到达根名称空间的以太网设备 (3) 之前，iptables 会处理数据包 (3) 。在这种情况下，数据包的源 IP 地址是 Pod，如果我们将源保留为 Pod，则 Internet 网关将拒绝它，因为网关 NAT 仅了解连接到 VM 的 IP 地址。解决方案是让 iptables 执行源 NAT (更改数据包源) ，以便数据包看起来是来自 VM 而不是 Pod。有了正确的源 IP，数据包现在可以离开 VM (4) 并到达 Internet 网关 (5) 。Internet 网关将执行另一个 NAT，将源 IP 从 VM 内部 IP 重写为外部 IP。最终，数据包将到达公共 Internet (6) 。在返回的过程中，数据包遵循相同的路径，并且所有源 IP 处理都被撤消，因此系统的每一层都接收其能够理解的 IP 地址：节点或 VM 级别的 VM 内部 IP，以及 Pod 命名空间内的 Pod IP。&lt;/p&gt;
&lt;h3 id=&quot;6-2-ingress-jiang-internet-liu-liang-lu-you-dao-kubernetes&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#6-2-ingress-jiang-internet-liu-liang-lu-you-dao-kubernetes&quot; aria-label=&quot;Anchor link for: 6-2-ingress-jiang-internet-liu-liang-lu-you-dao-kubernetes&quot;&gt;6.2 Ingress — 将 Internet 流量路由到 Kubernetes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Ingress——将流量引入群集——是一个非常棘手的问题。同样，这是特定于您正在运行的网络的，但是通常，Ingress 分为两个可在网络堆栈的不同部分上运行的解决方案： (1) Service LoadBalancer 和 (2) Ingress 控制器。&lt;/p&gt;
&lt;h4 id=&quot;6-2-1-4ceng-ingress-loadbalancer&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#6-2-1-4ceng-ingress-loadbalancer&quot; aria-label=&quot;Anchor link for: 6-2-1-4ceng-ingress-loadbalancer&quot;&gt;6.2.1 4层 Ingress: LoadBalancer&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;创建 Kubernetes 服务时，可以选择指定一个 &lt;code&gt;LoadBalancer&lt;/code&gt; 来配合它。云控制器提供了 &lt;code&gt;LoadBalancer&lt;/code&gt; 的实现，该控制器知道如何为您的服务创建负载均衡器。创建服务后，它将为负载均衡器通告 IP 地址。作为最终用户，您可以开始将流量定向到负载平衡器，以开始与服务进行通信。&lt;/p&gt;
&lt;p&gt;借助 AWS，负载均衡器可以知道其目标组中的节点，并将平衡群集中所有节点上的流量。一旦流量到达节点，先前在整个群集中为您的服务安装的 iptables 规则将确保流量到达您感兴趣的服务的 Pod。&lt;/p&gt;
&lt;h4 id=&quot;6-2-2-bao-de-sheng-ming-zhou-qi-loadbalancer-dao-service&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#6-2-2-bao-de-sheng-ming-zhou-qi-loadbalancer-dao-service&quot; aria-label=&quot;Anchor link for: 6-2-2-bao-de-sheng-ming-zhou-qi-loadbalancer-dao-service&quot;&gt;6.2.2 包的生命周期：LoadBalancer 到 Service&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/a-guide-to-the-kubernetes-networking-model/images/internet-to-service.gif&quot; alt=&quot;Figure 11&quot; /&gt;&lt;/p&gt;
&lt;p&gt;图11. 从 Internet 发送到 Service 的数据包&lt;/p&gt;
&lt;p&gt;让我们看看这在实践中是如何工作的。部署服务后，您正在使用的云提供商将为您创建一个新的负载均衡器 (1) 。因为负载平衡器不感知容器，所以一旦流量到达负载平衡器，它便会分布在组成群集的所有 VM 中 (2) 。每个 VM 上的 iptables 规则会将来自负载均衡器的传入流量定向到正确的 Pod (3) ——这些规则与创建服务期间制定的 IP 表规则相同，前面已经讨论过。从 Pod 到客户端的响应将返回 Pod 的 IP，但客户端需要具有负载均衡器的 IP 地址。如前所述，iptables 和 conntrack 用于在返回路径上正确重写 IP。&lt;/p&gt;
&lt;p&gt;下图显示了在承载 Pod 的三个 VM 之前的网络负载平衡器。传入流量 (1) 指向服务的负载平衡器。一旦负载均衡器收到数据包 (2) ，它就会随机选择一个 VM。在这种情况下，我们病理地选择了没有运行 Pod 的 VM：VM 2 (3) 。在这里，在 VM 上运行的 iptables 规则将使用通过 kube-proxy 安装到群集中的内部负载平衡规则将数据包定向到正确的 Pod。iptables 执行正确的 NAT，并将数据包转发到正确的 Pod (4) 。&lt;/p&gt;
&lt;h4 id=&quot;6-2-3-7ceng-ingress-ingress-controller&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#6-2-3-7ceng-ingress-ingress-controller&quot; aria-label=&quot;Anchor link for: 6-2-3-7ceng-ingress-ingress-controller&quot;&gt;6.2.3 7层 Ingress: Ingress Controller&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;7 层网络 Ingress 在网络堆栈的 HTTP/HTTPS 协议范围内运行，并建立在服务之上。启用 Ingress 的第一步是使用 Kubernetes 中的 &lt;code&gt;NodePort&lt;/code&gt; 服务类型在服务上打开端口。如果将服务的 &lt;code&gt;type&lt;/code&gt; 字段设置为 &lt;code&gt;NodePort&lt;/code&gt;，则 Kubernetes 主服务器将在您指定的范围内分配一个端口，并且每个节点会将该端口 (每个节点上的相同端口号) 代理到您的服务中。也就是说，使用 iptables 规则，任何定向到该节点端口的流量都将转发到该服务。这种从 Service 到 Pod 的路由遵循了我们之前在讨论从 Service 到 Pod 的流量路由时已经讨论过的相同的内部集群负载平衡模式。&lt;/p&gt;
&lt;p&gt;要将节点的端口暴露给 Internet，请使用 &lt;code&gt;Ingress&lt;/code&gt; 对象。Ingress 是更高级别的 HTTP 负载平衡器，可将 HTTP 请求映射到 Kubernetes Services。Ingress 方法将有所不同，具体取决于 Kubernetes 云提供商控制器如何实现它。HTTP 负载平衡器 (如 4 层网络负载平衡器) 仅了解 Node IP (而非 Pod IP) ，因此流量路由同样利用 kube-proxy 在每个节点上安装的 iptables 规则提供的内部负载平衡。&lt;/p&gt;
&lt;p&gt;在 AWS 环境中，ALB Ingress Controller 使用 Amazon 的 7 层应用程序负载平衡器 (ALB) 提供 Kubernetes Ingress。下图详细说明了此控制器创建的 AWS 组件。它还演示了 Ingress 流量从 ALB 到 Kubernetes 集群的路由。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/a-guide-to-the-kubernetes-networking-model/images/ingress-controller-design.png&quot; alt=&quot;Figure 12&quot; /&gt;&lt;/p&gt;
&lt;p&gt;图12. Ingress Controller 的设计&lt;/p&gt;
&lt;p&gt;创建后， (1) Ingress Controller 将监视来自 Kubernetes API 服务器的 Ingress 事件。当发现满足其要求的 Ingress 资源时，它将开始创建 AWS 资源。AWS 将应用程序负载平衡器 (ALB)  (2) 用于 Ingress 资源。负载均衡器与用于将请求路由到一个或多个已注册节点的目标组 (Target Groups) 配合使用。(3) 在 AWS 中为 Ingress 资源描述的每个唯一的 Kubernetes 服务创建目标组。(4) 侦听器 (Listener) 是 ALB 进程，它使用您配置的协议和端口检查连接请求。Ingress 控制器为 Ingress 资源注释中详细说明的每个端口创建侦听器。最后，为 Ingress 资源中指定的每个路径创建目标组规则。这样可以确保将到特定路径的流量路由到正确的 Kubernetes 服务 (5) 。&lt;/p&gt;
&lt;h4 id=&quot;6-2-4-bao-de-sheng-ming-zhou-qi-ingress-dao-service&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#6-2-4-bao-de-sheng-ming-zhou-qi-ingress-dao-service&quot; aria-label=&quot;Anchor link for: 6-2-4-bao-de-sheng-ming-zhou-qi-ingress-dao-service&quot;&gt;6.2.4 包的生命周期：Ingress 到 Service&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://memwey.github.io/posts/a-guide-to-the-kubernetes-networking-model/images/ingress-to-service.gif&quot; alt=&quot;Figure 13&quot; /&gt;&lt;/p&gt;
&lt;p&gt;图13. 从 Ingress 发送到 Service 的数据包&lt;/p&gt;
&lt;p&gt;流经 Ingress 的数据包的生命周期与 LoadBalancer 的非常相似。主要区别在于，Ingress 知道 URL 的路径 (允许并可以根据服务的路径将流量路由到服务) ，并且 Ingress 和 Node 之间的初始连接是通过为每个服务在 Node 上暴露的端口进行的。&lt;/p&gt;
&lt;p&gt;让我们看看这在实践中是如何工作的。部署服务后，您正在使用的云提供商将为您创建一个新的 Ingress 负载均衡器 (1) 。由于负载平衡器不感知容器，因此，一旦流量到达负载平衡器，它将通过为您的服务通告的端口在组成您的群集 (2) 的所有 VM 中进行分配。如前所述，每个 VM 上的 iptables 规则会将来自负载均衡器的传入流量定向到正确的 Pod (3) 。从 Pod 到客户端的响应将返回 Pod 的 IP，但客户端需要具有负载均衡器的 IP 地址。如前所述，iptables 和 conntrack 用于在返回路径上正确重写 IP。&lt;/p&gt;
&lt;p&gt;7 层负载平衡器的一个好处是它们可以识别 HTTP，因此他们知道 URL 和路径。这使您可以按 URL 路径细分服务流量。它们通常还会在 HTTP 请求的 &lt;code&gt;X-Forwarded-For&lt;/code&gt; 标头中提供原始客户端的 IP 地址。&lt;/p&gt;
&lt;h2 id=&quot;7-zong-jie&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#7-zong-jie&quot; aria-label=&quot;Anchor link for: 7-zong-jie&quot;&gt;7 总结&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;本指南为理解 Kubernetes 网络模型及其如何实现常见的网络任务提供了基础。网络领域既广且深，不可能在这里涵盖所有内容。本指南应为您提供一个起点，以深入研究您感兴趣并希望了解更多的主题。每当您遇到困难时，请利用 Kubernetes 文档和 Kubernetes 社区来帮助您找到方向。&lt;/p&gt;
&lt;h3 id=&quot;8-shu-yu-biao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#8-shu-yu-biao&quot; aria-label=&quot;Anchor link for: 8-shu-yu-biao&quot;&gt;8 术语表&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Kubernetes 依靠几种现有技术来构建可运行的集群。全面探索每种技术不在本指南的讨论范围内，但是本节将对每种技术进行足够详细的介绍，以供讨论时参考。您可以随意浏览本节，完全跳过本节，或者在感到困惑或需要复习时根据需要参考。&lt;/p&gt;
&lt;h3 id=&quot;2ceng-wang-luo-layer-2-networking&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#2ceng-wang-luo-layer-2-networking&quot; aria-label=&quot;Anchor link for: 2ceng-wang-luo-layer-2-networking&quot;&gt;2层网络 (Layer 2 Networking)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;第 2 层是提供节点到节点数据传输的数据链路层。它定义了在两个物理连接的设备之间建立和终止连接的协议，以及它们之间的流量控制协议。&lt;/p&gt;
&lt;h3 id=&quot;4ceng-wang-luo-layer-4-networking&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#4ceng-wang-luo-layer-4-networking&quot; aria-label=&quot;Anchor link for: 4ceng-wang-luo-layer-4-networking&quot;&gt;4层网络 (Layer 4 Networking)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;传输层通过流量控制来控制给定链接的可靠性。在 TCP/IP 中，该层指的是用于在不可靠网络上交换数据的 TCP 协议。&lt;/p&gt;
&lt;h3 id=&quot;7ceng-wang-luo-layer-7-networking&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#7ceng-wang-luo-layer-7-networking&quot; aria-label=&quot;Anchor link for: 7ceng-wang-luo-layer-7-networking&quot;&gt;7层网络 (Layer 7 Networking)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;应用层是离最终用户最近的层，这意味着应用层和用户都直接与软件应用程序交互。该层与实现通信组件的软件应用程序交互。通常，7 层网络指的是 HTTP。&lt;/p&gt;
&lt;h3 id=&quot;nat-wang-luo-di-zhi-zhuan-huan-network-address-translation&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#nat-wang-luo-di-zhi-zhuan-huan-network-address-translation&quot; aria-label=&quot;Anchor link for: nat-wang-luo-di-zhi-zhuan-huan-network-address-translation&quot;&gt;NAT — 网络地址转换 (Network Address Translation)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;NAT 或网络地址转换是将一个地址空间 IP 级别地重新映射到另一个地址空间。该映射通过在数据包在流量路由设备中传输时修改其 IP 头中的网络地址信息来实现。&lt;/p&gt;
&lt;p&gt;基本的 NAT 是从一个 IP 地址到另一个 IP 地址的简单映射。更常见的是，NAT 用于将多个私有 IP 地址映射到一个公共暴露的 IP 地址。通常，本地网络使用私有 IP 地址空间，该网络上的路由器在该空间中被赋予一个私有地址。然后，该路由器通过一个公共 IP 地址连接到 Internet。当流量从本地网络传递到 Internet 时，每个数据包的源地址从私有地址转换为公共地址，使其看起来好像请求直接来自路由器。路由器维护连接跟踪，以将回复转发到本地网络上正确的私有 IP。&lt;/p&gt;
&lt;p&gt;NAT 提供了一个额外的好处，即允许大型私有网络使用单个公共 IP 地址连接到 Internet，从而节省了公共使用的 IP 地址数量。&lt;/p&gt;
&lt;h3 id=&quot;snat-yuan-wang-luo-di-zhi-zhuan-huan-source-network-address-translation&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#snat-yuan-wang-luo-di-zhi-zhuan-huan-source-network-address-translation&quot; aria-label=&quot;Anchor link for: snat-yuan-wang-luo-di-zhi-zhuan-huan-source-network-address-translation&quot;&gt;SNAT — 源网络地址转换 (Source Network Address Translation)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;SNAT 仅指修改 IP 数据包源地址的 NAT 过程。这是上述 NAT 的典型行为。&lt;/p&gt;
&lt;h3 id=&quot;dnat-mu-de-wang-luo-di-zhi-zhuan-huan-destination-network-address-translation&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#dnat-mu-de-wang-luo-di-zhi-zhuan-huan-destination-network-address-translation&quot; aria-label=&quot;Anchor link for: dnat-mu-de-wang-luo-di-zhi-zhuan-huan-destination-network-address-translation&quot;&gt;DNAT — 目的网络地址转换 (Destination Network Address Translation)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;DNAT 指修改 IP 数据包目的地址的 NAT 过程。DNAT 用于将位于私有网络中的服务发布到可公开寻址的 IP 地址。&lt;/p&gt;
&lt;h3 id=&quot;wang-luo-ming-ming-kong-jian-network-namespace&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#wang-luo-ming-ming-kong-jian-network-namespace&quot; aria-label=&quot;Anchor link for: wang-luo-ming-ming-kong-jian-network-namespace&quot;&gt;网络命名空间 (Network Namespace)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;在网络中，每台机器 (真实的或虚拟的) 都有一个以太网设备 (我们称之为 eth0) 。所有进出机器的流量都与该设备相关联。实际上，Linux 将每个以太网设备与一个网络命名空间相关联——这是整个网络堆栈的逻辑副本，拥有自己的路由、防火墙规则和网络设备。最初，所有进程都从 init 进程共享相同的默认网络命名空间，称为根命名空间。默认情况下，进程从其父进程继承其网络命名空间，因此，如果您不做任何更改，所有网络流量都将通过为根网络命名空间指定的以太网设备流动。&lt;/p&gt;
&lt;h3 id=&quot;veth-xu-ni-yi-tai-wang-she-bei-dui-virtual-ethernet-device-pairs&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#veth-xu-ni-yi-tai-wang-she-bei-dui-virtual-ethernet-device-pairs&quot; aria-label=&quot;Anchor link for: veth-xu-ni-yi-tai-wang-she-bei-dui-virtual-ethernet-device-pairs&quot;&gt;veth — 虚拟以太网设备对 (Virtual Ethernet Device Pairs)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;计算机系统通常由一个或多个网络设备 (eth0，eth1 等) 组成，这些设备与负责将数据包放置到物理线路上的物理网络适配器相关联。Veth 设备是始终以互连对形式创建的虚拟网络设备。它们可以充当网络命名空间之间的隧道，以创建到另一个命名空间中物理网络设备的桥梁，但也可以用作独立的网络设备。您可以将 veth 设备视为设备之间的虚拟跳线——一端进去的东西会从另一端出来。&lt;/p&gt;
&lt;h3 id=&quot;bridge-wang-luo-wang-qiao-network-bridge&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#bridge-wang-luo-wang-qiao-network-bridge&quot; aria-label=&quot;Anchor link for: bridge-wang-luo-wang-qiao-network-bridge&quot;&gt;bridge — 网络网桥 (Network Bridge)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;网络网桥是一种从多个通信网络或网段创建单个聚合网络的设备。桥接将两个独立的网络连接起来，就好像它们是一个单一的网络一样。桥接使用内部数据结构来记录每个数据包发送到的位置，作为性能优化。&lt;/p&gt;
&lt;h3 id=&quot;cidr-wu-lei-bie-yu-jian-lu-you-classless-inter-domain-routing&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#cidr-wu-lei-bie-yu-jian-lu-you-classless-inter-domain-routing&quot; aria-label=&quot;Anchor link for: cidr-wu-lei-bie-yu-jian-lu-you-classless-inter-domain-routing&quot;&gt;CIDR — 无类别域间路由 (Classless Inter-Domain Routing)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;CIDR 是一种分配 IP 地址和执行 IP 路由的方法。使用 CIDR，IP 地址由两组组成：网络前缀 (标识整个网络或子网) 和主机标识符 (指定该网络或子网上主机的特定接口) 。CIDR 使用 CIDR 表示法表示 IP 地址，其中地址或路由前缀带有一个后缀，指示前缀的位数，例如 IPv4 的 192.0.2.0/24。IP 地址是 CIDR 块的一部分，如果地址的初始 n 位与 CIDR 前缀相同，则称其属于该 CIDR 块。&lt;/p&gt;
&lt;h3 id=&quot;cni-rong-qi-wang-luo-jie-kou-container-network-interface&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#cni-rong-qi-wang-luo-jie-kou-container-network-interface&quot; aria-label=&quot;Anchor link for: cni-rong-qi-wang-luo-jie-kou-container-network-interface&quot;&gt;CNI — 容器网络接口 (Container Network Interface)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;CNI (容器网络接口) 是云原生计算基金会 (Cloud Native Computing Foundation) 的一个项目，由一个规范和用于编写插件以在 Linux 容器中配置网络接口的库组成。CNI 只关心容器的网络连接性以及在容器被删除时移除已分配的资源。&lt;/p&gt;
&lt;h3 id=&quot;vip-xu-ni-ip-di-zhi-virtual-ip-address&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#vip-xu-ni-ip-di-zhi-virtual-ip-address&quot; aria-label=&quot;Anchor link for: vip-xu-ni-ip-di-zhi-virtual-ip-address&quot;&gt;VIP — 虚拟 IP 地址 (Virtual IP Address)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;虚拟 IP 地址 (VIP) 是一种软件定义的 IP 地址，不对应于实际的物理网络接口。&lt;/p&gt;
&lt;h3 id=&quot;netfilter-linux-de-bao-guo-lu-kuang-jia&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#netfilter-linux-de-bao-guo-lu-kuang-jia&quot; aria-label=&quot;Anchor link for: netfilter-linux-de-bao-guo-lu-kuang-jia&quot;&gt;netfilter — Linux 的包过滤框架&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;netfilter 是 Linux 中的数据包过滤框架。实现此框架的软件负责数据包过滤、网络地址转换 (NAT) 和其他数据包处理。&lt;/p&gt;
&lt;p&gt;netfilter、ip_tables、连接跟踪 (ip_conntrack, nf_conntrack) 和 NAT 子系统共同构成了该框架的主要部分。&lt;/p&gt;
&lt;h3 id=&quot;iptables-bao-chu-li-gong-ju&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#iptables-bao-chu-li-gong-ju&quot; aria-label=&quot;Anchor link for: iptables-bao-chu-li-gong-ju&quot;&gt;iptables — 包处理工具&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;iptables 是一个允许 Linux 系统管理员配置 netfilter 及其存储的链和规则的程序。IP 表中的每个规则都由多个分类器 (iptables 匹配) 和一个连接的动作 (iptables 目标) 组成。&lt;/p&gt;
&lt;h3 id=&quot;conntrack-lian-jie-gen-zong&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#conntrack-lian-jie-gen-zong&quot; aria-label=&quot;Anchor link for: conntrack-lian-jie-gen-zong&quot;&gt;conntrack — 连接跟踪&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;conntrack 是建立在 Netfilter 框架之上的用于处理连接跟踪的工具。连接跟踪允许内核跟踪所有逻辑网络连接或会话，并将每个连接或会话的数据包定向到正确的发送方或接收方。NAT 依赖此信息以相同的方式转换所有相关数据包，而 iptables 可以使用此信息充当有状态的防火墙。&lt;/p&gt;
&lt;h3 id=&quot;ipvs-ip-xu-ni-fu-wu-qi-ip-virtual-server&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ipvs-ip-xu-ni-fu-wu-qi-ip-virtual-server&quot; aria-label=&quot;Anchor link for: ipvs-ip-xu-ni-fu-wu-qi-ip-virtual-server&quot;&gt;IPVS — IP 虚拟服务器 (IP Virtual Server)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;IPVS 作为 Linux 内核的一部分实现传输层负载均衡。&lt;/p&gt;
&lt;p&gt;IPVS 是一个类似于 iptables 的工具。它基于 Linux 内核的 netfilter 钩子函数，但使用哈希表作为底层数据结构。这意味着，与 iptables 相比，IPVS 重定向流量的速度要快得多，在同步代理规则时性能要好得多，并提供更多的负载均衡算法。&lt;/p&gt;
&lt;h3 id=&quot;dns-yu-ming-xi-tong-the-domain-name-system&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#dns-yu-ming-xi-tong-the-domain-name-system&quot; aria-label=&quot;Anchor link for: dns-yu-ming-xi-tong-the-domain-name-system&quot;&gt;DNS — 域名系统 (The Domain Name System)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;域名系统 (DNS) 是一种用于将系统名称与 IP 地址关联的分散式命名系统。它将域名转换为数字 IP 地址，以便定位计算机服务。&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>Oh MySQL Emoji</title>
        <published>2019-10-14T11:12:33+08:00</published>
        <updated>2019-10-14T11:12:33+08:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/oh-mysql-emoji/"/>
        <id>https://memwey.github.io/posts/oh-mysql-emoji/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/oh-mysql-emoji/">&lt;p&gt;又是关于 &lt;code&gt;MySQL&lt;/code&gt; 的, 标题来自于 &amp;lt;轮到你了&amp;gt; 这部&lt;del&gt;烂尾&lt;/del&gt;日剧, 里面非常魔性的 Oh~my~Ju~lia~&lt;/p&gt;
&lt;p&gt;在MySQL里面如何保存 &lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Emoji&quot;&gt;Emoji&lt;/a&gt;, 这个问题搜一搜很容易找到答案, 设置 &lt;strong&gt;CHARSET&lt;/strong&gt; 为 &lt;code&gt;utf8mb4&lt;/code&gt;, 看来天下苦 &lt;code&gt;MySQL&lt;/code&gt; 久矣.&lt;/p&gt;
&lt;h2 id=&quot;ji-chu-zhi-shi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ji-chu-zhi-shi&quot; aria-label=&quot;Anchor link for: ji-chu-zhi-shi&quot;&gt;基础知识&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;计算机最早是美国人发明的, 那时的编码都是 &lt;code&gt;ASCII&lt;/code&gt; 码, 毕竟英文字符加上标点符号也就那么多嘛, 一个字节绰绰有余.&lt;/p&gt;
&lt;p&gt;但是随着计算机的发展, 计算机面向的国家和语言越来越多, 一个字节根本不够用了, 于是就有了很多面向特定语言的编码, 比如汉字的&lt;code&gt;GBK&lt;/code&gt;, &lt;code&gt;Big5&lt;/code&gt;, 韩文的 &lt;code&gt;EUC-KR&lt;/code&gt;, 日文的 &lt;code&gt;Shift_JIS&lt;/code&gt; 什么的. 总体思想是, 既然一个字节不够用, 那就多几个字节就是了嘛.&lt;/p&gt;
&lt;p&gt;但是问题又出现了, 同一个编码在不同语言中会有不同的含义,比如韩文编码 &lt;code&gt;EUC-KR&lt;/code&gt; 中 &lt;code&gt;한국어&lt;/code&gt; 的编码值正好对应着汉字编码 &lt;code&gt;GBK&lt;/code&gt; 中的 &lt;code&gt;茄惫绢&lt;/code&gt;. 还有那个著名的 &lt;code&gt;瓣B变巨肚&lt;/code&gt;, 也正是同样的原因. 这样仿佛就变成了巴别塔的故事. 人们虽然用着同样的二进制编码, 但是却表达着不同的意思; 就像人们虽然同样发出声带的震动, 但是却无法互相理解对方的话语.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/International_Organization_for_Standardization&quot;&gt;国际标准化组织&lt;/a&gt; 和 &lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Unicode_Consortium&quot;&gt;统一码联盟&lt;/a&gt; 意识到, 这样下去是不行的, 不如搞一个超大的字符集, 然后把人类所有字符都弄进去, 这样人类都可以用同一个标准了. 于是, 他们分别制定了 &lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Universal_Coded_Character_Set&quot;&gt;USC&lt;/a&gt; 和 &lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Unicode&quot;&gt;Unicode&lt;/a&gt;. 当然, 如果两个标准各搞各的, 那就和他们最初的想法背道而驰了, 所以目前这两个字符集在实际使用中是一致的. 一般还是把这个字符集叫 &lt;code&gt;Unicode&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Unicode&lt;/code&gt; 的范围上限是 &lt;code&gt;0x10FFFF&lt;/code&gt;, 换算成十进制就是 &lt;code&gt;1,114,111&lt;/code&gt; 这么多. 所以如果以后外星人的文字太多, 说不定这个范围就不够了.&lt;/p&gt;
&lt;p&gt;其实这里悄悄的偷换了一个概念, 之前在说编码, 现在变成了字符集了. 在很多之前提到的编码中, 这些编码既是字符集, 又是直接的编码. 而在 &lt;code&gt;Unicode&lt;/code&gt; 中, 字符集实际上是与编码分开的两个概念, 而对应的编码, 实际上就是我们经常见到的 &lt;code&gt;UTF&lt;/code&gt;. 其中 &lt;code&gt;UTF-8&lt;/code&gt; 是我们最常见的一种编码了.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;UTF-8&lt;/code&gt; 最大的特点, 就是它是一种变长的编码方式, 而且完全兼容 &lt;code&gt;ASCII&lt;/code&gt;码.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;UTF-8&lt;/code&gt; 的编码规则也很简单，只有二条:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;对于单字节的符号, 字节的第一位设为&lt;code&gt;0&lt;/code&gt;, 后面7位为这个符号的 Unicode 码. 对于 &lt;code&gt;0 - 127&lt;/code&gt; 位的字符，UTF-8 编码和 ASCII 码是完全相同的.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于 &lt;code&gt;n&lt;/code&gt; 字节的符号 &lt;code&gt;(n &amp;gt; 1)&lt;/code&gt; , 第一个字节的前 &lt;code&gt;n&lt;/code&gt; 位都设为 &lt;code&gt;1&lt;/code&gt;, 第 &lt;code&gt;n + 1&lt;/code&gt; 位设为 &lt;code&gt;0&lt;/code&gt;, 后面字节的前两位一律设为 &lt;code&gt;10&lt;/code&gt;. 剩下的没有提及的二进制位, 全部为这个符号的 Unicode 码.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Unicode符号范围&lt;/th&gt;&lt;th&gt;UTF-8编码方式&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;U+0000 - U+007F&lt;/td&gt;&lt;td&gt;0xxxxxxx&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;U+0080 - U+07FF&lt;/td&gt;&lt;td&gt;110xxxxx 10xxxxxx&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;U+0800 - U+FFFF&lt;/td&gt;&lt;td&gt;1110xxxx 10xxxxxx 10xxxxxx&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;U+010000 - U+10FFFF&lt;/td&gt;&lt;td&gt;11110xxx 10xxxxxx 10xxxxxx 10xxxxxx&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;在这里我们发现, &lt;code&gt;U+0000 - U+FFFF&lt;/code&gt; 这个&lt;strong&gt;平面&lt;/strong&gt;(Plane), 即&lt;strong&gt;基本多文种平面&lt;/strong&gt;, 简称 &lt;code&gt;BMP&lt;/code&gt;, 用 &lt;code&gt;UTF-8&lt;/code&gt; 进行编码, 只需要三个字节.&lt;/p&gt;
&lt;h2 id=&quot;lun-dao-ni-liao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#lun-dao-ni-liao&quot; aria-label=&quot;Anchor link for: lun-dao-ni-liao&quot;&gt;轮到你了&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;MySQL&lt;/code&gt; 的 &lt;strong&gt;CHARSET&lt;/strong&gt; 中, &lt;code&gt;utf8&lt;/code&gt; 支持存储 1 - 3字节的字符, 即对应 &lt;code&gt;Unicode&lt;/code&gt; 中 &lt;code&gt;BMP&lt;/code&gt; 的部分, 而 &lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Emoji&quot;&gt;Emoji&lt;/a&gt;, 则大多数在 &lt;code&gt;BMP&lt;/code&gt; 之外. 所以 &lt;code&gt;MySQL&lt;/code&gt; 中的 &lt;code&gt;utf8&lt;/code&gt; 是假的, 是化学的成分, 是加了特技的. 如果想储存真正的 &lt;code&gt;UTF-8&lt;/code&gt; 的内容, 就一定要使用 &lt;code&gt;utf8mb4&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;当然, 并不是所有的 &lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/Emoji&quot;&gt;Emoji&lt;/a&gt; 都在 &lt;code&gt;BMP&lt;/code&gt; 之外, 比如 ☺ 这个表情, 它的编码是 &lt;code&gt;U+263A&lt;/code&gt;, 还有 ☹️ , 编码是 &lt;code&gt;U+2639&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;当然, 也不是所有的汉字都被包含在了 &lt;code&gt;BMP&lt;/code&gt; 里面了, 在 &lt;a rel=&quot;external&quot; href=&quot;https://www.compart.com/en/unicode/block&quot;&gt;这里&lt;/a&gt; 可以看到字符是按照一定的规律划分为 &lt;code&gt;Block&lt;/code&gt; 放进来的, 在&lt;strong&gt;表意文字补充平面&lt;/strong&gt;也就是 &lt;code&gt;U+20000 - U+2FFFF&lt;/code&gt; 这个&lt;strong&gt;平面&lt;/strong&gt;(Plane), 还是有很多 &lt;code&gt;CJK&lt;/code&gt; 命名的 &lt;code&gt;Block&lt;/code&gt; 的.&lt;/p&gt;
&lt;h2 id=&quot;zai-lai-yi-ping&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#zai-lai-yi-ping&quot; aria-label=&quot;Anchor link for: zai-lai-yi-ping&quot;&gt;再来一瓶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;你以为坑到这里就结束了吗&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;CREATE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; TABLE&lt;/span&gt;&lt;span&gt; `&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;emoji_test&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;  `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; INT&lt;/span&gt;&lt;span&gt; UNSIGNED &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NOT NULL&lt;/span&gt;&lt;span&gt; AUTO_INCREMENT,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;  `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;emoji&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; VARCHAR&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1024&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; NOT NULL&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  PRIMARY KEY&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;) ENGINE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;InnoDB &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;DEFAULT&lt;/span&gt;&lt;span&gt; CHARSET&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;utf8mb4;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SET&lt;/span&gt;&lt;span&gt; NAMES utf8mb4;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;MySQL&lt;/span&gt;&lt;span&gt; [test&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; SELECT &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt; FROM emoji_test&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;+----+-------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; id&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; emoji&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;+----+-------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;  8&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; 😃&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;      |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;  9&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; 😂&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;      |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; 10&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; 🤦&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;      |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;+----+-------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; rows&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; set&lt;/span&gt;&lt;span&gt; (0.001&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; sec&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;MySQL&lt;/span&gt;&lt;span&gt; [test&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; SELECT &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt; FROM emoji_test WHERE emoji = &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;😂&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;+----+-------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; id&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; emoji&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;+----+-------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;  8&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; 😃&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;      |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;  9&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; 😂&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;      |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; 10&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; 🤦&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;      |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;+----+-------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; rows&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; set&lt;/span&gt;&lt;span&gt; (0.001&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; sec&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;是不是很神奇呢, 看下表的信息呢&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;MySQL &lt;/span&gt;&lt;span&gt;[test]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; SHOW &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;TABLE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; STATUS&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; WHERE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; Name&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;emoji_test&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;----------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+------------+--------------------+----------+----------------+---------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Name&lt;/span&gt;&lt;span&gt;       | Engine | &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Version&lt;/span&gt;&lt;span&gt; | Row_format | &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Rows&lt;/span&gt;&lt;span&gt; | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Create_time         | Update_time         | Check_time | Collation          | &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Checksum&lt;/span&gt;&lt;span&gt; | Create_options | Comment |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;----------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+------------+--------------------+----------+----------------+---------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| emoji_test | InnoDB |      &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;10&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Dynamic&lt;/span&gt;&lt;span&gt;    |    &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;3&lt;/span&gt;&lt;span&gt; |           &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;5461&lt;/span&gt;&lt;span&gt; |       &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;16384&lt;/span&gt;&lt;span&gt; |               &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt; |            &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt; |         &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt; |             &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;11&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;2019&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 11&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;16&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;28&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;2019&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 11&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;25&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;10&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;span&gt;       | utf8mb4_general_ci |     &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;span&gt; |                |         |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;----------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+------------+--------------------+----------+----------------+---------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; row&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; set&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;001&lt;/span&gt;&lt;span&gt; sec)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到此时默认的字符序是 &lt;code&gt;utf8mb4_general_ci&lt;/code&gt;, 使用 &lt;code&gt;WEIGHT_STRING&lt;/code&gt; 来查询这些 Emoji 的字符序&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;MySQL &lt;/span&gt;&lt;span&gt;[test]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; SET&lt;/span&gt;&lt;span&gt; @s&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;😂&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; COLLATE&lt;/span&gt;&lt;span&gt; utf8mb4_general_ci;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Query OK, &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; rows&lt;/span&gt;&lt;span&gt; affected (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;003&lt;/span&gt;&lt;span&gt; sec)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;MySQL &lt;/span&gt;&lt;span&gt;[test]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; SELECT&lt;/span&gt;&lt;span&gt; @s&lt;/span&gt;&lt;span&gt;, HEX(&lt;/span&gt;&lt;span&gt;@s&lt;/span&gt;&lt;span&gt;), HEX(WEIGHT_STRING(&lt;/span&gt;&lt;span&gt;@s&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;----+----------+------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| &lt;/span&gt;&lt;span&gt;@s&lt;/span&gt;&lt;span&gt;   | HEX(&lt;/span&gt;&lt;span&gt;@s&lt;/span&gt;&lt;span&gt;)  | HEX(WEIGHT_STRING(&lt;/span&gt;&lt;span&gt;@s&lt;/span&gt;&lt;span&gt;)) |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;----+----------+------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| 😂     | F09F9882 | FFFD                   |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;----+----------+------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; row&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; set&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;001&lt;/span&gt;&lt;span&gt; sec)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;MySQL &lt;/span&gt;&lt;span&gt;[test]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; SET&lt;/span&gt;&lt;span&gt; @s&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;😃&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; COLLATE&lt;/span&gt;&lt;span&gt; utf8mb4_general_ci;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Query OK, &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; rows&lt;/span&gt;&lt;span&gt; affected (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;001&lt;/span&gt;&lt;span&gt; sec)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;MySQL &lt;/span&gt;&lt;span&gt;[test]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; SELECT&lt;/span&gt;&lt;span&gt; @s&lt;/span&gt;&lt;span&gt;, HEX(&lt;/span&gt;&lt;span&gt;@s&lt;/span&gt;&lt;span&gt;), HEX(WEIGHT_STRING(&lt;/span&gt;&lt;span&gt;@s&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;----+----------+------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| &lt;/span&gt;&lt;span&gt;@s&lt;/span&gt;&lt;span&gt;   | HEX(&lt;/span&gt;&lt;span&gt;@s&lt;/span&gt;&lt;span&gt;)  | HEX(WEIGHT_STRING(&lt;/span&gt;&lt;span&gt;@s&lt;/span&gt;&lt;span&gt;)) |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;----+----------+------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| 😃     | F09F9883 | FFFD                   |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;----+----------+------------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; row&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; set&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;001&lt;/span&gt;&lt;span&gt; sec)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到, 它们的编码虽然不同, 但是字符序的值是相同的, 都是 &lt;code&gt;0xFFFD&lt;/code&gt;, 所以在匹配的时候, 它们被认为是同一个字符, 这又是 &lt;code&gt;MySQL&lt;/code&gt; 的一个大坑&lt;/p&gt;
&lt;p&gt;要是想粗暴点解决问题的话, 直接使用 &lt;code&gt;utf8mb4_bin&lt;/code&gt; 作为字符序就好了&lt;/p&gt;
&lt;h2 id=&quot;you-lai-yi-ping&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#you-lai-yi-ping&quot; aria-label=&quot;Anchor link for: you-lai-yi-ping&quot;&gt;又来一瓶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;但是这还不是结束, 当业务代码连接数据库并插入一些 Emoji 时, 还是可能遇到如下错误&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ERROR 1366: Incorrect string value: &amp;#39;\xF0\x9D\x8C\x86&amp;#39; for column &amp;#39;column_name&amp;#39; at row 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个是因为客户端到 MySQL 服务器的连接还是 utf8 而非 utf8mb4, 所以你需要在业务逻辑中做类似如下语句的事情&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;SET NAMES utf8mb4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者修改 MySQL 的配置文件, 当然这样不太现实, 需要重启 MySQL 进程.&lt;/p&gt;
&lt;p&gt;这样你才能终于用上 Emoji&lt;/p&gt;
&lt;h2 id=&quot;can-kao-zi-liao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#can-kao-zi-liao&quot; aria-label=&quot;Anchor link for: can-kao-zi-liao&quot;&gt;参考资料&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://stackoverflow.com/questions/41147829/how-can-i-search-by-emoji-in-mysql-using-utf8mb4&quot;&gt;How can I search by emoji in MySQL using utf8mb4?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html&quot;&gt;字符编码笔记：ASCII，Unicode 和 UTF-8
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://www.zhihu.com/question/60072951&quot;&gt;如何通俗地理解Unicode、UTF-8、ASCII、GBK等字符编码？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://medium.com/@adamhooper/in-mysql-never-use-utf8-use-utf8mb4-11761243e434&quot;&gt;In MySQL, never use “utf8”. Use “utf8mb4”.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://mathiasbynens.be/notes/mysql-utf8mb4&quot;&gt;How to support full Unicode in MySQL databases&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://dev.mysql.com/doc/refman/5.7/en/charset-connection.html&quot;&gt;Connection Character Sets and Collations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://stackoverflow.com/questions/10957238/incorrect-string-value-when-trying-to-insert-utf-8-into-mysql-via-jdbc&quot;&gt;“Incorrect string value” when trying to insert UTF-8 into MySQL via JDBC?&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;kuo-zhan-yue-du&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#kuo-zhan-yue-du&quot; aria-label=&quot;Anchor link for: kuo-zhan-yue-du&quot;&gt;扩展阅读&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://zhuanlan.zhihu.com/p/53714077&quot;&gt;其实你并不懂 Unicode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://eev.ee/blog/2015/09/12/dark-corners-of-unicode/&quot;&gt;Dark corners of Unicode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;http://mysql.taobao.org/monthly/2017/03/06/&quot;&gt;MySQL · 实现分析 · 对字符集和字符序支持的实现&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://docs.python.org/3.7/library/unicodedata.html&quot;&gt;unicodedata — Unicode Database&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>Oh MySQL IN Subquery</title>
        <published>2019-09-21T15:22:58+08:00</published>
        <updated>2019-09-21T15:22:58+08:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/oh-mysql-in-subquery/"/>
        <id>https://memwey.github.io/posts/oh-mysql-in-subquery/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/oh-mysql-in-subquery/">&lt;p&gt;感觉 &lt;code&gt;MySQL&lt;/code&gt; 的坑实在是有点多,记录一下这个 &lt;code&gt;MySQL&lt;/code&gt; 的坑, 也记录一下这个教训吧, 下次在数据库中直接操作一定要多小心&lt;/p&gt;
&lt;h2 id=&quot;xian-xiang-hui-fang&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#xian-xiang-hui-fang&quot; aria-label=&quot;Anchor link for: xian-xiang-hui-fang&quot;&gt;现象回放&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;现在有两张表, 表结构如下, 无关字段已经省略&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;team&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+-------------+---------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| Field       | Type                |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+-------------+---------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| id          | int(10) unsigned    |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| status      | tinyint(3) unsigned |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+-------------+---------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;player&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+-------------+---------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| Field       | Type                |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+-------------+---------------------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| id          | int(10) unsigned    |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| team_id     | int(10) unsigned    |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| status      | tinyint(3) unsigned |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+-------------+---------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;容易理解, 这是简单的一对多的关系, 一个足球队 team 里面, 有多个球员 player&lt;/p&gt;
&lt;p&gt;现在想取出有 player 的状态为 &lt;code&gt;1&lt;/code&gt; 的 team&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SELECT&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; FROM&lt;/span&gt;&lt;span&gt; team &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;WHERE&lt;/span&gt;&lt;span&gt; id &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;IN&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SELECT&lt;/span&gt;&lt;span&gt; id &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;FROM&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SELECT&lt;/span&gt;&lt;span&gt; team_id &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;FROM&lt;/span&gt;&lt;span&gt; player &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;WHERE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; status&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;AS&lt;/span&gt;&lt;span&gt; a);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;理论上, 这条语句是不能执行的, 注意这里&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;...... SELECT id FROM (SELECT team_id FROM ......&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;但是, 不知道为何, 这条语句是可以执行的, 而且等价于&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SELECT&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; FROM&lt;/span&gt;&lt;span&gt; team;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果单独把最外层的 IN 里面的 subquery 取出来, &lt;code&gt;MySQL&lt;/code&gt; 会报错&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SELECT&lt;/span&gt;&lt;span&gt; id &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;FROM&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SELECT&lt;/span&gt;&lt;span&gt; team_id &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;FROM&lt;/span&gt;&lt;span&gt; player &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;WHERE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; status&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;AS&lt;/span&gt;&lt;span&gt; a;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;ERROR 1054 (42S22): Unknown column &#39;id&#39; in &#39;field list&#39;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;再试着把 id 改为不存在的字段&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SELECT&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; FROM&lt;/span&gt;&lt;span&gt; team &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;WHERE&lt;/span&gt;&lt;span&gt; id &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;IN&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SELECT&lt;/span&gt;&lt;span&gt; not_exist_field &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;FROM&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SELECT&lt;/span&gt;&lt;span&gt; team_id &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;FROM&lt;/span&gt;&lt;span&gt; player &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;WHERE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; status&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;AS&lt;/span&gt;&lt;span&gt; a);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;ERROR 1054 (42S22): Unknown column &#39;not_exist_field&#39; in &#39;field list&#39;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这样才能如预期的报错&lt;/p&gt;
&lt;h2 id=&quot;wen-ti-pai-cha&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#wen-ti-pai-cha&quot; aria-label=&quot;Anchor link for: wen-ti-pai-cha&quot;&gt;问题排查&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;先 EXPLAIN 试试呢&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;           id: &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  select_type: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SIMPLE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        table&lt;/span&gt;&lt;span&gt;: team&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   partitions: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;         type&lt;/span&gt;&lt;span&gt;: ALL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;possible_keys: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;          key&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      key_len: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          ref: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;         rows&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     filtered: &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;100&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;00&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Extra: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;           id: &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  select_type: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SIMPLE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        table&lt;/span&gt;&lt;span&gt;: player&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   partitions: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;         type&lt;/span&gt;&lt;span&gt;: ALL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;possible_keys: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;          key&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      key_len: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          ref: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;         rows&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     filtered: &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;20&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;00&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Extra: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Using&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; where&lt;/span&gt;&lt;span&gt;; FirstMatch(team); &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Using&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; join&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; buffer&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Block&lt;/span&gt;&lt;span&gt; Nested &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Loop&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; rows&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; set&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;2&lt;/span&gt;&lt;span&gt; warnings (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;001&lt;/span&gt;&lt;span&gt; sec)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;好像看不出来什么问题呢, 不过有 warnings, 看一下呢&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;SHOW WARNINGS\G&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;*************************** 1. row ***************************&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Level: Note&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   Code: 1276&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Message: Field or reference &amp;#39;test.team.id&amp;#39; of SELECT #2 was resolved in SELECT #1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;*************************** 2. row ***************************&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  Level: Note&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   Code: 1003&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Message: /* select#1 */ select `test`.`team`.`id` AS `id`,`test`.`team`.`status` AS `status` from `test`.`team` semi join (`test`.`player`) where (`test`.`player`.`status` = 1)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2 rows in set (0.001 sec)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里出现了一个 &lt;code&gt;semi join&lt;/code&gt;, 没有见过呢, 查看一下文档, 大意就是, 比如使用 &lt;code&gt;INNER JOIN&lt;/code&gt; 的时候, 会返回匹配次数个结果. 但是我们并不关注匹配的次数, 比如如下语句, 我想取出有球员的 status 为 0 的球队, 可以这样写&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SELECT&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; FROM&lt;/span&gt;&lt;span&gt; team &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;INNER JOIN&lt;/span&gt;&lt;span&gt; player &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;ON&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; team&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; team_id &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;WHERE&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; player&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果一个球队里有多个 status 为 0 的球员, 那么就会出现多个记录, 比如这样&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+----+--------+----+---------+--------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| id | status | id | team_id | status |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+----+--------+----+---------+--------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|  1 |      0 |  1 |       1 |      0 |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|  1 |      0 |  2 |       1 |      0 |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|  2 |      0 |  5 |       2 |      0 |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|  2 |      0 |  6 |       2 |      0 |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+----+--------+----+---------+--------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样明显有些冗余的数据了. 当然我们可以用 &lt;code&gt;DISTINCT&lt;/code&gt; 什么的再处理一遍, 但是这样效率会比较低. 那么, 就可以用类似的子查询就方便多了&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SELECT&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; FROM&lt;/span&gt;&lt;span&gt; team &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;WHERE&lt;/span&gt;&lt;span&gt; id &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;IN&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SELECT&lt;/span&gt;&lt;span&gt; team_id &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;FROM&lt;/span&gt;&lt;span&gt; player &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;WHERE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; status&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回的结果也简洁多了&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+----+--------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;| id | status |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+----+--------+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|  1 |      0 |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|  2 |      0 |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+----+--------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然, 要这样优化还是有很多条件的, 林林总总的, 可以去官方文档查看&lt;/p&gt;
&lt;p&gt;看了这么多, 感觉还是和这个问题没什么关系啊, 试着 EXPLAIN 一下正确的语句&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;           id: &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  select_type: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SIMPLE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        table&lt;/span&gt;&lt;span&gt;: team&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   partitions: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;         type&lt;/span&gt;&lt;span&gt;: ALL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;possible_keys: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;PRIMARY&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;          key&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      key_len: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          ref: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;         rows&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     filtered: &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;100&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;00&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Extra: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;           id: &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  select_type: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SIMPLE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        table&lt;/span&gt;&lt;span&gt;: player&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   partitions: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;         type&lt;/span&gt;&lt;span&gt;: ALL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;possible_keys: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;          key&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      key_len: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          ref: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NULL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;         rows&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     filtered: &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;20&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;00&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Extra: &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Using&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; where&lt;/span&gt;&lt;span&gt;; FirstMatch(team); &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Using&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; join&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; buffer&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Block&lt;/span&gt;&lt;span&gt; Nested &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Loop&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; rows&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; set&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt; warning (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;001&lt;/span&gt;&lt;span&gt; sec)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再看看 warnings&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  Level&lt;/span&gt;&lt;span&gt;: Note&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   Code: &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1003&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Message&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;/*&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; select#1 &lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;*/&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; select&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;team&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; AS&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;team&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; AS&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; from&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;team&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt; semi &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;join&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;where&lt;/span&gt;&lt;span&gt; ((&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;team_id&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;team&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;and&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; row&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; set&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;001&lt;/span&gt;&lt;span&gt; sec)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对照着实际运行的语句的 Note, 发现错误的语句缺少了以下这个条件&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;((&lt;code&gt;test&lt;/code&gt;.&lt;code&gt;player&lt;/code&gt;.&lt;code&gt;team_id&lt;/code&gt; = &lt;code&gt;test&lt;/code&gt;.&lt;code&gt;team&lt;/code&gt;.&lt;code&gt;id&lt;/code&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;难道就是你! 但是为什么又有&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Field or reference &#39;test.team.id&#39; of SELECT #2 was resolved in SELECT #1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个问题呢&lt;/p&gt;
&lt;h2 id=&quot;po-an&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#po-an&quot; aria-label=&quot;Anchor link for: po-an&quot;&gt;破案&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;后来翻了翻文档, 发现这压根不是 &lt;code&gt;MySQL&lt;/code&gt; 的 Bug, 而是 SQL 一条相当坑的作用域规则, 子查询里解析不了的列名, 会跑到外层查询去找.&lt;/p&gt;
&lt;p&gt;关键还是在那个派生表 &lt;code&gt;a&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SELECT&lt;/span&gt;&lt;span&gt; team_id &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;FROM&lt;/span&gt;&lt;span&gt; player &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;WHERE&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; status&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;AS&lt;/span&gt;&lt;span&gt; a&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它只有 &lt;code&gt;team_id&lt;/code&gt; 一列, 根本没有 &lt;code&gt;id&lt;/code&gt;. 于是外面那句 &lt;code&gt;SELECT id FROM a&lt;/code&gt; 里的 &lt;code&gt;id&lt;/code&gt;, 在 &lt;code&gt;a&lt;/code&gt; 里是找不到的. 按照作用域规则, 内层找不到就往外层找, 而外层的 &lt;code&gt;team&lt;/code&gt; 表恰好有 &lt;code&gt;id&lt;/code&gt;, 所以这个 &lt;code&gt;id&lt;/code&gt; 就被解析成了 &lt;code&gt;team.id&lt;/code&gt;. 这也正是那条 Note 想告诉我们的&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Field or reference &#39;test.team.id&#39; of SELECT #2 was resolved in SELECT #1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;id&lt;/code&gt; 变成 &lt;code&gt;team.id&lt;/code&gt; 之后, 子查询就成了「对当前这一行 &lt;code&gt;team&lt;/code&gt;, 返回它自己的 &lt;code&gt;team.id&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt; 里有几行就返回几次」, 整个条件相当于&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;team&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; IN&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SELECT&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; team&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; FROM&lt;/span&gt;&lt;span&gt; a)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只要 &lt;code&gt;a&lt;/code&gt; 非空, 也就是全表里存在任意一个 &lt;code&gt;status&lt;/code&gt; 为 &lt;code&gt;1&lt;/code&gt; 的球员, 这个 &lt;code&gt;IN&lt;/code&gt; 对每一行 &lt;code&gt;team&lt;/code&gt; 都恒为真. 本该起作用的关联条件 &lt;code&gt;player.team_id = team.id&lt;/code&gt; 从头到尾就没参与进来, 所以每支球队都被捞了出来, 等价于 &lt;code&gt;SELECT * FROM team&lt;/code&gt;. 再对照前面两条 Note 里 &lt;code&gt;semi join&lt;/code&gt; 的 &lt;code&gt;where&lt;/code&gt;, 一个有 &lt;code&gt;team_id = team.id&lt;/code&gt;, 一个没有, 也就说得通了.&lt;/p&gt;
&lt;p&gt;回过头看之前那几个奇怪的现象, 也都顺理成章了&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单独跑 &lt;code&gt;SELECT id FROM a&lt;/code&gt; 会报错, 是因为没有外层可以借, &lt;code&gt;id&lt;/code&gt; 真的无处可寻&lt;/li&gt;
&lt;li&gt;嵌套里把 &lt;code&gt;id&lt;/code&gt; 换成 &lt;code&gt;not_exist_field&lt;/code&gt; 也报错, 是因为这个名字在 &lt;code&gt;a&lt;/code&gt; 和外层 &lt;code&gt;team&lt;/code&gt; 里都不存在&lt;/li&gt;
&lt;li&gt;唯独 &lt;code&gt;id&lt;/code&gt; 不报错, 只是因为它恰好是外层 &lt;code&gt;team&lt;/code&gt; 的列, 被悄悄借走了, 报错被吃掉, 结果却全错&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以并不是 &lt;code&gt;MySQL&lt;/code&gt; 抽风, 而是「外层恰好有个同名列」, 把一句本该报错的语句, 变成了能跑但结果完全不对的语句, 属实有点阴险.&lt;/p&gt;
&lt;p&gt;教训还是开头那句, 在数据库里直接操作一定要多小心. 具体到这个坑, 一个好习惯是子查询里的列名都带上表名或别名, 写 &lt;code&gt;a.team_id&lt;/code&gt; 而不是光秃秃的 &lt;code&gt;id&lt;/code&gt;, 一旦写错会立刻报错, 而不会被外层悄悄借走. 另外, 这个规则在 &lt;code&gt;DELETE FROM team WHERE id IN (SELECT id FROM ...)&lt;/code&gt; 里最要命, 列名一写错解析成外层的 &lt;code&gt;id&lt;/code&gt;, 条件恒真, 整张表就没了.&lt;/p&gt;
&lt;h2 id=&quot;can-kao-zi-liao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#can-kao-zi-liao&quot; aria-label=&quot;Anchor link for: can-kao-zi-liao&quot;&gt;参考资料&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://dev.mysql.com/doc/refman/5.7/en/semijoins.html&quot;&gt;Optimizing Subqueries, Derived Tables, and View References with Semijoin Transformations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://dev.mysql.com/doc/refman/8.0/en/correlated-subqueries.html&quot;&gt;Correlated Subqueries&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>Yes</title>
        <published>2018-12-14T23:34:15+08:00</published>
        <updated>2018-12-14T23:34:15+08:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/yes/"/>
        <id>https://memwey.github.io/posts/yes/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/yes/">&lt;p&gt;最近看到一个有趣的 &lt;code&gt;GNU/Linux&lt;/code&gt; 命令, &lt;code&gt;yes&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;先 &lt;code&gt;man&lt;/code&gt; 一下&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;YES(1&lt;/span&gt;&lt;span&gt;)                    BSD General Commands Manual                   YES(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;NAME&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;     yes&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; be&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; repetitively&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; affirmative&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;SYNOPSIS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;     yes&lt;/span&gt;&lt;span&gt; [expletive&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;DESCRIPTION&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;     yes&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; outputs&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; expletive,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; or,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; by&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; default,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; `&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; forever.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;HISTORY&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;     The&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; yes&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; command&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; appeared&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; 4.0BSD.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;4th&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Berkeley&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Distribution&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;        June&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; 6,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1993&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;        4th&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Berkeley&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Distribution&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;END&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;emmmmm, 在 &lt;code&gt;macOS&lt;/code&gt; 下并没有 &lt;code&gt;--help&lt;/code&gt; 或者 &lt;code&gt;-h&lt;/code&gt; 的选项&lt;/p&gt;
&lt;p&gt;试试 &lt;code&gt;Linux&lt;/code&gt; 下呢&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;➜&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;  /&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; yes&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-help&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Usage:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; yes&lt;/span&gt;&lt;span&gt; [STRING&lt;/span&gt;&lt;span&gt;]...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;  or:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;  yes&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; OPTION&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Repeatedly&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; output&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; a&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; line&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; with&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; all&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; specified&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; STRING&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;s&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; or&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;      --help&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;     display&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; this&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; help&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; and&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; exit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;      --version&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;  output&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; version&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; information&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; and&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; exit&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;GNU&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; coreutils&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; online&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; help:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;https://www.gnu.org/software/coreutils&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Full&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; documentation&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; at:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;https://www.gnu.org/software/coreutils/ye&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;or&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; available&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; locally&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; via:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; info&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;(coreutils) yes invocation&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有了&lt;/p&gt;
&lt;p&gt;简单来说这个命令就是可以反复的输出一个字符串, 这个字符串的默认值是 &lt;code&gt;y&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;于是有了一些特别的用法, 比如, 注意这里没有 &lt;code&gt;f&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;yes&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; rm&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还有&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;yes&lt;/span&gt;&lt;span&gt; $(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;yes&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; yes&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;好孩子不要乱试&lt;/p&gt;
&lt;p&gt;当然这个命令的效果其实一般都有替代, 比如加上 &lt;code&gt;-f&lt;/code&gt;, &lt;code&gt;-y&lt;/code&gt; 什么的, 生成一个大文件也可以用 &lt;code&gt;/dev/urandom&lt;/code&gt; 来做&lt;/p&gt;
&lt;p&gt;还可以在冬天做暖手宝, 噗&lt;/p&gt;
&lt;p&gt;最后, 跟我一起在命令行输入&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;yes&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;AMD, yes!&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;果然人类的本质就是复读机&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>重新开始</title>
        <published>2018-09-17T23:26:20+08:00</published>
        <updated>2018-09-17T23:26:20+08:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/restart/"/>
        <id>https://memwey.github.io/posts/restart/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/restart/">&lt;p&gt;新的博客, 重新开始&lt;/p&gt;
&lt;p&gt;这次尝试着坚持一下吧&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>红黑树笔记</title>
        <published>2016-09-13T23:32:27+08:00</published>
        <updated>2016-09-13T23:32:27+08:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/red-black-tree/"/>
        <id>https://memwey.github.io/posts/red-black-tree/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/red-black-tree/">&lt;p&gt;最近研究了一下红黑树的一些性质和思想, 在这里记录一下.&lt;/p&gt;
&lt;p&gt;Map, 或者在 &lt;code&gt;Python&lt;/code&gt; 等一些语言中叫做 dictionary 的常用的以键值对形式储存的数据结构一般有两种实现方式, 在 &lt;code&gt;C++&lt;/code&gt; 的 &lt;code&gt;STL&lt;/code&gt; 中使用了红黑树的方式, 而在&lt;code&gt;Python&lt;/code&gt;中使用了哈希表的方式.&lt;/p&gt;
&lt;p&gt;一般认为采用哈希表的方式查找删除的时间复杂度为 &lt;code&gt;O(1)&lt;/code&gt;, 而红黑树为 &lt;code&gt;O(logn)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Python&lt;/code&gt; 中的哈希表使用开放寻址法解决冲突.&lt;/p&gt;
&lt;p&gt;对于普通的二叉查找树来说, 查找和插入的时间复杂度在最坏的情况下可能会变成 &lt;code&gt;O(n)&lt;/code&gt;, 即完全偏向一边的不平衡情况使其成为一个单链表. 如果只是在树的叶子上增加节点而不进行其他的调整, 很容易会使只向下增长的树不平衡. 为了保证 &lt;code&gt;O(logn)&lt;/code&gt; 的时间复杂度, 我们需要一种动态的机制, 来调整树的父节点, 乃至于根节点.&lt;/p&gt;
&lt;p&gt;红黑树是一种动态调整的实现方式. 红黑树其实是在 &lt;code&gt;2, 3-树&lt;/code&gt; 的基础上实现的. 他们也完全可以等价的转换. 不过 &lt;code&gt;2, 3-树&lt;/code&gt; 在程序的实现上比较复杂, 而且查找操作也和二叉搜索树有一些不同, 所以在程序实现中一般使用红黑树.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;2, 3-树&lt;/code&gt; 和红黑树他们共同的思想是, 将树的叶子节点上的操作造成的影响, 逐步的传递给父节点, 按照一定的方式对当前子树进行调整. 父节点再传递给它的父节点, 调整更大一些的子树. 最终传递到根节点, 调整整颗树.&lt;/p&gt;
&lt;p&gt;可以这样理解, 在红黑树中, 用红色来标记正在累计调整的节点. 当节点M的两个子节点均被标记, 则M取消子节点的标记, 并标记自己, 使调整向根节点传递.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>在CentOS上使用较新的软件</title>
        <published>2016-08-02T23:30:33+08:00</published>
        <updated>2025-02-15T23:30:33+09:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/centos-latest-software/"/>
        <id>https://memwey.github.io/posts/centos-latest-software/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/centos-latest-software/">&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;更新于 2025-02-15:&lt;/strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https://ius.io/faq&quot;&gt;IUS 官方 FAQ&lt;/a&gt; 已明确说明该项目不再维护, 历史软件包仅保存在 IUS Vault. 本文使用的 CentOS 6 仓库和安装命令均已失效, 请勿在新环境中继续使用; 正文仅作为历史记录保留. 新环境应选择仍受支持的操作系统版本及其官方软件源, 或软件供应方仍在维护的软件源.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;公司的服务器都是 &lt;code&gt;CentOS&lt;/code&gt; 的, 带的软件都比较旧, 让我这个不更新会死星人很难过啊.&lt;/p&gt;
&lt;p&gt;开玩笑的, 主要问题是 &lt;code&gt;git&lt;/code&gt; 版本太旧, &lt;code&gt;clone&lt;/code&gt; 的时候会报错, 然后也需要 &lt;code&gt;python3.5&lt;/code&gt;. 在 &lt;code&gt;git&lt;/code&gt; 上面发现 &lt;a rel=&quot;external&quot; href=&quot;https://ius.io/&quot;&gt;ius&lt;/a&gt; 来解决这个问题.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://ius.io/&quot;&gt;ius&lt;/a&gt; 是一个社区项目, 目的是为 &lt;a rel=&quot;external&quot; href=&quot;https://www.kernel.org/&quot;&gt;Linux&lt;/a&gt; 的企业发行版提供一些更新版本的RPM包.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; yum&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; epel-release&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; yum&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; https://centos6.iuscommunity.org/ius-release.rpm&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了解决一些冲突和共存的问题, 有一些 &lt;code&gt;package&lt;/code&gt; 在 &lt;a rel=&quot;external&quot; href=&quot;https://ius.io/&quot;&gt;ius&lt;/a&gt; 中的包名有一些改动&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; yum&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; git2u&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; yum&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; python35u&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一般的命名规则是&lt;/p&gt;
&lt;p&gt;&lt;code&gt;{name}{major_version}{minor_version}u&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这样就可以在 &lt;code&gt;CentOS&lt;/code&gt; 上使用较新的软件啦.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>Linux后台命令</title>
        <published>2016-06-15T23:28:24+08:00</published>
        <updated>2016-06-15T23:28:24+08:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/linux-backgroud/"/>
        <id>https://memwey.github.io/posts/linux-backgroud/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/linux-backgroud/">&lt;p&gt;最近在跑 DHT 网络拟真的时候涉及到一些 linux 后台运行的一些东西,现在总结一下&lt;/p&gt;
&lt;p&gt;例如&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;nohup&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ../src/OverSim&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ChordChurn&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Cmdenv&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; out.file&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&amp;amp;1&lt;/span&gt;&lt;span&gt; &amp;amp;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使得 &lt;code&gt;../src/OverSim -c ChordChurn -u Cmdenv&lt;/code&gt; 命令在后台运行,并将 &lt;code&gt;stderr&lt;/code&gt; 重定向到 &lt;code&gt;stdout&lt;/code&gt; 中然后再将 &lt;code&gt;stdout&lt;/code&gt; 重定向到 &lt;code&gt;out.file&lt;/code&gt;, 并且在当前终端关闭时仍然运行&lt;/p&gt;
&lt;p&gt;具体说一下吧&lt;/p&gt;
&lt;h2 id=&quot;nohup&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#nohup&quot; aria-label=&quot;Anchor link for: nohup&quot;&gt;nohup&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;退出终端的时候,一般在终端中运行的进程会随之关闭,因为此时终端中的子进程会收到 &lt;code&gt;SIGHUP&lt;/code&gt; 信号. 而 &lt;code&gt;nohup&lt;/code&gt; 使得进程忽略所有 &lt;code&gt;SIGHUP&lt;/code&gt; 信号. &lt;code&gt;stdout&lt;/code&gt; 默认会重定向到 &lt;code&gt;nohup.out&lt;/code&gt; 文件中&lt;/p&gt;
&lt;h2 id=&quot;&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#&quot; aria-label=&quot;Anchor link for: &quot;&gt;&amp;amp;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;在命令中末尾加入 &lt;code&gt;&amp;amp;&lt;/code&gt; 符号,使进程在后台运行&lt;/p&gt;
&lt;h2 id=&quot;2-1&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#2-1&quot; aria-label=&quot;Anchor link for: 2-1&quot;&gt;2&amp;gt;&amp;amp;1&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;放在 &lt;code&gt;&amp;gt;&lt;/code&gt; 后面的 &lt;code&gt;&amp;amp;&lt;/code&gt;, 表示重定向的目标不是一个文件, 而是一个文件描述符, 文件描述符&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1 =&amp;gt; stdout&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2 =&amp;gt; stderr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0 =&amp;gt; stdin&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;qi-ta-yi-xie-ming-ling-he-cao-zuo&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#qi-ta-yi-xie-ming-ling-he-cao-zuo&quot; aria-label=&quot;Anchor link for: qi-ta-yi-xie-ming-ling-he-cao-zuo&quot;&gt;其他一些命令和操作&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Control+z&lt;/code&gt; 可以发出 &lt;code&gt;SIGSTOP&lt;/code&gt; 信号, 使当前前台进程暂停并放入后台&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fg [job_id]&lt;/code&gt;使进程在前台运行&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bg [job_id]&lt;/code&gt;使进程在后台运行&lt;/p&gt;
&lt;p&gt;&lt;code&gt;jobs&lt;/code&gt; 查看后台进程&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>第一篇博客</title>
        <published>2016-06-14T23:22:32+08:00</published>
        <updated>2016-06-14T23:22:32+08:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/posts/first-blog/"/>
        <id>https://memwey.github.io/posts/first-blog/</id>
        
        <content type="html" xml:base="https://memwey.github.io/posts/first-blog/">&lt;p&gt;其实很早就想弄个博客什么的, 觉得有些东西费了很大的力气解决了, 然后久而久之就忘了......记录下来应该会好一点吧, 这样觉得.&lt;/p&gt;
&lt;p&gt;所以还是写一些东西吧, 嗯嗯......&lt;/p&gt;
&lt;p&gt;其实还是不太喜欢这种不怎么能自己定制的东西......总有一种不知所措的感觉......自己写一个后台倒是没什么问题, 前端的话就实在是无力了, 噗&lt;/p&gt;
&lt;p&gt;对 Markdown 的语法还不那么熟悉, 而且好像 Markdown 有一些不同的解释器, 很麻烦啊&lt;/p&gt;
&lt;p&gt;每次生成的静态网页还要在本地生成了之后看过效果......啊, 真的很麻烦&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>关于</title>
        <published>2016-06-14T00:00:00+00:00</published>
        <updated>2025-11-13T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/about/"/>
        <id>https://memwey.github.io/about/</id>
        
        <content type="html" xml:base="https://memwey.github.io/about/">&lt;p&gt;Gopher &amp;amp; Pythonista&lt;/p&gt;
&lt;h2 id=&quot;guan-yu-bo-ke&quot;&gt;关于博客&lt;/h2&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;h2 id=&quot;guan-yu-gong-zuo&quot;&gt;关于工作&lt;/h2&gt;
&lt;h3 id=&quot;xian-zai-de&quot;&gt;现在的&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;開発 @&lt;a rel=&quot;external&quot; href=&quot;https://www.netstars.co.jp/&quot;&gt;株式会社ネットスターズ&lt;/a&gt; @东京, 日本&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;ceng-jing-de&quot;&gt;曾经的&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;中级运维开发工程师 @&lt;a rel=&quot;external&quot; href=&quot;https://www.datastory.com.cn/&quot;&gt;数说故事&lt;/a&gt; @广州, 中国&lt;/li&gt;
&lt;li&gt;后端开发工程师 @画音 @广州, 中国&lt;/li&gt;
&lt;li&gt;后端开发工程师 @壁虎科技 @广州, 中国&lt;/li&gt;
&lt;li&gt;Senior Engineer @&lt;a rel=&quot;external&quot; href=&quot;https://www.bigo.sg/&quot;&gt;BIGO&lt;/a&gt; @广州, 中国&lt;/li&gt;
&lt;li&gt;Python Engineer @&lt;a rel=&quot;external&quot; href=&quot;https://www.akulaku.com/&quot;&gt;Akulaku&lt;/a&gt; @深圳, 中国&lt;/li&gt;
&lt;li&gt;服务器端开发工程师 @&lt;a rel=&quot;external&quot; href=&quot;https://www.cmcm.com/&quot;&gt;猎豹移动&lt;/a&gt; @珠海, 中国&lt;/li&gt;
&lt;li&gt;Python 开发实习生 @&lt;a rel=&quot;external&quot; href=&quot;http://www.cnicg.cn/&quot;&gt;广州中国科学院计算机网络信息中心&lt;/a&gt; @广州, 中国&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;guan-yu-jiao-yu&quot;&gt;关于教育&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;学生 @&lt;a rel=&quot;external&quot; href=&quot;https://www.jnu.edu.cn/&quot;&gt;暨南大学&lt;/a&gt; @广州, 中国&lt;/li&gt;
&lt;li&gt;学生 @&lt;a rel=&quot;external&quot; href=&quot;http://www.shimen.org/&quot;&gt;石门中学&lt;/a&gt; @佛山, 中国&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>链接</title>
        <published>2016-06-14T00:00:00+00:00</published>
        <updated>2025-09-07T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://memwey.github.io/link/"/>
        <id>https://memwey.github.io/link/</id>
        
        <content type="html" xml:base="https://memwey.github.io/link/">&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https://leoeatle.github.io&quot;&gt;LeoEatle&#39;s World&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
</feed>
