{
    "version" : "https://jsonfeed.org/version/1.1",
    "title" : "Misael Zapata",
    "description": "Misael Zapata 的个人网站",
    "home_page_url" : "https://misael.org/",
    "feed_url" : "https://misael.org/zh-cn/index.feed",
    "icon" : "https://misael.org/apple-touch-icon.png",
    "favicon" : "https://misael.org/favicon.ico",
    "author" : {
        "name" : "Misael Zapata",
        "url": "https://github.com/misaelzapata",
        "avatar": "https://misael.org/images/avatar.webp"
    },
    "items" : [
    {
        "title" : "没人要求的那次更新",
        "date_published" : "2026-06-28T00:00:00Z",
        "date_modified" : "2026-06-28T00:00:00Z",
        "id" : "https://misael.org/zh-cn/the-update-nobody-asked-for/",
        "url" : "https://misael.org/zh-cn/the-update-nobody-asked-for/",
        "summary": "应用更新不再是为了变得更好——而是为了赶上某个事件。本文从 2026 年世界杯的更新狂潮出发，沿着真实且带出处的数字一路走下去，看驱动这一切的 FOMO（错失恐惧）是如何被设计并量产出来的——以及那条不断缩短的炒作周期，从外包到 AI“智能体”。",
        "content_html" : "\u003cp\u003e这一周，随便打开哪个应用，总有东西在一夜之间变了。我叫了一辆 Uber，地图上那辆小车挂着我们国家队的国旗。我打开一个外卖应用，一张优惠券弹了出来，\u003cem\u003e就因为有人在地球另一端进了一个球\u003c/em\u003e。我进 Duolingo 上课，多儿（Duo）穿着一件国家队球衣。我用足球表情发了条消息，它变成了官方世界杯用球。我在 Google 上搜了一个比分，屏幕朝我甩出一段庆祝动画。\u003c/p\u003e\n\u003cp\u003e这些更新没有一个修好了任何东西。没有一个让应用更快、更安全或更有用。它们存在的理由都一样：某个球场里有一只球在滚，没人想被排除在这场对话之外。\u003c/p\u003e\n\u003cp\u003e曾经有一段时间，一次软件更新是一个承诺。你等上好几个月，就为了\u003cem\u003e那个\u003c/em\u003e缺陷被修好，为了电池能再多撑一会儿，为了那玩意儿别再崩溃。更新触达了数百万人，正因如此它才被认真对待。今天我们部署给同样的数百万人，同时协调五个平台，只为给一个图标贴上一面国旗。下周我们又会把它撤掉，没人会记得它存在过。\u003c/p\u003e\n\u003cp\u003e这篇文章讲的就是这件事：重要的是数字，不是内容。讲一件微不足道的小事如何最终影响到世界另一头的某个人，然后又以它出现时同样的速度消失。讲让这一切运转的那根心理弹簧是如何被\u003cem\u003e刻意\u003c/em\u003e设计出来的。也讲为什么这种模式和 OpenClaw、和加密货币、和物联网、和大数据，以及我们把工作从美洲挪到亚洲的那一次，是一模一样的。\u003c/p\u003e\n\u003cdiv class=\"details admonition note open\"\u003e\n    \u003cdiv class=\"details-summary admonition-title\"\u003e\n        \u003cspan class=\"icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z\"/\u003e\u003c/svg\u003e\u003c/span\u003e关于出处\u003cspan class=\"details-icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"details-content\"\u003e\n        \u003cdiv class=\"admonition-content\"\u003e本文中的每一个数字都链接到它原始的、公开的、免费的来源。如果某个说法没有链接，那就是我的个人观点，并且会以观点的方式写出来。文中的应用图片是\u003cstrong\u003e示意性的重建图\u003c/strong\u003e（不是真实截图）：重要的是模式，而不是像素，而且每个案例都注明了其官方公告的出处。\u003c/div\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ch2 id=\"把世界杯当成一次大规模部署\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e6%8a%8a%e4%b8%96%e7%95%8c%e6%9d%af%e5%bd%93%e6%88%90%e4%b8%80%e6%ac%a1%e5%a4%a7%e8%a7%84%e6%a8%a1%e9%83%a8%e7%bd%b2\" class=\"header-mark\" aria-label=\"Header mark for '把世界杯当成一次大规模部署'\"\u003e\u003c/a\u003e1 把世界杯当成一次大规模部署\u003c/h2\u003e\u003cp\u003e我们来逐一点名看看发生了什么。这不是某一个应用——而是所有应用。\u003c/p\u003e\n\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/the-update-nobody-asked-for/images/world-cup-apps_zh.png\" title=\"\" data-thumbnail=\"/the-update-nobody-asked-for/images/world-cup-apps_zh.png\" data-sub-html=\"\u003ch2\u003e同一只球，在每一块屏幕上触发了一次部署。示意性重建图；每个案例都在下文链接到其来源。\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/the-update-nobody-asked-for/images/world-cup-apps_zh.png\" srcset=\"/the-update-nobody-asked-for/images/world-cup-apps_zh_hu62f88420a028ac49d0d03396dc3fb086_92759_800x0_resize_q75_h2_box_3.webp 800w, /the-update-nobody-asked-for/images/world-cup-apps_zh_hu62f88420a028ac49d0d03396dc3fb086_92759_1200x0_resize_q75_h2_box_3.webp 1200w, /the-update-nobody-asked-for/images/world-cup-apps_zh_hu62f88420a028ac49d0d03396dc3fb086_92759_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"822\" width=\"1080\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003e同一只球，在每一块屏幕上触发了一次部署。示意性重建图；每个案例都在下文链接到其来源。\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003cp\u003e\u003cstrong\u003eUber\u003c/strong\u003e 让你用国家队的国旗来定制地图上的小车图标，并推出了*“出局优惠”*：如果你支持的球队被淘汰，它就给你一张未来打车 7 折券（\u003ca href=\"https://www.uber.com/us/en/newsroom/traveling-for-soccer-this-summer/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eUber 新闻室\u003c/a\u003e、\u003ca href=\"https://thepointsguy.com/news/uber-world-cup-features/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eThe Points Guy\u003c/a\u003e）。你的体育悲伤，被换成了一张优惠券。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePedidosYa\u003c/strong\u003e（拉美外卖巨头）推出了*“有进球，就有券”*：每进一个球，应用就实时放出优惠券。这场活动承诺平均发放总额超过 \u003cstrong\u003e27 亿比索\u003c/strong\u003e的优惠券——每个比赛日超过 \u003cstrong\u003e7700 万\u003c/strong\u003e——覆盖 14 个拉美国家，从 6 月 11 日到 7 月 19 日（\u003ca href=\"https://www.infobae.com/america/agencias/2026/06/12/pedidosya-celebra-la-fiesta-del-futbol-entregando-millones-de-cupones-con-una-campana-inedita-si-hay-gol-hay-cupon/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eInfobae\u003c/a\u003e、\u003ca href=\"https://mercado.com.ar/marketing/pedidosya-activa-cupones-de-descuento-por-cada-gol-con-una-campana-regional\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eRevista Mercado\u003c/a\u003e）。球场里的一个进球，触发一次优惠券投放，让数百万人争相在券发完之前打开应用。这是一次由一只球触发的基础设施事件。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDuolingo\u003c/strong\u003e 搭起了\u003cem\u003e多儿杯（Duo Cup）\u003c/em\u003e：\u003cstrong\u003e48 套国家队球衣\u003c/strong\u003e，你通过完成课程来解锁，而且解锁顺序是\u003cstrong\u003e随机的\u003c/strong\u003e，所以你不知道接下来轮到哪一套，于是你每天都回来看看（\u003ca href=\"https://www.duolingo.com/duo_cup_suits_intro\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eDuolingo\u003c/a\u003e、\u003ca href=\"https://duolingo.fandom.com/wiki/Duo_Cup\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eDuolingo Wiki\u003c/a\u003e）。它的营销是纯粹的民族主义：\u003cem\u003e“国家队需要你，解锁你的球衣”\u003c/em\u003e（\u003ca href=\"https://www.facebook.com/duolingo/posts/the-national-team-needs-you-unlock-your-duo-cup-avatar-suit-%EF%B8%8F/1440333504788285/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eDuolingo 的 Facebook\u003c/a\u003e）。\u003c/p\u003e\n\u003cp\u003e巨头们也没有袖手旁观：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eWhatsApp\u003c/strong\u003e 联手阿迪达斯和国际足联，让足球表情在整届赛事期间变成官方用球\u003cem\u003eTrionda\u003c/em\u003e——外加主题贴纸、通话特效，以及一个世界杯频道目录（\u003ca href=\"https://www.socialmediatoday.com/news/meta-announces-world-cup-features-across-its-apps/822741/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eSocial Media Today\u003c/a\u003e、\u003ca href=\"https://www.clarosports.com/futbol/mundial-2026/el-mundial-2026-llego-a-whatsapp-con-golazo-como-utilizar-el-nuevo-emoji-animado-del-balon-trionda/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eClaro Sports\u003c/a\u003e）。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eGoogle\u003c/strong\u003e 推出了一系列 \u003cstrong\u003e69 个涂鸦（Doodle）\u003c/strong\u003e（横跨 189 个市场的 36 件独立作品，6 月 11 日到 7 月 20 日），以及搜索\u003cem\u003e彩蛋\u003c/em\u003e，为你的胜利欢呼，或安慰你的失利（\u003ca href=\"https://www.mediapost.com/publications/article/415745/google-touts-ai-search-for-world-cup-games.html\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eMediaPost\u003c/a\u003e）。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eInstagram\u003c/strong\u003e 加入了实时比分提醒、主题贴纸和特效（\u003ca href=\"https://www.socialmediatoday.com/news/meta-announces-world-cup-features-across-its-apps/822741/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eSocial Media Today\u003c/a\u003e）。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eTikTok\u003c/strong\u003e 甚至走到了推出\u003cem\u003eTikTok Pro Events\u003c/em\u003e 的地步，\u003cstrong\u003e一个专门的独立应用\u003c/strong\u003e，献给世界杯，你在里面参与互动就能赚取“星星”（\u003ca href=\"https://techcrunch.com/2026/06/03/tiktok-launches-tiktok-pro-events-an-app-for-cultural-moments-like-the-fifa-world-cup/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eTechCrunch\u003c/a\u003e）。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e一个全新的应用，为了一个为期五周的赛事。这就是一只小球所能触发的投入规模。\u003c/p\u003e\n\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/the-update-nobody-asked-for/images/app-shots_zh.png\" title=\"\" data-thumbnail=\"/the-update-nobody-asked-for/images/app-shots_zh.png\" data-sub-html=\"\u003ch2\u003e其中一些的样子：WhatsApp 里的 Trionda 表情和“进球”、Google 的庆祝涂鸦、PedidosYa 的活动、TikTok Pro Events，以及 Instagram 的世界杯专区。相比之下，Uber 的国旗功能从未出现在任何已发布的截图中。媒体报道与官方素材，编辑用途。\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/the-update-nobody-asked-for/images/app-shots_zh.png\" srcset=\"/the-update-nobody-asked-for/images/app-shots_zh_hu8acb9c9bdadc4b0ce18af21346b6ff91_369579_800x0_resize_q75_h2_box_3.webp 800w, /the-update-nobody-asked-for/images/app-shots_zh_hu8acb9c9bdadc4b0ce18af21346b6ff91_369579_1200x0_resize_q75_h2_box_3.webp 1200w, /the-update-nobody-asked-for/images/app-shots_zh_hu8acb9c9bdadc4b0ce18af21346b6ff91_369579_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"584\" width=\"1024\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003e其中一些的样子：WhatsApp 里的 Trionda 表情和“进球”、Google 的庆祝涂鸦、PedidosYa 的活动、TikTok Pro Events，以及 Instagram 的世界杯专区。相比之下，Uber 的国旗功能从未出现在任何已发布的截图中。媒体报道与官方素材，编辑用途。\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003ch2 id=\"duolingo-的案例他们把你祖国的球衣卖给你\" class=\"headerLink\"\u003e\n    \u003ca href=\"#duolingo-%e7%9a%84%e6%a1%88%e4%be%8b%e4%bb%96%e4%bb%ac%e6%8a%8a%e4%bd%a0%e7%a5%96%e5%9b%bd%e7%9a%84%e7%90%83%e8%a1%a3%e5%8d%96%e7%bb%99%e4%bd%a0\" class=\"header-mark\" aria-label=\"Header mark for 'Duolingo 的案例：他们把你祖国的球衣卖给你'\"\u003e\u003c/a\u003e2 Duolingo 的案例：他们把你祖国的球衣卖给你\u003c/h2\u003e\u003cp\u003eDuolingo 的案例最直白地暴露了实际上在卖的是什么。它加入世界杯，靠的不是一段免费动画：它开了一家\u003cstrong\u003e商店\u003c/strong\u003e。\u003cem\u003e多儿杯\u003c/em\u003e里有一个“多儿杯商店”，邀请你*“购买你最喜欢的球队的球衣”*，每套 \u003cstrong\u003e1.99 美元\u003c/strong\u003e，旁边滴答走着一个 21 天的倒计时，还有一个“特别优惠：买得越多，省得越多”的套餐专区。几乎每一支国家队都要花钱；只有少数几支是免费的。\u003c/p\u003e\n\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/the-update-nobody-asked-for/images/duolingo-store_zh.png\" title=\"\" data-thumbnail=\"/the-update-nobody-asked-for/images/duolingo-store_zh.png\" data-sub-html=\"\u003ch2\u003e多儿杯商店：你祖国球队的球衣要 1.99 美元。阿根廷、法国、克罗地亚、西班牙、墨西哥、日本、塞内加尔……几乎全都收费，配上 21 天倒计时和套餐折扣。作者本人的截图，2026 年 6 月。\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/the-update-nobody-asked-for/images/duolingo-store_zh.png\" srcset=\"/the-update-nobody-asked-for/images/duolingo-store_zh_hu9061cee63bb02369414663bf7ebd7fd0_256786_800x0_resize_q75_h2_box_3.webp 800w, /the-update-nobody-asked-for/images/duolingo-store_zh_hu9061cee63bb02369414663bf7ebd7fd0_256786_1200x0_resize_q75_h2_box_3.webp 1200w, /the-update-nobody-asked-for/images/duolingo-store_zh_hu9061cee63bb02369414663bf7ebd7fd0_256786_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"712\" width=\"906\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003e多儿杯商店：你祖国球队的球衣要 1.99 美元。阿根廷、法国、克罗地亚、西班牙、墨西哥、日本、塞内加尔……几乎全都收费，配上 21 天倒计时和套餐折扣。作者本人的截图，2026 年 6 月。\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003cp\u003e真正耐人寻味的是它的话术：应用说*“用阿根廷球衣表达你的支持！”*，而下面唯一的按钮是\u003cstrong\u003e以 1.99 美元购买\u003c/strong\u003e。如果你真心想为你的球队助威，你就得花钱买：在那个界面上，想支持和付钱是同一个动作。一个语言学习应用，把一个国家的归属感变成了一件标了价的虚拟饰品，还加上一个倒计时来缩短你做决定的时间。\u003c/p\u003e\n\u003cp\u003e其实手边就有一个更有意思的选择：应用本可以给每个用户分配一个国家，让你在全球排行榜上看到每面国旗背后真实的人——一幅关于这个赛事有多么全球化的具体图景。可它选了转化率最高的那套机制：为球衣收费。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e目的不是改进软件，而是在一波本就存在的注意力浪潮消散之前，搭上它。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"这不是足球这是每个国家的每一个事件\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e8%bf%99%e4%b8%8d%e6%98%af%e8%b6%b3%e7%90%83%e8%bf%99%e6%98%af%e6%af%8f%e4%b8%aa%e5%9b%bd%e5%ae%b6%e7%9a%84%e6%af%8f%e4%b8%80%e4%b8%aa%e4%ba%8b%e4%bb%b6\" class=\"header-mark\" aria-label=\"Header mark for '这不是足球：这是每个国家的每一个事件'\"\u003e\u003c/a\u003e3 这不是足球：这是每个国家的每一个事件\u003c/h2\u003e\u003cp\u003e很容易看着这一切就想：“嗨，这是世界杯，四年才一次。”但足球只是这一周里最显眼的例子。这套机制是全球性的，而且从不停歇：总有一个事件可以攀附，在某本日历上，在某种文化里。\u003c/p\u003e\n\u003cp\u003e在\u003cstrong\u003e中国\u003c/strong\u003e，阿里巴巴发明的\u003cem\u003e双十一\u003c/em\u003e（11.11）把一个本无意义的日子——11 月 11 日——变成了地球上最大的购物盛事：光阿里巴巴一家\u003cstrong\u003e在 2021 年就录得 845 亿美元\u003c/strong\u003e，连同京东一起，单日突破了 \u003cstrong\u003e1390 亿美元\u003c/strong\u003e（\u003ca href=\"https://www.cnbc.com/2021/11/12/china-singles-day-2021-alibaba-jd-hit-record-139-billion-of-sales.html\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eCNBC\u003c/a\u003e、\u003ca href=\"https://chainstoreage.com/alibaba-sets-new-singles-day-record-845-billion-sales\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eChain Store Age\u003c/a\u003e）。一个制造出来的日子，一个应用，一天的紧迫感。\u003c/p\u003e\n\u003cp\u003e在\u003cstrong\u003e印度\u003c/strong\u003e，主角不是足球——是板球。当 IPL 开打时，号称拥有\u003cstrong\u003e超过 2 亿用户\u003c/strong\u003e的梦幻体育应用 \u003cstrong\u003eDream11\u003c/strong\u003e，在赛事第一天就冲上\u003cstrong\u003e超过 1500 万同时在线用户\u003c/strong\u003e，其中板球占了\u003cstrong\u003e所有投注内容的 87%\u003c/strong\u003e（\u003ca href=\"https://restofworld.org/2023/dream11-fantasy-cricket-app-india/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eRest of World\u003c/a\u003e、\u003ca href=\"https://en.wikipedia.org/wiki/Dream11\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eWikipedia\u003c/a\u003e）。整整一个国家，随着每一个回合刷新一个应用。\u003c/p\u003e\n\u003cp\u003e在\u003cstrong\u003e中东\u003c/strong\u003e，事件是阴历：到了斋月，像 \u003cstrong\u003eCareem\u003c/strong\u003e 这样的超级应用会围绕开斋饭（iftar）和封斋饭（suhoor）重新组织整个体验——定时下单、特殊模式、捐赠功能——因为日子本身就变了形状（\u003ca href=\"https://blog.careem.com/posts/ramadan-trends-2025\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eCareem\u003c/a\u003e、\u003ca href=\"https://www.arabianbusiness.com/culture-society/careem-shares-ramadan-2025-trends-massive-remittances-to-india-and-pakistan-most-popular-iftar-orders-top-bookings\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eArabian Business\u003c/a\u003e）。\u003c/p\u003e\n\u003cp\u003e黑色星期五、农历新年、万圣节、超级碗、排灯节：换个国家、换个由头，机制是完全一样的。总有一波浪潮，总有一个应用等在那里，让你不会错过。有意思的问题不是它们\u003cem\u003e为什么\u003c/em\u003e这么做——那很明显：因为转化好。问题是\u003cem\u003e我们怎么\u003c/em\u003e走到了这样一个世界：这成了我们接触的每一件软件的默认行为。\u003c/p\u003e\n\u003ch2 id=\"这是怎么被量产出来的\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e8%bf%99%e6%98%af%e6%80%8e%e4%b9%88%e8%a2%ab%e9%87%8f%e4%ba%a7%e5%87%ba%e6%9d%a5%e7%9a%84\" class=\"header-mark\" aria-label=\"Header mark for '这是怎么被量产出来的'\"\u003e\u003c/a\u003e4 这是怎么被量产出来的\u003c/h2\u003e\u003cp\u003e这不是偶然。今天的 FOMO 是一个设计理念的产物，这个理念经过了半个世纪的打磨，最终成为整个行业的默认引擎。\u003c/p\u003e\n\u003cp\u003e它的起点，在互联网之前，是一个经济学洞见。\u003cstrong\u003e1971 年\u003c/strong\u003e，诺贝尔奖得主**赫伯特·西蒙（Herbert Simon）**写下了定义其后一切的那句话：\u003cem\u003e“信息的富足造成了注意力的贫乏”\u003c/em\u003e（\u003ca href=\"https://en.wikipedia.org/wiki/Attention_economy\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e注意力经济，Wikipedia\u003c/a\u003e）。如果信息是无限的、注意力是有限的，那么注意力就是稀缺资源——而哪里有稀缺资源，哪里就会形成一个市场去攫取它。\u003c/p\u003e\n\u003cp\u003e这个市场在\u003cstrong\u003e可变奖励\u003c/strong\u003e中找到了它的机制，也就是让老虎机让人上瘾的同一个原理：你不知道拉下拉杆会给你点什么还是什么都没有，而正是这种不确定性——不是奖品本身——让你一直拉下去。2014 年，**尼尔·埃亚尔（Nir Eyal）\u003cstrong\u003e在《上瘾》（\u003cem\u003eHooked\u003c/em\u003e）一书中把它包装成了一本产品手册：触发、行动、可变奖励、投入、重复（\u003ca href=\"https://www.thebehavioralscientist.com/articles/an-incomplete-loop-a-review-of-nir-eyals-hooked\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eThe Behavioral Scientist 的书评\u003c/a\u003e）。前 Google 设计师\u003c/strong\u003e特里斯坦·哈里斯（Tristan Harris）*\u003cem\u003e说得更直白：你的手机是\u003c/em\u003e“你口袋里的一台老虎机”*，每次你下拉刷新信息流，你就是在玩它（\u003ca href=\"https://archive-yaleglobal.yale.edu/content/smartphone-addiction-slot-machine-your-pocket\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eYaleGlobal\u003c/a\u003e）。\u003c/p\u003e\n\u003cp\u003e而且这不是阴谋论——亲手造出它的人之一坦白了。2017 年，Facebook 的创始总裁**肖恩·帕克（Sean Parker）\u003cem\u003e\u003cem\u003e说，当年的设计问题字面上就是\u003c/em\u003e“我们如何尽可能多地消耗掉你的时间和有意识的注意力？”\u003cem\u003e答案是时不时给你\u003c/em\u003e“一点多巴胺刺激”\u003cem\u003e——一个赞、一条评论——来构建一个\u003c/em\u003e“社会认同反馈循环”\u003cem\u003e，他说，这个循环\u003c/em\u003e“利用了人类心理中的一个弱点”\u003cem\u003e。他的收尾是：\u003c/em\u003e“天知道这对我们孩子的大脑正在做什么”\u003cem\u003e（\u003ca href=\"https://www.cnbc.com/2017/11/09/facebooks-sean-parker-on-social-media.html\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eCNBC\u003c/a\u003e、\u003ca href=\"https://www.axios.com/2017/12/15/sean-parker-facebook-was-designed-to-exploit-human-vulnerability-1513306782\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eAxios\u003c/a\u003e）。\u003c/em\u003e“我们对此心知肚明。但我们还是这么做了。”\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e由此诞生了两套机制，把 FOMO 量产到了我们都看不见它的地步：\u003c/p\u003e\n\u003cp\u003e**Snapchat 连续记录（streaks）。**也许是消费软件里最具胁迫性的机制：它不只在你每天使用应用时奖励你，还在你停下来时\u003cem\u003e惩罚\u003c/em\u003e你。连续记录是你和一个朋友一起累积起来的一个数字，只要漏掉一天就会被清零。研究将它直接与青少年的智能手机依赖和 FOMO 联系起来（\u003ca href=\"https://www.sciencedirect.com/science/article/pii/S2772503023000476\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eScienceDirect\u003c/a\u003e）。害怕的不是错过什么好东西——而是失去你已经拥有的东西。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eSpotify Wrapped（年度回顾）。\u003cstrong\u003e这是分享型 FOMO 的零号病人，它的狡猾之处堪称精妙。2016 年，Spotify 拿走了你自己的数据——那种通常会吓到你的数据——把它当作一份年终礼物包装好还给你，随时可以发布。它把监视变成了庆祝。打开它的用户从 2017 年的 \u003cstrong\u003e3000 万\u003c/strong\u003e涨到了 2022 年的 \u003cstrong\u003e1.56 亿\u003c/strong\u003e，而在 2023 年它在社交媒体上产生了\u003c/strong\u003e超过 20 亿次曝光\u003c/strong\u003e——由你制作的免费广告，因为你不想在所有人都晒自己的 Wrapped 的那一天被落下（\u003ca href=\"https://en.wikipedia.org/wiki/Spotify_Wrapped\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eWikipedia\u003c/a\u003e、\u003ca href=\"https://theconversation.com/spotify-wrapped-success-story-unpacked-what-are-the-takeaways-251337\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eThe Conversation\u003c/a\u003e）。一旦有一个应用证明了你可以让数百万人在一年中固定的某一天为自己打广告，所有人都想要自己的“Wrapped”。多儿杯就是世界杯的 Wrapped。\u003c/p\u003e\n\u003cp\u003e而和任何被推向极端的机制一样，它也有自己的漫画式夸张版：\u003cstrong\u003eBeReal\u003c/strong\u003e。这个应用\u003cem\u003e唯一\u003c/em\u003e的功能就是最纯粹形态的 FOMO——在随机的某个时刻推送一条通知，给你两分钟拍照，外加“该 BeReal 了”的那种慌乱。它运转得太成功了，\u003cstrong\u003e68% 的用户在通知发出后 3 分钟内就打开了应用\u003c/strong\u003e。然后它蒸发了，就像注意力经济里一切都会蒸发的那样。\u003c/p\u003e\n\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/the-update-nobody-asked-for/images/massification-decline_zh.png\" title=\"\" data-thumbnail=\"/the-update-nobody-asked-for/images/massification-decline_zh.png\" data-sub-html=\"\u003ch2\u003e同一条曲线，镜像对称。Spotify Wrapped 在向上攀升（每年 12 月打开它的用户），而 BeReal 在 2022 年的峰值之后泄气下滑（月活跃用户）。来源：Wikipedia（Wrapped）、Business of Apps（BeReal）。\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/the-update-nobody-asked-for/images/massification-decline_zh.png\" srcset=\"/the-update-nobody-asked-for/images/massification-decline_zh_huda539335f5df043f89db700471e67a4f_81582_800x0_resize_q75_h2_box_3.webp 800w, /the-update-nobody-asked-for/images/massification-decline_zh_huda539335f5df043f89db700471e67a4f_81582_1200x0_resize_q75_h2_box_3.webp 1200w, /the-update-nobody-asked-for/images/massification-decline_zh_huda539335f5df043f89db700471e67a4f_81582_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"540\" width=\"1200\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003e同一条曲线，镜像对称。Spotify Wrapped 在向上攀升（每年 12 月打开它的用户），而 BeReal 在 2022 年的峰值之后泄气下滑（月活跃用户）。来源：Wikipedia（Wrapped）、Business of Apps（BeReal）。\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003cp\u003eBeReal 从 2022 年 8 月的 \u003cstrong\u003e7300 万\u003c/strong\u003e月活跃用户掉到了 2023 年 3 月的 \u003cstrong\u003e3300 万\u003c/strong\u003e，它的下载量从 2023 年到 2024 年崩跌了 \u003cstrong\u003e60%\u003c/strong\u003e（\u003ca href=\"https://www.businessofapps.com/data/bereal-statistics/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eBusiness of Apps\u003c/a\u003e、\u003ca href=\"https://en.wikipedia.org/wiki/BeReal\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eWikipedia\u003c/a\u003e）。FOMO 沿着同一条曲线起落：像火箭一样蹿升的那份注意力，正是之后什么也托不住的那份注意力。这对一个完整的应用是这样，对一个车标上的小国旗——在四周的时间尺度上——也是这样。\u003c/p\u003e\n\u003ch2 id=\"重要的是数字不是内容\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e9%87%8d%e8%a6%81%e7%9a%84%e6%98%af%e6%95%b0%e5%ad%97%e4%b8%8d%e6%98%af%e5%86%85%e5%ae%b9\" class=\"header-mark\" aria-label=\"Header mark for '重要的是数字，不是内容'\"\u003e\u003c/a\u003e5 重要的是数字，不是内容\u003c/h2\u003e\u003cp\u003e把这一切串起来的，是我们不再衡量贡献，转而开始衡量当下。重要的是在场，而不是你说了什么。而且这不只是应用：严肃的新闻编辑室同样在追逐数字，而不是内容。一则曾经能持续数周的报道，如今只能持续数天，因为重要的不是深度，而是及时赶上浪潮。\u003c/p\u003e\n\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/the-update-nobody-asked-for/images/newspapers-real_zh.png\" title=\"\" data-thumbnail=\"/the-update-nobody-asked-for/images/newspapers-real_zh.png\" data-sub-html=\"\u003ch2\u003e2026 年 6 月 12 日星期五，揭幕战次日的真实头版：来自 11 个国家的 20 个封面（来自 kiosko.net）。从《号角报》（Clarín）到《纽约时报》，从《队报》（L\u0026rsquo;Équipe）到《每日邮报》，它们全都把它放上了头版。为编辑性评论而转载。\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/the-update-nobody-asked-for/images/newspapers-real_zh.png\" srcset=\"/the-update-nobody-asked-for/images/newspapers-real_zh_hu6367727ceb15580e130c84a26b51b760_2555097_800x0_resize_q75_h2_box_3.webp 800w, /the-update-nobody-asked-for/images/newspapers-real_zh_hu6367727ceb15580e130c84a26b51b760_2555097_1200x0_resize_q75_h2_box_3.webp 1200w, /the-update-nobody-asked-for/images/newspapers-real_zh_hu6367727ceb15580e130c84a26b51b760_2555097_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"1490\" width=\"1274\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003e2026 年 6 月 12 日星期五，揭幕战次日的真实头版：来自 11 个国家的 20 个封面（来自 kiosko.net）。从《号角报》（Clarín）到《纽约时报》，从《队报》（L\u0026rsquo;Équipe）到《每日邮报》，它们全都把它放上了头版。为编辑性评论而转载。\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003cp\u003e这是有数据可证的。《Nature Communications》上的一项研究，\u003ca href=\"https://www.nature.com/articles/s41467-019-09311-w\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003cem\u003e“集体注意力的加速动态”\u003c/em\u003e\u003c/a\u003e（\u003ca href=\"https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6465266/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003ePMC 上的免费版本\u003c/a\u003e），发现一个进入 Twitter 前 50 名的话题标签，在 \u003cstrong\u003e2013 年大约能停留 17.5 小时\u003c/strong\u003e，到 \u003cstrong\u003e2016 年只剩 11.9 小时\u003c/strong\u003e。同样的模式出现在票房的上映周期里、在科学引用里、在一个世纪前的 Google Books 数据里。作者的结论是：集体注意力有一个固定的容量，但我们不断往里塞进更多东西来争夺它，于是每个话题烧得更快，下一个立刻把它踩在脚下。\u003c/p\u003e\n\u003cp\u003e这就是文明尺度上的 FOMO。这种\u003cem\u003e错失恐惧\u003c/em\u003e——自 2013 年起就用 \u003ca href=\"https://pmc.ncbi.nlm.nih.gov/articles/PMC10943642/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003ePrzybylski 量表\u003c/a\u003e测量，该量表把它与焦虑、更差的睡眠和有问题的手机使用关联起来——已经不再是个人问题，而成了商业模式。Uber 的国旗、PedidosYa 的优惠券、Duolingo 的球衣：三者都押注于你害怕被排除在外。而它们几乎总是押对了。\u003c/p\u003e\n\u003ch2 id=\"这场派对看不见的成本\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e8%bf%99%e5%9c%ba%e6%b4%be%e5%af%b9%e7%9c%8b%e4%b8%8d%e8%a7%81%e7%9a%84%e6%88%90%e6%9c%ac\" class=\"header-mark\" aria-label=\"Header mark for '这场派对看不见的成本'\"\u003e\u003c/a\u003e6 这场派对看不见的成本\u003c/h2\u003e\u003cp\u003e这一切都不是免费的，哪怕它免费送到你面前。\u003c/p\u003e\n\u003cp\u003e这些“庆祝”中的每一个，都是一次真实的部署：新的 iOS 和 Android 构建、远程配置、功能开关、横幅、素材资源、用来衡量有多少人点了那面国旗的遥测数据。把它乘以每一个加入了世界杯的应用，再乘以它们并行维护的每一个平台。这是一笔巨大的工程、数据和能源开销，花在了四周后就会被删掉的东西上。\u003c/p\u003e\n\u003cp\u003e而且这些数据并不抽象。根据\u003ca href=\"https://www.iea.org/reports/energy-and-ai/executive-summary\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e国际能源署\u003c/a\u003e，到 2030 年，全球数据中心的用电量将\u003cstrong\u003e翻一倍以上，达到约 945 太瓦时（TWh）\u003c/strong\u003e——大致相当于日本一年的全部消耗。显然，这其中并非全是足球国旗。但每一个昙花一现的功能、每一个遥测事件、每一次向数百万人投放的实时优惠券，都在累加到一个已经重得抵得上整个国家的基础设施上。我们生成汪洋般的数据，来庆祝一件我们终将忘记的事。而这还只是电力：全世界每年已经产生 \u003ca href=\"https://unitar.org/about/news-stories/press/global-e-waste-monitor-2024-electronic-waste-rising-five-times-faster-documented-e-waste-recycling\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e6200 万吨电子垃圾\u003c/a\u003e，其回收速度比堆积速度慢五倍。\u003c/p\u003e\n\u003cp\u003e还有一项几乎没人在这些办公室里考虑过的成本：不是每个人都有数据流量可以花在一面国旗上。GSMA 估计，\u003cstrong\u003e31 亿人——占全球 38%——头顶上有移动互联网信号，却并不使用它\u003c/strong\u003e，而主要障碍之一就是成本（\u003ca href=\"https://www.gsma.com/r/wp-content/uploads/2024/10/The-State-of-Mobile-Internet-Connectivity-Report-Key-Findings-2024.pdf\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eGSMA，《2024 年移动互联网连接状况》\u003c/a\u003e）。\u003ca href=\"https://a4ai.org/affordable-internet-is-1-for-2/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e可负担互联网联盟（Alliance for Affordable Internet）\u003c/a\u003e将 1GB 价格在月收入 2% 及以下定义为“可负担”；在 99 个中低收入国家中，\u003cstrong\u003e只有 31 个达到了这一目标\u003c/strong\u003e，其余国家平均要为 1GB 支付\u003cstrong\u003e收入的 5.76%\u003c/strong\u003e。在阿根廷，移动套餐的价格\u003ca href=\"https://www.cronista.com/infotechnology/actualidad/argentina-tiene-uno-de-los-planes-de-celular-mas-caros-y-vale-cuatro-veces-el-precio-uruguayo/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e高达乌拉圭的四倍\u003c/a\u003e，精打细算地省着用流量是常态——以至于在 2026 年 6 月，一家公司推出了一种\u003ca href=\"https://www.canal26.com/general/2026/06/26/internet-sin-vueltas-en-la-calle-como-funciona-el-nuevo-wifi-prepago-de-telecentro-que-se-puede-usar-sin-ser-cliente/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e街头的*“预付费 WiFi”*，正是为你流量用光时设计的\u003c/a\u003e。那个靠免费 WiFi 二维码过活、不到熟悉的网络就一直关着流量的人，并不是什么边缘个案：那是半个人类。对那个人来说，一个为了一桩他从没要求过的事件而塞满动画、遥测和素材的应用，不是庆祝——而是一道收费关卡。他套餐里的兆字节，被烧掉，只为让一个品牌赶上一股潮流。\u003c/p\u003e\n\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/the-update-nobody-asked-for/images/growth_zh.png\" title=\"\" data-thumbnail=\"/the-update-nobody-asked-for/images/growth_zh.png\" data-sub-html=\"\u003ch2\u003e狂热背后的三条曲线：事件（双十一 GMV，以美元计）、数据（每台智能手机的 GB 数）和部署（每天新增的 iOS 应用数）。一切都在增长；什么都不停下。来源：Wikipedia、爱立信《2025 移动报告》、42matters。\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/the-update-nobody-asked-for/images/growth_zh.png\" srcset=\"/the-update-nobody-asked-for/images/growth_zh_hu14af900768c614a7a997b78334789a10_77542_800x0_resize_q75_h2_box_3.webp 800w, /the-update-nobody-asked-for/images/growth_zh_hu14af900768c614a7a997b78334789a10_77542_1200x0_resize_q75_h2_box_3.webp 1200w, /the-update-nobody-asked-for/images/growth_zh_hu14af900768c614a7a997b78334789a10_77542_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"420\" width=\"1200\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003e狂热背后的三条曲线：事件（双十一 GMV，以美元计）、数据（每台智能手机的 GB 数）和部署（每天新增的 iOS 应用数）。一切都在增长；什么都不停下。来源：Wikipedia、爱立信《2025 移动报告》、42matters。\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003ch2 id=\"没人记得的潮流游行\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e6%b2%a1%e4%ba%ba%e8%ae%b0%e5%be%97%e7%9a%84%e6%bd%ae%e6%b5%81%e6%b8%b8%e8%a1%8c\" class=\"header-mark\" aria-label=\"Header mark for '没人记得的潮流游行'\"\u003e\u003c/a\u003e7 没人记得的潮流游行\u003c/h2\u003e\u003cp\u003e这就是核心论点。世界杯是个容易讲的例子，因为它有明确的起止日期。但整个行业都是这么运转的：一股潮流出现，所有人一拥而上，它持续几个月，影响到那些从没要求过它的人，然后消失。然后没人再提起它。\u003c/p\u003e\n\u003cp\u003e最新鲜的例子是 \u003cstrong\u003eOpenClaw\u003c/strong\u003e。还记得它吗？它起初是一个名叫 Clawdbot 的个人项目，先后改名为 Moltbot、再到 OpenClaw，并在 \u003cstrong\u003e2026 年 2 月突破了 10 万颗 GitHub 星标\u003c/strong\u003e——到了 3 月，它超过了 React（后者停留在 24.3 万左右），成为史上在如此短时间内星标数最高的非聚合类仓库之一（\u003ca href=\"https://www.kdnuggets.com/openclaw-explained-the-free-ai-agent-tool-going-viral-already-in-2026\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eKDnuggets\u003c/a\u003e、\u003ca href=\"https://en.wikipedia.org/wiki/OpenClaw\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eWikipedia\u003c/a\u003e）。《财富》（\u003cem\u003eFortune\u003c/em\u003e）把它当作*“最新的狂热”*来报道，称这股“养龙虾”的热潮正在改变中国的 AI 行业（\u003ca href=\"https://fortune.com/2026/03/14/openclaw-china-ai-agent-boom-open-source-lobster-craze-minimax-qwen/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eFortune\u003c/a\u003e）。与此同时，一份 Bitsight 安全分析发现，\u003cstrong\u003e有超过 3 万个 OpenClaw 实例暴露在互联网上\u003c/strong\u003e，其中许多被用户错误配置，他们一路点掉了那些警告（\u003ca href=\"https://www.bitsight.com/blog/openclaw-ai-security-risks-exposed-instances\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eBitsight\u003c/a\u003e）。它的创造者去了 OpenAI，而这场狂热也以它兴起时同样的速度开始消退。\u003c/p\u003e\n\u003cp\u003e一场持续数月的狂热。真实的人被暴露。而现在几乎没人再提起它。\u003c/p\u003e\n\u003cp\u003e这并不新鲜。这就是 \u003ca href=\"https://en.wikipedia.org/wiki/Gartner_hype_cycle\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eGartner 的炒作周期（Hype Cycle）\u003c/a\u003e，早在 1995 年就被描述过：每一项技术都会爬上一座“期望膨胀的顶峰”，然后在实验未能兑现时跌入“幻灭的低谷”。它们全都走过这条路：\u003c/p\u003e\n\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/the-update-nobody-asked-for/images/hype-cycle_zh.png\" title=\"\" data-thumbnail=\"/the-update-nobody-asked-for/images/hype-cycle_zh.png\" data-sub-html=\"\u003ch2\u003e同一张图，一遍又一遍：每一项技术都爬上期望膨胀的顶峰，又跌入幻灭的低谷。每跌下去十项，就有六项再也不会回来。来源：Gartner 炒作周期。（图表标注为西班牙语。）\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/the-update-nobody-asked-for/images/hype-cycle_zh.png\" srcset=\"/the-update-nobody-asked-for/images/hype-cycle_zh_huf9b52dc10442dce762d18f03fc6018ad_73772_800x0_resize_q75_h2_box_3.webp 800w, /the-update-nobody-asked-for/images/hype-cycle_zh_huf9b52dc10442dce762d18f03fc6018ad_73772_1200x0_resize_q75_h2_box_3.webp 1200w, /the-update-nobody-asked-for/images/hype-cycle_zh_huf9b52dc10442dce762d18f03fc6018ad_73772_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"540\" width=\"1200\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003e同一张图，一遍又一遍：每一项技术都爬上期望膨胀的顶峰，又跌入幻灭的低谷。每跌下去十项，就有六项再也不会回来。来源：Gartner 炒作周期。（图表标注为西班牙语。）\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003cp\u003e区块链从顶峰跌入低谷，然后就停在了那里：据 Gartner 自己所说，它的大多数应用至今仍\u003ca href=\"https://www.ciodive.com/news/most-blockchain-applications-sunk-in-the-trough-of-disillusionment-gartn/564613/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e深陷在幻灭的低谷里\u003c/a\u003e。物联网、大数据、无服务器架构：每一个在它自己的时代，都曾是那个将要改变一切的东西。在每一项跌入低谷的技术中，\u003cstrong\u003e十项里有六项再也爬不出来\u003c/strong\u003e。\u003c/p\u003e\n\u003cp\u003e而所有潮流之母甚至根本不是数字化的：是把工作从美洲挪到亚洲。它被当作进步、当作效率、当作不可避免来兜售。据\u003ca href=\"https://www.epi.org/publication/china-trade-outsourcing-and-jobs/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e经济政策研究所（Economic Policy Institute）\u003c/a\u003e估计，2001 年到 2013 年间，对华贸易逆差让美国付出了 \u003cstrong\u003e320 万个工作岗位\u003c/strong\u003e的代价，其中 \u003cstrong\u003e240 万个在制造业\u003c/strong\u003e。那不是一面四周后就抹掉的国旗；那永久地重塑了一座座城市。但其心理机制是完全一样的：一个所有人同时采纳的想法，因为没人想被落下，而代价由别处的另一个人来承担。\u003c/p\u003e\n\u003ch2 id=\"今天的潮流给一切都贴上智能体\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e4%bb%8a%e5%a4%a9%e7%9a%84%e6%bd%ae%e6%b5%81%e7%bb%99%e4%b8%80%e5%88%87%e9%83%bd%e8%b4%b4%e4%b8%8a%e6%99%ba%e8%83%bd%e4%bd%93\" class=\"header-mark\" aria-label=\"Header mark for '今天的潮流：给一切都贴上“智能体”'\"\u003e\u003c/a\u003e8 今天的潮流：给一切都贴上“智能体”\u003c/h2\u003e\u003cp\u003e如果说 OpenClaw 是病毒式的顶峰，那么 **AI“智能体”（agents）**就是紧随其后涌来的那波企业浪潮。在 2025 和 2026 年，没有一家大型软件公司不宣布自己的智能体平台。我不需要罗列一百个 logo——那个汇总数字说明的更多。\u003c/p\u003e\n\u003cp\u003eGartner 估计，在\u003cstrong\u003e成千上万家\u003c/strong\u003e声称在卖“智能体 AI（agentic AI）”的厂商中，\u003cstrong\u003e真正名副其实的只有大约 130 家\u003c/strong\u003e——其余的都在搞它所称的*“智能体洗白（agent washing）”*：把他们本就有的聊天机器人、RPA 或助手，重新包装成“智能体”。它还预测，由于成本不断攀升、商业价值不明、风险控制不足，\u003cstrong\u003e到 2027 年底，超过 40% 的智能体 AI 项目将被取消\u003c/strong\u003e（\u003ca href=\"https://www.gartner.com/en/newsroom/press-releases/2025-06-25-gartner-predicts-over-40-percent-of-agentic-ai-projects-will-be-canceled-by-end-of-2027\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eGartner，2025 年 6 月\u003c/a\u003e）。\u003c/p\u003e\n\u003cp\u003e*成千上万家公司在卖智能体，真正的只有 130 家。*这就是你那张一百个应用的截图，浓缩成了一个数字。不过举几个大名字，好让你看清这场雪崩：\u003c/p\u003e\n\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003e公司\u003c/th\u003e\n\u003cth\u003e他们交付了什么\u003c/th\u003e\n\u003cth\u003e来源\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eSalesforce\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eAgentforce——遍布整个企业的智能体，被宣传为“智能体化企业”\u003c/td\u003e\n\u003ctd\u003e\u003ca href=\"https://www.salesforce.com/news/press-releases/2025/10/13/agentic-enterprise-announcement/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eSalesforce\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eMicrosoft\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eCopilot +“Agent 365”，在 Ignite 2025 上发布\u003c/td\u003e\n\u003ctd\u003e\u003ca href=\"https://www.microsoft.com/en-us/microsoft-365/blog/2025/11/18/microsoft-ignite-2025-copilot-and-agents-built-to-power-the-frontier-firm/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eMicrosoft\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eOracle\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003e遍布 Fusion 应用的新智能体 + AI Agent Studio\u003c/td\u003e\n\u003ctd\u003e\u003ca href=\"https://www.oracle.com/news/announcement/ai-world-oracle-advances-enterprise-ai-with-new-agents-across-fusion-applications-2025-10-15/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eOracle\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eGoogle\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eAgentspace / Vertex AI Agent Builder\u003c/td\u003e\n\u003ctd\u003e\u003ca href=\"https://cloud.google.com/blog/products/ai-machine-learning/google-agentspace-enables-the-agent-driven-enterprise\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eGoogle Cloud\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eServiceNow\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003e智能体 AI 创新；收购了 Moveworks\u003c/td\u003e\n\u003ctd\u003e\u003ca href=\"https://newsroom.servicenow.com/press-releases/details/2025/ServiceNow-announces-new-agentic-AI-innovations-to-autonomously-solve-the-most-complex-enterprise-challenges-01-29-2025-traffic/default.aspx\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eServiceNow\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eSAP\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eJoule 智能体 + 嵌入式智能\u003c/td\u003e\n\u003ctd\u003e\u003ca href=\"https://news.sap.com/2025/10/sap-connect-business-ai-new-joule-agents-embedded-intelligence/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eSAP\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e这个模式总是一样的，对吧？不是“我有一个问题，我把它解决得更好”。而是“有一个气球正在膨胀，我想在它爆掉之前攀上去”。进步变成了一桩生意，而不是一项改进。等到 2027 年这些项目中有 40% 被取消时，我们都会望向别处，谈论着下一个不管是什么的潮流。\u003c/p\u003e\n\u003ch2 id=\"这些数字\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e8%bf%99%e4%ba%9b%e6%95%b0%e5%ad%97\" class=\"header-mark\" aria-label=\"Header mark for '这些数字'\"\u003e\u003c/a\u003e9 这些数字\u003c/h2\u003e\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/the-update-nobody-asked-for/images/numbers-grouped_zh.png\" title=\"\" data-thumbnail=\"/the-update-nobody-asked-for/images/numbers-grouped_zh.png\" data-sub-html=\"\u003ch2\u003e文中的那些数字，被归入它们的三台引擎：事件的峰值、FOMO 机器，以及它的代价。每一个来源都在正文中链接。（图表标注为西班牙语。）\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/the-update-nobody-asked-for/images/numbers-grouped_zh.png\" srcset=\"/the-update-nobody-asked-for/images/numbers-grouped_zh_huc1bb4498d36dfc22c2dd5bb3ab68bb1b_149570_800x0_resize_q75_h2_box_3.webp 800w, /the-update-nobody-asked-for/images/numbers-grouped_zh_huc1bb4498d36dfc22c2dd5bb3ab68bb1b_149570_1200x0_resize_q75_h2_box_3.webp 1200w, /the-update-nobody-asked-for/images/numbers-grouped_zh_huc1bb4498d36dfc22c2dd5bb3ab68bb1b_149570_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"760\" width=\"1200\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003e文中的那些数字，被归入它们的三台引擎：事件的峰值、FOMO 机器，以及它的代价。每一个来源都在正文中链接。（图表标注为西班牙语。）\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003ch2 id=\"明天我们就会忘了它\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e6%98%8e%e5%a4%a9%e6%88%91%e4%bb%ac%e5%b0%b1%e4%bc%9a%e5%bf%98%e4%ba%86%e5%ae%83\" class=\"header-mark\" aria-label=\"Header mark for '明天我们就会忘了它'\"\u003e\u003c/a\u003e10 明天我们就会忘了它\u003c/h2\u003e\u003cp\u003e当世界杯结束，小车会卸下国旗，优惠券会熄灭，多儿会换装，没人会再在乎那件辛辛苦苦刷出来的球衣。那个专为赛事打造的 TikTok 应用，会在商店里僵死。我们为这一切部署的基础设施会被拆掉，而我们会开始琢磨下一件事。因为记住是昂贵的。\u003c/p\u003e\n\u003cp\u003e这才是这整台机器真正的功能——帕克、埃亚尔和其他人设计的那台，Snapchat 和 Spotify 完善的那台：不是让你记住，而是让你永不停止观看。如果你一直在想着上周那面国旗，你就腾不出来给下周的潮流了。遗忘不是副作用；遗忘就是产品本身。\u003c/p\u003e\n\u003cp\u003e一段进球动画或一件足球球衣本身没有任何问题。值得注意的是，它们已经成了消费软件野心的\u003cem\u003e顶点\u003c/em\u003e。我们曾经要等上几个月才能等来一次更新，因为它会改进我们每天都在用的某样东西。今天我们每天更新，是为了让你别忘了观看——而且，最重要的是，为了让你去观看一件以前从没让你感兴趣过的东西：一场与你无关的两个国家之间的比赛，一个对你的生活毫无改变的结果，一个发生在几千公里之外的事件，而你无论如何都会听说它，因为应用确保了你无法忽视它。你被训练成对那些你昨天甚至都不知道存在的东西产生 FOMO。\u003c/p\u003e\n\u003cp\u003e下次当一个应用一夜之间变了样，问自己一件事：这是在为我服务，还是在为留住我服务？你几乎总会知道答案。而且几乎总是，到了明天，你不会记得——肯定有什么更紧急的事需要你的注意力，而你绝不能错过它。\u003c/p\u003e\n",
        "language": "zh-cn"
    },
    {
        "title" : "盗版世界杯直播流到底是怎么运作的",
        "date_published" : "2026-06-17T00:00:00Z",
        "date_modified" : "2026-06-17T00:00:00Z",
        "id" : "https://misael.org/zh-cn/clearkey-drm-world-cup-2026/",
        "url" : "https://misael.org/zh-cn/clearkey-drm-world-cup-2026/",
        "summary": "我去研究盗版流媒体网站和安卓应用到底是怎么播放世界杯的。DRM 密钥就在 HTML 里，应用的加密是 AES-ECB，而所谓的\u0026rsquo;授权服务器\u0026rsquo;不过是一个把密钥写在查询字符串里的 URL。",
        "content_html" : "\u003cp\u003e我想搞清楚那个明摆在眼前的小把戏到底是怎么运作的。盗版流媒体网站一直都存在，但在世界杯期间，你打开任何一个体育论坛，总能看到有人甩出一条直播比赛的链接——全高清、不卡顿、不用登录。我一直以为底层有什么玄妙的东西。一个朋友在英格兰对克罗地亚那场比赛前给我发了一条链接。\u0026ldquo;看这个，\u0026ldquo;他说。于是我打开了 DevTools。\u003c/p\u003e\n\u003cp\u003e我本以为会看到一个抓取来的 HLS 播放列表，或者一个转推的信号流。结果我看到的却是一个 \u003ccode\u003e\u0026lt;video\u0026gt;\u003c/code\u003e 标签、一个指向合法 Akamai CDN 的 Shaka Player 实例，以及一个 \u003ccode\u003e\u0026lt;script\u0026gt;\u003c/code\u003e 块里那两串十六进制字符串——所有的活儿都是它们干的。那两串十六进制字符串就是 DRM 解密密钥。就在页面源码里。没有藏在 API 后面。没有加密。只是裹在一层看起来吓人、实际上什么都不干的 JavaScript 里。\u003c/p\u003e\n\u003cp\u003e后来有人给我看了一个安卓应用，它对每一场世界杯比赛都做着同样的事情——但是\u0026quot;很正规地\u0026quot;做的，有加密的配置、Firebase Remote Config、一个授权服务器，应有尽有。一个专门的\u0026quot;MUNDIAL FIFA 2026\u0026quot;板块，每场比赛有 28 个频道，包括多机位、球员视角和教练视角。我花了大约一个小时才意识到，这就是同一个漏洞穿上了一身更体面的西装。\u003c/p\u003e\n\u003cp\u003e最初只是三十分钟的好奇，结果变成了一整个周末。到最后，我从网页这边绘制出了\u003cstrong\u003e横跨 33 个国家、169 个 CDN 的 670 路直播流\u003c/strong\u003e，又从\u003cstrong\u003e单单一个安卓应用里整理出了另外 525 个频道\u003c/strong\u003e——全都由一种被 W3C 自己称为\u0026quot;测试\u0026quot;工具的 DRM 方案保护着。而这正发生在一届世界杯期间——英格兰正把四个球灌进克罗地亚的球门，而每一个进球都同时出现在数百路未经授权的直播流上。\u003c/p\u003e\n\u003cdiv class=\"details admonition warning open\"\u003e\n    \u003cdiv class=\"details-summary admonition-title\"\u003e\n        \u003cspan class=\"icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 576 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z\"/\u003e\u003c/svg\u003e\u003c/span\u003e免责声明\u003cspan class=\"details-icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"details-content\"\u003e\n        \u003cdiv class=\"admonition-content\"\u003e**这是一篇漏洞分析。**文中不发布任何真实密钥、直播流 URL 或可用工具。应用名称和域名均已隐去。目的是记录各平台部署 DRM 时存在的系统性弱点——而非助长盗版。\u003c/div\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ch2 id=\"把密码刻在背面的挂锁\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e6%8a%8a%e5%af%86%e7%a0%81%e5%88%bb%e5%9c%a8%e8%83%8c%e9%9d%a2%e7%9a%84%e6%8c%82%e9%94%81\" class=\"header-mark\" aria-label=\"Header mark for '把密码刻在背面的挂锁'\"\u003e\u003c/a\u003e1 把密码刻在背面的挂锁\u003c/h2\u003e\u003cp\u003e每一个播放加密视频的浏览器都会用到 W3C 的 Encrypted Media Extensions（EME）。EME 支持四种密钥系统。其中三种——Widevine、FairPlay、PlayReady——使用授权服务器、加密的密钥交换以及由硬件支撑的解密。第四种是 ClearKey。\u003c/p\u003e\n\u003cp\u003eClearKey 以明文形式把解密密钥发给浏览器。\u003c/p\u003e\n\u003cp\u003e就这么简单。和 Widevine 一样的 AES-128 加密。一样的 MPEG-DASH 分发。但 Widevine 是通过安全通道协商密钥，并在浏览器无法窥探的硬件沙箱里解密视频，而 ClearKey 则是把原始密钥直接交给 JavaScript，说一句\u0026quot;给你\u0026rdquo;。\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-1\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    WIDEVINE L1                          CLEARKEY\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ───────────────────────              ───────────────────────\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    License server (HTTPS)               No license server\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Encrypted key exchange               Key in plaintext JS\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Hardware TEE decryption              Software decryption\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Key never in JS memory               Key IS the JS\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Per-device, per-session policy       No policy at all\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eDASH Industry Forum 表示 ClearKey \u0026ldquo;仅建议用于测试目的\u0026rdquo;。但有些平台还是决定在生产环境里使用它。我分析的那些网站和那个应用，都依赖于这个决定。\u003c/p\u003e\n\u003ch2 id=\"第一部分网站\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e4%b8%80%e9%83%a8%e5%88%86%e7%bd%91%e7%ab%99\" class=\"header-mark\" aria-label=\"Header mark for '第一部分：网站'\"\u003e\u003c/a\u003e2 第一部分：网站\u003c/h2\u003e\u003cp\u003e我检视了数十个盗版流媒体网站。它们全都遵循同一种套路。\u003c/p\u003e\n\u003ch3 id=\"架构\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e6%9e%b6%e6%9e%84\" class=\"header-mark\" aria-label=\"Header mark for '架构'\"\u003e\u003c/a\u003e2.1 架构\u003c/h3\u003e\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-2\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"err\"\u003e┌──────────────────────────────────────────────────────────────┐\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"err\"\u003e│\u003c/span\u003e                    \u003cspan class=\"n\"\u003eHOW\u003c/span\u003e \u003cspan class=\"n\"\u003eIT\u003c/span\u003e \u003cspan class=\"n\"\u003eACTUALLY\u003c/span\u003e \u003cspan class=\"n\"\u003eWORKS\u003c/span\u003e                     \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"err\"\u003e└──────────────────────────────────────────────────────────────┘\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eSTEP\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e                    \u003cspan class=\"n\"\u003eSTEP\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e                    \u003cspan class=\"n\"\u003eSTEP\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eUser\u003c/span\u003e \u003cspan class=\"n\"\u003evisits\u003c/span\u003e               \u003cspan class=\"n\"\u003eClicks\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e \u003cspan class=\"n\"\u003echannel\u003c/span\u003e          \u003cspan class=\"n\"\u003ePage\u003c/span\u003e \u003cspan class=\"n\"\u003eloads\u003c/span\u003e \u003cspan class=\"n\"\u003ean\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eiframe\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003epirate\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003esite\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eco\u003c/span\u003e            \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ee\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eg\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Sky Sports\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e       \u003cspan class=\"n\"\u003efrom\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e \u003cspan class=\"n\"\u003edifferent\u003c/span\u003e \u003cspan class=\"n\"\u003edomain\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"err\"\u003e┌──────────────┐\u003c/span\u003e          \u003cspan class=\"err\"\u003e┌──────────────┐\u003c/span\u003e          \u003cspan class=\"err\"\u003e┌──────────────────┐\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"err\"\u003e│\u003c/span\u003e              \u003cspan class=\"err\"\u003e│\u003c/span\u003e          \u003cspan class=\"err\"\u003e│\u003c/span\u003e  \u003cspan class=\"err\"\u003e┌────┐┌────┐│\u003c/span\u003e          \u003cspan class=\"err\"\u003e│\u003c/span\u003e  \u003cspan class=\"n\"\u003eplayer\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003ehost\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eco\u003c/span\u003e  \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"err\"\u003e│\u003c/span\u003e  \u003cspan class=\"n\"\u003eChannel\u003c/span\u003e     \u003cspan class=\"err\"\u003e│\u003c/span\u003e  \u003cspan class=\"n\"\u003eclick\u003c/span\u003e   \u003cspan class=\"err\"\u003e│\u003c/span\u003e  \u003cspan class=\"err\"\u003e│\u003c/span\u003e\u003cspan class=\"n\"\u003eESPN\u003c/span\u003e\u003cspan class=\"err\"\u003e││\u003c/span\u003e\u003cspan class=\"n\"\u003eDAZN\u003c/span\u003e\u003cspan class=\"err\"\u003e││\u003c/span\u003e  \u003cspan class=\"n\"\u003eiframe\u003c/span\u003e  \u003cspan class=\"err\"\u003e│\u003c/span\u003e                  \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"err\"\u003e│\u003c/span\u003e  \u003cspan class=\"n\"\u003eGrid\u003c/span\u003e \u003cspan class=\"n\"\u003ePage\u003c/span\u003e   \u003cspan class=\"err\"\u003e│\u003c/span\u003e \u003cspan class=\"err\"\u003e───────\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"err\"\u003e│\u003c/span\u003e  \u003cspan class=\"err\"\u003e├────┤├────┤│\u003c/span\u003e \u003cspan class=\"err\"\u003e───────\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"err\"\u003e│\u003c/span\u003e  \u003cspan class=\"n\"\u003eShaka\u003c/span\u003e \u003cspan class=\"n\"\u003ePlayer\u003c/span\u003e    \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"err\"\u003e│\u003c/span\u003e              \u003cspan class=\"err\"\u003e│\u003c/span\u003e          \u003cspan class=\"err\"\u003e│\u003c/span\u003e  \u003cspan class=\"err\"\u003e│\u003c/span\u003e\u003cspan class=\"n\"\u003eSky\u003c/span\u003e \u003cspan class=\"err\"\u003e││\u003c/span\u003e\u003cspan class=\"n\"\u003ebeIN\u003c/span\u003e\u003cspan class=\"err\"\u003e││\u003c/span\u003e          \u003cspan class=\"err\"\u003e│\u003c/span\u003e  \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eDRM\u003c/span\u003e \u003cspan class=\"n\"\u003ekeys\u003c/span\u003e      \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"err\"\u003e│\u003c/span\u003e  \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003estatic\u003c/span\u003e     \u003cspan class=\"err\"\u003e│\u003c/span\u003e          \u003cspan class=\"err\"\u003e│\u003c/span\u003e  \u003cspan class=\"err\"\u003e└────┘└────┘│\u003c/span\u003e          \u003cspan class=\"err\"\u003e│\u003c/span\u003e  \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003emanifest\u003c/span\u003e \u003cspan class=\"n\"\u003eURL\u003c/span\u003e  \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"n\"\u003eHTML\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e      \u003cspan class=\"err\"\u003e│\u003c/span\u003e          \u003cspan class=\"err\"\u003e└──────────────┘\u003c/span\u003e          \u003cspan class=\"err\"\u003e└────────┬─────────┘\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"err\"\u003e└──────────────┘\u003c/span\u003e                                             \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                                 \u003cspan class=\"err\"\u003e│\u003c/span\u003e \u003cspan class=\"n\"\u003efetches\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                                 \u003cspan class=\"err\"\u003e▼\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                    \u003cspan class=\"err\"\u003e┌──────────────────────┐\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                    \u003cspan class=\"err\"\u003e│\u003c/span\u003e  \u003cspan class=\"n\"\u003eLEGITIMATE\u003c/span\u003e \u003cspan class=\"n\"\u003eCDN\u003c/span\u003e      \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                    \u003cspan class=\"err\"\u003e│\u003c/span\u003e  \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eakamaized\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003enet\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e     \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                    \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"n\"\u003eskycdp\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecom\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e        \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                    \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"n\"\u003eindazn\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecom\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e        \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                    \u003cspan class=\"err\"\u003e│\u003c/span\u003e                      \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                    \u003cspan class=\"err\"\u003e│\u003c/span\u003e  \u003cspan class=\"n\"\u003eEncrypted\u003c/span\u003e \u003cspan class=\"n\"\u003eDASH\u003c/span\u003e      \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                    \u003cspan class=\"err\"\u003e│\u003c/span\u003e  \u003cspan class=\"n\"\u003esegments\u003c/span\u003e            \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                    \u003cspan class=\"err\"\u003e└──────────────────────┘\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eThe\u003c/span\u003e \u003cspan class=\"n\"\u003epirate\u003c/span\u003e \u003cspan class=\"n\"\u003esite\u003c/span\u003e \u003cspan class=\"n\"\u003eserves\u003c/span\u003e \u003cspan class=\"n\"\u003eZERO\u003c/span\u003e \u003cspan class=\"n\"\u003evideo\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eThe\u003c/span\u003e \u003cspan class=\"n\"\u003eCDN\u003c/span\u003e \u003cspan class=\"n\"\u003ebill\u003c/span\u003e \u003cspan class=\"n\"\u003ebelongs\u003c/span\u003e \u003cspan class=\"n\"\u003eto\u003c/span\u003e \u003cspan class=\"n\"\u003ethe\u003c/span\u003e \u003cspan class=\"n\"\u003elegitimate\u003c/span\u003e \u003cspan class=\"n\"\u003eprovider\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eThe\u003c/span\u003e \u003cspan class=\"n\"\u003epirate\u003c/span\u003e \u003cspan class=\"n\"\u003esite\u003c/span\u003e \u003cspan class=\"n\"\u003ehosts\u003c/span\u003e \u003cspan class=\"o\"\u003e~\u003c/span\u003e\u003cspan class=\"mi\"\u003e50\u003c/span\u003e\u003cspan class=\"n\"\u003eKB\u003c/span\u003e \u003cspan class=\"n\"\u003eof\u003c/span\u003e \u003cspan class=\"n\"\u003eHTML\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e盗版网站不过是一个知道那两串十六进制字符串的中间人。它把观众的浏览器指向一个真实的 CDN，把解密密钥递过去，剩下的事浏览器自己做。CDN 从头到尾都不知道这个观众并不是付费订阅用户。\u003c/p\u003e\n\u003ch3 id=\"密钥就在源码里\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e5%af%86%e9%92%a5%e5%b0%b1%e5%9c%a8%e6%ba%90%e7%a0%81%e9%87%8c\" class=\"header-mark\" aria-label=\"Header mark for '密钥就在源码里'\"\u003e\u003c/a\u003e2.2 密钥就在源码里\u003c/h3\u003e\u003cp\u003e播放器宿主页面包含一个带有密钥的 \u003ccode\u003e\u0026lt;script\u0026gt;\u003c/code\u003e 块，外面裹着一层混淆，而一个 30 行的 Node 脚本不到一秒就能把它解开：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ejavascript\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-3\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Before: 40 lines of _0x4a2f, IIFEs, string-array lookups\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// After: this is what it actually does\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003edrmKeyId\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;a1b2c3d4e5f6a7b80000000000000000\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003edrmKey\u003c/span\u003e   \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;0123456789abcdef0123456789abcdef\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eplayer\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003econfigure\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003edrm\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003eclearKeys\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003edrmKeyId\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003edrmKey\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eplayer\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eload\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;https://cdn.legitimate-provider.com/manifest.mpd\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e两个变量。两串十六进制字符串。这就是全部的\u0026quot;DRM 保护\u0026rdquo;。那层混淆——变量重命名、字符串数组、控制流扁平化——会被 \u003ccode\u003eeval()\u003c/code\u003e 还原，因为浏览器本身也得运行它。只要浏览器能执行，脚本就能执行。\u003c/p\u003e\n\u003cp\u003e一旦被提取出来，密钥就会通过 GitHub 和 Telegram 上的 M3U 播放列表扩散开去。我找到过一个 M3U 文件，里面有\u003cstrong\u003e横跨 92 个 CDN 的 545 条 ClearKey 条目\u003c/strong\u003e。公开、可搜索、被 Google 收录。从提取到全球分发的时间：几分钟。密钥被吊销的时间：通常永远不会。\u003c/p\u003e\n\u003ch2 id=\"第二部分安卓应用\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e4%ba%8c%e9%83%a8%e5%88%86%e5%ae%89%e5%8d%93%e5%ba%94%e7%94%a8\" class=\"header-mark\" aria-label=\"Header mark for '第二部分：安卓应用'\"\u003e\u003c/a\u003e3 第二部分：安卓应用\u003c/h2\u003e\u003cp\u003e并非每个盗版团伙都只搞一个静态网页。有些会发布一个完整的安卓应用——带登录界面、Firebase 分析、加密配置和专业的 UI。我在世界杯期间分析的一个应用就采用了这种做法。525 个频道。36 个分类。一个专门的\u0026quot;MUNDIAL FIFA 2026\u0026quot;板块，每场比赛都有多机位视角。\u003c/p\u003e\n\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/clearkey-drm-world-cup-2026/images/app-config.png\" title=\"\" data-thumbnail=\"/clearkey-drm-world-cup-2026/images/app-config.png\" data-sub-html=\"\u003ch2\u003e该应用加密的频道配置——横跨 36 个分类的 525 个频道，带有 AES 加密的 URL 和 DRM 凭据。[URL 已隐去]\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/clearkey-drm-world-cup-2026/images/app-config.png\" srcset=\"/clearkey-drm-world-cup-2026/images/app-config_hua7b6385fc770026e215313f327bfe973_145312_800x0_resize_q75_h2_box_3.webp 800w, /clearkey-drm-world-cup-2026/images/app-config_hua7b6385fc770026e215313f327bfe973_145312_1200x0_resize_q75_h2_box_3.webp 1200w, /clearkey-drm-world-cup-2026/images/app-config_hua7b6385fc770026e215313f327bfe973_145312_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"1132\" width=\"1621\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003e该应用加密的频道配置——横跨 36 个分类的 525 个频道，带有 AES 加密的 URL 和 DRM 凭据。[URL 已隐去]\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003ch3 id=\"加密aes-128-ecb\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e5%8a%a0%e5%af%86aes-128-ecb\" class=\"header-mark\" aria-label=\"Header mark for '加密（AES-128-ECB）'\"\u003e\u003c/a\u003e3.1 加密（AES-128-ECB）\u003c/h3\u003e\u003cp\u003e该应用从远程服务器获取其频道列表，形式是一段经过 AES 加密的 JSON。每一个字段——直播流 URL、DRM 授权 URI、HTTP 头——都是经过 base64 编码的 AES 密文。解密密钥是一个 16 字符的字符串，存放在 Firebase Remote Config 里，在启动时获取。\u003c/p\u003e\n\u003cp\u003e听起来挺正经。然后你看一眼密文：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-4\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    236 URLs share the same first 35 encrypted blocks.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Block 0: f91b7f273abccc6e...  ← identical across all 236\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Block 1: 932b23560f1d43ba...  ← identical\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Block 2: a7d788a399cea962...  ← identical\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ...\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ECB mode. Same plaintext block = same ciphertext block.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    The URLs all start with the same CDN domain prefix.\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eAES-ECB 是教科书级别的\u0026quot;如何\u003cem\u003e不\u003c/em\u003e正确使用 AES\u0026quot;的反面例子。每一门密码学课程都会用\u003ca href=\"https://blog.filippo.io/the-ecb-penguin/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eECB 企鹅\u003c/a\u003e来讲解这一点——用 ECB 加密一张位图，图像依然能被辨认出来，因为相同的明文块会产生相同的密文块。这个应用加密了 236 个以同一个 CDN 域名开头的 URL，产生了 236 段拥有\u003cstrong\u003e相同 560 字节前缀\u003c/strong\u003e的密文。光是做模式分析，你还没找到密钥，就已经能看出结构了。\u003c/p\u003e\n\u003ch3 id=\"名不副实的授权服务器\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e5%90%8d%e4%b8%8d%e5%89%af%e5%ae%9e%e7%9a%84%e6%8e%88%e6%9d%83%e6%9c%8d%e5%8a%a1%e5%99%a8\" class=\"header-mark\" aria-label=\"Header mark for '名不副实的\u0026amp;quot;授权服务器\u0026amp;quot;'\"\u003e\u003c/a\u003e3.2 名不副实的\u0026quot;授权服务器\u0026quot;\u003c/h3\u003e\u003cp\u003e一旦解密，ClearKey 凭据并不存在于某个单独的密钥交换里。它们就在\u003cstrong\u003e授权 URI 本身\u003c/strong\u003e之中，作为明文查询参数：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-5\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    drm_license_uri (after decryption):\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    https://[redacted]/?keyid=49eb924b...\u0026amp;key=6e131b04...\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                              ^^^^^^^^        ^^^^^^^^\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                              ClearKey ID     ClearKey Key\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                              (in the URL)    (in the URL)\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这个应用的\u0026quot;授权服务器\u0026quot;就是一个\u003cem\u003e包含着密钥\u003c/em\u003e的 URL。没有挑战-应答。没有会话绑定。应用去获取这个 URL，而这个 URL \u003cem\u003e就是\u003c/em\u003e密钥，密钥再去解密直播流。\u003c/p\u003e\n\u003cp\u003e三层间接——Firebase Remote Config、AES 加密、一个\u0026quot;授权服务器\u0026quot;端点——全都坍缩成同一个失败：解密密钥以明文形式落到了客户端手里，因为 ClearKey 要求如此。\u003c/p\u003e\n\u003ch3 id=\"应用的完整技术栈\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e5%ba%94%e7%94%a8%e7%9a%84%e5%ae%8c%e6%95%b4%e6%8a%80%e6%9c%af%e6%a0%88\" class=\"header-mark\" aria-label=\"Header mark for '应用的完整技术栈'\"\u003e\u003c/a\u003e3.3 应用的完整技术栈\u003c/h3\u003e\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-6\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ┌──────────────────────────────────────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    │           ANDROID APP ARCHITECTURE                           │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    └──────────────────────────────────────────────────────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    1. App launches\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       └──\u0026gt; Firebase Remote Config\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            └──\u0026gt; Fetches AES key (\u0026#34;claveapp\u0026#34;) + config URLs\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    2. App fetches encrypted JSON (525 channels)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       └──\u0026gt; Base64 decode ──\u0026gt; AES-128-ECB decrypt\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            └──\u0026gt; Stream URLs, DRM license URIs, headers (plaintext)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    3. For CLEARKEY channels (348 of 525):\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       └──\u0026gt; \u0026#34;License URI\u0026#34; = https://[redacted]/?keyid=XXX\u0026amp;key=YYY\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            └──\u0026gt; Key ID and Key are IN THE URL\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    4. App configures ExoPlayer with ClearKey\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       └──\u0026gt; Fetches DASH manifest from legitimate CDN\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            └──\u0026gt; Decrypts video with the key from step 3\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Package name: com.example.myapplication\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Encryption:   AES/ECB/PKCS5Padding (textbook insecure)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Key storage:  Firebase Remote Config (single API call to extract)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Key length:   16 characters, static, never rotated\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e包名 \u003ccode\u003ecom.example.myapplication\u003c/code\u003e 把这场操作背后的开发严谨程度交代得明明白白。Android Studio 的默认模板。连名字都没改。\u003c/p\u003e\n\u003ch3 id=\"实时频道列表就放在一个公开仓库里\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e5%ae%9e%e6%97%b6%e9%a2%91%e9%81%93%e5%88%97%e8%a1%a8%e5%b0%b1%e6%94%be%e5%9c%a8%e4%b8%80%e4%b8%aa%e5%85%ac%e5%bc%80%e4%bb%93%e5%ba%93%e9%87%8c\" class=\"header-mark\" aria-label=\"Header mark for '实时频道列表就放在一个公开仓库里'\"\u003e\u003c/a\u003e3.4 实时频道列表就放在一个公开仓库里\u003c/h3\u003e\u003cp\u003e最不动声色却又最致命的一点——也是最能体现这场操作\u003cem\u003e真正\u003c/em\u003e高明之处的一点（其高明之处主要在于后勤调度）——是那份加密配置的托管位置。该应用从一个公开的 GitHub 仓库获取它的频道 JSON。任何人都能浏览它。任何人都能 \u003ccode\u003egit clone\u003c/code\u003e 它。任何人都能查看提交历史。\u003c/p\u003e\n\u003cp\u003e而提交历史才是有意思的地方：\u003c/p\u003e\n\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/clearkey-drm-world-cup-2026/images/commits.png\" title=\"\" data-thumbnail=\"/clearkey-drm-world-cup-2026/images/commits.png\" data-sub-html=\"\u003ch2\u003e托管着该应用加密频道列表的那个公开 GitHub 仓库。每 4–5 分钟就会落下一个提交——这是对频道 JSON 的自动化编辑，随着上游直播流被轮换、被掐断或被替换而更新。用户名和仓库所有者已隐去。\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/clearkey-drm-world-cup-2026/images/commits.png\" srcset=\"/clearkey-drm-world-cup-2026/images/commits_hu2add8a55510f0a520df6ce349c817777_98640_800x0_resize_q75_h2_box_3.webp 800w, /clearkey-drm-world-cup-2026/images/commits_hu2add8a55510f0a520df6ce349c817777_98640_1200x0_resize_q75_h2_box_3.webp 1200w, /clearkey-drm-world-cup-2026/images/commits_hu2add8a55510f0a520df6ce349c817777_98640_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"900\" width=\"1300\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003e托管着该应用加密频道列表的那个公开 GitHub 仓库。每 4–5 分钟就会落下一个提交——这是对频道 JSON 的自动化编辑，随着上游直播流被轮换、被掐断或被替换而更新。用户名和仓库所有者已隐去。\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003cp\u003e每 4–5 分钟就会落下一个提交。差异始终是针对 \u003ccode\u003etv (10).json\u003c/code\u003e 或 \u003ccode\u003ebearer.json\u003c/code\u003e 的——前者是加密的频道列表，后者是用来从上游获取新直播流的鉴权凭据。提交信息全都是 \u003ccode\u003e\u0026quot;Actualizar tv (10).json desde tv.json\u0026quot;\u003c/code\u003e 或 \u003ccode\u003e\u0026quot;Actualizar bearer.json desde panel - \u0026lt;timestamp\u0026gt;\u0026quot;\u003c/code\u003e。翻译过来就是：某处有一个自动化任务，每隔几分钟就重写一遍频道配置并推送更新，好让运行该应用的手机在一个轮询周期之内拿到新的直播流 URL 和轮换后的 DRM 凭据。\u003c/p\u003e\n\u003cp\u003e这才是这场操作真正的护城河。不是 AES-ECB（已破解）。不是 Firebase Remote Config（一次 API 调用）。不是那个\u0026quot;授权服务器\u0026quot;（一个把密钥写在里头的 URL）。这条护城河是\u003cstrong\u003e运营层面的\u003c/strong\u003e：一段自动化程序，时刻让频道 JSON 与那一刻还活着的上游信号源保持对齐，并且明目张胆地托管在一个他们根本不用自己搭建的免费 CDN 上。当一个合法提供商轮换密钥时，机器人会察觉到，去获取新密钥，重新加密配置，然后提交。当一个 CDN 封掉源 IP 时，机器人就再找一个。客户的手机轮询、看到新提交、获取新配置，然后继续看比赛。用户甚至都看不到轮换发生。\u003c/p\u003e\n\u003cp\u003e人人都会想到的防御手段是\u0026quot;吊销密钥\u0026quot;。但你吊销一个密钥的速度，永远快不过一个脚本把新密钥推到 GitHub 仓库的速度。这种节奏上的不匹配，就是整盘棋的关键。\u003c/p\u003e\n\u003ch2 id=\"数字\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e6%95%b0%e5%ad%97\" class=\"header-mark\" aria-label=\"Header mark for '数字'\"\u003e\u003c/a\u003e4 数字\u003c/h2\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003e\u003c/th\u003e\n\u003cth\u003e网页审计\u003c/th\u003e\n\u003cth\u003e安卓应用\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003e直播流\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003e670\u003c/td\u003e\n\u003ctd\u003e525（348 路 ClearKey）\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eCDN\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003e169\u003c/td\u003e\n\u003ctd\u003e34\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003e国家\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003e33\u003c/td\u003e\n\u003ctd\u003e~8（以拉美为主）\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003e密钥存储\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eJavaScript \u003ccode\u003e\u0026lt;script\u0026gt;\u003c/code\u003e 块\u003c/td\u003e\n\u003ctd\u003eFirebase Remote Config\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003e混淆\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eJS 变量重命名 + IIFE\u003c/td\u003e\n\u003ctd\u003eAES-128-ECB（已破解）\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003e客户端明文密钥\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003e是\u003c/td\u003e\n\u003ctd\u003e是\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003e世界杯覆盖\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003e数十个体育频道\u003c/td\u003e\n\u003ctd\u003e28 个专属频道 + 多机位\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e两种做法——静态网站和完整的安卓应用——最终都落到同一处：一对 ClearKey 密钥躺在客户端内存里，没有任何机制能阻止它被提取，没有会话绑定，没有吊销，也没有密钥轮换。\u003c/p\u003e\n\u003ch2 id=\"钱以及为什么它停不下来\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e9%92%b1%e4%bb%a5%e5%8f%8a%e4%b8%ba%e4%bb%80%e4%b9%88%e5%ae%83%e5%81%9c%e4%b8%8d%e4%b8%8b%e6%9d%a5\" class=\"header-mark\" aria-label=\"Header mark for '钱，以及为什么它停不下来'\"\u003e\u003c/a\u003e5 钱，以及为什么它停不下来\u003c/h2\u003e\u003cp\u003e经济账解释了一切。盗版网站托管着约 50KB 的静态 HTML，把你的浏览器指向别人的 CDN。那个应用不过是裹在别人基础设施外面的一层薄薄的 ExoPlayer。两者都不提供视频。两者都靠广告网络变现。\u003c/p\u003e\n\u003cp\u003eStreamEast 在 2025 年 9 月被查封前，已经服务了 16 亿次访问。\u0026ldquo;Operation Takendown\u0026quot;行动中的那个 IPTV 团伙拥有 2200 万用户，每月进账 2.5 亿欧元。一个中等规模的网站在世界杯期间——比赛日一百万访客，每人六次广告展示，CPM 1.50 美元——在几乎为零的托管成本下，\u003cstrong\u003e每天净赚 9000 美元\u003c/strong\u003e。\u003c/p\u003e\n\u003cp\u003e执法力度不断升级——西班牙的监禁判决、得州的 1875 万美元判赔、世界杯前一周下架的 27000 个信号源——而新网站还是层出不穷。因为这个漏洞是架构性的。面对一个把解密密钥放进浏览器的规范，你抓人是抓不完的。你也加密不出去，正如那个安卓应用所示范的：三层加密，密钥最后还是以明文形式落在客户端，因为 ClearKey 就是这么要求的。\u003c/p\u003e\n\u003cp\u003e真正的修复办法，是彻底迁移出 ClearKey——转向 Widevine、FairPlay 或 PlayReady。在那之前，密码就刻在挂锁的背面，而世界杯正确保所有人都知道该往哪儿看。\u003c/p\u003e\n",
        "language": "zh-cn"
    },
    {
        "title" : "fastfn 第二部分：当函数不够用时（服务、工作负载，以及我本不想写的 docker-compose）",
        "date_published" : "2026-06-09T00:00:00Z",
        "date_modified" : "2026-06-09T00:00:00Z",
        "id" : "https://misael.org/zh-cn/fastfn-services-when-functions-arent-enough/",
        "url" : "https://misael.org/zh-cn/fastfn-services-when-functions-arent-enough/",
        "summary": "函数对于请求/响应是一种绝佳的形态。但对于数据库它是一种糟糕的形态。这是 fastfn 中长生命周期服务加入网关的那一部分——原生 docker、原生进程、Firecracker 微虚拟机——全部位于同一份工作负载配置之后。",
        "content_html" : "\u003cdiv class=\"details admonition info open\"\u003e\n    \u003cdiv class=\"details-summary admonition-title\"\u003e\n        \u003cspan class=\"icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z\"/\u003e\u003c/svg\u003e\u003c/span\u003e系列文章之一\u003cspan class=\"details-icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"details-content\"\u003e\n        \u003cdiv class=\"admonition-content\"\u003e这是 fastfn 记述的第二部分。第一部分——\u003ca href=\"https://misael.org/zh-cn/fastfn-lua-to-our-lives/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003cstrong\u003efastfn 第一部分：我遇到了一个问题（并把 Lua 引入了我的生活）\u003c/strong\u003e\u003c/a\u003e——讲的是函数那一面：Lua 网关、JSON 线缆协议、多语言运行时。本文从第一部分结束的地方接着讲，走过服务、工作负载，以及 Firecracker 微虚拟机的路径。\u003c/div\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ch2 id=\"第一章那份隐痛或者说为什么一个函数当不了-postgres\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e4%b8%80%e7%ab%a0%e9%82%a3%e4%bb%bd%e9%9a%90%e7%97%9b%e6%88%96%e8%80%85%e8%af%b4%e4%b8%ba%e4%bb%80%e4%b9%88%e4%b8%80%e4%b8%aa%e5%87%bd%e6%95%b0%e5%bd%93%e4%b8%8d%e4%ba%86-postgres\" class=\"header-mark\" aria-label=\"Header mark for '第一章：那份隐痛（或者说，为什么一个函数当不了 Postgres）'\"\u003e\u003c/a\u003e1 第一章：那份隐痛（或者说，为什么一个函数当不了 Postgres）\u003c/h2\u003e\u003cp\u003e在第一部分的结尾，fastfn 是个快乐的小东西。你把 \u003ccode\u003eget.users.py\u003c/code\u003e 放到磁盘上，网关会把它映射到一个 URL，一个 Python 守护进程会把它拾起来，于是你就有了一个 HTTP 端点，连 Dockerfile 都不用碰。它的优雅，是那种只解决了你一半问题的东西所特有的优雅。\u003c/p\u003e\n\u003cp\u003e因为接着我试着用它构建一个真正的应用。\u003c/p\u003e\n\u003cp\u003e而一旦你试图构建一个真正的应用，你的请求/响应原语就不再够用了。你需要状态。你需要一个数据库。你需要那个你同事已经写好的管理后台——它是一个 Next.js 应用，期望在 3000 端口上被 \u003ccode\u003enext start\u003c/code\u003e,而不是一个启动、计算、然后死掉的无状态 lambda。你需要一个 Redis 来存会话，或者一个 MinIO 来存 blob,又或者——在我的情况下——一个完整成型的 Flask 应用,它根本不可能挤过\u0026quot;一个文件一个 handler\u0026quot;那个针眼。\u003c/p\u003e\n\u003cp\u003e我盯着这个问题看了好一会儿。然后我说出了那句我一直竭力不想说的话：\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e函数对于请求/响应是一种绝佳的形态。它对于 Postgres 是一种糟糕的形态。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e这让我意识到一件令人不安的事。对于我曾经跑过的每一个业余项目，对这份隐痛的答案都是同一个文件：\u003ccode\u003edocker-compose.yml\u003c/code\u003e。一个服务是我的应用，另一个是数据库，也许还有一个边车，它们都在一个隐式网络上通信——这个网络是 compose 凭空变出来的。这是一件精美的人体工学作品，我一直在用它。但如果 fastfn 要成为我的本地平台，而我要在 \u003ccode\u003efastfn dev\u003c/code\u003e 旁边跑 \u003ccode\u003edocker-compose up\u003c/code\u003e,那我就会有两套 URL 空间、两套健康模型、两套 CORS 配置、两套认证面。网关之所以存在,整个理由就是要有\u003cstrong\u003e一个\u003c/strong\u003e地方了解我的 HTTP。一个并行的 \u003ccode\u003ecompose\u003c/code\u003e 会把这一点彻底拆掉。\u003c/p\u003e\n\u003cp\u003e所以我面临一个选择。要么我接受 fastfn 处理我应用的一个切片,而别的东西处理其余部分。要么我扩展 fastfn,让那些长生命周期的形态——Postgres、Next.js、Flask——和函数一样活在同一个网关之后。同一个 \u003ccode\u003efastfn.json\u003c/code\u003e、同一个健康端点、同一套 CORS、同一套认证,一个统一的 HTTP 面。\u003c/p\u003e\n\u003cp\u003e我并不打算去写 docker-compose。这篇文章讲的是我如何有意地写了半个 docker-compose。\u003c/p\u003e\n\u003cp\u003e本文主要围绕的提交是 \u003ccode\u003efirecracker-simple-images\u003c/code\u003e 分支上的 \u003ccode\u003e6a54c11\u003c/code\u003e：\u003cem\u003e\u0026ldquo;Add simple native image apps and services\u0026rdquo;\u003c/em\u003e,2026 年 3 月 31 日,跨 26 个文件 2254 处插入。正是在那里,\u003cstrong\u003e工作负载\u003c/strong\u003e这个概念在 fastfn 里有了一等公民的归宿。在那之后的一切——\u003ccode\u003e5568b6c\u003c/code\u003e 中的 vsock 对等网络、\u003ccode\u003e6fd5fec\u003c/code\u003e 中的保温(keep-hot)默认值、\u003ccode\u003efd8a6b5\u003c/code\u003e 中的防火墙 + 基准测试矩阵——都是同一抽象的演进。\u003c/p\u003e\n\u003ch2 id=\"第二章工作负载这个词\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e4%ba%8c%e7%ab%a0%e5%b7%a5%e4%bd%9c%e8%b4%9f%e8%bd%bd%e8%bf%99%e4%b8%aa%e8%af%8d\" class=\"header-mark\" aria-label=\"Header mark for '第二章：\u0026amp;ldquo;工作负载\u0026amp;quot;这个词'\"\u003e\u003c/a\u003e2 第二章：\u0026ldquo;工作负载\u0026quot;这个词\u003c/h2\u003e\u003cp\u003eCGI 时代对我想要的东西的叫法是 \u0026ldquo;daemon\u0026rdquo;。systemd 的叫法是 \u0026ldquo;unit\u0026rdquo;。Kubernetes 的叫法是 \u0026ldquo;Deployment\u0026rdquo; 和 \u0026ldquo;StatefulSet\u0026rdquo;。Heroku 的叫法是 \u0026ldquo;Procfile 条目\u0026rdquo;。docker-compose 的叫法,令人困惑地,是 \u0026ldquo;service\u0026rdquo;。我挑了 \u003cstrong\u003eworkload\u003c/strong\u003e(工作负载)这个词作为一把伞,然后把它一分为二。\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-1\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   fastfn.json\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   ┌──────────────────────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │                                              │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │  functions-dir: \u0026#34;functions\u0026#34;                  │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │                                              │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │  apps:                ← public HTTP faces    │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │    admin: { ... routes: [\u0026#34;/admin/*\u0026#34;] }       │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │                                              │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │  services:            ← private, injected    │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │    mysql: { port: 3306, volume: \u0026#34;mysql-data\u0026#34; }│\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │                                              │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   └──────────────────────────────────────────────┘\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这个区分很小,但承重。一个 \u003cstrong\u003eapp\u003c/strong\u003e 是有公开面的工作负载：它声明 \u003ccode\u003eroutes\u003c/code\u003e,网关把这些路由暴露给外部世界。一个 \u003cstrong\u003eservice\u003c/strong\u003e 是保持私有的工作负载：其他工作负载和函数可以触达它,但外部世界不能。Postgres 是一个 service。Next.js 管理后台是一个 app。在极端情况下,它们之间唯一的区别就是 routes 字段是否设置,以及网关是否对外宣告它们。\u003c/p\u003e\n\u003cp\u003e两者都活在同一个文件里,并且都由同一条代码路径校验(\u003ccode\u003ecli/internal/workloads/config.go:12-14\u003c/code\u003e)：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-2\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/workloads/config.go:12-15\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003eConfig\u003c/span\u003e \u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nx\"\u003eApps\u003c/span\u003e     \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"nx\"\u003eAppSpec\u003c/span\u003e     \u003cspan class=\"s\"\u003e`json:\u0026#34;apps,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nx\"\u003eServices\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"nx\"\u003eServiceSpec\u003c/span\u003e \u003cspan class=\"s\"\u003e`json:\u0026#34;services,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e两个兄弟列表。不是两个世界。同一个归一化器遍历两者,同一个状态写入器持久化两者,同一个 Lua 网关在请求时从同一个 JSON 文件读取两者。\u003c/p\u003e\n\u003cp\u003e这让我意识到一件几乎微不足道但值得一说的事：一旦你有了一个懂得路由的网关,给它多一种目标类型,并不是一个新项目。它只是表里多一行。\u003c/p\u003e\n\u003ch2 id=\"第三章一个工作负载的形态\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e4%b8%89%e7%ab%a0%e4%b8%80%e4%b8%aa%e5%b7%a5%e4%bd%9c%e8%b4%9f%e8%bd%bd%e7%9a%84%e5%bd%a2%e6%80%81\" class=\"header-mark\" aria-label=\"Header mark for '第三章：一个工作负载的形态'\"\u003e\u003c/a\u003e3 第三章：一个工作负载的形态\u003c/h2\u003e\u003cp\u003e我们来看看实际的形态。这是带一个 app 和一个 service 的最小 fastfn 配置,取自那个提交里的 README diff(\u003ccode\u003eREADME.md:178-202\u003c/code\u003e)：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ejson\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-3\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// fastfn.json\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;functions-dir\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;functions\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;public-base-url\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;https://api.example.com\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;openapi-include-internal\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;apps\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;admin\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;image\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;ghcr.io/acme/admin:latest\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;port\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e3000\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;routes\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/admin/*\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;services\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;mysql\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;image\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;mysql:8.4\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;port\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e3306\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;volume\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;mysql-data\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e有三样东西值得盯着看。第一,镜像来源。第二,端口。第三,路由(仅在 app 上)。其余一切都是可选的旋钮。\u003c/p\u003e\n\u003cp\u003e镜像来源很有意思,因为有三种有效的声明方式,而配置代码强制你恰好选其中一种(\u003ccode\u003ecli/internal/workloads/config.go:383-395\u003c/code\u003e)：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-4\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/workloads/config.go:387-395\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003esourceCount\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"nx\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003evalue\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"k\"\u003erange\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003eimage\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eimageFile\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003edockerfile\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003estrings\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eTrimSpace\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003esourceCount\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003esourceCount\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eErrorf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s\"\u003e\u0026#34;must set exactly one image source among image, image_file or dockerfile\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e所以你的三个选择是：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eimage\u003c/code\u003e：一个像 \u003ccode\u003emysql:8.4\u003c/code\u003e 这样的注册表引用,或者磁盘上一个本地 Firecracker bundle 目录的路径。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eimage_file\u003c/code\u003e：一个本地的 OCI 或 Docker 归档,在首次使用时转换为一个被缓存的 Firecracker bundle。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003edockerfile\u003c/code\u003e：一个指向 Dockerfile 的路径,fastfn 会通过 Docker Engine API 构建它,然后同样进行转换。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e恰好选一个的规则是承重的。它说\u0026quot;一个工作负载,一个事实来源\u0026rdquo;。你不能把一个本地 bundle 和一个注册表镜像混在一起然后看哪个胜出。那条路通向你不想要的调试。\u003c/p\u003e\n\u003cp\u003e端口更简单：它是工作负载监听的容器端口,带一个校验,确保它落在 \u003ccode\u003e1..65535\u003c/code\u003e 之内。对于 app,路由是一个 URL 前缀数组。路由的默认形式既支持精确匹配(\u003ccode\u003e/admin\u003c/code\u003e)也支持尾部 glob 匹配(\u003ccode\u003e/admin/*\u003c/code\u003e)。有一个归一化器会修剪尾部斜杠并强制一个前导斜杠(\u003ccode\u003ecli/internal/workloads/config.go:941-953\u003c/code\u003e),这样你可能写路由的三种方式最终都变成一个规范字符串。\u003c/p\u003e\n\u003cp\u003e完整的 AppSpec 比这个最小集大,因为一旦你开始跑真正的应用,你就想要旋钮。这是整个表面(\u003ccode\u003ecli/internal/workloads/config.go:66-88\u003c/code\u003e)：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-5\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/workloads/config.go:66-88\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003eAppSpec\u003c/span\u003e \u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eName\u003c/span\u003e          \u003cspan class=\"kt\"\u003estring\u003c/span\u003e             \u003cspan class=\"s\"\u003e`json:\u0026#34;name\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eScopeDir\u003c/span\u003e      \u003cspan class=\"kt\"\u003estring\u003c/span\u003e             \u003cspan class=\"s\"\u003e`json:\u0026#34;scope_dir,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eImage\u003c/span\u003e         \u003cspan class=\"kt\"\u003estring\u003c/span\u003e             \u003cspan class=\"s\"\u003e`json:\u0026#34;image,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eImageFile\u003c/span\u003e     \u003cspan class=\"kt\"\u003estring\u003c/span\u003e             \u003cspan class=\"s\"\u003e`json:\u0026#34;image_file,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eDockerfile\u003c/span\u003e    \u003cspan class=\"kt\"\u003estring\u003c/span\u003e             \u003cspan class=\"s\"\u003e`json:\u0026#34;dockerfile,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eContext\u003c/span\u003e       \u003cspan class=\"kt\"\u003estring\u003c/span\u003e             \u003cspan class=\"s\"\u003e`json:\u0026#34;context,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ePort\u003c/span\u003e          \u003cspan class=\"kt\"\u003eint\u003c/span\u003e                \u003cspan class=\"s\"\u003e`json:\u0026#34;port\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eEnv\u003c/span\u003e           \u003cspan class=\"kd\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e  \u003cspan class=\"s\"\u003e`json:\u0026#34;env,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eCommand\u003c/span\u003e       \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e           \u003cspan class=\"s\"\u003e`json:\u0026#34;command,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eWorkingDir\u003c/span\u003e    \u003cspan class=\"kt\"\u003estring\u003c/span\u003e             \u003cspan class=\"s\"\u003e`json:\u0026#34;working_dir,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eUser\u003c/span\u003e          \u003cspan class=\"kt\"\u003estring\u003c/span\u003e             \u003cspan class=\"s\"\u003e`json:\u0026#34;user,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eVolume\u003c/span\u003e        \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003eVolumeSpec\u003c/span\u003e        \u003cspan class=\"s\"\u003e`json:\u0026#34;volume,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eVolumes\u003c/span\u003e       \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003eVolumeSpec\u003c/span\u003e      \u003cspan class=\"s\"\u003e`json:\u0026#34;volumes,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eHealthcheck\u003c/span\u003e   \u003cspan class=\"nx\"\u003eHealthcheckSpec\u003c/span\u003e    \u003cspan class=\"s\"\u003e`json:\u0026#34;healthcheck,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eRoutes\u003c/span\u003e        \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e           \u003cspan class=\"s\"\u003e`json:\u0026#34;routes,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eReplicas\u003c/span\u003e      \u003cspan class=\"kt\"\u003eint\u003c/span\u003e                \u003cspan class=\"s\"\u003e`json:\u0026#34;replicas,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ePorts\u003c/span\u003e         \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"nx\"\u003ePortSpec\u003c/span\u003e         \u003cspan class=\"s\"\u003e`json:\u0026#34;ports,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eAccess\u003c/span\u003e        \u003cspan class=\"nx\"\u003eAccessSpec\u003c/span\u003e         \u003cspan class=\"s\"\u003e`json:\u0026#34;access,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eProcessGroups\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"nx\"\u003eProcessGroupSpec\u003c/span\u003e \u003cspan class=\"s\"\u003e`json:\u0026#34;process_groups,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eHA\u003c/span\u003e            \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003eHAConfig\u003c/span\u003e          \u003cspan class=\"s\"\u003e`json:\u0026#34;ha,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eLifecycle\u003c/span\u003e     \u003cspan class=\"nx\"\u003eLifecycleSpec\u003c/span\u003e      \u003cspan class=\"s\"\u003e`json:\u0026#34;lifecycle,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e\u003ccode\u003eServiceSpec\u003c/code\u003e 几乎完全相同(\u003ccode\u003econfig.go:90-111\u003c/code\u003e),只是少了 \u003ccode\u003eReplicas\u003c/code\u003e；对 service 来说,是进程组来做副本计数。我曾短暂地动过心思,想把它们统一成一个带 \u003ccode\u003eIsPublic bool\u003c/code\u003e 的结构体。我很庆幸我没那么做。这两种形态有着微妙不同的校验器和微妙不同的默认值,而试图把它们折叠成一个类型,总是产出三元表达式,而第二个类型则产出清晰。\u003c/p\u003e\n\u003cp\u003e这份对称隐藏着一个微妙的不对称,它出现在 \u003ccode\u003edefaultAppLifecycle\u003c/code\u003e 与 \u003ccode\u003edefaultServiceLifecycle\u003c/code\u003e 中(\u003ccode\u003econfig.go:658-672\u003c/code\u003e)：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-6\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/workloads/config.go:658-672\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003edefaultAppLifecycle\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"nx\"\u003eLifecycleSpec\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003eLifecycleSpec\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003eIdleAction\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e   \u003cspan class=\"s\"\u003e\u0026#34;run\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003ePauseAfterMS\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e15000\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003ePrewarm\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e      \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003edefaultServiceLifecycle\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"nx\"\u003eLifecycleSpec\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003eLifecycleSpec\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003eIdleAction\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e   \u003cspan class=\"s\"\u003e\u0026#34;run\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003ePauseAfterMS\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003ePrewarm\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e      \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eapp 得到一个 15 秒的空闲计时器,如果它们哪天选择 \u003ccode\u003epause\u003c/code\u003e,就能用上。service 得到的是零——因为你不会去暂停一个 Postgres。一个被暂停的 Postgres 叫\u0026quot;一次故障\u0026quot;。各处的默认策略都是\u003cem\u003e保持运行、保持热、并在启动时预热\u003c/em\u003e。两个生命周期模型,一个配置文件,零遗憾。\u003c/p\u003e\n\u003ch2 id=\"第四章三种后端一种配置形态\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e5%9b%9b%e7%ab%a0%e4%b8%89%e7%a7%8d%e5%90%8e%e7%ab%af%e4%b8%80%e7%a7%8d%e9%85%8d%e7%bd%ae%e5%bd%a2%e6%80%81\" class=\"header-mark\" aria-label=\"Header mark for '第四章：三种后端,一种配置形态'\"\u003e\u003c/a\u003e4 第四章：三种后端,一种配置形态\u003c/h2\u003e\u003cp\u003e这里就是工作负载抽象付房租的地方。同一段 JSON 节是要以三种不同方式运行的：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-7\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   fastfn.json workload\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            ├────────┬────────────┬──────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            │        │            │                  │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            ▼        ▼            ▼                  ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       docker   native process  Firecracker     (future\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       native   (no container)  microVM on       backends)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       (fallback)                Linux/KVM\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e跑哪一个,一部分是平台决定,一部分是分支决定。在这个分支上(\u003ccode\u003efirecracker-simple-images\u003c/code\u003e),Firecracker 是 Linux/KVM 主机上的目标,而 \u003ccode\u003edocker_native.go\u003c/code\u003e 是回退管理器——注意文件顶部的 build tag(\u003ccode\u003ecli/internal/workloads/docker_native.go:1\u003c/code\u003e)：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-8\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//go:build !linux\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003epackage\u003c/span\u003e \u003cspan class=\"nx\"\u003eworkloads\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这一行干的活比它看上去要多。它说\u0026quot;在非 Linux 上,使用基于 Docker 的工作负载管理器实现\u0026quot;。在 Linux 上,Firecracker 管理器接管(\u003ccode\u003efirecracker_manager_linux.go\u003c/code\u003e)。两个实现都满足 \u003ccode\u003eprocess/runner.go\u003c/code\u003e 接线的同一个内部 \u003ccode\u003enativeImageWorkloadManager\u003c/code\u003e 接口：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-9\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/process/runner.go (from the 6a54c11 diff)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003enativeImageWorkloadManager\u003c/span\u003e \u003cspan class=\"kd\"\u003einterface\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003eStart\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003econtext\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eContext\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003eerror\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003eStop\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003econtext\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eContext\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003eerror\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003eStatePath\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e三个方法。Start 把所有工作负载拉起来。Stop 把它们关掉。\u003ccode\u003eStatePath()\u003c/code\u003e 告诉系统其余部分 JSON 状态文件住在哪里,这样 Lua 那一侧就能找到它。就这样。Docker 管理器构建镜像、创建网络、启动容器、并开放发布的端口。Firecracker 管理器构建镜像、把它们转换成 bundle、引导微虚拟机、并把它们接到一个 vsock 对等网络上。机制不同,公开契约相同。\u003c/p\u003e\n\u003cp\u003edocker-native 管理器,老实说,就是我拒绝去写的那个 docker-compose。它为每个项目创建一个网络(\u003ccode\u003ecli/internal/workloads/docker_native.go:90-98\u003c/code\u003e)：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-10\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/workloads/docker_native.go:90-98\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003enetworkName\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;fastfn-\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"nf\"\u003eshortHash\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ecfg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eProjectDir\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ecfg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eStatePath\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ecli\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNetworkCreate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ectx\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003enetworkName\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003edockertypes\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eNetworkCreate\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eCheckDuplicate\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eDriver\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e         \u003cspan class=\"s\"\u003e\u0026#34;bridge\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eErrorf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;create docker network: %w\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003enetworkName\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003enetworkName\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e然后对于每个 service 和每个 app,它会用两个别名把容器接进来——裸名字和一个 \u003ccode\u003e\u0026lt;name\u0026gt;.internal\u003c/code\u003e 别名(\u003ccode\u003edocker_native.go:417-423\u003c/code\u003e)——这样网络内部的 app 可以触达 \u003ccode\u003emysql.internal:3306\u003c/code\u003e,而主机触达 \u003ccode\u003e127.0.0.1:\u0026lt;published\u0026gt;\u003c/code\u003e。这个拆分是有意的：公开的 app 通过网关在一个稳定的 URL 上触达外部世界,私有的 service 通过内部别名互相触达,而主机为了调试触达已发布的端口。\u003c/p\u003e\n\u003cp\u003e一个 service 起来时大致是这个样子：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-11\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/workloads/docker_native.go:251-266 (condensed)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003eservice\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eServiceState\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eName\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e         \u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eName\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eImage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e        \u003cspan class=\"nf\"\u003efirstNonEmpty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eImage\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eDockerfile\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eImageDigest\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e  \u003cspan class=\"nx\"\u003edigest\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eHost\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e         \u003cspan class=\"s\"\u003e\u0026#34;127.0.0.1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ePort\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e         \u003cspan class=\"nx\"\u003ehostPort\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eInternalHost\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eName\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;.internal\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eInternalPort\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ePort\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eContainerID\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e  \u003cspan class=\"nx\"\u003econtainerID\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eHealth\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e       \u003cspan class=\"nx\"\u003eWorkloadHealth\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003eUp\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eReason\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;ok\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eVolume\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e       \u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eVolume\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eBaseEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e      \u003cspan class=\"nf\"\u003ecloneEnvMap\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eURL\u003c/span\u003e         \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003eBuildServiceURL\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eName\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eHost\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e         \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ePort\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e         \u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eInternalURL\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003eBuildServiceURL\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eName\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eInternalHost\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eInternalPort\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eFunctionEnv\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003eBuildFunctionServiceEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eName\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e两点观察。第一,\u003ccode\u003eBuildServiceURL\u003c/code\u003e 检查 env 并推断出 scheme——如果它看到 \u003ccode\u003eMYSQL_USER\u003c/code\u003e 就构建一个 \u003ccode\u003emysql://\u003c/code\u003e URL,如果看到 \u003ccode\u003ePOSTGRES_USER\u003c/code\u003e 就是 \u003ccode\u003epostgres://\u003c/code\u003e,如果看到 \u003ccode\u003eREDIS_*\u003c/code\u003e 就是 \u003ccode\u003eredis://\u003c/code\u003e,否则回退到 \u003ccode\u003etcp://\u003c/code\u003e(\u003ccode\u003estate.go:143-157\u003c/code\u003e)。这是欢快的 URL 推断,意味着在百分之九十九的情况下我不必手敲 \u003ccode\u003eDATABASE_URL\u003c/code\u003e。第二,\u003ccode\u003eFunctionEnv\u003c/code\u003e 是项目里每个函数在请求时都会看到的那一袋变量。这就是那座桥：service 是私有的,但函数会拿到 \u003ccode\u003eSERVICE_MYSQL_HOST\u003c/code\u003e、\u003ccode\u003eSERVICE_MYSQL_PORT\u003c/code\u003e、\u003ccode\u003eSERVICE_MYSQL_URL\u003c/code\u003e,外加一个直接别名 \u003ccode\u003eMYSQL_HOST\u003c/code\u003e / \u003ccode\u003eMYSQL_PORT\u003c/code\u003e / \u003ccode\u003eMYSQL_URL\u003c/code\u003e,当名字没有歧义时(\u003ccode\u003estate.go:107-123\u003c/code\u003e,\u003ccode\u003estate.go:193-203\u003c/code\u003e)。\u003c/p\u003e\n\u003cp\u003e眯着眼看,这正是 docker-compose 用它的 \u003ccode\u003elinks\u003c/code\u003e 和 \u003ccode\u003edepends_on\u003c/code\u003e 注入所做的事。只不过是用了更紧的 schema 和更锋利的命名约定。\u003c/p\u003e\n\u003ch2 id=\"第五章lua-如何找到一个工作负载\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e4%ba%94%e7%ab%a0lua-%e5%a6%82%e4%bd%95%e6%89%be%e5%88%b0%e4%b8%80%e4%b8%aa%e5%b7%a5%e4%bd%9c%e8%b4%9f%e8%bd%bd\" class=\"header-mark\" aria-label=\"Header mark for '第五章：Lua 如何找到一个工作负载'\"\u003e\u003c/a\u003e5 第五章：Lua 如何找到一个工作负载\u003c/h2\u003e\u003cp\u003e现在是有趣的部分。网关仍然是 OpenResty + Lua——和第一部分是同一个东西。一个位于 CLI 侧的长生命周期 Go 进程,如何告诉网关侧的 Lua 工作负载存在且已就绪?\u003c/p\u003e\n\u003cp\u003e通过磁盘上的一个 JSON 文件。就这样。\u003c/p\u003e\n\u003cp\u003eCLI 的工作负载管理器写入一个位于已知路径的状态文件。这个路径被作为一个环境变量导出给网关(\u003ccode\u003eprocess/runner.go\u003c/code\u003e,在 6a54c11 diff 中)：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-12\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/process/runner.go (from 6a54c11)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003epath\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003estrings\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eTrimSpace\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eworkloadMgr\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eStatePath\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e \u003cspan class=\"nx\"\u003epath\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ebaseEnv\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ebaseEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;FN_IMAGE_WORKLOADS_STATE_PATH=\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e在 Lua 侧,\u003ccode\u003eimage_workloads.lua\u003c/code\u003e 在每次请求时读取这个路径：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003elua\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-13\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e-- openresty/lua/fastfn/core/image_workloads.lua:22-48\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nf\"\u003estate_path\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003epath\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eos.getenv\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;FN_IMAGE_WORKLOADS_STATE_PATH\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003epath\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003epath\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nf\"\u003eload_state\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003epath\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003estate_path\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003epath\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003eapps\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{},\u003c/span\u003e \u003cspan class=\"n\"\u003eservices\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eparsed\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eread_json_file\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eparsed\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e~=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;table\u0026#34;\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003eapps\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{},\u003c/span\u003e \u003cspan class=\"n\"\u003eservices\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eparsed.apps\u003c/span\u003e     \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eparsed.apps\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e     \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;table\u0026#34;\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003eparsed.apps\u003c/span\u003e     \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eparsed.services\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eparsed.services\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;table\u0026#34;\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003eparsed.services\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eparsed\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e每次请求读一次 JSON。在生产环境你会用某个小 TTL 缓存它；在开发环境这恰恰是对的做法。健康状态、路由、内部主机名、生命周期状态——全在那个文件里。\u003c/p\u003e\n\u003cp\u003e路由是下一步。网关对函数已经做了一支长匹配舞步(第一部分讲过)。对于工作负载,它向 \u003ccode\u003eimage_workloads.lua\u003c/code\u003e 索要那些路由能前缀匹配请求路径的候选项,然后按路由长度给它们打分(更长的路由胜出,因为 \u003ccode\u003e/admin/api/v1/users\u003c/code\u003e 比 \u003ccode\u003e/admin/*\u003c/code\u003e 更具体)：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003elua\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-14\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e-- openresty/lua/fastfn/core/image_workloads.lua:123-151\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nc\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003epublic_http_candidates\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erequest_path\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003estate\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eload_state\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003ecandidates\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ekind\u003c/span\u003e \u003cspan class=\"kr\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eipairs\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;apps\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;services\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e})\u003c/span\u003e \u003cspan class=\"kr\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eworkload_name\u003c/span\u003e \u003cspan class=\"kr\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eipairs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esorted_keys\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ekind\u003c/span\u003e\u003cspan class=\"p\"\u003e]))\u003c/span\u003e \u003cspan class=\"kr\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eworkload\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ekind\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003eworkload_name\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kr\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eendpoint\u003c/span\u003e \u003cspan class=\"kr\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eipairs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eworkload_public_endpoints\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eworkload\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \u003cspan class=\"kr\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eendpoint.protocol\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;http\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;http\u0026#34;\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eroutes\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eendpoint.routes\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;table\u0026#34;\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003eendpoint.routes\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          \u003cspan class=\"kr\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eroute\u003c/span\u003e \u003cspan class=\"kr\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eipairs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroutes\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kr\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eroute_matches\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroute\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003erequest_path\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              \u003cspan class=\"n\"\u003ecandidates\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"n\"\u003ecandidates\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003ekind\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ekind\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;apps\u0026#34;\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;app\u0026#34;\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;service\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003ename\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eworkload_name\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eworkload\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eworkload\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eendpoint\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eendpoint\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eroute\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eroute\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eroute_length\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroute\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003ecandidates\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e有两个细节我喜欢。外层循环遍历 \u003ccode\u003e{\u0026quot;apps\u0026quot;,\u0026quot;services\u0026quot;}\u003c/code\u003e 而不是把它们拼接起来,这在同一路由不知怎么被声明两次时(它不应该,但防御性代码很便宜)保留了 app 高于 service 的优先级。而 \u003ccode\u003eroute_length\u003c/code\u003e 随每个候选项一起带上,这样调用方就能挑出最长的匹配,这与每个理智的路由器解析重叠前缀的方式一致。\u003c/p\u003e\n\u003cp\u003e网关在请求时消费这个列表,并代理到胜出的端点(\u003ccode\u003eopenresty/lua/fastfn/http/gateway.lua:950-969\u003c/code\u003e)：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003elua\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-15\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e-- openresty/lua/fastfn/http/gateway.lua:950-969\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003erequest_host\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003erequest_authority\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003erequest_host_values\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003ematched_workload\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ematched_endpoint\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eworkload_access_err\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ematch_public_workload\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eimage_workloads.public_http_candidates\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erequest_uri\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003erequest_host\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003erequest_authority\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003erequest_client_ip\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ematched_workload\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;table\u0026#34;\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eapp_resp\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eapp_err\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eexecute_public_workload_proxy\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ematched_workload\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ematched_endpoint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003eapp_resp\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003estatus\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e502\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eapp_err\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\u003cspan class=\"n\"\u003efind\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;unavailable\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003estatus\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e503\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ewrite_response\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003estatus\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Content-Type\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;application/json\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                   \u003cspan class=\"n\"\u003ejson_error\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;public workload proxy failed: \u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e..\u003c/span\u003e \u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eapp_err\u003c/span\u003e\u003cspan class=\"p\"\u003e)))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003ewrite_response\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eapp_resp.status\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"mi\"\u003e502\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eapp_resp.headers\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"p\"\u003e{},\u003c/span\u003e \u003cspan class=\"n\"\u003eapp_resp.body\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e而这是真正的代理——HTTP 进,HTTP 出,在返回的路上剥掉逐跳(hop-by-hop)头部,这样就没有东西会让客户端困惑(\u003ccode\u003egateway.lua:487-537\u003c/code\u003e)：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003elua\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-16\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e-- openresty/lua/fastfn/http/gateway.lua:510-536\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eresp\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ehttp_client.request\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eurl\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003estring.format\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;http://%s:%d%s\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ehost\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eport\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003engx.var\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erequest_uri\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003emethod\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003engx.req\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_method\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eheaders\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esanitize_app_request_headers\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003engx.req\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_headers\u003c/span\u003e\u003cspan class=\"p\"\u003e()),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003ebody\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003etimeout_ms\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e30000\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003emax_body_bytes\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e10\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mi\"\u003e1024\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mi\"\u003e1024\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003eresp\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eerr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003efiltered\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003edrop\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;connection\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;keep-alive\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;transfer-encoding\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;content-length\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;upgrade\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ev\u003c/span\u003e \u003cspan class=\"kr\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003epairs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eresp.headers\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"p\"\u003e{})\u003c/span\u003e \u003cspan class=\"kr\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003edrop\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\u003cspan class=\"n\"\u003elower\u003c/span\u003e\u003cspan class=\"p\"\u003e()]\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003efiltered\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ev\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eresp.headers\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003efiltered\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eresp\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e十五兆字节的最大请求体。三十秒超时。逐跳头部被丢弃。这是你读过的最无聊的 HTTP 反向代理,而这正是你希望一个网关该有的样子。\u003c/p\u003e\n\u003cp\u003e聪明之处不在代理里。聪明之处在于\u003cem\u003e解析函数的同一个 ngx 阶段,现在也解析工作负载\u003c/em\u003e,带着已知的优先级,从同一个 JSON 状态文件,使用同一套基于 host/CIDR 的防火墙规则。整个东西得以成为一个统一的 HTTP 面。\u003c/p\u003e\n\u003ch2 id=\"第六章生命周期或者说把一张表变成一台状态机\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e5%85%ad%e7%ab%a0%e7%94%9f%e5%91%bd%e5%91%a8%e6%9c%9f%e6%88%96%e8%80%85%e8%af%b4%e6%8a%8a%e4%b8%80%e5%bc%a0%e8%a1%a8%e5%8f%98%e6%88%90%e4%b8%80%e5%8f%b0%e7%8a%b6%e6%80%81%e6%9c%ba\" class=\"header-mark\" aria-label=\"Header mark for '第六章：生命周期(或者说,把一张表变成一台状态机)'\"\u003e\u003c/a\u003e6 第六章：生命周期(或者说,把一张表变成一台状态机)\u003c/h2\u003e\u003cp\u003e一旦工作负载存在,它们就得走过一段人生。启动中。健康。不健康。已停止。已暂停。已恢复。生命周期让运维这一面的叙事保持诚实。\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-17\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ┌────────────┐   start()   ┌─────────────┐   healthcheck ok   ┌─────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    │  declared  │ ──────────▶ │  starting   │ ─────────────────▶ │ healthy │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    └────────────┘             └─────────────┘                    └────┬────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                     │                                 │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                     │ healthcheck fails               │ monitor tick\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                     ▼                                 │ reports failure\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                               ┌─────────────┐                         ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                               │  unhealthy  │ ◀──────────────── ┌─────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                               └──────┬──────┘                    │ flapping│\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                      │ stop()                   └─────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                      ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                ┌───────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                │  stopped  │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                └───────────┘\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e在 state.go 里,这些部件住在一小簇类型中(\u003ccode\u003estate.go:23-91\u003c/code\u003e)：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-18\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/workloads/state.go:23-38\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003eWorkloadHealth\u003c/span\u003e \u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eUp\u003c/span\u003e     \u003cspan class=\"kt\"\u003ebool\u003c/span\u003e   \u003cspan class=\"s\"\u003e`json:\u0026#34;up\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eReason\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"s\"\u003e`json:\u0026#34;reason,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003ePublicEndpointState\u003c/span\u003e \u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eName\u003c/span\u003e          \u003cspan class=\"kt\"\u003estring\u003c/span\u003e   \u003cspan class=\"s\"\u003e`json:\u0026#34;name\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eProtocol\u003c/span\u003e      \u003cspan class=\"kt\"\u003estring\u003c/span\u003e   \u003cspan class=\"s\"\u003e`json:\u0026#34;protocol,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eHost\u003c/span\u003e          \u003cspan class=\"kt\"\u003estring\u003c/span\u003e   \u003cspan class=\"s\"\u003e`json:\u0026#34;host,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ePort\u003c/span\u003e          \u003cspan class=\"kt\"\u003eint\u003c/span\u003e      \u003cspan class=\"s\"\u003e`json:\u0026#34;port,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eContainerPort\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e      \u003cspan class=\"s\"\u003e`json:\u0026#34;container_port,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eListenPort\u003c/span\u003e    \u003cspan class=\"kt\"\u003eint\u003c/span\u003e      \u003cspan class=\"s\"\u003e`json:\u0026#34;listen_port,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eRoutes\u003c/span\u003e        \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"s\"\u003e`json:\u0026#34;routes,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eAllowHosts\u003c/span\u003e    \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"s\"\u003e`json:\u0026#34;allow_hosts,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eAllowCIDRs\u003c/span\u003e    \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"s\"\u003e`json:\u0026#34;allow_cidrs,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e健康信号是一个两字段的结构体——up,以及当它不为真时的一个 reason。每个工作负载都保有一个。docker-native 管理器跑着一个监控 goroutine,每两秒醒一次,检查每个容器,并在任何东西变化时更新状态文件(\u003ccode\u003edocker_native.go:174-211\u003c/code\u003e)：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-19\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/workloads/docker_native.go:174-211 (condensed)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003eNativeManager\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"nf\"\u003emonitor\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edefer\u003c/span\u003e \u003cspan class=\"nb\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003edoneCh\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eticker\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNewTicker\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eSecond\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edefer\u003c/span\u003e \u003cspan class=\"nx\"\u003eticker\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eStop\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eselect\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ecase\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;-\u003c/span\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003estopCh\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ecase\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;-\u003c/span\u003e\u003cspan class=\"nx\"\u003eticker\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nx\"\u003echanged\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"nx\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eitem\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"k\"\u003erange\u003c/span\u003e \u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003econtainers\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"nx\"\u003ehealth\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003einspectHealth\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eitem\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"c1\"\u003e// ... update m.state.Apps or m.state.Services,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e                \u003cspan class=\"c1\"\u003e//     setting changed = true when different\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003echanged\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"nx\"\u003e_\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewriteState\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e仅在变化时写入的行为很重要。Lua 那一侧在每次请求时读那个状态文件。如果 Go 那一侧无条件地每两秒往文件里灌,那每次请求都会看到一个新文件,每一次小小的 fs 读取都被浪费。只在某样东西\u003cem\u003e真正动了\u003c/em\u003e的时候才写,能让文件在很长的区段里保持稳定,这正是你想从一个 IPC 机制那里得到的东西——往好里说,它是个 JSON 文件。\u003c/p\u003e\n\u003cp\u003e启动是有意串行的：service 先起来,然后是 app(\u003ccode\u003edocker_native.go:100-128\u003c/code\u003e)。app 启动时其环境里已经填好了 service 的 env——这正是配置文件那份干净的对称性把自己确立为一个依赖顺序的时刻。service 之所以存在是为了让 app 能用它们；因此 service 先启动。\u003c/p\u003e\n\u003cp\u003eCLI 的入口点在 \u003ccode\u003edev\u003c/code\u003e 和 \u003ccode\u003erun\u003c/code\u003e 里是同一段无聊的两行。对于 \u003ccode\u003edev\u003c/code\u003e,出自 6a54c11 diff：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-20\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/cmd/dev.go (from 6a54c11 diff)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003eimageWorkloads\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ehasImageWorkloads\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nf\"\u003econfiguredImageWorkloads\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003edevFatalf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Invalid apps/services config: %v\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// ...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nf\"\u003erunNative\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nf\"\u003econfiguredProjectRoot\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e \u003cspan class=\"nx\"\u003eabsPath\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eimageWorkloads\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003edevFatalf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Native dev failed: %v\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// ...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003ehasImageWorkloads\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003edevFatal\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;apps/services are only supported in native mode for this branch; rerun with --native\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e最后那个分支很重要。在这个分支上,app 和 service 只在 \u003ccode\u003e--native\u003c/code\u003e 下工作；经典的 Docker 开发模式仍然是仅函数的。这是一个已知限制,在配置参考中有明确文档(\u003ccode\u003edocs/en/reference/fastfn-config.md:13\u003c/code\u003e)。在旧的、基于 Docker 的网关路径里混合这两种生命周期,是那种我决定推迟的、剃牦牛毛式的活儿。原生模式是这个特性向前的唯一诚实的路。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003econfiguredImageWorkloads\u003c/code\u003e 这个 helper 是粘合剂。它从 viper 里读出 \u003ccode\u003eapps\u003c/code\u003e 和 \u003ccode\u003eservices\u003c/code\u003e 键,并通过 \u003ccode\u003eworkloads.NormalizeAppSpecs\u003c/code\u003e / \u003ccode\u003eNormalizeServiceSpecs\u003c/code\u003e 把它们归一化(\u003ccode\u003ecli/cmd/root.go:171-186\u003c/code\u003e)：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-21\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/cmd/root.go:171-186 (from 6a54c11 diff)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003econfiguredImageWorkloads\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eworkloads\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eConfig\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003ebool\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003eerror\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003ecfg\u003c/span\u003e \u003cspan class=\"nx\"\u003eworkloads\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eConfig\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eapps\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eappsSet\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eworkloads\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNormalizeAppSpecs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eviper\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eGet\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;apps\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003ecfg\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eservices\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservicesSet\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eworkloads\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNormalizeServiceSpecs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eviper\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eGet\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;services\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003ecfg\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ecfg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eApps\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eapps\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ecfg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eServices\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eservices\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003ecfg\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eappsSet\u003c/span\u003e \u003cspan class=\"o\"\u003e||\u003c/span\u003e \u003cspan class=\"nx\"\u003eservicesSet\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这就是全部接线。viper 读 \u003ccode\u003efastfn.json\u003c/code\u003e,工作负载包归一化它,而原生 runner 拉起一个管理器,它的 \u003ccode\u003eStatePath()\u003c/code\u003e 被盖印进每个运行时守护进程和每个 OpenResty worker 的环境里。\u003c/p\u003e\n\u003ch2 id=\"第七章firecracker-的转折\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e4%b8%83%e7%ab%a0firecracker-%e7%9a%84%e8%bd%ac%e6%8a%98\" class=\"header-mark\" aria-label=\"Header mark for '第七章：Firecracker 的转折'\"\u003e\u003c/a\u003e7 第七章：Firecracker 的转折\u003c/h2\u003e\u003cp\u003e到这个时候,我描述的一切都能在一台装了 Docker 的笔记本上工作,而我描述的一切都不需要 Firecracker。但整个分支叫 \u003ccode\u003efirecracker-simple-images\u003c/code\u003e,所以让我告诉你 Firecracker 在哪里登场,以及实际改变了什么。\u003c/p\u003e\n\u003cp\u003e在 Linux/KVM 主机上,管理器不是 \u003ccode\u003edocker_native.go\u003c/code\u003e。是 \u003ccode\u003efirecracker_manager_linux.go\u003c/code\u003e。同一个接口。同一个状态文件。底层的机制非常不同：OCI 镜像被转换成一个 Firecracker bundle(一个 \u003ccode\u003evmlinux\u003c/code\u003e 内核加一个 \u003ccode\u003erootfs.ext4\u003c/code\u003e),bundle 被缓存在 \u003ccode\u003e.fastfn/firecracker/images/\u003c/code\u003e 下,而一个微虚拟机以一份最小内核配置引导。公开契约是相同的：工作负载在它声明的端口上监听,网关代理到它,状态文件报告健康。\u003c/p\u003e\n\u003cp\u003e不同之处——而这正是提交 \u003ccode\u003e5568b6c\u003c/code\u003e(\u0026ldquo;Add vsock peer networking for Firecracker workloads\u0026rdquo;)重要的地方——在于网关实际上\u003cem\u003e如何触达\u003c/em\u003e一个 Firecracker 客户机。一个普通的 Docker 容器在一座桥上发布一个端口,而你拨 \u003ccode\u003e127.0.0.1:\u0026lt;hostport\u0026gt;\u003c/code\u003e。一个 Firecracker 微虚拟机默认没有这样的便利。于是这个提交引入了一个基于 vsock 的对等网络：一个客户机侧的 helper(\u003ccode\u003ecli/internal/firecrackerguest/main.go\u003c/code\u003e,在那个提交里有 455 行)负责终结 vsock,以及一个主机侧的 \u003ccode\u003eprivate_network.go\u003c/code\u003e 把这些部件缝合起来。网关仍然拨一个 \u003ccode\u003eInternalHost:InternalPort\u003c/code\u003e；那个 host/port 之下的管路是 vsock,而不是桥上的 TCP。抽象成立了。\u003c/p\u003e\n\u003cp\u003e后续的提交 \u003ccode\u003e6fd5fec\u003c/code\u003e(\u0026ldquo;Keep Firecracker image workloads hot by default\u0026rdquo;)是让这一切在实践中变得有用的那一个。没有它,一个刚引导起来的 Firecracker 工作负载在第一次请求时感觉很棒,但如果哪天被暂停就没那么棒了。有了保温(keep-hot)默认值,工作负载保持常驻,并在 \u003ccode\u003efastfn dev\u003c/code\u003e / \u003ccode\u003efastfn run\u003c/code\u003e 启动时被预热。文档把这叫做 \u0026ldquo;speed-first\u0026rdquo;(\u003ccode\u003edocs/en/reference/fastfn-config.md:302-307\u003c/code\u003e)：\u003ccode\u003eidle_action\u003c/code\u003e 默认为 \u003ccode\u003erun\u003c/code\u003e,\u003ccode\u003eprewarm\u003c/code\u003e 默认为 \u003ccode\u003etrue\u003c/code\u003e,对 app 和 service 都是如此。\u003c/p\u003e\n\u003cp\u003e真实的数字——而这些是从 \u003ccode\u003edocs/en/explanation/performance-benchmarks.md\u003c/code\u003e 里的基准测试矩阵复制粘贴过来的——是我唯一愿意把这其中任何东西称为完成的理由：\u003c/p\u003e\n\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eCase\u003c/th\u003e\n\u003cth\u003eSource\u003c/th\u003e\n\u003cth style=\"text-align:right\"\u003eBuild/Pull\u003c/th\u003e\n\u003cth style=\"text-align:right\"\u003eFirst OK\u003c/th\u003e\n\u003cth style=\"text-align:right\"\u003eHot p50\u003c/th\u003e\n\u003cth style=\"text-align:right\"\u003eHot p95\u003c/th\u003e\n\u003cth style=\"text-align:right\"\u003eHot p99\u003c/th\u003e\n\u003cth\u003eSame PID\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003eFlask (\u003ccode\u003eflask-compose\u003c/code\u003e)\u003c/td\u003e\n\u003ctd\u003eDockerfile repo\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e1168ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e5017ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e1.94ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e3.05ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e4.10ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003etrue\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eRegistry app (\u003ccode\u003etraefik/whoami:v1.10.2\u003c/code\u003e)\u003c/td\u003e\n\u003ctd\u003eRegistry image\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e98ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e2508ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e1.26ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e2.09ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e2.28ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003etrue\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eFastAPI + Postgres (\u003ccode\u003efastapi-realworld\u003c/code\u003e)\u003c/td\u003e\n\u003ctd\u003eDockerfile repo + private service\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e1202ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e17036ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e5.29ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e7.02ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e7.94ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003etrue\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTwo equal \u003ccode\u003epostgres:16\u003c/code\u003e services\u003c/td\u003e\n\u003ctd\u003eSame OCI, same native \u003ccode\u003e5432\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e1246ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e22090ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e10.92ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e28.85ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e32.58ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003etrue\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eRust + Postgres (\u003ccode\u003erust-postgres\u003c/code\u003e)\u003c/td\u003e\n\u003ctd\u003eDockerfile repo + private service\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e35139ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e47602ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e2.66ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e3.86ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e10.27ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003etrue\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e一个二十个案例的矩阵中的五行(2026 年 4 月 1 日的快照；完整列表在 \u003ccode\u003edocs/en/explanation/performance-benchmarks.md:46-54\u003c/code\u003e)。我对这张表的解读是：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e冷构建 + 预热是数秒,对一个 Rust 构建有时是数十秒。那不是免费的。但它只发生一次。\u003c/li\u003e\n\u003cli\u003e预热之后,热路径对轻量级 app 是低个位数毫秒,对有数据库支撑的仍然是个位到低双位数毫秒。\u003c/li\u003e\n\u003cli\u003e每一行里 \u003ccode\u003esame_firecracker_pid = true\u003c/code\u003e,这意味着热循环真的在复用同一个常驻微虚拟机。网关并没有在请求之间悄悄地重新生成 Firecracker。\u003c/li\u003e\n\u003cli\u003e两个相同的 \u003ccode\u003epostgres:16\u003c/code\u003e service 可以共享同一个原生 5432,只要它们的工作负载\u003cem\u003e名字\u003c/em\u003e不同。私有网络是按工作负载划分的；端口是在其各自客户机内部按进程划分的。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e那个诚实的注意事项——文档自己也承认,我也乐意重复(\u003ccode\u003edocs/en/explanation/performance-benchmarks.md:94-139\u003c/code\u003e)——是这个 20 案例的矩阵并非\u0026quot;二十个零改动的上游应用\u0026quot;。有些案例是上游原样。有些在上面加了一层基准测试覆盖。所有案例共享同一条 FastFN 运行时路径,但这套测试装置对它们当中的全部并非一个零接触的基准测试。我宁愿把这话大声说出来,也不愿给它上一层清漆。\u003c/p\u003e\n\u003cp\u003e而且没错,\u003ccode\u003efd8a6b5\u003c/code\u003e(\u0026ldquo;Add image workload firewall and benchmark matrix\u0026rdquo;)就是那个既带来了公开端口上 \u003ccode\u003eallow_hosts\u003c/code\u003e / \u003ccode\u003eallow_cidrs\u003c/code\u003e 访问控制、又带来了产出这些数字的工具的提交。我会在后面的文章里讲防火墙——它本身就是一整套美学——但简短版本是：一个公开的 app 可以被锁到一个 host 白名单和/或一个 CIDR 白名单上,两者都展示在我在第五章引用的那个网关打分函数里。\u003c/p\u003e\n\u003ch2 id=\"第八章函数遇见-service注入的故事\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e5%85%ab%e7%ab%a0%e5%87%bd%e6%95%b0%e9%81%87%e8%a7%81-service%e6%b3%a8%e5%85%a5%e7%9a%84%e6%95%85%e4%ba%8b\" class=\"header-mark\" aria-label=\"Header mark for '第八章：函数遇见 Service(注入的故事)'\"\u003e\u003c/a\u003e8 第八章：函数遇见 Service(注入的故事)\u003c/h2\u003e\u003cp\u003e回到我一带而过的一个细节。一个函数究竟是如何看到一个 service 的?\u003c/p\u003e\n\u003cp\u003e通过 env。当一个 service 引导起来时,管理器调用 \u003ccode\u003eBuildFunctionServiceEnv\u003c/code\u003e 并把结果藏进 service 的状态里(\u003ccode\u003estate.go:107-123\u003c/code\u003e)：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-22\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/workloads/state.go:107-123\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eBuildFunctionServiceEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eserviceName\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e \u003cspan class=\"nx\"\u003eServiceState\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ebaseEnv\u003c/span\u003e \u003cspan class=\"kd\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kd\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eout\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"kd\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003eappendScopedServiceEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eserviceName\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ebaseEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eupper\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nf\"\u003eserviceEnvToken\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eserviceName\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;SERVICE_\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003eupper\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;_HOST\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e         \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eHost\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;SERVICE_\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003eupper\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;_PORT\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e         \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eSprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;%d\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ePort\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;SERVICE_\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003eupper\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;_URL\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e          \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eURL\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;SERVICE_\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003eupper\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;_INTERNAL_HOST\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eInternalHost\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;SERVICE_\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003eupper\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;_INTERNAL_PORT\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eSprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;%d\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eInternalPort\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003estrings\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eTrimSpace\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eInternalURL\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;SERVICE_\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003eupper\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;_INTERNAL_URL\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eInternalURL\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003eappendDirectServiceAlias\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eserviceName\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eHost\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ePort\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eURL\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003eout\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e在 Lua 侧,当一个函数即将被调用时,网关把所有 service 的 FunctionEnv 的并集拉进它传给运行时守护进程的事件信封里(\u003ccode\u003eopenresty/lua/fastfn/http/gateway.lua:1169-1174\u003c/code\u003e)：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003elua\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-23\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e-- openresty/lua/fastfn/http/gateway.lua:1171-1174\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eservice_env\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eimage_workloads.function_env\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003enext\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eservice_env\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e~=\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eevent.env\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eservice_env\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这意味着一个 Python 函数可以直接这么写：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-24\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# a handler somewhere in functions/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003eos\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ehost\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eenviron\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;SERVICE_MYSQL_HOST\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eport\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eenviron\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;SERVICE_MYSQL_PORT\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eenviron\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;SERVICE_MYSQL_URL\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e……而且它能工作,无论背后的 MySQL 是 macOS 上的一个 Docker 容器还是 Linux 上的一个 Firecracker 微虚拟机。相同的变量名,相同的 URL scheme 推断,相同的代码。后端的改变是不可见的。\u003c/p\u003e\n\u003cp\u003e还有第二条路径。一个 \u003cstrong\u003eapp\u003c/strong\u003e 需要在进程启动时就拿到 service 的 env,而不是在请求时——因为一个 Next.js 应用是在 \u003ccode\u003enext start\u003c/code\u003e 时读 \u003ccode\u003eprocess.env\u003c/code\u003e,而不是每次请求都读。于是 docker-native 管理器从所有 service 构建一个 appServiceEnv 映射,并把它作为每个 app 的容器 env 传进去(\u003ccode\u003edocker_native.go:110-122\u003c/code\u003e,\u003ccode\u003e282-294\u003c/code\u003e)。因此 app 看到的是同样的 \u003ccode\u003eSERVICE_*\u003c/code\u003e 变量,但作用域是经由 \u003ccode\u003eBuildAppServiceEnv\u003c/code\u003e 而不是 \u003ccode\u003eBuildFunctionServiceEnv\u003c/code\u003e——区别在于你用的是内部主机名(对 app 而言,它们活在同一个私有网络上)还是公开 host(对函数而言,它们活在主机上)。\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-25\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                        service discovery fan-out\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                        ────────────────────────\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       ┌──────────────┐      BuildFunctionServiceEnv\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       │   service    │ ───────────────────────────▶  event.env\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       │  (e.g. mysql)│                               │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       └──────┬───────┘                               ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              │                                ┌─────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              │                                │  function   │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              │      BuildAppServiceEnv        └─────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              └──────────────────────▶\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                        container.Env\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                        │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                        ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                  ┌──────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                  │   app    │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                  └──────────┘\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e两个消费者。一个事实来源。看不到任何手写的 \u003ccode\u003eDATABASE_URL\u003c/code\u003e。\u003c/p\u003e\n\u003ch2 id=\"第九章我真正带走的那些教训\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e4%b9%9d%e7%ab%a0%e6%88%91%e7%9c%9f%e6%ad%a3%e5%b8%a6%e8%b5%b0%e7%9a%84%e9%82%a3%e4%ba%9b%e6%95%99%e8%ae%ad\" class=\"header-mark\" aria-label=\"Header mark for '第九章：我真正带走的那些教训'\"\u003e\u003c/a\u003e9 第九章：我真正带走的那些教训\u003c/h2\u003e\u003cp\u003e回头看那份 2254 行的 diff,这是我会对开始之前的过去的自己说的话。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e统一网关是整件事的要点。\u003c/strong\u003e 把 HTTP 切成\u0026quot;fastfn 处理函数 URL\u0026quot;和\u0026quot;docker-compose 处理其他一切\u0026quot;的诱惑是巨大的,因为它是阻力最小的路。但 HTTP 面的每一次切分,都是 CORS、认证、可观测性或 OpenAPI 上未来的一个 bug。让网关保持单一。给它更多种类的目标,而不是更多的朋友。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e两个生命周期模型可以共存,只要有一个配置文件来做编译。\u003c/strong\u003e 函数是请求作用域的。工作负载是长生命周期的。这些是真正不同的形态,有不同的失败模式。把它们俩都藏在一个 \u003ccode\u003efastfn.json\u003c/code\u003e 之后之所以行得通,是因为配置层把两种形态都编译成同一个\u0026quot;网关代理到的那个东西\u0026quot;的运行时表示。两个生命周期模型,一个配置文件,零遗憾。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e恰好声明一次镜像来源。\u003c/strong\u003e \u003ccode\u003eimage\u003c/code\u003e/\u003ccode\u003eimage_file\u003c/code\u003e/\u003ccode\u003edockerfile\u003c/code\u003e 上恰好选一个的规则,在第一天看起来很迂腐,在第两百天却把你从不可读的错误消息里救出来。你想要的是:有一种唯一的方式,让一个盯着 \u003ccode\u003efastfn.json\u003c/code\u003e 看的工程师能判断出这个工作负载是从哪里来的。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e一个 JSON 文件是你的 CLI 和你的网关之间一个相当好的 IPC 面,只要你小心。\u003c/strong\u003e 只在变化时写、按请求读、并通过一个 env 变量导出路径——这与我的先验相反——是一种极其平静的方式,在一个长生命周期的 Go 进程和一个长生命周期的 OpenResty 进程之间搬运信息。存在一个未来,这会变成一个 unix socket 和一个订阅模型。但目前,这个文件是诚实的,而且用 \u003ccode\u003ecat\u003c/code\u003e 就能轻松调试。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e按 service 本身是什么来给它命名。\u003c/strong\u003e 我差点就没有在 \u003ccode\u003eSERVICE_\u0026lt;NAME\u0026gt;_HOST\u003c/code\u003e 这些之外自动生成 \u003ccode\u003eMYSQL_HOST\u003c/code\u003e / \u003ccode\u003eMYSQL_URL\u003c/code\u003e 别名。然后我写了我第一个真正的 handler,记起人类并不想敲 \u003ccode\u003eSERVICE_MYSQL_HOST\u003c/code\u003e。直接别名是一个伪装成命名约定的可用性特性。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e基准测试让你保持诚实。\u003c/strong\u003e 上面那张矩阵表是我相信自己文档里\u0026quot;常驻\u0026quot;和\u0026quot;热\u0026quot;这些字眼的唯一理由。\u003ccode\u003esame_firecracker_pid = true\u003c/code\u003e 意味着没有人在我的请求和我的 p95 之间偷偷重启 VM。这个检查之所以存在,是因为早期它\u003cem\u003e不\u003c/em\u003e为真,而基准测试是唯一告诉我这一点的东西。度量那个性质,而不是那个意图。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e还有,关于种种保留意见。\u003c/strong\u003e 在这个分支上有很多我刻意没做的东西。app 和 service 只在 \u003ccode\u003e--native\u003c/code\u003e 下工作。Firecracker 工作负载只在 Linux/KVM 上工作。macOS 和 Windows 用户目前拿到的是 Docker 管理器而非 Firecracker,而那条路径在这个分支上,除了原生模式之外,根本不跑 image workload。滚动更新、蓝/绿、自动扩缩——全部缺席,而它们缺席是没问题的,因为这个阶段的要点是把抽象钉牢,而不是把运维钉牢。一个能干净地做三种后端的工作负载管理器,远比一个用 Kubernetes 曾经发布过的每一个特性来做一种后端的工作负载管理器更有价值。\u003c/p\u003e\n\u003cp\u003e接下来是什么?有两个方向我能清楚地看到。一个是把工作负载路径带进 Docker 模式的 \u003ccode\u003efastfn dev\u003c/code\u003e,这样 macOS 用户不用 Firecracker 也能得到同样的人体工学。另一个是让防火墙更丰富——超越 \u003ccode\u003eallow_hosts\u003c/code\u003e 和 \u003ccode\u003eallow_cidrs\u003c/code\u003e,变成某种能表达\u0026quot;这个 service 只能从这个私有网络上这些特定的工作负载触达\u0026quot;的东西。这两个都感觉像是真正的新篇章,而不是脚注。\u003c/p\u003e\n\u003cp\u003e目前,这个标题很小、很无聊、也很正确：如果你需要在函数旁边放一个 Postgres,你编辑你的 \u003ccode\u003efastfn.json\u003c/code\u003e,加上一个 \u003ccode\u003eservices.postgres\u003c/code\u003e 节,你的函数就在它们的环境里拿到 \u003ccode\u003eSERVICE_POSTGRES_URL\u003c/code\u003e。就这样。\u003c/p\u003e\n\u003cp\u003e函数对于请求/响应是一种绝佳的形态。工作负载是对于其余一切的形态。而现在它们活在同一个配置文件、同一个网关、同一个健康端点、同一个基准测试矩阵里,带着同样的保温默认值。\u003c/p\u003e\n\u003cp\u003e我本不想去写 docker-compose。我最后写出来的东西,与我实际用到的那十分之一的 docker-compose 相押韵,别的什么也没有。说实话,这大概是我职业生涯里第一次,我构建出来的平台比我以为我会构建的\u003cem\u003e更少\u003c/em\u003e。\u003c/p\u003e\n\u003cp\u003e第三部分见,我会把防火墙的故事、vsock 的管路,以及保温默认值逐一拆开细讲。在那之前:一个文件,一条路由,一个工作负载,一个网关。\u003c/p\u003e\n",
        "language": "zh-cn"
    }
    {
        "title" : "fastfn 第一部分：我遇到了一个问题（并把 Lua 引入了我的生活）",
        "date_published" : "2026-06-02T00:00:00Z",
        "date_modified" : "2026-06-02T00:00:00Z",
        "id" : "https://misael.org/zh-cn/fastfn-lua-to-our-lives/",
        "url" : "https://misael.org/zh-cn/fastfn-lua-to-our-lives/",
        "summary": "构建一个个人 FaaS 如何意外地与 FastCGI 押上了韵——多语言运行时、一个 Lua 网关，以及一个 JSON 线缆协议。第一部分讲函数侧；第二部分讲服务。",
        "content_html" : "\u003ch2 id=\"第一章那份痛楚或者说为什么一个-hello-world-要七个文件\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e4%b8%80%e7%ab%a0%e9%82%a3%e4%bb%bd%e7%97%9b%e6%a5%9a%e6%88%96%e8%80%85%e8%af%b4%e4%b8%ba%e4%bb%80%e4%b9%88%e4%b8%80%e4%b8%aa-hello-world-%e8%a6%81%e4%b8%83%e4%b8%aa%e6%96%87%e4%bb%b6\" class=\"header-mark\" aria-label=\"Header mark for '第一章：那份痛楚，或者说\u0026amp;quot;为什么一个 Hello World 要七个文件？\u0026amp;quot;'\"\u003e\u003c/a\u003e1 第一章：那份痛楚，或者说\u0026quot;为什么一个 Hello World 要七个文件？\u0026quot;\u003c/h2\u003e\u003cp\u003e整件事始于一个在 2026 年说出口几乎令人难堪的抱怨：我想往磁盘上扔一个 Python 文件，让它成为一个 HTTP 端点。就这样。不要 Dockerfile。除非我想要，否则也不要 \u003ccode\u003erequirements.txt\u003c/code\u003e。没有 \u003ccode\u003eapp = FastAPI()\u003c/code\u003e 样板代码，没有 \u003ccode\u003euvicorn\u003c/code\u003e 调用，没有那种留下十七个配置文件、让我接下来三个下午都在删除的\u0026quot;创建项目\u0026quot;向导。而且——这是它开始有了主见的部分——我想要一个合理的冷启动。不是 Lambda 那种冷，早上的第一个请求感觉像是带了额外步骤的 404。要温乎乎的。人类尺度的。\u003c/p\u003e\n\u003cp\u003e我厌倦了现代 Web 框架的形态。它们很美，能扩展，有生态系统，同时它们也要求你在第一条路由用 \u003ccode\u003e{\u0026quot;hello\u0026quot;: \u0026quot;world\u0026quot;}\u003c/code\u003e 响应之前，先在脑中编译出它们那个世界的一整套心智模型。对于一个用完即弃的内部工具来说，这是一笔用注意力支付的税。我已经支付这笔税十年了。我想停下来。\u003c/p\u003e\n\u003cp\u003e我想要的另一件事——也是悄悄驱动着故事其余部分的那个特性——是\u003cstrong\u003e默认多语言\u003c/strong\u003e。不是\u0026quot;用不同语言写的微服务通过 gRPC 对话\u0026quot;那种多语言。而是同一棵 URL 树里可以有 \u003ccode\u003eget.users.py\u003c/code\u003e、\u003ccode\u003epost.orders.js\u003c/code\u003e 和 \u003ccode\u003eget.health.go\u003c/code\u003e，彼此相邻地坐在同一个文件夹里，在同一个网关后面。把 Next.js 的基于文件的路由用于基于文件的处理器，与运行时无关。那就是梦想。\u003c/p\u003e\n\u003cp\u003e所以目标很清楚：一个 Function-as-a-Service 的东西，但本地优先，以 CLI 作为主要界面，以文件树作为数据库。我把它叫做 \u003ccode\u003efastfn\u003c/code\u003e。README 用一行描述了这个抱负：\u0026ldquo;Start with one file, a friendly CLI, and a route tree that can grow into a real API or SPA without a rewrite later\u0026rdquo;（\u003ccode\u003eREADME.md:8\u003c/code\u003e）。本文的其余部分讲的是那一句话如何变成了一个 Lua 网关、一个持久化的 Python 守护进程，以及一个带 4 字节长度前缀的线缆协议的故事。这是一个关于慢慢地、带着几分难堪地发现自己正在重新发明 FastCGI 的故事。\u003c/p\u003e\n\u003ch2 id=\"第二章一段简短略有偏颇的-cgi-历史\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e4%ba%8c%e7%ab%a0%e4%b8%80%e6%ae%b5%e7%ae%80%e7%9f%ad%e7%95%a5%e6%9c%89%e5%81%8f%e9%a2%87%e7%9a%84-cgi-%e5%8e%86%e5%8f%b2\" class=\"header-mark\" aria-label=\"Header mark for '第二章：一段简短、略有偏颇的 CGI 历史'\"\u003e\u003c/a\u003e2 第二章：一段简短、略有偏颇的 CGI 历史\u003c/h2\u003e\u003cp\u003e在我讲清楚 \u003ccode\u003efastfn\u003c/code\u003e 是什么之前，我得先谈谈它跟什么押韵。\u003c/p\u003e\n\u003ch3 id=\"cgi最早的-serverless\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cgi%e6%9c%80%e6%97%a9%e7%9a%84-serverless\" class=\"header-mark\" aria-label=\"Header mark for 'CGI：最早的 serverless'\"\u003e\u003c/a\u003e2.1 CGI：最早的 serverless\u003c/h3\u003e\u003cp\u003e起初有 CGI。在 serverless 成为一个品牌之前，Common Gateway Interface 就已经是 serverless 了。你把一个脚本放进 \u003ccode\u003e/cgi-bin/\u003c/code\u003e，Web 服务器对每一个请求 \u003ccode\u003efork\u003c/code\u003e 并 \u003ccode\u003eexec\u003c/code\u003e 它，它从环境变量和 stdin 读取请求，把响应写到 stdout，然后退出。操作系统负责清理。每个请求都是一个进程。每个进程都是一个存在 40 毫秒然后死去的宇宙。\u003c/p\u003e\n\u003cp\u003e这很美妙。这也是一桩性能罪行。\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-1\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCGI request model (what dies for you every time)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  client                 web server                 /cgi-bin/hello.pl\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |   HTTP GET /hello     |                            .\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    | --------------------\u0026gt; | fork()                     .\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |                       | exec(\u0026#34;perl hello.pl\u0026#34;)  --\u0026gt; [ cold Perl VM ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |                       |                            [ parse script ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |                       |                            [ run handler ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |                       | \u0026lt;-- stdout --              [ die ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    | \u0026lt;-- HTTP 200 --       |                            .\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |                       |                            .\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e每个请求都要为进程创建、解释器预热、库导入和拆解买单。在一台 1996 年的机器上这很痛。在一台 2026 年的机器上它依然痛，只是痛法不同：缓存冷的 Python 在你的处理器写下哪怕一个字节之前，会花上不小的一段时间仅仅导入它自己的标准库。我没有专门针对 \u003ccode\u003efastfn\u003c/code\u003e 做端到端的性能剖析，但量级足够大，以至于 FastCGI 风格的持久化池存在的意义正是为了把它摊销掉。\u003c/p\u003e\n\u003ch3 id=\"fastcgi那个修复以及我最终照抄的形态\" class=\"headerLink\"\u003e\n    \u003ca href=\"#fastcgi%e9%82%a3%e4%b8%aa%e4%bf%ae%e5%a4%8d%e4%bb%a5%e5%8f%8a%e6%88%91%e6%9c%80%e7%bb%88%e7%85%a7%e6%8a%84%e7%9a%84%e5%bd%a2%e6%80%81\" class=\"header-mark\" aria-label=\"Header mark for 'FastCGI：那个修复，以及我最终照抄的形态'\"\u003e\u003c/a\u003e2.2 FastCGI：那个修复，以及我最终照抄的形态\u003c/h3\u003e\u003cp\u003eFastCGI 的发明正是为了修复这个。事后看来这个想法几乎是显而易见的：不要在每个请求之后杀死处理器。保持一小撮处理器进程活着，让 Web 服务器通过 Unix socket 与它们对话，并对请求做帧封装，这样你就能干净地复用。Web 服务器是前端；处理器池是后端；它们之间流动的是一串带长度前缀的记录。\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-2\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eFastCGI\u003c/span\u003e \u003cspan class=\"n\"\u003erequest\u003c/span\u003e \u003cspan class=\"n\"\u003emodel\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ewhat\u003c/span\u003e \u003cspan class=\"n\"\u003edoesn\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;t die for you)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eclient\u003c/span\u003e         \u003cspan class=\"n\"\u003eweb\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e           \u003cspan class=\"n\"\u003eunix\u003c/span\u003e \u003cspan class=\"n\"\u003esocket\u003c/span\u003e          \u003cspan class=\"n\"\u003ehandler\u003c/span\u003e \u003cspan class=\"n\"\u003epool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e|\u003c/span\u003e  \u003cspan class=\"n\"\u003eHTTP\u003c/span\u003e \u003cspan class=\"n\"\u003eGET\u003c/span\u003e    \u003cspan class=\"o\"\u003e|\u003c/span\u003e                       \u003cspan class=\"o\"\u003e|\u003c/span\u003e                    \u003cspan class=\"o\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"o\"\u003e-----------\u0026gt;\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"n\"\u003epack\u003c/span\u003e \u003cspan class=\"n\"\u003erecord\u003c/span\u003e           \u003cspan class=\"o\"\u003e|\u003c/span\u003e                    \u003cspan class=\"o\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e|\u003c/span\u003e              \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"o\"\u003e--------\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003elen\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003epayload\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e------------\u0026gt;\u003c/span\u003e       \u003cspan class=\"p\"\u003e[\u003c/span\u003e \u003cspan class=\"n\"\u003ealready\u003c/span\u003e \u003cspan class=\"n\"\u003ewarm\u003c/span\u003e \u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e|\u003c/span\u003e              \u003cspan class=\"o\"\u003e|\u003c/span\u003e                       \u003cspan class=\"o\"\u003e|\u003c/span\u003e                    \u003cspan class=\"p\"\u003e[\u003c/span\u003e \u003cspan class=\"n\"\u003erun\u003c/span\u003e \u003cspan class=\"n\"\u003ehandler\u003c/span\u003e \u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e|\u003c/span\u003e              \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;-------\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003elen\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003epayload\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e------------\u003c/span\u003e        \u003cspan class=\"p\"\u003e[\u003c/span\u003e \u003cspan class=\"n\"\u003esleep\u003c/span\u003e \u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;---\u003c/span\u003e \u003cspan class=\"n\"\u003eHTTP\u003c/span\u003e \u003cspan class=\"o\"\u003e--\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e                       \u003cspan class=\"o\"\u003e|\u003c/span\u003e                    \u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e处理器是长寿的。解释器是热的。你处理器的全局状态在请求之间存活（有好有坏）。传输是一个无聊的 socket，带着帧封装的记录。你用每请求一进程的隔离性,换来了某种更接近\u0026quot;跨线缆调用\u0026quot;的东西。这是一笔非常划算的交易，而且本质上正是现代 Python WSGI 服务器、PHP-FPM 以及——咱们就实话实说吧——AWS Lambda 的热启动池在内部所做的事情。\u003c/p\u003e\n\u003cp\u003e我并没有打算去构建一个 FastCGI。我打算构建一个 serverless 的东西，你扔进一个文件，它就变成一条路由。结果是，一旦你想要热启动、多语言处理器，以及一棵可按文件系统寻址的路由树，设计空间就把你漏斗般地引向某种诡异地酷似身披 JSON 风衣的 FastCGI 的东西。风衣的事稍后再说。\u003c/p\u003e\n\u003ch2 id=\"第三章我遇到了一个问题并把-lua-引入了我的生活\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e4%b8%89%e7%ab%a0%e6%88%91%e9%81%87%e5%88%b0%e4%ba%86%e4%b8%80%e4%b8%aa%e9%97%ae%e9%a2%98%e5%b9%b6%e6%8a%8a-lua-%e5%bc%95%e5%85%a5%e4%ba%86%e6%88%91%e7%9a%84%e7%94%9f%e6%b4%bb\" class=\"header-mark\" aria-label=\"Header mark for '第三章：我遇到了一个问题……并把 Lua 引入了我的生活'\"\u003e\u003c/a\u003e3 第三章：我遇到了一个问题……并把 Lua 引入了我的生活\u003c/h2\u003e\u003cp\u003e这就是标题说得通的部分。\u003c/p\u003e\n\u003cp\u003e网关——那个终结 HTTP、读取请求、弄清楚磁盘上哪个文件该处理它、并转发调用的部件——是任何 FaaS 中最重要也最恼人的部件。它必须快。它必须在你保存文件时热重载。它必须做路由、认证、cookie、CORS、OpenAPI，而且它必须在做这一切的同时不变成一个带 400 毫秒冷启动的 30 MB Node 进程。\u003c/p\u003e\n\u003cp\u003e我试过用 Go 来写它。还行。但对于本质上就是\u0026quot;接收一个请求，查一个文件，开一个 socket，写，读，写响应\u0026quot;的东西来说，那也是一大堆代码。后来某个晚上我想起 OpenResty——内嵌了 Lua 的 nginx——已经把困难的部分（HTTP 解析、TLS、epoll、共享内存）做好了，只是让我用一种亚毫秒级启动的脚本语言去编写策略层。你不是每个请求启动一次 OpenResty。OpenResty 在进程启动时启动一次，然后你的 Lua 就在请求阶段的钩子里运行。把它想成是 Web 服务器邀请你的代码作为客人住进它的事件循环里。\u003c/p\u003e\n\u003cp\u003e于是我把 Lua 引入了我的生活。这与其说是一个决定，不如说是一棵内部的选项之树，不断地分叉展开：我需要的每三件事里就有一件结果是\u0026quot;哦，我可以直接在 Lua 里做这件事，而且它能工作\u0026quot;。路由发现？Lua 遍历一个目录。共享内存的路由表？\u003ccode\u003engx.shared.DICT\u003c/code\u003e。文件保存时热重载？一个微小的 Lua 定时器，对函数目录做 stat，并在一个 shared dict 里重建路由表。会话 cookie 解析？十二行：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/http/gateway.lua:70-82\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-3\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nf\"\u003eparse_cookies\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecookie_header\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003ecookies\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecookie_header\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e~=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;string\u0026#34;\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"n\"\u003ecookie_header\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003ecookies\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003epair\u003c/span\u003e \u003cspan class=\"kr\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003ecookie_header\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003egmatch\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;[^;]+\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kr\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ev\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epair\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003ematch\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;^%s*([^=]+)%s*=%s*(.-)%s*$\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003ek\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003ek\u003c/span\u003e \u003cspan class=\"o\"\u003e~=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003ecookies\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ev\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003ecookies\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e十二行。没有 \u003ccode\u003enpm install cookie-parser\u003c/code\u003e。没有 \u003ccode\u003ego get github.com/gorilla/sessions\u003c/code\u003e。没有那种 2019 年被弃用、如今由一个 bot 维护的传递依赖。十二行运行在 nginx 请求阶段里的 Lua，网关现在就懂会话 cookie 了。\u003c/p\u003e\n\u003cdiv class=\"details admonition info open\"\u003e\n    \u003cdiv class=\"details-summary admonition-title\"\u003e\n        \u003cspan class=\"icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z\"/\u003e\u003c/svg\u003e\u003c/span\u003e关于 Lua 的一点说明\u003cspan class=\"details-icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"details-content\"\u003e\n        \u003cdiv class=\"admonition-content\"\u003eLua 是一门奇怪的语言。它从 1 开始索引。它只有一种数据结构（表），并且在你眯起眼睛之前都没有真正的模块系统。它的标准库简直是近乎激进的极简。这些在这里都没有真正造成伤害——网关很短，热路径留在 LuaJIT 里，而 Lua \u003cem\u003e没有\u003c/em\u003e的那些东西（一个庞大的运行时、一个包生态系统、一套复杂的类型系统）恰恰是你在 nginx 内部的请求热路径里不想要的东西。\u003c/div\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp\u003e而它非常适合这份工作。运行时小，代码小，延迟小，整个网关——路由、会话解析、请求编组、响应解组、OpenAPI 端点、提供 Swagger UI——都活在 \u003ccode\u003eopenresty/lua/fastfn/\u003c/code\u003e 下的一小撮 Lua 文件里。\u003ccode\u003ehttp/\u003c/code\u003e 子树里恰好有你会从一个真正的网关期待的那些模块：\u003ccode\u003egateway.lua\u003c/code\u003e、\u003ccode\u003eassets.lua\u003c/code\u003e、\u003ccode\u003ecatalog.lua\u003c/code\u003e、\u003ccode\u003eopenapi_endpoint.lua\u003c/code\u003e、\u003ccode\u003eswagger_ui.lua\u003c/code\u003e、\u003ccode\u003ereload.lua\u003c/code\u003e。主 \u003ccode\u003egateway.lua\u003c/code\u003e 有 1341 行；实现线缆协议的整个 \u003ccode\u003eclient.lua\u003c/code\u003e 有 110 行。我马上会引用它的大部分。\u003c/p\u003e\n\u003ch3 id=\"路由表是一个-shared-dict\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e8%b7%af%e7%94%b1%e8%a1%a8%e6%98%af%e4%b8%80%e4%b8%aa-shared-dict\" class=\"header-mark\" aria-label=\"Header mark for '路由表是一个 shared dict'\"\u003e\u003c/a\u003e3.1 路由表是一个 shared dict\u003c/h3\u003e\u003cp\u003e让 Lua 网关快起来的诀窍并不聪明。诀窍在于路由表不住在数据库里，不住在 Redis 里，甚至不住在每个 worker 的内存里。它住在 \u003ccode\u003engx.shared.fn_cache\u003c/code\u003e 里，这是一个 nginx 共享内存区，每个 worker 进程都可读。当 \u003ccode\u003efastfn dev\u003c/code\u003e 启动时，Lua 遍历函数目录，构建一个索引——\u0026quot;\u003ccode\u003eGET /hello\u003c/code\u003e → \u003ccode\u003e/functions/get.hello.py\u003c/code\u003e → \u003ccode\u003epython\u003c/code\u003e 运行时\u0026quot;——并把它塞进 shared dict。文件变化时，一段 reload Lua 代码块重建它。请求在 \u003ccode\u003eaccess_by_lua\u003c/code\u003e 阶段做一次 shared-dict 查找然后分发。没有\u0026quot;框架\u0026quot;。有的是一张哈希表和一套约定。\u003c/p\u003e\n\u003ch2 id=\"第四章身披-json-风衣的-fastcgi\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e5%9b%9b%e7%ab%a0%e8%ba%ab%e6%8a%ab-json-%e9%a3%8e%e8%a1%a3%e7%9a%84-fastcgi\" class=\"header-mark\" aria-label=\"Header mark for '第四章：身披 JSON 风衣的 FastCGI'\"\u003e\u003c/a\u003e4 第四章：身披 JSON 风衣的 FastCGI\u003c/h2\u003e\u003cp\u003e现在讲协议。这就是我意外重建了 FastCGI 的部分。\u003c/p\u003e\n\u003cp\u003e当 Lua 网关判定 \u003ccode\u003eGET /hello\u003c/code\u003e 应当由 Python 运行时来服务时，它需要把请求送到 Python 守护进程那里。Python 守护进程是一个长寿进程，监听在一个 Unix socket 上（默认 \u003ccode\u003e/tmp/fastfn/fn-python.sock\u003c/code\u003e，可通过 \u003ccode\u003eFN_PY_SOCKET\u003c/code\u003e 配置，你可以在 \u003ccode\u003esrv/fn/runtimes/python-daemon.py:25\u003c/code\u003e 看到）。网关打开 socket，写入请求，读取响应。\u003c/p\u003e\n\u003cp\u003e帧格式刻意做得很无聊：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-4\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"mi\"\u003e4\u003c/span\u003e \u003cspan class=\"n\"\u003ebytes\u003c/span\u003e            \u003cspan class=\"n\"\u003eN\u003c/span\u003e \u003cspan class=\"n\"\u003ebytes\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"o\"\u003e---------------\u003c/span\u003e    \u003cspan class=\"o\"\u003e----------------------------------------\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"n\"\u003ebig\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003eendian\u003c/span\u003e  \u003cspan class=\"o\"\u003e|\u003c/span\u003e    \u003cspan class=\"o\"\u003e|\u003c/span\u003e                                      \u003cspan class=\"o\"\u003e|\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"n\"\u003euint32\u003c/span\u003e \u003cspan class=\"n\"\u003elen\u003c/span\u003e  \u003cspan class=\"o\"\u003e|\u003c/span\u003e    \u003cspan class=\"o\"\u003e|\u003c/span\u003e  \u003cspan class=\"n\"\u003eJSON\u003c/span\u003e \u003cspan class=\"n\"\u003epayload\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erequest\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e  \u003cspan class=\"o\"\u003e|\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"o\"\u003e---------------\u003c/span\u003e    \u003cspan class=\"o\"\u003e----------------------------------------\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e四字节的大端长度。然后是那么多字节的 JSON。这就是整个协议。它就是 FastCGI 的记录帧封装，简化为单一记录类型，记录体是 JSON 而不是 FastCGI 的键值二进制编码。如果 FastCGI 披上一件风衣、试图冒充一个现代 REST API，它就会这么打扮。\u003c/p\u003e\n\u003cp\u003e线缆的 Lua 一侧小到可以完整引用。这是那个发送请求并解析响应的客户端：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/core/client.lua:22-106\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-5\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nc\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ecall_unix\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esocket_uri\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ereq_obj\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etimeout_ms\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003epayload\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecjson.encode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ereq_obj\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003epayload\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;invalid_request\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;failed to encode request\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003econnect_timeout\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003emath.max\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e50\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003emath.floor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etimeout_ms\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.2\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eio_timeout\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003emath.max\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e50\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etimeout_ms\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003esock\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003engx.socket\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etcp\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003esock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003esettimeouts\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003econnect_timeout\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eio_timeout\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eio_timeout\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eok\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003econnect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esocket_uri\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003eok\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eis_timeout\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;timeout\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;connect timeout\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;connect_error\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003esent\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003esend_err\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003esend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epack_u32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"n\"\u003epayload\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e..\u003c/span\u003e \u003cspan class=\"n\"\u003epayload\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003esent\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003esock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eis_timeout\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esend_err\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;timeout\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;send timeout\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;send_error\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esend_err\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eheader\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eheader_err\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003ereceive\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003eheader\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003esock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;receive_error\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eheader_err\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003ebody_len\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eunpack_u32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eheader\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003ebody_len\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"n\"\u003ebody_len\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e10\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mi\"\u003e1024\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mi\"\u003e1024\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003esock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;invalid_response\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;invalid frame length\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ebody_err\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003ereceive\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebody_len\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003esock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"c1\"\u003e-- decode, validate, return\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e手写的 \u003ccode\u003epack_u32\u003c/code\u003e 函数是一个很好的提醒：Lua 5.1（OpenResty 所用的版本）并不自带 \u003ccode\u003estring.pack\u003c/code\u003e，所以我自己实现它：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/core/client.lua:5-11\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-6\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nf\"\u003epack_u32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eb1\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003emath.floor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e \u003cspan class=\"mi\"\u003e16777216\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"mi\"\u003e256\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eb2\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003emath.floor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e \u003cspan class=\"mi\"\u003e65536\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"mi\"\u003e256\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eb3\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003emath.floor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e \u003cspan class=\"mi\"\u003e256\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"mi\"\u003e256\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eb4\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003en\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"mi\"\u003e256\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003estring.char\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eb1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eb2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eb3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eb4\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e帧上限是 10 MB（\u003ccode\u003ebody_len \u0026gt; 10 * 1024 * 1024\u003c/code\u003e）。Python 一侧通过 \u003ccode\u003eFN_MAX_FRAME_BYTES\u003c/code\u003e 施加一个对称的限制，其默认值是 2 MB（\u003ccode\u003esrv/fn/runtimes/python-daemon.py:26\u003c/code\u003e）。是的，两个默认值并不匹配。这是故意的：网关比守护进程稍微宽容一点，这样一个配置错误的守护进程会在守护进程处失败，而不是在网关处陷入一次令人困惑的半读。这是那种在 code review 里读起来不对、但在你凌晨 2 点排查生产事故时读起来很对的不对称。\u003c/p\u003e\n\u003ch3 id=\"请求生命周期附带近似的毫秒数\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e8%af%b7%e6%b1%82%e7%94%9f%e5%91%bd%e5%91%a8%e6%9c%9f%e9%99%84%e5%b8%a6%e8%bf%91%e4%bc%bc%e7%9a%84%e6%af%ab%e7%a7%92%e6%95%b0\" class=\"header-mark\" aria-label=\"Header mark for '请求生命周期，附带近似的毫秒数'\"\u003e\u003c/a\u003e4.1 请求生命周期，附带近似的毫秒数\u003c/h3\u003e\u003cp\u003e一旦你有了线缆协议，整个请求生命周期就容易画出来了。这是一个热请求，附带我在一台 2024 年年中运行 Linux 的笔记本上测量的粗略计时：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-7\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.00\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003eclient\u003c/span\u003e \u003cspan class=\"n\"\u003esends\u003c/span\u003e \u003cspan class=\"n\"\u003eHTTP\u003c/span\u003e \u003cspan class=\"n\"\u003eGET\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ehello\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.10\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003enginx\u003c/span\u003e \u003cspan class=\"n\"\u003eaccept\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eparse\u003c/span\u003e \u003cspan class=\"n\"\u003eHTTP\u003c/span\u003e \u003cspan class=\"n\"\u003eheaders\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.20\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003eLua\u003c/span\u003e \u003cspan class=\"n\"\u003eaccess_by_lua\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eroute\u003c/span\u003e \u003cspan class=\"n\"\u003elookup\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003engx\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eshared\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efn_cache\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.30\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003eLua\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ecjson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eencode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ereq_obj\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eJSON\u003c/span\u003e \u003cspan class=\"n\"\u003epayload\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.40\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003eLua\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003engx\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esocket\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etcp\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e\u003cspan class=\"n\"\u003econnect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/tmp/fastfn/fn-python.sock\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.50\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003eLua\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003esend\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e \u003cspan class=\"n\"\u003elen\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003eJSON\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.60\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003ePython\u003c/span\u003e \u003cspan class=\"n\"\u003edaemon\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003erecv\u003c/span\u003e \u003cspan class=\"mi\"\u003e4\u003c/span\u003e \u003cspan class=\"n\"\u003ebytes\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edecode\u003c/span\u003e \u003cspan class=\"n\"\u003elength\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.70\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003ePython\u003c/span\u003e \u003cspan class=\"n\"\u003edaemon\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003erecv\u003c/span\u003e \u003cspan class=\"n\"\u003eN\u003c/span\u003e \u003cspan class=\"n\"\u003ebytes\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ejson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eloads\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.80\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003ePython\u003c/span\u003e \u003cspan class=\"n\"\u003edaemon\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003edispatch\u003c/span\u003e \u003cspan class=\"n\"\u003eto\u003c/span\u003e \u003cspan class=\"n\"\u003ehandler\u003c/span\u003e \u003cspan class=\"n\"\u003efrom\u003c/span\u003e \u003cspan class=\"n\"\u003e_HANDLER_CACHE\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e1.10\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003ehandler\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eevent\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003ereturns\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;status\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e200\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;body\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;...\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e1.20\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003ePython\u003c/span\u003e \u003cspan class=\"n\"\u003edaemon\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ejson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edumps\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003esend\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e \u003cspan class=\"n\"\u003elen\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003eJSON\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e1.30\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003eLua\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003erecv\u003c/span\u003e \u003cspan class=\"n\"\u003eheader\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003erecv\u003c/span\u003e \u003cspan class=\"n\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecjson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edecode\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e1.40\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003eLua\u003c/span\u003e \u003cspan class=\"n\"\u003econtent_by_lua\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003engx\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esay\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eresp\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e1.50\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003enginx\u003c/span\u003e \u003cspan class=\"n\"\u003eflushes\u003c/span\u003e \u003cspan class=\"n\"\u003eHTTP\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse\u003c/span\u003e \u003cspan class=\"n\"\u003eto\u003c/span\u003e \u003cspan class=\"n\"\u003eclient\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这些数字是粗略的——我没有为纯函数路径本身发布过端到端基准，所以请把这份逐行的毫秒拆解当作大致估计，而非金科玉律。即便确切数字会有漂移，形态是对的。冷启动是一个非常不同的话题，我会讲到。\u003c/p\u003e\n\u003ch2 id=\"第五章多语言运行时或者说没有亚马逊的-lambda\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e4%ba%94%e7%ab%a0%e5%a4%9a%e8%af%ad%e8%a8%80%e8%bf%90%e8%a1%8c%e6%97%b6%e6%88%96%e8%80%85%e8%af%b4%e6%b2%a1%e6%9c%89%e4%ba%9a%e9%a9%ac%e9%80%8a%e7%9a%84-lambda\" class=\"header-mark\" aria-label=\"Header mark for '第五章：多语言运行时，或者说\u0026amp;quot;没有亚马逊的 Lambda\u0026amp;quot;'\"\u003e\u003c/a\u003e5 第五章：多语言运行时，或者说\u0026quot;没有亚马逊的 Lambda\u0026quot;\u003c/h2\u003e\u003cp\u003e线缆协议刻意做成与语言无关。Python 守护进程是一个实现。还有一个 Node 守护进程。也有通往 Rust、Go、PHP 和 Lua 处理器的路径。每个运行时守护进程都做出同样的承诺：打开一个 Unix socket，讲那个 4 字节长度 + JSON 的协议，当一个请求进来时，分发到一个具有 AWS Lambda 所闻名的那种签名的函数。\u003c/p\u003e\n\u003cp\u003e这是一个真实的处理器，来自多语言教程示例：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eexamples/functions/polyglot-tutorial/step-2/index.py:1-18\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-8\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003ejson\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ehandler\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eevent\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003equery\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eevent\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;query\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ename\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003equery\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;name\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;friend\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;status\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e200\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;headers\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Content-Type\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;application/json\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;body\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ejson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edumps\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;step\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;message\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Hello \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e from Python.\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;runtime\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;python\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;name\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这就是整个契约。\u003ccode\u003edef handler(event): return {...}\u003c/code\u003e。没有亚马逊的 Lambda 形态。没有 Cloudflare 的 Cloudflare-Workers 形态。它也是，如果你眯起眼睛，不过是把环境变量和 stdin 换成了一个 JSON 信封和一个热 socket 的 CGI。这个形态自 1990 年代以来一直稳定，因为它就是对的形态：请求是一个字典，响应是一个字典，处理器是一个从前者到后者的纯函数。\u003c/p\u003e\n\u003cp\u003e守护进程显式支持三种 invoke 适配器（\u003ccode\u003esrv/fn/runtimes/python-daemon.py:51-53\u003c/code\u003e）：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-9\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e_INVOKE_ADAPTER_NATIVE\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;native\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e_INVOKE_ADAPTER_AWS_LAMBDA\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;aws-lambda\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e_INVOKE_ADAPTER_CLOUDFLARE_WORKER\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;cloudflare-worker\u0026#34;\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e\u0026ldquo;native\u0026rdquo; 是我的默认形态。另外两个存在是为了让你能把一个现有的 Lambda 或 Cloudflare Worker 几乎不做改动地搬进 fastfn。那种互操作性不是免费的；它花了我一个细心的事件形态转换器。但它意味着你可以拿起你已经跑在别人 serverless 上的代码，用 \u003ccode\u003efastfn dev\u003c/code\u003e 在本地运行它，当你雇主的 AWS 控制台又在过它\u0026quot;糟心的一周\u0026quot;时，这是真正有用的。\u003c/p\u003e\n\u003ch3 id=\"守护进程内部的一个-worker-池\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e5%ae%88%e6%8a%a4%e8%bf%9b%e7%a8%8b%e5%86%85%e9%83%a8%e7%9a%84%e4%b8%80%e4%b8%aa-worker-%e6%b1%a0\" class=\"header-mark\" aria-label=\"Header mark for '守护进程内部的一个 worker 池'\"\u003e\u003c/a\u003e5.1 守护进程内部的一个 worker 池\u003c/h3\u003e\u003cp\u003ePython 守护进程不只是在主线程里分发。它有一个 \u003ccode\u003eThreadPoolExecutor\u003c/code\u003e 顶在一个处理器 worker 池前面，由一个槽位预算门控。这些旋钮全都是环境变量：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003esrv/fn/runtimes/python-daemon.py:38-40\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-10\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eRUNTIME_POOL_ACQUIRE_TIMEOUT_MS\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eenviron\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;FN_PY_POOL_ACQUIRE_TIMEOUT_MS\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;5000\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eRUNTIME_POOL_IDLE_TTL_MS\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eenviron\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;FN_PY_POOL_IDLE_TTL_MS\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;300000\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eRUNTIME_POOL_REAPER_INTERVAL_MS\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eenviron\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;FN_PY_POOL_REAPER_INTERVAL_MS\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;2000\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e所以：获取超时 5 秒，空闲 TTL 5 分钟，回收器每 2 秒运行一次。这些是穿着 Python 衣服的 FastCGI 池数字。回收线程遍历池，杀掉超过其 TTL 的空闲 worker，并削减占用。获取超时是那个\u0026quot;我满负荷了，请退避\u0026quot;的信号。\u003c/p\u003e\n\u003ch2 id=\"第六章从头到尾都是-lua\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e5%85%ad%e7%ab%a0%e4%bb%8e%e5%a4%b4%e5%88%b0%e5%b0%be%e9%83%bd%e6%98%af-lua\" class=\"header-mark\" aria-label=\"Header mark for '第六章：从头到尾都是 Lua'\"\u003e\u003c/a\u003e6 第六章：从头到尾都是 Lua\u003c/h2\u003e\u003cp\u003e第三章是我把 Lua 引入生活的那一刻。我当时没领会到的是，它会以多快的速度殖民控制平面的每一个角落。我会着手用 Lua 解决一个问题——\u0026ldquo;让我解析一个 cookie\u0026rdquo;——而到下午结束时，路由表、OpenAPI 生成器、限流器和管理仪表盘也全都是 Lua 了，跑在同一个 nginx worker 里，共享着同样的 \u003ccode\u003engx.shared.DICT\u003c/code\u003e 区，它们之间没有任何进程间的跳跃。\u003c/p\u003e\n\u003cp\u003e这是我列举 Lua 运行之处的那一章。Lua 不是什么秘密武器。它是一门带快速 JIT 的小语言，内嵌在一个已经把困难部分做好的 Web 服务器里，而它恰好像钥匙配锁一样契合这个问题。\u003c/p\u003e\n\u003ch3 id=\"路由与发现文件树就是数据库\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e8%b7%af%e7%94%b1%e4%b8%8e%e5%8f%91%e7%8e%b0%e6%96%87%e4%bb%b6%e6%a0%91%e5%b0%b1%e6%98%af%e6%95%b0%e6%8d%ae%e5%ba%93\" class=\"header-mark\" aria-label=\"Header mark for '路由与发现：文件树\u0026lt;em\u0026gt;就是\u0026lt;/em\u0026gt;数据库'\"\u003e\u003c/a\u003e6.1 路由与发现：文件树\u003cem\u003e就是\u003c/em\u003e数据库\u003c/h3\u003e\u003cp\u003eLua 赢下的第一个地方是路由。路由不住在 YAML 文件或配置对象里；它们住在文件系统里。\u003ccode\u003ecore/routes.lua\u003c/code\u003e 在启动时遍历函数目录，从每个处理器文件读取元数据，并构建一个\u0026quot;\u003ccode\u003eGET /hello\u003c/code\u003e → Python → \u003ccode\u003eget.hello.py\u003c/code\u003e\u0026ldquo;的目录。然后它把目录缓存进 \u003ccode\u003engx.shared.fn_cache\u003c/code\u003e，这样每个 worker 都免费得到同样的视图。发现入口就只是这个：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/core/routes.lua:2228-2237\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-11\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nc\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ediscover_functions\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eforce\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003eforce\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eraw\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eCACHE\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;catalog:raw\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eraw\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eparsed\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecjson.decode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eraw\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eparsed\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eparsed\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"c1\"\u003e-- ... walk functions_root, populate catalog.runtimes / mapped_routes ...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e\u003ccode\u003ecore/routes.lua\u003c/code\u003e 是大块头——约 3,245 行——因为路由是每一项关切最终都会相遇之处：方法、保留前缀、冲突、来源排名。挨着它的是 \u003ccode\u003ecore/fs.lua\u003c/code\u003e（约 392 行），一个围绕 \u003ccode\u003estat\u003c/code\u003e、\u003ccode\u003eopendir\u003c/code\u003e、\u003ccode\u003ereaddir\u003c/code\u003e 等等的小 FFI 封装。它存在是因为 LuaJIT 的 FFI 让我能跳过 LuaFileSystem C 模块、直接调用 libc，从而保持 OpenResty 镜像精简：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/core/fs.lua:263-280\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-12\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nc\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003elist_dirs_recursive\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eskip_fn\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eout\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003eM.is_dir\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eout\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nf\"\u003ewalk\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edir\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eskip_fn\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;function\u0026#34;\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003eskip_fn\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edir\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edir\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003echild\u003c/span\u003e \u003cspan class=\"kr\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eipairs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eM.list_dirs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edir\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \u003cspan class=\"kr\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003ewalk\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003echild\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003ewalk\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003etable.sort\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eout\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这就是文件系统遍历。Lua 世界的 \u003ccode\u003ereaddir\u003c/code\u003e，带一个可插拔的 \u003ccode\u003eskip_fn\u003c/code\u003e 来剪掉 \u003ccode\u003e.git\u003c/code\u003e、\u003ccode\u003enode_modules\u003c/code\u003e、\u003ccode\u003e__pycache__\u003c/code\u003e 和 \u003ccode\u003e.fastfn\u003c/code\u003e。它返回一个排好序的目录列表，\u003ccode\u003eroutes.lua\u003c/code\u003e 和 \u003ccode\u003ewatchdog.lua\u003c/code\u003e 都依赖它。这让我想到：如果路由是磁盘上的一棵树，那么 OpenAPI 规范就是那同一棵树的一个投影，而仪表盘不过是它之上的一个 UI。\u003c/p\u003e\n\u003ch3 id=\"openapi免费\" class=\"headerLink\"\u003e\n    \u003ca href=\"#openapi%e5%85%8d%e8%b4%b9\" class=\"header-mark\" aria-label=\"Header mark for 'OpenAPI，免费'\"\u003e\u003c/a\u003e6.2 OpenAPI，免费\u003c/h3\u003e\u003cp\u003e那个\u0026quot;同一棵树的投影\u0026quot;的想法变成了 \u003ccode\u003ecore/openapi.lua\u003c/code\u003e，一个 1,309 行的模块，它接收 \u003ccode\u003eroutes.lua\u003c/code\u003e 产出的目录并发出一个 OpenAPI 3 文档。处理器函数上没有装饰器。没有 \u003ccode\u003e@app.route(\u0026quot;/hello\u0026quot;)\u003c/code\u003e。处理器就只是 \u003ccode\u003edef handler(event): return {...}\u003c/code\u003e，而规范是从路由树加上一薄层每函数元数据（\u003ccode\u003efn.config.json\u003c/code\u003e，如果处理器肯费心写一个的话）生成的。\u003c/p\u003e\n\u003cp\u003e它的 HTTP 一侧是一个 101 行的 Lua 文件，把这一切接起来：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/http/openapi_endpoint.lua:92-101\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-13\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003espec\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eopenapi.build\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecatalog\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eserver_url\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eserver_url\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eruntime_order\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eroutes_mod.get_runtime_order\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003einclude_internal\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eenv_bool\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;FN_OPENAPI_INCLUDE_INTERNAL\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003einvoke_meta_lookup\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003einvoke_meta_lookup\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003engx.status\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e200\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003engx.header\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Content-Type\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;application/json\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003engx.say\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecjson.encode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这就是整个 \u003ccode\u003e/openapi.json\u003c/code\u003e 端点。重载你的处理器，访问那个 URL，规范就反映出它。Swagger UI（\u003ccode\u003ehttp/swagger_ui.lua\u003c/code\u003e，119 行）提供一个指向那份 JSON 的静态页面，于是你不用跑一个单独的文档服务器就有了交互式文档。OpenAPI 生成器还依赖 \u003ccode\u003ecore/invoke_rules.lua\u003c/code\u003e 来归一化每函数的调用策略——哪些方法被允许，哪些路由被保留：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/core/invoke_rules.lua:4-14\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-14\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eM.ALLOWED_METHODS\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eGET\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003ePOST\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003ePUT\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003ePATCH\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eDELETE\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eM.RESERVED_ROUTE_PREFIXES\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"s2\"\u003e\u0026#34;/_fn\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"s2\"\u003e\u0026#34;/console\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这让我意识到，现代 API 工具有多少其实就是字符串匹配和允许列表。\u0026ldquo;规范\u0026quot;不是一份规范，它是一个视图。\u003c/p\u003e\n\u003ch3 id=\"可观测性与限制速率并发健康\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e5%8f%af%e8%a7%82%e6%b5%8b%e6%80%a7%e4%b8%8e%e9%99%90%e5%88%b6%e9%80%9f%e7%8e%87%e5%b9%b6%e5%8f%91%e5%81%a5%e5%ba%b7\" class=\"header-mark\" aria-label=\"Header mark for '可观测性与限制：速率、并发、健康'\"\u003e\u003c/a\u003e6.3 可观测性与限制：速率、并发、健康\u003c/h3\u003e\u003cp\u003e让路由变廉价的那个共享内存诀窍，正是让每函数并发限制变廉价的同一个诀窍。\u003ccode\u003ecore/limits.lua\u003c/code\u003e 有 133 行，其中大多是样板，而整个机制是在一个 \u003ccode\u003engx.shared.fn_conc\u003c/code\u003e 区上的 \u003ccode\u003eincr\u003c/code\u003e/\u003ccode\u003edecr\u003c/code\u003e 之舞：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/core/limits.lua:22-39\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-15\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nc\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003etry_acquire\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edict\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003efn_key\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003elimit\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003elimit\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"n\"\u003elimit\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003ekey\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ekey_for\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efn_key\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003ecurrent\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edict\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003eincr\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003ecurrent\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;counter_error:\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e..\u003c/span\u003e \u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003ecurrent\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003elimit\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003edict\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003eincr\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;busy\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e那个函数就是整个\u0026quot;在整个网关范围内，函数 X 的在途调用不超过 N 个\u0026quot;的原语。\u003ccode\u003edict:incr\u003c/code\u003e 在 worker 之间是原子的，因为 \u003ccode\u003engx.shared.DICT\u003c/code\u003e 是底下带一把锁的共享内存。没有 Redis 往返。没有准入控制器 pod。它是 RAM 里的一个计数器，而且它是正确的，因为 nginx 把它正确地交给了我。\u003c/p\u003e\n\u003cp\u003e在限制之上坐着 \u003ccode\u003ecore/watchdog.lua\u003c/code\u003e（299 行），一个用 LuaJIT FFI 写的 Linux \u003ccode\u003einotify\u003c/code\u003e 监视器。它打开 \u003ccode\u003einotify_init1(IN_NONBLOCK | IN_CLOEXEC)\u003c/code\u003e，遍历函数树，给每个子目录加上一个 watch，并在任何文件变化时调度一次去抖动的重载：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/core/watchdog.lua:268-284\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-16\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eok_poll\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003epoll_err\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003engx.timer\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eevery\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epoll_interval\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kr\"\u003efunction\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epremature\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003epremature\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003echanged\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eread_err\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edrain_events\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"c1\"\u003e-- ...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003epending_since\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003engx.now\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e \u003cspan class=\"n\"\u003epending_since\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;=\u003c/span\u003e \u003cspan class=\"n\"\u003edebounce_s\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003epending_since\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eschedule_reload\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e一个 150 毫秒的去抖动，一个共享的重载回调，路由表就地重建。保存一个文件，在我切回浏览器之前路由就已经上线了。\u003c/p\u003e\n\u003ch3 id=\"进程内调度把-ngxtimerat-当-cron-用\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e8%bf%9b%e7%a8%8b%e5%86%85%e8%b0%83%e5%ba%a6%e6%8a%8a-ngxtimerat-%e5%bd%93-cron-%e7%94%a8\" class=\"header-mark\" aria-label=\"Header mark for '进程内调度：把 \u0026lt;code\u0026gt;ngx.timer.at\u0026lt;/code\u0026gt; 当 cron 用'\"\u003e\u003c/a\u003e6.4 进程内调度：把 \u003ccode\u003engx.timer.at\u003c/code\u003e 当 cron 用\u003c/h3\u003e\u003cp\u003e如果我能用 \u003ccode\u003engx.timer\u003c/code\u003e 调度一次重载，我就能调度任何东西。\u003ccode\u003ecore/scheduler.lua\u003c/code\u003e（1,712 行）和 \u003ccode\u003ecore/jobs.lua\u003c/code\u003e（993 行）一起实现了一个类 cron、感知重试、可选持久化的作业运行器，它活在 nginx worker \u003cem\u003e内部\u003c/em\u003e。没有单独的 \u003ccode\u003ecron\u003c/code\u003e 守护进程，没有 Celery，没有 RabbitMQ：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/core/scheduler.lua:1659-1667\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-17\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eok\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003engx.timer\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eevery\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einterval\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kr\"\u003efunction\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epremature\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003epremature\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eok2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eerr2\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epcall\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etick_once\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003eok2\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003engx.log\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003engx.ERR\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;scheduler tick failed: \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eerr2\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e那个单一的 \u003ccode\u003engx.timer.every\u003c/code\u003e 就是 tick 循环。每次 tick 它遍历活跃的调度，把 cron 表达式与挂钟时间对照，并通过 \u003ccode\u003engx.timer.at(0, ...)\u003c/code\u003e 把运行入队。带指数退避的重试从同一个原语里掉出来。持久化是每 15 秒写到磁盘的一个 JSON blob。对一个小想法而言这是相当多的代码：\u003cem\u003e我已经拥有的那个事件循环，对一个单节点 FaaS 来说就足以充当调度器了。\u003c/em\u003e\u003c/p\u003e\n\u003ch4 id=\"一个具体例子一个每小时运行的-telegram-ai-摘要\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e4%b8%80%e4%b8%aa%e5%85%b7%e4%bd%93%e4%be%8b%e5%ad%90%e4%b8%80%e4%b8%aa%e6%af%8f%e5%b0%8f%e6%97%b6%e8%bf%90%e8%a1%8c%e7%9a%84-telegram-ai-%e6%91%98%e8%a6%81\" class=\"header-mark\" aria-label=\"Header mark for '一个具体例子：一个每小时运行的 Telegram AI 摘要'\"\u003e\u003c/a\u003e6.4.1 一个具体例子：一个每小时运行的 Telegram AI 摘要\u003c/h4\u003e\u003cp\u003e调度器在你看到一个使用它的函数之前都是抽象的。仓库里最干净的一个是 \u003ca href=\"https://github.com/misaelzapata/fastfn/tree/main/examples/functions/node/telegram-ai-digest\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003eexamples/functions/node/telegram-ai-digest\u003c/code\u003e\u003c/a\u003e——一个 Node 函数，它从一个 Telegram 群拉取消息，用 OpenAI 把它们总结，并把摘要发回一个聊天。对本节而言让它有趣的是其配置里的 \u003ccode\u003eschedule\u003c/code\u003e 块：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eexamples/functions/node/telegram-ai-digest/fn.config.json\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-18\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;timeout_ms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e30000\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;invoke\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;summary\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Telegram AI digest: fetches group messages, summarizes with OpenAI, sends digest\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;methods\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;GET\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;content_type\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;application/json\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;schedule\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;enabled\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;every_seconds\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e3600\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;method\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;GET\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e那就是整个\u0026quot;把这个变成一个 cron job\u0026quot;的接口：函数自己的配置里、紧挨着处理器的一段三行 \u003ccode\u003eschedule\u003c/code\u003e 节。没有外部 crontab，没有单独的注册步骤，没有 YAML 流水线。当 fastfn 启动时，调度器读取每个函数的 \u003ccode\u003efn.config.json\u003c/code\u003e，看到这个有一个 \u003ccode\u003eschedule.enabled = true\u003c/code\u003e，就以 \u003ccode\u003eevery_seconds = 3600\u003c/code\u003e 把它加入 tick 循环。每隔一小时，调度器向函数发出一个内部 \u003ccode\u003eGET\u003c/code\u003e 请求，就好像一个客户端调用了它一样——同样的路由，同样的处理器，同样的日志——而函数完成它的工作。\u003c/p\u003e\n\u003cp\u003e同样的模式出现在那些兄弟示例里：\u003ca href=\"https://github.com/misaelzapata/fastfn/tree/main/examples/functions/node/telegram-ai-reply\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003etelegram-ai-reply\u003c/code\u003e\u003c/a\u003e 是一个 webhook（\u003ccode\u003ePOST\u003c/code\u003e 处理器，没有 schedule——Telegram 在一条消息到达时调用它），\u003ca href=\"https://github.com/misaelzapata/fastfn/tree/main/examples/functions/node/telegram-send\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003etelegram-send\u003c/code\u003e\u003c/a\u003e 是一个库式函数，你可以从别的处理器调用它来发送一条消息（默认 \u003ccode\u003edry_run\u003c/code\u003e，这是我在第二个本该静默的 bot 之后学会添加的那种安全细节）。三个函数，三种生命周期——webhook、库调用、cron——而它们全都不过是 \u003ccode\u003efunctions/\u003c/code\u003e 里的文件。唯一让这个摘要\u0026quot;成为一个 cron\u0026quot;的，就是那三行配置。\u003c/p\u003e\n\u003cp\u003e我喜欢这个形态，因为函数的\u0026quot;部署模型\u0026quot;和它的\u0026quot;调用模型\u0026quot;在同一个地方。如果一个队友想知道\u0026quot;这个怎么运行的？\u0026quot;，他打开 \u003ccode\u003efn.config.json\u003c/code\u003e，看到 \u003ccode\u003eschedule.every_seconds = 3600\u003c/code\u003e，就知道了。不用去一台他没有 ssh 权限的服务器上的某个 cron 文件里翻找。\u003c/p\u003e\n\u003ch4 id=\"配置与令牌fnenvjson-与-is_secret-标志\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e9%85%8d%e7%bd%ae%e4%b8%8e%e4%bb%a4%e7%89%8cfnenvjson-%e4%b8%8e-is_secret-%e6%a0%87%e5%bf%97\" class=\"header-mark\" aria-label=\"Header mark for '配置与令牌：\u0026lt;code\u0026gt;fn.env.json\u0026lt;/code\u0026gt; 与 \u0026lt;code\u0026gt;is_secret\u0026lt;/code\u0026gt; 标志'\"\u003e\u003c/a\u003e6.4.2 配置与令牌：\u003ccode\u003efn.env.json\u003c/code\u003e 与 \u003ccode\u003eis_secret\u003c/code\u003e 标志\u003c/h4\u003e\u003cp\u003e那个被调度的摘要函数没有两个密钥和一个标识符就没用：一个 Telegram bot 令牌、一个 OpenAI API 密钥，以及要发往的聊天 ID。这是每个 FaaS 教程里那个你要么对环境变量含糊其辞、要么贴上一段关于 Vault 的文字的节点。fastfn 两者都不做。它把配置放在函数旁边，放在一个叫 \u003ccode\u003efn.env.json\u003c/code\u003e 的同级文件里，带着少量结构，让每个值的\u003cem\u003e含义\u003c/em\u003e变得明确：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eexamples/functions/node/telegram-ai-digest/fn.env.json\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-19\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;TELEGRAM_BOT_TOKEN\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;value\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026lt;set-me\u0026gt;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;is_secret\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;TELEGRAM_CHAT_ID\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;value\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026lt;set-me\u0026gt;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;is_secret\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;OPENAI_API_KEY\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;value\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026lt;set-me\u0026gt;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;is_secret\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e有两件事要注意。第一，每个键都是一个对象，而不是一个裸字符串——值住在 \u003ccode\u003e.value\u003c/code\u003e 里，元数据住在它旁边。第二，那个 \u003ccode\u003eis_secret\u003c/code\u003e 布尔值是承重的。运行时用它来决定一个值是否在日志和管理仪表盘里被遮蔽，是否能通过一个 \u003ccode\u003e_fn/ui_state\u003c/code\u003e 端点被回显，以及仪表盘的\u0026quot;view\u0026quot;按钮是否被允许以明文揭示它。\u003ccode\u003eTELEGRAM_CHAT_ID\u003c/code\u003e 不是密钥——它只是一个数字，而当你在排查\u0026quot;为什么我的摘要没出现\u0026quot;时，你会想在 UI 里看到它。\u003ccode\u003eTELEGRAM_BOT_TOKEN\u003c/code\u003e 是密钥——而如果你不小心把它泄露到日志里，Telegram 的反应是让令牌失效，于是调度器会静默地停止工作，直到你注意到。\u003ccode\u003eis_secret\u003c/code\u003e 标志就是这两种结果之间的全部差别。\u003c/p\u003e\n\u003cp\u003e值 \u003ccode\u003e\u0026quot;\u0026lt;set-me\u0026gt;\u0026quot;\u003c/code\u003e 也不是一个 bug——它是仓库刻意使用的一种模式。\u003ccode\u003ecore.js\u003c/code\u003e 里的处理器把 \u003ccode\u003e\u0026lt;set-me\u0026gt;\u003c/code\u003e、\u003ccode\u003eset-me\u003c/code\u003e、\u003ccode\u003echangeme\u003c/code\u003e、\u003ccode\u003e\u0026lt;changeme\u0026gt;\u003c/code\u003e 和 \u003ccode\u003ereplace-me\u003c/code\u003e 都当作\u0026quot;未设置\u0026quot;的哨兵：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eexamples/functions/node/telegram-ai-digest/core.js:1-8\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-20\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunction\u003c/span\u003e \u003cspan class=\"nx\"\u003eisUnsetConfigValue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003evalue\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"kc\"\u003eundefined\u003c/span\u003e \u003cspan class=\"o\"\u003e||\u003c/span\u003e \u003cspan class=\"nx\"\u003evalue\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003eString\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nx\"\u003etrim\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003el\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003etoLowerCase\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003el\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026lt;set-me\u0026gt;\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e||\u003c/span\u003e \u003cspan class=\"nx\"\u003el\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;set-me\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e||\u003c/span\u003e \u003cspan class=\"nx\"\u003el\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;changeme\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"o\"\u003e||\u003c/span\u003e \u003cspan class=\"nx\"\u003el\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026lt;changeme\u0026gt;\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e||\u003c/span\u003e \u003cspan class=\"nx\"\u003el\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;replace-me\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这意味着你可以把带 \u003ccode\u003e\u0026quot;\u0026lt;set-me\u0026gt;\u0026quot;\u003c/code\u003e 占位符的 \u003ccode\u003efn.env.json\u003c/code\u003e 检入 git，而函数会干净地失败，而不是假装拿着一个空字符串在运行。真实的部署会把那些占位符（在仪表盘里、通过 API，或在一台受控主机上编辑文件）替换为实际的值；\u003ccode\u003eis_secret: true\u003c/code\u003e 的条目在保存时立即被遮蔽。\u003c/p\u003e\n\u003cp\u003e在处理器内部，运行时把这些值通过 \u003ccode\u003eevent.env\u003c/code\u003e 交付，与请求作用域的 \u003ccode\u003eevent.method\u003c/code\u003e、\u003ccode\u003eevent.query\u003c/code\u003e 等等并列。摘要的 \u003ccode\u003ecore.js\u003c/code\u003e 用一个小小的回退来读取它们，优先用函数本地的 env，回退到守护进程在启动时列入允许列表的环境 \u003ccode\u003eprocess.env\u003c/code\u003e：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eexamples/functions/node/telegram-ai-digest/core.js:86-88\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-21\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003ebotToken\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003echooseConfigValue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eenv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eTELEGRAM_BOT_TOKEN\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eprocess\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eenv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eTELEGRAM_BOT_TOKEN\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003echatId\u003c/span\u003e   \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003echooseConfigValue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eenv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eTELEGRAM_CHAT_ID\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e   \u003cspan class=\"nx\"\u003eprocess\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eenv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eTELEGRAM_CHAT_ID\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eapiKey\u003c/span\u003e   \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003echooseConfigValue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eenv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eOPENAI_API_KEY\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e     \u003cspan class=\"nx\"\u003eprocess\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eenv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eOPENAI_API_KEY\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这是我一再回归的那种配置形态：一个紧挨处理器的 JSON 文件，每个密钥一个值，一个显式的 \u003ccode\u003eis_secret\u003c/code\u003e 标志驱动着每一个下游界面的遮蔽，以及一个语言层面的 \u003ccode\u003eevent.env\u003c/code\u003e，它让处理器对那些值的依赖变得可以轻松地 grep 出来。它，再说一次，不是一个新想法——它基本上跟 Heroku 的 config vars 或一对 Kubernetes 的 \u003ccode\u003eSecret\u003c/code\u003e/\u003ccode\u003eConfigMap\u003c/code\u003e 是同一个形态。但它住在使用它的代码的同一个文件夹里，它带着安全的占位符可被版本控制，而且它不需要一个外部的密钥管理器就能上手。\u003c/p\u003e\n\u003cp\u003e把调度器、配置和处理器放进一个目录，Telegram 摘要就从\u0026quot;某处一个模糊的 cron job\u0026quot;变成了\u0026quot;我能在一分钟内读完、并交给一个队友的四个文件\u0026rdquo;。\u003c/p\u003e\n\u003ch3 id=\"仪表盘是-lua\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e4%bb%aa%e8%a1%a8%e7%9b%98%e6%98%af-lua\" class=\"header-mark\" aria-label=\"Header mark for '仪表盘是 Lua'\"\u003e\u003c/a\u003e6.5 仪表盘是 Lua\u003c/h3\u003e\u003cp\u003eLua 最让我吃惊的地方是管理控制台。我并没有打算用 Lua 写一个 Web 应用。我以为我最终会接进一个小 SPA，多半是 Vue 或 Svelte，因为\u0026quot;大家都这么干\u0026rdquo;。然后我用一个下午写了 \u003ccode\u003econsole/login_endpoint.lua\u003c/code\u003e，并意识到我不需要那个 SPA。\u003c/p\u003e\n\u003cp\u003e登录端点有 95 行，做了一个登录端点该做的一切：方法检查、在 \u003ccode\u003engx.shared.fn_cache\u003c/code\u003e 里限流、恒定时间比较、PBKDF2 密码验证、会话 cookie。这是限流的那一片：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/console/login_endpoint.lua:38-46\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-22\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eclient_ip\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003engx.var\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eremote_addr\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;unknown\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003erate_store\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003engx.shared\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efn_cache\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003erate_store\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003efail_count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003erate_store\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003elogin_rate_key\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eclient_ip\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eLOGIN_MAX_ATTEMPTS\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e     \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efail_count\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;number\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e     \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003efail_count\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;=\u003c/span\u003e \u003cspan class=\"n\"\u003eLOGIN_MAX_ATTEMPTS\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eguard.write_json\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e429\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003eerror\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;too many login attempts, try again later\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e五秒钟的 shared-dict 算术，我就有了一个能用的锁定。\u003ccode\u003econsole/auth.lua\u003c/code\u003e（484 行）处理会话 cookie、最少 100,000 次迭代的 PBKDF2、用于管理员密码的可选从文件读取密钥，以及一个 12 小时的 TTL。\u003ccode\u003econsole/guard.lua\u003c/code\u003e（387 行）是每个仪表盘端点都先调用、用以强制认证、请求体限制、CSRF 和写入门控的中间件。\u003ccode\u003econsole/data.lua\u003c/code\u003e 是大块头——约 2,623 行——它是仪表盘端点委托给它的后备\u0026quot;服务层\u0026quot;：列出函数、读取一个函数的代码、设置代码、列出版本、读取日志、读取调度、聚合仪表盘指标。\u003c/p\u003e\n\u003cp\u003e端点本身则很小。仪表盘指标端点有 21 行：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/console/dashboard_endpoint.lua:9-21\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-23\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003emethod\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003engx.req\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_method\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003emethod\u003c/span\u003e \u003cspan class=\"o\"\u003e~=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;GET\u0026#34;\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eguard.write_json\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e405\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003eerror\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;method not allowed\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e-- Aggregate metrics from shared dicts or external systems\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003emetrics\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003econsole.get_dashboard_metrics\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eguard.write_json\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e200\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003emetrics\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003einvocations\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eerrors\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003elatency\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e围绕着它活着的是 \u003ccode\u003efunctions_endpoint.lua\u003c/code\u003e（34 行）、\u003ccode\u003elogout_endpoint.lua\u003c/code\u003e（15 行）、\u003ccode\u003eui_state_endpoint.lua\u003c/code\u003e（50 行）、\u003ccode\u003esecrets_endpoint.lua\u003c/code\u003e（70 行）、\u003ccode\u003epacks.lua\u003c/code\u003e（77 行）、\u003ccode\u003eui.lua\u003c/code\u003e（66 行），以及更胖一点的 \u003ccode\u003einvoke_endpoint.lua\u003c/code\u003e（526 行），它驱动着\u0026quot;从浏览器调用这个函数\u0026quot;的控制台。没有单独的管理 Web 应用。没有 \u003ccode\u003enpm run dev\u003c/code\u003e。没有第二套构建系统。有的是 Lua，从服务网关流量的同一个 nginx worker 里渲染出 HTML 和 JSON。\u003c/p\u003e\n\u003ch3 id=\"lua-作为运行时以及一个非常微小的-http-客户端\" class=\"headerLink\"\u003e\n    \u003ca href=\"#lua-%e4%bd%9c%e4%b8%ba%e8%bf%90%e8%a1%8c%e6%97%b6%e4%bb%a5%e5%8f%8a%e4%b8%80%e4%b8%aa%e9%9d%9e%e5%b8%b8%e5%be%ae%e5%b0%8f%e7%9a%84-http-%e5%ae%a2%e6%88%b7%e7%ab%af\" class=\"header-mark\" aria-label=\"Header mark for 'Lua 作为运行时，以及一个非常微小的 HTTP 客户端'\"\u003e\u003c/a\u003e6.6 Lua 作为运行时，以及一个非常微小的 HTTP 客户端\u003c/h3\u003e\u003cp\u003e我还添加了 \u003ccode\u003ecore/lua_runtime.lua\u003c/code\u003e（399 行），它让函数目录里的 Lua 文件\u003cem\u003e成为\u003c/em\u003e处理器——和 Python 与 Node 一样的 \u003ccode\u003ehandler(event) -\u0026gt; response\u003c/code\u003e 契约，只是没有跨进程的跳跃，因为处理器跑在同一个 worker 里。我没有为这条路径发布过基准，所以我不会给它安上一个毫秒数。对于需要向外伸手的处理器，\u003ccode\u003ecore/http_client.lua\u003c/code\u003e（356 行）用 URL 解析、keepalive 和超时封装了 \u003ccode\u003engx.socket.tcp\u003c/code\u003e，于是处理器调用上游 API 时不用把 \u003ccode\u003eluasocket\u003c/code\u003e 拉进镜像。\u003ccode\u003ecore/home.lua\u003c/code\u003e（234 行）在 \u003ccode\u003e/\u003c/code\u003e 提供默认落地页，而 \u003ccode\u003ehttp/function_code.lua\u003c/code\u003e / \u003ccode\u003ehttp/function_file_content.lua\u003c/code\u003e（50 和 76 行）在仪表盘编辑器和磁盘之间来回传送函数源码。\u003c/p\u003e\n\u003ch3 id=\"诚实的取舍\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e8%af%9a%e5%ae%9e%e7%9a%84%e5%8f%96%e8%88%8d\" class=\"header-mark\" aria-label=\"Header mark for '诚实的取舍'\"\u003e\u003c/a\u003e6.7 诚实的取舍\u003c/h3\u003e\u003cp\u003e那么——Lua 在这里是不是了不起？是的，有凭有据。凭据是：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e冷启动本质上是免费的。\u003c/strong\u003e OpenResty 启动一次。我的 Lua 代码是一组在 worker init 时加载的模块。\u003ccode\u003egateway.lua\u003c/code\u003e 文件有 1,341 行；加载它对请求延迟几乎不增加什么，因为到第一个请求到达时请求阶段的钩子已经被 JIT 编译好了。我这里没有一个干净的\u0026quot;Lua 模块加载时间\u0026quot;数字可引用，所以我不会安上一个毫秒数字——但仓库里发布的每一个热路径 \u003ccode\u003ep50\u003c/code\u003e，跨各种负载，端到端穿过这个 Lua 网关都是个位数毫秒，而那是唯一对一个用户重要的数字。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e共享内存几乎免费。\u003c/strong\u003e \u003ccode\u003engx.shared.DICT\u003c/code\u003e 是 nginx 里的一张哈希表，由一把自旋锁保护。我不用为持有计数器付一个 Redis 容器的钱。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e每个关切一个模块，都很小。\u003c/strong\u003e 路由发现核心除外，大多数控制平面模块都在 100–500 行的区间。\u003ccode\u003elimits.lua\u003c/code\u003e 是 133 行。\u003ccode\u003ewatchdog.lua\u003c/code\u003e 是 299。\u003ccode\u003elogin_endpoint.lua\u003c/code\u003e 是 95。这些是阅读距离内的数字。它们当中任何一个我都能装进脑子里。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e还有代价，因为代价是有的：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eLua 5.1 的数值怪癖。\u003c/strong\u003e OpenResty 的 LuaJIT 是带一些 5.2/5.3 扩展的 Lua-5.1-ish。没有原生的 \u003ccode\u003estring.pack\u003c/code\u003e，这就是第四章里那个 \u003ccode\u003epack_u32\u003c/code\u003e 存在的原因。在写限流器时，整数对双精度的混淆咬过我两次；\u003ccode\u003engx.shared.DICT:incr\u003c/code\u003e 返回的数字是双精度的，而我不得不在那些我打算拿来和 \u003ccode\u003etonumber(env)\u003c/code\u003e 值比较的计数器上小心使用 \u003ccode\u003emath.floor\u003c/code\u003e。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e到处都要有 \u003ccode\u003epcall\u003c/code\u003e 纪律。\u003c/strong\u003e 一个 \u003ccode\u003engx.timer\u003c/code\u003e 回调里未捕获的错误会静默地杀死定时器并记到 \u003ccode\u003eerror.log\u003c/code\u003e。我写的每一个定时器都裹在 \u003ccode\u003epcall\u003c/code\u003e 里，而调度器 tick、watchdog 回调和持久化写入器都遵循同样的模式。跳过那份纪律，你就会得到一个在凌晨 3 点神秘地停止 tick 的调度器。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e一切都是字符串，直到它不是。\u003c/strong\u003e Shared dict 存储字符串和数字，而不是表，这意味着在边界处做 JSON 编码/解码。我依靠 \u003ccode\u003ecjson.safe\u003c/code\u003e，让坏输入返回 \u003ccode\u003enil, err\u003c/code\u003e 而不是抛出，而当一个表有数值上的空洞时，我仍然偶尔会得到 \u003ccode\u003ecannot encode sparse array\u003c/code\u003e。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e这些都不是致命问题。它们是选择一门带快速 JIT、内嵌在 Web 服务器里的小语言所要付的价钱，而这笔交易一直在赢。整个控制平面——路由、OpenAPI、限制、watchdog、调度器、控制台、认证——是我一个下午能读完的一小撮文件。大多数关切各自是 20 到 40 KB 的可读 Lua。整个 \u003ccode\u003ecore/\u003c/code\u003e 目录在磁盘上约 304 KB；整个 \u003ccode\u003econsole/\u003c/code\u003e 目录约 156 KB。这远远少于一个单一的现代 SPA 在抵达第一条路由之前就发出的 JavaScript 的量。而且那个 Lua \u003cem\u003e是产品\u003c/em\u003e，不是脚手架。\u003c/p\u003e\n\u003cp\u003e这把我带到了我试图从这堆东西里抽出教训的部分。\u003c/p\u003e\n\u003ch2 id=\"第七章我只料到了一半的教训\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e4%b8%83%e7%ab%a0%e6%88%91%e5%8f%aa%e6%96%99%e5%88%b0%e4%ba%86%e4%b8%80%e5%8d%8a%e7%9a%84%e6%95%99%e8%ae%ad\" class=\"header-mark\" aria-label=\"Header mark for '第七章：我只料到了一半的教训'\"\u003e\u003c/a\u003e7 第七章：我只料到了一半的教训\u003c/h2\u003e\u003ch3 id=\"永远把主机环境列入允许列表\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e6%b0%b8%e8%bf%9c%e6%8a%8a%e4%b8%bb%e6%9c%ba%e7%8e%af%e5%a2%83%e5%88%97%e5%85%a5%e5%85%81%e8%ae%b8%e5%88%97%e8%a1%a8\" class=\"header-mark\" aria-label=\"Header mark for '永远把主机环境列入允许列表'\"\u003e\u003c/a\u003e7.1 永远把主机环境列入允许列表\u003c/h3\u003e\u003cp\u003e每个 FaaS 都有同样的诱惑：把主机的环境变量传进处理器。这很方便。这也是密钥泄露的方式。如果你的 CI/CD 流水线为部署脚本导出了 \u003ccode\u003eAWS_SECRET_ACCESS_KEY\u003c/code\u003e，而你的 Python 守护进程把那个 env 继承进了每一个处理器，恭喜你，你刚刚把一个云端 root 凭证交给了机器上的每一个处理器。\u003c/p\u003e\n\u003cp\u003e修复是一个允许列表，而不是一个阻止列表。我是用那种无聊的方式发现这一点的：读别人的事故报告，然后决定我不想写我自己的。守护进程附带一个保守的、处理器能看见的环境 env 键列表（\u003ccode\u003esrv/fn/runtimes/python-daemon.py:59-103\u003c/code\u003e）：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-24\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e_ALLOWED_WORKER_ENV_KEYS\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;PATH\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;HOME\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;USER\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;LOGNAME\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# ... language runtimes\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;PYTHONHOME\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;PYTHONPATH\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;GOPATH\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;GOCACHE\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;CARGO_HOME\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;RUSTUP_HOME\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# ... CA bundles\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;SSL_CERT_FILE\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;SSL_CERT_DIR\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;REQUESTS_CA_BUNDLE\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# ... locale\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;LANG\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;TZ\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e_ALLOWED_WORKER_ENV_PREFIXES\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;LC_\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e它上面的注释把那句悄悄话说出了声：\u003cem\u003e\u0026ldquo;User-defined secrets should come from fn.env.json or request-scoped event.env, not ambient host env.\u0026rdquo;\u003c/em\u003e 允许列表就是那个强制执行。一切不在那个集合里的、或者以 \u003ccode\u003eLC_\u003c/code\u003e 开头的，都在处理器运行之前被清除。这是安全卫生，不是安全表演。\u003c/p\u003e\n\u003ch3 id=\"配置优先级flag--env--config--默认\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e9%85%8d%e7%bd%ae%e4%bc%98%e5%85%88%e7%ba%a7flag--env--config--%e9%bb%98%e8%ae%a4\" class=\"header-mark\" aria-label=\"Header mark for '配置优先级：flag \u0026amp;gt; env \u0026amp;gt; config \u0026amp;gt; 默认'\"\u003e\u003c/a\u003e7.2 配置优先级：flag \u0026gt; env \u0026gt; config \u0026gt; 默认\u003c/h3\u003e\u003cp\u003e这是那种听起来显而易见、却在我用过的一半 CLI 里都搞错了的规则。我自己的 CLI 把它搞对了，是因为我第一次搞错了。来自 \u003ccode\u003ecli/cmd/run.go:64-73\u003c/code\u003e：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-25\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Resolve hot-reload: flag \u0026gt; env \u0026gt; config \u0026gt; default (true)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003ehotReload\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003erunHotReload\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e// Explicit --hot-reload flag always wins\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"nx\"\u003ehotReload\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003eenvVal\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eGetenv\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;FN_HOT_RELOAD\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e \u003cspan class=\"nx\"\u003eenvVal\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ehotReload\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eenvVal\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;0\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan class=\"nx\"\u003eenvVal\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;false\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan class=\"nx\"\u003eenvVal\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;off\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan class=\"nx\"\u003eenvVal\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;no\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003eviper\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eIsSet\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;hot-reload\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ehotReload\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eviper\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eGetBool\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;hot-reload\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e注意第 64 行的注释。它不是为我而写的——它是为下一个试图通过让配置文件压过 env 来\u0026quot;修复\u0026quot;这个的人而写的。每一个曾经把这个顺序颠倒过来的配置系统，都引发过一次生产事故：有人在 CI 里设置了一个环境变量，然后纳闷为什么容器一直从一个检入的文件里读取一个陈旧的值。\u003c/p\u003e\n\u003ch3 id=\"一切都住在文件系统里\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e4%b8%80%e5%88%87%e9%83%bd%e4%bd%8f%e5%9c%a8%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e9%87%8c\" class=\"header-mark\" aria-label=\"Header mark for '一切都住在文件系统里'\"\u003e\u003c/a\u003e7.3 一切都住在文件系统里\u003c/h3\u003e\u003cp\u003e没有数据库。没有 registry。没有 etcd。路由是文件。配置是 \u003ccode\u003efastfn.json\u003c/code\u003e。环境是 \u003ccode\u003efn.env.json\u003c/code\u003e。函数本地的覆盖是 \u003ccode\u003efn.config.json\u003c/code\u003e。依赖状态是 \u003ccode\u003e.fastfn-deps-state.json\u003c/code\u003e。\u003c/p\u003e\n\u003cp\u003e这个决定是出于懒惰做出的，结果却成了一个特性。一个文件系统优先的 FaaS 可以用我已经拥有的工具来做版本控制、rsync、快照和 diff。你不需要一个\u0026quot;平台\u0026quot;来检视它。\u003ccode\u003els\u003c/code\u003e 就是管理 UI。\u003ccode\u003egrep\u003c/code\u003e 就是调试器。这跟 \u003ccode\u003einetd\u003c/code\u003e 在 1986 年学到的是同一个教训：有时候控制平面应该是一个文本文件。\u003c/p\u003e\n\u003ch2 id=\"第八章故事的寓意第一部分\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%ac%ac%e5%85%ab%e7%ab%a0%e6%95%85%e4%ba%8b%e7%9a%84%e5%af%93%e6%84%8f%e7%ac%ac%e4%b8%80%e9%83%a8%e5%88%86\" class=\"header-mark\" aria-label=\"Header mark for '第八章：故事的寓意（第一部分）'\"\u003e\u003c/a\u003e8 第八章：故事的寓意（第一部分）\u003c/h2\u003e\u003cp\u003e我着手构建\u0026quot;扔进一个 Python 文件，得到一个 URL\u0026quot;。我最终是有意地重建了 FastCGI，用一个 Lua 网关因为 OpenResty 真真切切是这份工作的正确工具，用一个 JSON 线缆协议因为对一个 2026 年的本地优先系统而言，二进制记录格式是个糟糕的取舍，用一个多语言运行时契约因为 \u003ccode\u003edef handler(event): return {...}\u003c/code\u003e 是正确的抽象，而且自 Lambda 发布以来一直是。\u003c/p\u003e\n\u003cp\u003e我没料到的是，有多少设计决定早已被历史替我预先做好了。杀死 CGI 的每请求一 fork 的代价，正是今天让每请求一容器的 serverless 感觉慢的同一个代价。FastCGI 的持久化池 + 帧封装 socket，正是现代 serverless 平台在内部收敛到的同一个形态。Cloudflare Workers 骨子里就是用一个 v8 isolate 取代了进程的 FastCGI。AWS Lambda 的热启动池是前面摆了一个 API Gateway 的 FastCGI。\u003ccode\u003efastfn\u003c/code\u003e——至少在我刚刚描述的形态里——就是带 JSON 帧和一个 OpenResty 网关的 FastCGI。\u003c/p\u003e\n\u003cp\u003e我会带着我确实拥有的那个唯一的遗憾来收尾第一部分：我本可以早一年就从 Lua 开始。每一次我写下\u0026quot;只是一个 Go 写的小网关\u0026quot;，我都是在写一个 nginx 已经做了的东西的更差版本，带着更高的冷启动、更多的代码和更少的可观测性。教训——而且它不是一个新教训，这正是难堪之处——是当你问题的形态匹配上一块现存的、广受喜爱的基础设施时，你就该直接用那块基础设施。FastCGI 在 1996 年告诉了我数据平面该是什么样。OpenResty 在 2010 年告诉了我控制平面该是什么样。Lambda 在 2014 年告诉了我处理器契约该是什么样。\u003ccode\u003efastfn\u003c/code\u003e 的第一部分所做的全部，不过是倾听。\u003c/p\u003e\n\u003cp\u003e这个故事有第二条支线，本文刻意不去覆盖：当一个函数不是正确的形态时会发生什么——当你需要一个长寿的服务，比如一个数据库、一个 Flask 应用，或者一个住在同一个网关里的 Next.js 前端。那项工作目前在一个 feature 分支上进行中、尚未发布，所以我把它留给第二部分，在那里 Firecracker microVM 隔离、\u003ccode\u003eapps\u003c/code\u003e / \u003ccode\u003eworkloads\u003c/code\u003e 配置，以及 vsock 对等网络都将得到它们所需要的篇幅。函数是第一部分。服务是第二部分。\u003c/p\u003e\n\u003cdiv class=\"details admonition tip open\"\u003e\n    \u003cdiv class=\"details-summary admonition-title\"\u003e\n        \u003cspan class=\"icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 352 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M96.06 454.35c.01 6.29 1.87 12.45 5.36 17.69l17.09 25.69a31.99 31.99 0 0 0 26.64 14.28h61.71a31.99 31.99 0 0 0 26.64-14.28l17.09-25.69a31.989 31.989 0 0 0 5.36-17.69l.04-38.35H96.01l.05 38.35zM0 176c0 44.37 16.45 84.85 43.56 115.78 16.52 18.85 42.36 58.23 52.21 91.45.04.26.07.52.11.78h160.24c.04-.26.07-.51.11-.78 9.85-33.22 35.69-72.6 52.21-91.45C335.55 260.85 352 220.37 352 176 352 78.61 272.91-.3 175.45 0 73.44.31 0 82.97 0 176zm176-80c-44.11 0-80 35.89-80 80 0 8.84-7.16 16-16 16s-16-7.16-16-16c0-61.76 50.24-112 112-112 8.84 0 16 7.16 16 16s-7.16 16-16 16z\"/\u003e\u003c/svg\u003e\u003c/span\u003e继续阅读\u003cspan class=\"details-icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"details-content\"\u003e\n        \u003cdiv class=\"admonition-content\"\u003e第二部分——\u003ca href=\"https://misael.org/zh-cn/fastfn-services-when-functions-arent-enough/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003cstrong\u003efastfn 第二部分：当一个函数还不够时\u003c/strong\u003e\u003c/a\u003e——从本文结束之处接续：服务、workloads、Firecracker microVM，以及那个让一个函数能在 \u003ccode\u003epostgres.internal\u003c/code\u003e 触及一个 Postgres VM、却从不在主机上暴露端口的 vsock 网络。\u003c/div\u003e\u003c/div\u003e\u003c/div\u003e\n",
        "language": "zh-cn"
    },
    {
        "title" : "教技嘉键盘讲 Linux",
        "date_published" : "2026-05-26T00:00:00Z",
        "date_modified" : "2026-05-26T00:00:00Z",
        "id" : "https://misael.org/zh-cn/teaching-a-gigabyte-keyboard-to-speak-linux/",
        "url" : "https://misael.org/zh-cn/teaching-a-gigabyte-keyboard-to-speak-linux/",
        "summary": "一块技嘉 Aero RGB 键盘、/dev/hidraw、一个非异或校验和，以及一条把\u0026rsquo;每次都要 sudo\u0026rsquo;变成\u0026rsquo;它就是能用\u0026rsquo;的 udev 规则。",
        "content_html" : "\u003ch2 id=\"一块漆黑键盘的隐痛\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e4%b8%80%e5%9d%97%e6%bc%86%e9%bb%91%e9%94%ae%e7%9b%98%e7%9a%84%e9%9a%90%e7%97%9b\" class=\"header-mark\" aria-label=\"Header mark for '一块漆黑键盘的隐痛'\"\u003e\u003c/a\u003e1 一块漆黑键盘的隐痛\u003c/h2\u003e\u003cp\u003e我买了一台技嘉 Aero X16，本打算把它打造成我的\u0026quot;日常主力机，顺便还能跑构建而不哭鼻子\u0026quot;。在 Windows 上，开机后发生的第一件事颇具戏剧性：键盘亮起一波青色光浪，像一支小小的 LED 行进乐队，技嘉 Control Center 还兴高采烈地坚持要我在十几种 RGB 效果里挑一个。它感觉像是一块想跟我说话的键盘。\u003c/p\u003e\n\u003cp\u003e然后我装上了 Linux，键盘就闭嘴了。没有一丝闪烁。没有一点微光。什么都没有。\u003c/p\u003e\n\u003cp\u003e平心而论，机器还能打字。按键还能敲出字符。但对于那些为 RGB 付了钱、然后花一整晚盯着昏暗塑料的开发者来说，存在一种特有的隐痛。这隐痛顶出一个念头：如果硬件还在工作，而唯一改变的只是操作系统，那么在我的用户会话和键盘固件之间的某个地方，有一场本应进行却没有进行的对话——而没有进行的对话，原则上是可以被开启的。我看了看现有的工具：有一个很棒的 Windows 项目叫 \u003ca href=\"https://gitlab.com/wtwrp/aeroctl\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003ewtwrp/aeroctl\u003c/code\u003e\u003c/a\u003e，它能做到你想要的一切，前提是你也愿意一起跑 Windows。我不愿意。于是我做了该做的事：打开一个终端，对着 \u003ccode\u003elsusb\u003c/code\u003e 嘟囔了一句不可印的脏话，然后开始捅鼓。\u003c/p\u003e\n\u003cp\u003e几个晚上之后，我做出了一个小小的 Python 项目，取名 \u003ca href=\"https://github.com/misaelzapata/aeroctl-linux-indicator\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003eaeroctl-linux-indicator\u003c/code\u003e\u003c/a\u003e。整个包的源码是 593 行（\u003ccode\u003edevice.py\u003c/code\u003e + \u003ccode\u003ecli.py\u003c/code\u003e + \u003ccode\u003etray.py\u003c/code\u003e），测试覆盖率约 80%，它提供一个 CLI 和一个托盘指示器，而它存在的全部目的，就是说服一颗深埋在技嘉机身里的 ITE-829X 控制器：Linux 确实是一个值得拥有彩色光的合法操作系统。\u003c/p\u003e\n\u003cp\u003e这是我如何走到那一步的故事。它也是关于一个提交——\u003ccode\u003e68dbdae\u003c/code\u003e，\u0026ldquo;chore: remove unsupported RGB effects\u0026rdquo;——为何是整个仓库里最悲伤的一行的故事。\u003c/p\u003e\n\u003cp\u003e不过，那个提交是线头的末端。它的开端是一个更天真的问题——一个我在被别人答案折腾了几个晚上\u003cem\u003e之后\u003c/em\u003e才问出口的问题：USB 总线上到底插着什么我还没跟它说过话的东西，而我又怎么会知道？\u003c/p\u003e\n\u003ch2 id=\"前人之作四个项目没有一个是为这台笔记本\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e5%89%8d%e4%ba%ba%e4%b9%8b%e4%bd%9c%e5%9b%9b%e4%b8%aa%e9%a1%b9%e7%9b%ae%e6%b2%a1%e6%9c%89%e4%b8%80%e4%b8%aa%e6%98%af%e4%b8%ba%e8%bf%99%e5%8f%b0%e7%ac%94%e8%ae%b0%e6%9c%ac\" class=\"header-mark\" aria-label=\"Header mark for '前人之作：四个项目，没有一个是为这台笔记本'\"\u003e\u003c/a\u003e2 前人之作：四个项目，没有一个是为这台笔记本\u003c/h2\u003e\u003cp\u003e在写下一行 Python 之前，我做了每个工程师都该做的事，去寻找已有的工作。令人沮丧又令人鼓舞的消息是，关于\u0026quot;从 Linux 控制技嘉笔记本 RGB 键盘\u0026quot;已经有相当多的前人之作。令人沮丧的部分是，没有一个能在\u003cem\u003e我这台\u003c/em\u003e特定的笔记本上开箱即用。令人鼓舞的部分是，它们每一个都教会了我某些我最终用上的东西。\u003c/p\u003e\n\u003cp\u003e下面是我在离开之前走过的那棵树。\u003c/p\u003e\n\u003ch3 id=\"wtwrpaeroctl--我借来的名字\" class=\"headerLink\"\u003e\n    \u003ca href=\"#wtwrpaeroctl--%e6%88%91%e5%80%9f%e6%9d%a5%e7%9a%84%e5%90%8d%e5%ad%97\" class=\"header-mark\" aria-label=\"Header mark for 'wtwrp/aeroctl —— 我借来的名字'\"\u003e\u003c/a\u003e2.1 wtwrp/aeroctl —— 我借来的名字\u003c/h3\u003e\u003cp\u003e\u003ca href=\"https://gitlab.com/wtwrp/aeroctl\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003ewtwrp/aeroctl\u003c/code\u003e\u003c/a\u003e 是最初的 AeroCtl，一个面向 Windows 的 C# / .NET 应用程序。它给了我的项目名字，而阅读它是理解一台受到良好支持的技嘉 Aero 从主机端看起来是什么样的最简单办法：风扇控制、充电策略、非标准 Fn 键、键盘 RGB、GPU boost，全都走技嘉的 ACPI WMI 驱动（\u003ccode\u003eacpimof.dll\u003c/code\u003e 之流）。RGB 那一节尤其有参考价值：它确认了键盘是通过一个 HID feature report 触及的，而不是通过某个专有的 IO 端口或 WMI blob。\u003c/p\u003e\n\u003cp\u003e问题显而易见：它是 Windows。整个前提是你已经装好了技嘉 ControlCenter 或 SmartManager，所以 \u003ccode\u003eacpimof.dll\u003c/code\u003e 是存在的。这些在我这台只跑 Linux 的笔记本上都不存在。但 \u003ccode\u003eSamples/\u003c/code\u003e 里的 RGB 示例代码——就是那段\u0026quot;打开 HID 设备，写一个 feature report，关闭\u0026quot;的部分——在精神上是可移植的。那正是我搬过来的那块。\u003c/p\u003e\n\u003ch3 id=\"aerocontrolcenter--那个索要内核驱动的-linux-移植版\" class=\"headerLink\"\u003e\n    \u003ca href=\"#aerocontrolcenter--%e9%82%a3%e4%b8%aa%e7%b4%a2%e8%a6%81%e5%86%85%e6%a0%b8%e9%a9%b1%e5%8a%a8%e7%9a%84-linux-%e7%a7%bb%e6%a4%8d%e7%89%88\" class=\"header-mark\" aria-label=\"Header mark for 'AeroControlCenter —— 那个索要内核驱动的 Linux 移植版'\"\u003e\u003c/a\u003e2.2 AeroControlCenter —— 那个索要内核驱动的 Linux 移植版\u003c/h3\u003e\u003cp\u003e\u003ca href=\"https://github.com/tangalbert919/AeroControlCenter\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003etangalbert919/AeroControlCenter\u003c/code\u003e\u003c/a\u003e 是技嘉 Control Center 的 Linux 移植版，用 C++/Qt 写成，面向 Aero 15 Classic（SA/WA/XA/YA）。它的路子比我的更有野心：它想处理风扇控制、电池策略\u003cem\u003e以及\u003c/em\u003e RGB，而为了干净地做到这一点，它依赖一个配套的内核驱动 \u003ca href=\"https://github.com/tangalbert919/gigabyte-laptop-wmi\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003egigabyte-laptop-wmi\u003c/code\u003e\u003c/a\u003e，把 WMI 接口暴露为 Linux sysfs 节点。\u003c/p\u003e\n\u003cp\u003e我欣赏这个架构，却用不了它。我的机器不在受测列表里，\u003ccode\u003egigabyte-laptop-wmi\u003c/code\u003e 在我机身上的现代内核上无法干净地绑定，而我也没有那个胃口去在一台我还得用来收发邮件的笔记本上调试一张 WMI ACPI 表。不过 AeroControlCenter \u003cem\u003e给了\u003c/em\u003e我那个 \u003ccode\u003e70-keyboard.rules\u003c/code\u003e 文件——也就是说，确认了 udev 这条路子对于权限问题来说是对的形状。我写了自己的规则，但我之所以会写，是因为那个项目的规则告诉了我该把它放在哪里。\u003c/p\u003e\n\u003ch3 id=\"fusion-kbd-controller--libusb内核解绑以及一句警告\" class=\"headerLink\"\u003e\n    \u003ca href=\"#fusion-kbd-controller--libusb%e5%86%85%e6%a0%b8%e8%a7%a3%e7%bb%91%e4%bb%a5%e5%8f%8a%e4%b8%80%e5%8f%a5%e8%ad%a6%e5%91%8a\" class=\"header-mark\" aria-label=\"Header mark for 'fusion-kbd-controller —— libusb、内核解绑，以及一句警告'\"\u003e\u003c/a\u003e2.3 fusion-kbd-controller —— libusb、内核解绑，以及一句警告\u003c/h3\u003e\u003cp\u003e\u003ca href=\"https://github.com/martin31821/fusion-kbd-controller\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003emartin31821/fusion-kbd-controller\u003c/code\u003e\u003c/a\u003e 是一个用 libusb 写的小小的 C 用户态二进制程序，面向 AERO 15X。读它的 README 是一趟过山车：它需要 root，它会临时把 USB 设备从内核的 \u003ccode\u003eusbhid\u003c/code\u003e 驱动上解绑，作者还乐呵呵地警告说\u0026quot;在这里发送荒谬的值可能会把你的键盘变砖\u0026quot;。\u003c/p\u003e\n\u003cp\u003e它能用，在 15X 上，用 libusb，靠内核解绑。这些东西我一个都不想要。我想要 \u003ccode\u003e/dev/hidraw\u003c/code\u003e，这样我就能在内核仍然掌管 HID 接口的同时跟设备对话（这样我发送颜色命令时按键还能继续打字），我想要一条 udev 规则这样我就不需要 root，而我\u003cem\u003e真的\u003c/em\u003e不想为了搞清楚\u0026quot;荒谬的值\u0026quot;对别人硬件意味着什么，把自己的键盘变成砖。但 fusion-kbd-controller 是我捡到\u0026quot;带有一个模式和一个参数区域的字节包\u0026quot;这个心智模型的地方，它最终推广到了我后来写的那个 ITE-829X feature report 包上。\u003c/p\u003e\n\u003ch3 id=\"keyboard-fusion-rgb--对的语言错的厂商\" class=\"headerLink\"\u003e\n    \u003ca href=\"#keyboard-fusion-rgb--%e5%af%b9%e7%9a%84%e8%af%ad%e8%a8%80%e9%94%99%e7%9a%84%e5%8e%82%e5%95%86\" class=\"header-mark\" aria-label=\"Header mark for 'keyboard-fusion-rgb —— 对的语言，错的厂商'\"\u003e\u003c/a\u003e2.4 keyboard-fusion-rgb —— 对的语言，错的厂商\u003c/h3\u003e\u003cp\u003e\u003ca href=\"https://github.com/rcassani/keyboard-fusion-rgb\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003ercassani/keyboard-fusion-rgb\u003c/code\u003e\u003c/a\u003e 是一个面向 AORUS 15G 的 Python HIDAPI 驱动。它是最接近\u0026quot;我最终做的事\u0026quot;的东西——Python、HID、用户态、通过观察 Windows 版 AORUS Control Center 逆向得来——只差一个细节：厂商/产品 ID 是 \u003ccode\u003e0x1044:0x7a3c\u003c/code\u003e（Chu Yuen Enterprise Co., Ltd.），这是一个与我笔记本里那颗（\u003ccode\u003e0x0414:0x8104\u003c/code\u003e，即 ITE-829X）完全不同的键盘控制器家族。\u003c/p\u003e\n\u003cp\u003e所以代码无法照搬使用——线上格式不同，每键布局不同，模式 ID 不同。但那\u003cem\u003e路子\u003c/em\u003e正是我想要的：从 Python 发出 HIDAPI 风格的 feature report，通过观察 Windows 工具写了什么来解码。那个项目的存在也是一记道德上的推动：如果 \u003ccode\u003ercassani\u003c/code\u003e 能靠观察 USB 流量解码一个技嘉子家族，那我也能用同样的方式解码我笔记本上的这个。\u003c/p\u003e\n\u003ch3 id=\"openrgb--那个还不认识这台机器的巨人\" class=\"headerLink\"\u003e\n    \u003ca href=\"#openrgb--%e9%82%a3%e4%b8%aa%e8%bf%98%e4%b8%8d%e8%ae%a4%e8%af%86%e8%bf%99%e5%8f%b0%e6%9c%ba%e5%99%a8%e7%9a%84%e5%b7%a8%e4%ba%ba\" class=\"header-mark\" aria-label=\"Header mark for 'OpenRGB —— 那个还不认识这台机器的巨人'\"\u003e\u003c/a\u003e2.5 OpenRGB —— 那个还不认识这台机器的巨人\u003c/h3\u003e\u003cp\u003e\u003ca href=\"https://gitlab.com/CalcProgrammer1/OpenRGB\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003eCalcProgrammer1/OpenRGB\u003c/code\u003e\u003c/a\u003e 是通用的 RGB 工具——Windows、Linux、macOS，天底下每一块主板和每一家内存厂商。它正是那个我想用却用不上的项目。他们那个关于技嘉 Aero 15 OLED YD 的\u003ca href=\"https://gitlab.com/CalcProgrammer1/OpenRGB/-/issues/2288\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e未关闭 issue #2288\u003c/a\u003e，本质上就是\u0026quot;我们还没有这个型号\u0026quot;。我的笔记本和那个 issue 是不同的 SKU，但近到足以让我知道答案是一样的：还没到。\u003c/p\u003e\n\u003cp\u003e我之所以明确提到 OpenRGB，是因为在一个更美好的世界里这篇文章不会存在——我本可以打开它们的设置面板，挑一个效果，然后就此打住。可现实是，我要去解码一个数据包、写 593 行 Python，再假装我对此没意见。\u003c/p\u003e\n\u003ch3 id=\"我从这棵树上拿走了什么\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e6%88%91%e4%bb%8e%e8%bf%99%e6%a3%b5%e6%a0%91%e4%b8%8a%e6%8b%bf%e8%b5%b0%e4%ba%86%e4%bb%80%e4%b9%88\" class=\"header-mark\" aria-label=\"Header mark for '我从这棵树上拿走了什么'\"\u003e\u003c/a\u003e2.6 我从这棵树上拿走了什么\u003c/h3\u003e\u003cp\u003e一小时研究的总结大致是这样：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-1\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  prior art                         usable for my laptop?\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  ----------------------------      ---------------------\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  wtwrp/aeroctl (Windows, C#)       no  (wrong OS, WMI-based)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                    yes — packet shape intuition\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  AeroControlCenter (Linux, Qt)     no  (different model; wants kernel driver)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                    yes — udev rule as the permissions story\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  fusion-kbd-controller (C, libusb) no  (wrong model; kernel unbind; root only)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                    yes — \u0026#34;mode byte + params + checksum\u0026#34; mental model\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  keyboard-fusion-rgb (Py, HIDAPI)  no  (different vendor, 0x1044 vs 0x0414)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                    yes — Python + HID is the right shape\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  OpenRGB (C++, everywhere)         no  (my SKU not yet supported)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                    yes — confirmation that nobody else had this\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这正是在世界这个角落里做前人之作研究的诀窍：\u0026ldquo;有没有给我这台机器的东西\u0026quot;这个问题的答案几乎总是\u003cem\u003e没有\u003c/em\u003e，但\u0026quot;别人都搞清楚了哪些我不该重新推导一遍的东西\u0026quot;这个问题的答案几乎总是\u003cem\u003e有那么一些\u003c/em\u003e。我关掉浏览器标签页，带着一个半成形的计划：Python、\u003ccode\u003e/dev/hidraw\u003c/code\u003e、一条让我不需要 sudo 的 udev 规则、一个很可能是\u0026quot;模式 + 参数 + checksum8\u0026quot;的数据包，以及——最大的风险——一个我得靠猜的校验和算法，因为前人之作里没有一个用的正是这颗控制器。\u003c/p\u003e\n\u003cp\u003e这把我带回了那个天真的问题：我的 USB 总线上\u003cem\u003e实际\u003c/em\u003e有什么，而它又想听到什么？\u003c/p\u003e\n\u003ch2 id=\"逆向工程这块键盘\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e9%80%86%e5%90%91%e5%b7%a5%e7%a8%8b%e8%bf%99%e5%9d%97%e9%94%ae%e7%9b%98\" class=\"header-mark\" aria-label=\"Header mark for '逆向工程这块键盘'\"\u003e\u003c/a\u003e3 逆向工程这块键盘\u003c/h2\u003e\u003cp\u003e我学到的第一件事是，逆向工程一个 USB 外设，10% 是聪明的侦探活，90% 是在凌晨一点你配偶问你在干什么时阅读 \u003ccode\u003edmesg\u003c/code\u003e 输出。技嘉 Aero 的键盘当然不是一个独立的 dongle——它是一个内部 USB 设备，而 \u003ccode\u003elsusb | grep 0414\u003c/code\u003e 殷勤地确认了这一点：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-2\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eBus 001 Device 003: ID 0414:8104 Gigabyte Technology Co.,Ltd ...\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e厂商 \u003ccode\u003e0x0414\u003c/code\u003e，产品 \u003ccode\u003e0x8104\u003c/code\u003e。那就是 ITE-829X。我把能找到的一切都谷歌了一遍，跟 Windows 工具的源码交叉比对，形成了一个假设：RGB 控制藏在一个 HID feature report 之后，暴露在一个 usage page 为 \u003ccode\u003e0xFF01\u003c/code\u003e 的接口上——一个厂商自定义的 HID 页，相当于 USB 世界里一条标着\u0026quot;私人\u0026quot;的土路。\u003c/p\u003e\n\u003ch3 id=\"找到正确的-devhidraw\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e6%89%be%e5%88%b0%e6%ad%a3%e7%a1%ae%e7%9a%84-devhidraw\" class=\"header-mark\" aria-label=\"Header mark for '找到正确的 \u0026lt;code\u0026gt;/dev/hidraw\u0026lt;/code\u0026gt;'\"\u003e\u003c/a\u003e3.1 找到正确的 \u003ccode\u003e/dev/hidraw\u003c/code\u003e\u003c/h3\u003e\u003cp\u003e这里就是逆向工程不再像魔法、开始像文件系统考古的地方。Linux 把每一个 HID 设备暴露在 \u003ccode\u003e/sys/bus/hid/devices/\u003c/code\u003e 下，而对每个设备它都递给你一个 \u003ccode\u003ereport_descriptor\u003c/code\u003e——一团字节，告诉你（如果你眯起眼睛的话）这个设备愿意聊些什么。\u003c/p\u003e\n\u003cp\u003e我写了一个扫描器。给定正确的厂商/产品对，它对 sysfs 目录做 glob，读取每一个 \u003ccode\u003ereport_descriptor\u003c/code\u003e，并寻找那三个字节 \u003ccode\u003e\\x06\\x01\\xff\u003c/code\u003e——HID 行话里的\u0026quot;usage page \u003ccode\u003e0xFF01\u003c/code\u003e\u0026quot;。当我找到一个匹配时，我顺着 symlink 进入它的 \u003ccode\u003ehidraw\u003c/code\u003e 子目录，抓住属于它的那个 \u003ccode\u003e/dev/hidrawN\u003c/code\u003e 节点。\u003c/p\u003e\n\u003cp\u003e把这里的 udev + sysfs 想象成外设的某种 \u003ccode\u003e/proc\u003c/code\u003e：一份只读的、活的文档，描述内核当前对每个插入设备的认知。而且和 \u003ccode\u003e/proc\u003c/code\u003e 一样，使用它的正确方式不是去信任任何特定的设备号——今天的 \u003ccode\u003e/dev/hidraw0\u003c/code\u003e 在你插上一个鼠标后明天可能就成了 \u003ccode\u003e/dev/hidraw3\u003c/code\u003e——而是去扫描、过滤，并按属性挑选。\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-3\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# src/aeroctl_linux_indicator/device.py:99-119\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@staticmethod\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_find_hidraw\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evendor_id\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eVID_DEFAULT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eproduct_id\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003ePID_DEFAULT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eusage_page_bytes\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sa\"\u003eb\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u003c/span\u003e\u003cspan class=\"se\"\u003e\\x06\\x01\\xff\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u0026#34;Find the hidraw node with Usage Page 0xFF01 by scanning /sys.\u0026#34;\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003epattern\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;/sys/bus/hid/devices/0003:\u003c/span\u003e\u003cspan class=\"si\"\u003e%04X\u003c/span\u003e\u003cspan class=\"s2\"\u003e:\u003c/span\u003e\u003cspan class=\"si\"\u003e%04X\u003c/span\u003e\u003cspan class=\"s2\"\u003e.*\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evendor_id\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eproduct_id\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003epath\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"nb\"\u003esorted\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eglob\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eglob\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epattern\u003c/span\u003e\u003cspan class=\"p\"\u003e)):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003erd_path\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ejoin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;report_descriptor\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"nb\"\u003eopen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erd_path\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;rb\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"n\"\u003ef\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003erd\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ef\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eread\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eexcept\u003c/span\u003e \u003cspan class=\"ne\"\u003eOSError\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003econtinue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eusage_page_bytes\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003erd\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003ehidraw_dir\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ejoin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;hidraw\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eentries\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elistdir\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ehidraw_dir\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eentries\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"n\"\u003edevpath\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;/dev/\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eentries\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eexists\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edevpath\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003edevpath\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eexcept\u003c/span\u003e \u003cspan class=\"ne\"\u003eOSError\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003econtinue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eglob 模式里的 \u003ccode\u003e0003:\u003c/code\u003e 前缀值得停一下。HID 设备由内核用总线 ID 注册，而 \u003ccode\u003e0003\u003c/code\u003e 是 USB。对我的机器来说，完整的模式展开为 \u003ccode\u003e/sys/bus/hid/devices/0003:0414:8104.*\u003c/code\u003e。那个 \u003ccode\u003e*\u003c/code\u003e 之所以在那里，是因为单个物理设备可以暴露好几个 HID 接口——一个给普通按键，一个给媒体键，一个给厂商控制通道——而我想要的是那个 report descriptor 声明了 \u003ccode\u003e0xFF01\u003c/code\u003e 的接口。\u003c/p\u003e\n\u003cp\u003e下面是我最终得到的发现流程：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-4\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 ┌──────────────────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 │  /sys/bus/hid/devices/0003:0414:8104.*   │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 │  glob-expand (one entry per HID iface)   │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 └─────────────────────┬────────────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                       │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                       ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 ┌──────────────────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 │  open report_descriptor, scan for        │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 │  b\u0026#39;\\x06\\x01\\xff\u0026#39;  ==  usage page 0xFF01  │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 └─────────────────────┬────────────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                       │ match\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                       ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 ┌──────────────────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 │  ls \u0026lt;iface\u0026gt;/hidraw/  -\u0026gt;  \u0026#34;hidraw0\u0026#34;       │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 │  devpath = \u0026#34;/dev/\u0026#34; + entry               │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 └─────────────────────┬────────────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                       │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                       ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 ┌──────────────────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 │  os.open(devpath, O_RDWR)                │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 │  fcntl.ioctl(fd, HIDIOCSFEATURE(9), buf) │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 └──────────────────────────────────────────┘\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e一旦那个 hidraw 路径到手，剩下的一切都是 \u003ccode\u003efcntl.ioctl\u003c/code\u003e。\u003ccode\u003eHIDIOCSFEATURE\u003c/code\u003e 和 \u003ccode\u003eHIDIOCGFEATURE\u003c/code\u003e 这两个 ioctl 是从用户态发送和接收 HID feature report 的标准方式。我手搓了那些魔数，因为我不想依赖 \u003ccode\u003ehidapi\u003c/code\u003e：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-5\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# src/aeroctl_linux_indicator/device.py:56-62\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_ioc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edirection\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etyp\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003esize\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edirection\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e30\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esize\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e16\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eord\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etyp\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e8\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"n\"\u003enr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eHIDIOCSFEATURE\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003elambda\u003c/span\u003e \u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003e_ioc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;H\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mh\"\u003e0x06\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eHIDIOCGFEATURE\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003elambda\u003c/span\u003e \u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003e_ioc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;H\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mh\"\u003e0x07\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eREPORT_SIZE\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e9\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e\u003ccode\u003eAeroKeyboardRGB\u003c/code\u003e 类（\u003ccode\u003esrc/aeroctl_linux_indicator/device.py:77-240\u003c/code\u003e）把整个硬件抽象集中在一处：打开设备，写 9 字节的数据包，读 9 字节的响应，退出时关闭。它还是一个上下文管理器，因为我喜欢我的文件描述符就像我喜欢我那些昂贵的笔记本一样：用完就关。\u003c/p\u003e\n\u003cp\u003e有一个文件描述符是一回事。有\u003cem\u003e要往里送下去的正确字节\u003c/em\u003e是另一回事，而那正是我撞上第一堵看起来像谜题而非管道问题的墙的地方——而事实证明，那恰恰就是逆向工程变得有趣的时候。\u003c/p\u003e\n\u003ch2 id=\"数据包九个字节和一个非异或校验和\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e6%95%b0%e6%8d%ae%e5%8c%85%e4%b9%9d%e4%b8%aa%e5%ad%97%e8%8a%82%e5%92%8c%e4%b8%80%e4%b8%aa%e9%9d%9e%e5%bc%82%e6%88%96%e6%a0%a1%e9%aa%8c%e5%92%8c\" class=\"header-mark\" aria-label=\"Header mark for '数据包：九个字节和一个非异或校验和'\"\u003e\u003c/a\u003e4 数据包：九个字节和一个非异或校验和\u003c/h2\u003e\u003cp\u003e现在，说说数据包。这是有趣的地方。\u003c/p\u003e\n\u003cp\u003e你或许会预期——我当然预期了——一个用于 LED 控制器的、区区 9 字节的控制数据包，会用异或来校验自己。人人都用异或。异或很便宜，异或是固件工程师在周五下午四点、一心想回家时顺手就抓的东西。我盯着从 Windows 工具抓来的 Wireshark 捕获。我写了个小 Python 脚本，对数据包每一种可能的切片尝试异或。没有一个对得上。\u003c/p\u003e\n\u003cp\u003e我试了简单求和。也不对。\u003c/p\u003e\n\u003cp\u003e我试了 \u003ccode\u003e256 - sum\u003c/code\u003e。接近了。可疑地接近。差了一。\u003c/p\u003e\n\u003cp\u003e事实证明，答案是这个：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-6\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003echecksum = (0xFF - (sum(bytes[1..7]) \u0026amp; 0xFF)) \u0026amp; 0xFF\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e不是异或。**不是异或。**它是一种 8 位的、类似反码的东西：取 payload 字节之和，掩码成一个字节，然后从 \u003ccode\u003e0xFF\u003c/code\u003e 里减掉。一个\u0026quot;让整个东西总和为 \u003ccode\u003e0xFF\u003c/code\u003e\u0026ldquo;的校验和。这是完全合理的，就像所有硬件协议在你不再指望它们合理之后都变得完全合理一样。\u003c/p\u003e\n\u003cp\u003e如果你在想\u0026quot;啊，这不就是 \u003ccode\u003e~sum + 1 - 1\u003c/code\u003e 嘛？\u0026quot;——是的，而且，不，别这样，它不是二的补码。它是一个\u0026quot;和加校验和应该等于 \u003ccode\u003e0xFF\u003c/code\u003e\u0026ldquo;的不变量。这很巧妙，虽然略微不寻常，而逆向工程的第一条规则是：硬件怎么做你就怎么做，哪怕硬件有点古怪。\u003c/p\u003e\n\u003cp\u003e下面是全部实现：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-7\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# src/aeroctl_linux_indicator/device.py:94-96\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@staticmethod\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_checksum8\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mh\"\u003e0xFF\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003esum\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e \u003cspan class=\"mh\"\u003e0xFF\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e \u003cspan class=\"mh\"\u003e0xFF\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e两行，外加一个装饰器。外面那个 \u003ccode\u003e\u0026amp; 0xFF\u003c/code\u003e 是双保险——Python 里的算术是无界的，而无论我传进多少字节，我都想让这个结果塞进一个字节里。\u003c/p\u003e\n\u003cp\u003e数据包布局本身是这样：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-8\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  Offset  ┬───────────────────────────────────────────────────────────\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   B0     │  0x00                        ← HID report ID / padding\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   B1     │  command   (e.g. 0x08 = SET_EFFECT, 0x80 = GET_FIRMWARE)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   B2     │  sub-arg   (often 0x00)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   B3     │  effect id (1=static, 2=breathing, 3=wave, ...)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   B4     │  speed\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   B5     │  brightness\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   B6     │  color id  (0..8)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   B7     │  direction\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   B8     │  checksum  =  (0xFF - (sum(B0..B7) \u0026amp; 0xFF)) \u0026amp; 0xFF\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  ────────┴───────────────────────────────────────────────────────────\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e           9 bytes total, sent via HIDIOCSFEATURE ioctl on /dev/hidrawN\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这个分帧的选择值得欣赏片刻。字节 0 是 HID report ID，而由于这个特定设备使用 report ID \u003ccode\u003e0\u003c/code\u003e，它实际上就是 padding——但 Linux 的 \u003ccode\u003ehidraw\u003c/code\u003e ABI 总是要求一个 report ID 作为 feature report 的第一个字节，所以我把它一路带着。字节 1–7 才是真正命令所在的地方。字节 8 是那个我花了两个小时弄错的校验和。\u003c/p\u003e\n\u003cp\u003e如果你写过终端模拟器，对我而言一拍即合的类比是这个：**一个 HID feature report 数据包基本上就是一个带校验和的 ANSI 转义码。**你有一个引导符（命令字节），你有参数（效果、速度、亮度、颜色、方向），你还有一个终止符——只不过终止符不是像 \u003ccode\u003em\u003c/code\u003e（\u0026ldquo;设置图形模式\u0026rdquo;）那样的可打印字母，而是一个校验和字节，用来核实固件没有被递了一堆垃圾。送错字节，控制器就礼貌地无视你。送对字节，你的键盘就变成一个迪斯科。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003edevice.py\u003c/code\u003e 的其余部分只是词汇表。\u003ccode\u003eset_effect\u003c/code\u003e 是 \u003ccode\u003e_send_feature(_packet(0x08, 0x00, effect, speed, brightness, color, direction))\u003c/code\u003e。\u003ccode\u003eget_firmware_version\u003c/code\u003e 是 \u003ccode\u003e_send_feature(_packet(0x80))\u003c/code\u003e 后跟一个 \u003ccode\u003e_get_feature(9)\u003c/code\u003e 读取。\u003ccode\u003ereset\u003c/code\u003e 是 \u003ccode\u003e_send_feature(_packet(0x13, 0xFF))\u003c/code\u003e。每个命令是一行。每一行都是一门我最终学会说的语言里的一个句子。\u003c/p\u003e\n\u003cp\u003e然而，正确地说这门语言，前提是我首先得能打开那个文件描述符——而当我在一天里第四十次敲入我的 sudo 密码之后，这棵树的下一根枝丫基本上是自己宣告登场的。\u003c/p\u003e\n\u003ch2 id=\"udev幸福的守门人\" class=\"headerLink\"\u003e\n    \u003ca href=\"#udev%e5%b9%b8%e7%a6%8f%e7%9a%84%e5%ae%88%e9%97%a8%e4%ba%ba\" class=\"header-mark\" aria-label=\"Header mark for 'udev：幸福的守门人'\"\u003e\u003c/a\u003e5 udev：幸福的守门人\u003c/h2\u003e\u003cp\u003eLinux 桌面的炼狱里有一个特殊的圈层，专门留给那些工作得完美无瑕、但前提是你得用 \u003ccode\u003esudo\u003c/code\u003e 来跑的工具。每次我切换键盘，我都得敲我的密码。每次托盘指示器想读取当前效果，它都读不到，因为一个普通用户进程默认无法以读写方式打开 \u003ccode\u003e/dev/hidraw0\u003c/code\u003e。对一个\u0026quot;托盘指示器\u0026quot;来说，这是致命伤。\u003c/p\u003e\n\u003cp\u003e修复办法是一条四行的 \u003ccode\u003eudev\u003c/code\u003e 规则，它是一件小小的杰作：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ebash\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-9\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# install.sh:14-21\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eUDEV_RULE_FILE\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/etc/udev/rules.d/70-gigabyte-kbd.rules\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e ! -f \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$UDEV_RULE_FILE\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;SUBSYSTEMS==\u0026#34;usb\u0026#34;, ATTRS{idVendor}==\u0026#34;0414\u0026#34;, ATTRS{idProduct}==\u0026#34;8104\u0026#34;, MODE=\u0026#34;0660\u0026#34;, TAG+=\u0026#34;uaccess\u0026#34;\u0026#39;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e sudo tee \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$UDEV_RULE_FILE\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u0026gt; /dev/null\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    sudo udevadm control --reload-rules \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e sudo udevadm trigger\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;udev rules created and applied.\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;udev rules already exist.\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efi\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这里的魔法词是 \u003ccode\u003eTAG+=\u0026quot;uaccess\u0026quot;\u003c/code\u003e。这个标签告诉 systemd-logind：\u0026ldquo;当前在本地座位登录的人，应该被允许访问这个设备。\u0026ldquo;没有它，\u003ccode\u003e/dev/hidraw0\u003c/code\u003e 归 \u003ccode\u003eroot:root\u003c/code\u003e 所有、权限为 \u003ccode\u003e0660\u003c/code\u003e，而你的用户进程会像扑窗的飞蛾一样被弹回来。\u003c/p\u003e\n\u003cp\u003e有了它，systemd 就给设备节点加上一条 ACL，自动地给你这个登录用户读+写权限，只要你还坐在键盘前就一直有效。注销，ACL 消失。插上第二块 Aero 键盘，ACL 也会出现在新的那块上。它是 Linux 版的酒店门卡：你当客人期间获得访问权，离店时系统把它收回。\u003c/p\u003e\n\u003cp\u003e我喜欢把 udev 规则想成一封\u003cstrong\u003e写给未来的你的情书\u003c/strong\u003e。你写它一次，把它留在磁盘上，从此往后，机器就永远欢迎你未来的自己——那个已经把这个项目忘得一干二净、只想让键盘亮起来的自己——仿佛一切从来都不曾艰难过。\u003ccode\u003einstall.sh\u003c/code\u003e 脚本在首次安装时自动送出这份礼物。你再也不会见到这封情书，因为你再也不需要它。\u003c/p\u003e\n\u003cp\u003eudev 规则解决了权限的舞步，但解决它也浮出了一个更安静的问题：既然现在任何登录用户都能驱动这个设备，那他们究竟\u003cem\u003e该如何\u003c/em\u003e驱动它？凌晨一点在命令行上？还是在写邮件的同时从一个托盘菜单里？理想情况下，两者都要——而当我把\u0026quot;两者都要\u0026quot;大声说出来的那一刻，项目的下一根枝丫就自己铺展开了。\u003c/p\u003e\n\u003ch2 id=\"两个前端一个大脑\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e4%b8%a4%e4%b8%aa%e5%89%8d%e7%ab%af%e4%b8%80%e4%b8%aa%e5%a4%a7%e8%84%91\" class=\"header-mark\" aria-label=\"Header mark for '两个前端，一个大脑'\"\u003e\u003c/a\u003e6 两个前端，一个大脑\u003c/h2\u003e\u003cp\u003e一旦设备层跑通了，就有一个决定要做：CLI 还是 GUI？我说：何不两者都要。但我也说：\u003cstrong\u003e别做两遍。\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e我最终得到的形态是经典的\u0026quot;两个前端共享一个大脑\u0026rdquo;——底部是同一个 \u003ccode\u003eAeroKeyboardRGB\u003c/code\u003e 类，顶部是两个很不一样的 UI。两个 UI 都对 HID 一无所知。两个 UI 都不做自己的校验和运算。它们都倚靠同一个设备类，而它们共享的唯一状态，是用户缓存目录里一个小小的 JSON 文件。\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-10\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   ┌──────────────────────────┐    ┌─────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │   aeroctl-kbd  (CLI)     │    │   aeroctl-kbd-tray  (GTK)   │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │                          │    │                             │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │   argparse, stdin/stdout │    │   AppIndicator menu         │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │   src/.../cli.py:69-162  │    │   src/.../tray.py:44-150    │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   └──────────────┬───────────┘    └──────────────┬──────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                  │                               │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                  │         both talk to          │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                  ▼                               ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         ┌────────────────────────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         │           AeroKeyboardRGB (device.py)          │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         │                                                │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         │    _find_hidraw  →  os.open  →  ioctl dance    │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         │                                                │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         │    set_effect / get_effect / firmware / reset  │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         └────────────────────────┬───────────────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                  │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                  ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                  /dev/hidraw?   ────────►   ITE-829X   ────►  RGB\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                           (keyboard LEDs)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         ┌────────────────────────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         │    ~/.cache/aeroctl-kbd-state.json             │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         │    { effect, speed, brightness, color, dir }   │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         │    written on \u0026#34;off\u0026#34;, read on \u0026#34;on\u0026#34;/\u0026#34;toggle\u0026#34;     │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         │    shared by CLI and tray                      │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         └────────────────────────────────────────────────┘\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eCLI 位于 \u003ccode\u003esrc/aeroctl_linux_indicator/cli.py:69-162\u003c/code\u003e。它是一个带子命令的 \u003ccode\u003eargparse\u003c/code\u003e 大门——\u003ccode\u003edevices\u003c/code\u003e、\u003ccode\u003estatus\u003c/code\u003e、\u003ccode\u003efirmware\u003c/code\u003e、\u003ccode\u003eset\u003c/code\u003e、\u003ccode\u003eoff\u003c/code\u003e、\u003ccode\u003eon\u003c/code\u003e、\u003ccode\u003etoggle\u003c/code\u003e、\u003ccode\u003ereset\u003c/code\u003e、\u003ccode\u003ekeyboard-mode\u003c/code\u003e、\u003ccode\u003eraw\u003c/code\u003e——而每个子命令都是一个 \u003ccode\u003ewith kb:\u003c/code\u003e 代码块，打开设备，干它的活，然后关闭。\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-11\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# src/aeroctl_linux_indicator/cli.py:95-118  (off/on, state round-trip)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecmd\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;off\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ecur\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ekb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_effect\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;brightness\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003e_save_state\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;effect\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;effect\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;speed\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;speed\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;brightness\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;brightness\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;color\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;color\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;direction\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;direction\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ekb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eset_effect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eEFFECTS\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;static\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eCOLORS\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;black\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;ok: off\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecmd\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;on\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003est\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003e_load_state\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ekb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eset_effect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;effect\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;speed\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;brightness\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;color\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;direction\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;ok: on (restored)\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ekb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eset_effect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eEFFECTS\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;static\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e100\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eCOLORS\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;white\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;ok: on (default white)\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e\u0026ldquo;off\u0026rdquo; 命令不是\u0026quot;请熄灭\u0026rdquo;。在底层，它是：\u0026ldquo;把当前效果捕获到磁盘，然后在一个静态黑色效果上把亮度设为零。\u0026ldquo;键盘固件没有真正的关机——它有的是 \u003ccode\u003ebrightness = 0\u003c/code\u003e。\u003ccode\u003eon\u003c/code\u003e 命令是对称的：读取 JSON，把先前的效果推回去。如果没有先前的状态（首次启动、全新安装），它会回退到一个舒服的默认值：白色，100% 亮度。\u003c/p\u003e\n\u003cp\u003e托盘指示器（\u003ccode\u003esrc/aeroctl_linux_indicator/tray.py:44-184\u003c/code\u003e）跳同样的舞步，只是由鼠标点击来驱动。它构建一个 GTK 菜单，有三个子菜单——Brightness、Color、Effect——外加 \u003ccode\u003eToggle On/Off\u003c/code\u003e、\u003ccode\u003eReset\u003c/code\u003e、\u003ccode\u003eStatus\u003c/code\u003e、\u003ccode\u003eQuit\u003c/code\u003e。每个菜单项都接到同样的辅助函数上：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-12\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# src/aeroctl_linux_indicator/tray.py:62-83\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_set\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eeffect\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecolor\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ebrightness\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e100\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003espeed\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edirection\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003e_safe\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003elambda\u003c/span\u003e \u003cspan class=\"n\"\u003edev\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003edev\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eset_effect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eEFFECTS\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eeffect\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003espeed\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ebrightness\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eCOLORS\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ecolor\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003edirection\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_set_brightness\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ebrightness\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_op\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edev\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eAeroKeyboardRGB\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ecur\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edev\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_effect\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003edev\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eset_effect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;effect\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;speed\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ebrightness\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;color\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;direction\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003e_safe\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e_op\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_set_effect_only\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eeffect\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_op\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edev\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eAeroKeyboardRGB\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ecur\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edev\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_effect\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ebri\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;brightness\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;brightness\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"mi\"\u003e100\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003edev\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eset_effect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eEFFECTS\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eeffect\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;speed\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ebri\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;color\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;direction\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003e_safe\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e_op\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e\u003ccode\u003e_safe\u003c/code\u003e 包装器是一句三行的招供：\u0026ldquo;我是一个托盘应用，如果设备打开失败，我不该把整个桌面搞崩。\u0026ldquo;它吞掉异常并把它们记录到 stderr。这不优雅。这很务实。一个崩溃了的托盘应用比一个悄悄地点不亮你键盘的托盘应用是更糟糕的用户体验，而如果出了什么差错——线缆拔了、udev 规则没了、有人把电池拔了——你还有一个能用的 GNOME 会话，可以从里面去修。\u003c/p\u003e\n\u003cp\u003e因为两个前端都写入同一个 \u003ccode\u003e~/.cache/aeroctl-kbd-state.json\u003c/code\u003e，从托盘切换、然后从 CLI 切换会做对的事。\u0026ldquo;从托盘关、从 CLI 开\u0026quot;会恢复你之前拥有的同一套颜色和效果。这点小小的一致性，老实说，正是这个指示器之所以感觉精致的全部原因。\u003c/p\u003e\n\u003cp\u003e不过，精致是在我这台笔记本上。一直拉扯着我的念头是：\u0026ldquo;在我这台笔记本上精致\u0026quot;是一个陷阱——一个只能在我恰好装了的那个发行版、那个 Python、那个 AppIndicator 构建上运行的托盘指示器不是工具，而是一个派对小把戏。这就是我如何掉进 PyInstaller 兔子洞的。\u003c/p\u003e\n\u003ch2 id=\"pyinstaller--appindicator一场发行版外交\" class=\"headerLink\"\u003e\n    \u003ca href=\"#pyinstaller--appindicator%e4%b8%80%e5%9c%ba%e5%8f%91%e8%a1%8c%e7%89%88%e5%a4%96%e4%ba%a4\" class=\"header-mark\" aria-label=\"Header mark for 'PyInstaller \u0026#43; AppIndicator：一场发行版外交'\"\u003e\u003c/a\u003e7 PyInstaller + AppIndicator：一场发行版外交\u003c/h2\u003e\u003cp\u003eLinux 桌面是很多东西，但\u0026quot;在系统托盘 API 上保持一致\u0026quot;不是其中之一。最初的这个库叫 \u003ccode\u003eAppIndicator3\u003c/code\u003e，由 Canonical 为 Unity 维护。Unity 死了之后，它被 MATE 收养。然后 GNOME 通过一个 shell 扩展让它活了下来。然后 Ubuntu 24.04 推出了——随之而来的是改名：\u003ccode\u003eAyatanaAppIndicator3\u003c/code\u003e，那个所有人现在都应该用的\u0026quot;分叉的分叉\u0026rdquo;。\u003c/p\u003e\n\u003cp\u003e24.04 上的全新安装只带 Ayatana。更老的系统和一些发行版只带遗留的那个。少数几个两个都带。我需要我的二进制文件在这三种情况下都能用。\u003c/p\u003e\n\u003cp\u003e托盘在导入时用一个小小的 try/except 处理这件事，先尝试现代的名字，再回退：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-13\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# src/aeroctl_linux_indicator/tray.py:12-17\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003egi\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erequire_version\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;AyatanaAppIndicator3\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;0.1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eAppIndicator\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eimportlib\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eimport_module\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;gi.repository.AyatanaAppIndicator3\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eexcept\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"ne\"\u003eValueError\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"ne\"\u003eImportError\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003egi\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erequire_version\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;AppIndicator3\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;0.1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eAppIndicator\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eimportlib\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eimport_module\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;gi.repository.AppIndicator3\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e对一个 Python 导入来说没问题。对 PyInstaller 来说就不行了，它必须在构建时就知道要打包哪些模块。于是我给构建脚本加了一个对应的嗅探测试：它对着活动的解释器跑一小段内联 Python，检查这两个库里哪一个是真正装着的，并打印出导入路径。然后 shell 把那个路径作为 \u003ccode\u003e--hidden-import\u003c/code\u003e 传给 PyInstaller：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ebash\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-14\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# scripts/build-binaries.sh:14-24\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eAPPINDICATOR_IMPORT\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"nv\"\u003eAPPINDICATOR_IMPORT\u003c/span\u003e\u003cspan class=\"k\"\u003e:-$(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$PYTHON_BIN\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e - \u003cspan class=\"s\"\u003e\u0026lt;\u0026lt;\u0026#39;PY\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003eimport gi\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003etry:\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e    gi.require_version(\u0026#34;AyatanaAppIndicator3\u0026#34;, \u0026#34;0.1\u0026#34;)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e    print(\u0026#34;gi.repository.AyatanaAppIndicator3\u0026#34;)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003eexcept ValueError:\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e    gi.require_version(\u0026#34;AppIndicator3\u0026#34;, \u0026#34;0.1\u0026#34;)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e    print(\u0026#34;gi.repository.AppIndicator3\u0026#34;)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003ePY\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e)\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这是那种进不了博客文章标题的细节，因为\u0026quot;我给构建脚本加了一段 18 行的 heredoc\u0026quot;算不上头条。但它确确实实是\u0026quot;在我机器上能跑\u0026quot;和\u0026quot;在用户机器上也能跑\u0026quot;之间的区别。当有人在 Fedora 上克隆这个仓库时，嗅探测试解析为 Ayatana。当有人在一台从没收到过通知的 20.04 LTS 机器上克隆它时，它解析为遗留的名字。PyInstaller 打包正确的那个，于是二进制文件就是能用。\u003c/p\u003e\n\u003cp\u003eCLI 二进制用 \u003ccode\u003e--onefile\u003c/code\u003e 构建。托盘二进制用 \u003ccode\u003e--onedir\u003c/code\u003e 构建，因为 GTK 的运行时需要一大堆兄弟文件（图标缓存、schema 文件、typelib），PyInstaller 没法在不带来痛苦的情况下把它们内联进单个可执行文件。然后 \u003ccode\u003einstall.sh\u003c/code\u003e 脚本把这棵 onedir 树打成 tar，解压到 \u003ccode\u003e~/.local/bin/aeroctl-kbd-tray/\u003c/code\u003e，并创建一个指向里面那个托盘二进制的 \u003ccode\u003eautostart\u003c/code\u003e 桌面项。\u003c/p\u003e\n\u003cp\u003e到这一步，软件这边的故事实际上已经讲完了——至少是发布的那部分。剩下的是硬件那边的故事，而那个故事我只能事后从提交日志里读出来。其中一个提交，正是我之所以会写这篇随笔的全部原因。\u003c/p\u003e\n\u003ch2 id=\"硬件的极限由提交日志讲述\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%a1%ac%e4%bb%b6%e7%9a%84%e6%9e%81%e9%99%90%e7%94%b1%e6%8f%90%e4%ba%a4%e6%97%a5%e5%bf%97%e8%ae%b2%e8%bf%b0\" class=\"header-mark\" aria-label=\"Header mark for '硬件的极限，由提交日志讲述'\"\u003e\u003c/a\u003e8 硬件的极限，由提交日志讲述\u003c/h2\u003e\u003cp\u003e如果你把这个项目的 git log 从头读到尾，它读起来几乎像一篇短篇小说。我打算引用提交哈希——真实的那些——因为正是在这里，叙事变得真切起来。\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-15\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e550ee28  feat: setup project, cli, tray and configure auto-releases      (1077 LoC)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e00055f2  test: add test suite, mock hardware IO and integrate pytest     (coverage to CI)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e74646e6  fix: tray mock attribute error and boost test coverage to ~78%\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecaea8df  test: add missing branches coverage to cli.py reaching +80%\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ef39a4b1  fix(install): use python environment for pyinstaller build\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ef4a204e  fix(install): auto-start the tray indicator immediately\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e68dbdae  chore: remove unsupported RGB effects                           ← the sad one\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ee1ac392  docs: add supported device model AERO X16 2WH\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e48c0f46  docs: clarify native hardware RGB effect limitations\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e\u003ccode\u003e550ee28\u003c/code\u003e 是最初的倾倒：一口气 1,077 行，16 个文件，CLI、托盘、构建脚本、安装脚本、README。一个逆向工程项目的第一个提交总是这副样子——不是因为你不懂良好的提交卫生，而是因为你刚刚花了一个月，让它住在一个没有版本控制的单一目录里，一边琢磨这玩意儿到底行不行得通。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e00055f2\u003c/code\u003e 是第一套测试。我把硬件层 mock 掉了（\u003ccode\u003e/dev/hidraw\u003c/code\u003e 在 CI 里不存在），这样测试装置就能在不需要 runner 上插着物理键盘的情况下，演练数据包构造、状态往返和 CLI 参数解析。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e74646e6\u003c/code\u003e 是那次出丑。我 mock 了 \u003ccode\u003eAppIndicator.Indicator.new\u003c/code\u003e，却忘了它返回的一个属性，于是托盘测试在 GTK 接线深处以一个 \u003ccode\u003eAttributeError\u003c/code\u003e 失败了。一行，一个提交，35 行新测试，以及覆盖率上升到约 78%。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ecaea8df\u003c/code\u003e 把我在 \u003ccode\u003ecli.py\u003c/code\u003e 上推过了 80% 的逻辑覆盖率——它在 \u003ccode\u003etests/test_cli.py\u003c/code\u003e 里加了 56 行，击中了每一个子命令分支。对一个 593 行的项目来说，80% 是那个甜点位：每一条主要路径都被演练到，但我又不至于去给 \u003ccode\u003eargparse\u003c/code\u003e 的内部写测试。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ef39a4b1\u003c/code\u003e 和 \u003ccode\u003ef4a204e\u003c/code\u003e 是安装脚本的成长之痛。\u003ccode\u003ef39a4b1\u003c/code\u003e 做了两件事：它把整个脚本从西班牙语（作者的母语之一）翻译成英语，好让不说西班牙语的人也能审计它；它还让 PyInstaller 构建使用一个本地 virtualenv，这样脚本就不会在现代基于 Debian 的发行版上撞上 PEP-668。\u003ccode\u003ef4a204e\u003c/code\u003e 是那点小小的打磨，它说的是\u0026quot;在一次全新安装之后，也马上把托盘启动起来，而不是逼用户注销再登录\u0026rdquo;。那就是 \u003ccode\u003einstall.sh\u003c/code\u003e 末尾那个 \u003ccode\u003enohup\u003c/code\u003e 调用。\u003c/p\u003e\n\u003cp\u003e然后就是 \u003ccode\u003e68dbdae\u003c/code\u003e：\u003cstrong\u003e\u0026ldquo;chore: remove unsupported RGB effects\u0026rdquo;。\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e它是从 \u003ccode\u003edevice.py\u003c/code\u003e 删掉的六行，以及在 \u003ccode\u003etray.py\u003c/code\u003e 里改动的八行。它也是我得知我那台崭新的 Aero X16 竟不如一台 2019 年的 Aero 15 能干的那一刻。看看这个 diff：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-16\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# commit 68dbdae, src/aeroctl_linux_indicator/device.py, removed:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;ripple\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e6\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;neon\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e8\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;rainbow\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e9\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;circle\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e11\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;hedge\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e12\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;rotate\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e13\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e那些是真实的效果。它们存在于 Windows 固件协议里。它们有文档。它们只是在第 12 代 Aero 16 上不工作。固件接受了命令，但键盘不渲染那个效果。更新的型号把这些效果卸载给了 \u003cstrong\u003eGigabyte Control Center\u003c/strong\u003e——一个只在 Windows 上的服务，它从用户态通过 USB 一帧一帧地绘制按键，因为固件自己已经不再知道怎么做它们了。\u003c/p\u003e\n\u003cdiv class=\"details admonition note open\"\u003e\n    \u003cdiv class=\"details-summary admonition-title\"\u003e\n        \u003cspan class=\"icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z\"/\u003e\u003c/svg\u003e\u003c/span\u003e为何 `68dbdae` 是仓库里最悲伤的一行\u003cspan class=\"details-icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"details-content\"\u003e\n        \u003cdiv class=\"admonition-content\"\u003e它不是一个 bug 修复。它不是一次重构。它是一个承认：技嘉做了一个商业决定——\u0026ldquo;让 Windows 应用去做渲染，把固件削薄\u0026rdquo;——而这个决定以六个不再存在的效果名字的形式，永久地显现在我的 Linux 键盘上。\u003c/div\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp\u003e\u003ccode\u003ee1ac392\u003c/code\u003e 和 \u003ccode\u003e48c0f46\u003c/code\u003e 是那两个 README 提交，它们加了一个专门的章节向用户解释这件事，好让别人不必像我那样去学会它。\u003c/p\u003e\n\u003cp\u003e留给我的，是固件仍然原生会说的那七个效果：\u003ccode\u003estatic\u003c/code\u003e、\u003ccode\u003ebreathing\u003c/code\u003e、\u003ccode\u003ewave\u003c/code\u003e、\u003ccode\u003efade\u003c/code\u003e、\u003ccode\u003emarquee\u003c/code\u003e、\u003ccode\u003eflash\u003c/code\u003e、\u003ccode\u003eraindrop\u003c/code\u003e。它们够用了。它们很美。在 \u003ccode\u003e68dbdae\u003c/code\u003e 之前，它们还有 \u003ccode\u003eripple\u003c/code\u003e、\u003ccode\u003eneon\u003c/code\u003e 和 \u003ccode\u003erainbow\u003c/code\u003e 相伴，而我想念它们。\u003c/p\u003e\n\u003ch2 id=\"教训\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e6%95%99%e8%ae%ad\" class=\"header-mark\" aria-label=\"Header mark for '教训'\"\u003e\u003c/a\u003e9 教训\u003c/h2\u003e\u003cp\u003e如果我要再做一遍这件事，我会带上几条教训往前走。不多。这些小项目以小口小口的方式教授它们的教训。\u003c/p\u003e\n\u003ch3 id=\"跨代检测是脆弱的\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e8%b7%a8%e4%bb%a3%e6%a3%80%e6%b5%8b%e6%98%af%e8%84%86%e5%bc%b1%e7%9a%84\" class=\"header-mark\" aria-label=\"Header mark for '跨代检测是脆弱的'\"\u003e\u003c/a\u003e9.1 跨代检测是脆弱的\u003c/h3\u003e\u003cp\u003e每一代技嘉 Aero 都有它自己的怪癖。vendor ID 始终是 \u003ccode\u003e0x0414\u003c/code\u003e，但 product ID 会漂移，report descriptor 会漂移，效果表会漂移。我的扫描器之所以拿 \u003ccode\u003e0xFF01\u003c/code\u003e 这个 usage page 当锚点，恰恰因为那是最稳定的信号——如果未来某个型号改了 product ID，只要厂商还在用 ITE 控制器家族，我那个 sysfs glob 和 descriptor 嗅探很可能仍然能找到它。另一条路（把 PID 写死在代码里）不出一年就会烂掉。\u003c/p\u003e\n\u003ch3 id=\"ayatana遗留回退是真正要紧的那点打磨\" class=\"headerLink\"\u003e\n    \u003ca href=\"#ayatana%e9%81%97%e7%95%99%e5%9b%9e%e9%80%80%e6%98%af%e7%9c%9f%e6%ad%a3%e8%a6%81%e7%b4%a7%e7%9a%84%e9%82%a3%e7%82%b9%e6%89%93%e7%a3%a8\" class=\"header-mark\" aria-label=\"Header mark for 'Ayatana/遗留回退是真正要紧的那点打磨'\"\u003e\u003c/a\u003e9.2 Ayatana/遗留回退是真正要紧的那点打磨\u003c/h3\u003e\u003cp\u003e\u003ccode\u003ebuild-binaries.sh:14-24\u003c/code\u003e 里那 18 行 heredoc 并不光鲜。没有人会为 \u003ccode\u003etray.py:12-17\u003c/code\u003e 里的那个 \u003ccode\u003etry/except ValueError\u003c/code\u003e 写一篇博客文章。但合在一起，它们就是\u0026quot;装了对的发行版的 Linux 用户\u0026quot;和\u0026quot;Linux 用户\u0026quot;之间的区别。如果你打算在 2026 年发布一个桌面工具，你就得跳那支 Ayatana 之舞。它是过度工程的反面：它是十分钟的工作，悄悄地覆盖了三年的发行版碎片化。\u003c/p\u003e\n\u003ch3 id=\"一个-593-行的代码库可以达到-80-覆盖率只要硬件层被-mock-掉\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e4%b8%80%e4%b8%aa-593-%e8%a1%8c%e7%9a%84%e4%bb%a3%e7%a0%81%e5%ba%93%e5%8f%af%e4%bb%a5%e8%be%be%e5%88%b0-80-%e8%a6%86%e7%9b%96%e7%8e%87%e5%8f%aa%e8%a6%81%e7%a1%ac%e4%bb%b6%e5%b1%82%e8%a2%ab-mock-%e6%8e%89\" class=\"header-mark\" aria-label=\"Header mark for '一个 593 行的代码库可以达到 80% 覆盖率——只要硬件层被 mock 掉'\"\u003e\u003c/a\u003e9.3 一个 593 行的代码库可以达到 80% 覆盖率——只要硬件层被 mock 掉\u003c/h3\u003e\u003cp\u003e测试上最大的单项胜利，是下定决心在 \u003ccode\u003etests/conftest.py\u003c/code\u003e 里搞一个假的 \u003ccode\u003eAeroKeyboardRGB\u003c/code\u003e。一旦硬件可被 mock，每一个 CLI 子命令就都变成了它输入的纯函数：argparse 进去，假设备交互出来。CI 在 runner 上没插键盘的情况下就能跑。测试在零点几秒内跑完。而那些提升了覆盖率的提交（\u003ccode\u003e74646e6\u003c/code\u003e、\u003ccode\u003ecaea8df\u003c/code\u003e）两者加起来添加的测试还不到 100 行。小代码库，小测试套件，真实的覆盖率。这是一个不错的位置。\u003c/p\u003e\n\u003ch3 id=\"相信校验和而不是你的直觉\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%9b%b8%e4%bf%a1%e6%a0%a1%e9%aa%8c%e5%92%8c%e8%80%8c%e4%b8%8d%e6%98%af%e4%bd%a0%e7%9a%84%e7%9b%b4%e8%a7%89\" class=\"header-mark\" aria-label=\"Header mark for '相信校验和，而不是你的直觉'\"\u003e\u003c/a\u003e9.4 相信校验和，而不是你的直觉\u003c/h3\u003e\u003cp\u003e这一条我要是能办到就刺在自己身上。当你在逆向一个二进制协议、而那个显而易见的算法对不上捕获时，别耍小聪明。别假设厂商用的是某个已知的 CRC 变体。先试那些笨办法，再试笨办法的略微偏一点的变体，然后跟数学一起坐着，直到它咔哒一声对上。技嘉键盘的校验和是\u0026quot;让数据包总和为 \u003ccode\u003e0xFF\u003c/code\u003e\u0026quot;，没人会管这叫顶尖技术，但它就是固件实际计算的东西，而在逆向工程里，\u0026ldquo;固件实际计算的东西\u0026quot;每一次都胜过\u0026quot;你以为它应该计算的东西\u0026rdquo;。\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003e我的键盘现在会发光了。它在我登录时发光，因为 \u003ccode\u003einstall.sh\u003c/code\u003e 在 \u003ccode\u003e~/.config/autostart/\u003c/code\u003e 里丢了一个 autostart 文件。它在我把它关了又开之后还记得它上一次的颜色，因为 \u003ccode\u003ecli.py\u003c/code\u003e 和 \u003ccode\u003etray.py\u003c/code\u003e 都写入 \u003ccode\u003e~/.cache/\u003c/code\u003e 里同一个小小的 JSON 文件。它不用 \u003ccode\u003esudo\u003c/code\u003e 也能工作，因为一条 udev 规则里的一句 \u003ccode\u003eTAG+=\u0026quot;uaccess\u0026quot;\u003c/code\u003e，那条规则我设置过一次，此后再也不会去想它。\u003c/p\u003e\n\u003cp\u003e而在这一切之下的某个地方，一个 9 字节的数据包——八个字节的意图，一个字节的非异或校验和——正被从 Python 一路递交给 \u003ccode\u003efcntl.ioctl\u003c/code\u003e，递给 Linux 的 hidraw 驱动，递给一颗 ITE-829X 控制器，它终于，自我抹掉 Windows 以来第一次，明白了我想要什么。\u003c/p\u003e\n\u003cp\u003e而那，从头到尾，不过是想让键盘回我一句话。\u003c/p\u003e\n",
        "language": "zh-cn"
    },
    {
        "title" : "Gocracker 编年史：用 Go 写的 microVM，从周末玩具到生产级沙箱",
        "date_published" : "2026-05-19T00:00:00Z",
        "date_modified" : "2026-05-19T00:00:00Z",
        "id" : "https://misael.org/zh-cn/gocracker-part-1-foundation/",
        "url" : "https://misael.org/zh-cn/gocracker-part-1-foundation/",
        "summary": "一口气讲完整个 gocracker 的故事：为什么选 Go，KVM 到底是怎么工作的，差点把它搞死的并发 bug，把冷启动压到 200ms 以下的性能优化，以及那个差点把自己悄无声息搞死的生产层。",
        "content_html" : "\u003ch2 id=\"firecracker-很棒但我还是又写了一个\" class=\"headerLink\"\u003e\n    \u003ca href=\"#firecracker-%e5%be%88%e6%a3%92%e4%bd%86%e6%88%91%e8%bf%98%e6%98%af%e5%8f%88%e5%86%99%e4%ba%86%e4%b8%80%e4%b8%aa\" class=\"header-mark\" aria-label=\"Header mark for 'Firecracker 很棒。但我还是又写了一个。'\"\u003e\u003c/a\u003e1 Firecracker 很棒。但我还是又写了一个。\u003c/h2\u003e\u003cp\u003e我喜欢 Firecracker。要是 AWS 出周边，我会在笔记本上贴一个小小的 Firecracker 贴纸。它的理念——在毫秒级启动一台真正的 Linux 虚拟机，剥掉所有没人需要的设备，锁死系统调用，然后收工——是过去十年里最优雅的系统工程理念之一。它把\u0026quot;容器，但真正隔离\u0026quot;从一个梗变成了一个产品。\u003c/p\u003e\n\u003cp\u003e于是很自然地，某个周末，我决定取代它。\u003c/p\u003e\n\u003cp\u003e不是因为它不好。而是因为每次我想把 \u003ccode\u003eubuntu:22.04\u003c/code\u003e 当作 microVM 来跑时，在我和一个 shell 提示符之间隔着六个手动步骤：拉取镜像、解出 rootfs、构建 ext4 磁盘、生成 initrd、写一份四十行的 JSON 配置、创建 TAP 设备、和 iptables 搏斗、\u003cem\u003e然后\u003c/em\u003e才调用 API。Firecracker 是个 Rust 专才。它把虚拟机启动得无比漂亮，并假定剩下的都是你的事。对 AWS 来说这是正确的设计，那里每一步都是另一个专才服务。但对于在笔记本上跑东西的人来说，那是一场标签页的灾难。\u003c/p\u003e\n\u003cp\u003e我想要一个通才。\u003ccode\u003egocracker run --image ubuntu:22.04\u003c/code\u003e，而且我希望在我按完回车的那一刻，它已经把上面那些都做完了。所以我写了一个。用 Go。因为\u0026quot;自带电池\u0026quot;是其文化的那门语言，正是构建一个自带电池的 microVM 的显而易见之选。\u003c/p\u003e\n\u003cp\u003e如果说 Firecracker 是 CGI——一个朴素而有原则的接口，任何聪明的组件都能驱动它——那么 gocracker 就是 FastCGI：同样的接口，外面裹着一个长期运行、舒适的进程，它假定你是个在笔记本上干活的开发者，想把今天的活儿干完接着过日子。\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-1\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    FIRECRACKER                gocracker\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    (Rust specialist)          (Go generalist)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    just a VMM                 VMM + OCI + initrd + TAP + Compose\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    you bring:                 you bring:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      - rootfs                   - one command\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      - kernel                   - one kernel\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      - initrd\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      - tap + NAT\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      - JSON config\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      - 40 lines of bash\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    beautiful.                 I am lazy and I like it.\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003ch2 id=\"kvm-到底是什么\" class=\"headerLink\"\u003e\n    \u003ca href=\"#kvm-%e5%88%b0%e5%ba%95%e6%98%af%e4%bb%80%e4%b9%88\" class=\"header-mark\" aria-label=\"Header mark for 'KVM 到底是什么'\"\u003e\u003c/a\u003e2 KVM 到底是什么\u003c/h2\u003e\u003cp\u003eKVM——Kernel-based Virtual Machine——是一个字符设备 \u003ccode\u003e/dev/kvm\u003c/code\u003e，它把硬件虚拟化以 ioctl 的形式暴露出来。就这么回事。打开一个文件，对它调 ioctl，于是一颗 CPU 就在硬件沙箱里替你开始运行代码。\u003c/p\u003e\n\u003cp\u003e我喜欢把 KVM 的 ioctl 形容为虚拟机的汇编语言。底层、正交，每一个都只干一件事，而你必须自己把它们组合成有用的东西。没有调度器，没有设备模型。只有一小套原语字母表——创建虚拟机、创建 vCPU、映射内存、运行、读寄存器、写寄存器——而把这些变成一台计算机的人是你。\u003c/p\u003e\n\u003cp\u003e每一个写过的 VMM 的核心都是一个循环。你在 vCPU 上调用 run。宿主线程阻塞。guest 运行。最终 guest 做了某件需要宿主注意的事——碰了一个 MMIO 寄存器、击中了一个 I/O 端口、收到一个中断、关机——于是 KVM 把控制权交还。这种返回叫 VMexit。其他一切——设备、引导加载器、快照引擎——都是这个循环周围的糖。\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-2\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            HOST                              GUEST\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    +---------------------+            +-------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    | ioctl(KVM_RUN)      | ---------\u0026gt; | guest instructions|\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |                     |            |                   |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |                     | \u0026lt;--------- | VMexit            |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    | dispatch(exit_reason)            | (MMIO / IO / IRQ) |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |   handle_mmio()     |            |                   |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |   handle_io()       |            |                   |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |   inject_irq()      | ---------\u0026gt; | resume            |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    +---------------------+            +-------------------+\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e整个与 KVM 的绑定就住在一个文件里，而那个核心数字恰好是五位十六进制：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-3\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// internal/kvm/kvm.go\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003ekvmRun\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"mh\"\u003e0xAE80\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e当用户态调用 \u003ccode\u003eioctl(vcpu_fd, 0xAE80, 0)\u003c/code\u003e 时，内核把控制权交给 guest 的 CPU。宿主线程阻塞。guest 运行。这就是每一个写过的 VMM 的核心。\u003c/p\u003e\n\u003ch2 id=\"每个-vcpu-一个-goroutine-就是整个模型\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e6%af%8f%e4%b8%aa-vcpu-%e4%b8%80%e4%b8%aa-goroutine-%e5%b0%b1%e6%98%af%e6%95%b4%e4%b8%aa%e6%a8%a1%e5%9e%8b\" class=\"header-mark\" aria-label=\"Header mark for '每个 vCPU 一个 goroutine 就是整个模型'\"\u003e\u003c/a\u003e3 每个 vCPU 一个 goroutine 就是整个模型\u003c/h2\u003e\u003cp\u003e这就是用 Go 写 VMM 的乐趣所在，也是让那个周末真正感觉像个周末的东西。一颗虚拟 CPU 是一个会阻塞直到 guest 退出的文件描述符。如果你想要两个 vCPU，你就要两个宿主线程。如果你想要十六个，你就要十六个线程。\u003c/p\u003e\n\u003cp\u003eGo 有一个词来描述\u0026quot;一个看起来像线程、会阻塞在系统调用上的东西\u0026quot;。烦人的脚注是：Go 的调度器通常会想什么时候就什么时候把 goroutine 在 OS 线程之间挪来挪去，而 KVM \u003cem\u003e真的\u003c/em\u003e不喜欢这样。修复办法是三个词：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-4\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eruntime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eLockOSThread\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e就这样。多进程处理的故事就是这样。每个 vCPU 拿到一个 goroutine。每个 goroutine 锁住自己的线程，跑那个退出循环。运行时负责其余的。不用写线程池。甚至连一个同步原语都不用——你工具箱里的 channel 就已经好使了。\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-5\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            per-vCPU goroutine (LockOSThread)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    +--------------------------------------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |                                                  |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |   for {                                          |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       err := ioctl(vcpu_fd, KVM_RUN, 0)          |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       if err == EINTR || err == EAGAIN {         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |           continue          // transient; resume |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       }                                          |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       switch run.exit_reason {                   |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       case IO:        handleIO(run.io)           |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       case MMIO:      handleMMIO(run.mmio)       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       case SHUTDOWN:  return                     |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       case INTR:      continue                   |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       case HLT:       waitForIRQ(); continue     |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       }                                          |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |   }                                              |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    +--------------------------------------------------+\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e当一个真正的 guest 启动时，这个循环每秒运行数百万次。大多数退出都很快——一次 virtio 队列通知、一次 UART 写、一次定时器滴答——循环在任何人察觉之前就返回到了 \u003ccode\u003eKVM_RUN\u003c/code\u003e 里。艺术在于让 switch 的每一个 case 都廉价。\u003c/p\u003e\n\u003cp\u003e到周末结束时，我有了一个绿色的提示符、一台运行中的虚拟机，以及一个低于 400 ms 的冷启动。我也会在笔记本贴纸上贴 Rust。但 Go 给了我 goroutine，让\u0026quot;每个 vCPU 一个线程\u0026quot;变成一个三词功能、一个大约两百行的 HTTP API 服务器、开箱即用的 JSON 配置，以及任何语言中最成熟的 OCI 库生态。交叉编译到 ARM64 只需两个环境变量。我和 Go 的搏斗是真实的，但有界；而那份便利在数月里复利累积。\u003c/p\u003e\n\u003ch2 id=\"接下来的三周\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e6%8e%a5%e4%b8%8b%e6%9d%a5%e7%9a%84%e4%b8%89%e5%91%a8\" class=\"header-mark\" aria-label=\"Header mark for '接下来的三周'\"\u003e\u003c/a\u003e4 接下来的三周\u003c/h2\u003e\u003cp\u003e虚拟机启动了。一条命令。生活很美好。然后，正是那个让第一个周末愉快的廉价-goroutine 特性，开始收利息了。\u003c/p\u003e\n\u003cp\u003e每个 bug 都以不同的症状开始，却在同一个地方走进死胡同：两个 goroutine 对谁拥有某块内核状态各执一词。jailer 总把鞋留在门口——bind mount 在崩溃的沙箱之后存活下来，毒害下一台虚拟机。一个对共享 vsock 文件描述符的裸 \u003ccode\u003eclose\u003c/code\u003e，结果是一次引用计数操作，而非一条协议消息;宿主永远阻塞，直到用户出于纯粹的绝望按下一个键，这唤醒了一个后台 goroutine，它放掉了最后一个引用，最终才发出关机数据包。清理过程中的一次 panic 让终端停在 raw 模式。Go 运行时的每个发布版都想多要一个 seccomp 过滤器不认识的系统调用，于是\u0026quot;Bad system call\u0026quot;成了发布周的主题曲。\u003c/p\u003e\n\u003cp\u003e十五个 goroutine 套着一件风衣，那也还是十五个 goroutine。教训与其说是关于某一个 bug，不如说是关于它们全体的形状：在 microVM 里，宿主内核保留着关于你虚拟机的状态，如果你不在顺利路径上清理它，没人会在不顺利路径上替你清理。在三天内的第五次竞态之后，结构性的修复不是\u0026quot;更小心点\u0026quot;——我本来就已经很小心了——而是在 CI 里打开 \u003ccode\u003e-race\u003c/code\u003e，并写一些故意让生产者和消费者赛跑的测试，让竞态条件可复现地暴露出来。竞态检测器是你能在一个 Go 项目上打开的最重要的一个设置。它慢十到一百倍，但每一个时钟周期都值。\u003c/p\u003e\n\u003cp\u003e当 CI 不再抖动后，我终于有了一台可信到足以拿来测量的虚拟机。\u003c/p\u003e\n\u003ch2 id=\"我盯错了盒子\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e6%88%91%e7%9b%af%e9%94%99%e4%ba%86%e7%9b%92%e5%ad%90\" class=\"header-mark\" aria-label=\"Header mark for '我盯错了盒子'\"\u003e\u003c/a\u003e5 我盯错了盒子\u003c/h2\u003e\u003cp\u003e有大约两周，我以为 gocracker 对比 Firecracker 有个 2× 的问题。我打印的那个 \u003ccode\u003eduration\u003c/code\u003e 字段，从不带 jailer 的 ~30 ms 涨到了带 jailer 的 ~55 ms。慢两倍。fork-exec 是反派。jailer 是反派。七个 REST PUT 是反派。\u003c/p\u003e\n\u003cp\u003e不过，看看\u003cem\u003e墙钟时间\u003c/em\u003e。大约 860 ms 对大约 880 ms。在一个 860 ms 的基线上差大约二十毫秒。那不是 2×;那大约是 2%，而 2% 是噪声。那个\u0026quot;2×\u0026ldquo;完全出在一个在两条代码路径上度量了不同东西的 \u003ccode\u003eduration\u003c/code\u003e 字段里。进程内路径度量的是原始的 \u003ccode\u003evmm.New\u003c/code\u003e。worker 路径度量的是 fork-exec、jailer 设置、chroot、七个 REST PUT，\u003cem\u003e外加\u003c/em\u003e \u003ccode\u003evmm.New\u003c/code\u003e。两者都在 guest 内核打印出哪怕一个字节之前就停了表。两个数字都没度量到\u0026quot;到达可用 guest\u0026quot;的时间。两者彼此也无法比较。\u003c/p\u003e\n\u003cp\u003e把度量拆成四个诚实的阶段——编排、VMM 设置、启动、guest 首次输出——让那个 2× 蒸发了：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-6\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// pkg/vmm/timings.go\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// BootTimings is the per-phase breakdown of how long it took to\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// bring a microVM to life.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//   - Orchestration:    host-side work *before* the guest kernel starts\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//   - VMMSetup:         time inside vmm.New() — KVM_CREATE_VM, memory...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//   - Start:            KVM_RUN starts on the vCPU goroutines\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//   - GuestFirstOutput: first byte the guest prints on the UART\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003ejailer 在编排上大约花了 30 ms，叠加在一个两条路径共享的 ~300 ms guest 内核启动之上。一个诚实的 ~10% 编排税，而非 2× 的惩罚。\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-7\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   before the breakdown                   after the breakdown\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   (one misleading number)            (four honest numbers)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   +-----------------------+         +--------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   | duration = ~55ms      |         | orchestration ~30ms|\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   | (in runViaWorker this |         | vmm_setup      ~8ms|\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   |  includes jailer,     |         | start          ~2ms|\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   |  fork, a few REST     |         | guest_first  ~320ms|\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   |  PUTs, then vmm.New,  |         | total        ~360ms|\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   |  then start)          |         +--------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   +-----------------------+            |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                        v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u0026#34;2x slower\u0026#34;                 \u0026#34;I was staring\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                     at the wrong box.\u0026#34;\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e然后又掉出来一件事：那些毫秒里大约有三百是 \u003cem\u003eLinux 在虚拟机里启动\u003c/em\u003e。如果我想要一台更快的虚拟机，我的代码不是问题。我的内核才是。\u003c/p\u003e\n\u003cp\u003e调试性能就像审计报销账单。你盯着一行行条目。你一直盯着，直到找到那行写着\u0026quot;商务午餐 480 美元\u0026quot;的，而那家餐厅原来是 Costco。坑从来不在你以为的地方。\u003c/p\u003e\n\u003ch2 id=\"内核才是问题\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e5%86%85%e6%a0%b8%e6%89%8d%e6%98%af%e9%97%ae%e9%a2%98\" class=\"header-mark\" aria-label=\"Header mark for '内核才是问题'\"\u003e\u003c/a\u003e6 内核才是问题\u003c/h2\u003e\u003cp\u003e我把 guest 内核分叉成两套配置：一套通用的，我默认发布;还有一套\u0026quot;最小化\u0026quot;的，它扯掉了一台只有 virtio、别无他物的虚拟机永远用不到的任何东西。ACPI NUMA 走了。休眠走了。整个 USB 子系统走了。电源管理、profiling、SCSI、loop 设备、XFS、NFS——全没了。virtio 留下了。ext4 留下了。kvm-clock 留下了。内核缩小了约 12%，而启动仅靠少跑些 initcall 就降了一截。\u003c/p\u003e\n\u003cp\u003e然后来了那个比其他任何改动都更重要的小改动。我给内核命令行加了一个参数：\u003ccode\u003eloglevel=4\u003c/code\u003e。它告诉内核\u0026quot;只把警告及以上打印到控制台;其余的仍然进 ring buffer，这样你可以通过 dmesg 看到。\u0026ldquo;大部分启动输出不再发往那个被模拟的 UART。\u003c/p\u003e\n\u003cp\u003e事实证明，一个虚拟化的 UART 按字节算是昂贵的。内核写入串口控制台的每一个字节都是一次进入用户态的 MMIO 退出，那是一次上下文切换，是几微秒被浪费的时间。乘以几千个启动期字节，启动就被\u003cem\u003e打印\u003c/em\u003e主导了。让控制台安静下来给启动减掉了大约 130 ms。\u003c/p\u003e\n\u003cp\u003e一行。\u003c/p\u003e\n\u003cp\u003e那些更小的胜利遵循同样的主题：别和内核搏斗，而是让它干自己的活。缓存一个其答案只取决于宿主文件系统、与 guest 无关的 discard 探测。让 x86 中断走 \u003ccode\u003eeventfd + IRQFD\u003c/code\u003e，而不是每次断言一个 ioctl，正如 ARM64 后端早就在做的那样。在短命的 VMM 子进程里关掉 Go 的垃圾回收器：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-8\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cmd/gocracker-vmm/main.go\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;runtime/debug\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003einit\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003edebug\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eSetGCPercent\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e// short-lived process; let the OS reap memory\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这个进程不需要 GC;它会高高兴兴地一直运行到结束，然后由 OS 回收它的内存。这里几毫秒，那里几毫秒。单看都不聪明。累加起来才有意义。\u003c/p\u003e\n\u003cp\u003e把这些胜利按真实测量的大致比例堆成一张柱状图：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-9\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e standard kernel:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   [==orch==][vmm][======guest_first_output: ~305ms======]   ~390ms\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ~70ms    ~15  ~305\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                              this is Linux booting.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e minimal kernel:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   [==orch==][vmm][=====guest_first_output: ~280ms=====]     ~365ms (-25)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                              fewer init calls.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e minimal + loglevel=4:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   [==orch==][vmm][guest_first_output: ~170ms]                ~250ms (-115)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                              80% of the cost\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                              was *printing*.\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这一切之后：冷启动落在 150–170 ms 区间。比 Firecracker 慢大约 45 ms，而此前慢得多得多。一个 Go-对-Rust 的差距，以毫秒计，而这个启动被一个我无法控制的外来 Linux 内核所主导。这是个该停下来的好地方。如果有人告诉你他们的 microVM 比 Firecracker 慢几十毫秒，你礼貌地点头;如果他们告诉你慢 2×，你就有问题要问了。\u003c/p\u003e\n\u003ch2 id=\"冷启动小了之后热路径就尴尬了\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e5%86%b7%e5%90%af%e5%8a%a8%e5%b0%8f%e4%ba%86%e4%b9%8b%e5%90%8e%e7%83%ad%e8%b7%af%e5%be%84%e5%b0%b1%e5%b0%b4%e5%b0%ac%e4%ba%86\" class=\"header-mark\" aria-label=\"Header mark for '冷启动小了之后，热路径就尴尬了'\"\u003e\u003c/a\u003e7 冷启动小了之后，热路径就尴尬了\u003c/h2\u003e\u003cp\u003e快照恢复曾经是那条快路径。在 400 ms 的冷启动之上做一次 80 ms 的恢复，是个舍入误差。在 170 ms 的冷启动之上做一次 80 ms 的恢复，是你预算的一半。\u003c/p\u003e\n\u003cp\u003e旧的恢复干的是显而易见的事：给 guest RAM 分配一块全新的 128 MiB 匿名 mmap，把整个快照文件读进一个 Go 字节切片，再把整块 memcpy 到位。第一步到第三步大约花 80 ms，如果你曾经在每个请求上 memcpy 过 128 MiB，这正是你会预料到的。\u003c/p\u003e\n\u003cp\u003e然后是那个问题：要是我干脆不拷贝呢？\u003c/p\u003e\n\u003cp\u003eLinux 有个标志叫 \u003ccode\u003eMAP_PRIVATE\u003c/code\u003e。当你用它 mmap 一个文件时，内核事先不做任何实际的 I/O。它建立一个页表项，意思是\u0026quot;如果用户态碰这个页，缺页进内核，从文件里读出来，映射进去。如果用户态\u003cem\u003e写\u003c/em\u003e这个页，缺页，写时复制到一个私有的匿名页，并把映射重定向到那个副本。\u0026ldquo;文件本身从不被修改。\u003c/p\u003e\n\u003cp\u003e那个 Netflix 类比是我一再回想的。Netflix 不会先把整部电影下载到你的设备上再开始播放。它立刻开始播放，并在你观看时逐分钟地获取。如果你快进略过一些部分，那些部分永远不会被下载。你为观看的分钟付费，而非为选定的电影付费。\u003ccode\u003eMAP_PRIVATE\u003c/code\u003e 就是 guest RAM 的这种模式。\u003c/p\u003e\n\u003cp\u003e新路径把快照直接 mmap 进 guest 的内存区域：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-10\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003emem\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003e_\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eunix\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eMmap\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ef\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eFd\u003c/span\u003e\u003cspan class=\"p\"\u003e()),\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ememSize\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eunix\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ePROT_READ\u003c/span\u003e\u003cspan class=\"p\"\u003e|\u003c/span\u003e\u003cspan class=\"nx\"\u003eunix\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ePROT_WRITE\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eunix\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eMAP_PRIVATE\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003e_\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eunix\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eMadvise\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003emem\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eunix\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eMADV_HUGEPAGE\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eguest 从不碰的页永远不会被加载。它读但不写的页保持与 page cache 共享。它写的页进入私有的 COW 副本，而快照文件保持干净。\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-11\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  BEFORE: eager copy\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +----------------+   +----------------+    +----------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |  mem.bin file  |--\u0026gt;|   os.ReadFile  |---\u0026gt;| copy(ram, mem) |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |    128 MiB     |   |  read 128 MiB  |    |  128 MiB memcpy|\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +----------------+   +----------------+    +----------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                      |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                      v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                           ~80ms before this point\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  AFTER: lazy mmap (MAP_PRIVATE)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +----------------+   +----------------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |  mem.bin file  |\u0026lt;--| mmap(fd, PRIVATE)          |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |    128 MiB     |   | sets up page table only    |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +----------------+   +----------------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                   |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                   v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                        guest touches page N\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                   |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                   v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                        minor fault (-\u0026gt; page cache)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                        kernel maps the page on the fly\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                   |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                   v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                         ~20ms to \u0026#34;running\u0026#34;\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e底层完整的缺页舞步看起来是这样的：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-12\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  guest vCPU                  host kernel (KVM + mm)        snapshot\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +---------+                                                +-------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  | read P  |---(EPT miss)---\u0026gt;| PTE not-present, PRIVATE     | on    |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |                 | -\u0026gt; minor fault                | disk  |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |                 | -\u0026gt; page cache lookup          |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |                 |    (or read from disk)    \u0026lt;---+       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |                 | -\u0026gt; install PTE readable       |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |\u0026lt;-----(resume)---|                               |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +---------+                                                 +-------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  later, guest writes page P:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +---------+                                                 +-------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  | write P |---(EPT miss)---\u0026gt;| PTE readable only             |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |                 | -\u0026gt; COW fault                  |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |                 | -\u0026gt; alloc anon page            |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |                 | -\u0026gt; copy from page cache       |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |                 | -\u0026gt; install PTE writable       |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |                 |    (snapshot unchanged!)      |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |\u0026lt;-----(resume)---|                               |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +---------+                                                 +-------+\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e那里的每一步都是 Linux 对任何文件支持的 mmap 早就在做的事。没有一行缺页处理需要去实现。只要别和内核搏斗，让它干自己的活就好。\u003c/p\u003e\n\u003cp\u003e在一个 128 MiB 的 Alpine 快照上，恢复从 ~80 ms 降到大约 20 ms。快照恢复一下子比冷启动快了好几倍。（一个重要的告诫：当虚拟机正基于某个快照文件运行时，别删那个文件。问我是怎么知道的。）\u003c/p\u003e\n\u003ch2 id=\"为虚拟机做-mise-en-place\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e4%b8%ba%e8%99%9a%e6%8b%9f%e6%9c%ba%e5%81%9a-mise-en-place\" class=\"header-mark\" aria-label=\"Header mark for '为虚拟机做 Mise en place'\"\u003e\u003c/a\u003e8 为虚拟机做 Mise en place\u003c/h2\u003e\u003cp\u003e午餐时间走进一家像样的餐厅。点 steak frites。它六分钟内就上桌了。光是那块牛排，往宽松了估，就是六分钟的烹制。薯条要十二分钟。荷兰酱要十五分钟。厨房是怎么在六分钟内做到的？\u003c/p\u003e\n\u003cp\u003eMise en place（备料就位）。土豆在你到达之前就已半熟并沥干。荷兰酱已乳化并保温待用。订单一打到出菜口，盘子就从保温柜里端了出来。厨房在\u003cem\u003e你下单之后\u003c/em\u003e唯一做的事，就是最后一道煎封。\u003c/p\u003e\n\u003cp\u003e一个 warm pool 就是为虚拟机做的 mise en place。公开基准测试上最快的竞争对手沙箱供应商大约在 100 ms——这正是你会预料到的：靠让一台虚拟机已经在运行、已暂停、等着有人说开始，从而彻底跳过恢复。如果领跑者靠预先备菜取胜，那就别再优化炉灶了。\u003c/p\u003e\n\u003cp\u003e这个 warm pool 变成了三个设计决策，每一个都是与一个假想的糟糕日子争论的结果。\u003c/p\u003e\n\u003cp\u003e第一，\u003ccode\u003eAcquire\u003c/code\u003e 是非阻塞的。pool API 的诱惑是让 \u003ccode\u003eAcquire\u003c/code\u003e 阻塞，直到有 worker 可用。那种\u0026quot;总是给用户一个 worker\u0026quot;感觉很安全。它不安全。如果池子空了，说明已经出了岔子，而让用户等一次全新的恢复，严格地说比落到那条本来就好使的冷启动路径更糟。池子是尽力而为的。一次未命中绝不能让用户比基线更慢。\u003c/p\u003e\n\u003cp\u003e第二，释放一个 worker 会\u003cem\u003e杀掉\u003c/em\u003e它。每个池化库最终都想把一个 worker 回收回池子里。在多租户的世界里，刚处理完一个请求的 worker，碰过上一个租户要求的任何东西。把它交给下一个租户是一个租户隔离的漏洞，而至今没人利用过它这个事实，并不是一个安全论据。每次 \u003ccode\u003eAcquire\u003c/code\u003e 返回的都是一个从未服务过请求的进程。补充在后台进行，所以下一个调用者什么都不用付。池子总在流动。从不复用。\u003c/p\u003e\n\u003cp\u003e第三，补充是异步的、有上限的、且竞态安全的。针对同一个模板的一阵补充请求不应踩踏成十个并行的 spawn;一个与关停赛跑的补充 spawn 应当自己清理干净;而时钟必须可注入，好让陈旧性测试是确定性的。这些都不聪明。它们只是那些等池子第一次在生产中运行时、你会后悔当初没加的不变量。\u003c/p\u003e\n\u003cp\u003e把缓存和池子都接好的完整流程：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-13\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  request arrives\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  warmcache.Lookup(key)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         +-- miss --\u0026gt; cold boot (~250ms)  \u0026lt;-- baseline\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  hit, snapshotDir=S\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  pool.Acquire(key, S)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         +-- empty --\u0026gt; restore_direct (~20ms)   \u0026lt;-- still better\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  got a warm worker\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  worker.Resume (~3ms)  \u0026lt;-- fastest path\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  serve request\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  pool.Release(w)  --\u0026gt; worker.Close()\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  EnsureRefill in background\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  spawn replacement (~20ms off the hot path)\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e池子的 API 表面有意做得很小：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-14\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// pkg/warmpool/pool.go\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003eWorker\u003c/span\u003e \u003cspan class=\"kd\"\u003einterface\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003eID\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003eClose\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"kt\"\u003eerror\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ep\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003ePool\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"nf\"\u003eAcquire\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ekey\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003esnapshotDir\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eWorker\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003ebool\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003eerror\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ep\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003ePool\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"nf\"\u003eRelease\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ew\u003c/span\u003e \u003cspan class=\"nx\"\u003eWorker\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ep\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003ePool\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"nf\"\u003eEnsureRefill\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ekey\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003esnapshotDir\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e在热路径上：那个热 worker 已经把 guest RAM 映射好、vCPU 状态加载好、虚拟机暂停好。\u003ccode\u003eAcquire\u003c/code\u003e 返回。一个单独的 resume ioctl 把它从暂停翻转到运行。三毫秒之后，guest 已经打过招呼了。Mise en place。\u003c/p\u003e\n\u003ch2 id=\"九个沙箱白白烧着一颗-cpu\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e4%b9%9d%e4%b8%aa%e6%b2%99%e7%ae%b1%e7%99%bd%e7%99%bd%e7%83%a7%e7%9d%80%e4%b8%80%e9%a2%97-cpu\" class=\"header-mark\" aria-label=\"Header mark for '九个沙箱白白烧着一颗 CPU'\"\u003e\u003c/a\u003e9 九个沙箱白白烧着一颗 CPU\u003c/h2\u003e\u003cp\u003e九个沙箱在运行，已暂停，空闲。没有流量。没有 exec 会话。没有 HTTP。坐在一个 warm pool 内的 shell 提示符上，等着有人让它们干活。\u003ccode\u003etop\u003c/code\u003e 显示宿主占着一颗核的 46%。\u003c/p\u003e\n\u003cp\u003e百分之四十六，就为了让九个空闲的 Linux guest 活着。大约每个空闲虚拟机占一颗核的百分之五。一台物理的空闲 Linux 机器在现代硬件上大约用一颗核的 0.1%。一个被恰当虚拟化的空闲 guest 应当\u003cem\u003e更便宜\u003c/em\u003e，而不是贵五十倍。\u003c/p\u003e\n\u003cp\u003e有什么东西大错特错了。\u003c/p\u003e\n\u003cp\u003e看清一个 vCPU 线程在干什么的干净办法是采样它。几秒钟的 \u003ccode\u003eperf\u003c/code\u003e 回了一个毫不含糊的栈轨迹：进入 KVM，几乎立刻退出，睡一毫秒，再进去。一遍又一遍，每秒一千次，在每个 vCPU 线程上，并行地。九个线程同时这么干，正好就是宿主报告的那颗核的 370%。\u003c/p\u003e\n\u003cp\u003e起因是一个对冲。在 vCPU 循环深处，\u003ccode\u003eHLT\u003c/code\u003e 退出被一个一毫秒的 sleep \u0026ldquo;处理\u0026quot;了：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-15\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003ecase\u003c/span\u003e \u003cspan class=\"nx\"\u003eKVM_EXIT_HLT\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e// Guest is idle. Don\u0026#39;t spin; give it a breather.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eSleep\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eMillisecond\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e在一个 VMM 于用户态拥有中断控制器的世界里，这个 sleep 是合理的。gocracker 不是那样。gocracker 用的是内核内 IRQCHIP——对几乎每一种工作负载来说都是正确的默认——在那里 KVM 本应把线程一直保持在 ioctl \u003cem\u003e内部\u003c/em\u003e，直到下一个中断触发，根本没有退出。那个 sleep 是死代码，它在一次没人质疑的设计变更中幸存了下来。\u003c/p\u003e\n\u003cp\u003e修复是一次删除：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-16\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003ecase\u003c/span\u003e \u003cspan class=\"nx\"\u003eKVM_EXIT_HLT\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e// No-op. In-kernel IRQCHIP already blocks the vCPU until the\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"c1\"\u003e// next interrupt. There is no productive work for userspace here.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e在下一次循环迭代里，代码再次调入 KVM，而 KVM——因为它拥有 IRQCHIP 并且知道没有中断将至——会把线程阻塞在内核内，只要 guest 保持空闲就一直阻塞。\u003c/p\u003e\n\u003cp\u003e同样的九空闲沙箱测试。\u003ccode\u003etop\u003c/code\u003e：7%。不是每台虚拟机百分之七。是整个机群百分之七。从 ~370% 到 ~7%，靠删掉一行。少了五十倍。\u003c/p\u003e\n\u003cp\u003e这个普遍的模式值得起个名字。你\u0026quot;只为安全起见\u0026quot;加的代码，往往是最值得删掉的代码，因为没人质疑它。一个系统里你与之搏斗的部分，会被评审到死。没人抱怨的部分，得以安然腐烂。当腐烂最终让你付出代价时，它让你付出的，是你曾经想过的任何东西的五十倍。\u003c/p\u003e\n\u003ch2 id=\"一台快的-microvm-是一套工具箱不是一个产品\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e4%b8%80%e5%8f%b0%e5%bf%ab%e7%9a%84-microvm-%e6%98%af%e4%b8%80%e5%a5%97%e5%b7%a5%e5%85%b7%e7%ae%b1%e4%b8%8d%e6%98%af%e4%b8%80%e4%b8%aa%e4%ba%a7%e5%93%81\" class=\"header-mark\" aria-label=\"Header mark for '一台快的 microVM 是一套工具箱，不是一个产品'\"\u003e\u003c/a\u003e10 一台快的 microVM 是一套工具箱，不是一个产品\u003c/h2\u003e\u003cp\u003e当从 \u003ccode\u003eAcquire\u003c/code\u003e 到 guest 第一条指令变成三毫秒时，我为此自豪了大约一周。然后我试着用它\u003cem\u003e构建\u003c/em\u003e点什么。\u003c/p\u003e\n\u003cp\u003e我想要的是如今人人都想要的东西：一个 REST API，客户说\u0026quot;给我一个带 \u003ccode\u003enumpy\u003c/code\u003e 和 \u003ccode\u003epandas\u003c/code\u003e 的 Python 3.12 沙箱，让我在里面跑代码\u0026rdquo;，一个沙箱出现了，三秒后他拿回一份 stdout，然后接着过日子。一个裸的 \u003ccode\u003egocracker run\u003c/code\u003e 做不到这些中的任何一件。它启动一台虚拟机。仅此而已。如果说 microVM 是一个发动机缸体，那我需要的是这辆车的其余部分。\u003c/p\u003e\n\u003cp\u003e第一个决策是最重要的：让 gocracker 严格保持它原本的样子，而把那个托管层构建成一个独立的东西。gocracker 仍是底层 VMM、快照缓存，以及那个 worker 的 warm pool。它说的是字节和 ioctl。它对客户或模板没有任何意见。\u003cem\u003esandboxd\u003c/em\u003e 是一个坐在它之上的新守护进程，它拥有模板、租约、池子和预览令牌。SDK 只和 sandboxd 说话。sandboxd 只通过一个 unix socket 和 gocracker 说话。那个额外的往返是一个特性，不是一个缺陷。\u003c/p\u003e\n\u003cp\u003e我学到这个拆分的价值，靠的是先\u003cem\u003e不\u003c/em\u003e把它拆干净，然后花三个小时调试一个竞态条件，它之所以存在，纯粹是因为两层在共享一个它们根本没理由共享的指针。跨越一个进程边界逼你去协商。共享一个指针让你能作弊。边界就是安全带。\u003c/p\u003e\n\u003cp\u003e这个拆分是一个干净的三层流程：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-17\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eSDK\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ePython\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e \u003cspan class=\"n\"\u003eGo\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e \u003cspan class=\"n\"\u003eTS\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"o\"\u003e|\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"o\"\u003e|\u003c/span\u003e  \u003cspan class=\"n\"\u003eHTTP\u003c/span\u003e \u003cspan class=\"n\"\u003eover\u003c/span\u003e \u003cspan class=\"n\"\u003eunix\u003c/span\u003e \u003cspan class=\"n\"\u003esocket\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003ev\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003esandboxd\u003c/span\u003e            \u003cspan class=\"o\"\u003e\u0026lt;--\u003c/span\u003e \u003cspan class=\"n\"\u003emanaged\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003eruntime\u003c/span\u003e \u003cspan class=\"n\"\u003edaemon\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"o\"\u003e|\u003c/span\u003e             \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etemplates\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eleases\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003epools\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003epreview\u003c/span\u003e \u003cspan class=\"n\"\u003etokens\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"o\"\u003e|\u003c/span\u003e  \u003cspan class=\"n\"\u003eHTTP\u003c/span\u003e \u003cspan class=\"n\"\u003eover\u003c/span\u003e \u003cspan class=\"n\"\u003eunix\u003c/span\u003e \u003cspan class=\"n\"\u003esocket\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003ev\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003egocracker\u003c/span\u003e \u003cspan class=\"n\"\u003eserve\u003c/span\u003e     \u003cspan class=\"o\"\u003e\u0026lt;--\u003c/span\u003e \u003cspan class=\"n\"\u003elow\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003elevel\u003c/span\u003e \u003cspan class=\"n\"\u003eVMM\u003c/span\u003e \u003cspan class=\"n\"\u003eorchestrator\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"o\"\u003e|\u003c/span\u003e             \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eKVM\u003c/span\u003e \u003cspan class=\"n\"\u003eioctls\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003esnapshots\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ewarm\u003c/span\u003e \u003cspan class=\"n\"\u003epool\u003c/span\u003e \u003cspan class=\"n\"\u003eof\u003c/span\u003e \u003cspan class=\"n\"\u003eworkers\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"o\"\u003e|\u003c/span\u003e  \u003cspan class=\"n\"\u003eKVM\u003c/span\u003e \u003cspan class=\"n\"\u003eioctls\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003evsock\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003ev\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eguest\u003c/span\u003e \u003cspan class=\"n\"\u003eVM\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"o\"\u003e|\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"o\"\u003e+--\u003c/span\u003e \u003cspan class=\"n\"\u003etoolbox\u003c/span\u003e \u003cspan class=\"n\"\u003eagent\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003elistens\u003c/span\u003e \u003cspan class=\"n\"\u003eon\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e \u003cspan class=\"n\"\u003evsock\u003c/span\u003e \u003cspan class=\"n\"\u003eport\u003c/span\u003e \u003cspan class=\"n\"\u003einside\u003c/span\u003e \u003cspan class=\"n\"\u003ethe\u003c/span\u003e \u003cspan class=\"n\"\u003eguest\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e三跳。两个守护进程。SDK 从不直接和 gocracker 说话——它根本不知道 gocracker 的存在。sandboxd 是唯一的公开 API 表面;下游的一切都是实现细节。在一个 unix socket 上跨越一个进程边界很廉价（小 JSON 负载是亚毫秒级），而这种可分离性在你第一次想要重启 sandboxd 却不杀掉上百台活着的虚拟机时，就把自己的成本赚回来了。\u003c/p\u003e\n\u003cp\u003e模板是另一个承重的理念。一个客户想要的不是\u0026quot;一台 Linux 虚拟机\u0026rdquo;。他想要的是他给自己的 AI agent 用的那个环境——一个特定的基础镜像、一些 apt 包、一些 pip 包、一个工作目录、一些环境变量。一个模板捕获这个组合，\u003cem\u003e外加\u003c/em\u003e把这份规格启动一次、让它达到稳态后所产生的那个快照。两个规格相同的模板共享一个快照。用相同规格再做一次 create 是一个 no-op。\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-18\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003eTemplate\u003c/span\u003e \u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eID\u003c/span\u003e           \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eName\u003c/span\u003e         \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eSnapshotDir\u003c/span\u003e  \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eSpecHash\u003c/span\u003e     \u003cspan class=\"kt\"\u003estring\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// canonical fingerprint of image, kernel, mem, env...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"nx\"\u003eContextHash\u003c/span\u003e  \u003cspan class=\"kt\"\u003estring\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// build-context tarball when using a Dockerfile\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"nx\"\u003eWarmPolicy\u003c/span\u003e   \u003cspan class=\"nx\"\u003eWarmPolicy\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e这听起来显而易见，直到你想象一个真实 SaaS 的生命周期：大多数模板 create 是幂等重试。一次部署重跑。一个 CI job 重新提交。一个 SDK 在 create 之前惰性地确保-存在。如果这些里的每一个都要花一次全新的 \u003ccode\u003edocker build\u003c/code\u003e，你就会在每月 400 美元的基础设施上交付一个每月 40 美元的产品。每一层的内容寻址身份会复利累积：warm 缓存是内容寻址的，模板在它之上是内容寻址的，而沙箱之所以廉价，是因为模板廉价。\u003c/p\u003e\n\u003ch2 id=\"五个热就绪一个都没有\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e4%ba%94%e4%b8%aa%e7%83%ad%e5%b0%b1%e7%bb%aa%e4%b8%80%e4%b8%aa%e9%83%bd%e6%b2%a1%e6%9c%89\" class=\"header-mark\" aria-label=\"Header mark for '五个热就绪。一个都没有。'\"\u003e\u003c/a\u003e11 五个热就绪。一个都没有。\u003c/h2\u003e\u003cp\u003e我在对一个刚重建好的 sandboxd 跑负载测试。没什么花哨的——创建一个沙箱、exec \u003ccode\u003eecho hi\u003c/code\u003e、删除沙箱，在一个紧凑的循环里。池子为单个模板配置了三个 hot-ready 和三个 paused-ready。每次 create 都应当基本上是瞬时的，因为池子应当保持六个预热的沙箱活着，而我每次只需要一个。\u003c/p\u003e\n\u003cp\u003e它工作了大约九十秒。\u003c/p\u003e\n\u003cp\u003e然后每次 create 都开始失败。不是慢慢地。不是带着背压。每一个，带着\u0026quot;runtime returned 404: unknown vm\u0026quot;的各种变体。池子状态端点报告三个 hot-ready、两个 paused-ready、零个 leased。一个完美健康的池子，按它自己的说法。那些虚拟机已经死了好几分钟了。\u003c/p\u003e\n\u003cp\u003e那是个有趣的周二。\u003c/p\u003e\n\u003cp\u003ereconciler 的第一个版本信任它自己的内存内记录。它数标记为 \u003ccode\u003ewarm_ready\u003c/code\u003e 的条目，把数目和 \u003ccode\u003eMinHot\u003c/code\u003e 比较，然后得出结论：健康，无需行动。reconciler 里没有任何东西在\u003cem\u003e看\u003c/em\u003e。一台 warm-ready 虚拟机悄无声息地死了——vCPU panic、OOM-kill、guest 卡死、fstab 打错字把 systemd 扔进救援模式，随便哪种——而 sandboxd 继续把它数成活的。随后的租约在 attach 时带着 404 失败，租约处理器把那个条目标记为\u0026quot;broken\u0026quot;并落到冷启动，但那些坏掉的条目作为 \u003ccode\u003ewarm_leased\u003c/code\u003e 滞留在内存里，直到一个独立的清理 goroutine 把它们回收掉。与此同时，池子继续宣称五个热，reconciler 继续不做任何决策，而每一个请求都在冷启动。\u003c/p\u003e\n\u003cp\u003e这场连锁反应并不壮观。没有火警。没有 pager。系统正悄无声息地把自己降级到最坏情况模式，一次一个 404，同时尽职地报告绿色。\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-19\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  sandboxd\u0026#39;s view              actual runtime state\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +-----------------+          +----------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  | warm_ready: 3   |          | VM #1: dead          |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  | warm_ready: 2   |          | VM #2: dead          |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  | leased:     0   |          | VM #3: alive but oom |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  | total:      5   |          | VM #4: missing       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +-----------------+          | VM #5: missing       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         ^                     +----------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |                                  |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         | \u0026#34;healthy, no action\u0026#34;             | lease attempt -\u0026gt; 404\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |                                  |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |                                  v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   reconciler tick               lease handler marks broken,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   counts in-memory state        falls through to cold boot\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   compares with MinHot                     |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   does nothing                             v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                 user sees 2-second cold boot\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                 every single request\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e悄无声息的降级是最坏的模式。响亮的失败让你能为它设报警。无声的失败意味着图表看起来是绿的，而客户正在离开。\u003c/p\u003e\n\u003cp\u003e修复是结构性的，而且很小。reconciler 现在按顺序做三件事，而这个顺序是承重的：\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-20\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003eManager\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"nf\"\u003ereconcileTemplate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003etpl\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003eTemplate\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ereapDead\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003etpl\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e              \u003cspan class=\"c1\"\u003e// probe runtime, drop ghosts\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"nx\"\u003einv\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003einventoryFor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003etpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eID\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e// count from honest state\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003epruneExcess\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003etpl\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003einv\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ereplenishUpToMin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003etpl\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003einv\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e第一，探测 manager 认为自己拥有的每一个热沙箱，回收掉运行时不再认识的任何东西——\u0026ldquo;无法确定\u0026quot;算作死亡，因为一个由也许-活着的虚拟机组成的池子，比一个有窟窿的池子更糟。第二，从如今诚实的库存里计数。第三，修剪多余的并补充到最小值。修复之前：五个幽灵沙箱，每个请求一次冷启动，池子欢快地报告健康。之后：又是瞬时的 create 了。\u003c/p\u003e\n\u003cp\u003e我以前撞过这个一模一样的 bug。我猜你也撞过。每次它都穿着略有不同的行头——一个信任缓存的 pod 状态而非 kubelet 的 Kubernetes 控制器、一个因为最后一次响应是 200 就把后端标记为健康的连接池（而那个 socket 三十秒前就已经被 FIN 掉了）、一个心跳线程与工作线程毫无关系的服务注册表（于是服务可以死锁却还在 ping）、一个把 DNS 记录缓存到超出现实的浏览器。底层的错误每次都一样：信任一个关于世界的内存内表示，跨越一个进程边界，却不去探测。内存内状态和进程外的现实总会漂移。问题不是你会不会注意到;而是\u003cem\u003e什么时候\u003c/em\u003e，以及在这中间积累了多少用户可见的损害。\u003c/p\u003e\n\u003cp\u003e大约在同一时间又加了两道护栏。一个按模板的退避，好让一个单独损坏的模板——比如，一个快照微妙地损坏了的——不至于单枪匹马地把 reconciler 钉死在每个 tick 都 spawn 失败的虚拟机上，从而饿死那些更健康的模板。还有一个跨整个宿主的全局在途 spawn 工作预算，因为十个模板各自想同时补充三台虚拟机就是三十个并行 spawn，这足以让每个 spawn 都比它需要的更慢，这让超时更紧，这又会级联。按模板的上限不够。能在 N 个模板间同时出错的事情的数量，增长得比按模板的上限约束它的速度更快。\u003c/p\u003e\n\u003ch2 id=\"这套地基教会了我什么\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e8%bf%99%e5%a5%97%e5%9c%b0%e5%9f%ba%e6%95%99%e4%bc%9a%e4%ba%86%e6%88%91%e4%bb%80%e4%b9%88\" class=\"header-mark\" aria-label=\"Header mark for '这套地基教会了我什么'\"\u003e\u003c/a\u003e12 这套地基教会了我什么\u003c/h2\u003e\u003cp\u003e回望整段弧线，有几件事突出到值得带着往前走。\u003c/p\u003e\n\u003cp\u003e宿主内核状态比你的进程活得更久。在启动时和关停时一样要清理它。\u003ccode\u003eclose(fd)\u003c/code\u003e 是一次引用计数操作，不是一条协议消息——如果你需要对端知道你走了，你就得真的说出来。每条退出路径都需要一个终端复原，因为 defer 是一个会被信号和 seccomp 触发所忽略的建议。CI 里的竞态检测器，对任何在 goroutine 间持有状态的 Go 项目来说，是不容商量的。\u003c/p\u003e\n\u003cp\u003e你最大的成本几乎肯定不是你写的那个东西。Linux 在虚拟机里启动占了一个四百毫秒冷启动的四分之三。在我去把它缩小之前，我写的任何东西都无关紧要。一个虚拟化的 UART 按字节算昂贵;让内核日志在控制台路径上安静下来，是这个项目里最大的单项性能胜利。\u003ccode\u003eMAP_PRIVATE\u003c/code\u003e 对快照恢复是白捡的钱。Go 垃圾回收器是一笔税，在短命的子进程里你可以选择不交。\u003c/p\u003e\n\u003cp\u003e信任内核胜过信任你的直觉。内核内 IRQCHIP 早已解决了空闲 vCPU 的驻泊。在它之上那个防御性的 sleep 是负功。防御性代码是一个测谎仪，专测那些此后已经改变了的假设：当底层系统变动时，去重新审视那些对冲。而有时最大的胜利是一次删除。\u003c/p\u003e\n\u003cp\u003e一旦一个 warm pool 上升到一台 microVM 之上，规则就变了。池子是尽力而为的——一次未命中绝不能让用户比基线更慢。在释放时杀掉 worker;绝不把一个碰过另一个租户数据的进程给某个租户。reconciler 循环必须先观察后行动，因为唯一比一个错误的缓存更危险的，是一个系统已经停止质疑的缓存。而在一个池子里，无法确定永远算死亡——把一个也许-活着的沙箱当作死亡的代价是一次冷启动;把一个死的当作活的代价，是你的客户看到的那次租约失败。\u003c/p\u003e\n\u003cp\u003e这些胜利里没有一个是单独看就聪明的。每一个都是别人多年前就想明白了的东西——mmap、写时复制、按租户隔离、eventfd 加 IRQFD、作为一个概念的 mise en place、信任内核内 IRQCHIP 调度器。这里没有任何发明。发生的事情是，停止和它们每一个搏斗，一次一个。这就是 ~3 ms 的用户可见冷启动是怎么发生的。你一层一层地把它挣来。没有单一的英雄式改动。有的是一摞小而诚实的改动，每一个都让下一个写起来更便宜。\u003c/p\u003e\n\u003cp\u003e这台机器有趣的部分已经做完了。剩下的，是让它们保持诚实。\u003c/p\u003e\n",
        "language": "zh-cn"
    },
    {
        "title" : "Memoirs：教会智能体记忆（而不必在云端失去理智）",
        "date_published" : "2026-05-12T00:00:00Z",
        "date_modified" : "2026-05-12T00:00:00Z",
        "id" : "https://misael.org/zh-cn/memoirs-local-first-memory/",
        "url" : "https://misael.org/zh-cn/memoirs-local-first-memory/",
        "summary": "为什么拼接聊天记录无法扩展，以及我如何在 SQLite 之上构建了一个本地优先的概率性长期记忆系统。",
        "content_html" : "\u003ch2 id=\"千-token-的失忆症\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e5%8d%83-token-%e7%9a%84%e5%a4%b1%e5%bf%86%e7%97%87\" class=\"header-mark\" aria-label=\"Header mark for '千 Token 的失忆症'\"\u003e\u003c/a\u003e1 千 Token 的失忆症\u003c/h2\u003e\u003cp\u003e我用过的每一个对话式智能体——Claude、Cursor、各种 CLI 循环——都有着同样一个悄无声息的缺陷。它们醒来时对昨天毫无记忆。上下文窗口已经变得巨大，但它们在每个新会话中仍然从一张白纸开始。如果你试图通过保持一个持续的会话线程来绕过这一点，最终会得到更糟糕的东西：一个在每一轮都把整个聊天历史倾倒进上下文窗口的架构怪物。你付出了 40K token 的推理成本，而智能体仍然把最重要的事实埋在中间某处——看不见，被忽略。这就是 \u003cem\u003eLost in the Middle\u003c/em\u003e（迷失在中间）效应。它既昂贵，又根本不起作用。\u003c/p\u003e\n\u003cp\u003e针对这个问题有企业级方案。沉重的方案。云托管的向量数据库，在好的日子里网络往返时间以秒计。但我可不打算把我的架构笔记、我半遮半掩的凭据和我个人的约定发送给某个我无法掌控的外部 API。我想要一个\u003cem\u003e本地优先\u003c/em\u003e的东西。从构造上就是私密的。一个真正理解我的环境变量、我的框架选择、我的风格——并把这些知识保留在身边的东西。\u003c/p\u003e\n\u003cp\u003e这就是后来的 \u003ca href=\"https://github.com/misaelzapata/memoirs\" target=\"_blank\" rel=\"noopener noreferrer\"\u003ememoirs\u003c/a\u003e。不是一个 RAG 封装层。那会太容易，也太无聊。我想要一个真正的记忆系统——一个能有机地遗忘并巩固真正重要内容的系统。\u003c/p\u003e\n\u003ch2 id=\"底层解剖sqlite-之上的-6-层\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e5%ba%95%e5%b1%82%e8%a7%a3%e5%89%96sqlite-%e4%b9%8b%e4%b8%8a%e7%9a%84-6-%e5%b1%82\" class=\"header-mark\" aria-label=\"Header mark for '底层解剖：SQLite 之上的 6 层'\"\u003e\u003c/a\u003e2 底层解剖：SQLite 之上的 6 层\u003c/h2\u003e\u003cp\u003e我把持久化层构建在大多数人低估的东西之上：SQLite。不只是一个扁平的 \u003ccode\u003e.db\u003c/code\u003e 文件。而是一个被强力加持的 SQLite 实例，搭配 \u003ccode\u003esqlite-vec\u003c/code\u003e 用于稠密向量搜索，以及原生的 \u003ccode\u003eFTS5\u003c/code\u003e 用于带 BM25 评分的倒排索引词法搜索。\u003c/p\u003e\n\u003cp\u003e架构最终落定为六层，因为记忆系统不是一个存储盒——它是一座水处理厂：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e原始日志\u003c/strong\u003e：进水管。接收一切——完整的对话历史、嘈杂的 diff、原始输出。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e提取\u003c/strong\u003e：一个在本地运行的小型策展 LLM（目前是 Qwen 2.5 3B——快到察觉不出来），从噪声中过滤出信号：启发式规则、凭据、偏好、风格标记。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e图谱\u003c/strong\u003e：连接组织。记忆使用 Zettelkasten 风格的原则相互链接，在概念和会话之间寻找共享的语义节点。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e双重索引\u003c/strong\u003e：对 sqlite-vec 和 FTS5 进行原子化的并行写入。两个索引保持同步。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e记忆引擎\u003c/strong\u003e：策展层。记忆随时间获得和失去分数，具备完整的双时态支持——你可以查询系统在过去任意时刻所相信的内容。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e表层\u003c/strong\u003e：暴露层。HTTP + REST，更重要的是一个 22 端点的 MCP 服务器，它让任何智能体都能干净地查询系统，而无需了解其内部任何细节。\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"rrf-与-bm25向量并不能解决一切\" class=\"headerLink\"\u003e\n    \u003ca href=\"#rrf-%e4%b8%8e-bm25%e5%90%91%e9%87%8f%e5%b9%b6%e4%b8%8d%e8%83%bd%e8%a7%a3%e5%86%b3%e4%b8%80%e5%88%87\" class=\"header-mark\" aria-label=\"Header mark for 'RRF 与 BM25：向量并不能解决一切'\"\u003e\u003c/a\u003e3 RRF 与 BM25：向量并不能解决一切\u003c/h2\u003e\u003cp\u003e早期，我撞上了一堵墙。\u003ccode\u003esentence-transformers\u003c/code\u003e 的嵌入以一种非常特定的方式失败：精确标识符召回。让 \u003ccode\u003ememoirs\u003c/code\u003e 检索一个特定的工具名或一个带版本号的依赖字符串，它返回的却是语义相邻的噪声。向量空间在意义上很出色。它们知道\u0026quot;汽车\u0026quot;接近\u0026quot;车辆\u0026quot;。但当一条记忆包含 \u003cem\u003e\u0026ldquo;旧系统使用 psql-driver-v9\u0026rdquo;\u003c/em\u003e 时，嵌入并不会给这个精确字符串任何特殊权重。对于一个试图重建某个决策的智能体来说，这是一个致命的疏漏。\u003c/p\u003e\n\u003cp\u003e解决方案是把一个新东西与一个非常古老的东西结合起来：Reciprocal Rank Fusion（倒数排名融合，RRF）。我们并行运行两种搜索——一个通过 sqlite-vec 的稠密语义查询，以及一个通过带 BM25 评分的 FTS5 的精确匹配倒排索引查询。如果 BM25 精确找到了字符串，而语义搜索理解了周围的上下文，RRF 就会稳定分数，把正确的结果稳稳地推到第一位。结果是：p50 检索延迟降到了约 3.9 毫秒。在一个本地 SQLite 数据库上。这远远胜过大多数云向量存储。\u003c/p\u003e\n\u003ch2 id=\"艾宾浩斯遗忘曲线与异步睡眠\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e8%89%be%e5%ae%be%e6%b5%a9%e6%96%af%e9%81%97%e5%bf%98%e6%9b%b2%e7%ba%bf%e4%b8%8e%e5%bc%82%e6%ad%a5%e7%9d%a1%e7%9c%a0\" class=\"header-mark\" aria-label=\"Header mark for '艾宾浩斯、遗忘曲线与异步睡眠'\"\u003e\u003c/a\u003e4 艾宾浩斯、遗忘曲线与异步睡眠\u003c/h2\u003e\u003cp\u003e如果一个系统不能遗忘，它就没有长期记忆。智能体有一种淹没在陈旧知识中的倾向。\u0026ldquo;昨天我在和 Docker API 较劲\u0026quot;紧接着就是\u0026quot;今天我们彻底放弃了 Docker，改用裸 OCI 调用。\u0026ldquo;如果记忆引擎以同等置信度呈现这两个事实，智能体就会做出错误的决定。每一次都是。\u003c/p\u003e\n\u003cp\u003e我翻回一本旧笔记本，实现了基于赫尔曼·艾宾浩斯遗忘曲线的衰减函数：$R(t) = e^{-\\Delta t_h / (S \\cdot 24)}$。新记忆随时间渐近地失去权重。但它们的强度分数——它们原始的信号质量——会在系统每次检索并积极确认那条记忆时被乘大。核心架构决策和深层偏好的衰减，比一次深夜会话中的调试战记要慢得多。\u003c/p\u003e\n\u003cp\u003e这所需的计算——巩固、语义冲突消解、图谱压缩——太昂贵了，无法实时运行。所以我求助于一个人类时刻在用、而软件几乎从不使用的机制：\u003cem\u003e睡眠\u003c/em\u003e。\u003c/p\u003e\n\u003cp\u003e我写了一个守护进程（\u003ccode\u003esleep_consolidation.py\u003c/code\u003e），它监视 CPU 的低谷。它只在开发者和智能体都离开时运行——也就是锁空闲且系统空闲已超过 N 分钟的那些空闲窗口。在那个盲目的异步窗口里，本地的 Qwen 或 Phi 模型醒来，审查最近存储的画像，搜寻记忆版本之间的矛盾，并巩固或归档那些陈旧的部分。图谱会在下一个会话开始之前，在后台悄悄地被压缩。\u003c/p\u003e\n\u003cp\u003e在基准测试周期结束时，在像 LoCoMo 这样的记忆检索基准上取得强劲的 MRR 分数——而 RAM 开销仅为 231 MB，相比之下 LlamaIndex 或 Mem0 所消耗的要多得多——验证了这个假设。最好的智能体功能不是靠把数百万 token 发送给远程提供商而构建的。它们是靠深入理解基础索引、并在你自己的机器上强加无情的资源纪律而构建的。\u003c/p\u003e\n",
        "language": "zh-cn"
    },
    {
        "title" : "node-vmm：感觉像创建进程一样简单的 VM 隔离",
        "date_published" : "2026-05-05T00:00:00Z",
        "date_modified" : "2026-05-05T00:00:00Z",
        "id" : "https://misael.org/zh-cn/node-vmm-instant-vms/",
        "url" : "https://misael.org/zh-cn/node-vmm-instant-vms/",
        "summary": "1 关于容器的那个令人安心的谎言现代开发中有一个大家都默许的体面假象，因为它很方便：我们假装 Docker 容器既快又轻量。如果你的参照系是 2005 年裸机的配置过",
        "content_html" : "\u003ch2 id=\"关于容器的那个令人安心的谎言\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e5%85%b3%e4%ba%8e%e5%ae%b9%e5%99%a8%e7%9a%84%e9%82%a3%e4%b8%aa%e4%bb%a4%e4%ba%ba%e5%ae%89%e5%bf%83%e7%9a%84%e8%b0%8e%e8%a8%80\" class=\"header-mark\" aria-label=\"Header mark for '关于容器的那个令人安心的谎言'\"\u003e\u003c/a\u003e1 关于容器的那个令人安心的谎言\u003c/h2\u003e\u003cp\u003e现代开发中有一个大家都默许的体面假象，因为它很方便：我们假装 Docker 容器既快又轻量。如果你的参照系是 2005 年裸机的配置过程，那确实如此。但随着我的工具需要越来越频繁——而且越来越动态地——隔离工作负载，我开始感到一种拖累：在我和我真正想要的东西之间，横亘着一个庞大的外部引擎。\u003c/p\u003e\n\u003cp\u003e我想要的是某种在人体工程学上\u003cem\u003e感觉\u003c/em\u003e像在 Node.js 中调用 \u003ccode\u003echild_process.spawn()\u003c/code\u003e 的东西，但它能提供一台完整虚拟机的硬件级隔离。不是共享命名空间的容器。我想要一个真正的内核——或者至少是一个微内核——在隔离环境中运行。最重要的是，我希望暂停和恢复足够快，快到 VM 内部的 HTTP API 能够响应客户端，并让人感觉这只是普通的网络延迟。没有\u0026quot;解冻\u0026quot;的延迟。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/misaelzapata/node-vmm\" target=\"_blank\" rel=\"noopener noreferrer\"\u003enode-vmm\u003c/a\u003e 就是这么来的。而正如这类事情通常的走向一样，这个想法一直很简单，直到我一头撞上了原生 hypervisor 的现实。\u003c/p\u003e\n\u003ch2 id=\"用艰难的方式绕过-qemu\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e7%94%a8%e8%89%b0%e9%9a%be%e7%9a%84%e6%96%b9%e5%bc%8f%e7%bb%95%e8%bf%87-qemu\" class=\"header-mark\" aria-label=\"Header mark for '用艰难的方式绕过 QEMU'\"\u003e\u003c/a\u003e2 用艰难的方式绕过 QEMU\u003c/h2\u003e\u003cp\u003e几乎每一个需要跨平台虚拟化的项目都会本能地求助于 QEMU。它就是瑞士军刀。但 QEMU \u003cem\u003e很庞大\u003c/em\u003e。从 Node 中封装 QEMU 本可以在一周内解决跨平台问题——却会彻底葬送延迟和轻量的目标。\u003c/p\u003e\n\u003cp\u003e所以我换了一种做法：直接与每个操作系统的原生 hypervisor API 对话，用 C++ 通过 N-API 绑定到 Node。结果是一套被拆分为三个完全独立世界的架构，它们在最底层不共享任何代码：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eLinux\u003c/strong\u003e：直接对 KVM 发起 \u003ccode\u003eioctl\u003c/code\u003e 调用。一个带着某种粗犷优雅的文件——\u003ccode\u003enative/kvm/backend.cc\u003c/code\u003e。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eWindows\u003c/strong\u003e：Windows Hypervisor Platform（WHP）。这一个是真的折磨人。WHP 丢给你一个赤裸的虚拟 CPU，然后说祝你好运自己去组装主板，于是我不得不从零开始模拟 APIC、定时器和 UART 端口。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003emacOS / Apple Silicon\u003c/strong\u003e：Hypervisor.framework（HVF）。熬了好几个深夜之后，我意识到最干净的路径不是假装成 x86，而是使用 ARM64 机器配置（基于 virt）来保持一切快速且原生。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e把这三者隐藏在 TypeScript 中一个单一的 \u003ccode\u003einterface NativeRunConfig\u003c/code\u003e 之后并不简单。Virtio 的 MMIO 中断布局是事情变得棘手的地方。在 KVM 上，设备的内存步长很干净——0x1000。在 Windows 上，我不得不把它们更紧凑地塞在 0x200，以避免与 ACPI 表重叠。最终这个抽象层稳住了，任何引入这个库的开发者永远都不必去想这些。\u003c/p\u003e\n\u003ch2 id=\"解雇中间人没有-docker-engine-的-oci\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e8%a7%a3%e9%9b%87%e4%b8%ad%e9%97%b4%e4%ba%ba%e6%b2%a1%e6%9c%89-docker-engine-%e7%9a%84-oci\" class=\"header-mark\" aria-label=\"Header mark for '解雇中间人：没有 Docker Engine 的 OCI'\"\u003e\u003c/a\u003e3 解雇中间人：没有 Docker Engine 的 OCI\u003c/h2\u003e\u003cp\u003e每个现代工具都需要运行镜像。显而易见的做法是桥接到本地 Docker socket。这违反了我不使用重量级引擎的规则。\u003c/p\u003e\n\u003cp\u003e所以我写了 \u003ccode\u003eoci.ts\u003c/code\u003e——一个用 TypeScript 实现的完整 OCI（Open Container Initiative）registry 客户端。它解析 manifest，协商 token，逐层拉取 tar.gz blob，直接把它们注入到一个 VM 可以即时挂载的 ext4 rootfs 中。通过在本地解码镜像并在不接触 dockerd 的情况下挂载它来引导 \u003ccode\u003enode:22-alpine\u003c/code\u003e，从实践层面改变了\u0026quot;即时\u0026quot;的含义。在那些默认不安装 \u003ccode\u003emkfs.ext4\u003c/code\u003e 的架构上，我们会优雅地回退到 WSL2 或 Homebrew，而不会打断流程。\u003c/p\u003e\n\u003ch2 id=\"顿悟时刻作为违背物理定律之黏合剂的-sharedarraybuffer\" class=\"headerLink\"\u003e\n    \u003ca href=\"#%e9%a1%bf%e6%82%9f%e6%97%b6%e5%88%bb%e4%bd%9c%e4%b8%ba%e8%bf%9d%e8%83%8c%e7%89%a9%e7%90%86%e5%ae%9a%e5%be%8b%e4%b9%8b%e9%bb%8f%e5%90%88%e5%89%82%e7%9a%84-sharedarraybuffer\" class=\"header-mark\" aria-label=\"Header mark for '顿悟时刻：作为违背物理定律之黏合剂的 SharedArrayBuffer'\"\u003e\u003c/a\u003e4 顿悟时刻：作为违背物理定律之黏合剂的 SharedArrayBuffer\u003c/h2\u003e\u003cp\u003e1 到 3 秒的冷启动时间还可以接受。我真正的执念是让被暂停的进程在一个网络请求的延迟内恢复——亚 100 毫秒。\u003c/p\u003e\n\u003cp\u003eVM 暂停/恢复的传统做法是冻结 CPU，把 RAM 和中断状态序列化到磁盘，唤醒时再恢复。对我的需求而言，这慢了好几个数量级。另一个选项是在 Node.js 主线程和管理 hypervisor 的 Worker 线程之间传递消息。问题在于：Node 在事件循环之上的消息传递桥会引入延迟抖动和停顿。\u003c/p\u003e\n\u003cp\u003e灵感来自思考现代游戏引擎是如何渲染的：\u003cstrong\u003eSharedArrayBuffer\u003c/strong\u003e 与 Atomics 的结合。\u003c/p\u003e\n\u003cp\u003e我实现了一个小巧的结构化缓冲区，带有用于 \u003ccode\u003eCONTROL_COMMAND\u003c/code\u003e、\u003ccode\u003eCONTROL_STATE\u003c/code\u003e 和控制台的槽位——TypeScript 主线程和 C++ Worker 线程都能原子地读取它，无需昂贵的锁，也无需通过 V8 进行任何消息序列化。\u003c/p\u003e\n\u003cp\u003e当我想暂停一台 VM 时，TS 线程会原子地往缓冲区里写入一个 \u003ccode\u003e1\u003c/code\u003e。KVM/WHP Worker 在它某次微观的 VM-exit 期间，检查那个共享字节，然后直接停止 vCPU 的执行——而不拆除机器的内存基础设施。VM 并不在磁盘上。它仍然在 hypervisor 中活着，只是睡着了，不消耗任何周期。\u003c/p\u003e\n\u003cp\u003e里面的 Fastify 或 Express 服务器会恢复并在 \u003cstrong\u003e5 到 50 毫秒\u003c/strong\u003e内解析一个挂起的 \u003ccode\u003eGET /\u003c/code\u003e。与商业 hypervisor 的做法相比，这是个不起眼的小把戏。但对于启动隔离环境而言，它带来的人体工程学收益一点也不小。\u003c/p\u003e\n\u003cp\u003e这个项目还没完成。从冷快照完整恢复 RAM 仍是我借助 dirty-page 跟踪在追求的目标，我已经把它接入了代码的基础结构中。但到目前为止，我已经得到了我当初想要构建的东西：一台真正虚拟机毫不妥协的隔离，被隐藏在某种看起来、运行起来、消亡起来都像我终端里又一个进程那样轻松的东西里。\u003c/p\u003e\n",
        "language": "zh-cn"
    },
    ]
}
