<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/rss.xsl" type="text/xsl"?>
<rss version="2.0">
  <channel>
    <title>IT瘾应用推荐</title>
    <link>https://itindex.net/categories/应用</link>
    <description>IT社区推荐资讯 - ITIndex.net</description>
    <language>zh</language>
    <copyright>https://itindex.net/</copyright>
    <generator>https://itindex.net/</generator>
    <docs>http://backend.userland.com/rss</docs>
    <image>
      <url>https://itindex.net/images/logo.gif</url>
      <title>IT社区推荐资讯 - ITIndex.net</title>
      <link>https://itindex.net/categories/应用</link>
    </image>
    <item>
      <title>OECD：人工智能在教育体系中的应用与采纳</title>
      <link>https://itindex.net/detail/63145-oecd-%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD-%E6%95%99%E8%82%B2</link>
      <description>&lt;p&gt;经合组织与阿涅利基金会联合发布的最新报告系统梳理了人工智能在教育体系中的渗透路径及其现实影响。数据显示，AI在课堂中的使用呈现明显的学段差异，小学阶段经常使用AI的学生比例仅为8%，而普通高中已升至50%，职业教育为42%。在家庭场景中，用于完成作业的AI使用率也随学段上升，最高达到54%，显示出学习场景正加速向校外延伸。&lt;/p&gt;
 &lt;p&gt;教师端的采用速度相对谨慎。2024年TALIS调查显示，OECD国家平均37%的初中教师在过去一年中使用过AI，而意大利仅为25%，显著低于新加坡的75%。在具体用途上，68%的教师将AI用于资料整理与知识总结，64%用于生成教案，显示AI更多被视为效率工具而非教学范式的重塑力量。&lt;/p&gt;
 &lt;p&gt;在教师态度层面，认可与担忧并存。意大利有60%的教师认为AI有助于支持特殊需求学生，高于OECD平均水平，但同时67%的教师担心AI助长学术不端，32%认为算法可能放大偏见。这种结构性分歧意味着AI的制度化应用仍受信任门槛制约。&lt;/p&gt;
 &lt;p&gt;培训供给正在追赶技术扩散。2018至2024年间，意大利参与数字化培训的教师比例从68%升至84%，但接受过AI专项培训的教师仅26%，显著低于部分国家超过60%的水平。能力建设的滞后，成为限制AI深度应用的重要约束。&lt;/p&gt;
 &lt;p&gt;学生侧的采用更为激进。小规模调查显示，87%的高中生已使用过AI工具，主要集中在家庭作业支持，真正嵌入课堂教学的比例仅为25%。这加剧了学校对评价体系有效性的担忧，也推动意大利重新重视口试等难以被AI替代的评估方式。&lt;/p&gt;
 &lt;p&gt;从基础条件看，硬件与基础设施短板依然突出。意大利平均每7.1名学生才配备一台设备，显著落后于北欧国家的1:1水平，且约42%的学校仍依赖不稳定的网络连接，区域差异明显。&lt;/p&gt;
 &lt;p&gt;综合来看，AI正在重塑学习方式，但其教育红利并非自动实现。未来趋势将取决于三点：教师AI能力能否规模化提升，课程与评价体系是否同步调整，以及数字基础设施能否补齐短板。AI更可能成为放大教育质量差异的“加速器”，而非天然的均衡器，政策设计的前瞻性将决定其最终走向。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0001-12.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0002-11.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0003-11.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0004-11.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0005-11.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0006-11.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0007-11.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0008-11.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0009-11.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0010-11.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0011-11.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0012-11.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0013-11.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0014-11.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0015-10.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0016-10.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0017-10.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0018-10.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0019-10.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0020-10.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0021-9.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0022-9.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0023-9.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0024-8.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0025-8.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0026-7.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0027-7.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0028-7.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0029-7.jpg" width="992"&gt;&lt;/img&gt;   &lt;img alt="" height="1403" src="http://www.199it.com/wp-content/uploads/2026/01/0030-7.jpg" width="992"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;​文档链接将分享到199IT知识星球，扫描下面二维码即可查阅！&lt;/strong&gt;&lt;/p&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;h3&gt;更多阅读：&lt;/h3&gt;   &lt;ul&gt;    &lt;li&gt;     &lt;a href="http://www.199it.com/archives/1796741.html"&gt;OECD：教育促进人类繁荣&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.199it.com/archives/1630038.html"&gt;OECD：人工智能与劳动力市场&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.199it.com/archives/1764524.html"&gt;OECD：人工智能时代公共就业服务提供的新曙光&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.199it.com/archives/778402.html"&gt;OECD：2018年教育报告&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.199it.com/archives/1798813.html"&gt;TechEquity：2025年人工智能与劳动力发展报告&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.199it.com/archives/1735249.html"&gt;世界银行集团：教育数字创新之AI和教育&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.199it.com/archives/1766237.html"&gt;ASEF：2024年人工智能与教育报告&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.199it.com/archives/1766681.html"&gt;Common sense：生成式AI在K-12教育中的挑战和机遇&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.199it.com/archives/1695900.html"&gt;世界经济论坛：人工智能对教育4.0的影响&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.199it.com/archives/1787762.html"&gt;用人工智能进行治理：核心政府职能中的现状评估与未来路径&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.199it.com/archives/687600.html"&gt;Accenture：调查显示企业不愿花钱对员工进行人工智能培训&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.199it.com/archives/1795000.html"&gt;OECD：2025年生成式人工智能与中小企业劳动力研究报告&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.199it.com/archives/1771259.html"&gt;OECD：在技能优先策略背景下赋能劳动力&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.199it.com/archives/1781459.html"&gt;OECD报告：韧性技能政策&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.199it.com/archives/1790067.html"&gt;OECD：衡量科学和创新促进可持续增长&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>199IT推荐文章 人工智能 研究报告 OECD 教育</category>
      <guid isPermaLink="true">https://itindex.net/detail/63145-oecd-%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD-%E6%95%99%E8%82%B2</guid>
      <pubDate>Mon, 19 Jan 2026 05:30:40 CST</pubDate>
    </item>
    <item>
      <title>抖B红应有尽有，为何你就是不用</title>
      <link>https://itindex.net/detail/63137-</link>
      <description>&lt;div&gt;  &lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVySLR0xvd_OKtMrLUffEKbULojw5SYbuCVlK6nISdXJ70ZXYkXBJWSFwv1VDc-cm7ceLyGMj9SY6li_kIqDFGIaOE_4OoC-zp7JX4mpilOePmtrcRdX47xeOkCT7cjzBOUapdLQt9zWvp86TjDLh0yiamJ1Bdu3ZeTIEAINdxOaTL_bSm7IeSq9a3_pk/s2464/hecaitou_A_bald_obese_bespectacled_elderly_Chinese_man_stands_t_61a132aa-1d74-4754-8da3-3331f807a0db-gigapixel-cgi-2x.jpeg"&gt;   &lt;img border="0" height="468" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVySLR0xvd_OKtMrLUffEKbULojw5SYbuCVlK6nISdXJ70ZXYkXBJWSFwv1VDc-cm7ceLyGMj9SY6li_kIqDFGIaOE_4OoC-zp7JX4mpilOePmtrcRdX47xeOkCT7cjzBOUapdLQt9zWvp86TjDLh0yiamJ1Bdu3ZeTIEAINdxOaTL_bSm7IeSq9a3_pk/w352-h468/hecaitou_A_bald_obese_bespectacled_elderly_Chinese_man_stands_t_61a132aa-1d74-4754-8da3-3331f807a0db-gigapixel-cgi-2x.jpeg" width="352"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/div&gt; &lt;br /&gt; &lt;br /&gt; &lt;br /&gt;我在一篇文章里谈到自己通过 AI 为自己的音响设备搭配找答案，留言区里不出意外地出现了这样一条跟帖： &lt;br /&gt;「虽然但是，搭音响教程不应该找影音设备的b站博主和小红书博主吗？」 &lt;br /&gt;我想，这不是一个人，不是第一次，也不是在一件事上有类似的想法，那我在这里就谈一谈我的个人看法，争取这次把这个问题给谈透。 &lt;br /&gt;先给一个简单的个人结论：如果你在 21 世纪过着一种网络集体生活，那么抖音、B 站、小红书上的攻略的确算得上是「包罗万有」，而且绝对足够多，足够有效。但是，只要你稍微偏离一点这种生活，事情完全就不是那么一回事。 &lt;br /&gt;为什么是打引号的「包罗万有」？因为这里的万有对应着的是一种数字时代的集体生活。这是一群平均高中以上教育文化程度，以文员为主要职业，熟练使用手机的年轻城市居民。然后这一大群人对于如何吃，如何玩，如何看，诸如此类的事情存在着高度认可的几个有限模板。去到一个地方，住什么宾馆民宿，探访什么景点，尝试什么餐厅小吃，甚至是以什么角度在什么地点用什么器材什么参数拍照，都有模板。 &lt;br /&gt;活在这些模板里的人当然觉得包罗万有，但凡自己想到的，都必然能找到攻略，不单能找到攻略，还能发现海量的「我们」在相互分享，在相互赞美肯定。这不是包罗万有又是什么？既然包罗万有，像我这样不懂去查一下攻略的人，岂不是显得很傻很落伍，居然用什么 AI 去问，当然要嘲讽性地用「虽然但是」开头，这也是模板式的俏皮，在抖B红里很吃得开。 &lt;br /&gt;问题是，我又不要过这种数字时代的集体生活。不是现在如此，一直都是如此。你什么时候看到我去「打卡」了？我推荐过的餐厅是因为它火还是因为我发现它好吃？我去旅行的地方在当时，是任何攻略推荐的热门项目热门地点吗？ &lt;br /&gt;哦，好像我是写攻略的那一个？ &lt;br /&gt;具体说到 HiFi 音响器材，这是一个小众爱好，不属于任何集体活动。然而它的寿命又很长，那么多年有过那么多公司，出过那么多产品，这些产品又依据具体功能分为很多类。所以，即便它很小众，考虑到可以选择的器材太多，那么搭配的可能组合在数量上也就大到惊人。这样的话，为什么会觉得抖B红里会有那么多博主、Up主、达人都逐一试过，而且写出过教程？ &lt;br /&gt;事实上是，稍微脱离赛博集体生活一点点距离，在资讯上就是一片荒漠。这里举一个简单例子： &lt;br /&gt;去年（2025 年）3 月，海外最著名的科学 HIFI 网站 ASR 上，著名站长Amirm发布了韩国新兴音响品牌的 AsciLab F6Bs 音箱，因为测评数据太过漂亮，他做出了个人推荐。现在，这个帖子已经累积了 23 页，许多人不单购买，而且在这个帖子下讨论和分享。 &lt;br /&gt;现在是 2026 年 1 月 8 日下午，谁告诉我一句：B 站关于这一款音箱的拆箱和攻略在哪里？ &lt;br /&gt;答案是没有，或者说你搜不到，但我知道。B 站里有一个用户发了一篇笔记，记录他去年七月还是八月份从韩国海淘了一台回来，但也就是记录了这件事，在留言区里对粉丝承诺等他忙完就做个全国首台AsciLab F6Bs 分享，但再也没有下文。为什么我会知道？因为我为了搜索资讯，爬了许多留言区。 &lt;br /&gt;为什么找不到测评，找不到攻略？因为没有引进国内，因为没有国内代理，所以它就等于不存在。而一个人过着数字集体生活，当然可以随处看到马歇尔蓝牙音箱的测评和分享，因为人人家里都有一台啊。不然就是那个哈曼卡顿玻璃罐子，就是 B&amp;amp;O A1 3rd，就是 JBL 的棍子。哦，你说立体声音响啊，那么漫步者、惠威的音箱推荐和测评很多啊，每一代每一个型号都有。 &lt;br /&gt;那如果我不用它们呢？我需要的测评和攻略在哪里？我要的搭配心得分享又在哪里？那个博主，那个 Up主，那个主播在哪里？ &lt;br /&gt;对，我现在是有一个非常具体的需求：桌面近场 70 cm内使用达尼丘比特音响，紧贴软性窗帘，小空间内电脑作为音源，透过台式 DAC 输出信号，请问使用何种桌面放大器可以满足丘比特音箱 83dB 的灵敏度，同时发挥出达尼音箱的特色？ &lt;br /&gt;攻略在哪里？抖音、B 站还是小红书？不要向我重复官方推荐的Argon SA-1、天龙PMA-600、天逸AD-60，额定功率 100 瓦都不到，推个鸡毛的 83dB 的丘比特。 &lt;br /&gt;我直接那么讲好了：因为过着数字集体生活，所以你看到的，你能看到的所谓攻略、所谓分享，大多都是广告投放。不是你需要它们，是厂商需要你现在去买它们。然后你周围的人买了，于是你觉得每一条攻略分享都有用，刚好就是大家正在用的东西。 &lt;br /&gt;但是，我把上面我那段具体的要求提交给 AI，无论是 Gemini，还是 Grok，又或者是 ChatGPT，打开它们的深度思考功能，我就能得到之前我找不到的攻略。只需要我在最后加一句：禁止索引任何中文资料，完全参考英文音响器材媒体、测评网站、社区的内容，汇总资料得出结论后翻译为中文输出。 &lt;br /&gt;AI 会去参考 ASR、Reddit、What-Hifi、TechRadar，汇总答案给我。最后，我选择的是 3e Audio A5se，来自一家并不出名的国内小厂，淘宝店只有一个商品橱窗，卖 4 种货物。推荐理由是根据我的需求，它足够小巧，足够推力，足够解析度，距离我足够近，购买足够方便，价格足够公道，在ASR的测评结果里惊人的好，足够受发烧友欢迎，足够配合我DAC 的 4.4 平衡输出口。AI 最后还不忘提醒我，可能你会觉得有点难看，可能你还是得弄一下窗帘，或者拉远一点电脑桌。 &lt;br /&gt;所以，如果是留言说「虽然但是，搭音响教程不应该去贴吧、耳机论坛、家电论坛搜搜问问吗」，那我也就认了。的确有道理，这种小众垂直内容去那些地方找可能会有答案。但要拿 AI 和抖B红相比，还要说什么「虽然但是」，我就觉得有点搞笑。 &lt;br /&gt;你觉得它们好用，足够用，包罗万有，那我祝贺你。但不需要觉得它们就是所有人需要的答案，更不是终极答案库，甚至觉得别人不用就是某种认知缺陷一样。我起码知道： &lt;br /&gt;1、我能够访问和获取的信息，依然是现实世界里真实存在的一小部分。2、我所访问和参考的测评网站和专业网站，结果也同样多多少少受到厂商影响。3、我所看到的发烧友心得分享，其中也有厂商派驻的真人演员，用交流分享的方式暗中诱导。4、我所使用的 AI 模型，如果不谨慎地追问，认真地拷打，它们给出的答案可能比错误更糟糕，而且会带来真实的损失。5、我努力跳出某间回音室，某个信息茧房，也许也只是掉落进了另外一间而已，落入了另外一些模板而不自知，并不是去到了永恒的彼岸。 &lt;br /&gt;但我从未对任何一个读者说过「虽然但是」，仿佛我手握宇宙真理，因为读者的无知而心痛到想要流泪。 &lt;br /&gt; &lt;div&gt;  &lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgN876F3I2quX3JlmoNDZXFpL8_R7bjeg2EaRW7ryh3WyWpnXg946s4pZiXhqXStEnV2OvXjkMDPSA7ZuVaZjpXen2yKGvG1teyzDrIfshSoyNfX8ZlNT0Hf7WQqO4khkv7rVlblHViz6IKFoRYUxQAcGWfMf5GhAsxyqEX696jW9XMUXWYmrPqR0zJCYk/s2464/hecaitou_a_half-eaten_apple_d18f260a-bdb4-4738-a1af-4975429fd0bf-gigapixel-cgi-2x.jpeg"&gt;   &lt;img border="0" height="517" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgN876F3I2quX3JlmoNDZXFpL8_R7bjeg2EaRW7ryh3WyWpnXg946s4pZiXhqXStEnV2OvXjkMDPSA7ZuVaZjpXen2yKGvG1teyzDrIfshSoyNfX8ZlNT0Hf7WQqO4khkv7rVlblHViz6IKFoRYUxQAcGWfMf5GhAsxyqEX696jW9XMUXWYmrPqR0zJCYk/w389-h517/hecaitou_a_half-eaten_apple_d18f260a-bdb4-4738-a1af-4975429fd0bf-gigapixel-cgi-2x.jpeg" width="389"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;  &lt;div&gt;---   &lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgG0XOWBm4-P9RrqQG1wS1pTY8RE8y2i1ahaHWc8OFpw9yWa0wJGM7-CnL4pzymFL9c02jg9_hEYozHglN3OtFp_WxEHl3BGlrU-wYl6qde4zY4JjpylYxIO7Tq5-qNDjiqmKEGCDIwAw9OJgosfsfsNN2ku9nYLmZkEBrA6kdSJnsMlutamczfNoH/s256/WechatIMG546-gigapixel-art-scale-2_00x.jpeg"&gt;    &lt;img border="0" height="23" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgG0XOWBm4-P9RrqQG1wS1pTY8RE8y2i1ahaHWc8OFpw9yWa0wJGM7-CnL4pzymFL9c02jg9_hEYozHglN3OtFp_WxEHl3BGlrU-wYl6qde4zY4JjpylYxIO7Tq5-qNDjiqmKEGCDIwAw9OJgosfsfsNN2ku9nYLmZkEBrA6kdSJnsMlutamczfNoH/w22-h23/WechatIMG546-gigapixel-art-scale-2_00x.jpeg" width="22"&gt;&lt;/img&gt;&lt;/a&gt;---&lt;/div&gt;  &lt;div&gt;   &lt;br /&gt;&lt;/div&gt;&lt;/div&gt; &lt;br /&gt; &lt;br /&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/63137-</guid>
      <pubDate>Thu, 08 Jan 2026 15:44:00 CST</pubDate>
    </item>
    <item>
      <title>DeepWiki 一个常用 RAG 应用的开发流程</title>
      <link>https://itindex.net/detail/63128-deepwiki-rag-%E5%BA%94%E7%94%A8</link>
      <description>&lt;p&gt;上一篇文章：  &lt;a href="https://crossoverjie.top/2025/12/23/AI/LLM-app-concept/"&gt;大模型应用开发必需了解的基本概念&lt;/a&gt; 分享了关于 LLM 大模型应用开发的一些基础知识，本文乘热打铁，借助一个真实的大模型应用来分析下其中的流程&lt;/p&gt; &lt;h1&gt;  &lt;a href="http://crossoverjie.top/#deepwiki-&amp;#20171;&amp;#32461;" title="deepwiki &amp;#20171;&amp;#32461;"&gt;&lt;/a&gt;deepwiki 介绍&lt;/h1&gt; &lt;p&gt;这里我们还是以   &lt;a href="https://github.com/AsyncFuncAI/deepwiki-open/"&gt;deepwiki-open&lt;/a&gt;为例进行分析。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://s2.loli.net/2025/12/24/uaLBwngDf5cyQMo.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;通过这个截图可以知道它的主要功能：一键把任意 GitHub/GitLab/Bitbucket 仓库生成“可浏览的交互式 Wiki”&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;支持 RAG 的问答，根据 repo 的现有内容进行问答。&lt;/li&gt;  &lt;li&gt;支持多种模型（Google Gemini、OpenAI、OpenRouter、Azure OpenAI、本地 Ollama等）&lt;/li&gt;  &lt;li&gt;支持 DeepResearch：多轮研究流程，自动迭代直至给出结构化结论（适合复杂问题）&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;  &lt;a href="http://crossoverjie.top/#&amp;#20351;&amp;#29992;" title="&amp;#20351;&amp;#29992;"&gt;&lt;/a&gt;使用&lt;/h2&gt; &lt;p&gt;要使用也很简单，我们用一个兼容 openai 的 key 就可以使用了。&lt;/p&gt; &lt;p&gt;在   &lt;code&gt;.env&lt;/code&gt; 里配置下相关环境变量：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;     &lt;code&gt;OPENAI_API_KEY=&amp;quot;xxcdxxe&amp;quot;        &lt;br /&gt;OPENAI_BASE_URL=&amp;quot;https://dashscope.aliyuncs.com/compatible-mode/v1&amp;quot;      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;同时我们在   &lt;code&gt;generator.json&lt;/code&gt; 里为 openai 新增一个你所使用的模型：&lt;/p&gt; &lt;p&gt;  &lt;img src="https://s2.loli.net/2025/12/24/GnNhFULzrKE3g2X.png"&gt;&lt;/img&gt;  &lt;br /&gt;这样就可以在页面上选择这个模型了。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://s2.loli.net/2025/12/24/lwp5zyV3JedY4KZ.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;同时我们还需要再   &lt;code&gt;embedder.json&lt;/code&gt; 配置一个 embedding 模型，这个你的 LLM 提供商也会提供：&lt;/p&gt; &lt;p&gt;  &lt;img src="https://s2.loli.net/2025/12/24/zYLm1ZV8QFrshbR.png"&gt;&lt;/img&gt;  &lt;br /&gt;  &lt;img src="https://s2.loli.net/2025/12/24/6wFGvg4Uip9ZnNq.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;注意这里的 batch_size 需要修改为模型支持的大小&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;然后我们便可以填入一个 repo 地址，系统会自动生成 wiki。&lt;/p&gt; &lt;h1&gt;  &lt;a href="http://crossoverjie.top/#&amp;#27969;&amp;#31243;" title="&amp;#27969;&amp;#31243;"&gt;&lt;/a&gt;流程&lt;/h1&gt; &lt;p&gt;官方提供了一个流程图如下：  &lt;br /&gt;  &lt;img src="https://s2.loli.net/2025/12/24/7dnTIKvMRPyCEzu.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;这个流程图略显粗糙，我整理一版更细的的流程如下：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;获取 repo 信息（前端）   &lt;br /&gt;    1.1. clone repo，同时在本地生成 RAG。   &lt;br /&gt;    1.2. 根据目录结构树和 readme 拼接内容传递给 AI 生成 wiki 的   &lt;strong&gt;目录结构&lt;/strong&gt;：   &lt;a href="http://crossoverjie.top/%5Bhttps://github.com/AsyncFuncAI/deepwiki-open/blob/bb3c67d235e0f504c38ddf661dc3022fdc7ebcef/src/app/%5Bowner%5D/%5Brepo%5D/page.tsx#L712-L832%5D(https://github.com/AsyncFuncAI/deepwiki-open/blob/bb3c67d235e0f504c38ddf661dc3022fdc7ebcef/src/app/%5Bowner%5D/%5Brepo%5D/page.tsx#L712-L832)"&gt; prompt &lt;/a&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;        1.2.1. 通过【目录结构树和 readme】 先到 RAG 里查询具体的文档，然后再拼接与 system_prompt 拼接成一个完整 prompt 生成目录结构&lt;/p&gt; &lt;p&gt;        1.2.2. 如果 repo 过大导致目录树和 readme 的内容超过 token 限制，  &lt;strong&gt;则不会去 RAG 里查询具体的内容来拼接生成目录结构&lt;/strong&gt;，只会根据目录树和 readme 来生成，这样的目录结构信息可能会不全。&lt;/p&gt; &lt;ol start="2"&gt;  &lt;li&gt;根据   &lt;strong&gt;目录结构&lt;/strong&gt;拼接 prompt 生成每个目录的具体内容：   &lt;a href="http://crossoverjie.top/%5Bhttps://github.com/AsyncFuncAI/deepwiki-open/blob/bb3c67d235e0f504c38ddf661dc3022fdc7ebcef/src/app/%5Bowner%5D/%5Brepo%5D/page.tsx#L419-L526%5D(https://github.com/AsyncFuncAI/deepwiki-open/blob/bb3c67d235e0f504c38ddf661dc3022fdc7ebcef/src/app/%5Bowner%5D/%5Brepo%5D/page.tsx#L419-L526)"&gt;prompt&lt;/a&gt;（前端）   &lt;br /&gt;    2.1 根据前端提交的   &lt;strong&gt;目录 prompt 执行 RAG 检索&lt;/strong&gt;，找出需要查询的 document，根据 document 里的源码构建最终的发往 LLM 的 prompt（后端）   &lt;br /&gt;        3. 将文件分组，拼接成    &lt;a href="http://crossoverjie.top/%5Bhttps://github.com/AsyncFuncAI/deepwiki-open/blob/cdf06314e416074fe9750de36e0829f79497711e/api/websocket_wiki.py#L199-L223%5D(https://github.com/AsyncFuncAI/deepwiki-open/blob/cdf06314e416074fe9750de36e0829f79497711e/api/websocket_wiki.py#L199-L223)"&gt;context_text&lt;/a&gt;   &lt;br /&gt;    2.2. 与 system_prompt 拼接成一个完整    &lt;a href="http://crossoverjie.top/%5Bhttps://github.com/AsyncFuncAI/deepwiki-open/blob/cdf06314e416074fe9750de36e0829f79497711e/api/websocket_wiki.py#L407-L421%5D(https://github.com/AsyncFuncAI/deepwiki-open/blob/cdf06314e416074fe9750de36e0829f79497711e/api/websocket_wiki.py#L407-L421)"&gt;prompt&lt;/a&gt;（后端）&lt;/li&gt;&lt;/ol&gt; &lt;p&gt; 4. 循环 2， 继续处理前端提交上来的目录结构 prompt。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://s2.loli.net/2025/12/24/qWrCBmNkx7pJUv4.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h2&gt;  &lt;a href="http://crossoverjie.top/#&amp;#29983;&amp;#25104;&amp;#26412;&amp;#22320;&amp;#26412;&amp;#22320;&amp;#21521;&amp;#37327;&amp;#25968;&amp;#25454;&amp;#24211;" title="&amp;#29983;&amp;#25104;&amp;#26412;&amp;#22320;&amp;#26412;&amp;#22320;&amp;#21521;&amp;#37327;&amp;#25968;&amp;#25454;&amp;#24211;"&gt;&lt;/a&gt;生成本地本地向量数据库&lt;/h2&gt; &lt;p&gt;第一步是 clone 我们指定的 repo，同时会读取该 repo 里的所有内容在本地生成一个向量数据库。&lt;/p&gt; &lt;p&gt;相关的关键代码：  &lt;br /&gt;  &lt;img src="https://s2.loli.net/2025/12/24/7SchRv3zriZu4dI.png"&gt;&lt;/img&gt;  &lt;br /&gt;  &lt;img src="https://s2.loli.net/2025/12/24/LxzyOKsM7RinagY.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h2&gt;  &lt;a href="http://crossoverjie.top/#Spitter" title="Spitter"&gt;&lt;/a&gt;Spitter&lt;/h2&gt; &lt;p&gt;在生成向量之前我们还需要构建一个分词器，它用于将我们的文本切分为一个个 chunk，以便：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;避免超出模型/嵌入接口的长度上限&lt;/li&gt;  &lt;li&gt;提高检索命中率（更细粒度地召回与问题相关的片段）&lt;/li&gt;  &lt;li&gt;减少无关上下文的干扰，提升回答质量&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;在 deepwiki 里的配置如下：  &lt;br /&gt;  &lt;img src="https://s2.loli.net/2025/12/24/CVtdMlnwZzuHDGT.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;split_by: “word”（按“词”维度切分）&lt;/li&gt;  &lt;li&gt;chunk_size: 350（单块目标长度，约等于几百个词）&lt;/li&gt;  &lt;li&gt;chunk_overlap: 100（相邻块的重叠长度，保证跨块语义连续）&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;可能新手对 overlap 的作用不太清楚，它的好处是：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;代码或文档的关键信息可能跨越边界；设置 overlap 能让相邻块共享一部分上下文，减少“切断语义”的风险。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;对他的配置也需要按需使用：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;过大 overlap 会导致重复计算、存储和费用增加；过小可能丢失跨段语义。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;普通场景下   &lt;code&gt;text_splitter&lt;/code&gt; 够用，但对于我们这种存代码的场景就需要使用特殊的   &lt;code&gt;Spitter&lt;/code&gt; 了；主要问题是它不理解语言结构，容易把函数/类等语义单元切断，导致检索召回片段不完整、上下文丢失。&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;th&gt;Splitter 类型&lt;/th&gt;   &lt;th&gt;核心思路&lt;/th&gt;   &lt;th&gt;主要优点&lt;/th&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;AST/语法树型（    &lt;a href="https://pypi.org/project/code-splitter/"&gt;Tree-sitter&lt;/a&gt;、    &lt;a href="https://developers.llamaindex.ai/python/framework/module_guides/loading/node_parsers/modules/#codesplitter"&gt;LlamaIndex CodeSplitter&lt;/a&gt;）&lt;/td&gt;   &lt;td&gt;按语言语法解析，按文件→模块→类/函数→代码块分层切分&lt;/td&gt;   &lt;td&gt;边界与语义单元对齐（函数/类/方法）；检索更精准；可附带符号名/签名/路径等元数据；减少“切断语义”导致的幻觉&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;语言/模式感知启发式（    &lt;a href="https://reference.langchain.com/python/langchain_text_splitters/?_gl=1*1wsspz*_gcl_au*MTQxMjAyNDczOS4xNzY1NDQ2MTUx*_ga*NDcyOTM2OTM2LjE3NjU0NDYxNTI.*_ga_47WX3HKKY2*czE3NjY0NjkxODUkbzYkZzEkdDE3NjY0NzA0MDkkajYwJGwwJGgw#langchain_text_splitters.RecursiveCharacterTextSplitter.from_language"&gt;LangChain Recursive&lt;/a&gt; + 语言分隔符）&lt;/td&gt;   &lt;td&gt;维护各语言的分隔符（class/def/function/export 等），先递归按分隔符切分，再做 token 约束&lt;/td&gt;   &lt;td&gt;实现简单、跨语言容易落地；比纯词/字符切分更稳；成本低、工程集成快&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;这两者的对比结果还在做测试，但都会比存文本分割好很多；具体对比结果可以参考后续的文章。&lt;/td&gt;   &lt;td&gt;&lt;/td&gt;   &lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h3&gt;  &lt;a href="http://crossoverjie.top/#embedding" title="embedding"&gt;&lt;/a&gt;embedding&lt;/h3&gt; &lt;p&gt;这里有一个关键的 embedding 操作，他是将我们的文字、语音、视频等非结构化数据转换为一个向量；类似于下面的代码：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;14     &lt;br /&gt;15     &lt;br /&gt;16     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;     &lt;code&gt;import os      &lt;br /&gt;from openai import OpenAI      &lt;br /&gt;      &lt;br /&gt;client = OpenAI(      &lt;br /&gt;    api_key=os.getenv(&amp;quot;DASHSCOPE_API_KEY&amp;quot;),  # 如果您没有配置环境变量，请在此处用您的API Key进行替换      &lt;br /&gt;    base_url=&amp;quot;https://dashscope.aliyuncs.com/compatible-mode/v1&amp;quot;  # 百炼服务的base_url      &lt;br /&gt;)      &lt;br /&gt;      &lt;br /&gt;completion = client.embeddings.create(      &lt;br /&gt;    model=&amp;quot;text-embedding-v4&amp;quot;,      &lt;br /&gt;    input=&amp;apos;衣服的质量杠杠的，很漂亮，不枉我等了这么久啊，喜欢，以后还来这里买&amp;apos;,      &lt;br /&gt;    dimensions=1024, # 指定向量维度（仅 text-embedding-v3及 text-embedding-v4支持该参数）      &lt;br /&gt;    encoding_format=&amp;quot;float&amp;quot;      &lt;br /&gt;)      &lt;br /&gt;      &lt;br /&gt;print(completion.model_dump_json())      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;而模型返回的数据如下：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;14     &lt;br /&gt;15     &lt;br /&gt;16     &lt;br /&gt;17     &lt;br /&gt;18     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;     &lt;code&gt;{       &lt;br /&gt;  &amp;quot;data&amp;quot;: [      &lt;br /&gt;    {      &lt;br /&gt;      &amp;quot;embedding&amp;quot;: [      &lt;br /&gt;        0.0023064255,      &lt;br /&gt;        -0.009327292,      &lt;br /&gt;        ....       &lt;br /&gt;        -0.0028842222      &lt;br /&gt;      ],      &lt;br /&gt;      &amp;quot;index&amp;quot;: 0,      &lt;br /&gt;      &amp;quot;object&amp;quot;: &amp;quot;embedding&amp;quot;      &lt;br /&gt;    }      &lt;br /&gt;  ],      &lt;br /&gt;  &amp;quot;model&amp;quot;:&amp;quot;text-embedding-v4&amp;quot;,      &lt;br /&gt;  &amp;quot;object&amp;quot;:&amp;quot;list&amp;quot;,      &lt;br /&gt;  &amp;quot;usage&amp;quot;:{&amp;quot;prompt_tokens&amp;quot;:23,&amp;quot;total_tokens&amp;quot;:23},      &lt;br /&gt;  &amp;quot;id&amp;quot;:&amp;quot;f62c2ae7-0906-9758-ab34-47c5764f07e2&amp;quot;      &lt;br /&gt;}      &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;在 deepwiki 这个项目里，我们没有主动调用这个接口，而是由   &lt;a href="https://github.com/SylphAI-Inc/AdalFlow"&gt;AdalFlow&lt;/a&gt; 这个库在内部完成的。&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;也就是这行代码    &lt;code&gt;db.transform(key=&amp;quot;split_and_embed&amp;quot;)&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;在 deepwiki 中我们只是简单将向量数据存放在了本地，实际生产使用时还需要将其存放到一个单独的向量数据库。&lt;/p&gt; &lt;h2&gt;  &lt;a href="http://crossoverjie.top/#&amp;#29983;&amp;#25104;&amp;#30446;&amp;#24405;" title="&amp;#29983;&amp;#25104;&amp;#30446;&amp;#24405;"&gt;&lt;/a&gt;生成目录&lt;/h2&gt; &lt;p&gt;然后就是与 AI 交互了，第一步是生成目录，类似于这样：&lt;/p&gt; &lt;p&gt;  &lt;img src="https://s2.loli.net/2025/12/24/jFkGsBCP4Aa5VUq.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://s2.loli.net/2025/12/24/tSDVvaLTU1Eymbl.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;系统生成的提示词如下，其实就是把 repo 的目录结构树+readme 文件的内容与 system_prompt 拼接成一个完整的提示词告诉 LLM，让它返回一个项目的目录结构。&lt;/p&gt; &lt;p&gt;主要是以下的一些要求：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;必须只返回 XML，根节点为    &lt;code&gt;&amp;lt;wiki_structure&amp;gt;&lt;/code&gt;，以    &lt;code&gt;&amp;lt;/wiki_structure&amp;gt;&lt;/code&gt; 结束&lt;/li&gt;  &lt;li&gt;XML 必须语法有效、严格按指定结构&lt;/li&gt;  &lt;li&gt;页面数量要求：   &lt;ul&gt;    &lt;li&gt;综合模式（comprehensive）：8–12 个页面&lt;/li&gt;    &lt;li&gt;精简模式（concise）：4–6 个页面&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;内容语言由参数指定（英文、中文、日文等）&lt;/li&gt;  &lt;li&gt;每个页面应聚焦代码库的一个具体方面（架构、特性、部署、前端/后端模块等）&lt;/li&gt;  &lt;li&gt;   &lt;code&gt;relevant_files&lt;/code&gt; 必须是仓库中的真实文件路径，用于后续生成具体页面内容&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;  &lt;a href="http://crossoverjie.top/#&amp;#27599;&amp;#20010;&amp;#30446;&amp;#24405;&amp;#30340;&amp;#20855;&amp;#20307;&amp;#35814;&amp;#24773;&amp;#39029;" title="&amp;#27599;&amp;#20010;&amp;#30446;&amp;#24405;&amp;#30340;&amp;#20855;&amp;#20307;&amp;#35814;&amp;#24773;&amp;#39029;"&gt;&lt;/a&gt;每个目录的具体详情页&lt;/h2&gt; &lt;p&gt;目录生成完成之后就需要生成该页面的具体内容了，比如这个页面：  &lt;br /&gt;  &lt;img src="https://s2.loli.net/2025/12/24/gsEO5th9B4CD21q.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;他的提示词如下：  &lt;br /&gt;  &lt;img src="https://s2.loli.net/2025/12/24/WY8iIKlzaEAney6.png"&gt;&lt;/img&gt;  &lt;br /&gt;其中的主要约束如下：&lt;/p&gt; &lt;p&gt;相关源文件清单（强制）&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;在最顶部的  中逐条列出所有用于生成内容的源文件，且至少 5 个。&lt;/li&gt;  &lt;li&gt;每个文件以 Markdown 链接形式出现（已给出可点击的仓库文件 URL）。&lt;/li&gt;  &lt;li&gt;若输入文件少于 5 个，模型需“补足更多相关文件”。&lt;/li&gt;  &lt;li&gt;Mermaid Diagrams（大量使用，强制规则）&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;总结就是：结构合理、层次分明，便于理解与导航点击。&lt;/p&gt; &lt;h1&gt;  &lt;a href="http://crossoverjie.top/#&amp;#24635;&amp;#32467;" title="&amp;#24635;&amp;#32467;"&gt;&lt;/a&gt;总结&lt;/h1&gt; &lt;p&gt;以上就是对 deepwiki 项目的分析，作为一个典型的 RAG 应用，掌握它的流程便可以举一反三来实现其他类似的 RAG 应用。&lt;/p&gt; &lt;p&gt;当然其中有许多需要调优的地方，比如模型的选择、Spitter 参数的配置、RAG 召回 top_k 的配置等等。&lt;/p&gt; &lt;p&gt;还要平衡好效果与成本。  &lt;br /&gt;#Blog  &lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>AI AI</category>
      <guid isPermaLink="true">https://itindex.net/detail/63128-deepwiki-rag-%E5%BA%94%E7%94%A8</guid>
      <pubDate>Thu, 25 Dec 2025 02:23:28 CST</pubDate>
    </item>
    <item>
      <title>‎Google Gemini3有哪些实用且高效的应用场景</title>
      <link>https://itindex.net/detail/63098-google-gemini3-%E5%BA%94%E7%94%A8</link>
      <description>&lt;p&gt;结合   &lt;strong&gt;Gemini 1.5 Pro&lt;/strong&gt; 强大的超长上下文和多模态能力，以下是大约   &lt;strong&gt;15个&lt;/strong&gt; 极其实用且高效的应用场景：&lt;/p&gt; &lt;h3&gt;📚 研究与文档处理 (利用超长上下文)&lt;/h3&gt; &lt;ol start="1"&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;长篇报告摘要与问答：&lt;/strong&gt;&lt;/p&gt;   &lt;ul&gt;    &lt;li&gt;     &lt;p&gt;      &lt;strong&gt;场景：&lt;/strong&gt; 上传一本厚厚的行业分析报告、学术论文或公司年报（数百页的 PDF）。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;strong&gt;用途：&lt;/strong&gt; 要求 Gemini 总结核心观点、提炼关键数据，或直接针对某个具体问题（如“2025年的主要增长驱动力是什么？”）进行精准问答。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;代码库分析与审查：&lt;/strong&gt;&lt;/p&gt;   &lt;ul&gt;    &lt;li&gt;     &lt;p&gt;      &lt;strong&gt;场景：&lt;/strong&gt; 将一个包含完整代码、文档和日志的压缩包（几万行代码）上传。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;strong&gt;用途：&lt;/strong&gt; 要求 Gemini 找出所有潜在的安全漏洞、提出优化代码结构或重构的建议，甚至根据日志找出 Bug 的根源。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ol&gt; &lt;h3&gt;🎥 媒体与多模态分析 (利用多模态能力)&lt;/h3&gt; &lt;ol start="3"&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;视频内容精准定位与提炼：&lt;/strong&gt;&lt;/p&gt;   &lt;ul&gt;    &lt;li&gt;     &lt;p&gt;      &lt;strong&gt;场景：&lt;/strong&gt; 上传一段 2 小时长的会议录像、教学课程或访谈视频。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;strong&gt;用途：&lt;/strong&gt; 询问视频中某个话题（如“谁提到了预算削减？”）的精确时间戳，或要求总结所有发言人的核心观点。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;扫描手写文档的数字化：&lt;/strong&gt;&lt;/p&gt;   &lt;ul&gt;    &lt;li&gt;     &lt;p&gt;      &lt;strong&gt;场景：&lt;/strong&gt; 上传一张模糊的、老旧的、手写的食谱或笔记的照片。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;strong&gt;用途：&lt;/strong&gt; 让 Gemini 不仅将其准确转录为电子文本，还能根据内容推断出一些模糊字迹的含义。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;图表数据解读与对比：&lt;/strong&gt;&lt;/p&gt;   &lt;ul&gt;    &lt;li&gt;     &lt;p&gt;      &lt;strong&gt;场景：&lt;/strong&gt; 上传包含大量复杂图表和数据的商业演示文稿（PPT/PDF）。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;strong&gt;用途：&lt;/strong&gt; 让 Gemini 分析图表的趋势，解释数据之间的关系，并将其结果总结为几点清晰的结论。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ol&gt; &lt;h3&gt;💡 规划与创意辅助 (利用高级推理)&lt;/h3&gt; &lt;ol start="6"&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;复杂项目资料整合：&lt;/strong&gt;&lt;/p&gt;   &lt;ul&gt;    &lt;li&gt;     &lt;p&gt;      &lt;strong&gt;场景：&lt;/strong&gt; 同时上传包含合同、设计草稿、供应商邮件、项目排期表的多个文件。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;strong&gt;用途：&lt;/strong&gt; 要求 Gemini 整合所有信息，找出项目中可能存在的“时间冲突”或“未解决的风险点”。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;旅行路线优化与定制：&lt;/strong&gt;&lt;/p&gt;   &lt;ul&gt;    &lt;li&gt;     &lt;p&gt;      &lt;strong&gt;场景：&lt;/strong&gt; 上传多篇旅行博客、地图截图和酒店预订信息。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;strong&gt;用途：&lt;/strong&gt; 让 Gemini 根据您的偏好（如“避开人群”、“美食优先”）规划出一条最优化、最顺畅的 5 日旅行路线。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;专业术语和概念连接：&lt;/strong&gt;&lt;/p&gt;   &lt;ul&gt;    &lt;li&gt;     &lt;p&gt;      &lt;strong&gt;场景：&lt;/strong&gt; 上传一本专业领域的入门教材和一本进阶教材。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;strong&gt;用途：&lt;/strong&gt; 让 Gemini 解释两个教材中某个关键概念的不同侧重点和演变历程。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ol&gt; &lt;h3&gt;🌐 个人与生活辅助&lt;/h3&gt; &lt;ol start="9"&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;跨文件信息一致性检查：&lt;/strong&gt;&lt;/p&gt;   &lt;ul&gt;    &lt;li&gt;     &lt;p&gt;      &lt;strong&gt;场景：&lt;/strong&gt; 上传您的简历、求职信和 LinkedIn 个人资料的文本。&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;     &lt;p&gt;      &lt;strong&gt;用途：&lt;/strong&gt; 让 Gemini 检查所有文件中关于您的工作经历、职位名称、或专业技能的描述是否完全一致，避免矛盾。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;长期邮件/对话总结：&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;     &lt;strong&gt;场景：&lt;/strong&gt; 上传您和合作伙伴长达一年的邮件往来记录（或聊天记录）。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;     &lt;strong&gt;用途：&lt;/strong&gt; 让 Gemini 总结你们在某个特定主题（如“上次的合作项目”）上，最终达成了哪些具体决定和未完成的事项。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ol&gt; &lt;h4&gt;11.实时数据流监控与预警&lt;/h4&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;场景：&lt;/strong&gt; 监控大型服务器、金融市场交易、或工业物联网 (IoT) 传感器传来的海量、持续的数据流。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;Gemini 优势：&lt;/strong&gt; 它可以同时处理并记住数小时甚至数天的数据，快速识别**“异常模式”    &lt;strong&gt;。它能将当前的实时数据与&lt;/strong&gt;历史正常数据模式**进行比较，一旦发现哪怕是细微的偏差，立刻触发预警，远超简单阈值警报。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;h4&gt;12. 持久记忆的个性化 AI 伴侣&lt;/h4&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;场景：&lt;/strong&gt; 将 Gemini 作为您的长期私人助理。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;Gemini 优势：&lt;/strong&gt; 由于上下文窗口巨大，它可以    &lt;strong&gt;记住&lt;/strong&gt;您数月来的所有对话、习惯、偏好、甚至细微的情绪变化。例如，您不用重复提到您对咖啡的偏好、您的家庭成员名字或您正在进行的项目进度。它能以一种前所未有的连贯性和深度来提供个性化服务。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;h4&gt;13. 跨语言实时会议口译&lt;/h4&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;场景：&lt;/strong&gt; 国际会议、跨国商务谈判或教育研讨会。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;Gemini 优势：&lt;/strong&gt; 它可以    &lt;strong&gt;持续接收&lt;/strong&gt;来自不同语言的音频输入，并提供    &lt;strong&gt;实时、高语境关联&lt;/strong&gt;的口译。由于它能记住完整的对话流程和专业术语，因此翻译的连贯性和准确性远高于传统机器翻译。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;h4&gt;14. 交互式教育与技能陪练&lt;/h4&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;场景：&lt;/strong&gt; 编程教学、复杂数学辅导或语言学习。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;Gemini 优势：&lt;/strong&gt; 它可以    &lt;strong&gt;记住&lt;/strong&gt;学生在整个课程中的所有测试结果、犯错的模式和学习进度。它能像人类导师一样，根据学生实时的输入，即时调整难度和教学策略，提供高度个性化和连续性的教学体验。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;h4&gt;15. 复杂设备故障排除与辅助&lt;/h4&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;场景：&lt;/strong&gt; 维修复杂的机械设备、飞机引擎或医疗设备。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;Gemini 优势：&lt;/strong&gt; 维修人员可以将实时诊断数据、现场照片、甚至视频流同时输入给 Gemini。模型可以一边参考    &lt;strong&gt;整本设备维修手册&lt;/strong&gt;（巨大的上下文），一边分析实时数据，给出    &lt;strong&gt;逐步&lt;/strong&gt;的、针对现场情况的、精确的排除故障指令。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;
     
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/63098-google-gemini3-%E5%BA%94%E7%94%A8</guid>
      <pubDate>Wed, 03 Dec 2025 10:21:35 CST</pubDate>
    </item>
    <item>
      <title>从KV Cache到Prompt Cache的应用</title>
      <link>https://itindex.net/detail/63096-kv-cache-prompt</link>
      <description>&lt;h2&gt;引子&lt;/h2&gt; &lt;img alt="" height="1008" src="https://www.edony.ink/content/images/2025/11/image.png" width="2000"&gt;&lt;/img&gt;Screenshot from  &lt;a href="https://www.youtube.com/watch?v=EMQxQwoFSb4&amp;ref=edony.ink" rel="noreferrer"&gt;  &lt;em&gt;   &lt;em&gt;YouTube&lt;/em&gt;&lt;/em&gt;&lt;/a&gt; &lt;p&gt;从工程师的视角来观察，随着Scaling Law失效问题被更多的人提起，我越来越认同LLM正在逐渐进入「精打细算，收个果实的平庸时代」。Andrew Ng在他的感恩节给读者的来信中提到，AI可能存在泡沫但是一定不是在AI应用开发：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;AI 应用层： 投资不足。其潜力远超大多数人的认知。&lt;/li&gt;  &lt;li&gt;AI 推理基础设施： 仍需大量投资。&lt;/li&gt;  &lt;li&gt;AI 模型训练基础设施： 我对这一领域仍持谨慎乐观态度，但可能存在泡沫。&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;1. 大模型推理的物理瓶颈：透视KV Cache&lt;/h2&gt; &lt;p&gt;在探讨具体的优化技术之前，必须从第一性原理出发，理解为何KV Cache会成为大模型推理的阿喀琉斯之踵。这不仅是显存容量的问题，更是显存带宽（Memory Bandwidth）与计算强度（Arithmetic Intensity）之间矛盾的体现。&lt;/p&gt; &lt;h3&gt;1.1 Transformer解码的自回归特性&lt;/h3&gt; &lt;p&gt;Transformer模型的推理过程分为两个阶段：预填充（Prefill）和解码（Decoding）。&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;预填充阶段（Prefill）：模型并行处理输入的所有token。由于可以并行计算，这一阶段主要受限于GPU的计算能力（Compute-bound）。此时，GPU的利用率通常很高。&lt;/li&gt;  &lt;li&gt;解码阶段（Decoding）：模型逐个生成后续token。这是一个自回归（Autoregressive）过程，即生成第 $t$ 个token需要依赖于前 $t-1$ 个token的内部状态。&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;在标准的注意力机制（Self-Attention）中，计算公式为：&lt;/p&gt; &lt;p&gt;$$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$  &lt;br /&gt;其中，$Q$（Query）是当前时间步的查询向量，而 $K$（Key）和 $V$（Value）则包含了所有历史token的信息。为了避免在生成每一个新token时都重新计算前面所有token的 $K$ 和 $V$ 投影，系统会将这些计算好的向量存储在显存中，这就是 KV Cache。&lt;/p&gt; &lt;h3&gt;1.2 显存占用的数学推导&lt;/h3&gt; &lt;p&gt;KV Cache的显存占用量是序列长度的线性函数，且随层数、头数和隐藏层维度倍增。对于一个标准的Transformer模型，其KV Cache的大小可以通过以下公式精确计算：&lt;/p&gt; &lt;p&gt;$$ \text{Size}{KV} = 2 \times L{seq} \times B_{batch} \times N_{layers} \times H_{heads} \times D_{head} \times P_{prec} $$&lt;/p&gt; &lt;p&gt;其中：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;$2$：代表Key和Value两个矩阵。&lt;/li&gt;  &lt;li&gt;$L_{seq}$：当前的序列长度（上下文窗口大小）。&lt;/li&gt;  &lt;li&gt;$B_{batch}$：并发请求的批处理大小（Batch Size）。&lt;/li&gt;  &lt;li&gt;$N_{layers}$：Transformer的层数。&lt;/li&gt;  &lt;li&gt;$H_{heads}$：注意力头的数量。&lt;/li&gt;  &lt;li&gt;$D_{head}$：每个注意力头的维度（通常为 $D_{model} / H_{heads}$）。&lt;/li&gt;  &lt;li&gt;$P_{prec}$：数据精度（FP16为2字节，FP32为4字节）。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;案例分析：Llama-2 70B模型  &lt;br /&gt;假设我们使用FP16精度（2字节），序列长度为4096，Batch Size为1。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;$N_{layers} = 80$&lt;/li&gt;  &lt;li&gt;$H_{heads} = 64$ (GQA之前)&lt;/li&gt;  &lt;li&gt;$D_{head} = 128$&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;单次请求的KV Cache大小为：&lt;/p&gt; &lt;p&gt;$$2 \times 4096 \times 1 \times 80 \times 64 \times 128 \times 2 \approx 10.7 \text{ GB}$$  &lt;br /&gt;如果我们将上下文扩展到100k tokens（如Claude或GPT-4 Turbo常见场景），单次请求的KV Cache将膨胀至 260 GB。这已经远远超过了单张NVIDIA A100 (80GB) 甚至 H100 (80GB) 的显存容量。这意味着，在长文本场景下，显存容量直接限制了Batch Size，导致GPU的算力无法被填满，推理成本急剧上升。&lt;/p&gt; &lt;h3&gt;1.3 内存墙与带宽瓶颈&lt;/h3&gt; &lt;p&gt;除了容量限制，更致命的是带宽限制。在解码阶段，每生成一个token，GPU都需要从高带宽内存（HBM）中读取整个KV Cache到片上SRAM进行计算。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;计算量（FLOPs）：随着KV Cache的增长，计算量仅线性增长。&lt;/li&gt;  &lt;li&gt;数据传输量（Bytes）：数据传输量也线性增长，但由于矩阵乘法中的一维退化为向量（Query vector），算术强度（Arithmetic Intensity，即FLOPs/Bytes比率）极低。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;现代GPU（如H100）具有极高的算力（~1000 TFLOPS FP16）和极高的带宽（~3.35 TB/s）。然而，在解码阶段，由于每次矩阵向量乘法（GEMV）都需要搬运庞大的KV Cache，GPU大部分时间都在等待数据从HBM传输，导致计算单元闲置。这就是所谓的“内存受限”（Memory-bound）场景。&lt;/p&gt; &lt;p&gt;为了缓解这一问题，业界在过去两年中经历了从算法架构改革到系统工程优化的剧烈演变。&lt;/p&gt; &lt;h2&gt;2. 注意力机制的架构演进：从MHA到MLA&lt;/h2&gt; &lt;img alt="" height="325" src="https://www.edony.ink/content/images/2025/11/image-1.png" width="1080"&gt;&lt;/img&gt; &lt;p&gt;为了从根本上减小KV Cache的体积，模型架构师们对Transformer的核心——注意力机制进行了多次手术。这一演进路径清晰地展示了从追求极致性能到追求效率与性能平衡的过程。&lt;/p&gt; &lt;h3&gt;2.1 多头注意力（MHA）：昂贵的基准&lt;/h3&gt; &lt;p&gt;在《Attention Is All You Need》原论文中提出的多头注意力（Multi-Head Attention, MHA）机制中，模型拥有 $H$ 个查询头（Query Heads），同时也对应拥有 $H$ 个键值头（Key/Value Heads）。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;机制：每个Query Head都有自己独立的Key和Value投影矩阵。这意味着模型可以从 $H$ 个不同的子空间（Subspaces）捕捉信息，理论上表达能力最强。&lt;/li&gt;  &lt;li&gt;代价：正如第1节中的计算所示，KV Cache的大小与头数 $H$ 成正比。对于大模型，这意味着巨大的显存开销。&lt;/li&gt;  &lt;li&gt;现状：早期的BERT、GPT-2、GPT-3以及Llama-1均采用MHA。但在长上下文时代，MHA已成为不可承受之重。&lt;/li&gt;&lt;/ul&gt; &lt;h3&gt;2.2 多查询注意力（MQA）：激进的压缩&lt;/h3&gt; &lt;p&gt;为了解决推理性价比问题，Noam Shazeer在2019年提出了多查询注意力（Multi-Query Attention, MQA）。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;机制：无论有多少个Query Heads，整个层只保留1个 Key Head 和 1个 Value Head。所有的Query Heads共享这唯一的KV对。&lt;/li&gt;  &lt;li&gt;压缩比：$H : 1$。如果模型有64个头，KV Cache的大小直接缩小64倍。&lt;/li&gt;  &lt;li&gt;优势：极大地减少了显存占用和数据搬运量，使得推理速度显著提升，且能支持更大的Batch Size。&lt;/li&gt;  &lt;li&gt;劣势：这种压缩过于激进，导致模型在处理复杂任务时，无法同时关注输入序列的不同方面，造成性能（Perplexity）下降和训练不稳定性。&lt;/li&gt;  &lt;li&gt;应用：Google的PaLM模型和Falcon系列采用了MQA，但在开源社区并未立刻成为主流。&lt;/li&gt;&lt;/ul&gt; &lt;h3&gt;2.3 分组查询注意力（GQA）：中庸之道的胜利&lt;/h3&gt; &lt;p&gt;在Llama-2发布时，Meta引入了分组查询注意力（Grouped-Query Attention, GQA），这一机制迅速成为当今开源大模型（如Llama-3, Mistral, Qwen）的实际标准。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;机制：GQA是MHA和MQA的折中方案。它将Query Heads分为 $G$ 个组，每个组内的Query Heads共享一个KV Head。&lt;/li&gt;  &lt;li&gt;配置：例如，Llama-2 70B使用了8个KV Heads（$G=8$），而Query Heads为64个。这意味着每8个Query Heads共享1个KV Head。&lt;/li&gt;  &lt;li&gt;压缩比：$H : G$。在上述例子中，压缩比为8:1。&lt;/li&gt;  &lt;li&gt;效果：研究表明，GQA在显存占用和推理速度上接近MQA，而在模型效果（Accuracy/Perplexity）上几乎等同于MHA。它成功地在帕累托前沿（Pareto Frontier）上找到了最佳平衡点。&lt;/li&gt;&lt;/ul&gt; &lt;h3&gt;2.4 多头潜在注意力（MLA）：DeepSeek的架构革命&lt;/h3&gt; &lt;p&gt;DeepSeek-V2（及其后的V3）引入的MLA（Multi-Head Latent Attention）不仅仅是分组策略的调整，而是对KV Cache存储方式的根本性重构。这是DeepSeek能够提供极低API价格的核心技术支撑。&lt;/p&gt; &lt;h4&gt;2.4.1 低秩矩阵压缩（Low-Rank Compression）原理&lt;/h4&gt; &lt;p&gt;传统的注意力机制直接存储投影后的 $K$ 和 $V$ 矩阵，维度为 $d_{model} \times L$。MLA认为这些高维矩阵中存在大量的冗余信息，可以通过低秩分解来压缩。MLA不直接存储 $K$ 和 $V$，而是将输入的隐藏状态投影到一个低维的“潜在向量”（Latent Vector, $c_{KV}$）。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;压缩：输入向量首先经过一个下投影矩阵，变为极低维度的潜在向量（例如，压缩比可达数十倍）。&lt;/li&gt;  &lt;li&gt;存储：在KV Cache中，只存储这个压缩后的潜在向量。&lt;/li&gt;  &lt;li&gt;还原：在计算注意力分数时，通过一个上投影矩阵，将潜在向量实时还原为用于计算的Key和Value。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;这种方法将KV Cache的显存占用从 $O(H \times d_{head})$ 降低到了 $O(d_{latent})$，其中 $d_{latent}$ 远小于前者。&lt;/p&gt; &lt;h4&gt;  &lt;strong&gt;2.4.2 解耦旋转位置编码（Decoupled RoPE）&lt;/strong&gt;&lt;/h4&gt; &lt;p&gt;低秩压缩的一个巨大挑战是如何兼容旋转位置编码（RoPE）。RoPE对向量的旋转操作具有几何敏感性，直接在压缩向量上应用RoPE会破坏其位置信息，或者在还原时引入巨大误差。&lt;/p&gt; &lt;p&gt;DeepSeek创造性地提出了“解耦RoPE”策略：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;内容头（Content Head）：负责捕捉语义信息，采用上述的低秩压缩（不带RoPE）。&lt;/li&gt;  &lt;li&gt;位置头（Position Head）：一个单独的、维度很小的向量，专门用于携带RoPE位置信息。&lt;/li&gt;  &lt;li&gt;拼接（Concatenation）：在计算Attention Score时，将还原后的内容头与位置头拼接，共同参与计算。&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;通过这种方式，MLA既实现了极致的KV Cache压缩（仅存储压缩内容+少量位置信息），又完美保留了长上下文所需的位置感知能力。根据DeepSeek的报告，MLA使得KV Cache的大小在同等参数规模下只有GQA模型的1/5甚至更低，这使得将KV Cache放入显存之外的介质（如内存或SSD）成为可能，因为数据传输的带宽压力被大幅减轻了。&lt;/p&gt;

 &lt;table&gt;

  &lt;tr&gt;
   &lt;th&gt;特性&lt;/th&gt;
   &lt;th&gt;MHA (Llama-1)&lt;/th&gt;
   &lt;th&gt;MQA (Falcon)&lt;/th&gt;
   &lt;th&gt;GQA (Llama-3)&lt;/th&gt;
   &lt;th&gt;MLA (DeepSeek-V3)&lt;/th&gt;
&lt;/tr&gt;


  &lt;tr&gt;
   &lt;td&gt;KV头数量&lt;/td&gt;
   &lt;td&gt;等于Query头数 ($H$)&lt;/td&gt;
   &lt;td&gt;1&lt;/td&gt;
   &lt;td&gt;分组数 ($G$, 如8)&lt;/td&gt;
   &lt;td&gt;虚拟/动态生成&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;显存占用&lt;/td&gt;
   &lt;td&gt;极高 (100%)&lt;/td&gt;
   &lt;td&gt;极低 (~1-2%)&lt;/td&gt;
   &lt;td&gt;中等 (~12-25%)&lt;/td&gt;
   &lt;td&gt;    &lt;strong&gt;极致压缩 (5-10%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;模型性能&lt;/td&gt;
   &lt;td&gt;基准 (高)&lt;/td&gt;
   &lt;td&gt;有损&lt;/td&gt;
   &lt;td&gt;接近无损&lt;/td&gt;
   &lt;td&gt;    &lt;strong&gt;无损甚至更优&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;推理速度&lt;/td&gt;
   &lt;td&gt;慢 (受限于带宽)&lt;/td&gt;
   &lt;td&gt;极快&lt;/td&gt;
   &lt;td&gt;快&lt;/td&gt;
   &lt;td&gt;极快&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;RoPE兼容性&lt;/td&gt;
   &lt;td&gt;原生支持&lt;/td&gt;
   &lt;td&gt;原生支持&lt;/td&gt;
   &lt;td&gt;原生支持&lt;/td&gt;
   &lt;td&gt;    &lt;strong&gt;需解耦设计&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;

 &lt;h2&gt;3. 系统级显存管理与优化：从分页到流式&lt;/h2&gt; &lt;p&gt;如果说Transformer架构决定了KV Cache的“理论最小体积”，那么系统软件则决定了如何在物理硬件上高效地“摆放”这些数据。2023年以来，以vLLM为代表的推理框架通过引入操作系统领域的经典思想，彻底改变了显存管理的范式。&lt;/p&gt; &lt;h3&gt;3.1 显存碎片化与PagedAttention (vLLM)&lt;/h3&gt; &lt;p&gt;在vLLM出现之前，主流推理框架（如FasterTransformer）采用的是静态显存分配。对于一个请求，系统必须按照其“最大可能长度”（Max Sequence Length）预先分配一块连续的显存空间。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;内部碎片（Internal Fragmentation）：如果预分配了2048长度，但用户只生成了50个token，剩余的空间全部被浪费。&lt;/li&gt;  &lt;li&gt;外部碎片（External Fragmentation）：不同请求的显存块大小不一，导致显存中出现许多无法被利用的空隙。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;据统计，这种方式导致的显存浪费率高达60%-80%。&lt;/p&gt; &lt;h4&gt;3.1.1 PagedAttention的原理&lt;/h4&gt; &lt;img alt="" height="542" src="https://www.edony.ink/content/images/2025/11/image-2.png" width="1080"&gt;&lt;/img&gt; &lt;p&gt;vLLM团队受操作系统虚拟内存（Virtual Memory）分页机制的启发，提出了PagedAttention。&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;KV Block：将KV Cache切分为固定大小的块（Block），例如每块存储16个token的KV数据。&lt;/li&gt;  &lt;li&gt;非连续存储：这些Block在物理显存（HBM）中不需要连续存放，可以分散在任意位置。&lt;/li&gt;  &lt;li&gt;页表（Block Table）：系统维护一张映射表，记录每个请求的逻辑token顺序对应哪些物理Block。&lt;/li&gt;  &lt;li&gt;按需分配：只有当新的token生成填满当前Block时，系统才申请下一个Block。&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;优势：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;零浪费：内部碎片仅存在于最后一个未填满的Block中，浪费率降至4%以下。&lt;/li&gt;  &lt;li&gt;显存共享（Memory Sharing）：这是PagedAttention最强大的特性。对于这就如Python中的引用计数，如果多个请求共享相同的System Prompt（例如“你是一个有用的助手...”），vLLM只需在物理显存中存储一份该Prompt的KV Block，所有请求的页表都指向这一份数据。只有当各自生成不同的后续内容时，才触发“写时复制”（Copy-on-Write）。这为由Prompt Cache奠定了系统基础。&lt;/li&gt;&lt;/ul&gt; &lt;img alt="" height="404" src="https://www.edony.ink/content/images/2025/11/image-3.png" width="1014"&gt;&lt;/img&gt; &lt;ul&gt;  &lt;li&gt;并行采样（Parallel Sampling）：在 Parallel Sampling 中，同一个 prompt 会生成多个候选输出，便于用户从多个备选中选择最佳响应，常用于内容生成或模型对比测试。在 vLLM 中，这些采样序列共享相同的 prompt，其对应的 KV Cache 也可以共用同一组物理块。PagedAttention 通过引用计数和 block-level 的 copy-on-write 机制实现共享与隔离的平衡：只有当序列出现不同分支时，才会触发复制操作。&lt;/li&gt;&lt;/ul&gt; &lt;img alt="" height="1105" src="https://www.edony.ink/content/images/2025/11/image-4.png" width="1202"&gt;&lt;/img&gt; &lt;h3&gt;3.2 动态前缀复用与RadixAttention (SGLang)&lt;/h3&gt; &lt;p&gt;虽然vLLM解决了分配问题，但在处理复杂的、非线性的对话历史时，如何自动发现可复用的KV Cache仍是难题。SGLang框架提出了RadixAttention，将缓存管理提升到了一个新的维度。&lt;/p&gt; &lt;h4&gt;3.2.1 Radix Tree（基数树）结构&lt;/h4&gt; &lt;img alt="" height="1128" src="https://www.edony.ink/content/images/2025/11/image-5.png" width="2000"&gt;&lt;/img&gt; &lt;p&gt;SGLang不再将KV Cache视为线性的数组，而是将其维护为一个基数树（Radix Tree）。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;节点：树的边代表token序列，节点代表KV Cache的状态。&lt;/li&gt;  &lt;li&gt;路径：从根节点到叶子节点的路径代表一个完整的对话历史。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;Hash RadixAttention 代码走读：&lt;/p&gt; &lt;img alt="" height="1060" src="https://www.edony.ink/content/images/2025/11/image-6.png" width="1440"&gt;&lt;/img&gt; &lt;h4&gt;3.2.2 自动复用机制&lt;/h4&gt; &lt;img alt="" height="454" src="https://www.edony.ink/content/images/2025/11/image-7.png" width="1774"&gt;&lt;/img&gt; &lt;p&gt;当一个新的请求到达时，系统将Prompt作为搜索键在Radix Tree中进行最长前缀匹配（Longest Prefix Match）。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;场景A（多轮对话）：用户问“A”，模型答“B”。用户接着问“C”。RadixAttention自动匹配到“A-&amp;gt;B”的路径，直接复用其KV Cache，只需计算“C”。&lt;/li&gt;  &lt;li&gt;场景B（Few-Shot Learning）：多个请求使用相同的Few-Shot示例，但在最后的问题上不同。RadixAttention自动锁定公共前缀节点，无需人工干预。&lt;/li&gt;  &lt;li&gt;LRU淘汰：当显存不足时，系统根据最近最少使用（LRU）原则，从叶子节点开始剪枝，释放显存。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;prefix hash 代码走读：&lt;/p&gt; &lt;img alt="" height="406" src="https://www.edony.ink/content/images/2025/11/image-8.png" width="1294"&gt;&lt;/img&gt; &lt;p&gt;与vLLM早期的前缀缓存相比，RadixAttention无需用户手动配置，且能处理更复杂的分支结构（如Tree-of-Thoughts推理），显著提高了复杂Agent任务的吞吐量。&lt;/p&gt; &lt;h3&gt;3.3 无限流式生成与StreamingLLM&lt;/h3&gt; &lt;p&gt;对于需要7x24小时运行的数字人或长期助理，KV Cache理论上会无限增长直至显存溢出。简单的“滑动窗口”（Sliding Window，只保留最近N个token）会导致模型崩溃，因为Transformer在训练时并未适应这种信息的突然截断。&lt;/p&gt; &lt;h4&gt;3.3.1 注意力汇聚（Attention Sink）现象&lt;/h4&gt; &lt;p&gt;MIT的研究人员发现，Transformer模型在推理时，会倾向于将大量的注意力分数分配给序列开头的几个token（通常是前4个）。这些token充当了“锚点”（Anchor），稳定了后续层的注意力计算，即使它们本身可能没有太多语义信息。&lt;/p&gt; &lt;h4&gt;3.3.2 StreamingLLM机制&lt;/h4&gt; &lt;p&gt;基于上面的发现，StreamingLLM提出了一种特殊的缓存策略：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;保留汇聚点：永久保留序列开头的几个token（Attention Sinks）的KV Cache。&lt;/li&gt;  &lt;li&gt;滑动窗口：对后续的token使用滑动窗口，只保留最近的N个。&lt;/li&gt;  &lt;li&gt;位置编码调整：对RoPE进行相对位置的平移，使模型感知到正确的距离。&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;这种方法使得模型可以在有限的显存（例如只缓存2048个token）下，处理长度达到400万甚至无限的输入流，且困惑度（Perplexity）不发生爆炸。&lt;/p&gt; &lt;h2&gt;4. 极致压缩：KV Cache量化技术&lt;/h2&gt; &lt;p&gt;除了架构和系统优化，降低数据本身的精度是另一个维度的压缩手段。KV Cache量化（Quantization）正从研究走向生产环境。&lt;/p&gt; &lt;h3&gt;4.1 精度格式的演变&lt;/h3&gt; &lt;ul&gt;  &lt;li&gt;FP16/BF16：传统的基准，每个参数2字节。&lt;/li&gt;  &lt;li&gt;FP8 (E4M3/E5M2)：NVIDIA H100原生支持。将KV Cache压缩到1字节/参数。vLLM和TensorRT-LLM已经支持FP8 KV Cache，通常能带来2倍的吞吐量提升，且精度损失微乎其微。&lt;/li&gt;&lt;/ul&gt; &lt;h3&gt;4.2 激进量化：INT4与非均匀分布挑战&lt;/h3&gt; &lt;p&gt;将KV Cache压缩到4-bit（INT4）可以将显存占用减少4倍，但这面临巨大挑战。&lt;/p&gt; &lt;h4&gt;4.2.1 异常值（Outliers）问题&lt;/h4&gt; &lt;p&gt;研究发现，Key和Value矩阵中的数值分布并非均匀的高斯分布。特定的通道（Channels）或Token会出现数值极大的异常值。如果使用标准的均匀量化（Uniform Quantization），这些异常值会拉大量化范围（Range），导致大部分小数值的精度被严重吞噬，模型彻底失效。&lt;/p&gt; &lt;p&gt;解决方案：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;SmoothQuant / Atom：引入平滑因子，将激活值（Activation）中的异常值迁移到权重（Weight）中，或者在量化前对通道进行缩放（Per-channel scaling），使得分布更平滑，适合INT8/INT4量化。&lt;/li&gt;  &lt;li&gt;KIVI / KVQuant：采用非均匀量化策略，或者将少量的异常值单独以高精度（FP16）存储，而对绝大部分数据进行INT4甚至2-bit量化。&lt;/li&gt;  &lt;li&gt;分组量化：类似于GQA，对每128个或64个元素进行独立的量化统计，减小异常值的影响范围。&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;目前，INT4 KV Cache在部分长文本场景下已可投入使用，但在高精度要求的逻辑推理任务中仍需谨慎评估。&lt;/p&gt; &lt;h2&gt;5. 各大厂商Prompt Cache支持情况深度评测&lt;/h2&gt; &lt;p&gt;2025年以来，随着上述技术的成熟，各大模型厂商纷纷推出了面向开发者的“Context Caching”或“Prompt Caching”服务。这一功能被视为RAG（检索增强生成）和Agent（智能体）应用的经济基石。&lt;/p&gt; &lt;h3&gt;5.1 DeepSeek：磁盘缓存与价格屠夫&lt;/h3&gt; &lt;p&gt;DeepSeek（深度求索）是目前市场上最具颠覆性的玩家。依托于其独特的MLA架构，DeepSeek实现了真正的磁盘级上下文缓存。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;技术原理：由于MLA将KV Cache压缩到了极小（约为MHA的1/10甚至更低），数据量小到足以从SSD硬盘阵列中实时读取，而不会造成严重的延迟瓶颈。这打破了“KV Cache必须在显存”的铁律 6。&lt;/li&gt;  &lt;li&gt;缓存策略（Implicit）：自动触发。无需用户显式创建缓存ID，系统自动识别重复的前缀。&lt;/li&gt;  &lt;li&gt;价格体系：   &lt;ul&gt;    &lt;li&gt;缓存命中（Cache Hit）：$0.014 / 百万tokens。这是一个惊人的数字。相比之下，GPT-4o的输入价格约为$2.50，DeepSeek的价格仅为OpenAI的      &lt;strong&gt;0.5%&lt;/strong&gt;。&lt;/li&gt;    &lt;li&gt;缓存未命中（Cache Miss）： 约 $0.14 - $0.27 / 百万tokens（取决于具体版本），依然远低于竞品。&lt;/li&gt;    &lt;li&gt;存储费：免费。得益于磁盘存储的低成本，DeepSeek不收取额外的时间存储费。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;持久性：由于存储在磁盘，其TTL（生存时间）远长于基于显存的竞品，理论上可达数小时甚至数天（取决于系统调度），非常适合低频但长尾的知识库查询。&lt;/li&gt;&lt;/ul&gt; &lt;h3&gt;5.2 Google Gemini：TPU加持下的灵活双模&lt;/h3&gt; &lt;p&gt;Google Vertex AI利用其TPU Pod的庞大显存池，提供了最为灵活的“隐式+显式”双轨制。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;隐式缓存（Implicit）：针对Gemini 2.5 Flash等模型。自动检测，无需代码更改。   &lt;ul&gt;    &lt;li&gt;门槛：1024或2048 tokens以上。&lt;/li&gt;    &lt;li&gt;价格：命中时约为标准输入价格的25%（即75%折扣）。无存储费。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;显式缓存（Explicit）：针对企业级长文档。   &lt;ul&gt;    &lt;li&gt;机制：用户调用API创建CachedContent对象，获得一个ID。&lt;/li&gt;    &lt;li&gt;价格结构：包含两部分。     &lt;ul&gt;      &lt;li&gt;计算费：命中缓存的Token价格极低（甚至在某些层级接近免费）。&lt;/li&gt;      &lt;li&gt;存储费：按小时计费。例如，Gemini 2.5 Pro约为$4.50 / 100万tokens / 小时。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;    &lt;li&gt;适用场景：** 这是一种“租赁显存”的模式。只有当你的查询频率非常高（例如每小时数百次查询同一份文档），节省的计算费才能覆盖存储费。如果只是偶尔查询，显式缓存反而更贵。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt; &lt;h3&gt;5.3 Anthropic Claude：极速流转的显存租赁&lt;/h3&gt; &lt;p&gt;Anthropic的策略非常激进，专注于“高频会话”场景，尤其是配合Claude 4 Sonnet强大的编码能力。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;显式断点：用户需在API参数中设置 cache_control: {&amp;quot;type&amp;quot;: &amp;quot;ephemeral&amp;quot;} 断点。&lt;/li&gt;  &lt;li&gt;TTL（5分钟）：这是最大的争议点与特点。缓存仅保存5分钟。每次命中（Read），TTL重置为5分钟。这意味着它不适合长期存储，只适合连续不断的对话。&lt;/li&gt;  &lt;li&gt;价格：   &lt;ul&gt;    &lt;li&gt;写入（Write）：1.25倍标准输入价格。你需要支付溢价来创建缓存。&lt;/li&gt;    &lt;li&gt;读取（Read）：0.1倍（10%）标准输入价格。90%的折扣。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;盈亏平衡点：由于有25%的写入溢价，用户必须在5分钟内至少复用一次缓存（即第二次提问），才能开始省钱。如果写入后5分钟内没有再次提问，缓存失效，用户的写入溢价就“白花了”。&lt;/li&gt;&lt;/ul&gt; &lt;h3&gt;5.4 OpenAI：保守的黑盒策略&lt;/h3&gt; &lt;p&gt;OpenAI在Prompt Cache上显得相对保守和不透明。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;机制：纯隐式。自动匹配1024 tokens以上的块。&lt;/li&gt;  &lt;li&gt;支持模型：GPT-4、5等系列。&lt;/li&gt;  &lt;li&gt;价格：   &lt;ul&gt;    &lt;li&gt;写入：原价（无溢价）。&lt;/li&gt;    &lt;li&gt;读取：50%折扣。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;分析：50%的折扣力度远小于DeepSeek (95%+) 或 Anthropic (90%)。但由于没有写入溢价，这是一种“无风险”的优惠——即使没命中也不会亏。&lt;/li&gt;  &lt;li&gt;TTL：不透明，通常为5分钟-24小时，受系统负载影响极大。这使得开发者很难依赖它来做严格的成本控制。&lt;/li&gt;&lt;/ul&gt; &lt;h3&gt;5.5 阿里云 Qwen (通义千问)：混合模式&lt;/h3&gt; &lt;p&gt;阿里云Model Studio紧跟国际步伐，提供了类似的机制。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;隐式：命中时按标准价的20%计费（80%折扣）。&lt;/li&gt;  &lt;li&gt;显式：   &lt;ul&gt;    &lt;li&gt;写入：1.25倍价格。&lt;/li&gt;    &lt;li&gt;读取：20%价格。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;Qwen-Long：支持文件ID引用的长上下文，本质上是一种持久化的上下文缓存，支持高达10M tokens，适合超长文档分析。&lt;/li&gt;&lt;/ul&gt; &lt;h3&gt;5.6 厂商对比汇总表&lt;/h3&gt;

 &lt;table&gt;

  &lt;tr&gt;
   &lt;th&gt;厂商&lt;/th&gt;
   &lt;th&gt;机制类型&lt;/th&gt;
   &lt;th&gt;最小Token限制&lt;/th&gt;
   &lt;th&gt;存储介质推测&lt;/th&gt;
   &lt;th&gt;TTL (生存时间)&lt;/th&gt;
   &lt;th&gt;写入成本 (Write)&lt;/th&gt;
   &lt;th&gt;命中成本 (Read)&lt;/th&gt;
   &lt;th&gt;存储费用&lt;/th&gt;
&lt;/tr&gt;


  &lt;tr&gt;
   &lt;td&gt;DeepSeek&lt;/td&gt;
   &lt;td&gt;隐式 (自动)&lt;/td&gt;
   &lt;td&gt;无/低&lt;/td&gt;
   &lt;td&gt;SSD/磁盘&lt;/td&gt;
   &lt;td&gt;长 (小时/天)&lt;/td&gt;
   &lt;td&gt;1.0x (原价)&lt;/td&gt;
   &lt;td&gt;~0.05x ($0.014)&lt;/td&gt;
   &lt;td&gt;免费&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Anthropic&lt;/td&gt;
   &lt;td&gt;显式 (断点)&lt;/td&gt;
   &lt;td&gt;1024&lt;/td&gt;
   &lt;td&gt;显存 (HBM)&lt;/td&gt;
   &lt;td&gt;5分钟 (刷新)&lt;/td&gt;
   &lt;td&gt;1.25x (溢价)&lt;/td&gt;
   &lt;td&gt;0.10x (一折)&lt;/td&gt;
   &lt;td&gt;包含在写入溢价中&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Google&lt;/td&gt;
   &lt;td&gt;显式 + 隐式&lt;/td&gt;
   &lt;td&gt;1024/2048&lt;/td&gt;
   &lt;td&gt;TPU HBM&lt;/td&gt;
   &lt;td&gt;1小时 (显式, 可续)&lt;/td&gt;
   &lt;td&gt;1.0x&lt;/td&gt;
   &lt;td&gt;~0.25x&lt;/td&gt;
   &lt;td&gt;按小时收费 (显式)&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;OpenAI&lt;/td&gt;
   &lt;td&gt;隐式 (自动)&lt;/td&gt;
   &lt;td&gt;1024&lt;/td&gt;
   &lt;td&gt;显存 (HBM)&lt;/td&gt;
   &lt;td&gt;动态 (短)&lt;/td&gt;
   &lt;td&gt;1.0x&lt;/td&gt;
   &lt;td&gt;0.50x (五折)&lt;/td&gt;
   &lt;td&gt;免费&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;Alibaba&lt;/td&gt;
   &lt;td&gt;显式 + 隐式&lt;/td&gt;
   &lt;td&gt;256/1024&lt;/td&gt;
   &lt;td&gt;显存&lt;/td&gt;
   &lt;td&gt;5分钟/动态&lt;/td&gt;
   &lt;td&gt;1.25x (显式)&lt;/td&gt;
   &lt;td&gt;0.10x - 0.20x&lt;/td&gt;
   &lt;td&gt;免费&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;

 &lt;h3&gt;5.7 成本情景模拟：法律文档分析&lt;/h3&gt; &lt;p&gt;场景：上传一本50,000 tokens的法律法典，并在接下来的2小时内进行100次问答查询。&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;Anthropic (Claude 3.5 Sonnet):   &lt;ul&gt;    &lt;li&gt;首单（写入）：$50k \times $3.75/M = $0.1875$&lt;/li&gt;    &lt;li&gt;后续99次（读取）：$99 \times 50k \times $0.30/M = $1.485$&lt;/li&gt;    &lt;li&gt;总计：~$1.67&lt;/li&gt;    &lt;li&gt;风险：如果中间停顿超过5分钟，需重新支付写入费。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;Google (Gemini 2.5 Pro - 显式缓存):   &lt;ul&gt;    &lt;li&gt;写入：$50k \times $1.25/M = $0.0625$ (假设基础价)&lt;/li&gt;    &lt;li&gt;存储：$2 \text{小时} \times 0.05M \text{ tokens} \times $4.50/M/hr = $0.45$&lt;/li&gt;    &lt;li&gt;读取：$100 \times 50k \times $0.30/M = $1.50$&lt;/li&gt;    &lt;li&gt;总计：~$2.01&lt;/li&gt;    &lt;li&gt;优势：即使2小时内没人提问，缓存也在。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;OpenAI (GPT-4o):   &lt;ul&gt;    &lt;li&gt;首单：$50k \times $2.50/M = $0.125$&lt;/li&gt;    &lt;li&gt;后续99次：$99 \times 50k \times $1.25/M = $6.18$&lt;/li&gt;    &lt;li&gt;总计：~$6.30&lt;/li&gt;    &lt;li&gt;劣势：读取折扣力度不够。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;DeepSeek (V3):   &lt;ul&gt;    &lt;li&gt;首单：$50k \times $0.14/M = $0.007$&lt;/li&gt;    &lt;li&gt;后续99次：$99 \times 50k \times $0.014/M = $0.069$&lt;/li&gt;    &lt;li&gt;总计：~$0.076&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ol&gt; &lt;h2&gt;6. 语义缓存与应用层优化&lt;/h2&gt; &lt;p&gt;除了依赖模型厂商的Prompt Cache，开发者在应用层（Client-side）还可以通过语义缓存（Semantic Caching）进一步降低成本。这与Prompt Cache是互补关系。&lt;/p&gt; &lt;h3&gt;6.1 语义缓存的原理&lt;/h3&gt; &lt;p&gt;传统的缓存（如Redis）基于Key-Value精确匹配。如果用户问“苹果的价格？”和“苹果多少钱？”，传统缓存会视为两个请求。  &lt;br /&gt;语义缓存引入了向量嵌入（Embedding）技术：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;Embedding： 将用户Query转化为向量。&lt;/li&gt;  &lt;li&gt;向量搜索： 在向量数据库（如Milvus, Qdrant, Redis Vector）中搜索历史Query。&lt;/li&gt;  &lt;li&gt;相似度阈值： 如果发现一个历史Query的余弦相似度大于阈值（如0.95），则直接返回之前缓存的LLM回答 36。&lt;/li&gt;&lt;/ol&gt; &lt;h3&gt;6.2 开源工具：GPTCache&lt;/h3&gt; &lt;p&gt;GPTCache是目前最流行的开源语义缓存库，支持LangChain集成。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;模块化设计： 支持多种Embedding模型（OpenAI, HuggingFace）和多种向量存储（Redis, FAISS）。&lt;/li&gt;  &lt;li&gt;后处理： 甚至支持对缓存的回答进行评估，确保时效性。&lt;/li&gt;  &lt;li&gt;对比：   &lt;ul&gt;    &lt;li&gt;Prompt Cache (服务端)： 解决的是“长Context复用”的问题。输入是旧的（书），问题是新的。&lt;/li&gt;    &lt;li&gt;Semantic Cache (客户端)： 解决的是“高频重复问题”的问题。输入是新的（问题），但意义是旧的。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;最佳实践： 只有当用户的提问具有高重复性（如客服系统常见问题）时，语义缓存才有意义。对于开放式分析任务，Prompt Cache更为关键 。&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;7. Prompt Cache在X-Sec中的应用&lt;/h2&gt; &lt;h4&gt;7.1 Prompt Cache&lt;/h4&gt; &lt;pre&gt;  &lt;code&gt;# Qwen3 等支持 Cache 的 LLM 使能 Prompt Caching
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
  SystemMessage(
    # Qwen启动显示prompt cache能力
    content=[
      {
        &amp;quot;type&amp;quot;: &amp;quot;text&amp;quot;,
        &amp;quot;text&amp;quot;: app_prompt_template.format(vars),
        &amp;quot;cache_control&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;ephemera&amp;quot;},
      }
    ]
  ),
  HumanMessage(content=app_user_prompt_template.format(input_data),
])

# OpenAI 使能 Prompt Caching
from openai import OpenAI

client = OpenAI()

completion = client.chat.completions.create(
    model=&amp;quot;gpt-4o&amp;quot;,
    # openai 支持 24h 保存 cache
    prompt_cache_retention: &amp;quot;24h&amp;quot;
    messages=[
      {&amp;quot;role&amp;quot;: &amp;quot;system&amp;quot;, &amp;quot;content&amp;quot;: &amp;quot;You are a helpful assistant.&amp;quot;},
      {&amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: &amp;quot;Tell me a joke.&amp;quot;},
    ]
)&lt;/code&gt;&lt;/pre&gt; &lt;h4&gt;7.2 Semantic Cache&lt;/h4&gt; &lt;p&gt;X-Sec需要Memory系统的支持（langchain checkpoint支持vector store），所以当前还没有实现数据。Stay Tuned！&lt;/p&gt; &lt;img alt="" height="1548" src="https://www.edony.ink/content/images/2025/11/image-9.png" width="2000"&gt;&lt;/img&gt; &lt;h2&gt;7. 总结&lt;/h2&gt; &lt;p&gt;大模型推理的演进史，本质上是一部与显存带宽和容量搏斗的抗争史。&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;架构与硬件的协同：DeepSeek MLA的成功证明了算法创新（低秩压缩）可以解锁硬件潜力（SSD存储），从而颠覆商业模式。这预示着未来模型设计将更多地考虑底层硬件特性，而非单纯追求参数量。&lt;/li&gt;  &lt;li&gt;有状态API（Stateful API）的兴起：HTTP无状态协议已不再适应LLM应用。Prompt Cache的普及标志着LLM服务正演变为“有状态”的操作系统。开发者必须学会管理“Context 生命周期”，像管理数据库连接池一样管理Prompt Cache。&lt;/li&gt;  &lt;li&gt;成本的断崖式下降：随着$0.014/M tokens这种价格的出现，RAG应用的瓶颈将从“检索多少文档省钱”转变为“模型能处理多少文档不幻觉”。全量知识库注入（Full Context）正在取代部分切片检索，成为新的技术趋势。&lt;/li&gt;  &lt;li&gt;对于Agent应用开发者而言，当下的最优策略是：对于高频低延时任务（如代码助手）选择Anthropic或vLLM自建服务；对于海量数据分析和知识库问答，DeepSeek的磁盘缓存方案提供了目前无法匹敌的性价比优势。&lt;/li&gt;&lt;/ol&gt; &lt;h2&gt;  &lt;strong&gt;References&lt;/strong&gt;&lt;/h2&gt; &lt;ol&gt;  &lt;li&gt;Optimizing Transformer Inference with Grouped Query Attention | Towards AI, accessed November 27, 2025,    &lt;a href="https://towardsai.net/p/machine-learning/optimizing-transformer-inference-with-grouped-query-attention?ref=edony.ink"&gt;https://towardsai.net/p/machine-learning/optimizing-transformer-inference-with-grouped-query-attention&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;arXiv:2305.13245v3 [cs.CL] 23 Dec 2023, accessed November 27, 2025,    &lt;a href="https://arxiv.org/pdf/2305.13245?ref=edony.ink"&gt;https://arxiv.org/pdf/2305.13245&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Attention Mechanisms in Transformers: Comparing MHA, MQA, and GQA | Yue Shui Blog, accessed November 27, 2025,    &lt;a href="https://syhya.github.io/posts/2025-01-16-group-query-attention/?ref=edony.ink"&gt;https://syhya.github.io/posts/2025-01-16-group-query-attention/&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Understanding Multi-Head Latent Attention, accessed November 27, 2025,    &lt;a href="https://planetbanatt.net/articles/mla.html?ref=edony.ink"&gt;https://planetbanatt.net/articles/mla.html&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;DeepSeek-V2: A Strong, Economical, and Efficient Mixture-of-Experts Language Model, accessed November 27, 2025,    &lt;a href="https://arxiv.org/html/2405.04434v2?ref=edony.ink"&gt;https://arxiv.org/html/2405.04434v2&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;DeepSeek API introduces Context Caching on Disk, cutting prices by an order of magnitude, accessed November 27, 2025,    &lt;a href="https://api-docs.deepseek.com/news/news0802?ref=edony.ink"&gt;https://api-docs.deepseek.com/news/news0802&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;MLA: Redefining KV-Cache Through Low-Rank Projections and On-Demand Decompression - Hugging Face, accessed November 27, 2025,    &lt;a href="https://huggingface.co/blog/NormalUhr/mla-explanation?ref=edony.ink"&gt;https://huggingface.co/blog/NormalUhr/mla-explanation&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;How PagedAttention resolves memory waste of LLM systems - Red Hat Developer, accessed November 27, 2025,    &lt;a href="https://developers.redhat.com/articles/2025/07/24/how-pagedattention-resolves-memory-waste-llm-systems?ref=edony.ink"&gt;https://developers.redhat.com/articles/2025/07/24/how-pagedattention-resolves-memory-waste-llm-systems&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Introduction to vLLM and PagedAttention | Runpod Blog, accessed November 27, 2025,    &lt;a href="https://www.runpod.io/blog/introduction-to-vllm-and-pagedattention?ref=edony.ink"&gt;https://www.runpod.io/blog/introduction-to-vllm-and-pagedattention&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;vLLM and PagedAttention: A Comprehensive Overview | by Abonia Sojasingarayar | Medium, accessed November 27, 2025,    &lt;a href="https://medium.com/@abonia/vllm-and-pagedattention-a-comprehensive-overview-20046d8d0c61?ref=edony.ink"&gt;https://medium.com/@abonia/vllm-and-pagedattention-a-comprehensive-overview-20046d8d0c61&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;vAttention: Dynamic Memory Management for Serving LLMs without PagedAttention - arXiv, accessed November 27, 2025,    &lt;a href="https://arxiv.org/html/2405.04437v1?ref=edony.ink"&gt;https://arxiv.org/html/2405.04437v1&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;When to Choose SGLang Over vLLM: Multi-Turn Conversations and KV Cache Reuse, accessed November 27, 2025,    &lt;a href="https://www.runpod.io/blog/sglang-vs-vllm-kv-cache?ref=edony.ink"&gt;https://www.runpod.io/blog/sglang-vs-vllm-kv-cache&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;SGLang: Efficient Execution of Structured Language Model Programs - arXiv, accessed November 27, 2025,    &lt;a href="https://arxiv.org/pdf/2312.07104?ref=edony.ink"&gt;https://arxiv.org/pdf/2312.07104&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Fast and Expressive LLM Inference with RadixAttention and SGLang | LMSYS Org, accessed November 27, 2025,    &lt;a href="https://lmsys.org/blog/2024-01-17-sglang/?ref=edony.ink"&gt;https://lmsys.org/blog/2024-01-17-sglang/&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Arxiv Dives - Efficient Streaming Language Models with Attention Sinks - Oxen.ai, accessed November 27, 2025,    &lt;a href="https://ghost.oxen.ai/arxiv-dives-efficient-streaming-language-models-with-attention-sinks/?ref=edony.ink"&gt;https://ghost.oxen.ai/arxiv-dives-efficient-streaming-language-models-with-attention-sinks/&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Efficient Streaming Language Models with Attention Sinks - arXiv, accessed November 27, 2025,    &lt;a href="https://arxiv.org/html/2309.17453v4?ref=edony.ink"&gt;https://arxiv.org/html/2309.17453v4&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;[2309.17453] Efficient Streaming Language Models with Attention Sinks - arXiv, accessed November 27, 2025,    &lt;a href="https://arxiv.org/abs/2309.17453?ref=edony.ink"&gt;https://arxiv.org/abs/2309.17453&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Attention Sinks for LLM - Endless Generation - Analytics Vidhya, accessed November 27, 2025,    &lt;a href="https://www.analyticsvidhya.com/blog/2023/12/attention-sinks-for-llm/?ref=edony.ink"&gt;https://www.analyticsvidhya.com/blog/2023/12/attention-sinks-for-llm/&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;FP8 E5M2 KV Cache - vLLM, accessed November 27, 2025,    &lt;a href="https://docs.vllm.ai/en/v0.6.3.post1/quantization/fp8_e5m2_kvcache.html?ref=edony.ink"&gt;https://docs.vllm.ai/en/v0.6.3.post1/quantization/fp8_e5m2_kvcache.html&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;FP8 quantization with AMD Quark for vLLM — Tutorials for AI developers 8.0, accessed November 27, 2025,    &lt;a href="https://rocm.docs.amd.com/projects/ai-developer-hub/en/latest/notebooks/gpu_dev_optimize/fp8_quantization_quark_vllm.html?ref=edony.ink"&gt;https://rocm.docs.amd.com/projects/ai-developer-hub/en/latest/notebooks/gpu_dev_optimize/fp8_quantization_quark_vllm.html&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;KVQuant: Towards 10 Million Context Length LLM Inference with KV Cache Quantization, accessed November 27, 2025,    &lt;a href="https://www.stat.berkeley.edu/~mmahoney/pubs/neurips-2024-kvquant.pdf?ref=edony.ink"&gt;https://www.stat.berkeley.edu/~mmahoney/pubs/neurips-2024-kvquant.pdf&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;AsymKV: Enabling 1-Bit Quantization of KV Cache with Layer-Wise Asymmetric Quantization Configurations - ACL Anthology, accessed November 27, 2025,    &lt;a href="https://aclanthology.org/2025.coling-main.158.pdf?ref=edony.ink"&gt;https://aclanthology.org/2025.coling-main.158.pdf&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;FireQ: Fast INT4-FP8 Kernel and RoPE-aware Quantization for LLM Inference Acceleration, accessed November 27, 2025,    &lt;a href="https://arxiv.org/html/2505.20839v1?ref=edony.ink"&gt;https://arxiv.org/html/2505.20839v1&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;NQKV: A KV Cache Quantization Scheme Based on Normal Distribution Characteristics, accessed November 27, 2025,    &lt;a href="https://arxiv.org/html/2505.16210v1?ref=edony.ink"&gt;https://arxiv.org/html/2505.16210v1&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Gemini Developer API pricing, accessed November 27, 2025,    &lt;a href="https://ai.google.dev/gemini-api/docs/pricing?ref=edony.ink"&gt;https://ai.google.dev/gemini-api/docs/pricing&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Context caching overview | Generative AI on Vertex AI - Google Cloud Documentation, accessed November 27, 2025,    &lt;a href="https://docs.cloud.google.com/vertex-ai/generative-ai/docs/context-cache/context-cache-overview?ref=edony.ink"&gt;https://docs.cloud.google.com/vertex-ai/generative-ai/docs/context-cache/context-cache-overview&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Context Caching In Google Gemini: Better Than RAG For Memory - Empathy First Media, accessed November 27, 2025,    &lt;a href="https://empathyfirstmedia.com/context-caching-google-gemini/?ref=edony.ink"&gt;https://empathyfirstmedia.com/context-caching-google-gemini/&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Prompt Caching Support in Spring AI with Anthropic Claude, accessed November 27, 2025,    &lt;a href="https://spring.io/blog/2025/10/27/spring-ai-anthropic-prompt-caching-blog?ref=edony.ink"&gt;https://spring.io/blog/2025/10/27/spring-ai-anthropic-prompt-caching-blog&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Prompt caching - Claude Docs, accessed November 27, 2025,    &lt;a href="https://platform.claude.com/docs/en/build-with-claude/prompt-caching?ref=edony.ink"&gt;https://platform.claude.com/docs/en/build-with-claude/prompt-caching&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Prompt Caching is a Must! How I Went From Spending $720 to $72 Monthly on API Costs | by Du&amp;apos;An Lightfoot | Medium, accessed November 27, 2025,    &lt;a href="https://medium.com/@labeveryday/prompt-caching-is-a-must-how-i-went-from-spending-720-to-72-monthly-on-api-costs-3086f3635d63?ref=edony.ink"&gt;https://medium.com/@labeveryday/prompt-caching-is-a-must-how-i-went-from-spending-720-to-72-monthly-on-api-costs-3086f3635d63&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Prompt caching - OpenAI API, accessed November 27, 2025,    &lt;a href="https://platform.openai.com/docs/guides/prompt-caching?ref=edony.ink"&gt;https://platform.openai.com/docs/guides/prompt-caching&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Prompt Caching in the API - OpenAI, accessed November 27, 2025,    &lt;a href="https://openai.com/index/api-prompt-caching/?ref=edony.ink"&gt;https://openai.com/index/api-prompt-caching/&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;How does Prompt Caching work? - OpenAI Developer Community, accessed November 27, 2025,    &lt;a href="https://community.openai.com/t/how-does-prompt-caching-work/992307?ref=edony.ink"&gt;https://community.openai.com/t/how-does-prompt-caching-work/992307&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Context Cache feature of Qwen models - Alibaba Cloud Model Studio, accessed November 27, 2025,    &lt;a href="https://www.alibabacloud.com/help/en/model-studio/context-cache?ref=edony.ink"&gt;https://www.alibabacloud.com/help/en/model-studio/context-cache&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Qwen context window: token limits, memory policy, and 2025 rules - Data Studios, accessed November 27, 2025,    &lt;a href="https://www.datastudios.org/post/qwen-context-window-token-limits-memory-policy-and-2025-rules?ref=edony.ink"&gt;https://www.datastudios.org/post/qwen-context-window-token-limits-memory-policy-and-2025-rules&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Semantic Cache: How to Speed Up LLM and RAG Applications - Medium, accessed November 27, 2025,    &lt;a href="https://medium.com/@svosh2/semantic-cache-how-to-speed-up-llm-and-rag-applications-79e74ce34d1d?ref=edony.ink"&gt;https://medium.com/@svosh2/semantic-cache-how-to-speed-up-llm-and-rag-applications-79e74ce34d1d&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Semantic Cache: Accelerating AI with Lightning-Fast Data Retrieval - Qdrant, accessed November 27, 2025,    &lt;a href="https://qdrant.tech/articles/semantic-cache-ai-data-retrieval/?ref=edony.ink"&gt;https://qdrant.tech/articles/semantic-cache-ai-data-retrieval/&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Semantic caching for faster, smarter LLM apps - Redis, accessed November 27, 2025,    &lt;a href="https://redis.io/blog/what-is-semantic-caching/?ref=edony.ink"&gt;https://redis.io/blog/what-is-semantic-caching/&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;zilliztech/GPTCache: Semantic cache for LLMs. Fully integrated with LangChain and llama_index. - GitHub, accessed November 27, 2025,    &lt;a href="https://github.com/zilliztech/GPTCache?ref=edony.ink"&gt;https://github.com/zilliztech/GPTCache&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Reducing LLM Costs and Latency via Semantic Embedding Caching - arXiv, accessed November 27, 2025,    &lt;a href="https://arxiv.org/html/2411.05276v2?ref=edony.ink"&gt;https://arxiv.org/html/2411.05276v2&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;If your app process many similar queries, use Semantic Caching to reduce your cost and latency : r/LangChain - Reddit, accessed November 27, 2025,    &lt;a href="https://www.reddit.com/r/LangChain/comments/1f4rlx0/if_your_app_process_many_similar_queries_use/?ref=edony.ink"&gt;https://www.reddit.com/r/LangChain/comments/1f4rlx0/if_your_app_process_many_similar_queries_use/&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;图解vLLM Automatic Prefix Cache(RadixAttention),    &lt;a href="https://zhuanlan.zhihu.com/p/693556044?ref=edony.ink"&gt;https://zhuanlan.zhihu.com/p/693556044&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Gemini 3技术是跳蛙式超越    &lt;a href="https://www.youtube.com/watch?v=EMQxQwoFSb4&amp;ref=edony.ink"&gt;https://www.youtube.com/watch?v=EMQxQwoFSb4&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;Andre Ng issue 329 | deeplearning.ai batch    &lt;a href="https://www.deeplearning.ai/the-batch/issue-329/?ref=edony.ink"&gt;https://www.deeplearning.ai/the-batch/issue-329/&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt; &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>AI</category>
      <guid isPermaLink="true">https://itindex.net/detail/63096-kv-cache-prompt</guid>
      <pubDate>Sun, 30 Nov 2025 20:40:41 CST</pubDate>
    </item>
    <item>
      <title>丹麦拟禁止15岁以下人群使用社媒，并提议免纸质书籍税：孩子应走出网络囚禁</title>
      <link>https://itindex.net/detail/63062-%E4%B8%B9%E9%BA%A6-%E4%BA%BA%E7%BE%A4-%E4%B9%A6%E7%B1%8D</link>
      <description>&lt;br /&gt;当地时间10月7日，丹麦首相弗雷泽里克森宣布，该国拟禁止15岁以下未成年人使用社交媒体。  &lt;br /&gt;  &lt;p&gt;丹麦首相弗雷泽里克森表示，手机和社交媒体“偷走了我们孩子的童年”。她还说：“我们释放了一个怪物。以前从未有这么多儿童和年轻人遭受焦虑和抑郁的困扰。”&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;弗雷泽里克森在10月7日的议会演讲上宣布了这项提案。她强调，许多孩子在阅读和集中注意力方面出现了困难，他们在屏幕上看到了儿童或年轻人不应该看到的东西。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;丹麦60%11-19岁男孩 &lt;/p&gt;  &lt;p&gt;空闲时见不到一个朋友&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;弗雷泽里克森并没有具体说明新措施将影响哪些社交网络，但表示该提案将涵盖多个社交媒体平台。另外，13岁到15岁孩子的父母可以选择允许孩子使用社交媒体。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;弗雷泽里克森在演讲中引用的数据显示，丹麦60%的11岁至19岁男孩在空闲时间里见不到一个朋友，而94%的七年级学生在13岁之前就在社交媒体上建立了个人账户。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;丹麦数字部长卡罗琳·斯泰奇表示，丹麦政府的这项决定是一个突破。她说道：“我以前说过，现在再说一遍：我们太天真了。我们把孩子们的网络生活完全交给那些从未考虑过他们福祉的平台。”斯泰奇强调，孩子们应该从网络的囚禁中走出来，到实际生活中的社区中交朋友。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;此前，丹麦于2月份宣布，将在所有学校禁止使用手机。2024年，丹麦有5万人签署请愿书，呼吁禁止社交媒体。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;丹麦提议免书籍税&lt;/p&gt;  &lt;p&gt;    &lt;strong&gt;希腊提议欧盟设定“数字成年年龄”&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;    &lt;strong&gt;      &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;丹麦在鼓励支持民众降低使用社交媒体的频率方面，做出了众多努力。今年8月，丹麦政府提出提案，将免除对纸质书籍征收的25%增值税。丹麦文化部长表示，这一政策旨在应对该国日益严重的“阅读危机”，尤其是在年轻人群体中。通过免税降低书籍价格，将改善目前年轻人沉迷网络的现状。他希望在书籍和社交媒体的竞争中，创造一个更公平的环境。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;  &lt;p&gt;▲丹麦哥本哈根河边的阅读者&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;丹麦出版商协会主管克里斯汀·博德切尔-汉森称，丹麦政府这一举措，反映了政策上对书籍价值和阅读文化重要性的认可，“在反思和批判性思维至关重要的时代，这一点比以往任何时候都更加重要。”&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;根据丹麦统计局的数据，丹麦90%的人口都在使用社交媒体，这一比例在欧盟中最高。丹麦所有16-19岁的青少年都在使用社交媒体。而且，丹麦20-34岁的年轻人中，98%的人都在使用社交媒体。 &lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;根据丹麦数字事务部的一项新研究，61%的丹麦人表示，他们在社交媒体上花费的时间超出了他们的预期。丹麦政府健康委员会最近的一项研究发现，社交媒体成瘾在女孩中尤为普遍：31%的13岁至17岁女孩表现出中度或重度社交媒体成瘾的迹象。 &lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;在欧洲，有越来越多的人认为，政府需要加大对大型社交媒体平台的监管力度。而丹麦走在了前面。丹麦政府顾问、数字化与心理健康领域专家伊姆兰·拉希德表示，丹麦从今年7月至12月担任欧盟轮值主席国，这意味着该国可以“为未来的欧盟技术监管奠定基础”。据了解，欧盟已经开始研究如何迫使大型社交媒体公司改变其极易上瘾的算法。&lt;/p&gt;  &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;目前，澳大利亚已宣布禁止16岁以下人士使用脸书、油管等社交媒体平台。挪威总理斯特勒则表示，挪威将严格执行社交媒体的最低年龄限制，并且将使用社交媒体的最低年龄限制从13岁提高至15岁。斯特勒称，这将是一场“艰苦的战斗”，但政客们必须介入，保护儿童免受“算法的力量”的侵害。今年6月，希腊提议欧盟设定“数字成年年龄”，禁止儿童在未经父母同意的情况下访问社交媒体。&lt;/p&gt;  &lt;br /&gt;  &lt;img alt="&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/63062-%E4%B8%B9%E9%BA%A6-%E4%BA%BA%E7%BE%A4-%E4%B9%A6%E7%B1%8D</guid>
      <pubDate>Thu, 09 Oct 2025 06:25:10 CST</pubDate>
    </item>
    <item>
      <title>如何使用claude code开发完整的响应式web应用</title>
      <link>https://itindex.net/detail/63026-claude-code-%E5%BC%80%E5%8F%91</link>
      <description>&lt;div&gt;Claude Code 是由 Anthropic 开发的一款智能编码工具，能够通过自然语言指令辅助开发者快速生成代码、优化工作流程并构建应用。以下是一个基于 Claude Code 开发完整响应式 Web 应用的指南，结合了现代 Web 开发技术（如 React 和 Tailwind CSS），并通过实际案例展示具体步骤。以下内容基于 Claude Code 的功能特性，参考了网络资源和社区实践。&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;# 使用 Claude Code 开发完整的响应式 Web 应用&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;本指南将通过构建一个简单的响应式电影展示 Web 应用，展示如何利用 Claude Code 的智能代码生成、调试和优化功能，结合 React 和 Tailwind CSS，快速开发一个现代化的全栈应用。应用将包括前端界面、后端 API 以及响应式设计，确保在不同设备上都能良好运行。&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;## 准备工作&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;### 系统要求&lt;/div&gt; &lt;div&gt;- 安装 Node.js（建议版本 18 或以上）&lt;/div&gt; &lt;div&gt;- 安装一个支持 Claude Code 的开发环境（如 VS Code 或终端）&lt;/div&gt; &lt;div&gt;- 确保已安装 Git 用于版本控制&lt;/div&gt; &lt;div&gt;- （可选）Docker，用于快速部署或测试&lt;/div&gt; &lt;div&gt;- 访问 Claude API 或 Claude Code 工具（通过 Anthropic 官网申请或使用支持的 IDE 插件）&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;### 安装 Claude Code&lt;/div&gt; &lt;div&gt;1. **获取 Claude Code**：&lt;/div&gt; &lt;div&gt;   - 访问 Anthropic 官网（https://www.anthropic.com/api）申请 Claude API 密钥，或使用支持 Claude Code 的 IDE 插件（如 VS Code 的 Claude Code 扩展）。&lt;/div&gt; &lt;div&gt;   - 如果使用免费 API，可参考 Slack 获取方式（需要注意使用限制）。[](https://explinks.com/blog/unlocking-claudes-free-api-model-from-getting-started-to-practical-application/)&lt;/div&gt; &lt;div&gt;2. **配置开发环境**：&lt;/div&gt; &lt;div&gt;   - 在终端或 IDE 中安装 Claude Code，按照官方文档配置环境变量（如 API 密钥）。&lt;/div&gt; &lt;div&gt;   - 示例配置（.env 文件）：&lt;/div&gt; &lt;div&gt;     ```&lt;/div&gt; &lt;div&gt;     CLAUDE_API_KEY=your_api_key_here&lt;/div&gt; &lt;div&gt;     ```&lt;/div&gt; &lt;div&gt;3. **安装项目依赖**：&lt;/div&gt; &lt;div&gt;   - 初始化一个 React 项目：&lt;/div&gt; &lt;div&gt;     ```bash&lt;/div&gt; &lt;div&gt;     npx create-react-app movie-app&lt;/div&gt; &lt;div&gt;     cd movie-app&lt;/div&gt; &lt;div&gt;     ```&lt;/div&gt; &lt;div&gt;   - 安装 Tailwind CSS：&lt;/div&gt; &lt;div&gt;     ```bash&lt;/div&gt; &lt;div&gt;     npm install -D tailwindcss&lt;/div&gt; &lt;div&gt;     npx tailwindcss init&lt;/div&gt; &lt;div&gt;     ```&lt;/div&gt; &lt;div&gt;   - 配置 `tailwind.config.js`：&lt;/div&gt; &lt;div&gt;     ```javascript&lt;/div&gt; &lt;div&gt;     /** @type {import(&amp;apos;tailwindcss&amp;apos;).Config} */&lt;/div&gt; &lt;div&gt;     module.exports = {&lt;/div&gt; &lt;div&gt;       content: [&amp;quot;./src/**/*.{js,jsx,ts,tsx}&amp;quot;],&lt;/div&gt; &lt;div&gt;       theme: { extend: {} },&lt;/div&gt; &lt;div&gt;       plugins: [],&lt;/div&gt; &lt;div&gt;     };&lt;/div&gt; &lt;div&gt;     ```&lt;/div&gt; &lt;div&gt;   - 添加 Tailwind 指令到 `src/index.css`：&lt;/div&gt; &lt;div&gt;     ```css&lt;/div&gt; &lt;div&gt;     @tailwind base;&lt;/div&gt; &lt;div&gt;     @tailwind components;&lt;/div&gt; &lt;div&gt;     @tailwind utilities;&lt;/div&gt; &lt;div&gt;     ```&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;## 使用 Claude Code 开发应用&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;### 1. 设计应用原型&lt;/div&gt; &lt;div&gt;Claude Code 支持通过自然语言生成代码和原型设计。我们将构建一个电影展示应用，包含以下功能：&lt;/div&gt; &lt;div&gt;- 首页：显示电影列表（从公开 API 获取数据）&lt;/div&gt; &lt;div&gt;- 详情页：展示单部电影的详细信息&lt;/div&gt; &lt;div&gt;- 响应式布局：适配桌面端和移动端&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;**提示 Claude Code 生成原型**：&lt;/div&gt; &lt;div&gt;在 Claude Code 界面或终端输入：&lt;/div&gt; &lt;div&gt;&amp;gt; &amp;quot;为我生成一个基于 React 和 Tailwind CSS 的响应式电影展示应用原型，包含首页电影列表和详情页，使用免费的 TMDB API 获取数据。&amp;quot;&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;Claude Code 将生成以下结构（可能需要调整提示词以优化输出）：&lt;/div&gt; &lt;div&gt;- 项目结构：&lt;/div&gt; &lt;div&gt;  ```&lt;/div&gt; &lt;div&gt;  movie-app/&lt;/div&gt; &lt;div&gt;  ├── src/&lt;/div&gt; &lt;div&gt;  │   ├── components/&lt;/div&gt; &lt;div&gt;  │   │   ├── MovieList.js&lt;/div&gt; &lt;div&gt;  │   │   ├── MovieCard.js&lt;/div&gt; &lt;div&gt;  │   │   ├── MovieDetails.js&lt;/div&gt; &lt;div&gt;  │   ├── App.js&lt;/div&gt; &lt;div&gt;  │   ├── index.js&lt;/div&gt; &lt;div&gt;  │   ├── index.css&lt;/div&gt; &lt;div&gt;  ├── public/&lt;/div&gt; &lt;div&gt;  ├── package.json&lt;/div&gt; &lt;div&gt;  ├── tailwind.config.js&lt;/div&gt; &lt;div&gt;  ```&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;### 2. 生成前端代码&lt;/div&gt; &lt;div&gt;**使用 Claude Code 生成组件**：&lt;/div&gt; &lt;div&gt;- 输入提示：&lt;/div&gt; &lt;div&gt;  &amp;gt; &amp;quot;生成一个 React 组件 `MovieCard.js`，使用 Tailwind CSS 设计响应式卡片，显示电影海报、标题和评分，适配移动端和桌面端。&amp;quot;&lt;/div&gt; &lt;div&gt;- Claude Code 输出（示例）：&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;import React from &amp;apos;react&amp;apos;;&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;const MovieCard = ({ movie }) =&amp;gt; {&lt;/div&gt; &lt;div&gt;  return (&lt;/div&gt; &lt;div&gt;    &amp;lt;div className=&amp;quot;max-w-sm rounded overflow-hidden shadow-lg m-4 hover:scale-105 transition-transform&amp;quot;&amp;gt;&lt;/div&gt; &lt;div&gt;      &amp;lt;img&lt;/div&gt; &lt;div&gt;        className=&amp;quot;w-full h-64 object-cover&amp;quot;&lt;/div&gt; &lt;div&gt;        src={`https://image.tmdb.org/t/p/w500${movie.poster_path}`}&lt;/div&gt; &lt;div&gt;        alt={movie.title}&lt;/div&gt; &lt;div&gt;      /&amp;gt;&lt;/div&gt; &lt;div&gt;      &amp;lt;div className=&amp;quot;px-6 py-4&amp;quot;&amp;gt;&lt;/div&gt; &lt;div&gt;        &amp;lt;div className=&amp;quot;font-bold text-xl mb-2&amp;quot;&amp;gt;{movie.title}&amp;lt;/div&amp;gt;&lt;/div&gt; &lt;div&gt;        &amp;lt;p className=&amp;quot;text-gray-700 text-base&amp;quot;&amp;gt;评分: {movie.vote_average}&amp;lt;/p&amp;gt;&lt;/div&gt; &lt;div&gt;      &amp;lt;/div&amp;gt;&lt;/div&gt; &lt;div&gt;    &amp;lt;/div&amp;gt;&lt;/div&gt; &lt;div&gt;  );&lt;/div&gt; &lt;div&gt;};&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;export default MovieCard;&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;- 类似地，生成 `MovieList.js` 和 `MovieDetails.js`，用于展示电影列表和详情页。&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;### 3. 集成后端 API&lt;/div&gt; &lt;div&gt;使用 TMDB API（https://www.themoviedb.org/documentation/api）获取电影数据。&lt;/div&gt; &lt;div&gt;- **提示 Claude Code 生成 API 调用代码**：&lt;/div&gt; &lt;div&gt;  &amp;gt; &amp;quot;为 React 应用生成一个使用 fetch 调用 TMDB API 的函数，获取热门电影列表，并处理错误。&amp;quot;&lt;/div&gt; &lt;div&gt;- 示例输出：&lt;/div&gt; &lt;div&gt;```javascript&lt;/div&gt; &lt;div&gt;const API_KEY = &amp;apos;your_tmdb_api_key_here&amp;apos;;&lt;/div&gt; &lt;div&gt;const BASE_URL = &amp;apos;https://api.themoviedb.org/3&amp;apos;;&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;export const fetchMovies = async () =&amp;gt; {&lt;/div&gt; &lt;div&gt;  try {&lt;/div&gt; &lt;div&gt;    const response = await fetch(`${BASE_URL}/movie/popular?api_key=${API_KEY}`);&lt;/div&gt; &lt;div&gt;    if (!response.ok) throw new Error(&amp;apos;Failed to fetch movies&amp;apos;);&lt;/div&gt; &lt;div&gt;    const data = await response.json();&lt;/div&gt; &lt;div&gt;    return data.results;&lt;/div&gt; &lt;div&gt;  } catch (error) {&lt;/div&gt; &lt;div&gt;    console.error(&amp;apos;Error fetching movies:&amp;apos;, error);&lt;/div&gt; &lt;div&gt;    return [];&lt;/div&gt; &lt;div&gt;  }&lt;/div&gt; &lt;div&gt;};&lt;/div&gt; &lt;div&gt;```&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;- 在 `MovieList.js` 中集成：&lt;/div&gt; &lt;div&gt;  ```javascript&lt;/div&gt; &lt;div&gt;  import React, { useState, useEffect } from &amp;apos;react&amp;apos;;&lt;/div&gt; &lt;div&gt;  import MovieCard from &amp;apos;./MovieCard&amp;apos;;&lt;/div&gt; &lt;div&gt;  import { fetchMovies } from &amp;apos;./api&amp;apos;;&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;  const MovieList = () =&amp;gt; {&lt;/div&gt; &lt;div&gt;    const [movies, setMovies] = useState([]);&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;    useEffect(() =&amp;gt; {&lt;/div&gt; &lt;div&gt;      const loadMovies = async () =&amp;gt; {&lt;/div&gt; &lt;div&gt;        const data = await fetchMovies();&lt;/div&gt; &lt;div&gt;        setMovies(data);&lt;/div&gt; &lt;div&gt;      };&lt;/div&gt; &lt;div&gt;      loadMovies();&lt;/div&gt; &lt;div&gt;    }, []);&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;    return (&lt;/div&gt; &lt;div&gt;      &amp;lt;div className=&amp;quot;grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 p-4&amp;quot;&amp;gt;&lt;/div&gt; &lt;div&gt;        {movies.map((movie) =&amp;gt; (&lt;/div&gt; &lt;div&gt;          &amp;lt;MovieCard key={movie.id} movie={movie} /&amp;gt;&lt;/div&gt; &lt;div&gt;        ))}&lt;/div&gt; &lt;div&gt;      &amp;lt;/div&amp;gt;&lt;/div&gt; &lt;div&gt;    );&lt;/div&gt; &lt;div&gt;  };&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;  export default MovieList;&lt;/div&gt; &lt;div&gt;  ```&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;### 4. 实现响应式设计&lt;/div&gt; &lt;div&gt;Tailwind CSS 提供内置的响应式工具类（如 `sm:`, `md:`, `lg:`），确保应用在不同设备上的适配。&lt;/div&gt; &lt;div&gt;- **优化布局**：&lt;/div&gt; &lt;div&gt;  - 使用 `grid-cols-*` 实现动态网格布局。&lt;/div&gt; &lt;div&gt;  - 示例：`grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4` 确保小屏幕单列，大屏幕多列。&lt;/div&gt; &lt;div&gt;- **提示 Claude Code 优化响应式样式**：&lt;/div&gt; &lt;div&gt;  &amp;gt; &amp;quot;为 MovieCard 组件优化 Tailwind CSS 样式，确保在移动端和桌面端都有良好的视觉效果。&amp;quot;&lt;/div&gt; &lt;div&gt;- Claude Code 可能会建议添加：&lt;/div&gt; &lt;div&gt;  ```css&lt;/div&gt; &lt;div&gt;  @media (max-width: 640px) {&lt;/div&gt; &lt;div&gt;    .max-w-sm { max-width: 100%; }&lt;/div&gt; &lt;div&gt;    .h-64 { height: 16rem; }&lt;/div&gt; &lt;div&gt;  }&lt;/div&gt; &lt;div&gt;  ```&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;### 5. 测试与调试&lt;/div&gt; &lt;div&gt;- **使用 Claude Code 调试**：&lt;/div&gt; &lt;div&gt;  - 输入提示：“检查我的 React 代码，找出潜在的性能问题或错误。”&lt;/div&gt; &lt;div&gt;  - Claude Code 可识别问题，如未处理的 API 错误、重复渲染等，并建议优化方案。&lt;/div&gt; &lt;div&gt;- **运行测试**：&lt;/div&gt; &lt;div&gt;  - 运行项目：`npm start`&lt;/div&gt; &lt;div&gt;  - 检查移动端响应式效果：使用浏览器的开发者工具模拟不同设备。&lt;/div&gt; &lt;div&gt;- **自动化测试**：&lt;/div&gt; &lt;div&gt;  - 提示 Claude Code 生成测试用例：&lt;/div&gt; &lt;div&gt;    &amp;gt; &amp;quot;为 MovieList 组件生成 Jest 测试用例，测试 API 数据加载和渲染。&amp;quot;&lt;/div&gt; &lt;div&gt;  - 示例输出：&lt;/div&gt; &lt;div&gt;```javascript&lt;/div&gt; &lt;div&gt;import { render, screen, waitFor } from &amp;apos;@testing-library/react&amp;apos;;&lt;/div&gt; &lt;div&gt;import MovieList from &amp;apos;./MovieList&amp;apos;;&lt;/div&gt; &lt;div&gt;import { fetchMovies } from &amp;apos;./api&amp;apos;;&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;jest.mock(&amp;apos;./api&amp;apos;);&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;test(&amp;apos;renders movie list after fetching data&amp;apos;, async () =&amp;gt; {&lt;/div&gt; &lt;div&gt;  fetchMovies.mockResolvedValue([{ id: 1, title: &amp;apos;Test Movie&amp;apos;, vote_average: 8.0, poster_path: &amp;apos;/test.jpg&amp;apos; }]);&lt;/div&gt; &lt;div&gt;  render(&amp;lt;MovieList /&amp;gt;);&lt;/div&gt; &lt;div&gt;  await waitFor(() =&amp;gt; {&lt;/div&gt; &lt;div&gt;    expect(screen.getByText(&amp;apos;Test Movie&amp;apos;)).toBeInTheDocument();&lt;/div&gt; &lt;div&gt;  });&lt;/div&gt; &lt;div&gt;});&lt;/div&gt; &lt;div&gt;```&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;### 6. 部署应用&lt;/div&gt; &lt;div&gt;- **使用 Claude Code 编写部署脚本**：&lt;/div&gt; &lt;div&gt;  - 提示：“生成一个用于将 React 应用部署到 Vercel 的脚本。”&lt;/div&gt; &lt;div&gt;  - 示例输出：&lt;/div&gt; &lt;div&gt;```json&lt;/div&gt; &lt;div&gt;{&lt;/div&gt; &lt;div&gt;  &amp;quot;version&amp;quot;: 2,&lt;/div&gt; &lt;div&gt;  &amp;quot;builds&amp;quot;: [{ &amp;quot;src&amp;quot;: &amp;quot;package.json&amp;quot;, &amp;quot;use&amp;quot;: &amp;quot;@vercel/node&amp;quot; }],&lt;/div&gt; &lt;div&gt;  &amp;quot;routes&amp;quot;: [{ &amp;quot;src&amp;quot;: &amp;quot;/(.*)&amp;quot;, &amp;quot;dest&amp;quot;: &amp;quot;/&amp;quot; }]&lt;/div&gt; &lt;div&gt;}&lt;/div&gt; &lt;div&gt;```&lt;/div&gt; &lt;div&gt;- 部署步骤：&lt;/div&gt; &lt;div&gt;  1. 安装 Vercel CLI：`npm i -g vercel`&lt;/div&gt; &lt;div&gt;  2. 运行 `vercel` 命令，按照提示登录并部署。&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;## 优化与进阶&lt;/div&gt; &lt;div&gt;- **使用 Claude Code 优化代码**：&lt;/div&gt; &lt;div&gt;  - 提示：“优化 MovieList 组件，减少 API 调用次数并添加加载状态。”&lt;/div&gt; &lt;div&gt;  - Claude Code 可能建议添加加载状态和 memoization：&lt;/div&gt; &lt;div&gt;    ```javascript&lt;/div&gt; &lt;div&gt;    import React, { useState, useEffect, useMemo } from &amp;apos;react&amp;apos;;&lt;/div&gt; &lt;div&gt;    const MovieList = () =&amp;gt; {&lt;/div&gt; &lt;div&gt;      const [movies, setMovies] = useState([]);&lt;/div&gt; &lt;div&gt;      const [loading, setLoading] = useState(true);&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;      useEffect(() =&amp;gt; {&lt;/div&gt; &lt;div&gt;        const loadMovies = async () =&amp;gt; {&lt;/div&gt; &lt;div&gt;          setLoading(true);&lt;/div&gt; &lt;div&gt;          const data = await fetchMovies();&lt;/div&gt; &lt;div&gt;          setMovies(data);&lt;/div&gt; &lt;div&gt;          setLoading(false);&lt;/div&gt; &lt;div&gt;        };&lt;/div&gt; &lt;div&gt;        loadMovies();&lt;/div&gt; &lt;div&gt;      }, []);&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;      const memoizedMovies = useMemo(() =&amp;gt; movies, [movies]);&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;      return (&lt;/div&gt; &lt;div&gt;        &amp;lt;div&amp;gt;&lt;/div&gt; &lt;div&gt;          {loading ? (&lt;/div&gt; &lt;div&gt;            &amp;lt;p className=&amp;quot;text-center&amp;quot;&amp;gt;加载中...&amp;lt;/p&amp;gt;&lt;/div&gt; &lt;div&gt;          ) : (&lt;/div&gt; &lt;div&gt;            &amp;lt;div className=&amp;quot;grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 p-4&amp;quot;&amp;gt;&lt;/div&gt; &lt;div&gt;              {memoizedMovies.map((movie) =&amp;gt; (&lt;/div&gt; &lt;div&gt;                &amp;lt;MovieCard key={movie.id} movie={movie} /&amp;gt;&lt;/div&gt; &lt;div&gt;              ))}&lt;/div&gt; &lt;div&gt;            &amp;lt;/div&amp;gt;&lt;/div&gt; &lt;div&gt;          )}&lt;/div&gt; &lt;div&gt;        &amp;lt;/div&amp;gt;&lt;/div&gt; &lt;div&gt;      );&lt;/div&gt; &lt;div&gt;    };&lt;/div&gt; &lt;div&gt;    ```&lt;/div&gt; &lt;div&gt;- **集成 MCP（Model Context Protocol）**：&lt;/div&gt; &lt;div&gt;  - 如果需要更复杂的 AI 交互，可使用 MCP 协议连接 Claude Code 与数据源。[](https://www.apframework.com/blog/essay/2024-12-15-Claude-MCP)&lt;/div&gt; &lt;div&gt;  - 提示：“生成一个 MCP 配置文件，用于连接 Claude Code 和 Supabase 数据库。”&lt;/div&gt; &lt;div&gt;  - 示例输出：&lt;/div&gt; &lt;div&gt;```json&lt;/div&gt; &lt;div&gt;{&lt;/div&gt; &lt;div&gt;  &amp;quot;protocol&amp;quot;: &amp;quot;mcp&amp;quot;,&lt;/div&gt; &lt;div&gt;  &amp;quot;version&amp;quot;: &amp;quot;1.0&amp;quot;,&lt;/div&gt; &lt;div&gt;  &amp;quot;connections&amp;quot;: [&lt;/div&gt; &lt;div&gt;    {&lt;/div&gt; &lt;div&gt;      &amp;quot;type&amp;quot;: &amp;quot;database&amp;quot;,&lt;/div&gt; &lt;div&gt;      &amp;quot;provider&amp;quot;: &amp;quot;supabase&amp;quot;,&lt;/div&gt; &lt;div&gt;      &amp;quot;url&amp;quot;: &amp;quot;your_supabase_url&amp;quot;,&lt;/div&gt; &lt;div&gt;      &amp;quot;api_key&amp;quot;: &amp;quot;your_supabase_api_key&amp;quot;&lt;/div&gt; &lt;div&gt;    }&lt;/div&gt; &lt;div&gt;  ]&lt;/div&gt; &lt;div&gt;}&lt;/div&gt; &lt;div&gt;```&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;## 注意事项&lt;/div&gt; &lt;div&gt;- **API 限制**：Claude Code 的免费 API 可能有调用次数限制，建议升级到付费计划以获得更高配额。[](https://explinks.com/blog/unlocking-claudes-free-api-model-from-getting-started-to-practical-application/)&lt;/div&gt; &lt;div&gt;- **错误处理**：始终检查 Claude Code 生成的代码，确保错误处理逻辑完整。&lt;/div&gt; &lt;div&gt;- **安全性**：不要在前端代码中暴露 API 密钥，建议使用环境变量。&lt;/div&gt; &lt;div&gt;- **社区资源**：参考 Anthropic 官方手册和社区指南（如 GitHub 的 Claude Code Guide）获取更多高级功能。&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;## 总结&lt;/div&gt; &lt;div&gt;通过 Claude Code 的智能代码生成、调试和优化功能，结合 React 和 Tailwind CSS，您可以快速构建一个响应式的电影展示 Web 应用。Claude Code 的自然语言处理能力和终端集成使其成为开发者的强大助手，尤其适合快速原型设计和全栈开发。继续探索 Claude Code 的高级功能（如 MCP 集成和自动化测试），将进一步提升开发效率。&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;&amp;lt;/xaiArtifact&amp;gt;&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;**运行项目**：&lt;/div&gt; &lt;div&gt;1. 确保所有依赖已安装（`npm install`）。&lt;/div&gt; &lt;div&gt;2. 替换 `api.js` 中的 TMDB API 密钥（可从 TMDB 官网获取）。&lt;/div&gt; &lt;div&gt;3. 运行 `npm start` 启动开发服务器。&lt;/div&gt; &lt;div&gt;4. 访问 `http://localhost:3000` 查看应用。&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;**进一步学习**：&lt;/div&gt; &lt;div&gt;- 查阅 Anthropic 官方文档（https://www.anthropic.com/api）了解更多 Claude Code 功能。&lt;/div&gt; &lt;div&gt;- 参考社区教程，如 GitHub 上的 Claude Code Guide 或知乎上的最佳实践指南。[](https://zhuanlan.zhihu.com/p/1920263182062163086)&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;如果需要更详细的代码片段或特定功能的实现，请随时提供进一步的需求！&lt;/div&gt;
     
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/63026-claude-code-%E5%BC%80%E5%8F%91</guid>
      <pubDate>Fri, 11 Jul 2025 13:53:48 CST</pubDate>
    </item>
    <item>
      <title>如何为复杂的 Java 应用编写集成测试</title>
      <link>https://itindex.net/detail/62942-%E5%A4%8D%E6%9D%82-java-%E5%BA%94%E7%94%A8</link>
      <description>&lt;p&gt;最近有时间又把以前开源的   &lt;a href="https://github.com/crossoverJie/cim"&gt;IM 消息系统&lt;/a&gt;捡起来继续开发了（确实这些年经常有朋友催更）。&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;没错，确实是这些年，因为上次发版还是再 2019 年的八月份。&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;这段时间比较重大的更新就是把  &lt;a href="https://github.com/crossoverJie/cim/pull/140"&gt;元数据中心&lt;/a&gt;抽离出来了，以前是和 zookeeper 的代码强耦合在一起的，重构之后可以有多种实现了。&lt;/p&gt; &lt;p&gt;今后甚至可以提供一个 jar 包就可以把后端服务全部启动起来用于体验，此时就可以使用一个简单的基于内存的注册中心。&lt;/p&gt; &lt;p&gt;除此之外做的更多的就是新增了一个集成测试的模块，没有完善的集成测试功能在合并代码的时候都要小心翼翼，基本的功能需求都没法保证。&lt;/p&gt; &lt;p&gt;加上这几年我也接触了不少优秀的开源项目（比如 Pulsar、OpenTelemetry、HertzBeat 等），他们都有完整的代码合并流程；首先第一点就得把测试流水线跑通过。&lt;/p&gt; &lt;p&gt;这一点在 OpenTelemetry 社区更为严格：&lt;/p&gt; &lt;p&gt;  &lt;img src="https://s2.loli.net/2024/09/04/wlaPtbJNux5vc9n.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;他们的构建测试流程非常多，包括单元测试、集成测试、代码风格、多版本兼容等。&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;所以在结合了这些优秀项目的经验后我也为 cim 项目新增相关的模块   &lt;a href="https://github.com/crossoverJie/cim/pull/144"&gt;cim-integration-test&lt;/a&gt;，同时也在 github 上配置了相关的 action，最终的效果如下：&lt;/p&gt; &lt;p&gt;  &lt;img src="https://s2.loli.net/2024/09/04/p9tLcvPTlMBIVq4.png"&gt;&lt;/img&gt;  &lt;br /&gt;  &lt;img src="https://s2.loli.net/2024/09/04/Rxw5F8kNWT1pDO7.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;在   &lt;code&gt;“Build with Maven”&lt;/code&gt; 阶段触发单元测试和集成测试，最终会把测试结果上传到 Codecov，然后会在 PR 的评论区输出测试报告。  &lt;br /&gt;  &lt;img src="https://s2.loli.net/2024/09/04/zmPHB16ryCtGoEM.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;相关的 action 配置如下：&lt;/p&gt; &lt;p&gt;  &lt;img src="https://s2.loli.net/2024/09/05/mteK1Yj43Z7gIrR.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;就是配置了几个 Job，重点是这里的：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;mvn -B package --file pom.xml     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;它会编译并运行项目下面的所有 test 代码。&lt;/p&gt; &lt;h1&gt;  &lt;a href="http://crossoverjie.top/#cim-integration-test-&amp;#27169;&amp;#22359;" title="cim-integration-test &amp;#27169;&amp;#22359;"&gt;&lt;/a&gt;cim-integration-test 模块&lt;/h1&gt; &lt;p&gt;为了方便进行集成测试，我新增了   &lt;code&gt;cim-integration-test&lt;/code&gt; 这个模块，这里面没有任何源码，只有测试相关的代码。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://s2.loli.net/2024/09/05/RfK8FVrL916D7cI.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;类的继承关系图如下：&lt;/p&gt; &lt;p&gt;  &lt;img src="https://s2.loli.net/2024/09/05/75U9vbkPZOgqrRx.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;因为我们做集成测试需要把 cim 所依赖的服务都启动起来，目前主要由以下几个服务：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;cim-server: cim 的服务端&lt;/li&gt;  &lt;li&gt;cim-route: 路由服务&lt;/li&gt;  &lt;li&gt;cim-client: 客户端&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;而 route 服务是依赖于 server 服务，所以 route 继承了 server，client 则是需要 route 和 server 都启动，所以它需要继承 route。&lt;/p&gt; &lt;h2&gt;  &lt;a href="http://crossoverjie.top/#&amp;#38598;&amp;#25104;-test-container" title="&amp;#38598;&amp;#25104; test container"&gt;&lt;/a&gt;集成 test container&lt;/h2&gt; &lt;p&gt;先来看看 server 的测试实现：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;14     &lt;br /&gt;15     &lt;br /&gt;16     &lt;br /&gt;17     &lt;br /&gt;18     &lt;br /&gt;19     &lt;br /&gt;20     &lt;br /&gt;21     &lt;br /&gt;22     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;public abstract class AbstractServerBaseTest {       &lt;br /&gt;       &lt;br /&gt;    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName       &lt;br /&gt;            .parse(&amp;quot;zookeeper&amp;quot;)       &lt;br /&gt;            .withTag(&amp;quot;3.9.2&amp;quot;);       &lt;br /&gt;       &lt;br /&gt;    private static final Duration DEFAULT_STARTUP_TIMEOUT = Duration.ofSeconds(60);       &lt;br /&gt;       &lt;br /&gt;    @Container       &lt;br /&gt;    public final ZooKeeperContainer       &lt;br /&gt;            zooKeeperContainer = new ZooKeeperContainer(DEFAULT_IMAGE_NAME, DEFAULT_STARTUP_TIMEOUT);       &lt;br /&gt;       &lt;br /&gt;    @Getter       &lt;br /&gt;    private String zookeeperAddr;       &lt;br /&gt;       &lt;br /&gt;    public void startServer() {       &lt;br /&gt;        zooKeeperContainer.start();       &lt;br /&gt;        zookeeperAddr = String.format(&amp;quot;%s:%d&amp;quot;, zooKeeperContainer.getHost(), zooKeeperContainer.getMappedPort(ZooKeeperContainer.DEFAULT_CLIENT_PORT));       &lt;br /&gt;        SpringApplication server = new SpringApplication(CIMServerApplication.class);       &lt;br /&gt;        server.run(&amp;quot;--app.zk.addr=&amp;quot; + zookeeperAddr);       &lt;br /&gt;    }       &lt;br /&gt;}     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;因为   &lt;code&gt;server&lt;/code&gt; 是需要依赖   &lt;code&gt;zookeeper&lt;/code&gt; 作为元数据中心，所以在启动之前需要先把 zookeeper 启动起来。&lt;/p&gt; &lt;p&gt;此时就需要使用   &lt;a href="https://testcontainers.com/"&gt;testcontainer&lt;/a&gt; 来做支持了，使用它可以在单测的过程中使用 docker 启动任意一个服务，这样在 CI 中做集成测试就很简单了。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://s2.loli.net/2024/09/06/X3vzp5qd7tbAIHB.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;我们日常使用的大部分中间件都是支持的，使用起来也很简单。&lt;/p&gt; &lt;p&gt;先添加相关的依赖：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;14     &lt;br /&gt;15     &lt;br /&gt;16     &lt;br /&gt;17     &lt;br /&gt;18     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;&amp;lt;dependencies&amp;gt;     &lt;br /&gt;    &amp;lt;dependency&amp;gt;     &lt;br /&gt;        &amp;lt;groupId&amp;gt;org.postgresql&amp;lt;/groupId&amp;gt;     &lt;br /&gt;        &amp;lt;artifactId&amp;gt;postgresql&amp;lt;/artifactId&amp;gt;     &lt;br /&gt;        &amp;lt;version&amp;gt;42.7.3&amp;lt;/version&amp;gt;     &lt;br /&gt;    &amp;lt;/dependency&amp;gt;     &lt;br /&gt;    &amp;lt;dependency&amp;gt;     &lt;br /&gt;        &amp;lt;groupId&amp;gt;ch.qos.logback&amp;lt;/groupId&amp;gt;     &lt;br /&gt;        &amp;lt;artifactId&amp;gt;logback-classic&amp;lt;/artifactId&amp;gt;     &lt;br /&gt;        &amp;lt;version&amp;gt;1.5.6&amp;lt;/version&amp;gt;     &lt;br /&gt;    &amp;lt;/dependency&amp;gt;     &lt;br /&gt;    &amp;lt;dependency&amp;gt;     &lt;br /&gt;        &amp;lt;groupId&amp;gt;org.junit.jupiter&amp;lt;/groupId&amp;gt;     &lt;br /&gt;        &amp;lt;artifactId&amp;gt;junit-jupiter&amp;lt;/artifactId&amp;gt;     &lt;br /&gt;        &amp;lt;version&amp;gt;5.10.2&amp;lt;/version&amp;gt;     &lt;br /&gt;        &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;     &lt;br /&gt;    &amp;lt;/dependency&amp;gt;     &lt;br /&gt;&amp;lt;/dependencies&amp;gt;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;然后在选择我们需要依赖的服务，比如是   &lt;code&gt;PostgreSQL&lt;/code&gt;：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;&amp;lt;dependency&amp;gt;     &lt;br /&gt;    &amp;lt;groupId&amp;gt;org.testcontainers&amp;lt;/groupId&amp;gt;     &lt;br /&gt;    &amp;lt;artifactId&amp;gt;postgresql&amp;lt;/artifactId&amp;gt;     &lt;br /&gt;    &amp;lt;version&amp;gt;1.19.8&amp;lt;/version&amp;gt;     &lt;br /&gt;    &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;     &lt;br /&gt;&amp;lt;/dependency&amp;gt;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;然后在测试代码中启动相关的服务&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;14     &lt;br /&gt;15     &lt;br /&gt;16     &lt;br /&gt;17     &lt;br /&gt;18     &lt;br /&gt;19     &lt;br /&gt;20     &lt;br /&gt;21     &lt;br /&gt;22     &lt;br /&gt;23     &lt;br /&gt;24     &lt;br /&gt;25     &lt;br /&gt;26     &lt;br /&gt;27     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;class CustomerServiceTest {     &lt;br /&gt;     &lt;br /&gt;  static PostgreSQLContainer&amp;lt;?&amp;gt; postgres = new PostgreSQLContainer&amp;lt;&amp;gt;(     &lt;br /&gt;    &amp;quot;postgres:16-alpine&amp;quot;     &lt;br /&gt;  );     &lt;br /&gt;     &lt;br /&gt;  CustomerService customerService;     &lt;br /&gt;     &lt;br /&gt;  @BeforeAll     &lt;br /&gt;  static void beforeAll() {     &lt;br /&gt;    postgres.start();     &lt;br /&gt;  }     &lt;br /&gt;     &lt;br /&gt;  @AfterAll     &lt;br /&gt;  static void afterAll() {     &lt;br /&gt;    postgres.stop();     &lt;br /&gt;  }     &lt;br /&gt;     &lt;br /&gt;  @BeforeEach     &lt;br /&gt;  void setUp() {     &lt;br /&gt;    DBConnectionProvider connectionProvider = new DBConnectionProvider(     &lt;br /&gt;      postgres.getJdbcUrl(),     &lt;br /&gt;      postgres.getUsername(),     &lt;br /&gt;      postgres.getPassword()     &lt;br /&gt;    );     &lt;br /&gt;    customerService = new CustomerService(connectionProvider);     &lt;br /&gt;  }     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;通常情况下我们都是需要获取这些中间件的链接，比如 IP 端口啥的。&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;org.testcontainers.containers.ContainerState#getHost     &lt;br /&gt;org.testcontainers.containers.ContainerState#getMappedPort     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;通常是通过这两个函数来获取对应的 IP 和端口。&lt;/p&gt; &lt;h1&gt;  &lt;a href="http://crossoverjie.top/#&amp;#38598;&amp;#25104;" title="&amp;#38598;&amp;#25104;"&gt;&lt;/a&gt;集成&lt;/h1&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;14     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;@Container       &lt;br /&gt;RedisContainer redis = new RedisContainer(DockerImageName.parse(&amp;quot;redis:7.4.0&amp;quot;));       &lt;br /&gt;       &lt;br /&gt;public void startRoute() {       &lt;br /&gt;    redis.start();       &lt;br /&gt;    SpringApplication route = new SpringApplication(RouteApplication.class);       &lt;br /&gt;    String[] args = new String[]{       &lt;br /&gt;            &amp;quot;--spring.data.redis.host=&amp;quot; + redis.getHost(),       &lt;br /&gt;            &amp;quot;--spring.data.redis.port=&amp;quot; + redis.getMappedPort(6379),       &lt;br /&gt;            &amp;quot;--app.zk.addr=&amp;quot; + super.getZookeeperAddr(),       &lt;br /&gt;    };         &lt;br /&gt;    route.setAdditionalProfiles(&amp;quot;route&amp;quot;);       &lt;br /&gt;    route.run(args);       &lt;br /&gt;}     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;对于 route 来说不但需要   &lt;code&gt;zookeeper&lt;/code&gt; 还需要   &lt;code&gt;Redis&lt;/code&gt; 来存放用户的路由关系，此时就还需要运行一个 Redis 的容器，使用方法同理。&lt;/p&gt; &lt;p&gt;最后就需要以   &lt;code&gt;springboot&lt;/code&gt; 的方式将这两个应用启动起来，我们直接创建一个   &lt;code&gt;SpringApplication&lt;/code&gt; 对象，然后将需要修改的参数通过   &lt;code&gt;--varname=value&lt;/code&gt; 的形式将数据传递进去。&lt;/p&gt; &lt;p&gt;还可以通过   &lt;code&gt;setAdditionalProfiles()&lt;/code&gt; 函数指定当前应用运行的 profile，这样我们就可以在测试目录使用对应的配置文件了。&lt;/p&gt; &lt;p&gt;  &lt;img alt="image.png" src="https://s2.loli.net/2024/09/06/ySK2akYOIAPJqUH.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;route.setAdditionalProfiles(&amp;quot;route&amp;quot;);       &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;比如我们这里设置为 route 就可以使用   &lt;code&gt;application-route.yaml&lt;/code&gt; 作为 route 的配置文件启动，就不用每个参数都通过   &lt;code&gt;--&lt;/code&gt; 传递了。&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;14     &lt;br /&gt;15     &lt;br /&gt;16     &lt;br /&gt;17     &lt;br /&gt;18     &lt;br /&gt;19     &lt;br /&gt;20     &lt;br /&gt;21     &lt;br /&gt;22     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;private void login(String userName, int port) throws Exception {       &lt;br /&gt;    Long userId = super.registerAccount(userName);       &lt;br /&gt;    SpringApplication client = new SpringApplication(CIMClientApplication.class);       &lt;br /&gt;    client.setAdditionalProfiles(&amp;quot;client&amp;quot;);       &lt;br /&gt;    String[] args = new String[]{       &lt;br /&gt;            &amp;quot;--server.port=&amp;quot; + port,       &lt;br /&gt;            &amp;quot;--cim.user.id=&amp;quot; + userId,       &lt;br /&gt;            &amp;quot;--cim.user.userName=&amp;quot; + userName       &lt;br /&gt;    };       &lt;br /&gt;    client.run(args);       &lt;br /&gt;}       &lt;br /&gt;       &lt;br /&gt;@Test       &lt;br /&gt;public void olu() throws Exception {       &lt;br /&gt;    super.startServer();       &lt;br /&gt;    super.startRoute();       &lt;br /&gt;    this.login(&amp;quot;crossoverJie&amp;quot;, 8082);       &lt;br /&gt;    this.login(&amp;quot;cj&amp;quot;, 8182);       &lt;br /&gt;    MsgHandle msgHandle = SpringBeanFactory.getBean(MsgHandle.class);       &lt;br /&gt;    msgHandle.innerCommand(&amp;quot;:olu&amp;quot;);       &lt;br /&gt;    msgHandle.sendMsg(&amp;quot;hello&amp;quot;);       &lt;br /&gt;}     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;我们真正要测试的其实是客户端的功能，只要客户端功能正常，说明 server 和 route 也是正常的。&lt;/p&gt; &lt;p&gt;比如这里的   &lt;code&gt;olu(oline user)&lt;/code&gt; 的测试流程是：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;启动 server 和 route&lt;/li&gt;  &lt;li&gt;登录注册两个账号&lt;/li&gt;  &lt;li&gt;查询出所有用户&lt;/li&gt;  &lt;li&gt;发送消息&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;最终的测试结果如下，符合预期。&lt;/p&gt; &lt;p&gt;  &lt;img alt="image.png" src="https://s2.loli.net/2024/09/06/uX7BrNwC8iOHqSQ.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h1&gt;  &lt;a href="http://crossoverjie.top/#&amp;#30896;&amp;#21040;&amp;#30340;&amp;#38382;&amp;#39064;" title="&amp;#30896;&amp;#21040;&amp;#30340;&amp;#38382;&amp;#39064;"&gt;&lt;/a&gt;碰到的问题&lt;/h1&gt; &lt;h2&gt;  &lt;a href="http://crossoverjie.top/#&amp;#24212;&amp;#29992;&amp;#20998;&amp;#23618;" title="&amp;#24212;&amp;#29992;&amp;#20998;&amp;#23618;"&gt;&lt;/a&gt;应用分层&lt;/h2&gt; &lt;p&gt;不知道大家注意到刚才测试代码存在的问题没有，主要就是没法断言。&lt;/p&gt; &lt;p&gt;因为客户端、route、server 都是以一个应用的维度去运行的，没法获取到一些关键指标。&lt;/p&gt; &lt;p&gt;比如输出在线用户，当客户端作为一个应用时，在线用户就是直接打印在了终端，而没有直接暴露一个接口返回在线数据；收发消息也是同理。&lt;/p&gt; &lt;p&gt;其实在应用内部这些都是有接口的，但是作为一个整体的   &lt;code&gt;springboot&lt;/code&gt; 应用就没有提供这些能力了。&lt;/p&gt; &lt;p&gt;本质上的问题就是这里应该有一个 client-sdk 的模块，client 也是基于这个 sdk 实现的，这样就可以更好的测试相关的功能了。&lt;/p&gt; &lt;p&gt;之后就准备把 sdk 单独抽离一个模块，这样可以方便基于这个 sdk 实现不同的交互，甚至做一个 UI 界面都是可以的。&lt;/p&gt; &lt;h2&gt;  &lt;a href="http://crossoverjie.top/#&amp;#32534;&amp;#35793;&amp;#22833;&amp;#36133;" title="&amp;#32534;&amp;#35793;&amp;#22833;&amp;#36133;"&gt;&lt;/a&gt;编译失败&lt;/h2&gt; &lt;p&gt;还有一个问题就是我是直接将   &lt;code&gt;client/route/server&lt;/code&gt; 的依赖集成到   &lt;code&gt;integration-test&lt;/code&gt; 模块中：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;14     &lt;br /&gt;15     &lt;br /&gt;16     &lt;br /&gt;17     &lt;br /&gt;18     &lt;br /&gt;19     &lt;br /&gt;20     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;&amp;lt;dependency&amp;gt;       &lt;br /&gt;  &amp;lt;groupId&amp;gt;com.crossoverjie.netty&amp;lt;/groupId&amp;gt;       &lt;br /&gt;  &amp;lt;artifactId&amp;gt;cim-server&amp;lt;/artifactId&amp;gt;       &lt;br /&gt;  &amp;lt;version&amp;gt;${project.version}&amp;lt;/version&amp;gt;       &lt;br /&gt;  &amp;lt;scope&amp;gt;compile&amp;lt;/scope&amp;gt;       &lt;br /&gt;&amp;lt;/dependency&amp;gt;       &lt;br /&gt;       &lt;br /&gt;&amp;lt;dependency&amp;gt;       &lt;br /&gt;  &amp;lt;groupId&amp;gt;com.crossoverjie.netty&amp;lt;/groupId&amp;gt;       &lt;br /&gt;  &amp;lt;artifactId&amp;gt;cim-forward-route&amp;lt;/artifactId&amp;gt;       &lt;br /&gt;  &amp;lt;version&amp;gt;${project.version}&amp;lt;/version&amp;gt;       &lt;br /&gt;  &amp;lt;scope&amp;gt;compile&amp;lt;/scope&amp;gt;       &lt;br /&gt;&amp;lt;/dependency&amp;gt;       &lt;br /&gt;       &lt;br /&gt;&amp;lt;dependency&amp;gt;       &lt;br /&gt;  &amp;lt;groupId&amp;gt;com.crossoverjie.netty&amp;lt;/groupId&amp;gt;       &lt;br /&gt;  &amp;lt;artifactId&amp;gt;cim-client&amp;lt;/artifactId&amp;gt;       &lt;br /&gt;  &amp;lt;version&amp;gt;${project.version}&amp;lt;/version&amp;gt;       &lt;br /&gt;  &amp;lt;scope&amp;gt;compile&amp;lt;/scope&amp;gt;       &lt;br /&gt;&amp;lt;/dependency&amp;gt;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;在 IDEA 里直接点击测试按钮是可以直接运行这里的测试用例的，但是想通过   &lt;code&gt;mvn test&lt;/code&gt; 时就遇到了问题。&lt;/p&gt; &lt;p&gt;  &lt;img alt="image.png" src="https://s2.loli.net/2024/09/06/DFy6otpPvjar4JM.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;会在编译期间就是失败了，我排查了很久最终发现是因为这三个模块应用使用了springboot 的构建插件：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;&amp;lt;plugin&amp;gt;     &lt;br /&gt;&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;     &lt;br /&gt;&amp;lt;artifactId&amp;gt;spring-boot-maven-plugin&amp;lt;/artifactId&amp;gt;     &lt;br /&gt;&amp;lt;executions&amp;gt;     &lt;br /&gt;&amp;lt;execution&amp;gt;     &lt;br /&gt;&amp;lt;goals&amp;gt;     &lt;br /&gt;&amp;lt;goal&amp;gt;repackage&amp;lt;/goal&amp;gt;     &lt;br /&gt;&amp;lt;/goals&amp;gt;     &lt;br /&gt;&amp;lt;/execution&amp;gt;     &lt;br /&gt;&amp;lt;/executions&amp;gt;     &lt;br /&gt;&amp;lt;/plugin&amp;gt;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;这几个模块最终会被打包成一个 springboot 的 jar 包，从而导致 integration-test 在编译时无法加载进来从而使用里面的类。&lt;/p&gt; &lt;p&gt;暂时没有找到好的解决办法，我就只有把这几个插件先去掉，需要打包时再手动指定插件。&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;mvn clean package spring-boot:repackage -DskipTests=true     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;其实这里的本质问题也是没有分层的结果，最好还是依赖   &lt;code&gt;route&lt;/code&gt; 和   &lt;code&gt;server&lt;/code&gt; 的 SDK 进行测试。&lt;/p&gt; &lt;p&gt;现在因为有了测试的 CI 也欢迎大家来做贡献，可以看看这里的   &lt;code&gt;help want&lt;/code&gt;，有一些简单易上手可以先搞起来。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://s2.loli.net/2024/09/06/kmgfrIxdhGXib9L.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;a href="https://github.com/crossoverJie/cim/issues/135"&gt;https://github.com/crossoverJie/cim/issues/135&lt;/a&gt;&lt;/p&gt; &lt;p&gt;参考链接：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://github.com/crossoverJie/cim/pull/140"&gt;https://github.com/crossoverJie/cim/pull/140&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://github.com/crossoverJie/cim/pull/144"&gt;https://github.com/crossoverJie/cim/pull/144&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>cim test cim</category>
      <guid isPermaLink="true">https://itindex.net/detail/62942-%E5%A4%8D%E6%9D%82-java-%E5%BA%94%E7%94%A8</guid>
      <pubDate>Sun, 29 Sep 2024 11:37:13 CST</pubDate>
    </item>
    <item>
      <title>万物皆可“复制粘贴”！苹果商店新上的AR应用火了</title>
      <link>https://itindex.net/detail/62864-%E5%A4%8D%E5%88%B6-%E7%B2%98%E8%B4%B4-%E8%8B%B9%E6%9E%9C%E5%95%86%E5%BA%97</link>
      <description>&lt;blockquote&gt;
  &lt;p&gt;金磊 发自 凹非寺&lt;/p&gt;
  &lt;p&gt;量子位 | 公众号 QbitAI&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;现实里的  &lt;strong&gt;一切都可“复制粘贴”&lt;/strong&gt;，简直不要太好玩！&lt;/p&gt;
 &lt;p&gt;瞧，一位小可爱端了一杯茶送到老板桌前：&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/da53312652c5b708011ae9d4836512fc.gif"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;老板先是一愣，然后发出灵魂一问：&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/a8637db63f50fef5e373a74a29bd3c81.gif"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;或许你会困惑，茶不是明明就在那儿吗？但实际情况是这样的：&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/9cf563bba45579dbba2f965c5ea40de5.gif"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;很多小伙伴们应该秒懂了，这杯茶其实是“AR（增强现实）制造”。&lt;/p&gt;
 &lt;p&gt;如此效果，正是来自最近大火的  &lt;strong&gt;AR Code&lt;/strong&gt;。&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/316843b444c25faaa8168d2c8d9a213c.jpeg"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;而且它还是属于未发先火的那种，早在iOS 17发布之前，其效果就已经在推特上惊艳了不少网友。&lt;/p&gt;
 &lt;p&gt;现在随着App的正式推出，AR Code“一切皆可复制粘贴”的能力可谓是又火了一把。&lt;/p&gt;
 &lt;h1&gt;一切皆可“复制粘贴”&lt;/h1&gt;
 &lt;p&gt;那么这个AR Code到底“复制粘贴”能力几何？&lt;/p&gt;
 &lt;p&gt;刚才的那杯茶或许太小了，不足以展示它的全部功力。&lt;/p&gt;
 &lt;p&gt;那么我们就直接上一盏台灯，从头开始演示一遍。&lt;/p&gt;
 &lt;p&gt;在打开AR Code这款软件之后，我们首先需要做的就是  &lt;strong&gt;框选&lt;/strong&gt;中目标物体：&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/8d77aee12923aaf313c7eff1305d4fcd.gif"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;然后按照App给出的提示，“绕三圈”地给台灯做个全身扫描：&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/7d138ce5c00349c24204a2e74fd1a1b4.gif"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;在稍等片刻之后，一模一样的AR台灯就会出现在了屏幕之中：&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/c57bb565350bfde79a4b6e77e3803da9.gif"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;甚至你可以把AR台灯丢到地上，让它变大大大大大：&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/f891463ba43d02c0428de750d98c813c.gif"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;台灯还不够刺激？&lt;/p&gt;
 &lt;p&gt;那我们接下来去公园“复制粘贴”一把长椅。&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/5b31e37cb1dc1cf9344d3aacf2ca76d7.gif"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;把AR长椅放到真长椅旁边，简直毫无违和感：&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/f22f02039ad8aa82d43d1f094fb7f313.gif"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;还有更夸张的，巨大的雕像也能“复制粘贴”！&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/56e9311e7692bd48e0cf959db44fcb84.gif"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;如此效果，也难怪网友直呼：&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/4bacbc203fe82c1c9490403121d1e43b.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;h1&gt;怎么做到的？&lt;/h1&gt;
 &lt;p&gt;说到背后的技术，其实从这款App在App Store的全名中就可以挖掘一二。&lt;/p&gt;
 &lt;p&gt;在AR Code之后，还跟了一组单词——  &lt;strong&gt;Object Capture&lt;/strong&gt;（目标捕捉）。&lt;/p&gt;
 &lt;p&gt;Object Capture是苹果早在2021年的WWDC大会中亮相的一项技术。&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/cdd40888ff24b72a3af712d490e4868c.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;不过当时要想实现这种效果，操作上还是比较麻烦的。&lt;/p&gt;
 &lt;p&gt;用户需要先用iPhone或iPad拍照，上传到Mac电脑端，再进行3D建模。&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/27f8977e9bbcbcdda9eb118a4b5466ca.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/fa9c895d84ed555016e7de58398b4eb9.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;而在今年的WWDC 2023中，苹果对Object Capture做了升级，直接在iOS，即手机上便可完成。&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/2586d91ff9d36e837c9cba353a156a5c.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;而且从AR Code操作的方式上，我们也能看到，用户需要绕着物体转圈圈，然后从各个角度一直捕捉物体的各种细节。&lt;/p&gt;
 &lt;p&gt;本质上可以看做是识别物体的XYZ的三轴信息，并将其记录下来。&lt;/p&gt;
 &lt;p&gt;具体而言，苹果的Object Capture是通过RGB摄像头和激光雷达采集数据，通过算法，推算出物体XYZ轴上的位置数据，同时记录“贴图”数据，最终生成3D模型。&lt;/p&gt;
 &lt;p&gt;至于AR Code这家公司，它的创始人兼CEO叫做Maxime Maisonneuve。&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/92733ef32d34209437e346a8e3f4517b.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;在此之前，他也创立经营过Webcitric，而后做了近6年的自由职业；最终于2022年10月成立了AR Code。&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/57b682d660ff52857c2791e5cfbf4812.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;那么最后，还有小伙伴就要问了：&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;这个东西有什么用呢？&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;其实在现实特定场景中，我们还是可以用到它的。&lt;/p&gt;
 &lt;p&gt;例如在看家具的时候，如果不确定心仪的家具摆在家里是否合适，就可以把家具一键copy下来，在家里现场试一试了：&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/0fa8e1f66023333e1979068204a90c95.gif"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;那么你觉得这项技术还有什么有趣的玩法呢？&lt;/p&gt;
 &lt;h1&gt;One More Thing&lt;/h1&gt;
 &lt;p&gt;话说回来，开头送茶的小可爱又怎么样了呢？&lt;/p&gt;
 &lt;p&gt;“如喝”了茶的老板可以说是感动不已了：&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/f47a287b9df3d908c5a7bfb07bd08e3c.gif"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;div&gt;  &lt;img alt="&amp;#19975;&amp;#29289;&amp;#30342;&amp;#21487;&amp;#8220;&amp;#22797;&amp;#21046;&amp;#31896;&amp;#36148;&amp;#8221;&amp;#65281;&amp;#33529;&amp;#26524;&amp;#21830;&amp;#24215;&amp;#26032;&amp;#19978;&amp;#30340;AR&amp;#24212;&amp;#29992;&amp;#28779;&amp;#20102;" src="https://www.qbitai.com/wp-content/uploads/replace/a1aa7c75efa2115ffd3952b5ae7c91d7.jpeg"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;参考链接：  &lt;br /&gt;[1]https://ar-code.com/  &lt;br /&gt;
[2]https://www.youtube.com/watch?v=MGFGG8wPltU  &lt;br /&gt;
[3]https://www.youtube.com/watch?v=ASAz8x74VXw  &lt;br /&gt;
[4]https://ar-code.com/blog/ar-code-app-3d-object-capture-and-ar-qr-code-generation-on-ios-17&lt;/p&gt;
 &lt;p&gt;— 完 —&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>资讯 首页轮播 AR Object Capture</category>
      <guid isPermaLink="true">https://itindex.net/detail/62864-%E5%A4%8D%E5%88%B6-%E7%B2%98%E8%B4%B4-%E8%8B%B9%E6%9E%9C%E5%95%86%E5%BA%97</guid>
      <pubDate>Sat, 14 Oct 2023 14:21:44 CST</pubDate>
    </item>
    <item>
      <title>你的Spring Boot应用启动很慢？不妨试试这个工具！</title>
      <link>https://itindex.net/detail/62813-spring-boot-%E5%BA%94%E7%94%A8</link>
      <description>&lt;p&gt;睡不着闲逛，在GitHub上看到一个不错的开源项目：  &lt;strong&gt;Spring Startup Analyzer&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;从项目名称中就大概能猜到，这是一个分析Spring应用启动过程的工具。Spring Startup Analyzer通过采集Spring应用启动过程的数据，进而生成一个交互式的分析报告，帮助用户发现Spring应用启动慢的位置。同时，Spring Startup Analyzer还提供了Spring Bean异步初始化的工具，来帮助开发者加快Spring应用的启动时间。&lt;/p&gt; &lt;p&gt;下面一起来看看其提供的强大功能。&lt;/p&gt; &lt;h2&gt;  &lt;a href="https://blog.didispace.com/#&amp;#20998;&amp;#26512;&amp;#33021;&amp;#21147;" title="&amp;#20998;&amp;#26512;&amp;#33021;&amp;#21147;"&gt;&lt;/a&gt;分析能力&lt;/h2&gt; &lt;p&gt;我们可以先从该项目中给出HTML样例报告（  &lt;a href="https://linyimin-blog.oss-cn-beijing.aliyuncs.com/spring-satrtup-analyzer/hokage-20230618000928-192.168.0.101-analyzer.html" rel="external nofollow noopener noreferrer" target="_blank"&gt;点击这里查看&lt;/a&gt;）来看看它所提供的分析功能。&lt;/p&gt; &lt;p&gt;把报告内容的细节部分都收起来，可以看到如下图所示的内容：&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="https://blog.didispace.com/images2/202307/spring-satrtup-analyzer/1690049518900.png"&gt;&lt;/img&gt;  &lt;/p&gt; &lt;p&gt;主要有六个部分：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;启动的统计数据。其中包括：启动时间、Bean的数量、使用/总共的JAR包数量、未使用/总共的JAR包数量、ClassLoader数量&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;img alt="" src="https://blog.didispace.com/images2/202307/spring-satrtup-analyzer/1690049712845.png"&gt;&lt;/img&gt;  &lt;/p&gt; &lt;ul&gt;  &lt;li&gt;Spring Bean初始化数据。这里采集了每个Spring Bean的初始化时间及其细节内容&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;img alt="" src="https://blog.didispace.com/images2/202307/spring-satrtup-analyzer/1690049735608.png"&gt;&lt;/img&gt;  &lt;/p&gt; &lt;ul&gt;  &lt;li&gt;Bean初始化时间线。通过时间线的方式，清晰地展现了Spring应用启动时候，各个Bean的顺序关系以及时间消耗&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;img alt="" src="https://blog.didispace.com/images2/202307/spring-satrtup-analyzer/1690049783718.png"&gt;&lt;/img&gt;  &lt;/p&gt; &lt;ul&gt;  &lt;li&gt;方法调用的详细信息。这里统计了每个方法的调用时间、总时间开销和每次调用的平均时间&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;img alt="" src="https://blog.didispace.com/images2/202307/spring-satrtup-analyzer/1690049880585.png"&gt;&lt;/img&gt;  &lt;/p&gt; &lt;p&gt;点开之后，还能看到具体每次调用时候的时间开销和一些调用细节：&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="https://blog.didispace.com/images2/202307/spring-satrtup-analyzer/1690049910865.png"&gt;&lt;/img&gt;  &lt;/p&gt; &lt;ul&gt;  &lt;li&gt;启动后未使用的JAR。列出了所有Spring应用启动后没有使用的jar包，可以有效的帮助你清理不需要的依赖，为应用瘦身&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;img alt="" src="https://blog.didispace.com/images2/202307/spring-satrtup-analyzer/1690050005320.png"&gt;&lt;/img&gt;  &lt;/p&gt; &lt;ul&gt;  &lt;li&gt;应用启动过程的线程火焰图&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;img alt="" src="https://blog.didispace.com/images2/202307/spring-satrtup-analyzer/1690050108092.png"&gt;&lt;/img&gt;  &lt;/p&gt; &lt;h2&gt;  &lt;a href="https://blog.didispace.com/#&amp;#22914;&amp;#20309;&amp;#20351;&amp;#29992;" title="&amp;#22914;&amp;#20309;&amp;#20351;&amp;#29992;"&gt;&lt;/a&gt;如何使用&lt;/h2&gt; &lt;p&gt;通过上面的介绍，相信你已经了解该工具的强大之处了。接下来就可以通过下面的方法尝试分析一下自己的应用吧：&lt;/p&gt; &lt;p&gt;第一步：从里面的链接中下载最新的安装包&lt;/p&gt; &lt;p&gt;  &lt;a href="https://github.com/linyimin0812/spring-startup-analyzer/tags" rel="external nofollow noopener noreferrer" target="_blank"&gt;https://github.com/linyimin0812/spring-startup-analyzer/tags&lt;/a&gt;&lt;/p&gt; &lt;p&gt;第二步：解压下载的安装包，记住解压后的路径，下面一步要用&lt;/p&gt; &lt;p&gt;第三步：编辑Spring Boot的启动参数，包括：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;该工具采用agent的方式启动，所以要添加参数   &lt;code&gt;-javaagent:$HOME/spring-startup-analyzer/lib/spring-profiler-agent.jar&lt;/code&gt;，这里   &lt;code&gt;$HOME&lt;/code&gt;代表以前的解压路径，记得根据上面解压后的路径编辑这个参数&lt;/li&gt;  &lt;li&gt;配置分析工具的参数，这里根据自己需要添加即可，比如可以配置超时时间30分钟：   &lt;code&gt;-Dspring-startup-analyzer.app.health.check.timeout=30&lt;/code&gt;，其他可配置项如下表，你可以工具自己应用的情况去修改：&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;  &lt;img alt="" src="https://blog.didispace.com/images2/202307/spring-satrtup-analyzer/1690050542019.png"&gt;&lt;/img&gt;  &lt;/p&gt; &lt;p&gt;第四步：查看该工具的日志，可以通过  &lt;code&gt;$HOME/spring-startup-analyzer/logs&lt;/code&gt;路径，这里  &lt;code&gt;$HOME&lt;/code&gt;代表以前的解压路径，日志文件的类别为：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;startup.log: 启动过程中的日志&lt;/li&gt;  &lt;li&gt;transform.log: 被re-transform的类/方法信息&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;另外，该工具还支持自定义扩展，这里DD没试过，就不具体介绍了。感兴趣的童鞋可以根据文档去试试。&lt;/p&gt; &lt;h2&gt;  &lt;a href="https://blog.didispace.com/#&amp;#21551;&amp;#21160;&amp;#20248;&amp;#21270;" title="&amp;#21551;&amp;#21160;&amp;#20248;&amp;#21270;"&gt;&lt;/a&gt;启动优化&lt;/h2&gt; &lt;p&gt;这里提到了一个启动加速的优化思路，就是把一些耗时的Bean初始化改成异步就能实现。该项目提供了Bean的异步初始化工具，也非常好用，只需要下面几步就能完成。&lt;/p&gt; &lt;p&gt;第一步：引入依赖&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;&amp;lt;dependency&amp;gt;     &lt;br /&gt;    &amp;lt;groupId&amp;gt;io.github.linyimin0812&amp;lt;/groupId&amp;gt;     &lt;br /&gt;    &amp;lt;artifactId&amp;gt;spring-async-bean-starter&amp;lt;/artifactId&amp;gt;     &lt;br /&gt;    &amp;lt;version&amp;gt;2.0.2&amp;lt;/version&amp;gt;     &lt;br /&gt;&amp;lt;/dependency&amp;gt;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;第二步：配置参数&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;# 异步化的Bean可能在Spring Bean初始化顺序的末尾，导致异步优化效果不佳，打开配置优先加载异步化的Bean     &lt;br /&gt;spring-startup-analyzer.boost.spring.async.bean-priority-load-enable=true     &lt;br /&gt;# 指定异步的Bean名称     &lt;br /&gt;spring-startup-analyzer.boost.spring.async.bean-names=testBean,testComponent     &lt;br /&gt;# 执行异步化Bean初始化方法线程池的核心线程数     &lt;br /&gt;spring-startup-analyzer.boost.spring.async.init-bean-thread-pool-core-size=8     &lt;br /&gt;# 执行异步化Bean初始化方法线程池的最大线程数     &lt;br /&gt;spring-startup-analyzer.boost.spring.async.init-bean-thread-pool-max-size=8     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;第三步：检查Bean是否异步初始化。查看日志$HOME/spring-startup-analyzer/logs/startup.log文件，对于异步执行初始化的方法，会按照以下格式写一条日志:&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;async-init-bean, beanName: ${beanName}, async init method: ${initMethodName}     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;但是，作者在文档中也提到了，异步并不是万能的，你还需要注意以下这几点：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;应该优先从代码层面优化初始化时间长的Bean，从根本上解决Bean初始化耗时长问题&lt;/li&gt;  &lt;li&gt;对于二方包/三方包中初始化耗时长的Bean(无法进行代码优化)再考虑Bean的异步化&lt;/li&gt;  &lt;li&gt;对于不被依赖的Bean可以放心进行异步化，可以通过各个Bean加载耗时中的Root Bean判断Bean是否被其他Bean依赖&lt;/li&gt;  &lt;li&gt;对于被依赖的Bean需要小心分析，在应用启动过程中不能其他Bean被调用，否则可能会存在问题&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;好了，今天的学习就到这里！如果您学习过程中如遇困难？可以加入我们超高质量的  &lt;a href="https://blog.didispace.com/join-group-spring/index.html"&gt;Spring技术交流群&lt;/a&gt;，参与交流与讨论，更好的学习与进步！更多  &lt;a href="http://blog.didispace.com/spring-boot-learning-2x/"&gt;Spring Boot教程可以点击直达！&lt;/a&gt;，欢迎收藏与转发支持！&lt;/p&gt; &lt;p&gt;最后，奉上项目地址：  &lt;a href="https://github.com/linyimin0812/spring-startup-analyzer" rel="external nofollow noopener noreferrer" target="_blank"&gt;https://github.com/linyimin0812/spring-startup-analyzer&lt;/a&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>Spring Boot 开源 Spring Boot Spring</category>
      <guid isPermaLink="true">https://itindex.net/detail/62813-spring-boot-%E5%BA%94%E7%94%A8</guid>
      <pubDate>Sat, 22 Jul 2023 18:03:35 CST</pubDate>
    </item>
    <item>
      <title>交换机原理与应用（附数据链路层封装）</title>
      <link>https://itindex.net/detail/62797-%E4%BA%A4%E6%8D%A2%E6%9C%BA-%E5%8E%9F%E7%90%86-%E5%BA%94%E7%94%A8</link>
      <description>&lt;h2&gt;交换机原理&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;交换机的作用&lt;/strong&gt;:  &lt;br /&gt;
连接多个以太网物理段，隔离冲突域  &lt;br /&gt;
对以太网帧进行高速而透明的交换转发  &lt;br /&gt;
自行学习和维护MAC地址信息  &lt;br /&gt;
交换机工作在二层，可以用来隔离冲突域，在OSI参考模型中，二层的作用是寻址，这边寻址指的是MAC地址，而交换机就是对MAC地址进行转发，在每个交换机中，都有一张MAC地址表，这个表是交换机自动学习的。  &lt;br /&gt;
所以，总得来说交换机的作用是寻址和转发  ，但是要注意交换机用的是MAC地址  &lt;br /&gt;
  &lt;strong&gt;交换机的特点&lt;/strong&gt;:  &lt;br /&gt;
主要工作在OSI模型的物理层、数据链路层，提供以太网间的透明桥接和交换，依据链路层的MAC地址，将以太网数据帧在端口间进行转发  &lt;br /&gt;
  &lt;strong&gt;交换机的四个功能&lt;/strong&gt;：  &lt;br /&gt;
0.  学习功能---------数据帧经过交换机，交换机会自动学习数据帧的源mac地址
0.  转发功能---------数据帧经过交换机，会在mac表中查找数据帧的目的mac，查找成功，会从相应的接口转发数据
0.  查询功能（泛洪）---------数据帧经过交换机，会在mac表中查找数据帧的目的mac，查找失败，会从所有接口转发相同数据（泛洪）
0.  刷新功能---------交换机默认300s刷新一次mac地址表&lt;/p&gt;
 &lt;h3&gt;交换机运行原理&lt;/h3&gt;
 &lt;p&gt;第一次通信的时候，交换机处于 初始状态，mac 地址表的表项为空当主机 A 想和 主机B 进行通信的时候主机A 会发送 一个数据，设 数据帧的内容:  &lt;br /&gt;
源mac: 11-11-11  &lt;br /&gt;
目的mac: 22-22-22  &lt;br /&gt;
当这个数据帧经过交换机时，交换机会拆开 数据，分析源目mac地址交换机会自动 将源mac 地址和 交换机接口号 写入到表项中然后 去表项中查找 从接口 去往 目的mac ，发现表项中没有直接广播(除了发送接口)， 只有目的mac地址的 设备会回包，其余接口的 设备会丢弃回复方 主机B 也会发送一个数据帧，数据帧的内容  &lt;br /&gt;
源mac: 22-22-22  &lt;br /&gt;
目的mac: 11-11-11  &lt;br /&gt;
该数据帧经过交换机，交换机会将源mac地址和接口号写入 mac 地址表，自动学习。
然后会去 交换机的 mac地址表查找 去往目的mac地址的接口，发现表项 中有 并且是1号接口,接从 1号接口出去。  &lt;br /&gt;
由于 交换机mac地址表中有了A B主机的相关信息，所以下次AB 通信直接单播&lt;/p&gt;
 &lt;h2&gt;交换机应用&lt;/h2&gt;
 &lt;h5&gt;例一：不同域名计算机通过网关进行链接&lt;/h5&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/278ce631e62e4452b7f562e332ae0ffd~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;  &lt;br /&gt;
打开该软件ensp&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9f8977cb8e184a2a81f5f76c7ee7df61~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;  &lt;br /&gt;
选择新建拓扑，选择路由器，第三个&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9f14c77d6aeb486c9e712776389225f5~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;  &lt;br /&gt;
选择终端选择pc&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0326d33eb5d84e8c9b7f7250c9c97341~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;  &lt;br /&gt;
启动设备&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ed8af84065e84736978c667b84179fdf~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;  &lt;br /&gt;
配置PC地址和网关（注网关必须真实存在，所以后续需要路由器配置网关）&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b9a89014e91045afbb5362665f180514~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/be225bcd0ceb42178154e4c8e49548fe~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;  &lt;br /&gt;
接下来链接两台主机和路由器&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ea6c10cd79c44050bf67963b88269684~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4bba58490d0e4884b50491b618973cb1~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2a335a177fc64b4fb58baf17d718f62a~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;  &lt;br /&gt;
开始配置路由器网关&lt;/p&gt;
 &lt;h5&gt;命令：&lt;/h5&gt;
 &lt;p&gt;  &lt;strong&gt;sys &amp;lt;&amp;gt;-[]&lt;/strong&gt;
  &lt;strong&gt;sysname 改名&lt;/strong&gt;  &lt;br /&gt;
  &lt;strong&gt;u t m 关闭接口信息报告&lt;/strong&gt;  &lt;br /&gt;
  &lt;strong&gt;int gX/X/X 进入接口管理&lt;/strong&gt;  &lt;br /&gt;
  &lt;strong&gt;ip add 网关 掩码&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6ebc148a6e8a457390e806607c261cc6~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;  &lt;br /&gt;
进入配置后更改视图模式，然后更改名字（方便知道是哪台设备），int进入接口管理端，添加网关。接下来就进入了相同的网关建立链接。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0818827e6d6249dcbafd83efc7816d5c~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h5&gt;例二 交换机工作过程&lt;/h5&gt;
 &lt;p&gt;添加交换机&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ae7079e075a9486991ec3e676a1b7101~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;  &lt;br /&gt;
添加终端&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2a00b0ac50484d259c078016a3ad9bfa~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;  &lt;br /&gt;
链接交换机&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1a678837700a49f0a3e93ea404ceb878~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;  &lt;br /&gt;
配置计算机地址，因为同一ip下不用配置网关，为直观显示，更改MAC地址。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8efe3c22737b4b1a85b49ce0feeeb3d5~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/33548977253e45dfb6393a3641eb5428~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;
  &lt;img alt="image.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e3b9b068d16a403c9d05981f6f06bfe8~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;  &lt;br /&gt;
  &lt;strong&gt;命令：&lt;/strong&gt;  &lt;br /&gt;
  &lt;strong&gt;display mac-address mac地址表项&lt;/strong&gt;  &lt;br /&gt;
ping通后&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e496c83819f34f8f89f16f5270a1094f~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;  &lt;br /&gt;
在交换机可查看mac地址表项&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2a45517df3bd4018be53d5eb73314618~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;  &lt;br /&gt;
  &lt;strong&gt;该表格会默认保存300秒，如无继续使用会自动清除&lt;/strong&gt;&lt;/p&gt;
 &lt;h2&gt;数据链路层&lt;/h2&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1de8b518388746bc9dda2b6445ffac4d~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;前导码(Preamble) 包含八字节。前七字节的值为0xAA，最后一个字节的值为0xAB。在DIX以太网中，前导码被认为是物理层封装的一部分，而不是数据链路层的封装。&lt;/li&gt;
  &lt;li&gt;目的地址(DA) 包含六字节。DA标识了顿的目的站点的MAC地址。DA可以是单播地址 (单个目的组播地址 (组目的地)或广播地址。&lt;/li&gt;
  &lt;li&gt;源地址(SA) 包含六字节。SA标识了发送的站点的MAC地址。SA一定是单播地址(即第8位是0)。&lt;/li&gt;
  &lt;li&gt;类型域包含两字节，用来标识上层协议的类型，如0800H标识IP协议。&lt;/li&gt;
  &lt;li&gt;数据域包含46~1500字节。数据域封装了通过以太网传输的高层协议信息。高层协议要确保这个域至少包含46字节，如果实际数据不足46字节，则高层协议必须执行某些填充算法。数据域长度的上限是任意的，但目前已经被设置为1500字节，所以暂定46至1500字节。&lt;/li&gt;
  &lt;li&gt;顺校验序列(FCS)包合四字节。FCS是从DA开始到数据域结束这部分的校验和。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62797-%E4%BA%A4%E6%8D%A2%E6%9C%BA-%E5%8E%9F%E7%90%86-%E5%BA%94%E7%94%A8</guid>
      <pubDate>Wed, 05 Jul 2023 17:38:05 CST</pubDate>
    </item>
    <item>
      <title>应用不停服，风险可控！平滑升级分库分表去哪儿这么做~</title>
      <link>https://itindex.net/detail/62770-%E5%BA%94%E7%94%A8-%E9%A3%8E%E9%99%A9-%E5%B9%B3%E6%BB%91</link>
      <description>&lt;h1&gt;  &lt;strong&gt;作者介绍&lt;/strong&gt;&lt;/h1&gt;
 &lt;p&gt;  &lt;strong&gt;陈力&lt;/strong&gt;，2020年加入去哪儿网，有十余年互联网应用与游戏开发经验，目前专注于业务后台组件研发及业务系统的性能优化。&lt;/p&gt;
 &lt;h1&gt;  &lt;strong&gt;背景&lt;/strong&gt;&lt;/h1&gt;
 &lt;p&gt;分库分表是大型互联网应用经常采用的一种数据层优化方案，常见的分库分表中间件如 sharding-jdbc、mycat 都已经比较成熟，基本上可以应对我们一般的分库分表需求。做过分库分表的同学应该知道，在给业务系统做分库分表改造过程中，难的不是如何使用这些组件进行分库分表，而是如何将非分库分表的系统平滑的升级成一个分库分表的系统，升级期间业务不可暂停，升级过程及升级后风险可控，这个过程就像是给飞行中的飞机更换引擎，处理不好会产生重大的业务事故。去哪儿网机票辅营业务就经历过从主从读写分离系统升级到分库分表系统的过程，并在多次迭代过程中形成了一种与业务轻相关的平滑的分库分表方案，后续业务升级分库分表只需通过配置切换就可以将单库单表系统瞬切至分库分表系统。&lt;/p&gt;
 &lt;h1&gt;  &lt;strong&gt;一、原始问题&lt;/strong&gt;&lt;/h1&gt;
 &lt;p&gt;去哪儿网有自研的分库分表中间件 qdb, 是基于数据源进行分库分表的，它和那些开源的分库分表中间件一样，只解决了如何进行分库分表问题，没有解决如何将一个非分库分表系统升级至分库分表系统过度的问题。 如果我们直接使用 qdb 进行分库分表，不做任何过度方案，那么将有以下问题：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;升级过程中如果出现部分数据错误，如何回滚？ 如做回滚，新数据可能在回滚前落入新库，回滚后落入旧库，一部分数据在用户层面将看不到；如果错误只出现一次还好，可以通过洗数解决；但如果升级过程反复发现 bug，反复修订，一定会对业务造成影响；&lt;/li&gt;
  &lt;li&gt;迁移至分库分表后，为了保证数据被查询到且保证查询的性能，一般情况下 sql 的查询条件需要带上分表(片)键，但一个已经运转多年的业务系统它的 sql 肯定不能完全满足这个要求，如果进行全量的 sql 改写将是一个巨大的工作量，且有些业务场景根本就无法进行 sql 改写，比如辅营交易系统表的分表键一般是自身业务的订单号，但它有根据第三方券码查订单的客观需求( 一般是三方回调接口中)。&lt;/li&gt;
  &lt;li&gt;如何确定分库分表后的系统数据业务等价于分库分表前的系统。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;解决了这三个问题也就能顺利的从单库单表迁移至分库分表了。&lt;/p&gt;
 &lt;h1&gt;  &lt;strong&gt;二、第一次平滑迁移至分库分表的实践&lt;/strong&gt;&lt;/h1&gt;
 &lt;p&gt;简单来说第一次进行分库分表的平滑升级，其主要思路是：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;对数据进行双写；在分表键的基本之上增加了分表键映射的概念，通过 sql 条件分析自动或手动路由控制数据读写单库单表或分库分表；&lt;/li&gt;
  &lt;li&gt;再通过一种特殊的事务来实现的两套系统的一致性;&lt;/li&gt;
  &lt;li&gt;通过 iff 来确定两套数据库系统数据是等价的。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;这3个点分别对应解决上述的三个问题。&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;2.1 前置知识&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;为了方便理解后续内容，有必要对 mybatis 和 mybatis-spring 的一些原理作一些简单介绍，读者如果非常了解 spring 事务和 mybatis 的原码则可以跳过这一部分。&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;2.1.1 mybatis的整的框架&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/50430f6b88cd44d9bcda7d2fc3eba70e~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;接口层： 是 mybatis 提供给开发人员的 api,其主要是 SqlSession 对象， 开发人员通过 SqlSession 和 Mapper 接口来操作数据；平时我们做业务开发的时候感知不到 SqlSession，只是声明了一下 Dao 层的 Mapper 接口，就可以在 Spring 容器中拿到对应 Mapper 接口的实现来操作数据，这是因为框架帮我们做了很多事情，实际内部就是通 SqlSession 完成的，只是这个 SqlSession 的操作过程封装到了一个实现了 Mapper 接口的动态代理中，mybatis-spring 框架在扫苗包路径的时候将 Mapper 对应的动态代理实现注入到了 Spring 容器；对这块原理感兴趣的读取可以查询 mybatis 源码中 MapperProxy 及其相关类的实现。&lt;/li&gt;
  &lt;li&gt;数据处理层:  mybatis 的核心实现，主要是参数处理及 sql 解析、映射、执行、结果构建，详细处理流程见后文说明。&lt;/li&gt;
  &lt;li&gt;基础支撑层:  主要包括连接管理、事务管理、配置加载和缓存处理，将他们抽取出来作为最基础的组件，为上层的数据处理层提供最基础的支撑。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;  &lt;strong&gt;2.1.2 Mybatis-Spring及Mybatis的处理流程&lt;/strong&gt;&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/976f6ed6578e4ac8b8a1c62de9684618~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;
对这个图中涉及的原理做一个简单解释，读者如果对细节感兴趣，在随意起一个使用了 mybatis-spring 的项目，将图中关点节点打上断点观察即可。&lt;/p&gt;
 &lt;p&gt;  &lt;em&gt;   &lt;strong&gt;sql执行流程流程解释(红色组件部分)&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;由 SqlSession 开始， SqlSession 如上文所提及的是 mybatis 开放给用户顶层 api，它定义了 sql 操作的一个会话；SqlSession 通过Executor来完成操作；&lt;/li&gt;
  &lt;li&gt;Executor 是调度核心，它负责SQL语句的生成，调用 StatementHandler 访问数据库，查询缓存的维护，将 MappedStatement 对象进行解析，sql 参数转化、动态 sql 拼接，生成 jdbc Statement 对象；&lt;/li&gt;
  &lt;li&gt;StatementHandler 封装了 JDBC Statement 操作，负责对 JDBCstatement 的操作，设置参数、将 Statement 结果集转换成List 集合，是真正访问数据库的地方；在 StatementHandler 和 JDBC  Statement 之间可以通过：&lt;/li&gt;
&lt;/ol&gt;
 &lt;ul&gt;
  &lt;li&gt;ParameterHandler 负责将用户传递的参数转换成；&lt;/li&gt;
  &lt;li&gt;ResultSetHandler 负责将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合；处理查询结果；&lt;/li&gt;
  &lt;li&gt;TypeHandler 负责 java 数据类型和 jdbc 数据类型之间的映射和转换。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;em&gt;   &lt;strong&gt;Dao接口对应bean的创建及调用实现（绿色部分）&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;ClassPathBeanDefinitionScanner 负现扫苗由 @MapperScan 注解描述的包路径，对符合条件的 Dao 接口通过 Spring 的 BeanDefinitionRegistry 进行注册，并将 BeanDefinition 的 beanClass 属性设置为 MapperFactoryBean；&lt;/li&gt;
  &lt;li&gt;大家在业务代码中通过 Spring 容器拿到的 Dao 的实际其实就是 MapperFactoryBean 的通过调用 FactoryBean 接口的 getObject() 获取的；&lt;/li&gt;
  &lt;li&gt;getObject() 方法是通过 SqlSession 的 getMapper 方法(参数是 Dao 接口的类名)获取到了，前文提到过了 MapperProxy 实例，它体质就是动态代码调用 SqlSession，只是调过过程中的参数是由环境、Configuration 及上下文中获取；&lt;/li&gt;
  &lt;li&gt;MapperFactoryBean 的 SqlSession 一般就是 SessionTemplate，SessionTemplate 是 Mybatis-Spring 给的 SqlSession 的标准实现，它的核心功能是通过 SqlSessionFactory 来获取实际的 SqlSession 和对 SqlUtils 对获取过程进行拦截；&lt;/li&gt;
  &lt;li&gt;SqlUtils 对获取 SqlSession 的拦截主要目的就是联结 Spring 的事务处理环境，它会判定如果是在事务环境中，同一事务下通过 SqlSessionHolder 复用 SqlSession。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;em&gt;   &lt;strong&gt;SessionFactory的注入(蓝色部分)&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;SqlSessionFactoryBean 对过继承 Spring 的扩展接口FactoryBean、InitializingBean 在 Spring 初始化 bean 的时候SqlSessionFactoryBean 通过调用自身的 buildSqlSessionFactory 来构建 SqlSessionFactory,这个构建过程要么是通过 xml 要么是通过注解，构建的时候也完成的 Configuration 的设置，这个Configuration 主要包括了 MappedStatement 和 Interceptor(插件)。&lt;/li&gt;
  &lt;li&gt;MappedStatement 就是用来存放我们 SQL 映射文件中的信息包括 sql 语句，输入参数，输出参数等等。一个 Dao 方法对应一个 MappedStatement 对象。&lt;/li&gt;
  &lt;li&gt;Interceptor  就是 mybatis 的插件，它通过责任链模式实现，可分别在 Executor 阶段或 StatementHanlder 执行价段进行拦截。&lt;/li&gt;
&lt;/ol&gt;
 &lt;h2&gt;  &lt;strong&gt;2.2 数据双写&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;升级期间继续保留的单库单表数据库，同时按新的规则建立分库分表。在辅营业务系统中，分库是以业务线为依据的，基本按照一个业务线一个库来划分，分表是以辅营系统自身的订单号为依据，以月为单位进行划分的(单号中含日期信息)。所谓数据双写，就是当业务需要进行数据进行增删改的时候，同时对两套数据库进行增删改；当业务需要读数据的时候，只需对一套数据库进行读即可。当我们确定在所有情况下，对分表进行读写与对单表进行读写是等效的时候，我们就可以下线单表那套数据源了。这个双写的过程不是由业务代码来完成的，而是通过 mybatis 插件来实现。使用 mybatis 插件来拦截 sql，更换 sqlSession 及改写 sql，可以覆盖当下所有的 sql 及未来可能出现的 sql，同时开关切换可细化到 DAO 层的一个具体方法上，单库切换成分库的过程就可以最小粒度到一条条Sq的l执行上，在渐近性升级过程中，可以一步一确认了，通过监控和日志观察，如发现存在问题可以立马切回，将发生错误的负面影响降到最低。另外，借助于去哪儿的配置中心 qconfig 的部分推送功能，可以将线上的应用进程先小部分切换，待确定稳定后再推送到全部实例。
  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/96826c9b10254cffa54abeef006ce862~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;
双写思路看着简单，但要真正实现双写并不容易，中间会引出新的技术问题：
第一个问题是，在 mybatis 内部如何正确切换执行的目标库；第二个问题是，在 mybatis 内部如何对 sql 的执行过程进行复制并用分库的执行结果替换掉原有的执行结果。&lt;/p&gt;
 &lt;p&gt;  &lt;em&gt;   &lt;strong&gt;对于第一个问题&lt;/strong&gt;&lt;/em&gt;
  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d4b806300a0f4286b24db484903fffba~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;我们第一版本的做法是直接从待切数据源(分表)中重新获取 sqlSession，通过 Spring 的事务管理 api 来判定当前是否属于事务环境，如果是事务环境，则先从线程上下文中获取，如果不存在再从待先切数据源中获取。在事务环境下，我们需要将切换过来的连接的autoCommit 属性设置为 false(autoCommit 属性为 true 的时候相当于是自动提交事务，基本就是一条sql执行在一个事务里，在事务环境下它需要为false，业务开发平时感知不到这个值的设置是因为 spring 事务框架自动帮我们做了这件事，现在由于我们自己从新的数据源中捞了一个不在标准 Spring 流程的连接，所以需要自己补一下这个连接的维护), 在事务提交时再设置成 false(相当于归还连接池时进行复位了)，这期间连接的获取和释放得小心，切过来的那个连接的 ConnectionHolder 和 SessionHolder 都需要补一下引用记数的维护，因为它在 Spring 和 mybatis 标准处理之外，如果不作处理就会出现连接泄露或复用了已经关闭的连接。&lt;/p&gt;
 &lt;p&gt;  &lt;em&gt;   &lt;strong&gt;对于第二个问题&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
 &lt;p&gt;这个处理包括解代理、复制参数、根据参数构建新的 statement、再通过反射调用来实现 sql 在待切数据源上执行。为什么重新构键一个 statement呢？这是因为 statement 是 jdbc 提供的操作数据库的接口和概念，一个 statement 是和一个 connection 相关连的，既然双写阶段两个 connection 同时存在，那么 statement 也是有两个，分别来做两个库的执行。
  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/160cdf6c941345e6a714f967ceb9ff01~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;
为了解决 mybatis 插件内部再次调用 sql(再次调用是原于下文中分表键的处理)出现上下文间的干扰，我们定义 sql 执行的父子上下文的概念，父上下文感知不到子上下文的存在，子上下文对变量做的任何修改、覆盖或添加只在子上下文中有效，在父上下文环境下都是无效的，这相当于给子上下文开了一个安全的环境，在内部执行的 sql 不会对外层环境产生破坏。第二个问题是由我们的技术实现方案带来的新问题，所谓的上下文干扰一般包含分库分表中间件内部基于 ThreadLocal 做的一些变量记录，在 mybatis 插件内部再次调用另一条 sql 时可能就会出插件内调用的 sql 的上下文污染了原将要执行 sql 上下文。&lt;/p&gt;
 &lt;p&gt;双写其实可以在 mybatis 外部进行的， 在 mybatis 外部进行时就没有那么复杂的 statement 的复制和其参数的构建过程，但由于当时我们系统 mybatis 外部调用入口多且不统一，且先前在 Dao 层做了很多特殊注解和功能，这些功能没有考虑有两个完全一样的 Dao 的情况，直接在 mybatis 外部进行双写，改动太多其负面影响也不好预估，所以才在 mybatis 插件内部做了双写的实现。&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;2.3 分表键映射&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;通常不含分表键条件的 sql 来查询数据是不可避免的，一般的分库分表中间件对这种 sql 都是进行全表广播，其性能自然不太理想。为了解决非分表键全表广播的问题，我们提出了映射键的概念，映射键是相对分表键而言的，在 sql 查询中如不含分表键，就找映射键，再通过映射键到分表键，然后根据分表键计算出分表的物理坐标，最后通过分库分表中间件 sql 路由引导的 api (这里的概念如果不了解后面有解释)来引导分库分表中间件完成查询。映射键的映射关系维护在独立的映射表中，这个映射自身也是分表的，分表规则就是映射键的值的 hash。&lt;/p&gt;
 &lt;p&gt;举个例子，比如通过券码(couponId)查订单，那么这里的券码(couponId)就是映射键， 券码(couponId)的具体的值就是映射键的值，辅营自身的订单号(orderId)就是分表键，couponId→orderId 就形成了一个映射， 我们在 sql 查询中就可以只包含 couponId。除了这种直接映射外，还有一个间接映射，在辅营系统中，表面是按订单号进行分表的，本质是按订单号中的时间条件进行分表的，在上下文已知业务线的情况下，如果查询条件中包含订单号的创建时间，那么就算不含分表键和映射键也是可以对物理表坐标进行定位的，从而减少 sql 全表广播的可能。sql 执行过程可以抽象如下图:&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/eca49dbc37544d6794fd3c0ab60f674f~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;映射键思想的提出使得我们不用改写所有的 sql，大大提高了分库分表的适用范围。在实际开发过程中，还有一种 sql 也是无法改写的，那就是全表数据扫苗，比如我们要定期扫苗待过期的券码，在分库分表环境下应该怎么做呢？这里我们通过提供了一种手工定位和迭代所有物理库和物理表的 api，把形如 selectAll 的查询需求转化为对物理库和物理表下数据的分批访问，用户通过设置回调函数来处理每一批数据。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;2.4 diff和事务&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;diff 是指的是对单库单表和分库分表的内容进行比较，如果 diff 的结果一样，且主分是在一个事务中则可验证分库分表前后系统在业务上是等价的。然而，要做到这两点好像是不可能的，特别是事务，事务应该只在一个数据库会话中才是有效的。&lt;/p&gt;
 &lt;p&gt;理论上追求的是严紧、是必然；工程上追求的是可行、是可然和近然。接下来我们看一下 diff 和事务是如何实现的。&lt;/p&gt;
 &lt;p&gt;对于 diff，同步 diff 肯定影响性能，也不能进行采样化解，毕竟我们要确定全部数据是否一致；而异步 diff，可能由于读取的时间点不一样数据已经被改变了，这样就算 diff 结果显示不一致也不能说明同一时刻的数据是不一致的。我们最终的 diff 方案是离线 diff 加实时 diff 相结合的方式，通过 diff 一段时间，如果 diff 差异是收敛的说明细节的修订是有效的,  当24小时内偶发个位级别的不一致，我们就可以认为两边数据上基本等价了(实际上，我们最终 diff 差不多是0)。&lt;/p&gt;
 &lt;p&gt;离线 diff 就是对两套数据源当日之前的数据进行全量 diff，实时 diff 是指对当下数据操作进行 diff。实时 diff 先 diff 数据修改的返回结果，如果在数据增删改过程都不一样，那么数据读的过程就没必要进行 diff 了，毕竟在过度阶段双写是必然要进行了，直接拿双写的结果进行 diff 是没有额外性能开销的，待双写 diff 达到完全一致时，再有选择的分批对读进行 diff。为了不影响性能，读 diff 是异步的，前面也说过读 diff 不一致不能完成说明是数据是不一致的，但是可以作为一种参考，当 diff 出现不一致时我们打印出两边的线程堆栈来排查可能的不一致的原因。 我们最终以离线 diff 的为判定依据，实时 diff 还是多用于排查问题和确认问题。&lt;/p&gt;
 &lt;p&gt;再来说一下事务，事务用来保证两个地方的一致性。第一个是映射表与业务表的一致性，两方表任何一方漏数据必然导至业务在某个查询下检索不到数据，所以对于映射表的操作是和业务表的操作强绑在一个事务中。第二个是单库与分库在进行双写时也需要在一个事务中，这里显然要使用到分布式事务，传统的几种分布式事务都不适用我们的场景，不是需要一定的业务侵入配合就是性能上有影响，我们在这里采用了一种特殊的&amp;quot;分布式&amp;quot;事务的设计，既满足了性能要求，又能尽量做到一致性。其实现原理参见下图：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2f6cfd8baa1f49e0a8d6c4420009915b~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;事务管理器只能设置一个 DataSoure，当在事务环境下需要对另外一个数据源进行操作时，会将另一个数据源中获取的 connection 包在一个 Spring 的事务同步器中，并将这个 connection 的 autoCommit 属性设置为 false,  在同步器的回调函数 beforeCompletion 中分别增加 SqlSessionHolder 和 ConnectionHolder 引用计数(不增加会被 Spring和 mybatis 框架错误回收，到 afterCompletion 环节时连接就可能是已经关闭状态)， 在 afterCompletion 回调函数中根据事务状态对这个 connection 做提交或回滚，并分别将 SqlSessionHolder 和 ConnectionHolder 引用计数减一，将 autoCommit 重置为 true。 这个相当于一个数据源使用的 spring 事务框架事务，另外一个借助它的扩展手工处理事务， 虽然从严格意义上来说它们不是一个完整的事务，但是两个事务关联在一起只有后者(手工的那个)提交失败，前者提交成功才会引发不一致，出现这种情况的时间窗口很小，且在前者与后者间加段监控可以监测到这种现象的出来。 我们上线后通过观察没有出现过这种情况，只在人为测试制造这种 case 的时候才会出现，其他情况两个事务的状态完全一致。&lt;/p&gt;
 &lt;h1&gt;  &lt;strong&gt;三、新的问题&lt;/strong&gt;&lt;/h1&gt;
 &lt;p&gt;上述设计虽然帮助我们完成了辅营交易数据库从单库单表平滑迁移到分库分表，但是也存在一些后续问题，这些问题主要表现在以下方面:&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;测试开发不友好，分库分表的设计很重，如果所写的测试关系到数据层的话则需要依赖一整套分库分表环境，这个环境的建立是有成本的，结果大家只是依赖公共的测试环境，多人依赖测试数据容易有冲突，且对于本地测试极度不友好，集中表现在写本地单元测试时，启动一套分库分表过程很慢。&lt;/li&gt;
  &lt;li&gt;维护成本有点高，这个与方案本身的关系可能不太大，主要是技术实现细节上造成的。早期，主分之间的路由判定依赖于大量的注解和配置中心的配置，还有项目中的各种配置，新加分表关注点很多，如果不是很了解原理和技术的实现细节很容易错配，从而导致事故。&lt;/li&gt;
  &lt;li&gt;不好复用，实现上有一些业务侵入，比如依赖从spring 容器中取数据源的 bean; 分库分表规则也是一次性的，如果未来有变化也没有扩展点，比如说从一月分一次分表，改为一周分一次分表，那么就会出现新旧不兼容。这套代码也不好做到从一个项目迁一到另一个需要分库分表的项目中直接复用，迁代码需要大量修订。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;正因为上述问题的存在，我们在辅营 DDD 重构微服务拆分过程中，将这个分库分表方案进行组件化。除了方便方案更好的复用外， 在易用性上做了很多的提升，可以很方便的切换单库和分库的环境，也可以很方便的修改分库规则；这对于新建的 DDD 项目是非常提效的， 结合单测工具，在项目初建的时候可以完全只考虑业务领域模型问题，将分库分表后置，待业务逻辑跑通后，先配置单库验证订单数据的完整生命周期，无误后再通过一点配置就切换至分库分表环境了，且在开发过程中如表结果发生表数量表结构的变化可以随意修改分表规则配置，不会引起业务代码的改写。&lt;/p&gt;
 &lt;h1&gt;  &lt;strong&gt;四、分库分表平滑迁移组件化&lt;/strong&gt;&lt;/h1&gt;
 &lt;p&gt;如何将这个方案组件化，并且让大家在接入的时候做到最少知道，不必关心组件自身原理和实现细节呢？在谈具体实现过程前，再给大家普及一些分库分表中间件原理的基础知识，了解这部分的同学可以跳过。&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;4.1 前置知识&lt;/strong&gt;&lt;/h2&gt;
 &lt;h3&gt;  &lt;strong&gt;4.1.1 关键名称解释&lt;/strong&gt;&lt;/h3&gt;
 &lt;ul&gt;
  &lt;li&gt;分⽚键: ⽤于分⽚的字段，是将数据库（表）⽔平拆分的关键字段。&lt;/li&gt;
  &lt;li&gt;逻辑表: 是指一组具有相同逻辑和数据结构表的总称。&lt;/li&gt;
  &lt;li&gt;物理表: 与逻辑表对应，一个 order_form 可以被拆成多个物理表。&lt;/li&gt;
  &lt;li&gt;分片策略: 分⽚键 + 分⽚算法, 分片策略是 sql 进行路由的依据。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;  &lt;strong&gt;4.1.2分库分表中间件的基本原理&lt;/strong&gt;&lt;/h3&gt;
 &lt;p&gt;当我们执行一条 sql 的时候，分库分表中间件会对这条 sql 进行分析，根据配置的分片策略将数据路由到对应的物理表，具体过程如下图
  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fb6425efb33046f1a4cf4e055493a4c0~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt; 
一般的分库分表中间件都是在 Datasource 层面做数据库的路由，内部一般维护一个 dataSourceMap 的对象，key 就是分库时的分片键; 在 connection 或 statement 上做分表的路由，在 Resultset 上做结果数据的 merge。&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;4.2 设计思路&lt;/strong&gt;&lt;/h2&gt;
 &lt;h3&gt;  &lt;strong&gt;4.2.1 设计定位&lt;/strong&gt;&lt;/h3&gt;
 &lt;p&gt;本着不重复造轮子的原则，我们基础的分库分表能力还是借助现有的分库分表中间件，我们要做的是辅助分库分表中间件适配更多的 sql 场景和做好 sql 分发，所以我们定位在分库分表中间件上层做 plus。&lt;/p&gt;
 &lt;h3&gt;  &lt;strong&gt;4.2.2 设定切入点&lt;/strong&gt;&lt;/h3&gt;
 &lt;p&gt;组件化要求尽量做到对业务透明，为了满足这一要求就要从现有数据层中找核心概念(接口)进行扩展，我们来看看数据层一些核心概念及其所属的位置。
  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dbe8cbe36a1e4f0aa5c49a5e31642e02~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;
一般分库分表中间件都是从 DataSource 或 connection 之后开始做扩展的， 由于我们的系统中固定使用了 Spring 和 MyBatis，所以我们可以从 Spring 和 Mybatis 开始做扩展，这样双写或多写逻辑就可以在 mybatis 外部进行，实现上更容易，且不用改变 mybatis 内部的默认逻辑，没有 mybatis 本身升级所带来的兼容风险，同时可以对 mybatis 做一次增强，例如根据用户配置的分片键默认生成一批常见的 sql 映射到 BaseMapper 中，减少业务研发日常编写代码工作量(关于对 mybatis 增强这块不在本文讨论的范围，有兴趣的同学可以去看 mybatis-plus 的原码，原理是相同的)。&lt;/p&gt;
 &lt;h3&gt;  &lt;strong&gt;4.2.3 路由引导&lt;/strong&gt;&lt;/h3&gt;
 &lt;p&gt;是指对分库分表中间件分发 sql 的过程进行引导，使其按期望的过程进行，具体来说就在逻辑表转化成物理表的过程中指定转换的范围。根据前面介绍的分库表分中间件的原理，这个 api 就算中间件不提供也可以自己适配一个出来，通常可以通过自定义分片策略来造出来。去哪儿的分库分表中间件 qdb 直接提供了路由干预的 api，或者说是手动路由 api。&lt;/p&gt;
 &lt;h3&gt;  &lt;strong&gt;4.2.4 sql路由&lt;/strong&gt;&lt;/h3&gt;
 &lt;p&gt;sql 路由是实现整个分库分表增强的核心，在执行流程到达分库分表中间件之前先通过我们的组件进 sql 路由，具体路由过程见下图所示:
  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ebca3b7971e64b7cb101db014081eb8d~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;
注意该图路由范围仅画出分库分表中间件之上的部分，分库分表中间件内部如何路由对我们来说是透明的, 也就是说我们是可以按需更换分库分表中间件的。&lt;/p&gt;
 &lt;p&gt;  &lt;em&gt;   &lt;strong&gt;流程解读：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;对于走分库还是非分库是在最开始的时候由用户配置来决定，如果用户配置中有分库分表中间件，走分库分表逻辑；如果是单库则走单库逻辑；如果分表库和非分表都有则两个各配置配置一个 SessionFactory，分库的 SessionFactory 管理的表走分表库逻辑，单库的 SessionFactory 管理的表走单库逻辑；这一点与辅营交易现有的 SessionFactory 的分工是不同的，辅营交易现有分库分表上实现上，主表库的 SessionFactory 还管理着分表库的表。 值得说明的一点的是，在我们的设计里不强调全局表和广播表的概念，取之以单独的主库表替代，这种方式经营成本更低，缺点是表分别位于分表库和主表库中，无法进行 join 查询。 事实上，我们在划分表空间时，根据 DDD 结果也会尽量将同一个业务领域的表划分到一起，以便其可以进行 join 查询；所以一般不会出来主表库要和分表库进行 join 的场景。&lt;/li&gt;
  &lt;li&gt;无论是分表还是单表，执行流程都会进入 SqlRouteInterceptor (mybatis的插件)，都会进行路由干预，因为主表库至少也是有读写分离控制要求的嘛。&lt;/li&gt;
  &lt;li&gt;是否进行路由干预是由有无映射表逻辑或业务层面调用路由干预 api 来进行判定的，如果没有那么直接走后面逻辑即可，如果有，则对 sql 类型和条件进行判定，对于有复杂查询条件的 sql 查询可以走 ES 和数据组宽表查询的接口(初版没有开发这个功能，后续可按情况添加）。&lt;/li&gt;
  &lt;li&gt;对于有映射表逻辑的 sql 操作，先从映射表中找出分表键，然后再能通过分库分表中间的路由引导 api 来指导分库分表中间的执行。&lt;/li&gt;
&lt;/ol&gt;
 &lt;h2&gt;  &lt;strong&gt;4.3 整体架构&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/44d2a9963ec4456fb77322f8f1ad5076~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;
总体来说一共分为三层。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;接入层：负责给应用接入提供稳定和兼容的接口， 其中的 spring 接入适配是在项目稳定后再视情况开发，一般是在 spring 环境上提供一些注解和 starter；&lt;/li&gt;
  &lt;li&gt;core 层: 路由逻辑的核心实现，并基于路由逻辑建立生命周期，提供插件化的扩展点，使外围功能可以以插件的方式开发；&lt;/li&gt;
  &lt;li&gt;存储层: 负责最终 sql 的执行，数据的最后落地， 与接入层配合实现事务，主要由数据源和 mybatis 组成。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;在实际编码实现过程中将 core 层和存储层放在一个工程 qmall_db_core 中， 将接入层单独放入另外一个工程 qmall_db_shell; 处于接入 层的 api 都会在后续版本升级过程中保证向下兼容。下面对核心部分实现和接入部分实现加以说明，存储部分的实现主要是对 mybatis 做的增强，不在本文讨论范围内。&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;4.4 核心实现&lt;/strong&gt;&lt;/h2&gt;
 &lt;h3&gt;  &lt;strong&gt;4.4.1 核心代码流程&lt;/strong&gt;&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9ec66466ba4947be901a40b74a81c965~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;
以 SqlRouterInterceptor（mybatis插件）为 sql 进入路由的入口，   QmallDataSourceSupport 为参数处理的入口。SqlRouteProcessor 用于组织协调各组件进行路由干预。SqlRouteProcessor 隔离了对 mybatis 的依赖(也就是它之后的调用不依赖于 mybatis)，汇集了宏观路由流程。其主要流程如下:&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;调用语法分析行到 SqlInfo, SqlInfo 中包含了后续 sql 路由分析的所有数据结构，如sql中含有的查询条件、sql中关系到的表和列、sql的类型等;&lt;/li&gt;
  &lt;li&gt;调用参数处理，将 DAO 中传递的参数填充到 SqlInfo 结构中, 以使后续流程可以很方便的找到列或条件对应的实参值；&lt;/li&gt;
  &lt;li&gt;根据 SqlInfo 的内容选择合适的路由策略 RouteStrategy，选择的路由策略过程就是匹配得分最高的一个策略，比如有两个读的策略，一个是按分片键路由，一个是按映射键路由，当 sql 条件中有分片键时会优先命中分片键路由，而没有分片键的时候将命中映射键路由，路由也可以定制化，当对于某个特殊的 sql 想走 es 索引时可以针对这个 sql 的 Dao 名加方法特定命中一个走 es 索引的路由；&lt;/li&gt;
  &lt;li&gt;路由策略根据操作的类型来组织路由规则，对于写是所有路由规则都执行，对于读只要一个规则判定成功就返回，这个写的路由规则通常就是维护映射键的映射表数据，而读的路由规则是从多个映射键中选择一个可行的映射规则找到映射键的值，然后通过映射键的值找到分表键的值，通过分表键加分片策略算出待查数据的物理坐标；&lt;/li&gt;
  &lt;li&gt;根据计算出物理坐标，调用分库分表的路由中间件的引导 api 来执行路由引导，这个引导 api 就是图中定义的 SqlRouteGuide 接口，不同的分库分表中间件可以对这个接口进行实现来完成与本组件的基础能力对接。(完整对接还要有配置适配上的对接)&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;strong&gt;4.4.2 关键点&lt;/strong&gt;
  &lt;em&gt;   &lt;strong&gt;语法分析与参数填充的实现&lt;/strong&gt;&lt;/em&gt;
语法分析主要借助 druid 的语法分析工具对 sql 进行解析并提取出期望的数据结构。参数填充这里使用了一些技巧，应用在接入本组件设置数据源的时候显式或隐式的将这个数据源包装成 QmallDataSourceSupport 的子类实现，通过 QmallDataSourceSupport 来获取的 connection 是一个被包装后的 ConnectionSqlParameterSupport 实例，这个实例在执行 prepareStatement 方法的时候返回的是我们通过动态代理 PreparedStatement 接口的实例，其实际调用过程是通过实现了 InvocationHandler 接口的 PreparedParameterSupport 类完成， PreparedParameterSupport 类的作用是前面的 PreparedStatement 实例在调用各种 set 方法时记录下当时的参数的位置 ，这个位置与占用符的位置刚好是一一对应的(sql 语法解释出来的内容顺序要与 sql 字符串中占位符的顺序一致)，所以可以非常准确的将参数回填时 SqlInfo 这个结构中。&lt;/p&gt;
 &lt;p&gt;有人可能要问，为什么不直接分析 mybatis 内部的那个参数结构呢？ 这个试过但有各种坑，mybatis 在 DAO 的参数处理过程中它自己会做一些处理，map 中有值相同 key 不同的重复内容且从那个 map 获取不到 key 也会抛异常，另外我们的设计也不太想和 mybatis 内部数据结构有耦合，否则若 mybatis 升级把这个数据结构改动了我们这个系统不就用不了啦。其实，还有一种实现就是使用自定义 mybatis 的 ParameterHandler，这个实现方式我们也做过，两相比较，还是包装代理 Connection 的方式更好，因 Connection 是可以执行流程中被很容易拿到的，附带一些功能很方便，而且与 mybatis 没有任何关系，就算不用 mybatis 用 springjdbc 这套方案仍然是有效的。&lt;/p&gt;
 &lt;p&gt;  &lt;em&gt;   &lt;strong&gt;路由策略RouteStartegy、路由规则Rule、分片策略间的关系&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
 &lt;p&gt;路由策略是由一组路由规则组成的，选择不同的路由策略就像选择不同的数据库索引。路由规则是决定数据如何路由的原子单元，比如一个映射键可以构建一个路由规则，多个映射键就构建多个路由规则，一条sql中是可能包括多个映射键的，它就有多个路由规则，这多个路由规则共同构成一个路由策略。对于写逻辑多个映射关系都需要维护，所以写逻辑的路由规则必须都生效；对于读逻辑只需要通过一个映射键到找了分表键，后面的映射键的路由规则就不需要执行了，所以读的时候只需要一个路由规则有效即可。路由规则是由配置的分库分表规则动态生成的，分库分表规则使用到了不同的分片策略。&lt;/p&gt;
 &lt;p&gt;  &lt;em&gt;   &lt;strong&gt;配置定义&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
 &lt;p&gt;我们来看一下分库分表规则在我们这个组件中如何定义的？在项目的 resource 目录下放置一个 sharding.properties 的配置文件，内容如下:&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;#库的前缀(这么做完全是为了照顾qdb的配置)
db.prefix=qmall_supply_ 

#分库配置db.index.qmall.flight={dbIndex: 0}
db.index.qmall.inter={dbIndex: 1}
db.index.qmall.ticket={dbIndex: 2}
db.index.qmall.hermes={dbIndex: 3} 

#分表键配置
sharding.user_info=[{shardingKey: &amp;apos;last_name&amp;apos;,intervalMonth:2,hashCount:0,startTime: &amp;apos;2020-11-01&amp;apos;},{shardingKey: &amp;apos;last_name&amp;apos;,intervalMonth:2,hashCount:2,startTime: &amp;apos;2024-07-01&amp;apos;},{shardingKey: &amp;apos;last_name&amp;apos;,intervalMonth:2,hashCount:1,startTime: &amp;apos;2021-07-01&amp;apos;},{shardingKey: &amp;apos;last_name&amp;apos;,intervalMonth:1,hashCount:2,startTime: &amp;apos;2022-07-01&amp;apos;}]
sharding.supply_order=[{shardingKey: &amp;apos;supply_order_id&amp;apos;,intervalMonth:1,hashCount:2,startTime: &amp;apos;2022-11-01&amp;apos;, hashGroupReg: &amp;apos;20[0-9]{2}(0[1-9]|1[0-2])[0-9]{6}&amp;apos;}]
sharding.supply_order_ext=[{shardingKey: &amp;apos;supply_order_id&amp;apos;,intervalMonth:1,hashCount:2,startTime: &amp;apos;2022-11-01&amp;apos;}] 

#分表键日期提取正则(视情况可选)
shardingKey.extract.date.pattern={supply_order_id: &amp;apos;20[0-9]{2}(0[1-9]|1[0-2])&amp;apos;}

#映射键配置, priority越大，优先级越高, priority不可出现相同的值
table.supply_order=[{mapKey: &amp;apos;business_order_id&amp;apos;, type: &amp;apos;one2many&amp;apos;, priority: 1, maintain: &amp;apos;auto_manual&amp;apos;}]
table.user_info=[{mapKey: &amp;apos;id&amp;apos;, type: &amp;apos;one2one&amp;apos;, priority: 1},{mapKey: &amp;apos;phone&amp;apos;, type: &amp;apos;one2one&amp;apos;, priority: 1, maintain: &amp;apos;auto_manual&amp;apos;}]
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;从这个配置文件中我们可以看到，分库配置按我们内容业务线，分表配置是由分表键配置和映射键配置组成，分表键的分片算法配置项中目前只按我们自身业务需要支持的 hash 分片和时间分片，这两者可以同时使用。不知道读者有没有注意对于同一张表，我们可以有多条分片规则配置，它们主要是 startTime 不同，startTime 的含义是本条分片配置生效的起始时间，其作用时间范围直至出现下一个大于当前的 startTime，下一个 startTime 不出现则表示当条规则持续生效。一张表只有分表键配置存在时，映射键配置才有意义。正因为配置存在这样一些特点，我们可以通过配置平滑的把一个单表变成一个分表。比如，要将 supply_order 表从单表切换成分表，只需按下面进行分表配置即可&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;sharding.supply_order=[{shardingKey: &amp;apos;supply_order_id&amp;apos;,intervalMonth:0,hashCount:1,startTime: &amp;apos;2000-01-01&amp;apos;},{shardingKey: &amp;apos;supply_order_id&amp;apos;,intervalMonth:1,hashCount:2,startTime: &amp;apos;2023-11-01&amp;apos;, hashGroupReg: &amp;apos;20[0-9]{2}(0[1-9]|1[0-2])[0-9]{6}&amp;apos;}]
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;第一条配置作用时间是2000-01-01至2023-11-01，这期间是没有分表的；第二条配置作用从2023-11-01开始，每间隔一个月hash分两张表；这样就相当于业务无感知的从单表过渡到分表了。对于单库和分库的切换则使用的是多环境打包完成的，不同环境激活的是不同的数据源，单库激活的是单库的数据库连接池，多库激活多库的连接池。sharding.properties 这个配置文件也可以在不同环境中存在不同的内容；这样就可以很方便的做到本地测试用单库，测试和线上环境使用分库分表了。 注意要将不同环境的分表键及映射键的规则定义一致，这样在单库上能跑通的 sql 在分库分表环境上也不会有任何问题(因为只要分表的配置规则相同，即使底层数据源是单库或者没有用分库分表中间件，我们内部的那些路由判定规则一样会执行，不符合要求的 sql 是能暴露出来的)。&lt;/p&gt;
 &lt;p&gt;那么可能有人要问，你这里定义的分库分表规则，分库分表中间件里也定义了规则，两者有冲突怎么处理？&lt;/p&gt;
 &lt;p&gt;答案是没冲突。在决定开发配置的时候，我们就思考：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;新的组件是对已有分库分表中间件做 plus，所以必然会有一些新的配置需求，这些新的配置需求最好能很直观的与已有分库分表配置关联;&lt;/li&gt;
  &lt;li&gt;不要对已有分库分表中间件的配置文件做修改或对其有代码侵入，否则会增加用户的学习成本，而且从长期来看也会形成耦合，一旦分库分表中间件有大版本升级就不方便跟进了;&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;为此，我们做了两个件事：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;我们的组件不直接依赖任何底层数据源或分库分表的配置，只依赖 sharding.properties，数据源按标准接口接入即可。&lt;/li&gt;
  &lt;li&gt;分库分表中间件的配置统一使用自定义分片策略配置，由我们的组件根据不同的分库分表中间件的自定义分片策略接口来实现具体的分片，然后在分库分表中间件的配置文件中只配置我们自己的分片策略。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;第一件事是划清了本组件与分库分表中间件的边界，即双方只按标准接口对接；第二件事相当于是让分库分表中件间通过自定义策略的方式将它的分库分表规则委托给我们的组件，从而避免两头配置上的冲突，也就是最终如何分库分表将以我们的配置解释为准。&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;4.5 接入层实现&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;接入层分为四部分，限于篇幅，这里简单说明一下。&lt;/p&gt;
 &lt;p&gt;  &lt;em&gt;   &lt;strong&gt;基础接口&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
 &lt;p&gt;自定义 DataSouce 和自定义 SessionFactoryBean。 自定义 DataSouce 主要作用就是将对外部传入 DataSouce 进行包装，它的功能包括识别是否分库数据源、读写分离、多数源事务关联。SessionFactoryBean 的主要功能是组建初始化入口、自动生成分表、扫描 mybatis 的 mapper 文件初始化 mapper 实例。&lt;/p&gt;
 &lt;p&gt;  &lt;em&gt;   &lt;strong&gt;数据接口&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
 &lt;p&gt;主要有两个，一个是 DAOTemplate，模仿的是 JDBCTemplate,  与其不同的是它能很方便在分库分表环境下写各种临时 sql，适合测试场景写一条只在测试时才用的 sql 或一次性 sql；另一个是 BaseMapper, 它自带基础的增删改查功能，业务的 Mapper 继承于它可以省写很多常见的操作。&lt;/p&gt;
 &lt;p&gt;  &lt;em&gt;   &lt;strong&gt;路由接口&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
 &lt;p&gt;提供了手动指定路由过程的接口，如使用 SqlRouteHelper.runOnSpecificContext (SqlRouteCondition condition, Runnable runnable), 则  runnable 的运行过程中其内部的 sql 路由将受 condition 的影响，condition 的内容为是否走从库、走哪个物理库、哪些逻辑表、哪些物理表，这四方面内容可部分指定也可全部指定；除此之外，还提供了一些用于排查路由问题或数据问题的静态方法，比如通过映射键找分片键，通过映射键的值获取 db 索引等。&lt;/p&gt;
 &lt;p&gt;  &lt;em&gt;   &lt;strong&gt;事务接口&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
 &lt;p&gt;事务接口是对 DataSourceTransactionManager 与 TransactionTemplate 的继承，用于实现上文所提到的“分布式”事务。&lt;/p&gt;
 &lt;h1&gt;  &lt;strong&gt;总结&lt;/strong&gt;&lt;/h1&gt;
 &lt;p&gt;本文介绍了两次进行平滑分库分表的设计，第一次是在已经运行多年的系统上进行分库分表改造，这个过程为了求稳，主要采用了双写加 diff 的方式通过一条条的 sql 切换来降低升级过程中的风险，同时对于不支持分表键查询的 sql 采用了映射键和映射表的方式解决；第二次是在从旧系统拆分出新系统过程中，新系统也有分库分表需求，为了照顾新系统的易用性以及初始编码过程中可能出现的变化，减少底层分库分表的变化对上层业务编码的返工，在承接第一次方案的设计的基础上将方案进行了组件化。基于开发成本和开发时间的考虑，目前产出尽管不通用，但他完成了我们当时的首要目标——完成业务应用的 DDD 和微服务拆分。&lt;/p&gt;
 &lt;p&gt;通过本文介绍，我相信读者应该看到一种可能，那就是从单库单表系统至分库分表的系统的平滑过度是可能被中间件解决的。本文的产出是专用的，但思想通用的，随着实际场景的复杂，可能还会遇到更多问题，这需要我们开发者的更多的努力。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62770-%E5%BA%94%E7%94%A8-%E9%A3%8E%E9%99%A9-%E5%B9%B3%E6%BB%91</guid>
      <pubDate>Wed, 31 May 2023 17:26:48 CST</pubDate>
    </item>
    <item>
      <title>使用 Vue 3 时应避免的 10 个错误</title>
      <link>https://itindex.net/detail/62674-vue-%E9%94%99%E8%AF%AF</link>
      <description>&lt;p&gt;Vue 3已经稳定了相当长一段时间了。许多代码库都在生产环境中使用它，其他人最终都将不得不迁移到Vue 3。我现在有机会使用它并记录了我的错误，下面这些错误你可能想要避免。&lt;/p&gt; &lt;h2&gt;使用Reactive声明原始值&lt;/h2&gt; &lt;p&gt;数据声明在过去都是非常直接的，但是现在有很多帮助函数供我们使用。目前的规则是：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;使用   &lt;code&gt;reactive&lt;/code&gt;声明   &lt;code&gt;Object, Array, Map, Set&lt;/code&gt;&lt;/li&gt;  &lt;li&gt;使用   &lt;code&gt;ref&lt;/code&gt;声明   &lt;code&gt;String, Number, Boolean&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;为一个原始值使用  &lt;code&gt;reactive&lt;/code&gt;会返回一个警告，并且该值不会成为可响应式数据。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;/* DOES NOT WORK AS EXPECTED */
&amp;lt;script setup&amp;gt;
import { reactive } from &amp;quot;vue&amp;quot;;

const count = reactive(0);
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt; &lt;blockquote&gt;&lt;/blockquote&gt; &lt;p&gt;矛盾的是，另一种方式是可行的。例如，使用  &lt;code&gt;ref&lt;/code&gt;来声明一个  &lt;code&gt;Array&lt;/code&gt;会在内部调用  &lt;code&gt;reactive&lt;/code&gt;。&lt;/p&gt; &lt;h2&gt;解构响应式数据&lt;/h2&gt; &lt;p&gt;假设你有一个响应式对象拥有  &lt;code&gt;count&lt;/code&gt;属性，并且有一个按钮来递增  &lt;code&gt;count&lt;/code&gt;。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;&amp;lt;template&amp;gt;
  Counter: {{ state.count }}
  &amp;lt;button @click=&amp;quot;add&amp;quot;&amp;gt;Increase&amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { reactive } from &amp;quot;vue&amp;quot;;
export default {
  setup() {
    const state = reactive({ count: 0 });

    function add() {
      state.count++;
    }

    return {
      state,
      add,
    };
  },
};
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;上述逻辑相当直接，而且如预期的那样工作，但你可能会利用javascript的解构来做以下事情：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;/* DOES NOT WORK AS EXPECTED */
&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;Counter: {{ count }}&amp;lt;/div&amp;gt;
  &amp;lt;button @click=&amp;quot;add&amp;quot;&amp;gt;Increase&amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { reactive } from &amp;quot;vue&amp;quot;;
export default {
  setup() {
    const state = reactive({ count: 0 });

    function add() {
      state.count++;
    }

    return {
      ...state,
      add,
    };
  },
};
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;代码看起来是一样的，而且根据我们以前的经验应该是可行的，但事实上，Vue的响应式跟踪是通过属性访问进行的。这意味着我们不能赋值或解构一个响应式对象，因为与第一个引用的响应式连接已经断开。这就是使用响应式帮助函数的局限性之一。&lt;/p&gt; &lt;h2&gt;对.value感到困惑&lt;/h2&gt; &lt;p&gt;同样的，使用  &lt;code&gt;ref&lt;/code&gt;的一个怪异模式可能也很难习惯。&lt;/p&gt; &lt;p&gt;  &lt;code&gt;Ref&lt;/code&gt;接收一个值，并返回响应式对象。该值在对象内部的  &lt;code&gt;.value&lt;/code&gt;属性下可用。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;const count = ref(0)

console.log(count) // { value: 0 }
console.log(count.value) // 0

count.value++
console.log(count.value) // 1&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;但是  &lt;code&gt;ref&lt;/code&gt;在模板文件中使用时会被解包，并且不需要  &lt;code&gt;.value&lt;/code&gt;。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref } from &amp;apos;vue&amp;apos;

const count = ref(0)

function increment() {
  count.value++
}
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;button @click=&amp;quot;increment&amp;quot;&amp;gt;
    {{ count }} // no .value needed
  &amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;但是要小心了！解包只在顶级属性中生效。下面的代码片段会生成  &lt;code&gt;[object Object]&lt;/code&gt;。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;// DON&amp;apos;T DO THIS
&amp;lt;script setup&amp;gt;
import { ref } from &amp;apos;vue&amp;apos;

const object = { foo: ref(1) }

&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  {{ object.foo + 1 }}  // [object Object]
&amp;lt;/template&amp;gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;正确地使用  &lt;code&gt;.value&lt;/code&gt;需要时间。尽管某些时候我会忘记如何使用，但是使用它的频率越来越高。&lt;/p&gt; &lt;h2&gt;触发事件&lt;/h2&gt; &lt;p&gt;自从Vue的最初发布以来，子组件就可以与父组件使用  &lt;code&gt;emit&lt;/code&gt;来通信。你只需要添加自定义事件监听器来监听一个事件。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;// 子组件
this.$emit(&amp;apos;my-event&amp;apos;)

// 父组件
&amp;lt;my-component @my-event=&amp;quot;doSomething&amp;quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;现在，  &lt;code&gt;emit&lt;/code&gt;需要使用  &lt;code&gt;defineEmits&lt;/code&gt;来进行声明。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;&amp;lt;script setup&amp;gt;
const emit = defineEmits([&amp;apos;my-event&amp;apos;])
emit(&amp;apos;my-event&amp;apos;)
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;另一件要记住的事情是，  &lt;code&gt;defineEmits&lt;/code&gt;和  &lt;code&gt;defineProps&lt;/code&gt;都不需要被导入。它们在使用  &lt;code&gt;script setup&lt;/code&gt;时自动可用。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;&amp;lt;script setup&amp;gt;
const props = defineProps({
  foo: String
})

const emit = defineEmits([&amp;apos;change&amp;apos;, &amp;apos;delete&amp;apos;])
// setup code
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;最后，由于事件现在必须被声明，所以不需要使用  &lt;code&gt;.native&lt;/code&gt;修饰符，事实上它已经被移除了。&lt;/p&gt; &lt;h2&gt;声明附加选项&lt;/h2&gt; &lt;p&gt;Options API方法有几个属性在  &lt;code&gt;script setup&lt;/code&gt;中是不被支持的。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;code&gt;name&lt;/code&gt;&lt;/li&gt;  &lt;li&gt;   &lt;code&gt;inheritAttrs&lt;/code&gt;&lt;/li&gt;  &lt;li&gt;插件或库所需的自定义选项&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;解决办法是按照  &lt;code&gt;script setup&lt;/code&gt;RFC的  &lt;a href="https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md#declaring-additional-options" rel="nofollow noreferrer"&gt;定义&lt;/a&gt;，在同一个组件中设置两个不同的脚本。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;&amp;lt;script&amp;gt;
  export default {
    name: &amp;apos;CustomName&amp;apos;,
    inheritAttrs: false,
    customOptions: {}
  }
&amp;lt;/script&amp;gt;

&amp;lt;script setup&amp;gt;
  // script setup logic
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;使用响应式转换&lt;/h2&gt; &lt;p&gt;Reactivity Transform是Vue 3的一个  &lt;a href="https://github.com/vuejs/rfcs/discussions/369" rel="nofollow noreferrer"&gt;实验性但有争议的功能&lt;/a&gt;，目的是简化组件的声明方式。它的想法是利用编译时的转换来自动解包一个  &lt;code&gt;ref&lt;/code&gt;，并使  &lt;code&gt;.value&lt;/code&gt;过时。但现在它被放弃了，并将在Vue 3.3中被删除。它仍然可以作为一个包使用，但由于它不是Vue核心的一部分，所以最好不要在它身上投入时间。&lt;/p&gt; &lt;h2&gt;定义异步组件&lt;/h2&gt; &lt;p&gt;以前的异步组件是通过将其包含在一个函数中来声明的。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;const asyncModal = () =&amp;gt; import(&amp;apos;./Modal.vue&amp;apos;)&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;从 Vue 3开始，异步组件需要使用  &lt;code&gt;defineAsyncComponent&lt;/code&gt;帮助函数来显式地定义。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;import { defineAsyncComponent } from &amp;apos;vue&amp;apos;

const asyncModal = defineAsyncComponent(() =&amp;gt; import(&amp;apos;./Modal.vue&amp;apos;))&lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;在模板中使用多余的包裹元素&lt;/h2&gt; &lt;p&gt;在Vue 2中，组件模板需要一个单一的根元素，这有时会引入不必要的包裹元素。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;&amp;lt;!-- Layout.vue --&amp;gt;
&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;header&amp;gt;...&amp;lt;/header&amp;gt;
    &amp;lt;main&amp;gt;...&amp;lt;/main&amp;gt;
    &amp;lt;footer&amp;gt;...&amp;lt;/footer&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;现在不再需要这样了，因为现在支持多个根元素。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;&amp;lt;!-- Layout.vue --&amp;gt;
&amp;lt;template&amp;gt;
  &amp;lt;header&amp;gt;...&amp;lt;/header&amp;gt;
  &amp;lt;main v-bind=&amp;quot;$attrs&amp;quot;&amp;gt;...&amp;lt;/main&amp;gt;
  &amp;lt;footer&amp;gt;...&amp;lt;/footer&amp;gt;
&amp;lt;/template&amp;gt;&lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;使用错误的生命周期&lt;/h2&gt; &lt;p&gt;所有的组件生命周期事件都被重新命名，要么添加  &lt;code&gt;on&lt;/code&gt;前缀，要么完全改变名称。你可以在下面的图表中查看所有的变化。&lt;/p&gt; &lt;p&gt;  &lt;img alt="Lifecycle Event.png" src="https://segmentfault.com/img/remote/1460000043521196" title="Lifecycle Event.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h2&gt;不看文档&lt;/h2&gt; &lt;p&gt;最后，官方文档已经进行了修改以反映新的API，并包括许多有价值的说明、指南和最佳实践。即使你是一个经验丰富的Vue 2工程师，你也一定会通过阅读文档学到一些新东西。&lt;/p&gt; &lt;h2&gt;总结&lt;/h2&gt; &lt;p&gt;每个框架都有一个学习曲线，而Vue 3的学习曲线无疑比Vue 2的更陡峭。我仍然不相信两个版本之间的迁移工作是合理的，但组合式API要整洁得多，在你掌握了它之后会感觉很自然。&lt;/p&gt; &lt;p&gt;最后，请记住：&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;犯错比什么都不做要好得多。&lt;/p&gt;  &lt;p&gt;Making mistakes is a lot better than not doing anything.&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;以上就是本文的全部内容。如果对你有所帮助，欢迎点赞、收藏、转发~&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>vue.js</category>
      <guid isPermaLink="true">https://itindex.net/detail/62674-vue-%E9%94%99%E8%AF%AF</guid>
      <pubDate>Fri, 10 Mar 2023 06:52:36 CST</pubDate>
    </item>
    <item>
      <title>DevOps最佳实践之应用开发和部署 (insights.thoughtworks.cn)</title>
      <link>https://itindex.net/detail/62531-devops-%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5-%E5%BA%94%E7%94%A8</link>
      <description>&lt;div&gt;  &lt;h2&gt;关于最佳实践&lt;/h2&gt;  &lt;p&gt;本系列内容是我们在不同项目的维护过程中总结的关于DevOps/SRE方面的最佳实践，我们将致力于在项目上尽最大的努力来推行这些最佳实践。我们希望这些最佳实践能对项目的稳定运营提供帮助，也希望刚接触DevOps/SRE的新人能通过学习这些最佳实践来提升自己在这方面的水平。&lt;/p&gt;  &lt;p&gt;因为DevOps/SRE涉及到的方方面面比较多，一次性完成的工作量太大，所以我们决定分阶段来完成，这一次发布的是“应用开发和部署”这个部分的内容，后续我们将逐步发布“云平台与网络”，“操作系统和服务”，“用户与权限”，“监控与可视化”，“数据与备份”，“敏感数据”，“故障与应急响应”这几部分的内容。&lt;/p&gt;  &lt;p&gt;所谓“最佳实践”应该是最适合自己的实践，而不一定是最先进的，而且每一种实践本身也存在一定的局限性，所以我们在描述了对应实践的优点的同时，也把可能存在的缺点写了出来，就是希望大家在看到它的好处的时候，也能知道可能存在的风险在那里，理性地去评估到底是不是要采用相应的实践，所以这里总结的最佳实践请适度取用，不要为了“最佳”而实践。&lt;/p&gt;  &lt;p&gt;我们深知自己在诸多方面存在一定的局限性，相关的内容可能存在一些不足，而且最佳实践本身会随着技术更新等因素不停地变化，我们将会把蓝皮书内容同步发布在Github上（   &lt;a href="https://github.com/toc-lib/DevOps-SRE-best-practice"&gt;https://github.com/toc-lib/DevOps-SRE-best-practice&lt;/a&gt;） ，希望引发更广范围的传播和讨论。也请使用PR或Issue的方式来提出你的不同的观点和更好的建议，谢谢。&lt;/p&gt;  &lt;h2&gt;应用开发和部署&lt;/h2&gt;  &lt;h3&gt;使用牲口模式&lt;/h3&gt;  &lt;p&gt;在传统的运维环境中，由于条件的限制无法快速的提供新的基础设施和环境，所以通常在业务的依赖环境如操作系统内核，服务，类库，运行时版本等需要变化时，我们会根据需要在现有的环境上做持续性变更。而且我们还可能会在机器上运行一些临时任务，做调试和排错等，很多的时候，这些操作对应的变化并不具有可追溯性，甚至不可以恢复到之前的状态。这样，刚开始统一配置的无差别的一批机器随着时间的推移慢慢的就会变得各自具有一些独有的特性。另外还有一些类型的服务，比如数据库，存储等，其业务本质就导致了集群中的每一台机器具有独特的属性。当我们在维护这些服务的时候，需要根据每台机器的特性来做不同的管理和配置，而且一旦机器出现故障的时候，也很难去创建出一样的机器来替代。因为这种情形和养宠物类似，比如我们会给宠物起一个名字，它也需要悉心照料，生病的时候要带去看病，所以我们称这种服务模式为宠物模式。&lt;/p&gt;  &lt;p&gt;而在具有云原生能力的平台上，我们可以按需定制基础镜像，也能快速的从这个基础镜像中创建出运行环境，我们的变更就可以基于基础镜像来做更新和版本迭代。这样当某一台机器发生了故障，我们可以快速的复制出一台一模一样的机器来替代。如果需要做一些临行性的操作和变化，在任务结束之后，也可以销毁这台已经发生了变化的机器，使用一台新的机器来替代，使整个集群恢复到一个最初的收敛状态。这个场景和我们现实生活中的规模化牲口养殖类似，对应的我们称这种服务模式为牲口模式。&lt;/p&gt;  &lt;p&gt;大家所熟知的无状态应用，就是牲口模式的最常用的一种实现方式。在业务的设计和实施过程中，我们建议把逻辑和数据分离，在逻辑运行环境不要兼顾数据存储工作，比如请求的session相关的数据，不要保存在本地，而是把它放在一个共享数据服务中，从而达到无状态的目的，这样就可以对逻辑运行环境进行牲口化的管理方式。&lt;/p&gt;  &lt;h4&gt;   &lt;strong&gt;优点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;可随时被销毁或替换，结合自动化基础设施和监控，自动完成对故障机器或节点的替换。&lt;/li&gt;   &lt;li&gt;配合自动化基础设施和监控，可实现自动水平伸缩，从容应对业务峰谷，节约成本。&lt;/li&gt;   &lt;li&gt;在不影响服务稳定性的前提下可部署所需要版本的应用、进行系统升级或者打补丁。&lt;/li&gt;   &lt;li&gt;监控和管理的重心不再是具体的单一资源的使用率，而是整体的承载能力和更深层次的性能关注点。&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;缺点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;需要基础设施平台具有相应的能力支撑，否则很难实现。&lt;/li&gt;   &lt;li&gt;不是所有的业务类型都能做牲口模式设计，比如数据库。&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;实施要点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;除计算和业务处理过程中的临时产生的数据，数据的来源和最终的持久化应由外部服务来提供，如独立的内存型数据库或者关系型数据库。&lt;/li&gt;   &lt;li&gt;可以使用客户端Cookie、cache取代外部数据服务。如果有敏感数据，服务器端可以加密后交由客户端存储，在之后的请求时发回服务器解密使用。&lt;/li&gt;   &lt;li&gt;通过锁或者幂等性设计，使得应用能正确、快速、自动地解决对同一份数据的竞争而导致的流程异常、数据不一致等问题。例如，多个定时任务同时处理一批数据。&lt;/li&gt;&lt;/ol&gt;  &lt;h3&gt;使业务升级向前兼容&lt;/h3&gt;  &lt;p&gt;向前兼容指低版本的系统、程序或技术能优雅处理（例如：忽略其不理解的部分）高版本的系统、程序或技术。向前兼容技术的目标是让旧系统能够识别为新系统生成的数据，简单的说就是旧版本的系统可以接受新版本的数据，是旧版本对新版本的兼容。&lt;/p&gt;  &lt;p&gt;我们建议在做业务升级时候，设计你的业务具有向前兼容的能力，以应对升级失败时某一功能模块或者依赖无法随之回滚的风险。比如说在有数据库字段变化的升级中，在正式对数据库做变动之前，基于旧的业务流程做代码层面更新，使其可以兼容数据库将要发生的改动并加以部署。在数据库升级完成之后，如果新的业务流程上线后不幸出现重大的问题等情况需要回滚时，回滚之后的代码仍然可以兼容数据库的变化，而不用对数据库也进行回滚，毕竟数据库的回滚成本非常高。&lt;/p&gt;  &lt;h4&gt;   &lt;strong&gt;优点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;可以在新版本出现不容易修复和存在重大的风险的时候快速当回滚到旧的版本，业务中断的可能性会大大降低。&lt;/li&gt;   &lt;li&gt;即使整个系统中存在不可回滚的部分，但我们不用花费很多的精力去考虑和解决完全不可回滚的问题。&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;缺点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;设计成本：要做到兼容未来的变化。这听起来就很难。一开始很难获知所有用例、极端案例和业务理解。回顾过去并说这是一个错误的决定很容易，今天做出明天不会后悔的决定要困难得多。&lt;/li&gt;   &lt;li&gt;为了同时兼容两种数据格式，需要在代码中增加额外的处理逻辑，增加复杂度和投入的成本。&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;实施要点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;select语句只获取需要的字段，避免使用select * from语句，有效防止新增字段对应用逻辑的影响，还能减少对性能的影响。&lt;/li&gt;   &lt;li&gt;对数据库表结构变更通过新增字段实现。&lt;/li&gt;   &lt;li&gt;尽量新增接口，避免对现有接口做修改，如需要修改现有接口，可尝试在接口上增加版本标识。&lt;/li&gt;&lt;/ol&gt;  &lt;h3&gt;使用唯一性标识给镜像打标签&lt;/h3&gt;  &lt;p&gt;当生成容器镜像时，应当使用唯一性标识来给容器镜像打标签，唯一标识可以更好的标记当次生成的镜像，避免出现多个同名标签但不同的版本镜像被使用的情况。例如多次部署都使用了latest标签的镜像，可能因为拉取和缓存策略导致不同节点使用了不同版本的镜像，从而导致功能上的不一致，在这种情况下，并不能很方便地判断出某个节点部署的是哪一个版本。&lt;/p&gt;  &lt;p&gt;唯一标识最好有一定的含义，不仅可以用来区分产物，还可以获取到本次构建的关键信息。比如git提交哈希等关联性比较强的标识。虽然时间戳也是一个唯一性比较强的标识，但是关联性相对较差，如果长度不足，也有一定的几率产生碰撞。可以考虑使用组合型标签，比如使用时间戳，build号，版本号等根据自己的需求来组合生成唯一标识，这样的标签本身就包含了很丰富的信息。&lt;/p&gt;  &lt;p&gt;不建议单纯使用pipeline的build序号来作为镜像的标签，如果需要更换CI工具或者重建pipeline时，这个序号将会被重置而可能产生重复，除非在构建脚本中加入偏移量。而且不同的CI工具获取这个序号的方法也有所不同，对于迁移并不友好。虽然它的可追溯性看起来较好，但是单纯的Build序号和代码之间并没有直接的关联。&lt;/p&gt;  &lt;p&gt;如果不是需要对外公开发布的镜像，并不建议对同一镜像打上多个不同标签。因为绝大部分的情况下，我们只会选用其中一个标签在所有的地方使用，多个标签的实际意义并不会很大。&lt;/p&gt;  &lt;p&gt;如果制品库支持immutable特性，强烈建议开启这个功能，防止因为意外情况导致对已上传的镜像的覆盖。&lt;/p&gt;  &lt;h4&gt;   &lt;strong&gt;优点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;可以准确对应的到源代码具体版本，在溯源时可以对应到特定的提交而不是可能存在的多个提交。&lt;/li&gt;   &lt;li&gt;不需要使用SHA256等额外的信息来区分同一标签的不同版本。&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;缺点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;一些类型的唯一性标识可读性不是很高，比如git提交哈希。&lt;/li&gt;   &lt;li&gt;一些类型的标识受时间影响，不能使用同一命令获得一致结果，需要使用其他的方式来传递给后续阶段，比如时间戳。&lt;/li&gt;   &lt;li&gt;制品库immutable功能开启之后，重跑已完成构建镜像的pipeline会发生上传镜像失败的错误，有可能会导致后续任务不能继续。&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;实施示例：&lt;/strong&gt;&lt;/h4&gt;  &lt;pre&gt;   &lt;code&gt;#!/usr/bin/env bash

GIT_HASH=$(git rev-parse HEAD)

docker build --rm -t &amp;quot;myapp:${GIT_HASH}&amp;quot; .&lt;/code&gt;&lt;/pre&gt;  &lt;h3&gt;在所有环境中使用同一个构建产物&lt;/h3&gt;  &lt;p&gt;应该在不同环境中使用相同的构建产物来部署，避免对不同的环境生成不同的构建产物，以确保环境的一致性，同时也保证部署在不同环境中的业务代码是测试和验证通过的。比如某次的构建产物，在测试环境部署后经由测试人员和相关的自动化测试工具完成相关的测试验证，如果没有问题才会继续部署到后续环境中，应继续使用该产物部署后面的环境，不建议重新构建新的产物来做后续环境的部署，也不建议覆盖之前的构建产物标识。因为在现有流行的语言和框架中，普遍存在大量的第三方依赖，即便是同一份源代码，由于其依赖以及构建环境的不同，会有一定几率出现由于外部依赖的更新导致构建产物存在差异，从而产生非预期的情况出现。&lt;/p&gt;  &lt;h4&gt;   &lt;strong&gt;优点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;确保所有的环境部署的构建产物是一样的，尽可能的保证环境的一致性。&lt;/li&gt;   &lt;li&gt;确保部署到生产环境的产物是测试验证之后并无变化的，避免出现非预期的差异。&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;缺点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;p&gt;对于如前端这类纯静态资源的应用，由于不同的环境需要连接不同的后端服务地址，因此无法直接使用唯一的构建产物。可以考虑在业务启动阶段，用一些额外的启动脚本或命令配合传入环境变量或参数来修改配置文件，从而达到所有环境使用同一个构建产物的目的。&lt;/p&gt;    &lt;p&gt;下面例子展示了在使用nginx的容器镜像里，通过在CMD指令里面先执行一段脚本来对配置进行修改，来达到在容器运行时根据传入的环境变量WEB_ENV的值来访问对应环境的后端服务的目的。&lt;/p&gt;    &lt;pre&gt;     &lt;code&gt;FROM nginx:1.21.3-alpine

COPY dist /app/

CMD sed -i -e &amp;quot;s/VUE_APP_ENV/${WEB_ENV}/g&amp;quot; /app/env-config.js \
&amp;amp;&amp;amp; nginx -g &amp;apos;daemon off;&amp;apos;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;对于移动端app，也存在与前端应用类似的问题，需要开发人员做额外设计和开发，在app启动时判断需要进入什么样的运行模式。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;实施要点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;在设计CICD流水线时，将构建产物同步到制品库时，给该产物打上唯一标识。&lt;/li&gt;   &lt;li&gt;如制品库支持，开启制品库的immutable特性。&lt;/li&gt;   &lt;li&gt;将该唯一标识传递到在后面所有的部署流水线任务中，所有的部署任务都使用该唯一标识所指向的构建产物。&lt;/li&gt;   &lt;li&gt;如果需要在多个制品库保存同一个构建产物，建议在上传成功之后对构建产物做完整性检查。&lt;/li&gt;&lt;/ol&gt;  &lt;h3&gt;减少脚本/工具对环境的依赖&lt;/h3&gt;  &lt;p&gt;一般情况下，脚本都会或多或少的使用到一些外部工具。而我们的脚本很有可能会运行在不同的环境中，不同环境中提供的工具也会有版本和用法的差异。如果需要在环境中维护某一工具的多个版本的，工具本身的版本管理，以及多个工具之间的依赖冲突和升级更新也会产生较高的管理和维护成本。&lt;/p&gt;  &lt;p&gt;我们建议尽可能的减少所使用的工具对环境的依赖，尤其是系统不会默认安装的工具。另外在编写脚本的时候，也尽量避免使用只有某些版本特有的语法特性。这些情况都会导致脚本有可能出现一些不可预期的结果。我们建议使用容器化工具或者容器化环境管理工具如Batect来替代对应的需求。&lt;/p&gt;  &lt;h4&gt;   &lt;strong&gt;优点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;p&gt;CI/CD agent中只需要安装容器运行时即可，可以减小agent的体积。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;容器化的工具因为对环境的依赖非常低，所以不论是工具升级还是降级都非常简单，同时也解耦了对agent特性的依赖，提高agent利用率。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;最大化的保证环境一致性，使用容器化的工具消除了环境差异可能导致的非预期异常。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;新人友好，新加入的团队成员可以快速的配置好可运行的环境，无需过多的考虑具体工具的安装，配置等。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;解耦对CI/CD工具的依赖，虽然在实际项目中很少会有更换CI/CD工具的情况，但是如果需要迁移，我们也只需在新的工具环境中构建出容器运行环境即可，大大减少了切换工具工作量，提高迁移的速度。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;缺点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;p&gt;因为没有预装构建所需要的各种软件，如果本地没有镜像缓存，在运行容器化的工具时都需要去容器仓库中获取对应的工具镜像，会有额外的带宽压力。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;因为需要获取工具镜像，容器启动也比二进制的程序要慢，所以整个任务运行过程需要的时间会更长。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;理论上来讲，容器化技术性能损耗很小，工具的性能和二进制程序的差别不会很大，但是在实际的使用中，我们发现因为容器引擎配置不当等原因会导致一些工具性能变差甚至无响应的情况出现。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;实施示例：&lt;/strong&gt;&lt;/h4&gt;  &lt;p&gt;在使用 terraform 时，不同版本之间的 terraform 并不兼容，那么如何保证所有人与 CI 都使用相同的 terraform 版本就是一个非常麻烦的事情。那么如果我们无论在 CI 还是本地都基于 docker 去运行 terraform 就可以解决这个问题。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;#!/usr/bin/env bash

function terraform() {
  docker run --rm -it \
      -v $(pwd):/app -w /app \
      hashicorp/terraform:1.1.4 -c &amp;quot;$@&amp;quot;
}

terraform init
terraform plan &amp;amp;&amp;amp; terraform apply&lt;/code&gt;&lt;/pre&gt;  &lt;h3&gt;使用auto/ACTION模式来维护管理脚本&lt;/h3&gt;  &lt;p&gt;auto/ACTION是我们在项目实践中总结并希望可以广泛推广的一个经验总结，在和客户合作过程中，尤其是有很多团队的大型项目上，我们从这个模式中受益匪浅。auto/ACTION模式的核心是使用统一语义能表明脚本目的的ACTION来命名管理脚本，如应用的测试(test)，验证(validate)，打包(build)，发布(deploy)等相关任务，统一把这些管理脚本归放在auto目录下来维护。&lt;/p&gt;  &lt;p&gt;因为类unix系统在运行的时候并不真正使用文件后缀来识别文件的类型，我们建议脚本名字不要加后缀。这个建议是基于管理脚本有可能会在多个地方被使用，而不同的开发和维护人员对于语言的偏好不同，如果在需要使用另外一种语言重写脚本的时候，使用这个脚本的地方就不需要做更新，消除了因为文件名变化可能导致的自动化任务的错误和中断。虽然没有后缀可能会带来一些不便，比如编辑器的语言类型识别错误等，但是相对于它带来的优点，还是非常值得的。&lt;/p&gt;  &lt;h4&gt;   &lt;strong&gt;优点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;p&gt;管理代码和业务代码放在同一代码库，使用版本控制，便于进行更新，回退。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;每个脚本只做一件事，职责单一，同时便于理解和管理。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;可以方便的知道所有可用的脚本。如：&lt;/p&gt;    &lt;pre&gt;     &lt;code&gt;$ ls -l auto/
auto/validate
auto/test
auto/build
auto/release
auto/deploy&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;如果跨团队合作，或者团队成员有轮换的时候，可以更快速的掌握业务管理的上下文。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;缺点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;每个项目都有一套自己的auto脚本，如果有基础性变化，改动成本较高，可以考虑使用git submodule等模式来管理。&lt;/li&gt;   &lt;li&gt;没有后缀的文件名会带来一些管理上的不便。&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;实施要点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;p&gt;脚本满足既可在本地执行，又能在CI流水线上执行，便于验证。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;脚本中的变量内容尽可能从环境变量中读取，避免向脚本中传入参数，方便运行。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;专属于CI/CD平台的脚本不要放在auto根目录下，建议创建一个对应的子目录，例如 .buildkite, .github, .travis来做管理。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;可根据团队的需求适当的扩展脚本的名字使之更容易理解，建议使用-而非_ 来分隔单词， 如auto/upload-image-to-ecr。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;h3&gt;管理脚本和业务脚本分离&lt;/h3&gt;  &lt;p&gt;我们的应用中一般都会有一些脚本来做一些辅助性的工作。这些脚本通常会和业务代码放在同一个代码仓库，使用版本控制来进行管理。这些脚本大致分为两种：管理脚本和业务脚本。&lt;/p&gt;  &lt;p&gt;管理脚本是用来做应用打包，部署等管理相关工作工作，这种类型的脚本是无需打包进业务运行所需的产出物中的；业务脚本是辅助业务运行，比如说初始化环境和配置，结束时的清理工作等，这些脚本需要打包到业务运行的产出物中。&lt;/p&gt;  &lt;p&gt;我们建议除了一些有特殊要求的脚本外，不要把脚本放在根目录。并且把这两种不同类型的脚本存放在不同的目录中。&lt;/p&gt;  &lt;h4&gt;   &lt;strong&gt;优点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;在封装镜像时，业务脚本和业务代码同等重要，需要封装在镜像中。将管理脚本和业务脚本分离可以减少镜像中的文件数量。&lt;/li&gt;   &lt;li&gt;在软件开发过程中，针对业务运行和自动化管理关注信息不一样，将管理脚本和业务脚本分离，让团队成员更加清楚脚本的类型和目的。&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;缺点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;将管理脚本和业务脚本分离，会增加仓库的层次结构。&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;实施要点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;推荐将管理脚本放置在auto目录，将业务脚本放置在scripts目录。&lt;/li&gt;   &lt;li&gt;脚本中的变量采用从环境变量中读取，避免向脚本中传入参数，方便运行。&lt;/li&gt;   &lt;li&gt;推荐脚本名称即表明脚本的作用，不建议使用auto/script这样不表意的脚本命名。&lt;/li&gt;   &lt;li&gt;不在文件名中使用文件类型后缀。&lt;/li&gt;&lt;/ol&gt;  &lt;h3&gt;及时更新容器的基础镜像&lt;/h3&gt;  &lt;p&gt;基础镜像是业务镜像的地基，其包含了我们业务和应用所必需的基础库、二进制文件和配置文件等。一个良好维护的基础镜像通常会根据需要做更新，这些更新通常包含安全补丁，新功能或对操作系统或框架的改进等，我们建议及时的更新容器的基础镜像来保障业务的安全性。除非有特定原因需要继续使用旧版本镜像，否则应及时跟进使用经过充分评估和测试的最新版本镜像。&lt;/p&gt;  &lt;p&gt;在Dockerfile和compose等文件中，可以通过指定镜像中的标识和sha256值组合来指定基础镜像的版本。当镜像有了更新之后，及时沿用了如latest或大版本号这类通用性比较高的标签时，其sha256的值也会发生变化，通过更新这个组合可以更新使用最新版本的基础镜像。&lt;/p&gt;  &lt;h4&gt;   &lt;strong&gt;优点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;最新的镜像通常带有可以增强应用程序安全性的补丁修复，降低安全风险。&lt;/li&gt;   &lt;li&gt;最新的镜像通常包括可以提高应用程序性能的新功能或改进功能。&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;缺点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;新功能可能存在不可预期的bug。&lt;/li&gt;   &lt;li&gt;新的功能有非常小的概率存在未知的安全漏洞，如果有特殊的安全需求，请在安全部门的指导下升级。&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;实施示例：&lt;/strong&gt;&lt;/h4&gt;  &lt;p&gt;可以使用   &lt;a href="https://github.com/realestate-com-au/dfresh"&gt;dfresh&lt;/a&gt;或者类似的工具来检查和更新基础镜像。&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;检查基础镜像是否有更新&lt;/p&gt;    &lt;pre&gt;     &lt;code&gt;$ dfresh check Dockerfile
Dockerfile:1: mysql
old sha256:5e515d337402579571c19a2a34a9b733d26788805429b5b3bdca12b76e7cc208
new sha256:fbe848f5738001063a89367adb747e7f283f9c87b20e74ccb6db3b13ec6e35cd&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;更新基础镜像&lt;/p&gt;    &lt;pre&gt;     &lt;code&gt;$ dfresh update Dockerfile
Dockerfile:1: mysql
old sha256:5e515d337402579571c19a2a34a9b733d26788805429b5b3bdca12b76e7cc208
new sha256:fbe848f5738001063a89367adb747e7f283f9c87b20e74ccb6db3b13ec6e35cd&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;回退方法 在需要回退基础镜像版本时，可从代码库的提交找到上一个可用版本的相应信息。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;h3&gt;定期检查和升级依赖包&lt;/h3&gt;  &lt;p&gt;随着 Bug 修复、新功能的开发或者其他更新，我们应用的依赖包可能会过时。此时应用的依赖项越多，就越难跟上这些更新。过时的依赖包可能对安全构成威胁，并对性能产生负面影响。最新的软件包可防止漏洞，这意味着定期的依赖性检查和更新很重要。我们建议定期的对应用的依赖包做更新和安全检查，并升级到一个合适的版本。并且我们建议在应用的 pipeline 中加入这些检查任务，并在常规的开发过程中及时发现和升级。如果应用已经处于维护阶段，我们也建议定期执行这些检查并在需要的时候加以升级。&lt;/p&gt;  &lt;h4&gt;   &lt;strong&gt;优点:&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;定期升级依赖可以让应用的安全性和代码的可用性都有保障。&lt;/li&gt;   &lt;li&gt;定期升级依赖会让解决依赖版本冲突和代码兼容性变得容易。&lt;/li&gt;   &lt;li&gt;更新依赖项可以获得新的依赖项版本提供的所有性能改进。 这些改进可以有多种形式，例如修复以前的性能问题、改进了实现和算法等。&lt;/li&gt;   &lt;li&gt;升级依赖项不仅可以改进现有功能，还可以使用到以前不存在的新功能。这些新功能最终可能让我们更好的实现自己应用的新功能。&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;缺点:&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;如果不及时更新依赖，将会使得产品难以维护，并可能导致开发人员的时间被常规的、无意义的工作占用。&lt;/li&gt;   &lt;li&gt;如果长期不更新依赖，会使应用面临无人问津的风险，之后在某一天需要进行改动的时候，面临大量的依赖包过期无法获取和版本升级造成的接口变化。这时就需要投入非常高的成本来让代码重新变得可用，甚至完全无法更新而变成遗留系统。&lt;/li&gt;   &lt;li&gt;当进行大的版本升级时，需要对应用程序进行更多的更改才能与较新的库兼容。这使得付出代价比及时更新依赖大得多。&lt;/li&gt;   &lt;li&gt;如果忽略升级依赖项，那么会面临无法在自己喜欢的平台上运行软件的可能。 例如，如果停止升级软件中的数据库驱动程序，那么将无法使用旧版本的数据库系统。这不仅会使应用变得过时且易受攻击，而且甚至可能无法从该数据库系统提供商处获得任何支持。&lt;/li&gt;   &lt;li&gt;如果应用依赖于过时的依赖项而导致升级困难变得很难维护，会使得项目很难找到对这些旧技术有经验的人，甚至失去现有的维护者。&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;实施示例:&lt;/strong&gt;&lt;/h4&gt;  &lt;h5&gt;   &lt;strong&gt;1. 手动检查&lt;/strong&gt;&lt;/h5&gt;  &lt;h6&gt;   &lt;strong&gt;JS 篇&lt;/strong&gt;&lt;/h6&gt;  &lt;ul&gt;   &lt;li&gt;npm-outdated &amp;amp; npm-update    &lt;ul&gt;     &lt;li&gt;npm outdated：可以使用 npm outdated 获取当前需要升级的包的信息。&lt;/li&gt;     &lt;li&gt;npm update: 会把所有的包升级到我们定义的需要的版本号。如果需要升级到最新的则需要使用@latest eg: npm update cypress@latest。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;npm-check-updates: 是一种更高级的检查工具&lt;/p&gt;    &lt;ul&gt;     &lt;li&gt;首先需要全局安装 npm-check-updates: npm install -g npm-check-updates ；&lt;/li&gt;     &lt;li&gt;ncu: 检查需要升级的包信息，这里类似 npm outdated；&lt;/li&gt;     &lt;li&gt;ncu --upgrade/ncu -u: 将所有的包升级到最新版本，即便是包含重大更改，也会进行更新。注意：更新完成后不会自动运行 npm install，所以还需要再手动执行来更新 package-lock.json。&lt;/li&gt;     &lt;li&gt;ncu --interactive/ncu -i : interactive mode 安装某个包。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;   &lt;li&gt;小结：npm-outdated 和 npm-check-updates都可以用来做 JS项目的包检查、升级。&lt;/li&gt;&lt;/ul&gt;  &lt;h6&gt;   &lt;strong&gt;Java 篇&lt;/strong&gt;&lt;/h6&gt;  &lt;ul&gt;   &lt;li&gt;在 build.gradle中配置 owasp.dependency-check&lt;/li&gt;   &lt;li&gt;执行./gradlew dependencyCheckAnalyze&lt;/li&gt;   &lt;li&gt;查看报告： 项目根目录&amp;gt;build&amp;gt;reports&amp;gt;dependency-check-report.html&lt;/li&gt;&lt;/ul&gt;  &lt;h5&gt;   &lt;strong&gt;2. CI Pipeline 集成&lt;/strong&gt;&lt;/h5&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;npm-check-updates 与 Buildkite Pipeline 的集成&lt;/p&gt;    &lt;p&gt;由于 buildkite 没有官方插件支持 dependency-check。所以对于buildkite 推荐两种方式：&lt;/p&gt;    &lt;ul&gt;     &lt;li&gt;自己开发对应功能的插件，然后集成到 pipeline 的 step 中；&lt;/li&gt;     &lt;li&gt;通过 docker-compose 的方式去运行对应的检查，将其在 pipeline 的 step 中去运行（如果需要可以添加 block 来强制检查 npm-check-updates 的结果）。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;pre&gt;   &lt;code&gt;## Dockerfile
FROM node:18.7.0
WORKDIR app
COPY [&amp;quot;package.json&amp;quot;, &amp;quot;package-lock.json*&amp;quot;, &amp;quot;./&amp;quot;]
RUN npm install -g npm-check-updates&lt;/code&gt;&lt;/pre&gt;  &lt;pre&gt;   &lt;code&gt;## docker-compose
version: &amp;quot;2&amp;quot;
services:
node-version-check:
  build: .
  command: sh -c &amp;quot;ncu&amp;quot;&lt;/code&gt;&lt;/pre&gt;  &lt;pre&gt;   &lt;code&gt;## auto/dependency-check.sh
#!/usr/bin/env bash
set -ex
docker-compose run --rm node-version-check&lt;/code&gt;&lt;/pre&gt;  &lt;pre&gt;   &lt;code&gt;## buildkite script
steps
 - label: &amp;apos;node version dependency-check&amp;apos;
   command: auto/dependency-check.sh
   agents:
     queue: &amp;apos;xxxx&amp;apos;
 - block: &amp;apos;Please check node-version-check&amp;apos;&lt;/code&gt;&lt;/pre&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;jenkins pipeline 的集成：需要安装 dependency-check Plugin。步骤如下：&lt;/p&gt;    &lt;ul&gt;     &lt;li&gt;在 Jenkins Global Tool Configuration 安装 dependency-check；&lt;/li&gt;     &lt;li&gt;在 Jenkins builder 配置已经安装好的 dependency-check；&lt;/li&gt;     &lt;li&gt;在 Jenkins Publish 里配置读取 dependency-check 的 report ，通过对相关指标进行读取，设置阈值，配置构建失败或者警告等设置。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;h5&gt;   &lt;strong&gt;3. 工具集成检查&lt;/strong&gt;&lt;/h5&gt;  &lt;p&gt;如果项目 code 托管在 Github，我们可以使用 Dependabot 和 Renovate 工具和 Github 集成来做依赖检查。这两个工具都会做定期扫描，创建依赖版本升级的 PR。&lt;/p&gt;  &lt;h6&gt;   &lt;strong&gt;配置 Dependabot 进行版本更新&lt;/strong&gt;&lt;/h6&gt;  &lt;ul&gt;   &lt;li&gt;在 GitHub 的代码仓库的主页，找到代码仓库名称下的 setting；&lt;/li&gt;   &lt;li&gt;在边栏的安全性部分中，单击代码安全性和分析；&lt;/li&gt;   &lt;li&gt;在代码安全和分析下，在Dependabot version updates右侧，单击启用以打开存储库 .github 目录中的基本 dependabot.yml 配置文件；&lt;/li&gt;   &lt;li&gt;添加version；&lt;/li&gt;   &lt;li&gt;添加 updates 部分，并输入希望 Dependabot 监视的每个包管理器的条目；&lt;/li&gt;   &lt;li&gt;对于每个包管理器，可使用：    &lt;ul&gt;     &lt;li&gt;package-ecosystem 指定包管理器。&lt;/li&gt;     &lt;li&gt;directory 指定清单或其他定义文件的位置。&lt;/li&gt;     &lt;li&gt;schedule.interval 指定检查新版本的频率。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;   &lt;li&gt;在代码仓库的根目录创建.github目录；&lt;/li&gt;   &lt;li&gt;创建 dependabot.yml文件并且存储到.github目录下。&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;示例 dependabot.yml&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;version: 2
updates:
  # Enable version updates for npm
  - package-ecosystem: &amp;quot;npm&amp;quot;
    # Look for&lt;/code&gt;   &lt;code&gt;package.json&lt;/code&gt;and   &lt;code&gt;lock&lt;/code&gt;files in the   &lt;code&gt;root&lt;/code&gt;directory
    directory: &amp;quot;/&amp;quot;
    # Check the npm registry for updates every day (weekdays)
    schedule:
      interval: &amp;quot;daily&amp;quot;&lt;/pre&gt;  &lt;h6&gt;   &lt;strong&gt;配置 Renovate&lt;/strong&gt;&lt;/h6&gt;  &lt;ul&gt;   &lt;li&gt;在 Github 的 App 里面安装 Renovate app    &lt;a href="https://github.com/apps/renovate%EF%BC%9B"&gt;https://github.com/apps/renovate；&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;安装并配置完成后可以在PR中看到一个自动生成的PR Configure Renovate，这个PR中包含一个 renovate.json 文件，这个文件中包含了 renovate 的一些默认设定；&lt;/li&gt;   &lt;li&gt;可以根据文档 (    &lt;a href="https://docs.renovatebot.com/configuration-options/"&gt;https://docs.renovatebot.com/configuration-options/&lt;/a&gt;) 添加或者修改适合自身项目的具体配置项；&lt;/li&gt;   &lt;li&gt;merge 此 PR；&lt;/li&gt;   &lt;li&gt;Renovate 会根据你配置的 schedule 时间去自动的扫描并生成包升级 PR 提醒&lt;/li&gt;&lt;/ul&gt;  &lt;h3&gt;定期的重新部署维护阶段的应用&lt;/h3&gt;  &lt;p&gt;在应用处于维护阶段，如果业务不再会增加新的功能，抑或因为某些原因无法做定期的应用依赖升级，我们也建议你定期的重新部署这个应用，以应对平台等更底层的变化带来的部署失败的风险。定期部署可以确保你的应用在新的平台环境中也可以正常的部署，如果在周期性的部署过程中发现应用无法在新的环境部署，你也会有一个缓冲期来制订应对策略，而不是在平台完成升级之后的某一天，应用发生了问题才发现已经无法部署。&lt;/p&gt;  &lt;h4&gt;   &lt;strong&gt;优点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;p&gt;定期部署应用是对部署工具和流程的有效验证，CI/CD Agent的一些升级有可能会导致我们在部署流程中使用工具发生兼容性问题，定期部署可以及早的发现这些问题。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;定期部署应用也能够有效缩短我们的依赖获取未验证的窗口期。虽然我们的应用依赖可以锁定版本，也可以将依赖保存到私有仓库，但长时间没有运行相关部署流程，我们无法保证应用的依赖能够在需要的时候可正常获取且可用。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;现在的应用的基础设施很多都基于各类云平台，服务提供商会定期的对自己的基础设施做升级和换代，定期部署应用可以让我们及早的获知基础设施变化带来的兼容性风险。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;h4&gt;   &lt;strong&gt;缺点：&lt;/strong&gt;&lt;/h4&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;p&gt;定期部署会对系统的稳定运行造成一些影响，变化本身就会带来一定的未知风险。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;自动化的发布一般情况下都需要配有完善的回归测试流程来确保业务的可用性，会带来成本的增加&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;h2&gt;感谢&lt;/h2&gt;  &lt;p&gt;蓝皮书在编撰的过程中，有很多热心的小伙伴加入贡献了自己的力量和知识，在这里非常感谢他们。&lt;/p&gt;  &lt;p&gt;常卉、冯炜、甘霖静、高语越、何水平、何蔚、毛灵、毛远鑫、钱文涛、乔伟星、石昆、孙瑞、许乐、杨伟健、赵浩、赵佩、周瑞丰、朱翔&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;    &lt;a href="https://www.addtoany.com/add_to/wechat?linkurl=https%3A%2F%2Finsights.thoughtworks.cn%2Fdevops-best-practices-application-development-deployment%2F&amp;linkname=DevOps%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E4%B9%8B%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%E5%92%8C%E9%83%A8%E7%BD%B2" rel="nofollow noopener" target="_blank" title="WeChat"&gt;&lt;/a&gt;    &lt;a href="https://www.addtoany.com/add_to/sina_weibo?linkurl=https%3A%2F%2Finsights.thoughtworks.cn%2Fdevops-best-practices-application-development-deployment%2F&amp;linkname=DevOps%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E4%B9%8B%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%E5%92%8C%E9%83%A8%E7%BD%B2" rel="nofollow noopener" target="_blank" title="Sina Weibo"&gt;&lt;/a&gt;    &lt;a href="https://www.addtoany.com/add_to/evernote?linkurl=https%3A%2F%2Finsights.thoughtworks.cn%2Fdevops-best-practices-application-development-deployment%2F&amp;linkname=DevOps%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E4%B9%8B%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%E5%92%8C%E9%83%A8%E7%BD%B2" rel="nofollow noopener" target="_blank" title="Evernote"&gt;&lt;/a&gt;    &lt;a href="https://www.addtoany.com/add_to/pocket?linkurl=https%3A%2F%2Finsights.thoughtworks.cn%2Fdevops-best-practices-application-development-deployment%2F&amp;linkname=DevOps%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E4%B9%8B%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%E5%92%8C%E9%83%A8%E7%BD%B2" rel="nofollow noopener" target="_blank" title="Pocket"&gt;&lt;/a&gt;    &lt;a href="https://www.addtoany.com/add_to/instapaper?linkurl=https%3A%2F%2Finsights.thoughtworks.cn%2Fdevops-best-practices-application-development-deployment%2F&amp;linkname=DevOps%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E4%B9%8B%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%E5%92%8C%E9%83%A8%E7%BD%B2" rel="nofollow noopener" target="_blank" title="Instapaper"&gt;&lt;/a&gt;    &lt;a href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Finsights.thoughtworks.cn%2Fdevops-best-practices-application-development-deployment%2F&amp;linkname=DevOps%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E4%B9%8B%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%E5%92%8C%E9%83%A8%E7%BD%B2" rel="nofollow noopener" target="_blank" title="Email"&gt;&lt;/a&gt;    &lt;a href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Finsights.thoughtworks.cn%2Fdevops-best-practices-application-development-deployment%2F&amp;linkname=DevOps%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E4%B9%8B%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%E5%92%8C%E9%83%A8%E7%BD%B2" rel="nofollow noopener" target="_blank" title="LinkedIn"&gt;&lt;/a&gt;    &lt;a href="https://www.addtoany.com/add_to/pinterest?linkurl=https%3A%2F%2Finsights.thoughtworks.cn%2Fdevops-best-practices-application-development-deployment%2F&amp;linkname=DevOps%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E4%B9%8B%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%E5%92%8C%E9%83%A8%E7%BD%B2" rel="nofollow noopener" target="_blank" title="Pinterest"&gt;&lt;/a&gt;    &lt;a href="https://www.addtoany.com/share"&gt;     &lt;img alt="Share" src="https://static.addtoany.com/buttons/favicon.png"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>jianshu</category>
      <guid isPermaLink="true">https://itindex.net/detail/62531-devops-%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5-%E5%BA%94%E7%94%A8</guid>
      <pubDate>Tue, 06 Dec 2022 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>知识图谱增强下的智能推荐系统与应用-于敬</title>
      <link>https://itindex.net/detail/62491-%E7%9F%A5%E8%AF%86-%E5%A2%9E%E5%BC%BA-%E6%99%BA%E8%83%BD</link>
      <description>&lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/91c10e8a-a59a-4c46-bcd6-5b8171f57b76.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;随着互联网技术的迅速发展，尤其是移动互联网的兴起，新产生的信息呈现爆炸式的增长。为了更好地解决信息获取中的信息过载（Information Overload）和长尾问题（Long Tail），推荐系统应运而生，目前基本上已经成为了各种产品的标配功能。推荐系统是信息过滤系统的一个分支，它可以自动地挖掘用户和物品之间的联系。具体来说，它试图基于用户本身的多维度属性数据（如年龄、地域、性别等）以及行为数据的反馈（如点击、收藏、点赞、购买等），结合物品自身属性数据（如标题、标签、类别、正文等），以预测用户对待推荐物品的评分或偏好。从用户的角度来看，推荐系统是基于用户个人的兴趣偏好进行千人千面的自动推荐，则有助于缓解信息过载问题。从物品的角度来看，其自身属性及对应的交互行为差异，通过各种推荐方式是可以触达到对其更感兴趣的用户群体中，缓解了曝光不足带来的长尾问题。从企业的角度来看，推荐系统带来了更好的产品交互方式，达到了沉浸式体验的效果，从而进一步提升了用户的黏性，并最终大幅度提升了转化收益。&lt;/p&gt;



 &lt;p&gt;  &lt;img src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/6c4285ff-bfa0-47ff-ac95-02afaf310d0c.jpg"&gt;&lt;/img&gt;图1 达观智能推荐系统&lt;/p&gt;



 &lt;p&gt;在智能推荐ToB企业服务领域，达观数据已经有了10余年的推荐技术沉淀和上千家客户的行业应用实践经验。早在2012年的时候，由达观数据创始人陈运文博士带领团队参加了在伦敦举办的EMI数据黑客竞赛并获得了国际冠军，该竞赛主要是围绕音乐推荐场景，如何基于用户听歌行为等数据进行分析挖掘来对预测用户兴趣偏好并进行歌曲推荐。经过激烈鏖战，由他们开发的智能推荐系统对500万听歌用户的数据进行建模，根据每个用户的个性化兴趣偏好从数十万首歌曲库中为每个用户生成千人千面的歌曲推荐结果，推荐精度力克包括来自剑桥大学、牛津大学、密歇根大学等等的300多支参赛队伍，一举获得冠军。达观智能推荐基于前沿的人工智能和大数据分析挖掘技术，经过多年的产品打磨和持续的行业应用探索，累计服务客户数量达到了上千家。  &lt;em&gt;（https://www.datagrand.com/products/recommend/）&lt;/em&gt;&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;基于过滤思想的推荐方法&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;经过多年的推荐系统理论发展，已经产生了三代主要的推荐系统。第一代推荐系统（1995-2005），主要包括三种方法：基于内容过滤的方法、基于协同过滤的方法和混合方法，技术上主要是规则统计和机器学习。第二代推荐系统（2003-2014），主要是基于时间、位置、用户组评分等特征上下文，对这一代推荐系统的研究目前仍在进行中。第三代推荐系统的研究更侧重在基于表示学习的语义模型以及在推荐过程中会有较多的关于知识组件的使用。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;01&lt;/strong&gt;  &lt;strong&gt;基于协同过滤的推荐方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;协同过滤方法（Collaborative Filtering，CF）是一种传统的推荐方法，体现的是群体智慧，它基于用户的兴趣偏好和与物品的历史交互行为进行推荐。这种方法可以分为基于记忆的方法和基于模型的方法。而基于记忆的方法可以分为两类：基于用户的（User-based CF）和基于物品的（Item-based CF）。基于内存的方法最流行的算法是KNN算法，该算法使用了一些传统的相似性度量，如 Pearson、Spearman、Cosine、Jaccard 等。另一方面，在基于模型的方法中，最常用的是矩阵分解（MF）及其变体（NMF、SVD）。目前，又出现了一些新的基于模型的协同过滤方法，如贝叶斯、基于聚类的、基于规则的和基于图的推荐方法。  &lt;br /&gt;  &lt;br /&gt;协同过滤主要存在两个问题：当用户与物品之间的交互很少时用户数据的稀疏性，以及冷启动问题（新用户和新物品）。另外就是是传统的推荐技术没有利用推荐场景中的诸多语义信息、关键字关系和层次结构。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;02&lt;/strong&gt;  &lt;strong&gt;基于内容过滤的推荐方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;基于该方法的推荐系统通过学习和用户过去偏好的物品在内容特征方面比较相似的新物品进行推荐。这类方法可以分为基于案例推理（case-based reasoning）和基于属性（attribute-based）的技术。基于案例推理的技术主要是推荐与用户过去喜欢的物品高度相关的物品。相比之下，基于属性的技术基于将物品属性与用户属性相匹配来进行推荐结果生成。大多数基于内容过滤的推荐系统使用的模型包括：关键字匹配或向量空间模型（VSM）、基于词频-逆文档频率（TF-IDF）加权、主题建模等。  &lt;br /&gt;  &lt;br /&gt;基于内容过滤的推荐方法，推荐出来的物品具有较高的文本相关性，同时可以很好的解释推荐结果，但是推荐出来的结果往往惊喜度较差，同时文本特征较为稀疏时也会影响相关性的计算。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;03&lt;/strong&gt;  &lt;strong&gt;基于人口统计信息过滤的推荐方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;该方法的主要思想是具有某些共同个人属性（性别、年龄、国家等）的用户也具有共同偏好这一事实。基于此，这些系统可以通过根据人口统计属性对用户进行分类来生成推荐结果。当物品的信息量很有限时，这些方法特别有用。该方法的一个优点是它不需要用户对基于内容和协同过滤方法所必需的物品进行评分或者有交互反馈。  &lt;br /&gt;  &lt;br /&gt;然而，这种类型的推荐方式的主要问题，一是由于涉及安全和隐私问题，为用户收集完整的信息是不切实际的；二是该方法向相关人口统计群体的用户推荐相同的商品，个性化程度受限。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;04&lt;/strong&gt;  &lt;strong&gt;基于上下文感知过滤的推荐方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;该类推荐系统结合场景上下文信息进行推荐。这种方法假设当前推荐场景的上下文是用一组预定义的可观察属性定义的，其结构不会随着时间的推移而发生显着变化。所谓的上下文信息主要包括时间、位置或者其他人（如朋友、亲戚或同事）。这些上下文信息为推荐结果的生成提供了额外的信息，相对于仅考虑用户或者物品自身信息，会有更多的补充。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;05&lt;/strong&gt;  &lt;strong&gt;基于知识过滤的推荐方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;该类推荐系统主要是基于领域知识考虑如何推荐以满足用户的兴趣偏好。这些系统应该使用三种类型的知识：关于用户的知识、关于物品的知识以及关于物品与用户需求之间对应关系的知识。总体上来说，该方法主要是依靠知识图谱来为推荐系统更多的辅助信息以提升推荐精准度。后面会展开来详细介绍。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;06&lt;/strong&gt;  &lt;strong&gt;混合过滤的推荐方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;这些系统通常将协同过滤与内容过滤或协同过滤与任何其他推荐方法相结合进行推荐。结合的目标是利用每种方法的优势以提高整体系统性能和推荐效果。目前，一些关于混合方法的工作包括基于深度学习方法、贝叶斯网络、聚类、潜在特征和图结构等等。  &lt;br /&gt;  &lt;br /&gt;近年来，基于深度神经网络的方法，如 DNN 、Wide &amp;amp; Deep、DeepFM在排序学习（Learn to Rank，LTR）方面取得了令人瞩目的表现。这些方法遵循嵌入（Enmbedding）和多层感知机(Multilayer Perceptron，MLP)范式，其中大规模稀疏特征首先嵌入到低维向量中，然后连接在一起输入多层感知器以学习特征之间的非线性关系。先进的LTR方法发现了从用户的历史行为中提取用户兴趣以进行排名的有效性。具体来说，DIN（Deep Interest Network）使用注意力机制从用户对候选物品的历史行为中学习用户兴趣的表示。DIEN（Deep Interest Evolution Network）使用循环神经网络来捕捉用户兴趣的演变。DMT（Method Deep Multifaceted Transformers）利用多个转换器对用户的不同行为序列进行建模。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;总体上来说，推荐算法是推荐系统的核心元素。基于协同过滤的推荐方式是以交互数据中用户或物品的相似性对用户兴趣偏好进行建模，而基于内容过滤的推荐方法则主要是利用物品的内容特征。基于协同过滤的推荐系统已被广泛应用，因为它们可以有效地捕获用户偏好，并且可以在多种场景中可以快速方便的实现，而无需像基于内容过滤的推荐系统中提取各种特征。然而，基于协同过滤的推荐方法存在数据稀疏和冷启动问题。为了解决这些问题，已经提出了很多类型的混合推荐系统来统一交互级相似性和内容级相似性。在这个过程中，也探索了多种类型的辅助信息，例如物品属性、评论数据、用户的社交网络等等。实践证明，混合推荐系统通常可以获得更好的推荐结果，并且近年来越来越受欢迎。  &lt;br /&gt;&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;知识图谱概述&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;知识图谱（Knowledge Graph，KG）是一种描述实体或概念并使用不同类型的语义关系将它们连接起来的结构。2012 年，Google提出术语“知识图谱”来指代语义知识在网络搜索中的使用，目的是提高搜索引擎的能力，增强用户的搜索体验。在“知识图谱”一词流行之前，DBPedia和其他链接数据集是由语义Web技术和Berners-Lee提出的链接数据设计问题生成的。如今，KG已经在业界获得了广泛关注并进行了大规模的系统应用。  &lt;br /&gt;  &lt;br /&gt;在过去的数年中，越来越多的语义数据遵循关联数据原则，通过将来自不同主题领域的各种信息（如人、书籍、音乐、电影和地理位置）连接到一个统一的全球数据空间中来发布。这些异构的数据相互联系，形成了一个巨大的信息资源库，称为知识库。已经构建了几个典型的知识库，包括YAGO、NELL、DBpedia、DeepDive等学术项目，以及微软的Satori、谷歌的Knowledge Graph等商业项目。使用来自知识库的异构连接信息有助于深入了解单个领域的数据难以发现的问题。&lt;/p&gt;



 &lt;p&gt;以下是部分知识库介绍：&lt;/p&gt;



 &lt;ol&gt;
  &lt;li&gt;Freebase是一个非常实用的并且可拓展的元组数据库系统，旨在成为世界知识的公共存储库。它的设计灵感来自广泛使用的信息社区，如语义网和维基百科。Freebase 中的数据是结构化的，通过协作创建的方式生成。它支持高度多样化和异构的数据，并具有高可扩展性。Freebase 目前包含125000000+ 元组、4000+类型和 7000+属性。MQL (Metaweb Query Language)作为一种对数据执行查询和操作的语言，通过基于HTTP协议的图查询（graph-query）API可以实现对Freebase的读写操作。MQL为Freebase中的元组数据提供了易于使用的面向对象的接口，它的产生旨在促进通过协作方式创建基于 Web 的面向数据的应用程序。&lt;/li&gt;



  &lt;li&gt;DBpedia是从111种语言的维基百科版本中提取结构化数据来构建的一个大规模多语言知识库。从英文版维基百科中抽取的最大DBpedia知识库包含4亿多条事实数据，用于描述370万种事物。从其它的110个维基百科版本中抽取的DBpedia知识库总共包含14.6亿事实数据，描述1000万种额外事物。DBpedia将27种不同语言版本的维基百科信息框（infoboxes）映射到一个单一的共享本体中，该本体由320个类和1650 个属性组成。这些映射是通过世界范围内的众包工作创建的，从而可以很好的融合来自不同维基百科版本的知识。该项目定期发布所有DBpedia知识库以供下载，并通过本地DBpedia章节的全球网络提供对111种语言版本中的14 种语言版本的SPARQL查询访问。除了定期发布之外，该项目还维护一个实时知识库，该知识库会在维基百科中的页面发生更改时进行更新。DBpedia设置了2700万个RDF链接，指向30多个外部数据源，从而使来自这些源的数据能够与DBpedia数据一起使用。&lt;/li&gt;



  &lt;li&gt;YAGO是由德国马普研究所研制的链接数据库。YAGO主要集成了Wikipedia、WordNet和GeoNames三个来源的数据。YAGO建立在实体和关系之上，目前包含超过 100 万个实体和 500 万个事实，1.2亿条三元组知识，包括 Is-A 层次结构以及实体之间的非分类关系，事实已自动从Wikipedia中提取并与 WordNet统一。YAGO将WordNet的词汇定义与Wikipedia的分类体系进行了融合集成，使得YAGO具有更加丰富的实体分类体系。YAGO还考虑了时间和空间知识，为很多知识条目增加了时间和空间维度的属性描述。   &lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;



 &lt;p&gt;知识图谱本质上是一种基于图的数据结构,是一种揭示实体之间关系的语义网络。通俗来讲，就是把不同种类的信息连接在一起得到的一个语义关系网，知识图谱以结构化的方式描述客观世界，沉淀背景知识，将信息知识表示成更接近人类认识世界的形式，已经被广泛应用于搜索引擎、智能推荐、智能问答、语言理解、决策分析等领域。&lt;/p&gt;



 &lt;p&gt;  &lt;img src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/f6446d32-f809-4e64-b692-3c4f78322bcf.jpg"&gt;&lt;/img&gt;&lt;/p&gt;



 &lt;p&gt;图2 达观知识图谱功能展示&lt;/p&gt;



 &lt;p&gt;达观知识图谱，是达观数据公司面向各行业知识图谱应用而推出的新一代产品，其整合了知识图谱的设计、构建、编辑、管理、应用等全生命周期实现，基于客户的多源异构数据整合构建知识中台，可以实现从业务场景出发到生成图谱、再到实现基于图谱的应用，显著提高了各行业中知识图谱的落地效率和效果。  &lt;br /&gt;&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;知识图谱和推荐系统&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;传统的推荐系统更多的是将用户和物品之间的显式或隐式反馈作为输入，这带来了两个问题：&lt;/p&gt;



 &lt;ol&gt;
  &lt;li&gt;在实际场景中，用户和物品之间的交互信息特别稀疏。例如，一个在线购物应用可能包含数十万的商品，而用户实际购买的商品数量可能仅有数百。使用如此少量的行为反馈数据来预测大量未知信息会显着增加算法过拟合的风险。   &lt;br /&gt;&lt;/li&gt;



  &lt;li&gt;对于新用户和新物品的推荐，由于缺乏历史交互信息，系统推荐的精准度就会受到极大的负面影响。解决稀疏性和冷启动问题的一种常见方法是在推荐算法的输入中引入额外的辅助信息，例如用户属性、项目属性和上下文信息等等。&lt;/li&gt;
&lt;/ol&gt;



 &lt;p&gt;近年来，将知识图谱作为辅助信息引入推荐系统已经成为了工业界和学术界的研究热点。KG一方面可以提供丰富的领域知识作为补充信息来克服协同过滤和基于内容过滤的推荐方法所面临的问题；另一方面，推荐系统可以使用 KG 中存在的语义关系来提高其准确性并增加推荐物品的多样性。具体来说，KG 推荐利用了代表用户的实体、要推荐的物品及其交互之间的联系。推荐系统使用各种连接来识别目标用户可能感兴趣的物品集合。因此，复杂的关系表示为基于KG的推荐系统提供了额外的有价值的信息，以在节点之间应用推理来发现新的连接。相反，一般来说，基于特征向量的经典推荐方法会忽略这种连接，这可能会导致整体的推荐性能欠佳，尤其是在数据稀疏的情况下。&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;融入知识图谱的推荐系统&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;KG是一个异质图，节点表示实体，边缘表示实体之间的关系。物品及其属性可以映射到 KG 中，以表征物品之间的相互关系。此外，用户及其信息也可以集成到KG中，这就使得用户和物品之间的关系以及用户偏好可以更准确地捕获。&lt;/p&gt;



 &lt;p&gt;一般来说，基于KG的推荐方法，第一步需要构建KG，可以是物品知识图谱（Item Knowledge Graph，IKG），也可以是用户物品知识图谱（User-Item Knowledge Graph，UIKG）。  &lt;br /&gt;&lt;/p&gt;



 &lt;ol&gt;
  &lt;li&gt;关于IKG。在IKG中，物品和他们关联的实体（如物品属性）作为节点，而边可以表示物品的属性级关系（如品牌、类别等），也可以表示为用户相关的关系（如“都浏览”、“都购买”）。&lt;/li&gt;



  &lt;li&gt;关于UIKG。在UIKG中，用户、物品和他们相关的实体都是节点，边可以表示用户和物品之间的关系（如点击、收藏、购买等）。&lt;/li&gt;
&lt;/ol&gt;



 &lt;p&gt;以IKG的构建为例，物品首先映射到外部 KG 以找到它们的关联实体，然后从 KG 中提取关联实体的多跳邻居，并形成推荐系统的子图。当然也可以不需要依赖外部KG，可以基于所提供的数据中的辅助信息来构建KG。  &lt;br /&gt;  &lt;br /&gt;可解释的推荐系统是近年来的另一个热门研究方向。一方面，在推荐结果呈现的实现如果可以向用户提供适当的推荐解释，则用户可以相对更好地接受推荐结果。另一方面，也可以更深入地了解推荐算法。与传统的推荐系统相比，基于知识图谱的推荐系统呈现了连接用户和物品的多种实体和关系，并且能够很好地展示推理过程。&lt;/p&gt;



 &lt;p&gt;基于知识图谱的推荐方法，按照如何应用知识图谱数据，可以分为三类，分别是基于嵌入的方法、基于连接的方法和基于传播的方法。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;01&lt;/strong&gt;  &lt;strong&gt;基于嵌入的方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;基于嵌入（Enbedding-based）的方法主要思想是使用KG中大量的事实知识来进一步地丰富用户和物品的多维度表示，其中主要包括两大基础模块，一个是图嵌入模块，用于学习KG中实体和关系的表示，也就是需要应用知识图嵌入（Knowledge Graph Embedding，KGE）算法将KG编码为低秩嵌入，KGE算法可以分为两类：平移距离模型，如TransE、TransH、TransR、TransD等，以及语义匹配模型，如 DistMult。  &lt;br /&gt;  &lt;br /&gt;另外一个是推荐模块，基于学习到的特征用于预测用户对物品的偏好。基于这两个模块在整个推荐框架中的关联方式的差异，基于嵌入的方法可以进一步细分为两阶段学习的方法、联合学习的方法和多任务学习的方法。该类方法面临的挑战包括如何使用合适的KGE方法以获得实体的嵌入表示以及如何将学习到的实体嵌入表示集成到推荐模块中。&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/f6719286-4fc4-41e7-a4bb-9d9b2da85a7d.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;图3 DKN框架&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;  &lt;strong&gt;（1）两阶段学习方法   &lt;br /&gt;   &lt;br /&gt;&lt;/strong&gt;两阶段学习方法是指分别对图嵌入模块和推荐模块进行训练。第一步，使用KGE算法学习实体和关系的嵌入表示，接着，将预训练好的图相关嵌入连同其它的用户特征和物品特征输入到推荐模型进行用户兴趣预测。图3是用于新闻推荐的DKN（Deep Knowledge-aware Network）两阶段学习框架图。在第一阶段，提取新闻标题中的实体并将其映射到 Satori KG以挖掘新闻之间的知识级关系。DKN 通过将用KCNN学习到的句子的文本嵌入表示和通过TransD将新闻内容中的实体的知识级嵌入二者结合来对新闻进行建模。为了捕捉用户对新闻的动态兴趣，通过引入注意力机制，聚合用户的历史点击新闻的嵌入来学习用户的表示。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;两阶段学习方法易于实现，其中 KG 嵌入通常被视为后续推荐模块的额外特征。另一个好处是可以在没有交互数据的情况下学习 KG 嵌入，因此，大规模交互数据集不会增加计算复杂度。此外，由于KG通常是稳定的，一旦学习好了嵌入表示，就没有必要频繁更新嵌入表示。但是，通过 KGE 模型优化的实体嵌入更适合于图内应用，例如 KG补全。由于 KGE 模块和推荐模块是松耦合的，因此学习到的嵌入也可能不适合后续的推荐任务。&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/63c75c22-fa89-406f-addd-9d1df350af98.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;图4 CKE推荐系统流程&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;  &lt;strong&gt;（2）联合学习法   &lt;br /&gt;&lt;/strong&gt;另一个趋势是以端到端（end-to-end）的训练方式联合学习（Joint Learning）图嵌入模块和推荐模块。这样，推荐模块可以指导图嵌入模块中的特征学习过程。CKE（Collaborative Knowledge Base Embedding）统一CF框架中的各种类型的辅助信息，包括物品的属性级特征、文本特征和视觉特征。属性级特征用TransR编码以从KG中学习结构知识，而文本特征和视觉特征用自动编码器进行提取。这三个特征学习模块的目标函数加上推荐模块共同学习模型参数。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;联合学习方法可以进行端到端的训练，并且可以使用 KG 结构对推荐系统进行正则化。然而，在实际应用过程中，需要对不同目标函数的组合进行微调。&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/bd9a851e-c904-48f0-b628-b44e16b254b5.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;图5  MKR框架及交叉压缩单元示例&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;  &lt;strong&gt;（3）多任务学习法   &lt;br /&gt;&lt;/strong&gt;最近的一个研究方向是采用多任务学习（Multi-Task Learning）的策略，在KG相关任务的指导下训练推荐任务。动机是用户-物品交互二分图中的物品及其在 KG 中的关联实体可能共享相似的结构。因此，物品和实体之间低级特征的转移有助于促进推荐系统的改进。MKR（Multi-task feature learning approach for Knowledge graph enhanced Recommendation）由一个推荐模块和一个KGE模块组成。这两个模块不是将 KG 嵌入输入到推荐模块中，而是独立的，并通过交叉压缩单元进行连接以共享知识。推荐模块被训练以估计用户对候选物品的偏好，而KGE模块被训练来估计给定头部实体和三元组中的尾部实体表示。具体来说，推荐模块基于MLP以获得最终用户表示。最终的物品表示由L层交叉压缩单元及其在KG中的相关实体来进行细化。使用非线性函数估计用户对候选物品的偏好程度。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;通过应用多任务学习策略，有助于防止推荐系统过拟合，提高模型的泛化能力。然而，与联合学习方法类似，它需要努力在一个框架下集成不同的任务。  &lt;br /&gt;综上，尽管两阶段学习方法易于实现，但学习到的实体嵌入可能不适合推荐任务，联合学习方法通过端到端训练学习优化的实体嵌入，多任务学习方法通过从KG相关任务中转移知识进一步提高模型的泛化能力。但是，它需要大量的实验来找到不同目标函数的最佳组合。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;02&lt;/strong&gt;  &lt;strong&gt;基于连接的方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;基于连接（Connection-based）的方法利用图中的连接模式来指导推荐。相关的大多数工作都使用UIKG来挖掘图中实体之间的关系。探索KG中的连接信息有两种主要方法。第一个方向是利用图中的元结构，包括元路径和元图，来计算实体之间的相似度。基于元结构的相似性可以作为用户和物品表示的约束，也可以用于预测用户对交互历史中相似用户或相似物品的兴趣偏好。第二种解决方案是将用户-物品对或物品-物品对之间的连接模式编码为向量，可以集成到推荐框架中。这种方法也叫基于路径嵌入的方法。这种方法的挑战包括：1）如何为不同的任务设计合适的元路径；2）如何对实体之间的连接模式进行建模。  &lt;br /&gt;  &lt;br /&gt;  &lt;strong&gt;（1）基于元结构的方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;基于元结构（Meta-structure based）的方法的一种实现是利用不同元路径中实体的连接相似性作为图谱正则化项来约束用户和物品的表示。其动机是基于元路径的实体相似度越高，则在潜在空间中越接近。  &lt;br /&gt;目标函数如式（1）所示：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/adafc654-04f5-4d06-9545-19749e082ecc.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;其中LRec表示推荐系统的目标函数，常见的选择是矩阵分解。相似性约束LSim指导用户嵌入和物品嵌入的学习。为了度量图中实体之间的连接相似性，通常使用PathSim, 如式（2）所示：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/9c802e8c-695c-4611-a461-deb4b403f8b3.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;其中Pm~n是实体m和n之间的一条路径。通常使用三种类型的实体相似性，具体如下：（a）用户-用户相似度，目标函数如式（3）所示：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/382e3b37-57cd-46f9-9c36-5aab988a6505.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;其中||Ui-Uj||F表示矩阵 Frobenius 范数， ɵ=[ɵ1,ɵ2,.....ɵL]表示每个元路径的权重，U=[u1，u2，...，um]表示所有用户的潜在向量，S[1-(i,j)]表示用户i和j在元路径中的相似度得分。如果用户共享基于元路径的高相似性，则用户-用户相似性会迫使用户的嵌入在潜在空间中接近。&lt;/p&gt;



 &lt;p&gt;（b）物品-物品相似度，目标函数如式（4）所示：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/d7b63be7-a3d5-4126-b234-b50710d060a6.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;其中 V=[v1,v2,...,vn]表示所有物品的潜在向量.与用户-用户相似度类似，如果物品的基于元路径的相似度很高，则物品的低秩表示应该是接近的。&lt;/p&gt;



 &lt;p&gt;（c）用户-物品相似度，目标函数如式（5）所示：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/967e12c6-a499-4ed6-bed4-01f926c3f3b6.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;如果基于元路径的相似度很高，则用户-物品相似度项将迫使用户和物品的潜在向量彼此接近。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;除了以上三种相似度的方法以外，基于元结构的方法也可以利用实体相似度来预测用户对未评分物品的兴趣，这可以作是KG中的偏好融合。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;综上，上述方法首先从交互矩阵及其基于元结构的相互相似性中学习用户和物品的潜在向量，然后基于增强的表示进行预测。也可以直接使用相似用户评分的加权集合来预测对未评分项目的偏好。基于元结构的方法是可以解释的，因为这些手动设计的元结构通过匹配候选物品与交互物品或目标用户之间的元结构来为推荐系统提供更多参考信息。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;基于元结构的方法易于实现，大多数工作都是基于模型复杂度相对较低的MF技术。然而，元路径或元图的选择需要领域知识，并且这些元结构对于不同的数据集可能会有很大差异。此外，在某些特定场景下可能不适合应用基于元结构的方法。例如，在新闻推荐任务中，属于一个新闻的实体可能属于不同的域，这使得元路径设计变得困难。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;  &lt;strong&gt;（2）基于路径嵌入的方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;基于元结构的方法的一个问题是连接模式没有明确建模，这使得很难学习用户-物品对和连接模式之间的相互影响。但是，基于路径嵌入的方法可以显式地学习连接模式的嵌入。通过学习连接UIKG中的用户-物品对或IKG 中的物品-物品对的路径的显式嵌入，以便直接建模用户-物品或物品-物品关系。以UIKG中的关系建模为例，假设KG中有K条连接ui和Vj的路径，路径p的嵌入表示为hp，则可以通过式（6）获得ui和Vj之间交互的最终表示：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/684caf7e-0939-43dc-97ca-9537a97d140c.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;其中g(∙)是从每个路径嵌入中汇总信息的函数，常见的选择是最大池化操作或加权求和操作。然后，ui和Vj的偏好可以通过式(7)建模：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/551a0ff2-171d-42ad-8e59-5c8249dd72e2.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;其中f(∙)是映射用户-物品对之间的交互表示以及用户-物品对嵌入到偏好分数的函数。&lt;/p&gt;



 &lt;p&gt;推荐结果可以通过检查每个元路径的权重来解释。较高的元路径权重意味着目标用户和候选物品之间的这种关系在做出决策时更重要。  &lt;br /&gt;  &lt;br /&gt;基于路径嵌入的方法将用户-物品对或物品-物品对的连接模式编码为潜在向量，从而可以考虑目标用户、候选物品和连接模式的相互影响.此外，大多数模型能够通过计算合适的路径并选择显著路径来自动挖掘连接模式，而无需预定义的元结构的帮助。因此，它很可能捕捉到富有表现力的连接模式。但是，如果图中的关系很复杂，则图中可能的路径数量可能会增长到很大。随意实际上，不可能利用大规模 KG 中每个实体对的所有路径，这可能会阻碍模型的性能。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;综上，基于连接的方法在很大程度上依赖于连接模式。但是元路径的表示能力是有限的，这阻碍了传统的基于元结构的方法的性能。基于路径嵌入的方法进一步克服了基于元结构的方法的另一个缺点，即需要领域知识和人工配置路径。这些方法枚举可能的路径并显式建模用户-物品对或物品-物品对之间的关系。然而，基于路径嵌入的方法在一定程度上牺牲了可扩展性，因为这些模型相对复杂，在枚举路径和学习表示时需要更多的计算。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;03&lt;/strong&gt;  &lt;strong&gt;基于传播的方法&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;基于嵌入的方法利用知识图谱中的语义关系来丰富用户和物品的表示，但难以捕捉实体之间的高阶关系。基于连接的方法使用图中的连接信息来指导推荐，但是，通过将复杂的用户物品连接模式分解为单独的线性路径，不可避免地会丢失信息。为了充分利用 KG 中的信息，基于传播的方法集成实体和关系的表示以及高阶连接模式，以实现更个性化的推荐。基于传播的方法的主要想法是嵌入传播，其中常见的实现方式是基于 GNN 技术。这些方法通过聚合KG 中多跳邻居的嵌入表示来细化实体表示。然后，可以使用用户和潜在项目的丰富表示来预测用户的偏好。&lt;/p&gt;



 &lt;p&gt;根据在消息传播过程中细化的实体类型产的差异可以进一步的进行细分为三类。这种方法的挑战包括：&lt;/p&gt;



 &lt;ol&gt;
  &lt;li&gt;如何为不同的邻居分配适当的权重&lt;/li&gt;



  &lt;li&gt;如何在不同的关系边上传播消息 &lt;/li&gt;



  &lt;li&gt;如何提高模型的可扩展性   &lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;



 &lt;p&gt;  &lt;strong&gt;（1）用户嵌入表示的细化&lt;/strong&gt;  &lt;br /&gt;根据用户的交互历史细化用户嵌入表示。先是构建IKG使用多个关系将交互物品和候选物品连接起来。则用户可以表示为他们交互物品及其多跳邻居的组合。具体来说，交互历史中的物品被选为传播过程的种子。然后，沿图中的链接提取多跳三元组集合S[k-ui](k=1,2,...，H),其中S[1-ui]是三元组集(eh,r,et)，头部实体是用户ui的交互过的物品列表。学习用户表示ui的过程可以表述为如下两步：&lt;/p&gt;



 &lt;p&gt;（a）通过聚合三元组集合S[k-ui](k=1,2,...，H)的每一层中的实体来计算用户的嵌入表示o[k-u]。&lt;/p&gt;



 &lt;p&gt;（b）合并o[k-u](k=1,2,...，H)，得到最终的用户嵌入表示ou。  &lt;br /&gt;&lt;/p&gt;



 &lt;p&gt;由于传播过程是从用户交互过的物品开始，到远邻结束，这个过程可以看作是在IKG中逐层向外传播用户的偏好。因此，这些方法可以解释为沿着 KG 中的路径从历史兴趣中传播用户的偏好。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;在这些方法中，边权重在IKG 中是明确的。因此，可以选择连接候选物品和交互项目的显著路径，并作为推荐结果的解释。尽管这些工作同时利用了实体嵌入和高阶连接信息，但只有用户嵌入表示在传播过程中得到更新。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;  &lt;strong&gt;（2）物品表示的细化   &lt;br /&gt;&lt;/strong&gt;上面介绍了通过在图中向外聚合实体来优化用户嵌入表示。另一种方式是通过聚合项目Vj的多跳邻居N[k-u](k=1,2,...，H)在IKG中向内的嵌入表示来学习候选物品Vj的高阶表示。在向内传播过程中，采用图注意力机制，其中不同邻居的权重是由用户和关系来确定的。主要是考虑到用户对不同的关系是有不同的偏好的，从而可以确定KG的信息流。&lt;/p&gt;



 &lt;p&gt;每一轮传播过程表示为如下两步:&lt;/p&gt;



 &lt;p&gt;（a）通过式（8）聚合实体ei的近邻：  &lt;img src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/869272fa-1fae-4ce2-9174-c38b1138187a.jpg"&gt;&lt;/img&gt;  &lt;br /&gt;（b）使用h—1阶邻居嵌入和自嵌入更新实体的h阶表示，如式（9）所示：&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221115/f35d8e8b-2b49-48a4-81da-28507bf808b3.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;其中e[0-i]代表实体的初始表示，e[h-i]代表实体的h阶表示，它是实体初始表示和来自h跳邻居的表示的混合。聚合函数将N个邻居映射到向量∈Rd，更新函数g(∙)是一个非线性函数：Rd ⨉ Rd → Rd。通过迭代地重复这个过程H次，候选物品的表示则包含了来自H跳邻居的信息。  &lt;br /&gt;  &lt;br /&gt;综上，通过IKG中的向内传播来细化物品的嵌入表示。然而，类似于在 KG 中向外聚合的用户细化，只有一种类型的实体被细化。  &lt;br /&gt;  &lt;br /&gt;  &lt;strong&gt;（3）用户和物品表示的细化   &lt;br /&gt;&lt;/strong&gt;在UIKG中的传播过程中，用户、物品及其关联实体都连接在一个图中，用户-物品对之间的交互作为一种关系。用户嵌入和物品嵌入可以在传播过程中使用其对应的邻居进行细化，如式 (8) 和 (9) 所示。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;与IKG中的传播类似，UIKG中边的权重也是由用户确定的。因此，这些模型可以通过检查连接目标用户和候选物品的显著路径来为推荐结果提供解释。由于用户被合并为一种类型的节点，因此解释更加直观，因为每个交互物品的贡献都是可用的。通过将用户纳入KG，可以更大程度地探索高阶连接模式。缺点是图中的关系越多，会带来不相关的实体，可能会误导用户在聚合过程中的偏好。&lt;/p&gt;



 &lt;p&gt;  &lt;br /&gt;综上，基于传播的方法通常计算成本高。随着图变大，模型变得难以收敛。为了提高效率，可以使用更快的图卷积运算，并且通常在每一层中应用邻域采样。但是，随机抽样不可避免地会导致信息丢失，无法充分挖掘图中的知识。  &lt;br /&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;04&lt;/strong&gt;  &lt;strong&gt;基于KG的推荐方法总结&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;通过上述基于嵌入方法、基于连接方法和基于传播方法的介绍，可知基于嵌入的方法是最灵活的方法。一方面，使用KGE模块对KG进行编码相对容易，并且学习到的嵌入可以自然地融入到用户表示或项目表示中。而在基于连接的方法中，在图中定义元路径或元图可能很繁琐。对于基于传播的方法，需要仔细设计聚合和更新部分。另一方面，基于嵌入的方法适用于大多数应用场景，因为外部知识通常在不同的任务中可用。相反，在基于元结构的方法中，元路径对于不同的应用场景通常是多种多样的，并且不能泛化到新的数据集。此外，对于特定场景，如新闻推荐，很难定义元路径并应用基于元结构的方法。同时，基于路径嵌入的方法和基于传播的方法都不适用于具有大规模数据集的推荐场景，因为在枚举路径和邻居时计算复杂度可能会变得很大。此外，路径的质量和数量对于基于连接的方法至关重要，因此，稀疏数据集可能无法提供足够的路径来挖掘此类方法的关系和模型兴趣。然而，基于嵌入的方法和基于连接的方法都未能充分探索KG中的信息。近年来，随着GNN技术的发展，基于传播的方法已成为一种新的研究趋势。此外，基于连接的方法和基于传播的方法都可以用KG中的路径来解释，而基于嵌入的方法解释起来不太直观。  &lt;br /&gt;&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;基于KG推荐的可解释性&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;KG中包含有大量的辅助信息可以用于推荐结果的解释，主要有以下几种方法：  &lt;br /&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;01&lt;/strong&gt;  &lt;strong&gt;关系嵌入的注意机制&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;这种方法主要应用于基于嵌入的方法。注意力机制应用于KG中实体之间关系的嵌入。从不同关系的注意力权重，可以得到每类物品属性对目标用户的意义。因此，这种技术可以为推荐提供偏好级别的解释。  &lt;br /&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;02&lt;/strong&gt;  &lt;strong&gt;定义元路径或者元图&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;所选物品与目标用户或交互物品之间的关系可以分解为若干元路径或元图的组合。通过将元路径或元图转换为可理解的规则，系统可以提供解释。  &lt;br /&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;03&lt;/strong&gt;  &lt;strong&gt;路径嵌入的注意机制&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;对于路径嵌入方法，连接目标用户和候选物品的特定路径的权重可通过注意力机制获得。每条路径的权重可以代表每条路径对用户的相对重要性。因此，可以根据图中的显著路径来提供解释。  &lt;br /&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;04&lt;/strong&gt;  &lt;strong&gt;UIKG中的强化学习&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;通过使用强化学习技术在UIKG中训练代理，可以挖掘连接用户物品对的实际路径。它可以直接显示KG中的推理过程，而不是为已经选择的推荐结果寻找事后解释。因此，推理过程对于目标用户来说是精确且值得信赖的。  &lt;br /&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;05&lt;/strong&gt;  &lt;strong&gt;提取边缘权重&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;基于传播的方法需要在聚合过程中为每种类型的邻居分配用户特定的权重。边权重控制图中实体之间的信息流，可以反映KG中每种关系的重要性。此外，KG中实体之间的边权重也可以从注意力权重或学习关系矩阵中获得。因此，可以通过找到连接候选物品和目标用户的显著路径或多跳邻居中的交互物品来生成解释。  &lt;br /&gt;&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;未来展望 &lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;通过前面的介绍可以知道，基于KG的推荐系统在推荐精准度和推荐结果可解释性方面具有诸多优势。在学术界和工业界也已经提出了很好的模型以充分利用KG中的辅助信息进行个性化精准推荐。但是在一些方向上依然还有很多工作值得深入研究，主要体现在：&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;01 &lt;/strong&gt;  &lt;strong&gt;动态推荐&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;尽管具有GNN或GCN架构的基于KG的推荐系统取得了良好的性能，但训练过程非常耗时。因此这样的模型可以被视为静态偏好推荐。然而，在某些场景下，例如在线购物、新闻推荐等，用户的兴趣会很快受到社交事件等的影响。在这种情况下，使用静态偏好建模的推荐可能不足以理解实时兴趣。为了捕捉动态偏好，利用动态图网络可能是一种解决方案。  &lt;br /&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;02 &lt;/strong&gt;  &lt;strong&gt;跨域推荐&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;在跨领域推荐的也有一些研究进展，主要是交互数据在各个领域是不平衡的。例如，在亚马逊平台上，图书子集大于其他域。通过迁移学习技术，可以共享来自具有相对丰富数据的源域的交互数据，以便在目标域中进行更好的推荐。  &lt;br /&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;03 &lt;/strong&gt;  &lt;strong&gt;知识增强语言表示&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;为了提高各种 NLP 任务的性能，有一种趋势是将外部知识集成到语言表示模型中，使知识表示和文本表示可以相互提炼。将知识增强文本表示策略应用于基于文本的推荐任务中，可以更好地进行表示学习，以提供更准确的推荐。  &lt;br /&gt;  &lt;br /&gt;   &lt;em&gt;参考文献   &lt;br /&gt;&lt;/em&gt;  &lt;em&gt;[1] Bollacker K, Evans C, Paritosh P, et al. Freebase: a collaboratively created graph database for structuring human knowledge[C]//Proceedings of the 2008 ACM SIGMOD international conference on Management of data. 2008: 1247-1250.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[2] Lehmann J, Isele R, Jakob M, et al. Dbpedia–a large-scale, multilingual knowledge base extracted from wikipedia[J]. Semantic web, 2015, 6(2): 167-195.&lt;/em&gt;  &lt;em&gt;[3] Suchanek F M, Kasneci G, Weikum G. Yago: a core of semantic knowledge[C]//Proceedings of the 16th international conference on World Wide Web. 2007: 697-706.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[4] Bordes A, Usunier N, Garcia-Duran A, et al. Translating embeddings for modeling multi-relational data[J]. Advances in neural information processing systems, 2013, 26.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[5] Wang Z, Zhang J, Feng J, et al. Knowledge graph embedding by translating on hyperplanes[C]//Proceedings of the AAAI conference on artificial intelligence. 2014, 28(1).&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[6] Lin Y, Liu Z, Sun M, et al. Learning entity and relation embeddings for knowledge graph completion[C]//Twenty-ninth AAAI conference on artificial intelligence. 2015.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[7] Ji G, He S, Xu L, et al. Knowledge graph embedding via dynamic mapping matrix[C]//Proceedings of the 53rd annual meeting of the association for computational linguistics and the 7th international joint conference on natural language processing (volume 1: Long papers). 2015: 687-696.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[8] Yang B, Yih W, He X, et al. Embedding entities and relations for learning and inference in knowledge bases[J]. arXiv preprint arXiv:1412.6575, 2014.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[9] Zou X. A survey on application of knowledge graph[C]//Journal of Physics: Conference Series. IOP Publishing, 2020, 1487(1): 012016.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[10] Q. Guo et al., &amp;quot;A Survey on Knowledge Graph-Based Recommender Systems,&amp;quot; in IEEE Transactions on Knowledge and Data Engineering, vol. 34, no. 8, pp. 3549-3568, 1 Aug. 2022, doi: 10.1109/TKDE.2020.3028705.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[11] Chicaiza J, Valdiviezo-Diaz P. A comprehensive survey of knowledge graph-based recommender systems: Technologies, development, and contributions[J]. Information, 2021, 12(6): 232.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[12] Choudhary S, Luthra T, Mittal A, et al. A survey of knowledge graph embedding and their applications[J]. arXiv preprint arXiv:2107.07842, 2021.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[13] Gao Y, Li Y F, Lin Y, et al. Deep learning on knowledge graph for recommender system: A survey[J]. arXiv preprint arXiv:2004.00387, 2020.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[14] Wang H, Zhang F, Xie X, et al. DKN: Deep knowledge-aware network for news recommendation[C]//Proceedings of the 2018 world wide web conference. 2018: 1835-1844.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[15] Zhang F, Yuan N J, Lian D, et al. Collaborative knowledge base embedding for recommender systems[C]//Proceedings of the 22nd ACM SIGKDD international conference on knowledge discovery and data mining. 2016: 353-362.&lt;/em&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;em&gt;[16] Wang H, Zhang F, Zhao M, et al. Multi-task feature learning for knowledge graph enhanced recommendation[C]//The world wide web conference. 2019: 2000-2010.   &lt;br /&gt;&lt;/em&gt;&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;作者简介&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;于敬，达观数据联合创始人，搜索推荐图谱产品团队的总负责人。同济大学计算机硕士，上海市青年科技启明星、上海市五一劳动奖章、上海市职工优秀创新成果奖、ACM CIKM算法竞赛国际冠军等奖项荣誉获得者。国际计算机学会（ACM）会员、中国计算机学会（CCF）高级会员、上海计算机学会（SCS）会员。曾先后在盛大创新院、盛大文学和腾讯文学从事技术研发工作，在智能推荐、搜索引擎、机器学习、大数据技术等领域有丰富的研究和工程经验，拥有十余项授权专利。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>人工智能 推荐系统 深度学习 自然语言处理 知识图谱</category>
      <guid isPermaLink="true">https://itindex.net/detail/62491-%E7%9F%A5%E8%AF%86-%E5%A2%9E%E5%BC%BA-%E6%99%BA%E8%83%BD</guid>
      <pubDate>Thu, 17 Nov 2022 15:35:47 CST</pubDate>
    </item>
    <item>
      <title>探索人机深度融合的高可用性人工智能应用</title>
      <link>https://itindex.net/detail/62471-%E6%B7%B1%E5%BA%A6-%E9%AB%98%E5%8F%AF%E7%94%A8%E6%80%A7-%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD</link>
      <description>&lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221025/3124cb84-010f-4d51-be3c-00dafe4546ed.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;目前，人工智能技术在世界范围内热度极高，但却出现了“雷声大、雨点小”的现象。一方面，随着近年来深度学习技术的不断发展，计算能力的不断提高，更深更复杂网络的普及使用，加上深度学习端到端的特性，看起来好像人工智能就是端到端的标注，不断地做数据清洗，增加标注数据，加深模型参数，就可以实现计算机像人类一样工作。另一方面，人工智能在实际应用场景落地时经常失败，常听到有“只见人工，不见智能”，“有多少人工就有多少智能”的吐槽。因此，目前许多人工智能技术的实现现阶段还不能脱离人工经验。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;人工智能落地需要人工智慧，这里核心在于结合场景和算法特点做合理的设计&lt;/strong&gt;，而非关注更多标准化的标注或者设计更精深的算法网络。达观是ToB的自然语言处理（NLP）公司，主要做办公文档自动处理。近年来在金融、政务、制造业等行业成功落地了非常多的NLP项目。&lt;/p&gt;



 &lt;p&gt;NLP也被誉为人工智能皇冠上的明珠，AI落地特别是NLP落地尤其不容易，通过机器处理办公文档远比从一堆图片中找出有猫的图片要复杂得多。因为让机器处理办公文档，往往存在缺少大量的训练语料情况，不同行业间需要处理的具体问题千差万别，人工都需要专业培训甚至几年工作经验才能处理妥当。本文主要结合达观的实践落地经验，探讨在具体NLP项目落地时，计算机“智能”需要哪些必不可少的“人工”。&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;“人工”化繁为简，拆解复杂问题&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;  &lt;strong&gt;人工智能难以落地的主要原因之一是要处理的问题过于复杂&lt;/strong&gt;，如果只靠算法模型的自学习，很难学到对应知识，从而作出正确决策，就像用大学生的题目考小学生，不可能考得好。但如果我们能  &lt;strong&gt;人工对负责问题进行拆解，分解成多个简单问题，那每个简单的问题可能通过模型就能解决&lt;/strong&gt;。但如何拆解？拆解成什么程度模型是可以处理的呢？达观的经验是：当面对一个NLP问题，人类看完后立刻就能反应出结果的，这样的问题模型就是我们定义的“简单问题”，是机器可以解决的。下面以合同文档抽取的场景为例帮助大家理解。&lt;/p&gt;



 &lt;p&gt;假设我们需要构建模型从PDF格式的合同中抽取出甲方、乙方、违约条款等字段信息，看看机器是怎么一步步进行拆解的：&lt;/p&gt;



 &lt;p&gt;首先看机器的输入数据。PDF格式内部只是规定了每个字符或者线条应该在屏幕上什么位置，这些元素本身没有任何语义上的信息，在计算机看来这份文档其实只有字符以及其位置等简单信息，并没有人看渲染好的PDF文件的对齐、大小、重要性等更多信息。如果通过端到端的方式，把文字以及坐标一起输入到模型，让模型自己学习文档结构，理论上可以抽取出需要的字段。这种方式乍听之下可以一试，但实际效果是非常差的。因为让人看到一堆字和坐标，希望判断出抽取的字段，那本身就是非常复杂的事情，所以我们还需要进一步拆解。&lt;/p&gt;



 &lt;img alt="" height="482" src="https://www.52nlp.cn/wp-content/uploads/2022/10/&amp;#30011;&amp;#26495;-7-&amp;#25335;&amp;#36125;-3-1024x482.jpg" width="1024"&gt;&lt;/img&gt;



 &lt;p&gt;文档解析模型负责解析PDF协议，并且通过一定算法将文档结构化，也就是转成章节、表格、段落等文字流，再输入到字段抽取的模型。这两个模型是否足够简单并能落地呢？&lt;/p&gt;



 &lt;p&gt;大部分文档下，哪个是文字块，哪个是表格，哪个是图片，人是可以瞬间判断出来的。而文字块拆成章节、标题、段落，尤其是有些文档段落开始并没有明显空格，那人还是需要仔细看，有时候还要分析上下文才能分析出来。所以我们将文档解析继续拆解成元素识别模型和段落识别模型。&lt;/p&gt;



 &lt;img alt="" height="418" src="https://www.52nlp.cn/wp-content/uploads/2022/10/&amp;#30011;&amp;#26495;-7-&amp;#25335;&amp;#36125;-2-1024x418.jpg" width="1024"&gt;&lt;/img&gt;



 &lt;p&gt;对于字段抽取，有些字段比较简单，比如甲方、乙方，人眼就能看出结果，这些字段直接通过模型抽取问题不大；有些字段稍微复杂一些，比如合同总金额有时候是在文本中的，有时候是在表格里面的，人在看的时候也需要反应一下才能得到信息，所以可以对字段抽取再进行拆解。表格里面需要专门的表格抽取模型，如果是无线表格，人在看的时候往往还需要将虚线进行对应，所以也可以拆出无线表格识别的模型。文本抽取中，有些字段是长文本。比如违约条款，人在找的时候往往是通过前后文找到抽取的开始和结束，而短字段则更关注抽取本身以及上下文的内容。通过对每个步骤的复杂度进行分析，可以进一步拆解为下面结构。&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221025/14b59b65-86bd-4302-a812-5051f3faa7d8.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;这就是文档抽取常见的模型，但在实际使用中，根据具体数据情况以及分析字段的特点，可能还会再进行拆解。比如某些字段可能是在固定的某些章节或者段落中，用全局的文本进行训练和预测有很大的干扰，那我们就可以再增加一个字段章节预测的模型，定位该字段所在章节。再比如租房合同抽取的字段的文本是比较简单的选择性文本，对于模型来说也有一定困难。在销售合同文本中常常出现：&lt;/p&gt;



 &lt;p&gt;如果需要退货，采用B进行退货退款：&lt;/p&gt;



 &lt;ol&gt;  &lt;li&gt;不能退货&lt;/li&gt;  &lt;li&gt;可以退货，收取20%赔偿金&lt;/li&gt;  &lt;li&gt;可以退货，收取50%赔偿金&lt;/li&gt;&lt;/ol&gt;



 &lt;p&gt;这样的文本则需要拆成2个模型，一个是抽取选择项的模型，另一个是抽取选择列表的模型。&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;“人工”模型选择与优化&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;  &lt;strong&gt;模型选择也是需要“人工”经验的&lt;/strong&gt;，  &lt;strong&gt;需要结合标注数据规模、数据特点、模型难度等进行选择和处理。&lt;/strong&gt;比如前面提到的章节预测的模型，如果章节标题特征比较明显，则可以直接通过关键词或者机器学习模型来进行分类处理，如果写法不太规范，需要通过章节标题和章节内容进行判断，则可能考虑基于bert的深度学习算法。就我们达观的经验而言，不同模型，如果使用完全相同的数据，调优后效果差距在5%以内，如果场景能比较好的使用上预训练模型，比如bert，那效果能提升10%-15%。&lt;/p&gt;



 &lt;p&gt;选定模型之后，也可以通过增加一些特征从而进一步降低模型的难度，提高准确度。在垂直领域文档处理上，业务词典是常用的方法。业务词典包括了专有名词，也包括了字段的重要关键信息的特征。比如我们要抽取合同的乙方，对于公司采购而言，很多都是有供应商库的，或者说可以获得之前与他们签合同的乙方的名称。这个名称构成的词典可能不全，所以不能只靠这个来匹配，但将这个“乙方专有名词”输入模型作为参考特征，是非常有用的。字段的重要关键信息的特征，指的是抽取的这个字段非常关键的上下文。比如抽取“甲方”这个字段，虽然话术可以有多种，比如甲方是xxx，甲方：xxx，甲方是本次的承办单位xxxx等等，但基本都会带“甲方”几个关键字，所以如果把这些专有名词也加入模型，准确度往往会有不小的提升。下面这个是重要词（专有名词或者业务词）使用的例子。&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221025/2949fd99-9642-4e6c-befa-3fa5a60e6b44.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;假设“委员”和“委员会”是重要词。需要对“美国联邦通信委员会最近正式批准苹果展开5G通信试验”的每个字生成词向量。这里的方法是通过2-gram，3-gram，4-gram和5-gram对每个字进行编码，编成8个位，每种gram各2个位表示上文是否是重要词和下文是否是重要词。以“委”字为例编码方式为：&lt;/p&gt;



 &lt;ul&gt;  &lt;li&gt;2-gram，就是“信委”和“委员”，“信委” 不是核心词，而“委员”是核心词，所以编码为“01”&lt;/li&gt;  &lt;li&gt;3-gram，就是“通信委”和“委员会”，“通信委” 不是核心词，而“委员会”是核心词，所以编码为“01”&lt;/li&gt;  &lt;li&gt;4-gram，就是“邦通信委”和“委员会最”都不是核心词，所以编码为“00”&lt;/li&gt;  &lt;li&gt;5-gram，就是“联邦通信委”和“委员会最近”都不是核心词，所以编码为“00”&lt;/li&gt;&lt;/ul&gt;



 &lt;p&gt;其他行业知识也可以用类似的方式生成字向量。把所有的行业向量和原始的字向量进行拼接，作为模型的输入，这样模型就能直接获得行业经验，从而有更好的效果。&lt;/p&gt;



 &lt;p&gt;&lt;/p&gt;



 &lt;h1&gt;  &lt;strong&gt;“人工”构建知识图谱&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;  &lt;strong&gt;有些文本问题有很强的业务性，难以进行拆解，或者业务逻辑太复杂，很难让机器学习到对应的知识。&lt;/strong&gt;清华大学人工智能研究院院长张钹院士在一次演讲中提到“人的智能没法通过单纯的大数据学习把它学出来，那怎么办？很简单，加上知识，让它有推理的能力，做决策的能力，这样就能解决突发事件。”  &lt;strong&gt;达观在落地实践中就是通过知识图谱来解决这种复杂的问题。&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;知识图谱的概念由 Google 在 2012 年正式提出，是一种语义网络知识库，将现有知识的以结构化多关系图（Multi-Relational Graph）的形式进行储存、使用、展示形成。通过将多个实体关系三元组进行融合，形成包含多个不同的实体节点和多种类别的关系边的多关系图，即知识图谱。知识图谱落地也有很多挑战，构建和维护知识图谱的工作量是非常大的，很多项目因为构建过程太过复杂而最终失败。需要合理设计和运用知识图谱，也需要“人工”经验。达观通过知识图谱辅助智能制造有很多成功的落地案例，下面结合实际应用场景，谈下里面的一些经验。&lt;/p&gt;



 &lt;p&gt;生产制造过程中，有很多时候会遇到一些故障，比如手机发热，螺丝拧不上等问题，不快速解决会影响生产流程。之前遇到这类问题只能通过咨询经验丰富的“专家”，但总会存在专家找不到或者专家不一定有空的情况。我们希望通过NLP和知识图谱技术可以解决这个问题。&lt;/p&gt;



 &lt;p&gt;达观通过对里面的数据进行研究发现，要找到这些问题的答案经常要涉及好多文件，比如产品说明书，故障手册等。有些问题容易获得答案，但有些问题可能需要通过一些复杂的推理才能获得答案，甚至不一定能找到答案。面对这个问题，我们设计了制造业失效图谱。&lt;/p&gt;



 &lt;p&gt;为了解决专家录入进行构建的成本过高的问题，一方面我们设计的失效图谱schema只和失效本身相关，其他生成过程中的知识并不纳入产品范围之类，从而减少生成图谱的工作量。另一方面，我们在  &lt;strong&gt;图谱构建的时候，以人工结合智能&lt;/strong&gt;。从相关的文档，比如产品说明书，故障维修手册，失效分析文档等内容中提取相关属性数据，经过人工审核，再录入到图谱中。这种人机结合的方式生成图谱相比于纯人工生成图谱可以大幅减少工作量。图谱数据的抽取主要采用基于pipeline抽取和联合抽取的方法。&lt;/p&gt;



 &lt;p&gt;pipeline抽取，是用NER技术先抽取出实体和属性后，再通过分类方法对实体两两进行分类判断。这种方法的优点是灵活性高，不同类型的实体可以用不同的模型进行抽取，关系抽取的分类算法也可以结合实际数据进行优化和调整，缺点在于可能产生错误传播，实体错误后面的关系肯定是错误的，以及忽略了实体属性抽取和关系抽取内部的可能联系。&lt;/p&gt;



 &lt;p&gt;基于联合抽取的方法是同时抽取实体、属性、关系。针对实体抽取出的实体对，在当前句子对应的依存句法树中找到能够覆盖该实体对的最小依存句法树， 并基于 TreeLSTM 生成该子树对应的向量表示，最后，根据子树根节点对应的 TreeLSTM 向量进行关系分类。&lt;/p&gt;



 &lt;p&gt;一些知识可以通过抽取已有的文档，但有些文档缺失或者抽取难度很高的，则由专家来进行人工录入，从而构造了一个针对失效的知识图谱。有了这个图谱，就形成了计算机的知识。&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221025/0f01399c-2f0f-4daf-a870-5d7351fab0de.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;失效图谱例&lt;/p&gt;



 &lt;p&gt;基于图谱赋予的知识，企业可以使用基于知识图谱的问答（KBQA）来解决生产中实际碰到的问题，我们叫“归因分析”。基于图谱的问答需要能理解各种query的真实意图，尤其是query可能输错，可能表述不规范，需要还能对应到图谱得到正确的答案。这里面也需要对问题进行拆解，分解成一个个可以解决的模型。&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221025/b9c55a01-5124-4364-b1c7-c56ec8808440.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;KBQA处理流程&lt;/p&gt;



 &lt;p&gt;一般来说，KBQA分为数据预处理，问句分析，知识检索，答案生成4个阶段。&lt;/p&gt;



 &lt;ol&gt;  &lt;li&gt;数据预处理，指的是query进行基础的NLP处理，包括了分词，格式转换，归一化，纠错等过程。这里面和传统搜索中的数据预处理比较不同的是，纠错往往可以结合图谱里面的各种名称进行纠错，并且可以保留多个纠错结果，在后面的过程中再结合其他信息判断是否需要纠错，或纠错成哪个结果。&lt;/li&gt;  &lt;li&gt;问句分析，核心是要对query进行意图识别，并且进行实体链接。意图识别指用户的query是关于什么的，比如是问解决办法还是问原因。实体链接就是将问句文本的某些字符串映射到知识库中对应的实体上。实体链接是问答系统的核心问题之一，因为实体链接如果出错，后面的结果会非常不相关。这里面的难点在于用户query的名称和图谱中实体的名称并不是完全一致。所以我们也会加上模糊搜索以及同义词等方式来解决这个问题。&lt;/li&gt;  &lt;li&gt;知识检索，需要从图谱中选出符合该query相关的子图，并且对其排序。由于问题可能需要图谱通过多跳获得答案，所以这个步骤里面返回的实体节点可能有多个。&lt;/li&gt;  &lt;li&gt;答案生成，一方面是根据返回的结果找出最符合的一个，并且根据问句以及图谱的信息，通过NLG的技术生成可能的文字答案。&lt;/li&gt;&lt;/ol&gt;



 &lt;h1&gt;  &lt;strong&gt;”人工”进行场景选择和产品形态设计&lt;/strong&gt;&lt;/h1&gt;



 &lt;p&gt;针对人工智能产品或解决方案，一般大家都在讨论技术如何提升，效果如何优化。以达观在过去几年落地的很多AI项目来说，  &lt;strong&gt;场景选择和产品形态的设计其实是落地非常关键的环节&lt;/strong&gt;。从落地的角度，本质需求是希望可以更快地高质量完成预计的工作，并不是需要一个多少准确率的模型。而且这里的高质量，在办公文档处理上的落地需求往往是100%准确。而目前的算法基本都不能达到100%准确，而且算法本身并不知道错在哪里，这也是AI落地碰到的最大挑战。因为当需要所有数据进行复核，“快速”这个需求就会大打折扣。如何“快速”审核就是需要在场景选择以及产品形态上做很多工作。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;01&lt;/strong&gt;  &lt;strong&gt;比对数据&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;用第三方数据或者有规则进行校验，就能快速发现AI错误之处。比如电子合同和图片合同进行文档比对的场景，ocr的错误通过比对，可以快速的找到出现ocr错误的地方，人工可以快速进行查看。&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221025/7afe0cec-68e2-4b0d-a64c-351655af39e9.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;文档比对产品kh&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;02&lt;/strong&gt;  &lt;strong&gt;业务关系&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;文档中识别的元素有些是有业务关系的，可以通过字段的关系来验证识别是否正确。比如下图总和的值应该是上面列表中数值计算后的结果。如果识别出来的结果总和公式不正确，那很可能是中间哪个元素识别出现了问题，如果识别出来的结果总和公式正确，那基本识别本身也是正确的。&lt;/p&gt;



 &lt;img alt="" height="699" src="https://www.52nlp.cn/wp-content/uploads/2022/10/640-1024x699.jpg" width="1024"&gt;&lt;/img&gt;



 &lt;p&gt;财务文档中的勾稽关系&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;03&lt;/strong&gt;  &lt;strong&gt;高效审核&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;人工审核过程的产品交互是非常重要的，需要对比较耗费时间的环节结合具体业务场景的审核过程进行合理交互设计。审核过程主要是“找到”和“修订”两个动作，达观通过对抽取结果进行高亮，点击字段跳转等功能帮助审核人员快速“找到”抽取结果以及上下文，通过划选和快捷键等功能加速人工“修订”的时间。&lt;/p&gt;



 &lt;img alt="" src="https://yixiaoer-img.oss-cn-shanghai.aliyuncs.com/20221025/8cbcff10-5342-4f71-9710-21996999eac0.jpg"&gt;&lt;/img&gt;



 &lt;p&gt;人工审核产品交互&lt;/p&gt;



 &lt;p&gt;人工智能落地是一个挺有挑战性的工作，既需要攻克技术难度，不断提升算法精度，也需要了解业务，了解场景，才能选择合适的场景，构建合理的算法流程，设计方便的产品交互，把这些“人工”的价值发挥出来，  &lt;strong&gt;以人机协同的方式促进算力和模型的提高，才能真正实现“人工智能”。&lt;/strong&gt;&lt;/p&gt;



 &lt;p&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>自然语言处理 深度学习</category>
      <guid isPermaLink="true">https://itindex.net/detail/62471-%E6%B7%B1%E5%BA%A6-%E9%AB%98%E5%8F%AF%E7%94%A8%E6%80%A7-%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD</guid>
      <pubDate>Wed, 26 Oct 2022 18:49:35 CST</pubDate>
    </item>
    <item>
      <title>应用不停机发布的思考与初识 - 运维</title>
      <link>https://itindex.net/detail/62395-%E5%BA%94%E7%94%A8-%E6%80%9D%E8%80%83-%E8%BF%90%E7%BB%B4</link>
      <description>&lt;p&gt;    &lt;strong&gt;应用不停机发布是一项综合性能力，&lt;/strong&gt;当明确好一种发布模式后，就需要逐步识别会涉及到哪些技术组件，以及明确技术组件在整个解决方案中所担任的职责边界，从而使它们能够相互协同工作。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;如下列举了一些主要的技术组件：&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;应用管理平台&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;主要负责控制整个应用发布流程，以及集成并调度不同的技术组件，协同完成应用不停机发布。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;负载均衡（4层）&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;主要负责服务请求的流量接入，可根据所识别的流量特征，负载分发到不同的负载均衡（7层）。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;负载均衡（7层）&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;主要负责服务请求的流量路由，可根据所识别的流量特征，路由分发到同一应用的不同实例。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;容器/虚拟机平台&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;主要负责容器/虚拟机资源管理，包括容器/虚拟机的创建、更新、销毁等。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;域名解析系统&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;主要负责域名地址管理，包括域名的注册、更新、解析等，以及提供用户访问应用或应用间访问等寻址能力。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;注册中心&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;主要负责服务资源管理，包括服务的注册、更新、注销等，以及提供服务请求方发现服务提供方的能力。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;在明确技术组件后，还需要对技术组件进行合理的架构规划，以适配不同的网络架构要求。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;本次主要将对负载均衡进行特别说明，一方面它是负责处理流量的核心技术组件，另一方面网络架构的不同，对它的部署架构影响可能也是最大的。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;在传统的网络架构环境中，出于对网络安全或其他考虑因素，通常会划分出多个不同的网络区域，网络区域间的访问需要通过开设防火墙访问策略才可以进行互通。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;但这种方式必然会对应用服务间直接进行点对点访问的方式造成影响，主要原因是虚拟机或容器环境中，应用的IP地址可能会发生变化，导致无法提前明确防火墙访问策略。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;所以，一般都会考虑使用负载均衡（代理模式）来解决这个问题。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;img alt="" src="https://dbaplus.cn/uploadfile/2022/0901/20220901102215931.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;strong&gt;建议前期优先选择方案三，&lt;/strong&gt;虽然链路较长会小幅影响性能，但此部署方案相对较为成熟，一方面可以避免流量负载不均的问题，另一方面对于应用的改造成本也会相对较低。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;注：除网络区域隔离会遇到这种情况外，在某些网络架构中，不同的容器集群间也同样无法访问，所以也同样适用。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;另外，除必要情况下，也应尽量减少跨网络区域或跨容器集群间的访问，例如：优先容器集群内路由访问，跨网络区域频繁交互的应用服务建议迁移至同一网络区域等。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;负载均衡通常可以分为4层模式和7层模式，其中4层更关注流量负载，而7层更关注流量路由。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;一般建议将负载均衡（4层）和负载均衡（7层）进行分层部署，以充分发挥它们的强项。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;img alt="" src="https://dbaplus.cn/uploadfile/2022/0901/20220901102225885.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;strong&gt;建议前期优先选择方案二，&lt;/strong&gt;虽然无法实现多容器集群间的全局流量调度，但对于当前可观测性和排障能力还不够健全的组织，通过物理隔离降低运维难度也是一种不错的选择。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;综上架构决策，最终的全局流量链路大致如下，默认情况下，数据中心间流量隔离，即：单数据中心流量收敛，但可根据实际情况进行选择性放行。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;img alt="" src="https://dbaplus.cn/uploadfile/2022/0901/20220901102236214.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62395-%E5%BA%94%E7%94%A8-%E6%80%9D%E8%80%83-%E8%BF%90%E7%BB%B4</guid>
      <pubDate>Thu, 01 Sep 2022 16:37:43 CST</pubDate>
    </item>
    <item>
      <title>Binlog 的三个业务应用场景</title>
      <link>https://itindex.net/detail/62378-binlog-%E4%B8%9A%E5%8A%A1-%E5%BA%94%E7%94%A8</link>
      <description>&lt;ul&gt;  &lt;li&gt;   &lt;a href="https://mp.weixin.qq.com/s?__biz=MzU2NjIzNDk5NQ==&amp;mid=2247509768&amp;idx=1&amp;sn=588c3685f05135b430484ea26e1d3e36&amp;scene=21#wechat_redirect"&gt;    &lt;strong&gt;01、什么是binlog&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://mp.weixin.qq.com/s?__biz=MzU2NjIzNDk5NQ==&amp;mid=2247509768&amp;idx=1&amp;sn=588c3685f05135b430484ea26e1d3e36&amp;scene=21#wechat_redirect"&gt;    &lt;strong&gt;02、binlog的业务应用&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://mp.weixin.qq.com/s?__biz=MzU2NjIzNDk5NQ==&amp;mid=2247509768&amp;idx=1&amp;sn=588c3685f05135b430484ea26e1d3e36&amp;scene=21#wechat_redirect"&gt;    &lt;strong&gt;03、数据异构&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://mp.weixin.qq.com/s?__biz=MzU2NjIzNDk5NQ==&amp;mid=2247509768&amp;idx=1&amp;sn=588c3685f05135b430484ea26e1d3e36&amp;scene=21#wechat_redirect"&gt;    &lt;strong&gt;04、缓存数据的补充&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://mp.weixin.qq.com/s?__biz=MzU2NjIzNDk5NQ==&amp;mid=2247509768&amp;idx=1&amp;sn=588c3685f05135b430484ea26e1d3e36&amp;scene=21#wechat_redirect"&gt;    &lt;strong&gt;05、基于数据的任务分发&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://mp.weixin.qq.com/s?__biz=MzU2NjIzNDk5NQ==&amp;mid=2247509768&amp;idx=1&amp;sn=588c3685f05135b430484ea26e1d3e36&amp;scene=21#wechat_redirect"&gt;    &lt;strong&gt;06、总结&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt; &lt;hr&gt;&lt;/hr&gt; &lt;h2&gt;  &lt;a href="https://mp.weixin.qq.com/s?__biz=MzU2NjIzNDk5NQ==&amp;mid=2247509768&amp;idx=1&amp;sn=588c3685f05135b430484ea26e1d3e36&amp;scene=21#wechat_redirect"&gt;01、什么是binlog&lt;/a&gt;&lt;/h2&gt; &lt;p&gt;binlog是mysql的一种二进制日志文件，用来记录数据的变化。mysql使用binlog进行主从复制，如图：&lt;/p&gt; &lt;img&gt;&lt;/img&gt; &lt;ol&gt;  &lt;li&gt;客户端向master的mysql sever写入数据&lt;/li&gt;  &lt;li&gt;当数据发生变化时，master将变更的数据记录写入到二进制文件中，即binlog。&lt;/li&gt;  &lt;li&gt;slave订阅了master的binlog，所以会通过一个I/O THREAD与master的DUMP THREAD进行通信，同步binlog&lt;/li&gt;  &lt;li&gt;I/O THREAD读取到binlog后会吸入到relay log中，准备重放。&lt;/li&gt;  &lt;li&gt;slave会通过SQL THREAD读取relay log，重放数据的改动并执行相应的改动。&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;这里有几点需要注意：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;主从复制不是强一致性，只能保证最终一致&lt;/li&gt;  &lt;li&gt;master配合binlog复制会影响性能，所以尽量不要在master上挂太多的slave，如果对时间要求不高，可以在slave上挂slave&lt;/li&gt;&lt;/ol&gt; &lt;h2&gt;  &lt;a href="https://mp.weixin.qq.com/s?__biz=MzU2NjIzNDk5NQ==&amp;mid=2247509768&amp;idx=1&amp;sn=588c3685f05135b430484ea26e1d3e36&amp;scene=21#wechat_redirect"&gt;02、binlog的业务应用&lt;/a&gt;&lt;/h2&gt; &lt;p&gt;上面介绍了mysql中应用binlog的场景，而我们的业务可以伪装成master的slave节点，感知数据的变化，这就给了我们很多的业务运用空间。&lt;/p&gt; &lt;h2&gt;  &lt;a href="https://mp.weixin.qq.com/s?__biz=MzU2NjIzNDk5NQ==&amp;mid=2247509768&amp;idx=1&amp;sn=588c3685f05135b430484ea26e1d3e36&amp;scene=21#wechat_redirect"&gt;03、数据异构&lt;/a&gt;&lt;/h2&gt; &lt;p&gt;经常有这样一个场景：&lt;/p&gt; &lt;p&gt;原来业务是一个很单一的系统，所以表也在一起。随着业务的发展，系统开始拆分，总有一些表是各个业务都关注的表，但是对相关的字段的运用场景不同，所以这样一份元数据怎样更好的为各个系统服务就成了问题。当然，多写或者读写分离可以从物理节点上减少对数据服务器的压力，但是对业务并没有做到足够的支持，因为这些表都是一样的。因此我们可以通过binlog进行数据异构。&lt;/p&gt; &lt;img&gt;&lt;/img&gt; &lt;p&gt;如图所示，订单系统生成订单后，通过binlog可以解析生成用户维度的订单信息供用户中心查询、商户维度订单表供运营管理，以及搜索系统的搜索数据，提供全文搜索功能。&lt;/p&gt; &lt;p&gt;这样，我们就通过原始的订单数据异构到三个系统中，提供了丰富的数据访问功能。不仅从节点上降低了数据服务器的压力，数据表现形式也更贴近自己的服务，减少不必要的字段冗余。&lt;/p&gt; &lt;h2&gt;  &lt;a href="https://mp.weixin.qq.com/s?__biz=MzU2NjIzNDk5NQ==&amp;mid=2247509768&amp;idx=1&amp;sn=588c3685f05135b430484ea26e1d3e36&amp;scene=21#wechat_redirect"&gt;04、缓存数据的补充&lt;/a&gt;&lt;/h2&gt; &lt;p&gt;对于高并发的系统，数据库往往是系统性能的瓶颈，毕竟IO响应速度是远远小于电子的运算速度的。因此，很多查询类服务都会在CPU与数据库之间加上一层缓存。即现从缓存获取，命中后直接返回，否则从DB中获取并存入缓存后返回。而如果原始数据变化了但缓存尚未超时，则缓存中的数据就是过时的数据了。当数据有变更的时候主动修改缓存数据。&lt;/p&gt; &lt;img&gt;&lt;/img&gt; &lt;p&gt;当客户端更改了数据之后，中间件系统通过binlog获得数据变更，并同步到缓存中。这样就保证了缓存中数据有效性，减少了对数据库的调用，从而提高整体性能。&lt;/p&gt; &lt;h2&gt;  &lt;a href="https://mp.weixin.qq.com/s?__biz=MzU2NjIzNDk5NQ==&amp;mid=2247509768&amp;idx=1&amp;sn=588c3685f05135b430484ea26e1d3e36&amp;scene=21#wechat_redirect"&gt;05、基于数据的任务分发&lt;/a&gt;&lt;/h2&gt; &lt;p&gt;有这样一个场景：&lt;/p&gt; &lt;p&gt;很多系统依赖同一块重要数据，当这些数据发生变化的时候，需要调用其他相关系统的通知接口同步数据变化，或者mq消息告知变化并等待其主动同步。这两种情况都对原始系统造成了侵入，原始系统改一块数据，并不想做这么多其他的事情。所以这时候可以通过binlog进行任务分发。&lt;/p&gt; &lt;img&gt;&lt;/img&gt; &lt;p&gt;当原始业务系统修改数据后，不需要进行其他的业务关联。由调度系统读取binlog进行相应的任务分发、消息发送以及同步其他业务状态。这样可以将其他业务与原始业务系统解耦，并从数据的角度将所有管理功能放在了同一个调度系统中，责任清晰。&lt;/p&gt; &lt;h2&gt;  &lt;a href="https://mp.weixin.qq.com/s?__biz=MzU2NjIzNDk5NQ==&amp;mid=2247509768&amp;idx=1&amp;sn=588c3685f05135b430484ea26e1d3e36&amp;scene=21#wechat_redirect"&gt;06、总结&lt;/a&gt;&lt;/h2&gt; &lt;p&gt;binlog是mysql提供的数据同步机制，很好的解决了主从分离、读写库分离等业务。而我们可以构建一个中间件系统，“伪造”成master的一个slave。当读取了binlog中的数据变化后，根据相应的业务场景做各种业务处理。而目前我接触到的最常见的就是第一个场景——数据异构，可以异构到其他表中，也可以异构到其他数据引擎中，比如Elastic Search。&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>dev</category>
      <guid isPermaLink="true">https://itindex.net/detail/62378-binlog-%E4%B8%9A%E5%8A%A1-%E5%BA%94%E7%94%A8</guid>
      <pubDate>Wed, 24 Aug 2022 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>Elasticsearch深度应用（上） - 女友在高考 - 博客园</title>
      <link>https://itindex.net/detail/62323-elasticsearch-%E6%B7%B1%E5%BA%A6-%E5%BA%94%E7%94%A8</link>
      <description>&lt;div&gt;    &lt;h3&gt;索引文档写入和近实时搜索原理&lt;/h3&gt;    &lt;p&gt;      &lt;strong&gt;基本概念&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Segments in Lucene&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;众所周知，Elasticsearch存储的基本单元是shard，ES中一个index可能分为多个shard，事实上每个shard都是一个Lucece的Index，并且每个Lucece Index由多个Segment组成，每个Segment事实上是一些倒排索引的集合，每次创建一个新的Document，都会归属一个新的Segment，而不会去修改原来的Segment。且每次的文档删除操作，仅仅会标记Segment的一个删除状态，而不会真正立马物理删除。所以说ES的Index可以理解为一个抽象的概念。如下图所示：&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://img2022.cnblogs.com/blog/1178991/202207/1178991-20220705221417374-846917018.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Commits in Lucene&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;Commit操作意味着将Segment合并，并写入磁盘。保证内存数据不丢失。但刷盘是很重的IO操作，所以为了性能不会刷盘那么及时。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Translog&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;新文档被索引意味着文档首先写入内存buffer和translog文件。每个shard都对应一个translog文件。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://img2022.cnblogs.com/blog/1178991/202207/1178991-20220705222444832-524022514.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Refresh in Elasticsearch&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;在Elasticsearch种，_refresh操作默认每秒执行一次，意味着将内存buffer的数据写入到一个新的Segment中，这个时候索引变成了可被检索的。写入新Segment后会清空内存。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://img2022.cnblogs.com/blog/1178991/202207/1178991-20220705222504624-2028885157.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Flush in Elasticsearch&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;Flush操作意味着内存buffer的数据全都写入新的Segment中，并将内存中所有的Segments全部刷盘，并且清空translog日志的过程。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;近实时搜索&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;提交一个新的段到磁盘需要一个fsync来确保段被物理性的写入磁盘，这样在断电的时候就不会丢数据。但是fsync操作代价很大，如果每次索引一个文档都去执行一次的话就会造成很大的性能问题。&lt;/p&gt;    &lt;p&gt;像之前描述的一样，在内存索引缓冲区中的文档会被写入到一个新的段中。但是这里新段会被先写入到文件系统缓存--这一步代价会比较低，稍后再被刷新到磁盘（这一步代价比较高）。不过只要文件已经在系统缓存中，就可以像其它文件一样被打开和读取了。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;原理：&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;当一个写请求发送到es后，es将数据写入memory buffer中，并添加事务日志（translog）。如果每次一条数据写入内存后立即写到硬盘文件上，由于写入的数据肯定是离散的，因此写入硬盘的操作也就是随机写入了。硬盘随机写入的效率相当低，会严重降低es的性能。&lt;/p&gt;    &lt;p&gt;因此es在设计时在memory buffer和硬盘间加入了Linux的高速缓存（Filesy stemcache）来提高es的写效率。当写请求发送到es后，es将数据暂时写入memory buffer中，此时写入的数据还不能被查询到。默认设置下，es每1秒钟将memory buffer中的数据refresh到Linux的Filesy stemcache，并清空memory buffer，此时写入的数据就可以被查询到了。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Refresh API&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;在Elasticsearch中，写入和打开一个新段的轻量的过程叫做refresh。默认情况下每个分片会每秒自动刷新一次。这就是为什么我们说Elasticsearch是近实时搜索：文档的变化并不是立即对搜索可见，但会在一秒之内变为可见。&lt;/p&gt;    &lt;p&gt;这些行为可能会对新用户造成困惑：他们索引了一个文档然后尝试搜索它，但却没有搜到。这个问题的解决办法是用refresh API执行一次手动刷新:&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;刷新所有索引&lt;/li&gt;&lt;/ol&gt;    &lt;pre&gt;      &lt;code&gt;POST /_refresh&lt;/code&gt;&lt;/pre&gt;    &lt;ol start="2"&gt;      &lt;li&gt;只刷新某一个索引&lt;/li&gt;&lt;/ol&gt;    &lt;pre&gt;      &lt;code&gt;POST /索引名/_refresh&lt;/code&gt;&lt;/pre&gt;    &lt;ol start="3"&gt;      &lt;li&gt;只刷新某一个文档&lt;/li&gt;&lt;/ol&gt;    &lt;pre&gt;      &lt;code&gt;PUT /索引名/_doc/{id}?refresh
{&amp;quot;test&amp;quot;:&amp;quot;test&amp;quot;}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;并不是所有的情况都需要每秒刷新。可能你正在使用Elasticsearch索引大量的日志文件，你可能想优化索引速度而不是近实时搜索，可以通过设置refresh_interval，降低每个索引的刷新频率。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;PUT /my_logs
{ 
&amp;quot;settings&amp;quot;: { &amp;quot;refresh_interval&amp;quot;: &amp;quot;30s&amp;quot; }
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;refresh_interval可以在既存索引上进行动态更新。在生产环境中，当你正在建立一个大的新索引时，可以先关闭自动刷新，待开始使用该索引时，再把它们调回来。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;PUT /my_logs/_settings
{ &amp;quot;refresh_interval&amp;quot;: -1 }&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;strong&gt;持久化变更&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;如果没有用fsync把数据从文件系统缓存刷（flush）到硬盘，我们不能保证数据在断电甚至是程序正常退出之后依然存在。为了保证Elasticsearch的可靠性，需要确保数据变化被持久化到磁盘。&lt;/p&gt;    &lt;p&gt;在动态更新索引时，我们说一次完整的提交会将段刷到磁盘，并写入一个包含所有段列表的提交点。Elasticsearch在启动或重新打开一个索引的过程中使用这个提交点来判断哪些段隶属于当前分片。&lt;/p&gt;    &lt;p&gt;即使通过每秒刷新（refresh）实现了近实时搜索，我们仍然需要经常进行完整提交来确保能从失败中恢复。但在两次提交之间发生变化的文档怎么办？我们也不希望丢失掉这些数据。Elasticsearch增加了一个translog，或者叫事务日志，在每一次对Elasticsearch进行操作时均进行了日志记录。&lt;/p&gt;    &lt;p&gt;整个流程如下：&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;一个文档被索引之后，就会被添加到内存缓冲区，并且追加到了translog。如下图：&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;      &lt;img alt="" src="https://img2022.cnblogs.com/blog/1178991/202207/1178991-20220705231224141-1280888697.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;ol start="2"&gt;      &lt;li&gt;        &lt;p&gt;分片每秒refres一次，refresh完成后，缓存被清空          &lt;br /&gt;          &lt;img alt="" src="https://img2022.cnblogs.com/blog/1178991/202207/1178991-20220705231254521-1711236768.png"&gt;&lt;/img&gt;&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;这个进程继续工作，更多的文档被添加到内存缓冲区和追加到事务日志&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;每隔一段时间--例如translog变得越来越大--索引被刷新（flush）；一个新的translog被创建，并且一个全量提交被执行。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;    &lt;ul&gt;      &lt;li&gt;所有在内存缓冲区的文档被写入一个新的段&lt;/li&gt;      &lt;li&gt;缓冲区被清空&lt;/li&gt;      &lt;li&gt;一个提交点被写入磁盘&lt;/li&gt;      &lt;li&gt;文件系统缓存通过fsync被刷新（flush）&lt;/li&gt;      &lt;li&gt;老的translog被删除&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;      &lt;img alt="" src="https://img2022.cnblogs.com/blog/1178991/202207/1178991-20220705231626341-967178992.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;translog提供所有还没有被刷到磁盘的操作的一个持久化纪录。当Elasticsearch启动的时候，它会从磁盘中使用最后一个提交点去恢复已知的段，并且会重放translog中所有在最后一次提交后发生的变更操作。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Flush API&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;这个执行一个提交并且截断translog的行为在Es中被称为一次flush。分片每30分钟被自动刷新（flush），或者在translog太大的时候也会刷新。&lt;/p&gt;    &lt;p&gt;flush API 可以被用来执行手工的刷新&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;POST /索引名称/_flush

#刷新（flush）所有的索引并且等待所有刷新在返回前完成
POST /_flush?wait_for_ongoin&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;我们知道用fsync把数据从文件系统缓存flush到硬盘是安全的，那么如果我们觉得偶尔丢失几秒数据也没关系，可以启用async。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;PUT /索引名/_settings {
&amp;quot;index.translog.durability&amp;quot;: &amp;quot;async&amp;quot;,
&amp;quot;index.translog.sync_interval&amp;quot;: &amp;quot;5s&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;    &lt;h3&gt;索引文档存储段合并机制&lt;/h3&gt;    &lt;p&gt;由于自动刷新流程每秒会创建一个新的段，这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。每一个段都会消耗文件句柄、内存和CPU运行周期。更重要的是，每个搜索请求都必须轮流检查每个段；所以段越多，搜索也就越慢。&lt;/p&gt;    &lt;p&gt;Elasticsearch通过在后台进行段合并来解决这个问题。小的段被合并到大的段，然后这些大的段再被合并到更大的段。段合并的时候会将那些旧的已删除文档从文件系统中清除。被删除的文档（或被更新文档的旧版本）不会被拷贝到新的大段中。&lt;/p&gt;    &lt;p&gt;合并大的段需要消耗大量的I/O和CPU资源，如果任其发展会影响搜索性能。Elasticsearch在默认情况下会对合并流程进行资源限制，所以搜索仍然有足够的资源很好地执行。默认情况下，归并线程的限速配置indices.store.throttle.max_bytes_per_sec是20MB。对于写入量较大，磁盘转速较高，甚至使用SSD盘的服务器来说，这个限速是明显过低的。对于ELKStack应用，建议可以适当调大到100MB或者更高。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;PUT /_cluster/settings
{
  &amp;quot;persistent&amp;quot; : {
  &amp;quot;indices.store.throttle.max_bytes_per_sec&amp;quot; : &amp;quot;100mb&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;strong&gt;归并策略&lt;/strong&gt;&lt;/p&gt;    &lt;blockquote&gt;      &lt;p&gt;归并线程是按照一定的运行策略来挑选 segment 进行归并的。主要有以下几条：&lt;/p&gt;&lt;/blockquote&gt;    &lt;blockquote&gt;      &lt;p&gt;index.merge.policy.floor_segment默认2MB，小于这个大小的segment，优先被归并。&lt;/p&gt;&lt;/blockquote&gt;    &lt;blockquote&gt;      &lt;p&gt;index.merge.policy.max_merge_at_once默认一次最多归并10个segment&lt;/p&gt;&lt;/blockquote&gt;    &lt;blockquote&gt;      &lt;p&gt;index.merge.policy.max_merge_at_once_explicit默认optimize时一次最多归并30个segment。&lt;/p&gt;&lt;/blockquote&gt;    &lt;blockquote&gt;      &lt;p&gt;index.merge.policy.max_merged_segment默认5GB，大于这个大小的segment，不用参与归并。optimize除外&lt;/p&gt;&lt;/blockquote&gt;    &lt;p&gt;      &lt;strong&gt;optimize API&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;optimizeAPI大可看做是强制合并API。它会将一个分片强制合并到max_num_segments参数指定大小的段数目。这样做的意图是减少段的数量（通常减少到一个），来提升搜索性能。&lt;/p&gt;    &lt;p&gt;在特定情况下，使用optimizeAPI颇有益处。例如在日志这种用例下，每天、每周、每月的日志被存储在一个索引中。老的索引实质上是只读的；它们也并不太可能会发生变化。在这种情况下，使用optimize优化老的索引，将每一个分片合并为一个单独的段就很有用了；这样既可以节省资源，也可以使搜索更加快速。&lt;/p&gt;    &lt;p&gt;api:&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;POST /logstash-2014-10/_optimize?max_num_segments=1&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;java api:&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;forceMergeRequest.maxNumSegments(1)&lt;/code&gt;&lt;/pre&gt;    &lt;h3&gt;Es乐观锁&lt;/h3&gt;    &lt;p&gt;Es的后台是多线程异步的，多个请求之间没有顺序，可能后发起修改请求的先被执行。Es的并发是基于自己的_version版本号进行并发控制的。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;1. 基于seq_no&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;乐观锁示例：&lt;/p&gt;    &lt;p&gt;先新增一条数据&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;PUT /item/_doc/4
{
  &amp;quot;date&amp;quot;:&amp;quot;2022-07-01 01:00:00&amp;quot;,
  &amp;quot;images&amp;quot;:&amp;quot;aaa&amp;quot;,
  &amp;quot;price&amp;quot;:22,
  &amp;quot;title&amp;quot;:&amp;quot;先&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;查询：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;GET /item/_doc/4&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;可以查出我们的seq_no和primary_term&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;{
  &amp;quot;_index&amp;quot; : &amp;quot;item&amp;quot;,
  &amp;quot;_type&amp;quot; : &amp;quot;_doc&amp;quot;,
  &amp;quot;_id&amp;quot; : &amp;quot;4&amp;quot;,
  &amp;quot;_version&amp;quot; : 5,
  &amp;quot;_seq_no&amp;quot; : 12,
  &amp;quot;_primary_term&amp;quot; : 5,
  &amp;quot;found&amp;quot; : true,
  &amp;quot;_source&amp;quot; : {
    &amp;quot;date&amp;quot; : &amp;quot;2022-07-01 01:00:00&amp;quot;,
    &amp;quot;images&amp;quot; : &amp;quot;aaa&amp;quot;,
    &amp;quot;price&amp;quot; : 33,
    &amp;quot;title&amp;quot; : &amp;quot;先&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;然后两个客户端都根据这个seq_no和primary_term去修改数据，会有一个提示异常的。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;PUT /item/_doc/4?if_seq_no=12&amp;amp;if_primary_term=5
{
  &amp;quot;date&amp;quot;:&amp;quot;2022-07-01 01:00:00&amp;quot;,
  &amp;quot;images&amp;quot;:&amp;quot;aaa&amp;quot;,
  &amp;quot;price&amp;quot;:33,
  &amp;quot;title&amp;quot;:&amp;quot;先&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;strong&gt;2. 基于external version&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;es提供了一个功能，不用它内部的_version来进行并发控制，你可以根据你自己维护的版本号进行并发控制。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;?version=1&amp;amp;version_type=external&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;区别在于，version方式，只有当你提供的version与es中的version一模一样的时候，才可以进行修改，只要不一样，就报错。当version_type=external的时候，只有当你提供的version比es中的_version大的时候，才能完成修改&lt;/p&gt;    &lt;p&gt;示例：&lt;/p&gt;    &lt;p&gt;我先查出目前的version为7&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;{
  &amp;quot;_index&amp;quot; : &amp;quot;item&amp;quot;,
  &amp;quot;_type&amp;quot; : &amp;quot;_doc&amp;quot;,
  &amp;quot;_id&amp;quot; : &amp;quot;4&amp;quot;,
  &amp;quot;_version&amp;quot; : 7,
  &amp;quot;_seq_no&amp;quot; : 14,
  &amp;quot;_primary_term&amp;quot; : 5,
  &amp;quot;found&amp;quot; : true,
  &amp;quot;_source&amp;quot; : {
    &amp;quot;date&amp;quot; : &amp;quot;2022-07-01 01:00:00&amp;quot;,
    &amp;quot;images&amp;quot; : &amp;quot;aaa&amp;quot;,
    &amp;quot;price&amp;quot; : 33,
    &amp;quot;title&amp;quot; : &amp;quot;先&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;只有设置为8才能成功修改了&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;PUT /item/_doc/4?version=8&amp;amp;version_type=external
{
  &amp;quot;title&amp;quot;:&amp;quot;先&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;    &lt;h3&gt;分布式数据一致性如何保证&lt;/h3&gt;    &lt;p&gt;es5.0版本后&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;PUT /test_index/_doc/1?wait_for_active_shards=2&amp;amp;timeout=10s
{
  &amp;quot;name&amp;quot;:&amp;quot;xiao mi&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;这代表着所有的shard中必须要有2个处于active状态才能执行成功，否则10s后超时报错。&lt;/p&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62323-elasticsearch-%E6%B7%B1%E5%BA%A6-%E5%BA%94%E7%94%A8</guid>
      <pubDate>Sat, 09 Jul 2022 14:17:14 CST</pubDate>
    </item>
    <item>
      <title>Elasticsearch深度应用（下） - 女友在高考 - 博客园</title>
      <link>https://itindex.net/detail/62322-elasticsearch-%E6%B7%B1%E5%BA%A6-%E5%BA%94%E7%94%A8</link>
      <description>&lt;div&gt;    &lt;h3&gt;Query文档搜索机制剖析&lt;/h3&gt;    &lt;p&gt;      &lt;strong&gt;1. query then fetch(默认搜索方式)&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;搜索步骤如下：&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;发送查询到每个shard&lt;/li&gt;      &lt;li&gt;找到所有匹配的文档，并使用本地的Term/Document Frequery信息进行打分&lt;/li&gt;      &lt;li&gt;对结果构建一个优先队列&lt;/li&gt;      &lt;li&gt;返回关于结果的元数据到请求节点。注意，实际文档还没有发送，只是分数&lt;/li&gt;      &lt;li&gt;来自所有shard的分数合并起来，并在请求节点上进行排序，文档被按照查询要去进行选择&lt;/li&gt;      &lt;li&gt;最终，实际文档从它们各自所在的独立的shard上检索出来&lt;/li&gt;      &lt;li&gt;结果被返回给用户&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;优点：返回的数据量是准确的&lt;/p&gt;    &lt;p&gt;缺点：性能一般，并且数据排名不准确&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;2. dfs query then fetch&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;比前面的方式多了一个DFS步骤。也就是查询之前，先对所有分片发送请求，把所有分片中的词频和文档频率等打分依据全部汇总到一块，再执行后面的操作。&lt;/p&gt;    &lt;p&gt;详细步骤如下：&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;预查询每个shard，询问Term和Document frequency&lt;/li&gt;      &lt;li&gt;发送查询到每个shard&lt;/li&gt;      &lt;li&gt;找到所有匹配的文档，并使用全局的Term/Document Frequency信息进行打分&lt;/li&gt;      &lt;li&gt;对结果构建一个优先队列&lt;/li&gt;      &lt;li&gt;返回关于结果的元数据到请求节点。注意，实际文档还没有发送，只是分数。&lt;/li&gt;      &lt;li&gt;来自所有shard的分数合并起来，并在请求节点进行排序，文档被按照查询要求进行选择&lt;/li&gt;      &lt;li&gt;最终，实际文档从它们各自所在的独立的shard上检索出来&lt;/li&gt;      &lt;li&gt;结果被返回给用户&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;优点：返回的数据和数据排名都是准确的&lt;/p&gt;    &lt;p&gt;缺点：性能较差&lt;/p&gt;    &lt;h3&gt;文档增删改和搜索的请求过程&lt;/h3&gt;    &lt;p&gt;      &lt;strong&gt;增删改流程&lt;/strong&gt;&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;客户端首先会选择一个节点发送请求过去，这个节点可能是协调节点&lt;/li&gt;      &lt;li&gt;协调节点会对document数据进行路由，将请求转发给对应的node&lt;/li&gt;      &lt;li&gt;实际上node的primary shard会处理请求，然后将数据同步到对应的含有replica shard的node上&lt;/li&gt;      &lt;li&gt;协调节点如果发现含有primary shard的节点和含有replica shard的节点的符合要求的数量后，就会将响应结果返回给客户端&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;      &lt;strong&gt;搜索流程&lt;/strong&gt;&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;客户端首先会选择一个节点发送请求获取，这个节点可能是协调节点&lt;/li&gt;      &lt;li&gt;协调节点将搜索请求转发到所有shard对应的primary shard或replica shard都可以&lt;/li&gt;      &lt;li&gt;query phase：每个shard将自己搜索结果的元数据发到请求节点（doc id和打分信息），由请求节点进行数据的合并、排序、分页等操作，产出最后结果&lt;/li&gt;      &lt;li&gt;fetch phase：请求节点根据doc id去各个节点上拉取实际的document数据，最终返回给客户端。&lt;/li&gt;&lt;/ol&gt;    &lt;h3&gt;排序详解&lt;/h3&gt;    &lt;p&gt;说到排序，我们必须要说Doc Values这个东西。那么Doc Values是什么呢？又有什么作用？&lt;/p&gt;    &lt;p&gt;我们都知道ES之所以那么快速，归功于他的倒排索引的设计，然而他也不是万能的，倒排索引的检索性能是非常快的，但是在字段值排序时却不是理想的结构。      &lt;br /&gt;      &lt;img alt="" src="https://img2022.cnblogs.com/blog/1178991/202207/1178991-20220707205501496-271284667.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;如上表可以看出，他只有词对应的doc，但是并不知道每一个doc中的内容，那么如果想要排序的话每一个doc都去获取一次文档内容岂不非常耗时？Doc Values的出现就是解决这个问题。&lt;/p&gt;    &lt;p&gt;Doc Values是可以根据doc_values属性进行配置的，默认为true。当配置为false时，无法基于该字段排序、聚合、在脚本中访问字段值。&lt;/p&gt;    &lt;p&gt;Doc Values是转置倒排索引和正排索引的关系来解决这个问题。倒排索引将词项映射到包含它们的文档，Doc Values将文档映射到它们包含的词项：&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://img2022.cnblogs.com/blog/1178991/202207/1178991-20220707212402766-1800355834.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;当数据被转置后，想要收集到每个文档行，获取所有的词项就比较简单了。所以搜索使用倒排索引查找文档，聚合操作和排序就要使用Doc Values里面的数据。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;深入理解Doc Values&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;Doc Values是在索引时与倒排索引同时生成。也就是说Doc Values和倒排索引一样，基于Segement生成并且是不可变的。同时Doc Values和倒排索引一样序列化到磁盘，这样对性能和扩展性有很大帮助。&lt;/p&gt;    &lt;p&gt;Doc Values通过序列化把数据结构持久化到磁盘，我们可以充分利用操作系统的内存，而不是JVM的Heap。当workingset远小于系统的可用内存，系统会自动将Doc Values保存在内存中，使得其读写十分高速；不过，当其远大于可用内存时，操作系统会自动把Doc Values写入磁盘。很显然，这样性能会比在内存中差很多，但是它的大小就不再局限于服务器的内存了。如果是使用JVM的Heap来实现是因为容易OutOfMemory导致程序崩溃了。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;禁用Doc Values&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;Doc Values默认对所有字段启用，除了analyzed strings。也就是说所有的数字、地理坐标、日志、IP和不分析字符类型都会默认开启。&lt;/p&gt;    &lt;p&gt;analyzed strings暂时不能使用Doc Values，因为分析后会生成大量的Token，这样非常影响性能。虽然Doc Values非常好用，但是如果你存储的数据确实不需要这个特性，就不如禁用他，这样不仅节省磁盘空间，也许会提升索引的速度。&lt;/p&gt;    &lt;p&gt;要禁用Doc Values，在mapping设置即可。示例：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;PUT my_index
{
 &amp;quot;mappings&amp;quot;: {
    &amp;quot;properties&amp;quot;: {
      &amp;quot;session_id&amp;quot;: {
        &amp;quot;type&amp;quot;: &amp;quot;keyword&amp;quot;,
        &amp;quot;doc_values&amp;quot;: false
      }
    }
 }
}&lt;/code&gt;&lt;/pre&gt;    &lt;h3&gt;Filter过滤机制剖析&lt;/h3&gt;    &lt;ol&gt;      &lt;li&gt;在倒排索引中查找搜索串，获取docment list&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;如下面这个例子，需要过滤date为2020-02-02的数据，去倒排索引中查找，发现2020-02-02对应的document list是doc2、doc3.      &lt;br /&gt;      &lt;img alt="" src="https://img2022.cnblogs.com/blog/1178991/202207/1178991-20220707215059705-1281131994.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;ol start="2"&gt;      &lt;li&gt;Filter为每个倒排索引中搜索到的结果，构建一个bitset&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;如上面的例子，根据document list，构建的bitset是[0,1,1]，1代表匹配，0代表不匹配&lt;/p&gt;    &lt;ol start="3"&gt;      &lt;li&gt;多个过滤条件时，遍历每个过滤条件对应的bitset，优先从最稀疏的开始搜索，查找满足所有条件的document。&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;另外多个过滤条件时，先过滤比较稀疏的条件，能先过滤掉尽可能多的数据。&lt;/p&gt;    &lt;ol start="4"&gt;      &lt;li&gt;        &lt;p&gt;caching bitset，跟踪query，在最近256个query中超过一定次数的过滤条件，缓存其bitset。对于小的segment（记录数小于1000或小于总大小3%），不缓存。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;如果document有新增或修改，那么cached bitset会被自动更新&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;filter大部分情况下，在query之前执行，先尽量过滤尽可能多的数据&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;    &lt;h3&gt;控制搜索精准度&lt;/h3&gt;    &lt;p&gt;      &lt;strong&gt;基于boost的权重控制&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;考虑如下场景：      &lt;br /&gt;我们搜索帖子，搜索标题包含java或spark或Hadoop或elasticsearch。但是需要优先输出包含java的，再输出spark的的，再输出Hadoop的，最后输出elasticsearch。&lt;/p&gt;    &lt;p&gt;我们先看如果不考虑优先级时怎么搜索：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;GET /article/_search
{
  &amp;quot;query&amp;quot;: {
    &amp;quot;bool&amp;quot;: {
      &amp;quot;should&amp;quot;: [
        {
          &amp;quot;term&amp;quot;: {
            &amp;quot;title&amp;quot;: {
              &amp;quot;value&amp;quot;: &amp;quot;java&amp;quot;
            }
          }
        },
        {
          &amp;quot;term&amp;quot;: {
            &amp;quot;title&amp;quot;: {
              &amp;quot;value&amp;quot;: &amp;quot;elasticsearch&amp;quot;
            }
          }
        },
       .....省略
      ]
    }
  }
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;搜索出来的结果跟我们想要的顺序不一致，那么我们下一步加权重。增加boost&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;GET /article/_search
{
  &amp;quot;query&amp;quot;: {
    &amp;quot;bool&amp;quot;: {
      &amp;quot;should&amp;quot;: [
        {
          &amp;quot;term&amp;quot;: {
            &amp;quot;title&amp;quot;: {
              &amp;quot;value&amp;quot;: &amp;quot;java&amp;quot;,
              &amp;quot;boost&amp;quot;: 5
            }
          }
        },
        {
          &amp;quot;term&amp;quot;: {
            &amp;quot;title&amp;quot;: {
              &amp;quot;value&amp;quot;: &amp;quot;spark&amp;quot;,
              &amp;quot;boost&amp;quot;: 4
            }
          }
        }
      ]
    }
  }
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;strong&gt;基于dis_max的策略控制&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;dix_max想要解决的是：      &lt;br /&gt;如果我们想要某一个filed中匹配到尽可能多的关键词的被排在前面，而不是在多个filed中重复出现相同的词语的排在前面。&lt;/p&gt;    &lt;p&gt;举例说明：      &lt;br /&gt;      &lt;img alt="" src="https://img2022.cnblogs.com/blog/1178991/202207/1178991-20220708220750471-1739291679.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;对于一个文档会将title匹配到的分数和content匹配到的分数相加。所以doc id为2的文档的分数比doc id为4的大。&lt;/p&gt;    &lt;p&gt;dis_max查询：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;GET /article/_search
{
  &amp;quot;query&amp;quot;: {
    &amp;quot;dis_max&amp;quot;: {
      &amp;quot;queries&amp;quot;: [
        {&amp;quot;match&amp;quot;: {&amp;quot;title&amp;quot;: &amp;quot;java&amp;quot;}},
        {&amp;quot;match&amp;quot;:{&amp;quot;content&amp;quot;:&amp;quot;java solution&amp;quot;}}  
        
        ]
    }
  }
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;查询到的结果如下：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;{
  &amp;quot;took&amp;quot; : 6,
  &amp;quot;timed_out&amp;quot; : false,
  &amp;quot;_shards&amp;quot; : {
    &amp;quot;total&amp;quot; : 1,
    &amp;quot;successful&amp;quot; : 1,
    &amp;quot;skipped&amp;quot; : 0,
    &amp;quot;failed&amp;quot; : 0
  },
  &amp;quot;hits&amp;quot; : {
    &amp;quot;total&amp;quot; : {
      &amp;quot;value&amp;quot; : 2,
      &amp;quot;relation&amp;quot; : &amp;quot;eq&amp;quot;
    },
    &amp;quot;max_score&amp;quot; : 1.4905943,
    &amp;quot;hits&amp;quot; : [
      {
        &amp;quot;_index&amp;quot; : &amp;quot;article&amp;quot;,
        &amp;quot;_type&amp;quot; : &amp;quot;_doc&amp;quot;,
        &amp;quot;_id&amp;quot; : &amp;quot;4&amp;quot;,
        &amp;quot;_score&amp;quot; : 1.4905943,
        &amp;quot;_source&amp;quot; : {
          &amp;quot;title&amp;quot; : &amp;quot;spark&amp;quot;,
          &amp;quot;content&amp;quot; : &amp;quot;spark is best big data solution based on scala,an programming language similar to java&amp;quot;
        }
      },
      {
        &amp;quot;_index&amp;quot; : &amp;quot;article&amp;quot;,
        &amp;quot;_type&amp;quot; : &amp;quot;_doc&amp;quot;,
        &amp;quot;_id&amp;quot; : &amp;quot;2&amp;quot;,
        &amp;quot;_score&amp;quot; : 1.2039728,
        &amp;quot;_source&amp;quot; : {
          &amp;quot;title&amp;quot; : &amp;quot;java&amp;quot;,
          &amp;quot;content&amp;quot; : &amp;quot;i think java is the best programming language&amp;quot;
        }
      }
    ]
  }
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;strong&gt;基于function_score自定义相关度分数&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;在用ES进行搜索时，搜索结果默认会以文档的相关度进行排序，而这个&amp;quot;文档的相关度&amp;quot;，是可以通过function_score自定义的。&lt;/p&gt;    &lt;p&gt;function_score提供了几种类型的得分函数：&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;script_score&lt;/li&gt;      &lt;li&gt;weight&lt;/li&gt;      &lt;li&gt;random_score&lt;/li&gt;      &lt;li&gt;field_value_factor&lt;/li&gt;      &lt;li&gt;decay functions:gauss、linear、exp&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;      &lt;strong&gt;random_score&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;随机打分，也就是每次查询出来的排序都不一样。&lt;/p&gt;    &lt;p&gt;举一个例子：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;GET /article/_search
{
  &amp;quot;query&amp;quot;: {
    &amp;quot;function_score&amp;quot;: {
      &amp;quot;query&amp;quot;: {&amp;quot;match_all&amp;quot;: {}},
       &amp;quot;random_score&amp;quot;: {}
    }
  }
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;strong&gt;field_value_factor&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;该函数可以根据文档中的字段来计算分数。&lt;/p&gt;    &lt;p&gt;示例：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;GET /item/_search
{
  &amp;quot;query&amp;quot;: {
    &amp;quot;function_score&amp;quot;: {
      &amp;quot;field_value_factor&amp;quot;: {
        &amp;quot;field&amp;quot;: &amp;quot;price&amp;quot;,
        &amp;quot;factor&amp;quot;: 1.2,
        &amp;quot;modifier&amp;quot;: &amp;quot;none&amp;quot;
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;    &lt;table&gt;      &lt;tr&gt;        &lt;th&gt;属性&lt;/th&gt;        &lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;field&lt;/td&gt;        &lt;td&gt;要从文档中提取的字段&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;factor&lt;/td&gt;        &lt;td&gt;字段值乘以的值，默认为1&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;modifier&lt;/td&gt;        &lt;td&gt;应用于字段值的修复符&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;    &lt;p&gt;modifier的取值有如下多种：&lt;/p&gt;    &lt;table&gt;      &lt;tr&gt;        &lt;th&gt;Modifier&lt;/th&gt;        &lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;none&lt;/td&gt;        &lt;td&gt;不要对字段值应用任何乘数&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;log&lt;/td&gt;        &lt;td&gt;取字段值的常用对数。因为此函数将返回负值并在0到1之间的值上使用时导致错误，所以建议改用log1p&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;log1p&lt;/td&gt;        &lt;td&gt;将字段值上加1并取对数&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;log2p&lt;/td&gt;        &lt;td&gt;将字段值上加2并取对数&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;ln&lt;/td&gt;        &lt;td&gt;取字段值的自然对数。因为此函数将返回负值并在0到1之间的值上使用时引起错误，所以建议改用 ln1p&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;ln1p&lt;/td&gt;        &lt;td&gt;将1加到字段值上并取自然对数&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;ln2p&lt;/td&gt;        &lt;td&gt;将2加到字段值上并取自然对数&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;square&lt;/td&gt;        &lt;td&gt;对字段值求平方&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;sqrt&lt;/td&gt;        &lt;td&gt;取字段值的平方根&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;reciprocal&lt;/td&gt;        &lt;td&gt;交换字段值，与1 / x相同，其中x是字段的值&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;    &lt;blockquote&gt;      &lt;p&gt;field_value_score函数产生的分数必须为非负数，否则将引发错误。如果在0到1之间的值上使用log和ln修饰符将产生负值。请确保使用范围过滤器限制该字段的值以避免这种情况，或者使用log1p和ln1p&lt;/p&gt;&lt;/blockquote&gt;    &lt;h3&gt;分页性能问题&lt;/h3&gt;    &lt;p&gt;在ES中我们一般采用的分页方式是from+size的形式，当数据量比较大时，Es会对分页作出限制，因为此时性能消耗很大。&lt;/p&gt;    &lt;p&gt;举个例子：一个索引分10个shards，然后一个搜索请求，from=990,size=10。      &lt;img alt="" src="https://img2022.cnblogs.com/blog/1178991/202207/1178991-20220708223847593-515560228.png"&gt;&lt;/img&gt;      &lt;br /&gt;此时es会从每个shards上去查询1000条数据，尽管每条数据只有_doc_id和_score，但是经不住它量大啊。如果from是10000呢？就更加耗费资源了。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;解决方案&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;1. 利用scroll遍历&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;scroll分为初始化和遍历两步。&lt;/p&gt;    &lt;p&gt;步骤1：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;POST /item/_search?scroll=1m&amp;amp;size=2
{
  &amp;quot;query&amp;quot;: { &amp;quot;match_all&amp;quot;: {}}
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;步骤2：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;GET /_search/scroll 
{
&amp;quot;scroll&amp;quot;: &amp;quot;1m&amp;quot;,
&amp;quot;scroll_id&amp;quot; : &amp;quot;步骤1中查询出的值&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;strong&gt;2. search after方式&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;在ES 5.x后提供的一种，根据上一页的最后一条数据来确定下一页的位置的方式。如果分页请求的过程中，有数据的增删改，也会实时的反映到游标上。这种方式依赖上一页的数据，所以不能跳页。&lt;/p&gt;    &lt;p&gt;步骤1：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;GET /item/_search
{
  &amp;quot;query&amp;quot;: {
    &amp;quot;match_all&amp;quot;: {}
  },
  &amp;quot;size&amp;quot;: 2
  ,&amp;quot;sort&amp;quot;: [
    {
      &amp;quot;_id&amp;quot;: {
        &amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;
      }
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;查询结果：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;{
  &amp;quot;took&amp;quot; : 0,
  &amp;quot;timed_out&amp;quot; : false,
  &amp;quot;_shards&amp;quot; : {
    &amp;quot;total&amp;quot; : 1,
    &amp;quot;successful&amp;quot; : 1,
    &amp;quot;skipped&amp;quot; : 0,
    &amp;quot;failed&amp;quot; : 0
  },
  &amp;quot;hits&amp;quot; : {
    &amp;quot;total&amp;quot; : {
      &amp;quot;value&amp;quot; : 6,
      &amp;quot;relation&amp;quot; : &amp;quot;eq&amp;quot;
    },
    &amp;quot;max_score&amp;quot; : null,
    &amp;quot;hits&amp;quot; : [
      {
        &amp;quot;_index&amp;quot; : &amp;quot;item&amp;quot;,
        &amp;quot;_type&amp;quot; : &amp;quot;_doc&amp;quot;,
        &amp;quot;_id&amp;quot; : &amp;quot;uL6choEB9TD2fYkcrziw&amp;quot;,
        &amp;quot;_score&amp;quot; : null,
        &amp;quot;_source&amp;quot; : {
          &amp;quot;title&amp;quot; : &amp;quot;小米8手机&amp;quot;,
          &amp;quot;images&amp;quot; : &amp;quot;http://image.lagou.com/12479122.jpg&amp;quot;,
          &amp;quot;price&amp;quot; : 2688,
          &amp;quot;createTime&amp;quot; : &amp;quot;2022-02-02 12:02:02&amp;quot;
        },
        &amp;quot;sort&amp;quot; : [
          &amp;quot;uL6choEB9TD2fYkcrziw&amp;quot;
        ]
      },
      {
        &amp;quot;_index&amp;quot; : &amp;quot;item&amp;quot;,
        &amp;quot;_type&amp;quot; : &amp;quot;_doc&amp;quot;,
        &amp;quot;_id&amp;quot; : &amp;quot;tr6YgYEB9TD2fYkcFzjY&amp;quot;,
        &amp;quot;_score&amp;quot; : null,
        &amp;quot;_source&amp;quot; : {
          &amp;quot;title&amp;quot; : &amp;quot;小米手机&amp;quot;,
          &amp;quot;images&amp;quot; : &amp;quot;http://image.lagou.com/12479122.jpg&amp;quot;,
          &amp;quot;price&amp;quot; : 2688,
          &amp;quot;createTime&amp;quot; : &amp;quot;2022-02-01 12:02:02&amp;quot;
        },
        &amp;quot;sort&amp;quot; : [
          &amp;quot;tr6YgYEB9TD2fYkcFzjY&amp;quot;
        ]
      }
    ]
  }
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;步骤2：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;GET /item/_search
{
  &amp;quot;query&amp;quot;: {
    &amp;quot;match_all&amp;quot;: {}
  },
  &amp;quot;size&amp;quot;: 2,
  &amp;quot;search_after&amp;quot;:[&amp;quot;tr6YgYEB9TD2fYkcFzjY&amp;quot;]
  ,&amp;quot;sort&amp;quot;: [
    {
      &amp;quot;_id&amp;quot;: {
        &amp;quot;order&amp;quot;: &amp;quot;desc&amp;quot;
      }
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;总结对比：&lt;/p&gt;    &lt;table&gt;      &lt;tr&gt;        &lt;th&gt;分页方式&lt;/th&gt;        &lt;th&gt;性能&lt;/th&gt;        &lt;th&gt;优点&lt;/th&gt;        &lt;th&gt;缺点&lt;/th&gt;        &lt;th&gt;场景&lt;/th&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;from + size&lt;/td&gt;        &lt;td&gt;低&lt;/td&gt;        &lt;td&gt;灵活性好，实现简单&lt;/td&gt;        &lt;td&gt;深度分页问题&lt;/td&gt;        &lt;td&gt;数据量比较小，能容忍深度分页问题&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;scroll&lt;/td&gt;        &lt;td&gt;中&lt;/td&gt;        &lt;td&gt;解决了深度分页问题&lt;/td&gt;        &lt;td&gt;无法反映数据的实时性（快照版本）维护成本高，需要维护一个scroll_id&lt;/td&gt;        &lt;td&gt;海量数据的导出需要查询海量结果集的数据&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;search_after&lt;/td&gt;        &lt;td&gt;高&lt;/td&gt;        &lt;td&gt;性能最好&lt;/td&gt;        &lt;td&gt;不存在深度分页问题能够反映数据的实时变更实现连续分页的实现会比较复杂，因为每一次查询都需要上次查询的结果&lt;/td&gt;        &lt;td&gt;海量数据的分页&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62322-elasticsearch-%E6%B7%B1%E5%BA%A6-%E5%BA%94%E7%94%A8</guid>
      <pubDate>Sat, 09 Jul 2022 14:14:38 CST</pubDate>
    </item>
    <item>
      <title>因果推断在游戏个性化数值中的实践及应用</title>
      <link>https://itindex.net/detail/62304-%E5%9B%A0%E6%9E%9C-%E6%B8%B8%E6%88%8F-%E4%B8%AA%E6%80%A7</link>
      <description>&lt;h2&gt;一、背景&lt;/h2&gt;
 &lt;p&gt;在游戏场景内，通常有着各种各样的玩法数值设计。由于不同用户在偏好、游戏经验等方面存在差异，因此同一数值并不适用于所有用户。例如一个闯关游戏，对于新手来说，设置关卡的难度系数可以比有丰富经验的老玩家低一些。为了让用户能够有更好的游戏体验，我们可以基于算法对用户进行个性化的数值调控，从而提升用户在游戏内的时长、留存等。&lt;/p&gt;
 &lt;p&gt;传统的监督学习方式聚焦于响应结果 Y 的预估，而我们场景更关注于变量的变化对于结果 Y 的影响。在业界，这类问题通常会放在因果推断(Causal Inference)的框架下进行讨论，我们通常将变量称为 T(treatment)，变量变化带来结果 Y 的变化称为 TE(treatment effect)，用来预估 TE 的模型称为因果模型(Uplift Model)。&lt;/p&gt;
 &lt;p&gt;目前业界中比较常用的因果模型有 meta-learner、dml、因果森林等，但是不同因果模型的优劣势及实际表现还没有做过很全面的对比。因此在我们场景中，我们对上述这些问题进行了详细的探索。&lt;/p&gt;
 &lt;p&gt;本文将从理论及实践两方面，对比及分析不同因果模型的优缺点及适用场景，希望能够为大家在后续处理相似问题时，提供启发及帮助。&lt;/p&gt;
 &lt;h2&gt;二、常见模型介绍&lt;/h2&gt;
 &lt;h3&gt;2.1 Meta-learner&lt;/h3&gt;
 &lt;p&gt;meta-learner 是目前主流的因果建模方式之一，其做法是使用基础的机器学习模型去预估不同 treatment 的 conditional average treatment effect(CATE)，常见的方法有：s-learner、t-learner。meta-learner 的思路比较简单，本质上都是使用 base-learner 去学习用户在不同 treatment 组中的 Y，再相减得到 te。区别在于在 s-learner 中，所有 treatment 的数据都是在一个模型中训练，treatment 通常会作为模型的一个输入特征。而 t-learner 会针对每个 treatment 组都训练一个模型。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/200d9fb07d3d478989f725573c1007df~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;2.2 Double machine learning\&lt;/p&gt;
 &lt;p&gt;在 meta-learner 中，中间变量的预测误差导致我们在进行 uplift 预估时天生存在 bias。为了解决该问题，DML 引入了残差拟合、cross fitting 等方式进行消偏处理，最终得到了无偏估计。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cf9ca3c4b48a433eb1a74604f6785376~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;DML 的核心思想就是通过  &lt;strong&gt;拟合残差&lt;/strong&gt;，来消除中间变量的 bias 的影响。论文中证实了误差的收敛速度快于 n^(-1/4)，确保了最终预估结果的收敛性。下图展示了论文中不使用 DML、使用 DML 但不使用 cross fitting、使用 DML-cross fitting 的效果对比：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6fea1a59d2904755b7d0ce48587afb78~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;2.3 Generalized Random Forests&lt;/h3&gt;
 &lt;p&gt;GRF 是一种广义的随机森林算法，和传统的随机森林算法的不同点在于，传统的随机森林算法在做 split 时，是找 loss 下降最大的方向进行划分，而 GRF 的思想是找到一种划分方式，能够最大化两个子节点对于干预效果之间的差异。和随机森林相同，GRF 也需要构建多棵树。在每次建树时，也需要随机无放回的进行抽样，抽取出来的样本一半用来建树、一半用来评估。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a891c0ae66454856b42f0e64363fa8e5~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;GRF 算法延续了 DML 的思想，在第一阶段时，使用任意的机器模型去拟合残差。第二阶段时，GRF 算法引入了得分函数 Ψ(Oi)、目标函数 θ(x)和辅助函数 v(x)，其中得分函数的计算公式为：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/02685d93e3174eb3aaebf4ac2c2e4462~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;很容易看出，得分函数 Ψ(Oi)其实就是残差，由公式 Y = θ(x)T + v(x)得到的。算法寻求满足局部估计等式的 θ(x)：对于所有 x，满足：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c490eadd7c074f4ca91579a141053b32~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;其实本质上也是学习 θ(x)，使得实验组和对照组数据的预估结果与真实值之差最小。&lt;/p&gt;
 &lt;h2&gt;三、评估方式&lt;/h2&gt;
 &lt;p&gt;目前因果模型常见的评估方式有两种：uplift bins 及 uplift curve&lt;/p&gt;
 &lt;h3&gt;3.1 Uplift bins&lt;/h3&gt;
 &lt;p&gt;将训练好的模型分别预测实验组和对照组的测试集数据，可以分别得到两组人群的 uplift score。按照 uplift score 的降序进行排列，分别截取 top10%、top20% .... top100%的用户，计算每一分位下两组人群分值的差异，这个差异可以近似认为是该分位下对应人群的真实 uplift 值。uplift bins 的缺陷在于，只能做一个定性的分析，无法比较不同模型效果好坏。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bcf7857a4a0a4e23a769dead8197278d~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;3.2 Qini curve&lt;/h3&gt;
 &lt;p&gt;在 uplift bins 的基础上，我们可以绘制一条曲线，用类似于 AUC 的方式来评价模型的表现，这条曲线称为 uplift curve；我们将数据组的数据不断细分，精确到样本维度时，每次计算截止前 t 个样本的增量时，得到对应的 uplift curve。&lt;/p&gt;
 &lt;p&gt;计算公式为：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b7e91c59edb7405e9ca6df012d9ed3c2~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;其中 Y_t^T 代表前 t 个样本增量时，实验组样本转化量，N_t^T 代表实验组的累计到 t 时，实验组样本总量，对照组同理。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bd5709bf270d40528a02c3565a5d1d5f~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;如上图，蓝线代表的 uplift curve，实黑线代表 random 的效果，两者之间的面积作为模型的评价指标，其面积越大越好，表示模型的效果比随机选择的结果好的更多。与 AUC 类似，这个指标我们称为 AUUC(Area Under Uplift Curve)。&lt;/p&gt;
 &lt;h2&gt;四、业务应用&lt;/h2&gt;
 &lt;h3&gt;4.1 样本准备&lt;/h3&gt;
 &lt;p&gt;因果建模对于样本的要求比较高，需要样本服从 CIA(conditional independence assumption)条件独立假设，即样本特征 X 与 T 相互独立。因此在进行因果建模前，需要进行随机实验进行样本收集，通常是通过 A/B 的方式将用户随机的分配至不同的 treatment 中，观测用户在不同 treatment 下的表现。&lt;/p&gt;
 &lt;h3&gt;4.2 样本构造&lt;/h3&gt;
 &lt;p&gt;样本构造与常规机器学习的样本构造步骤基本一致，但是需要特别关注以下方面：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;特征关联&lt;/strong&gt;：用户特征 X 必须严格使用进入随机实验组前的特征，例如：用户 T 日进入实验组，那么用户的特征必须使用 T-1 日及以前的特征。这样做的原因是用户进入 treatment 后，部分特征可能已经受到 treatment 的影响发生了改变，使用受影响后的特征进行模型训练有几率造成信息泄露，对模型的效果造成比较大的影响甚至起反向的作用。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;目标选择&lt;/strong&gt;：在某些场景中，treatment 的影响需要一段时间才能够产生作用，例如道具数量的调整对用户留存的影响可能需要过一段时间才能体现。因此在选择目标时，可以选择更长周期的目标，例如相比于次日留存，选择 7 日留存或 14 日留存会更优。不过也不是越长周期越好，因为越长周期的目标有可能导致模型的学习成本增加从而效果下降，这种情形在小样本的场景更为突出。选择一个合适的目标能够很大程度上提升模型的线上表现。&lt;/p&gt;
 &lt;h3&gt;4.3 模型训练&lt;/h3&gt;
 &lt;p&gt;在我们的场景中，用户每次完成任务发放的道具数量为 treatment，用户留存以及用户活跃时长变化为我们关注的 uplift。实验过程中，我们先后对比了 s-learner、t-learner 以及 dml 的效果，三种模型选择的 base-learner 都为 lightgbm。&lt;/p&gt;
 &lt;p&gt;在实验的过程中，我们发现，当使用 s-learner 对活跃时长进行建模时，无论如何调试模型，得到的 treatment effect 都为 0，即用户在不同 treatment 下的活跃时长预测结果相同。但是当我们将模型换成 t-learner 或 dml 时，treatment effect 数据恢复正常。输出 s-learner 的特征重要度，我们发现 treatment 特征的重要度为 0。我们对用户在不同 treatment 下活跃数据进行分析，发现不同组的活跃数据弹性很小，即用户在不同 treatment 下的活跃改变很小。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3d9cc948001e46c6bb59f1bd091446ef~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;而 s-learner 对于这种微弱的改动敏感度很低，因此效果不佳。而 t-learner 在进行训练时，会针对每个 treatment 都训练一个模型，相当于显性的将 treatment 的特征重要度加大，而 dml 在训练过程中主要关注训练的残差，因此这两类模型的效果都要好于 s-learner。  &lt;strong&gt;这也反映了 s-learner 在数据弹性不足时的效果缺陷&lt;/strong&gt;，因此在后续的训练中，我们放弃了 s-learner，主要关注在 t-learner 以及 dml 上。&lt;/p&gt;
 &lt;p&gt;后续在不同指标的离线评估上，  &lt;strong&gt;dml 模型的效果都要显著优于 t-learner&lt;/strong&gt;。这也与理论相互印证：t-learner 由于引入中间变量，中间变量的误差使得对于最终 uplift 的预估有偏，而 dml 通过拟合残差，最终实现了无偏估计。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5df26417c19c4df58bb01b3e3cb6550d~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;\&lt;/p&gt;
 &lt;h3&gt;4.4 人群分配&lt;/h3&gt;
 &lt;p&gt;根据训练效果，我们选择 dml 作为最终的预估模型，并得到了用户在不同 treatment 下的 uplift 值。我们会根据用户在不同 treatment 下的 uplift 值，对用户做人群分配。分配方案基于实际情况主要分为两种：有无约束条件下的人群分配及有约束条件下的人群分配。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;无约束条件下的人群分配&lt;/strong&gt;：只关心优化指标，不关心其他指标的变化。那么我们可以基于贪心的思想，选择每个用户 uplift 值最高的策略进行人群分配。&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;有约束条件下的人群分配&lt;/strong&gt;：关注优化指标的同时，对于其他指标的变化也有一定的约束。我们可以通过约束求解的方式对该类问题进行求解。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;在我们的业务场景下，我们同时对用户留存、活跃时长、流水等目标都有限制，因此进行了有约束条件下的人群分配方案。&lt;/p&gt;
 &lt;h3&gt;4.5 实验效果&lt;/h3&gt;
 &lt;p&gt;基于训练好的 dml 模型及约束分配后的结果，我们开启了线上 A/B 实验。在经过多周的测试后，相较于基准策略，我们的策略在流水、活跃等指标不降的情况，取得了置信的 10%+留存收益。目前我们基于因果模型的策略已经全量上线。&lt;/p&gt;
 &lt;h2&gt;五、总结及后续展望&lt;/h2&gt;
 &lt;p&gt;因果模型目前在互联网各大场景都得到了实践及应用，并取得了不错的收益。随着营销活动越来越多，营销手段越来越复杂，treatment 的维度也由常见的多 treatment 逐渐变为连续 treatment，这对于样本、模型学习能力等方面的要求也越来越严格。在后续工作开展，可以考虑从多目标建模、场景联动、无偏估计、强化学习等方面继续进行优化，为各个业务场景产生更大价值。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62304-%E5%9B%A0%E6%9E%9C-%E6%B8%B8%E6%88%8F-%E4%B8%AA%E6%80%A7</guid>
      <pubDate>Tue, 17 May 2022 11:54:50 CST</pubDate>
    </item>
    <item>
      <title>Spring奇技淫巧之扩展点的应用！</title>
      <link>https://itindex.net/detail/62245-spring-%E6%89%A9%E5%B1%95-%E5%BA%94%E7%94%A8</link>
      <description>&lt;h1&gt;前言&lt;/h1&gt;
 &lt;p&gt;文章首发在公众号（月伴飞鱼），之后同步到个人网站：  &lt;a href="https://xiaoflyfish.cn/"&gt;xiaoflyfish.cn/&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;觉得有收获，希望帮忙点赞，转发下哈，谢谢，谢谢&lt;/strong&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;最近在看公司项目和中间件的时候，看到一些Spring扩展点的使用，写篇文章学习下，对大家之后看源码都有帮助&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;strong&gt;「首先先介绍下Bean的生命周期」&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;我们知道Bean的生命周期分为几个主干流程&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Bean（单例非懒加载）的实例化阶段&lt;/li&gt;
  &lt;li&gt;Bean的属性注入阶段&lt;/li&gt;
  &lt;li&gt;Bean的初始化阶段&lt;/li&gt;
  &lt;li&gt;Bean的销毁阶段&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;下面是整个Spring容器的启动流程，可以看到除了上述几个主干流程外，Spring还提供了很多扩展点&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/73e1fa3f9fa54f4d87357c7ae8c4871d~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;下面详细介绍下Spring的常见的扩展点&lt;/p&gt;
 &lt;h1&gt;Spring常见扩展点&lt;/h1&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/441b32258d8c465c842befad465676fd~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;「BeanFactoryPostProcessor#postProcessBeanFactory」&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;有时候整个项目工程中bean的数量有上百个，而大部分单测依赖都是整个工程的xml，导致单测执行时需要很长时间（大部分时间耗费在xml中数百个单例非懒加载的bean的实例化及初始化过程）&lt;/p&gt;
 &lt;p&gt;解决方法：利用Spring提供的扩展点将xml中的bean设置为懒加载模式，省去了Bean的实例化与初始化时间&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public class LazyBeanFactoryProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        DefaultListableBeanFactory fac = (DefaultListableBeanFactory) beanFactory;
        Map&amp;lt;String, AbstractBeanDefinition&amp;gt; map = (Map&amp;lt;String, AbstractBeanDefinition&amp;gt;) ReflectionTestUtils.getField(fac, &amp;quot;beanDefinitionMap&amp;quot;);
        for (Map.Entry&amp;lt;String, AbstractBeanDefinition&amp;gt; entry : map.entrySet()) {
            //设置为懒加载
            entry.getValue().setLazyInit(true);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;「InstantiationAwareBeanPostProcessor#postProcessPropertyValues」&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;非常规的配置项比如&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;context:component-scan base-package=&amp;quot;com.zhou&amp;quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;Spring提供了与之对应的特殊解析器&lt;/p&gt;
 &lt;p&gt;正是通过这些特殊的解析器才使得对应的配置项能够生效&lt;/p&gt;
 &lt;p&gt;而针对这个特殊配置的解析器为 ComponentScanBeanDefinitionParser&lt;/p&gt;
 &lt;p&gt;在这个解析器的解析方法中，注册了很多特殊的Bean&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public BeanDefinition parse(Element element, ParserContext parserContext) {
  //...
  registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
    //...
  return null;
}
public static Set&amp;lt;BeanDefinitionHolder&amp;gt; registerAnnotationConfigProcessors(
   BeanDefinitionRegistry registry, Object source) {

  Set&amp;lt;BeanDefinitionHolder&amp;gt; beanDefs = new LinkedHashSet&amp;lt;BeanDefinitionHolder&amp;gt;(4);
  //...
    //@Autowire
  if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
   RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
   def.setSource(source);
   beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
  }

  // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
   //@Resource
  if (jsr250Present &amp;amp;&amp;amp; !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
      //特殊的Bean
   RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
   def.setSource(source);
   beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
  }
  //...
  return beanDefs;
 }
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;以@Resource为例，看看这个特殊的bean做了什么&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor
  implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {
     
      public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, 
      Object bean, String beanName) throws BeansException {
          InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass());
          try {
            //属性注入
            metadata.inject(bean, beanName, pvs);
          }
          catch (Throwable ex) {
            throw new BeanCreationException(beanName, &amp;quot;Injection of resource dependencies failed&amp;quot;, ex);
          }
          return pvs;
    }
    
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;我们看到在postProcessPropertyValues方法中，进行了属性注入&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;「invokeAware」&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;实现BeanFactoryAware接口的类，会由容器执行setBeanFactory方法将当前的容器BeanFactory注入到类中&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;@Bean
class BeanFactoryHolder implements BeanFactoryAware{
   
    private static BeanFactory beanFactory;
    
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;「BeanPostProcessor#postProcessBeforeInitialization」&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;实现ApplicationContextAware接口的类，会由容器执行setApplicationContext方法将当前的容器applicationContext注入到类中&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;@Bean
class ApplicationContextAwareProcessor implements BeanPostProcessor {

    private final ConfigurableApplicationContext applicationContext;

    public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
      this.applicationContext = applicationContext;
    }

    @Override
    public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
      //...
      invokeAwareInterfaces(bean);
      return bean;
    }

    private void invokeAwareInterfaces(Object bean) {
        if (bean instanceof ApplicationContextAware) {
          ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;我们看到是在BeanPostProcessor的postProcessBeforeInitialization中进行了setApplicationContext方法的调用&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;class ApplicationContextHolder implements ApplicationContextAware{
   
    private static ApplicationContext applicationContext;
    
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;「afterPropertySet()和init-method」&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;目前很多Java中间件都是基本Spring Framework搭建的，而这些中间件经常把入口放到afterPropertySet或者自定义的init中&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;「BeanPostProcessor#postProcessAfterInitialization」&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;熟悉aop的同学应该知道，aop底层是通过动态代理实现的&lt;/p&gt;
 &lt;p&gt;当配置了  &lt;code&gt;&amp;lt;aop:aspectj-autoproxy/&amp;gt;&lt;/code&gt;时候，默认开启aop功能，相应地调用方需要被aop织入的对象也需要替换为动态代理对象&lt;/p&gt;
 &lt;p&gt;不知道大家有没有思考过动态代理是如何**「在调用方无感知情况下替换原始对象」**的？&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;❝&lt;/p&gt;
  &lt;p&gt;根据上文的讲解，我们知道：&lt;/p&gt;
  &lt;p&gt;❞&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;aop:aspectj-autoproxy/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;Spring也提供了特殊的解析器，和其他的解析器类似，在核心的parse方法中注册了特殊的bean&lt;/p&gt;
 &lt;p&gt;这里是一个BeanPostProcessor类型的bean&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser {
 @Override
 public BeanDefinition parse(Element element, ParserContext parserContext) {
    //注册特殊的bean
  AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
  extendBeanDefinition(element, parserContext);
  return null;
    }
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;将于当前bean对应的动态代理对象返回即可，该过程对调用方全部透明&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator {
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean != null) {
          Object cacheKey = getCacheKey(bean.getClass(), beanName);
          if (!this.earlyProxyReferences.containsKey(cacheKey)) {
            //如果该类需要被代理，返回动态代理对象；反之，返回原对象
            return wrapIfNecessary(bean, beanName, cacheKey);
          }
        }
        return bean;
 }
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;正是利用Spring的这个扩展点实现了动态代理对象的替换&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;「destroy()和destroy-method」&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;bean生命周期的最后一个扩展点，该方法用于执行一些bean销毁前的准备工作，比如将当前bean持有的一些资源释放掉&lt;/p&gt;
 &lt;h1&gt;最后&lt;/h1&gt;
 &lt;p&gt;  &lt;strong&gt;「写文章画图不易，喜欢的话，希望帮忙点赞，转发下哈，谢谢」&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;微信搜索：月伴飞鱼，交个朋友&lt;/p&gt;
 &lt;p&gt;参考书籍：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Spring技术内幕&lt;/li&gt;
  &lt;li&gt;Spring源码深度解析&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62245-spring-%E6%89%A9%E5%B1%95-%E5%BA%94%E7%94%A8</guid>
      <pubDate>Tue, 19 Apr 2022 13:49:56 CST</pubDate>
    </item>
    <item>
      <title>详谈布隆过滤器在亿级流量电商系统的应用</title>
      <link>https://itindex.net/detail/62240-%E5%B8%83%E9%9A%86-%E8%BF%87%E6%BB%A4%E5%99%A8-%E6%B5%81%E9%87%8F</link>
      <description>&lt;blockquote&gt;
  &lt;p&gt;!!!  本文已参与「新人创作礼」活动，一起开启掘金创作之路。更多干货文章，可以访问    &lt;a href="https://blog.csdn.net/weixin_46785144?type=blog"&gt;菜鸟厚非&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h1&gt;简介&lt;/h1&gt;
 &lt;p&gt;布隆过滤器在实际中发挥着非常重要的作用，一呢可以防止网站被攻击，二呢可以提高系统的性能。接下来我们通过实际案例进行讲解。&lt;/p&gt;
 &lt;br /&gt;
 &lt;h1&gt;案例详解&lt;/h1&gt;
 &lt;p&gt;例如在电商平台，下面商品访问的地址为 https://item.jd.com/  &lt;strong&gt;857&lt;/strong&gt;.html，其中 857 是商品的 SKU 。不明白的同学可以简单理解为一个数字对于不同的商品。
  &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/59b0d6cc1ed04c8694e63a039e4c3be4~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;
接着我们来看一个正常的流程，用户访问商场，应用程序从 Redis 拿取数据，如此此时没有，则会到数据库里面拿取。
  &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9c1e66bfedb44e4aaeffcc212d6e422e~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;
此时我们的缓存服务器缓存了 1-1000 的商品，且用户访问都是正常状态。但在当前的设计下有个致命的问题，0-1000 是存在的数据，但这些之外商品不存在。
  &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d8ad6590b91c4d51a1bddf9f5c4bc996~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;
恰逢今年双十一，突然遭到了攻击，不法分子通过改变商品的 SKU 进行频繁访问，且访问的商品还是不存在的。&lt;/p&gt;
 &lt;p&gt;此时这些产品在缓存中是不存在，而且数据库也是不存在，大量的请求都会到 DB ，大家都是数据库在短时高并发情况下，承载能力是并不行的。这时就会引起系统瘫痪，这在电商系统是非常非常严重的事情。&lt;/p&gt;
 &lt;p&gt;这种绕过 Redis 服务器，直接查询 DB 的攻击方式，称之为缓存穿透攻击。下面我们进行讲解
  &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/19902b0d2d3042c59818aea736d3860a~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;h2&gt;缓存穿透&lt;/h2&gt;
 &lt;h3&gt;什么是缓存穿透&lt;/h3&gt;
 &lt;p&gt;缓存穿透攻击是指恶意用户在短时内大量查询不存在的数据，导致大量请求被送达数据库进行查询，当请求数量超过数据库负载上限时，使系统响应出现高延迟甚至瘫痪的攻击行为。&lt;/p&gt;
 &lt;p&gt;同学们不要小看这种攻入，如果感到像 618 双十一 等这样的节日活动，造成的损失可以不可估量的。&lt;/p&gt;
 &lt;h3&gt;如何预防缓存穿透&lt;/h3&gt;
 &lt;p&gt;要想预防缓存穿透，可以使用布隆过滤器。那什么是布隆过滤器呢？&lt;/p&gt;
 &lt;p&gt;巴顿.布隆于一九七零年提出的，其主旨是采用一个很长的二进制数组，通过一系列的Hash函数来确定该数据是否存在。如下图
  &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/12a9f8b289ae4c1ba61dfd6bdd173aa3~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;此时我们将 1-1000 商品初始化到布隆过滤中。如下图
  &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/733d5b625870407fb0c46c56b461132f~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;
  &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4811e6a2de894d078251bd2b90a33ee9~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;
  &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1e6b73672d9048f786fb70cfc179d887~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;我们对 858 商品在布隆过滤器中进行判断，可以看到布隆过滤器的判断过程，可以看到所处位全部是1。如下图
  &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0a4c11fd3fc846edb2ae4bc2a2839757~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;接着对不存在的商品 8888 进行布隆过滤器判断，可以看到某些位为 0 说明，此商品是不存在的。&lt;/p&gt;
 &lt;p&gt;如果当前是缓存穿透攻击，判断没有后，直接返回结果，就不用再进行查询，有效的阻挡了非法请求。
  &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a1a00f2a6bc04528869e62c5b53e3fcf~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;误判情况&lt;/h3&gt;
 &lt;p&gt;布隆过滤器并不是100%可以判断出是否存在，可以通过增加二进制数组位数、增加Hash次数来减少误判率。&lt;/p&gt;
 &lt;p&gt;通过例子解释什么是误判，商品 8889 在数据库是不存在的，但 hash 位全部是 1 ，之后就会进行查询操作，这种就是误判。
  &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7a9439a5c2a543c9a96ebe9a0cbe0447~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;有的同学会疑问误判了会怎么样，布隆过滤在设计之处就存在的误判情况，且误判可以优化到 1% ，如果面对攻击 10000 次也就只有 100 会误判，这种影响是不大的，在可接受范围内。&lt;/p&gt;
 &lt;br /&gt;
 &lt;h1&gt;项目中使用流程&lt;/h1&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/55b2b921d37e4a77b4d78c7577557be0~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;h1&gt;布隆过滤与数据一致性&lt;/h1&gt;
 &lt;ul&gt;
  &lt;li&gt;解决方案
定时异步重建布隆过滤器&lt;/li&gt;
  &lt;li&gt;解决方案
计数Bloom Fliter&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62240-%E5%B8%83%E9%9A%86-%E8%BF%87%E6%BB%A4%E5%99%A8-%E6%B5%81%E9%87%8F</guid>
      <pubDate>Tue, 03 May 2022 02:45:55 CST</pubDate>
    </item>
    <item>
      <title>老版本的Spring应用该如何应对CVE-2022-22965漏洞？</title>
      <link>https://itindex.net/detail/62184-%E7%89%88%E6%9C%AC-spring-%E5%BA%94%E7%94%A8</link>
      <description>&lt;p&gt;昨天，在发布了  &lt;a href="https://blog.didispace.com/spring-cve-2022-22965/"&gt;《Spring官宣承认网传大漏洞，并提供解决方案》&lt;/a&gt;之后。群里就有几个小伙伴问了这样的问题：**我们的Spring版本比较老，该怎么办？**这是一个好问题，所以DD今天单独拿出来说说。&lt;/p&gt;
 &lt;p&gt;这次的RCE漏洞宣布之后，官方给出的主要解决方案是升级版本，但只有Spring 5.2、5.3和Spring Boot 2.5、2.6提供了对应的升级版本。&lt;/p&gt;
 &lt;p&gt;那么对于一些还在用Spring 5.0、5.1甚至Spring 4.x、或者Spring Boot 1.x和Spring 2.4及以下版本的用户该怎么办呢？&lt;/p&gt;
 &lt;h2&gt;第一种方法&lt;/h2&gt;
 &lt;p&gt;官方给出过一种通过扩展  &lt;code&gt;RequestMappingHandlerAdapter&lt;/code&gt;来实现的方法。同时也给出了一个Spring Boot下使用Spring MVC的实现方案，如果是WebFlux的话略做修改即可。但如果不是Spring Boot的话，则Bean的初始化方式还要再改改。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;@SpringBootApplication
public class MyApp {

 public static void main(String[] args) {
  SpringApplication.run(CarApp.class, args);
 }


 @Bean
 public WebMvcRegistrations mvcRegistrations() {
  return new WebMvcRegistrations() {
   @Override
   public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
    return new ExtendedRequestMappingHandlerAdapter();
   }
  };
 }


 private static class ExtendedRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {

  @Override
  protected InitBinderDataBinderFactory createDataBinderFactory(List&amp;lt;InvocableHandlerMethod&amp;gt; methods) {

   return new ServletRequestDataBinderFactory(methods, getWebBindingInitializer()) {

    @Override
    protected ServletRequestDataBinder createBinderInstance(
      Object target, String name, NativeWebRequest request) throws Exception {
     
     ServletRequestDataBinder binder = super.createBinderInstance(target, name, request);
     String[] fields = binder.getDisallowedFields();
     List&amp;lt;String&amp;gt; fieldList = new ArrayList&amp;lt;&amp;gt;(fields != null ? Arrays.asList(fields) : Collections.emptyList());
     fieldList.addAll(Arrays.asList(&amp;quot;class.*&amp;quot;, &amp;quot;Class.*&amp;quot;, &amp;quot;*.class.*&amp;quot;, &amp;quot;*.Class.*&amp;quot;));
     binder.setDisallowedFields(fieldList.toArray(new String[] {}));
     return binder;
    }
   };
  }
 }
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这种需要我们去修改代码，其实我觉得还是有点麻烦的。如果对Spring机制不太熟悉的话，可能还会遇到不少麻烦。下面讲讲另外的便捷方法，也是我对老项目推荐的方法。&lt;/p&gt;
 &lt;h2&gt;第二种方法&lt;/h2&gt;
 &lt;p&gt;下面要讲的方法主要是规避的思路。什么是规避呢？就是针对该漏洞的利用条件去做一些调整。&lt;/p&gt;
 &lt;p&gt;比如，这次漏洞的条件是这些：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;JDK 9 +&lt;/li&gt;
  &lt;li&gt;使用Apache Tomcat部署&lt;/li&gt;
  &lt;li&gt;使用WAR方式打包&lt;/li&gt;
  &lt;li&gt;依赖spring-webmvc或spring-webflux&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;那么我就可以选择规避其中的1个条件就能防止漏洞的利用了，比如：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;降级到JDK 8&lt;/li&gt;
  &lt;li&gt;使用Undertow来部署&lt;/li&gt;
  &lt;li&gt;如果是Spring Boot的早期项的话，还能调整打包方式，采用JAR的方式打包和运行来规避。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;另外，DD有注意到，这次漏洞之后Tomcat的版本也更新了，所以当你用WAR部署的情况下，可以直接下载最新的Tomcat版本来规避也是一种不错的选择。&lt;/p&gt;
 &lt;p&gt;好了，今天的分享就到这里，解决群友(  &lt;a href="https://mp.weixin.qq.com/s?__biz=MzAxODcyNjEzNQ==&amp;mid=2247554212&amp;idx=4&amp;sn=609c66e339d7345ab00205da2abb8f9e&amp;chksm=9bd3b93caca4302ad7fd37133fb45f526d4268914a5f65839285adbd5dc7c77b057168f4a8d5&amp;token=2077530613&amp;lang=zh_CN&amp;scene=21#wechat_redirect"&gt;点击加群&lt;/a&gt;)的疑问是一方面，另一方面也是给大家讲讲解决问题时候的一种思考方式。有时候碰到硬茬，我们不一定要硬刚，换个方向解决可能性价比更高。如果您觉得今天的分享还不错，欢迎点赞、在看、转发到朋友圈。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;欢迎关注我的公众号：程序猿DD。第一时间了解前沿行业消息、分享深度技术干货、获取优质学习资源&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62184-%E7%89%88%E6%9C%AC-spring-%E5%BA%94%E7%94%A8</guid>
      <pubDate>Sat, 02 Apr 2022 06:34:26 CST</pubDate>
    </item>
    <item>
      <title>去中心化的招聘平台，会是Web3的应用典范吗？</title>
      <link>https://itindex.net/detail/62147-%E5%8E%BB%E4%B8%AD%E5%BF%83%E5%8C%96-%E6%8B%9B%E8%81%98-%E5%B9%B3%E5%8F%B0</link>
      <description>&lt;p&gt;本文来自微信公众号：  &lt;a href="https://mp.weixin.qq.com/s/EyxFSFv33BvlhD2lRWMp7Q" rel="nofollow" target="_blank"&gt;海外独角兽（ID：unicornobserver）&lt;/a&gt;，作者：Packy McCormick，编辑：Siqi，原文标题：《Braintrust ：去中心化的Boss直聘，Web3 融入现实的典范》，头图来自：视觉中国&lt;/p&gt; &lt;p&gt;DAO 的兴起让人们第一次开始认识到所有权经济：即由用户拥有、运营、维护、扩张自己正在使用的网络。越来越多的项目方开始这一理念带到了金融、社交、消费、社会等上层应用中。&lt;/p&gt; &lt;p&gt;Braintrust 在自己的白皮书中将其定义为一个  &lt;strong&gt;去中心化的人才网络&lt;/strong&gt;：一个由个人用户主导、为个人用户所拥有的人才网络，最终目标是取代传统的招聘平台。但作为一款  &lt;strong&gt;面向“零工经济（gig economy）”的招聘平台，&lt;/strong&gt;从表面上看，Braintrust 和其他求职平台没什么区别，用当下流行语言来讲：  &lt;strong&gt;它是一款 Web2 产品。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;通过引入 DAO 的理念和治理代币方案，Braintrust 试图通过极低的平台抽佣来获得市场竞争优势、挑战 Web2 领域公司对用户的不公平。Braintrust 是更进一步地讲 Web3 的理念融入人们日常生活中的用例，与此同时，他也搭乘上开放网络和加密货币时代的“顺风车”快速发展。&lt;/p&gt; &lt;p&gt;到目前为止，已有超过 3.4 万名自由职业者加入了 Braintrust 生态，包括 Twitter、保时捷、高盛和耐克在内的全球性企业在平台上提供了 1500 个岗位。平台的 85% 的用户拥有自己的数字钱包，2021 年 12 月，Coatue 和 Tiger 以购买代币的形式为 Braintrust  投入了 1 亿美元。&lt;/p&gt; &lt;p&gt;Braintrust 背后并不是一个崭新的产品或商业模式，  &lt;strong&gt;但通过将既有的商业模式和 Web3 精神相融合，Braintrust 或许是将 We3 带入现实世界的一个积极的实践。&lt;/strong&gt;不过，叠加 Web3 的理念并不代表它一定比现有的替代方案更好，在 Braintrust 的发展过程中，仍有大量的挑战需要面对。&lt;/p&gt; &lt;h3&gt;一、Braintrust ：Web3 融入 Web2 的商业实践  &lt;br /&gt;&lt;/h3&gt; &lt;p&gt;Web3 最让人兴奋的地方是它带来了新的商业模式，  &lt;strong&gt;以及移除“中介”后出现的新价值链。&lt;/strong&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://img.huxiucdn.com/article/content/202203/05/200908546681.png?imageView2/2/w/1000/format/png/interlace/1/q/85"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;目前美国有将近四成工作者是自由职业者，其市场规模约 1.5 万亿美元。疫情影响加速后的在线、远程协作，  &lt;strong&gt;让“自由职业”这一趋势更加长期化、固定化、普遍化， 自由职业者的数量和经济规模呈指数级增长。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;当市场上有充足且多元的自由从业者供给时，越来越多的企业开始选择将部分职能外包，从而更加集中地建设核心业务能力。这种工作模式创造了数百万个就业机会，并带动了自由职业者经济的增长，为传统雇佣关系带来了改变。&lt;/p&gt; &lt;p&gt;自由职业者和零工经济拥有巨大市场潜力：已上市的零工招聘平台 Upwork （市值近 30 亿美元）在 2021 年 Q4 实现了 9.04 亿美元的交易额，以色列该板块的龙头公司 Fiverr 的交易额为 2.6 亿美元，市值也达到了 27 亿美元。&lt;/p&gt; &lt;p&gt;Braintrust 正是把握了蓬勃发展的零工经济的趋势，通过平台的形式将世界各地的知识型自由职业者与 Nike、德勤、雀巢、NASA 等顶级公司进行匹配。Braintrust  的收益来自分成，一旦实现成功招聘，企业需要向 Braintrust 支付劳动合同金额的   &lt;strong&gt;10%&lt;/strong&gt; 作为服务费，  &lt;strong&gt;这一费率远低于 Upwork、Fiveer 、Toptal 等平台 30%~40% 的抽佣水平。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://img.huxiucdn.com/article/content/202203/05/200911507464.png?imageView2/2/w/1000/format/png/interlace/1/q/85"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;Braintrust 主页  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;而让 Braintrust 和 Upwork 等其他平台完全区分开来的点在于，  &lt;strong&gt;它并不想成为一个“中介”。&lt;/strong&gt;通过引入“去中心化”的理念和代币体系，Braintrust 试图借助 Web3 的力量让自己持续地保持低抽佣率，从而在市场竞争中获得优势。&lt;/p&gt; &lt;p&gt;Braintrust 开启于 2018 年，平台在 2020 年 6 月正式启动，它的早期投资人包括了 Variant、Multicoin、ACME、Hashkey 、Pantera 等，其中 Multicoin Capital 还投了很多类似的公司比如 Helium、Arweave、Livepeer 皆是用 Web 3 的手段全栈式地解决中心化带来的现实问题。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://img.huxiucdn.com/article/content/202203/05/200913051817.png?imageView2/2/w/1000/format/png/interlace/1/q/85"&gt;&lt;/img&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;2021 年 9 月，Braintrust 通过 Coinlist 众筹 1100 万美元，并发行治理代币 $BTRST、开始以  DAO 的形式进行治理。它的早期投资机构们享有 Braintrust 22% 的代币，另有 5% 的代币被分配给了众筹参与者，其余部分则留给了早期贡献者及社区。&lt;/p&gt; &lt;p&gt;2021 年 12 月，Coatue 和 Tiger 以购买代币的形式投资了 Braintrust  1 亿美元。目前 Braintrust 的代币完全稀释后的市值为 8.96 亿美元。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;Braintrust 专注于工程师、设计师、开发者这三类人才，将科技类知识从业者和顶级公司进行对接，&lt;/strong&gt;因为主打 Web3、去中心化的理念，所以 Braintrust 可以更容易地吸引到这部分人的关注，平台上 85% 的用户拥有自己的数字钱包。&lt;/p&gt; &lt;p&gt;到目前为止，已有超过 34,000 名自由职业者加入了 Braintrust 生态，包括 Twitter、保时捷、高盛和耐克在内的全球顶级企业在平台上提供了超过 1500 个岗位。Braintrust 官方数据看板显示：自 2020 年 6 月上线以来，Braintrust 总共完成了 2373 例工作需求对接，  &lt;strong&gt;平均每个工作项目的合同金额为 7.25 万美元&lt;/strong&gt;（按照：项目周数 x 小时/周 x 小时费计算得出）。  &lt;/p&gt; &lt;p&gt;  &lt;img src="https://img.huxiucdn.com/article/content/202203/05/200916686395.png?imageView2/2/w/1000/format/png/interlace/1/q/85"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;Braintrust Network Dashboard  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;即便有自己的代币，但 Braintrust 始终遵循一个理念：好的代币经济可以放大一个平台的价值，但不能完全替代它。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;所以 Braintrust 并不以代币激励作为吸引用户的理由，更想从以下几方面来吸引知识工作者和企业：&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://img.huxiucdn.com/article/content/202203/05/200917088639.png?imageView2/2/w/1000/format/png/interlace/1/q/85"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;Braintrust  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;同时，基于 DAO 的运营理念，持有平台 token 的 Braintrust 用户还可以参与平台运营的一系列决策投票。对于自由职业者们而言，当他们的收入和平台形成强绑定时，Braintrust 提供了一套机制来确保用户和平台之间的信任不会因一些外部因素而遭受破坏。&lt;/p&gt; &lt;p&gt;Braintrust 并不是一个崭新的产品或商业模式，通过和 Web3 理念进行融合，对交易平台的商业模型进行更先锋的改进。&lt;/p&gt; &lt;p&gt;但从商业实践的角度，它仍然要面对这样一个现实问题：去中心化的、由用户拥有和主导的交易平台能否比 Web2（由投资者所拥有的）交易平台更为高效？&lt;/p&gt; &lt;p&gt;Braintrust 并不能因为自己的 Web3 属性必然成功，  &lt;strong&gt;它同时面临着 Toptal 、Gigster 、传统的人事代理机构，以及 Recruiter.com 等一系列来自 “传统世界”的强劲对手。&lt;/strong&gt;&lt;/p&gt; &lt;h3&gt;二、Web2 交易平台的“原罪”&lt;/h3&gt; &lt;p&gt;“  &lt;strong&gt;Your margin is my opportunity&lt;/strong&gt;（你的利润就是我的机会）。”Braintrust 的创始人 Adam Jackson 借用亚马逊的创始人 Bezos 这一观点来描述 Braintrust 的出发点和长期竞争优势。&lt;/p&gt; &lt;p&gt;作为连续创业者和投资人， Adam Jackson 有丰富的平台型产品创业经验，并在过去 16 年里参与成立了 4 家风险投资公司和 1 家资产管理公司。&lt;/p&gt; &lt;p&gt;2012 年，他联合创建了医疗服务机构 Doctor On Demand，融资 1.65 亿美元。2017 年，他联合创建了区块链资产管理公司 Cambrian Asset Management。Adam 也是 100 多家公司/项目的天使投资人，包括：Eco、Collective、以太坊、Bolt、Placer、Aktana、Skale Labs、Protocol Labs、JCTurbo、Apero Health、Rappive、MyTime、Automatic、Womply、Superhuman、Zenefits……&lt;/p&gt; &lt;p&gt;Braintrust 的联合创始人 Gabriel Luna-Ostaseski 曾创立面向初创企业的咨询公司 Upshift Partners，旨在为硅谷的技术公司提供发展和客户增长方面的咨询服务，其客户包括 humbtack、Uber、Lending Home、Metromile、Massdrop 等等。&lt;/p&gt; &lt;p&gt;Adam Jackson 和 Gabriel Luna-Ostaseski 创立 Braintrust 的背后是他们在 Web2 领域创业投资的深刻洞察。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;过去 10 年间 Web2 领域诞生了一系列影响人们生活的创新公司，人们日常生活基本都围绕着这些平台展开&lt;/strong&gt;，一个典型的生活场景是：&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;一位纽约居民昨晚在 DoorDash 上点了晚餐、前一天晚上打了一辆 Uber 回家，由于下周的出行所以提前在 Airbnb 上订房。&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;即便经历了近期的美股大跳水，它们的总价值仍高达数千亿美元：&lt;/p&gt; &lt;p&gt;  &lt;img src="https://img.huxiucdn.com/article/content/202203/05/200919303762.png?imageView2/2/w/1000/format/png/interlace/1/q/85"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;但这些交易平台的商业模型同样存在挑战： &lt;/p&gt; &lt;blockquote&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;为了形成自己的网络效应，这些平台在其成长早期需要付出大量的成本来完成“获客、增长、留存”的不断循环。在这个过程中，市场竞争的加剧不仅抬升这部分成本，更可能让已经付出的巨大成本都“前功尽弃”，典型如 Uber 与 Lyft 之战。 &lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;当平台的供给无法完全满足需求时，     &lt;strong&gt;平台也许会对供给的质量标准做出牺牲，而对于平台上的用户来说，除了离开平台，他们没有其他选择，&lt;/strong&gt;例如亚马逊从全自营到引入第三方卖家。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt; 从收益模式上，成功的交易平台（marketplace）最终不可避免地走向广告业务，广告几乎是被所有公司验证并采用的业务，但这严重影响了客户体验。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;更重要的是，几乎所有的交易平台一旦意识到平台上的用户/参与者被充分锁定而不会轻易离开时，就会     &lt;strong&gt;提高平台抽佣率&lt;/strong&gt;以增加利润。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt; &lt;p&gt;以上情境几乎会发生在每一个交易平台的发展过程中。&lt;/p&gt; &lt;p&gt;从用户公平以及平台可持续发展这两个角度，Adam 不认为 Web2 平台的高抽佣率收入模式是可持续的。&lt;/p&gt; &lt;p&gt;Web2 交易平台公司（产品）的很大一部分创新在于他们调动了社会化供给、为原有市场引入新的供需关系，市场规模也因为新的供需关系的加入而被抬升到了新量级：Airbnb 通过非酒店类型的住宿供给扩大了度假租赁的市场规模，出行市场的规模因为 Uber 的 “共享汽车”概念而不局限于出租车。&lt;/p&gt; &lt;p&gt;Adam 看来，  &lt;strong&gt;大部分 Web2 平台的创新是对个人资源的货币化，但货币化的红利被平台“侵占”，助力平台成长的个人用户并没有获得自己应得的利益。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;再来看抽佣率攀升这件事。&lt;/p&gt; &lt;p&gt;虽然用户侧的体验很差，从公司管理层角度，提高抽佣率并不是坏事，成功提高平台抽佣率是一个极其重要的积极业务信号。例如，Fiverr 在其最近的股东信中着重标明了其抽佣率相较上一年提升了 140%。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://img.huxiucdn.com/article/content/202203/05/200922774130.png?imageView2/2/w/1000/format/png/interlace/1/q/85"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;Fiverr 2021 年第三季度收益  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;从平台的发展阶段来看，抽佣率的提升往往比业务真正站稳脚跟发生得更早，因为平台运营者们需要更早地向股东证明他们可以获得盈利。同时，在激烈的市场竞争中，平台不得不牺牲部分收益来提供一个较低的（但并不持续）的抽佣水平，所以提升抽拥水平的时间再次被提前。&lt;/p&gt; &lt;p&gt;a16z 的 Chris Dixon 在   &lt;em&gt;Why decentralization matters&lt;/em&gt; 文章中提到，在中心化的交易平台上一定会发生的情形是：&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;一旦平台建立了自己的网络效应，并且他们很确定平台上的各方参与者：用户、服务供给方、甚至开发者都开始对平台产生依赖，那么平台就会选择   &lt;strong&gt;从“吸引这部分人”转向“吸取这部分人群的价值”。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;  &lt;img src="https://img.huxiucdn.com/article/content/202203/05/200923121148.png?imageView2/2/w/1000/format/png/interlace/1/q/85"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;Chris Dixon，  &lt;em&gt;Why decentralization matters  &lt;/em&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;对提高抽佣率的一种正面解释是：提高抽佣率可以证明“交易平台的价值正在增加”。&lt;/p&gt; &lt;p&gt;如果一个平台可以没有任何摩擦地、成功地将自己的抽佣率从 10% 提高到 20% ，那么证明他们的产品对用户来说变得更有价值了。 &lt;/p&gt; &lt;p&gt;但是“没有任何摩擦”是一个十分关键的前置条件，  &lt;strong&gt;一旦平台的抽佣率提高到超过平台用户所认为公平的水平，就会为平台走向衰落埋下种子。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Benchmark 合伙人 Bill Gurley 在  &lt;em&gt;《最佳平台定价策略》&lt;/em&gt;一文中阐述过一个十分经典的观点：平台在设定抽佣率时的短视是十分危险的，短期内提高抽佣率能获得利润增长的正反馈，但如果你想让自己的平台在长期保持优势，那么这一行为在战略上就是次优的，因为提高抽佣率可能会导致供需双方离开平台去寻找替代方案。 &lt;/p&gt; &lt;p&gt;Gurley 的建议是，平台不应该持续提高向用户收取的费用，而应该想办法从那些试图从你那里获得更多用户的人手中赚取更多收益，比如广告。&lt;/p&gt; &lt;p&gt;某种程度上，Braintrust 的 Adam Jackson 和 Gurley 持有同样的观点，不论是从对用户公平的角度，还是平台持续发展的角度，Adam Jackson 都认为依赖抽佣对于平台发展来说是不利的，只不过他选择了 Web3 作为解决问题的工具。&lt;/p&gt; &lt;h3&gt;三、Braintrust 如何运作  &lt;br /&gt;&lt;/h3&gt; &lt;p&gt;对于 Braintrust  平台的企业或个人用户来说，  &lt;strong&gt;是否了解区块链并不影响使用，&lt;/strong&gt;任何人都可以参与到 Braintrust ，无论是职位发布、推荐人才还是投资、投票治理。总的来说，Braintrust 旨在解决传统招聘平台中存在的 3 个主要问题：  &lt;br /&gt;&lt;/p&gt; &lt;blockquote&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;     &lt;strong&gt;更公平的收费结构：&lt;/strong&gt;Toptal、Upwork 和 Gigster 等招聘平台收取 30~60% 的高额费用，高额的抽佣比例增加了企业招聘成本的同时，也减少了自由职业者们的实际收入，这并不公平。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;     &lt;strong&gt;更优质的人才供给：&lt;/strong&gt;Braintrust 专注于高级知识人才，所有用户都经过了点对点审核和 KYC 流程， 对于企业客户来说，Braintrust      &lt;strong&gt;将背调环节前置&lt;/strong&gt;，为他们提供了一个经审查的、优质的“人才库”。而区块链技术显然也能够提供一个系统化的声誉存储和追踪工具，提高招聘质量，这个我们后文会展开。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;     &lt;strong&gt;利益错位：&lt;/strong&gt;中心化的所有权结构造成了平台运营方和实际用户这两方在利益上的脱节，如果要长久地保持优势，就需要改变这种错位。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt; &lt;p&gt;  &lt;img src="https://img.huxiucdn.com/article/content/202203/05/200924943229.png?imageView2/2/w/1000/format/png/interlace/1/q/85"&gt;&lt;/img&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;人才（talent）、雇主（Employer）和连接者（Connector）  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;除了人才（Talent）和雇主（Employer）这两类人群，Braintrust 还引入了“连接者（Connector）”的角色，Braintrust 由这三个特定的利益相关者组成。每一位想要注册的用户都需要先通过平台的 KYC（用户身份审查），KYC 通过之后才可以成为平台成员。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://img.huxiucdn.com/article/content/202203/05/200926805323.png?imageView2/2/w/1000/format/png/interlace/1/q/85"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;从招聘业务链条上，连接者（Connector）可以看作是入驻平台的第三方猎头，可以是个人和组织，理论上，人才或雇主也可以同时担任连接者（Connector）的角色。&lt;/p&gt; &lt;p&gt;雇主发布的职位信息除了显示在潜在人才（Talent）的信息流，也会被连接者（Connector）们看到，当连接者通过定向推荐实现成功人岗匹配后，Braintrust 会以合同金额为基准，奖励给连接者们一定数量的平台代币 $BTRST。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;“连接者”角色的引入维持了 Braintrust 平台的动态流动和增长：他们不断邀请新客户和知识工作者加入 Braintrust、协助招聘和匹配工作。&lt;/strong&gt;“连接者”也可以看作是 Braintrust 的一种 Go-To-Market  策略，其他平台所使用的“用现金换增长”在 Braintrust 这里变成了代币方案，这是 Braintrust 可以长期保持 10% 低抽佣比例的重要原因。&lt;/p&gt; &lt;p&gt;企业雇主和人才之间的对接则遵循 Bid-Ask 流程。“Ask” 即企业雇主， 企业方提出他们需要的人才标准，比如技能、经验、地理位置、薪资等。合约自动将这些条件和平台上自由职业者的 “Bid” 去匹配，整个匹配过程全部由协议自动执行。&lt;/p&gt; &lt;p&gt;Braintrust 还允许雇主和人才双方在完成项目后相互评分，获得 5 星评分后即可得到代币奖励，除了作为收益，  &lt;strong&gt;也可以看成是一种声誉认证在链上保存。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;接下来，Brainturst 还将系统化地搭建自己的链上声誉系统。Web2 语境下，个人的工作历史、职业相关数据无法实现平台间互通，例如 Toptal 的资料无法在 Fiverr 中查询到，系统间的封闭让自由职业者无法拥有完整的声誉认证，但这些都可以通过区块链技术得到解决。&lt;/p&gt; &lt;p&gt;和竞争对手相比，Braintrust 除了更低的抽佣之外，还足够  &lt;strong&gt;高效&lt;/strong&gt;：根据平台公开的信息，Braintrust 的匹配算法保持了   &lt;strong&gt;80% 的匹配率&lt;/strong&gt;，可以在职位发布后的前 24 小时内将客户（公司）连接到 4 到 7 名自由职业者，整个匹配过程平均用时 48 小时，作为对比，该雇主在其他平台上花费的时间是 2 周左右。&lt;/p&gt; &lt;p&gt;人才和企业之间的服务结算以当地法币进行，以避免 Token 支付的价格波动而影响收入。&lt;/p&gt; &lt;p&gt;早期，企业方以现金的方式支付给 Braintrust 合同金额的 10% 服务费用，这 10% 的现金作为“劳务费用”支付给协助搭建 Braintrust 的各个节点。在最近一次的社区治理投票后改为企业方先行给 Braintrust 的费用节点支付对应的法币，由节点负责从法币转换为 USDC，再转至 Braintrust 在公开市场上回购 $BRST 代币，从而保证基金库平衡。&lt;/p&gt; &lt;p&gt;$BTRST 和 Braintrust 的 DAO 化治理  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;除了商业理念，Braintrust 自身的创立过程也让它本身更具社会实验的意味。现实世界中，并不存在一个名为 Braintrust Inc 的公司实体，整个 Braintrust 项目的构建主要由五个节点来完成：&lt;/p&gt; &lt;blockquote&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;Freelance Labs&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;HexOcean&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;SnowFork&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;Accelerated Labs&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;Muses&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt; &lt;p&gt;上述几个组织帮助 Braintrust 完成了从协议、代币模型设计，到早期营销、处理付款及货币兑换等功能的搭建，还包括了与 Nike、NASA 和美国宇航局等公司建立合作所需的、从 BD 到合规材料的所有准备流程。作为 Braintrust 的早期贡献者，这些节点组织享有 19% 的代币。&lt;/p&gt; &lt;p&gt;Braintrust 共发行了 2.5 亿个 $BTRST 代币，其中 54% 被作为社区激励来维持 Braintrust  DAO 的运转。  &lt;strong&gt;$BTRST 不用于支付，主要用于 Braintrust 的治理和奖励。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://img.huxiucdn.com/article/content/202203/05/200927878300.png?imageView2/2/w/1000/format/png/interlace/1/q/85"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;1. 治理&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;$BTRST 是 Braintrust 的治理代币，持有代币的人在 Vote 页面详细阐述提案，社区成员在 snapshot 上进行投票后，投票结果会被反馈到链上。社区成员可以针对 Braintrust 的路线图、费用方案、声誉和代币机制等进行投票。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;2. 解决争议&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Braintrust 上  &lt;strong&gt;劳动报酬的支付不在区块链上完成，&lt;/strong&gt;平台暂时也不支持任何资金托管服务，所以对于存在劳动纠纷风险，Braintrust 并不能从冻结资金的维度进行争议协调，但作为平台，在客户和人才发生冲突时展现平台立场又是必要的。&lt;/p&gt; &lt;p&gt;因为 Braintrust 的争议解决过程发生在链下（例如司法仲裁等），所以 $BTRST 代币治理机制是间接地在解决社区运营的争议。决议陪审团的参与者要么需要持有 BTRST 代币，要么需要被 BTRST 持有者评选为陪审团仲裁员。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;3. 激励人才网络&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;$BTRST 还被用于平台上利益相关者的激励，主要有几种主要类型的贡献者可以获得社区奖励： &lt;/p&gt; &lt;blockquote&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;     &lt;strong&gt;连接者（Connector）：&lt;/strong&gt;将人才或客户推荐给网络的人。任何人都可以成为连接者并推荐人才。     &lt;strong&gt;对于连接者的奖励是现阶段 Braintrust 代币的最主要支出。&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;     &lt;strong&gt;审核者：&lt;/strong&gt;负责平台 KYC 的成员，他们筛选、审核平台人才库，保证提交认证的申请人符合 Braintrust 及对应公司标准，如果要成为审核者必须先完成 Braintrust Academy 的课程，并为此获得代币，然后在完成筛选后获得 $BTRST 代币。 &lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;     &lt;strong&gt; 人才（Talent）：&lt;/strong&gt;从注册开始，人才就会在不同节点收到平台的代币奖励，例如补充完整的个人资料、参加平台课程、成功完成工作等。 &lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;     &lt;strong&gt;项目捐赠：&lt;/strong&gt;考虑到平台上的贡献者越来越多元化，Braintrust 推出了自己的激励计划， 主要分为三类：宣传大使奖励、开发者奖励以及内容建设类奖励。来自 Coatue 和 Tiger 的 1 亿美元也被用于这一计划。 &lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt; &lt;h3&gt;四、代币经济的风险与升级  &lt;br /&gt;&lt;/h3&gt; &lt;p&gt;通胀风险  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;白皮书中的这张插图很好地展示了平台上一个完整的照片流程以及 $BTRST 代币（图片中徽标的位置）是如何发挥作用的：&lt;/p&gt; &lt;p&gt;  &lt;img src="https://img.huxiucdn.com/article/content/202203/05/200928974917.png?imageView2/2/w/1000/format/png/interlace/1/q/85"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;Braintrust 白皮书  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;上图中标注“$”符号的位置对应的都是企业和人才的互动，就像他们在其他交易平台上一样，用工作换取现金。标注有 Braintrust 代币标志的地方则是 Braintrust 的代币经济和平台规则设定的结合：&lt;/p&gt; &lt;blockquote&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;连接者（Connector）奖励，正如前面提到的，Brainstrust 用平台代币来支付自己的获客成本；&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;和其他平台相比，在 Brainturst 平台，人才（Talent ）     &lt;strong&gt;不仅不用支付中介费用，还有可能在劳动报酬之外获得额外收益：&lt;/strong&gt;当他们成功申请到工作岗位、并在工作结束后获得雇主的五星好评时，都可以获得平台的代币奖励。除了作为他们参与社区治理的凭证，$BTRST 的升值也为他们带来了潜在经济收益。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt; &lt;p&gt;这里有一个微妙的平衡。&lt;/p&gt; &lt;p&gt;平台上自由职业者和企业客户的快速增长增强了 Braintrust 作为平台的网络效应，但当平台上人才、岗位短时间内大量增加、流通时，市场上流通的 $BTRST 数量也随之增加，随之而来的自然是 $BTRST 代币的通货膨胀：&lt;/p&gt; &lt;blockquote&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;如果只是作为平台活动的参与者（自由职业者、雇主）而持有 $BTRST 代币，那么通货膨胀对币价的稀释并不会对他们造成太多影响；&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;如果以金融投资、经济收益的视角来看待通货膨胀，则通货膨胀对于代币持有人的影响是巨大的。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt; &lt;p&gt;上述两点不会过多地影响求职者和企业，因为 $BTRST 升值带来的经济收益并不是他们来到 Braintrust 的最核心目的，  &lt;strong&gt;但对于连接者，$BTRST 的价值稀释一定会影响他们的积极推动 Braintrust 运转、增长的动力，&lt;/strong&gt;极端情况下，连接者（Connector）会因为看不到太多的经济收益而选择离开 Brainturst。&lt;/p&gt; &lt;p&gt;考虑到接下来早期投资机构锁定期结束后可能的抛售行为，$BTRST 贬值的风险似乎在被放大。即便 Braintrust 为 $BTRST 设定了一个 2.5 亿个代币数量的上限来规避通货膨胀，  &lt;strong&gt;但短期内则十分考验金融投资者的心态。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;理想状态下，作为社区治理凭证的代币，这一机制能够保证大部分 $BTRST 可以真正为需要 Braintrust 平台的人所享有。但考虑到 Braintrust 能够维持 10% 现金抽佣率关键原因在于它用代币来替代现金的获客成本，用 $BTRST 的增益来驱动平台增长，所以不考虑 $BTRST  为其持有者带来的财务价值是不现实的。&lt;/p&gt; &lt;p&gt;$BTRST 的持有人们可能希望这些代币随着时间的推移具有直接的财务价值，但在此期间，它们对网络参与者具有间接的财务价值，主要体现在以下三个方面： &lt;/p&gt; &lt;blockquote&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;p&gt;     &lt;strong&gt;社区治理。&lt;/strong&gt;在 Braintrust 平台上，治理参与可以带来财务价值，因为它可能意味着 Braintrust 可以修改自己的抽拥比例，要求企业支付更高的费用，这部分“利润”为社区成员所享有。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;     &lt;strong&gt;应聘质押。&lt;/strong&gt;客户和人才可以通过质押 $BTRST 来证明自己的参与意愿，从而在匹配过程中脱颖而出：例如，如果某位申请人通过质押 2 $BTRST 来获得优先面试 Nike 公司设计岗位的资格，但如果他没有参加面试，则代币会被没收。某种程度上，     &lt;strong&gt;这是 Web2 平台所采用的广告模型的 Web3 替代品。&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;     &lt;strong&gt;职业福利。&lt;/strong&gt;这一项内容暂时还没落实，但 Braintrust 网站已经暗示了代币持有者在未来可以享受到的利益：免费的职业教育内容、软件或求职辅导等。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/blockquote&gt; &lt;p&gt;举一个更直观的简单例子：&lt;/p&gt; &lt;blockquote&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;对于纯粹出于财务目的持有 $BTRST 投资人，一个 $BTRST 代币价值 4 美元，则他所得到的实际价值也是 4 美元； &lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;但对于 Braintrust 平台上的求职者，如果通过质押 1 个$BTRST 可以让他获得一份 7.2 万美元的工作可能性增加 0.01%，那么对他而言，     &lt;strong&gt;此时 1 个 $BTRST 的实际价值为 4 美元 + 7.20 美元，即 11.20 美元。  &lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt; &lt;p&gt;这里并没有考虑到如果求职者没有得到这份工作的情况，只是对总体逻辑的举例。&lt;/p&gt; &lt;p&gt;此外，  &lt;strong&gt;如果代币经济设计得足够好，也能够激励纯粹出于金融投资目的持有代币的人积极参与到网络构建中。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;例如，我持有 $BTRST 代币，但如果我不为 Braintrust 贡献新的人才或职位供给，那么我实际上在损害 $BTRST 的价值，此时的选择是：我是否应该将人才推荐给网络，为他们竞标以帮助他们建立声誉，从而在上述基础上，享受到  $BTRST 带来的收益？答案自然是肯定的。&lt;/p&gt; &lt;p&gt;价值累积和协议升级  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;因为平台上代币和现金流动混合在一起，所以 Braintrust 的代币激励机制经历过一次升级。&lt;/p&gt; &lt;p&gt;前面提到，Braintrust 是由几个节点团队共同搭建而成的。直到去年 9 月，Braintrust 才发布了自己的代币，但在那之前，Braintrust 又必须为这些节点的工作付费，所以在一开始，雇主企业给 Braintrust 的 10% 的服务费用会直接流向这些节点组织，作为他们的服务费用。&lt;/p&gt; &lt;p&gt;但随之而来的问题是，Braintrust 给社区的激励是来自基金库的单向支出，当基金库得不到供给时，激励也是不可持续的。如下图所示：&lt;/p&gt; &lt;p&gt;  &lt;img src="https://img.huxiucdn.com/article/content/202203/05/200931417159.png?imageView2/2/w/1000/format/png/interlace/1/q/85"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;支付的费用和代币之间没有联系  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;9 月，当 Braintrust 公开发行其代币时，它去中心化并成为了 DAO，平台将治理权正式交到代币持有者手中。10 月，$BTRST 的持有者们投票批准了 Braintrust 代币经济模型的重大变更。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;具体来说，  &lt;strong&gt;“平台上的所有费用，包括当前的客户成功费用以及未来可能发起的任何其他费用，都必须以 $BTRST 支付，而不是以现金形式支付给节点。”&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://img.huxiucdn.com/article/content/202203/05/200939929459.png?imageView2/2/w/1000/format/png/interlace/1/q/85"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;Braintrust 的快照投票  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;这并不代表企业用户必须持有 $BTRST 才能完成 10% 服务费用的支付。&lt;/p&gt; &lt;p&gt;在实际流程中，10% 的现金会先给负责接受法币的节点（Fee Converter），该节点负责将这部分现金转换为稳定币 USDC，并将它们发送到费用转换器的智能合约中，合约则会将这部分直接从交易所用这部分 USDC 来购买 $BTRST 代币，并将它们最终发送到 Braintrust DAO，以维持代币的循环。 &lt;/p&gt; &lt;p&gt;  &lt;strong&gt;这种协议升级不会不断消耗库存，而是会在每次获得 10% 的抽佣时完成基金库的自我维持。&lt;/strong&gt;节点们也仍然会从 10% 的费用中获得报酬，但因为他们前期开发 Braintrust 的付出已经得到了充足的现金回报，所以接下来，他们会完全以一个贡献者的角色投入他们未来的工作，并像其他人一样获得 $BTRST 代币奖励。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://img.huxiucdn.com/article/content/202203/05/200931417159.png?imageView2/2/w/1000/format/png/interlace/1/q/85"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;一个更加自给自足的生态系统  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;协议升级后，Braintrust 社区已经在公开市场上购买了 14.5 万个 $BTRST 代币。 &lt;/p&gt; &lt;p&gt;  &lt;img src="https://img.huxiucdn.com/article/content/202203/05/200942988259.png?imageView2/2/w/1000/format/png/interlace/1/q/85"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;Braintrust Dashboard  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;作为一个 DAO，  &lt;strong&gt;Braintrust 的数据也是完全公开的。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://img.huxiucdn.com/article/content/202203/05/200945246555.png?imageView2/2/w/1000/format/png/interlace/1/q/85"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;btrstinfo.xyz 费用查看器  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;btrstinfo.xyz 费用查看器是最典型的代表，  &lt;strong&gt;任何人都可以通过这一工具来查看平台的客户支付了多少钱、何时以及为什么工作，&lt;/strong&gt;即时 Braintrust 平台上的工作详情页，也可以是 Braintrust 的收入明细。接下来 Braintrust  还将推出一个 Twitter 机器人，它会在推特上发布诸如“Nike 刚刚支付了 1000 美元的发票，并购买质押了300 个 BTRST 代币”之类的推文。&lt;/p&gt; &lt;h3&gt;五、如何获胜？  &lt;br /&gt;&lt;/h3&gt; &lt;p&gt;Braintrust 的理念其实很简单：  &lt;strong&gt;抽佣最少的双边交易平台一定会在竞争中更具优势，而去中心化的、由用户完全拥有的平台更有可能抽佣较少。   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;需要再次强调的是，这并不意味着具备 Web3 要素和精神的交易平台们一定会在竞争中获胜，  &lt;strong&gt;它们仍然需要提供出色的产品并完成运营市场所需的艰巨任务。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;在一档播客节目中，Braintrust 的创始人 Adam 提到，一个交易平台收取的合理抽佣费率和它是否提供了独特的价值相关，一旦一家公司没有做到这一点，那它将很快被用户抛弃。Adam 举了 DoorDash 的案例，在他看来 DoorDash 日渐攀升的抽佣本质上是在“偷”配送人员应得的小费，所以这类公司将第一个“离开”。&lt;/p&gt; &lt;p&gt;不过这一论点有待商榷，  &lt;strong&gt;因为这句话背后并没有考虑到产品复杂性以及平均订单价值&lt;/strong&gt;（从绝对金额上 DoorDash 的抽拥并不多），这也是为什么不太可能存在一个去中心化的 Uber 或 DoorDash。&lt;/p&gt; &lt;p&gt;另一方面，Adam 认为 Airbnb 真实地为市场供给双方带来了巨大价值，并且它作为平台方抽佣比例合理，所以如果做一家 Web3 领域的 Airbnb，那么它要面临的竞争对手是十分强大的。&lt;/p&gt; &lt;p&gt;虽然分属于 Web 3 和 Web 2，但 Braintrust 正在做出与 Airbnb 相同的选择：抽佣最少的网络可以巩固它们运营的空间，并且在长期具有价值。很明显，  &lt;strong&gt;这种理念并不一定意味着需要代币的参与，&lt;/strong&gt;更多是创始人的主动选择。&lt;/p&gt; &lt;p&gt;所以回到最初的问题：由用户所拥有的去中心化的平台，能创造和释放更多价值？&lt;/p&gt; &lt;p&gt;根据 Gurley、Bezos 的观点，极具竞争力的交易平台一定是可以将低抽佣率维持得越久越好。Braintrust 已经做到了低抽拥率，它接下来要做的就是持续保持这一优势。随着自由职业者更容易通过 Braintrust 平台找到优质的、可持续的工作，那么 Braintrust 就会获得不断增长的正循环。&lt;/p&gt; &lt;p&gt;$BTRST 的代币提供了所有权和经济效用。那么对于平台参与者来说，他们需要明确“所有权”价值到底是多少？就目前而言，我们仍在谈论一个只发展了 18 个月的平台，它目前的总服务量为 3700 万美元，并且只服务于工程师、设计师和开发者这些细分领域里的自由职业者。&lt;/p&gt; &lt;p&gt;一个极端的想象是：  &lt;strong&gt;Braintrust 是否有可能覆盖所有人才招聘的需求？整合更多经济活动？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;根据哈佛商业评论的数据，2019 年美国人才招聘行业的规模为 1510 亿美元。因此，可以简单预测全球招聘行业的规模约为 4500 亿美元。全球范围内第三方知识服务机构的收入情况是：德勤在 2021 年的收入为 501 亿美元，TCS 为 220 亿美元。简单加总后，我们可以粗略估计全球知识工作者的工资规模为 6000 亿美元左右。&lt;/p&gt; &lt;p&gt;Braintrust 的抽拥比例为 10%，所以理论上，如果 Braintrust 能够打造一台机器，随着时间的推移巩固全球灵活的人才市场，那么它将有机会获得 600 亿美元/年的费用。总而言之，以上的预测是一个极端推论，并且在最乐观的情况下，如果要实现这一预测还有很长的路要走，但在接下来的几个月里，可以预见，Braintrust 覆盖的领域将从开发者、工程师、设计师这些行业向外延展。&lt;/p&gt; &lt;p&gt;切入德勤、普华永道和埃森哲等巨头所处的高端知识服务市场是一个选择，借助附加服务获得溢价，这些咨询公司通常可以做到 70% 的抽佣率。最近一位 IT 咨询公司出身的人正在研究一项建立新节点的提案，本质上是一个建立在 Braintrust 协议上的新创业公司，这一项目一旦通过就会获得上面的赠款项目的资助。&lt;/p&gt; &lt;p&gt;在时机成熟时，Braintrust 的框架也可以支持它和 Fiverr、Upwork 等蓝领类零工平台进行对抗，毕竟对于 Fiverr、Upwork 等平台上的人才而言，“获得更高的到手收入”这件事同样具有强吸引力。&lt;/p&gt; &lt;p&gt;在一定规模下，提供自由职业人才群体需要的服务和资源也具有价值，例如团体健康保险或退休金计划。 &lt;/p&gt; &lt;p&gt;无论哪种情况，  &lt;strong&gt;新节点都将受益于 Braintrust 网络在过去四年中建立的基础设施、社区和资源。&lt;/strong&gt;他们不需要再单独筹集资金来重新实现代币模型设计、协议设计和智能合约审计等，因为这些已经完成了。他们将利用现有的连接器和验证器池，也不再需要融资，因为他们能够从 DAO 的基金库中直接获得启动资金。 &lt;/p&gt; &lt;p&gt;在真正和 Web2 的巨头们交锋之前，更现实的挑战已经出现：Braintrust 的代币在交易所推出后峰值一度达到 40 美元以上但又迅速下跌、观看 Braintrust Academy 视频以获取 $BTRST 代币的机制遭到了机器人大军“薅羊毛”。  &lt;strong&gt;随着 Braintrust 的增长，一个比现在体量更大、更分散的社区也为 DAO 的治理提出挑战。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;Reference&lt;/p&gt; &lt;p&gt;Can crypto solve the problems of the freelance tech worker job market?&lt;/p&gt; &lt;p&gt;Messari - Bitcoin &amp;amp; crypto price， news， charts， and research&lt;/p&gt; &lt;p&gt;https://blockcast.cc/news/hashkey-interview-with-braintrust-exploring-decentralized-talent-market-opportunities/&lt;/p&gt; &lt;p&gt;本文来自微信公众号：  &lt;a href="https://mp.weixin.qq.com/s/EyxFSFv33BvlhD2lRWMp7Q" rel="nofollow" target="_blank"&gt;海外独角兽（ID：unicornobserver）&lt;/a&gt;，作者：Packy McCormick，编辑：Siqi&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62147-%E5%8E%BB%E4%B8%AD%E5%BF%83%E5%8C%96-%E6%8B%9B%E8%81%98-%E5%B9%B3%E5%8F%B0</guid>
      <pubDate>Sat, 05 Mar 2022 21:23:00 CST</pubDate>
    </item>
    <item>
      <title>openresty+lua实现WAF应用防火墙</title>
      <link>https://itindex.net/detail/62126-openresty-lua-waf</link>
      <description>&lt;h1&gt;1.#基础包安装&lt;/h1&gt;



 &lt;p&gt;yum -y install readline-devel pcre-devel zlib zlib-devel gcc&lt;/p&gt;



 &lt;h1&gt;2.升级openssl&lt;/h1&gt;



 &lt;p&gt;#yum -y openssl-devel&lt;/p&gt;



 &lt;p&gt;openssl version  &lt;br /&gt;OpenSSL 1.0.1e-fips 11 Feb 2013&lt;/p&gt;



 &lt;p&gt;wget –no-check-certificate https://www.openssl.org/source/openssl-1.1.1l.tar.gz  &lt;br /&gt;tar -zxvf openssl-1.1.1l.tar.gz  &lt;br /&gt;cd openssl-1.1.1l  &lt;br /&gt;./config shared zlib  &lt;br /&gt;make &amp;amp;&amp;amp; make install&lt;/p&gt;



 &lt;p&gt;#ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key&lt;/p&gt;



 &lt;p&gt;备份当前Openssl  &lt;br /&gt;mv /usr/bin/openssl /usr/bin/openssl.old  &lt;br /&gt;mv /usr/lib64/openssl /usr/lib64/openssl.old&lt;/p&gt;



 &lt;p&gt;使用新版Openssl  &lt;br /&gt;ln -s /usr/local/bin/openssl /usr/bin/openssl  &lt;br /&gt;ln -s /usr/local/include/openssl/ /usr/include/openssl  &lt;br /&gt;ln -s /usr/local/lib64/libssl.so.1.1 /usr/lib64/libssl.so.1.1  &lt;br /&gt;ln -s /usr/local/lib64/libcrypto.so.1.1 /usr/lib64/libcrypto.so.1.1&lt;/p&gt;



 &lt;p&gt;更新动态链接库数据  &lt;br /&gt;echo “/usr/local/lib/” &amp;gt;&amp;gt; /etc/ld.so.conf  &lt;br /&gt;ldconfig&lt;/p&gt;



 &lt;p&gt;openssl version  &lt;br /&gt;openssl: error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directory  &lt;br /&gt;ln -s /usr/local/lib64/libssl.so.1.1 /usr/lib64/libssl.so.1.1  &lt;br /&gt;ln -s /usr/local/lib64/libcrypto.so.1.1 /usr/lib64/libcrypto.so.1.1&lt;/p&gt;



 &lt;p&gt;openssl version  &lt;br /&gt;OpenSSL 1.1.1l 24 Aug 2021&lt;/p&gt;



 &lt;h1&gt;3.安装pcre&lt;/h1&gt;



 &lt;p&gt;pcre没找到,编辑时加上–with-pcre=../pcre-8.30 \  &lt;br /&gt;0.10/src/ngx_stream_lua_regex.c:205: undefined reference to `pcre_jit_stack_alloc’  &lt;br /&gt;collect2: ld returned 1 exit status&lt;/p&gt;



 &lt;p&gt;#wget -nv http://downloads.sourceforge.net/project/pcre/pcre/8.30/pcre-8.30.tar.gz -O pcre-8.30.tar.gz&lt;/p&gt;



 &lt;p&gt;wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.30.tar.gz  &lt;br /&gt;tar xzvf pcre-8.30.tar.gz  &lt;br /&gt;cd pcre-8.30  &lt;br /&gt;./configure –enable-utf8 –enable-unicode-properties  &lt;br /&gt;make  &lt;br /&gt;make install  &lt;br /&gt;ln -s /lib64/libpcre.so.0.0.1 /lib64/libpcre.so.1&lt;/p&gt;



 &lt;h1&gt;4.下载ngx_cache_purge清缓组件&lt;/h1&gt;



 &lt;p&gt;wget http://labs.frickle.com/files/ngx_cache_purge-2.3.tar.gz  &lt;br /&gt;tar zxvf ngx_cache_purge-2.3.tar.gz&lt;/p&gt;



 &lt;h1&gt;5.安装openresty&lt;/h1&gt;



 &lt;p&gt;wget https://openresty.org/download/openresty-1.19.9.1.tar.gz  &lt;br /&gt;tar zxvf openresty-1.19.9.1.tar.gz  &lt;br /&gt;cd openresty-1.19.9.1&lt;/p&gt;



 &lt;p&gt;伪装openresty为xcdn  &lt;br /&gt;sed -i ‘s/”openresty/”xcdn/g’ bundle/nginx-1.19.9/src/core/nginx.h  &lt;br /&gt;sed -i “s#Server: openresty#Server: xcdn#” bundle/nginx-1.19.9/src/http/ngx_http_header_filter_module.c  &lt;br /&gt;sed -i “s#\”&lt;/p&gt;



 &lt;hr&gt;&lt;/hr&gt;



 &lt;p&gt;openresty&amp;lt;\/center&amp;gt;\”#\”&lt;/p&gt;



 &lt;hr&gt;&lt;/hr&gt;



 &lt;p&gt;xcdn&amp;lt;\/center&amp;gt;\”#” bundle/nginx-1.19.9/src//http/ngx_http_special_response.c&lt;/p&gt;



 &lt;p&gt;./configure –user=www –group=website –prefix=/opt/openresty-1.19.9.1 \  &lt;br /&gt;–with-http_ssl_module –with-http_v2_module –with-http_realip_module –with-http_addition_module \  &lt;br /&gt;–with-http_geoip_module \  &lt;br /&gt;–with-http_gzip_static_module \  &lt;br /&gt;–with-http_auth_request_module \  &lt;br /&gt;–with-http_secure_link_module \  &lt;br /&gt;–with-http_degradation_module \  &lt;br /&gt;–with-http_stub_status_module \  &lt;br /&gt;–add-module=../ngx_cache_purge-2.3 \  &lt;br /&gt;–with-pcre=../pcre-8.30 \  &lt;br /&gt;–with-cc-opt=”-I /usr/local/include/openssl/ ” \  &lt;br /&gt;–with-ld-opt=”-L/usr/local/lib64″&lt;/p&gt;



 &lt;p&gt;gmake  &lt;br /&gt;gmake install&lt;/p&gt;



 &lt;p&gt;复制原配置文件  &lt;br /&gt;cd /opt/nginx/conf/  &lt;br /&gt;cp -ar ssl  webip.conf geo.*.conf GeoIP.dat manageip.conf fcgi.conf htpasswd nginx.conf /opt/openresty-1.19.9.1/nginx/conf/&lt;/p&gt;



 &lt;h1&gt;4.下载和配置 ngx_lua_waf&lt;/h1&gt;



 &lt;p&gt;nginx下常见的开源 waf 有 mod_security、naxsi、ngx_lua_waf 这三个，ngx_lua_waf 性能高和易用性强，基本上零配置，而且常见的攻击类型都能防御，是比较省心的选择。&lt;/p&gt;



 &lt;p&gt;其git 地址为 https://github.com/loveshell/ngx_lua_waf  &lt;br /&gt;  &lt;code&gt;wget --no-check-certificate https://github.com/loveshell/ngx_lua_waf/archive/master.zip   &lt;br /&gt;unzip master.zip   &lt;br /&gt;mv ngx_lua_waf-master /opt/openresty-1.19.9.1/nginx/conf/waf   &lt;br /&gt;chown -R www:website /opt/openresty-1.19.9.1/nginx/conf   &lt;br /&gt;chown -R www:website /opt/openresty-1.19.9.1/nginx/logs   &lt;br /&gt;chmod 775 /opt/openresty-1.19.9.1/nginx/conf   &lt;br /&gt;chmod 775 /opt/openresty-1.19.9.1/nginx/conf/waf   &lt;br /&gt;chmod 775 /opt/openresty-1.19.9.1/nginx/conf/waf/wafconf   &lt;br /&gt;chmod 664 /opt/openresty-1.19.9.1/nginx/conf/   &lt;em&gt;.&lt;/em&gt;&lt;/code&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;code&gt;mkdir -p /opt/openresty-1.19.9.1/nginx/logs/hack&lt;/code&gt;  &lt;br /&gt;  &lt;code&gt;chown www:website /opt/openresty-1.19.9.1/nginx/logs/hack   &lt;br /&gt;chmod -R 775 /opt/openresty-1.19.9.1/nginx/logs/hack&lt;/code&gt;&lt;/p&gt;



 &lt;p&gt;测试配置文件  &lt;br /&gt;/opt/openresty-1.19.9.1/nginx/sbin/nginx -t&lt;/p&gt;



 &lt;p&gt;注意和tengine的不兼容配置  &lt;br /&gt;注释掉server_info和#server_tag  &lt;br /&gt;[emerg] unknown directive “server_info” in /opt/openresty-1.19.9.1/nginx/conf/nginx.conf:49&lt;/p&gt;



 &lt;p&gt;  &lt;code&gt;#server_info off;&lt;/code&gt;&lt;/p&gt;



 &lt;p&gt;  &lt;code&gt;#server_tag off;&lt;/code&gt;&lt;/p&gt;



 &lt;p&gt;在http段增加清除server头  &lt;br /&gt;  &lt;code&gt;more_clear_headers &amp;quot;Server&amp;quot;;&lt;/code&gt;&lt;/p&gt;



 &lt;p&gt;去除dso段&lt;/p&gt;



 &lt;p&gt;limit_req_zone 不支持多个key  &lt;br /&gt;ginx: [emerg] invalid number of arguments in “limit_req_zone” directive in /opt/openresty-1.19.9.1/nginx/conf/nginx.conf:90&lt;/p&gt;



 &lt;p&gt;  &lt;code&gt;#limit_req_zone $binary_remote_addr $uri zone=two:30m rate=20r/s;&lt;/code&gt;&lt;/p&gt;



 &lt;h1&gt;5.配置ngx_lua_waf&lt;/h1&gt;



 &lt;p&gt;  &lt;a href="https://github.com/loveshell/ngx_lua_waf&amp;#22312;nginx.conf&amp;#30340;http&amp;#27573;&amp;#28155;&amp;#21152;"&gt;https://github.com/loveshell/ngx_lua_waf在nginx.conf的http段添加&lt;/a&gt;&lt;/p&gt;



 &lt;pre&gt;  &lt;code&gt;lua_package_path &amp;quot;/opt/openresty-1.19.9.1/lualib/?.lua;/opt/nginx/conf/waf/?.lua&amp;quot;;
lua_shared_dict limit 10m;
init_by_lua_file  /opt/openresty-1.19.9.1/nginx/conf/waf/init.lua; 
access_by_lua_file /opt/openresty-1.19.9.1/nginx/conf/waf/waf.lua;&lt;/code&gt;&lt;/pre&gt;



 &lt;p&gt;配置config.lua里的waf规则目录(一般在waf/conf/目录下)&lt;/p&gt;



 &lt;pre&gt;  &lt;code&gt;RulePath = &amp;quot;/opt/nginx/conf/waf/wafconf/&amp;quot;&lt;/code&gt;&lt;/pre&gt;



 &lt;p&gt;然后重启nginx即可  &lt;br /&gt;部署完毕可以尝试如下命令：&lt;/p&gt;



 &lt;p&gt;curl http://xxxx/test.php?id=../etc/passwd  &lt;br /&gt;日志文件名称格式如下:虚拟主机名_sec.log&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;配置时一些错误&lt;/strong&gt;  &lt;br /&gt;nginx: [alert] failed to load the ‘resty.core’ module (https://github.com/openresty/lua-resty-core); ensure you are using an OpenResty release from https://openresty.org/en/download.html (reason: module ‘resty.core’ not found:  &lt;br /&gt;no field package.preload[‘resty.core’]  &lt;br /&gt;no file ‘/opt/nginx/conf/waf/resty/core.lua’  &lt;br /&gt;no file ‘/opt/openresty-1.19.9.1/lualib/resty/resty/core.lua’  &lt;br /&gt;no file ‘/opt/openresty-1.19.9.1/site/lualib/resty/core.so’  &lt;br /&gt;no file ‘/opt/openresty-1.19.9.1/lualib/resty/core.so’  &lt;br /&gt;no file ‘./resty/core.so’  &lt;br /&gt;no file ‘/usr/local/lib/lua/5.1/resty/core.so’  &lt;br /&gt;no file ‘/opt/openresty-1.19.9.1/luajit/lib/lua/5.1/resty/core.so’  &lt;br /&gt;no file ‘/usr/local/lib/lua/5.1/loadall.so’  &lt;br /&gt;no file ‘/opt/openresty-1.19.9.1/site/lualib/resty.so’  &lt;br /&gt;no file ‘/opt/openresty-1.19.9.1/lualib/resty.so’  &lt;br /&gt;no file ‘./resty.so’  &lt;br /&gt;no file ‘/usr/local/lib/lua/5.1/resty.so’  &lt;br /&gt;no file ‘/opt/openresty-1.19.9.1/luajit/lib/lua/5.1/resty.so’  &lt;br /&gt;no file ‘/usr/local/lib/lua/5.1/loadall.so’) in /opt/openresty-1.19.9.1/nginx/conf/nginx.conf:214&lt;/p&gt;



 &lt;p&gt;将/opt/openresty-1.19.9.1/lualib/?.lua; 加进lua_package_path  &lt;br /&gt;lua_package_path “/opt/openresty-1.19.9.1/lualib/?.lua;/opt/nginx/conf/waf/?.lua”;&lt;/p&gt;



 &lt;p&gt;user-agent中去除|bench,允许NetworkBench访问  &lt;br /&gt;“Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.77.34.5 Safari/537.36; NetworkBench/79.0.3917.0-8710402-0” “(HTTrack|harvest|audit|dirbuster|pangolin|nmap|sqln|-scan|hydra|Parser|libwww|BBBike|sqlmap|w3af|owasp|Nikto|fimap|havij|PycURL|zmeu|BabyKrokodil|netsparker|httperf|bench| SF/)”&lt;/p&gt;



 &lt;p&gt;user-agent中去除|PycURL,允许监控访问&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;openresty编辑说明&lt;/strong&gt;  &lt;br /&gt;–help this message&lt;/p&gt;



 &lt;p&gt;–prefix=PATH set the installation prefix (default to /usr/local/openresty)&lt;/p&gt;



 &lt;p&gt;–with-debug enable debug logging&lt;/p&gt;



 &lt;p&gt;–with-no-pool-patch enable the no-pool patch for debugging memory issues&lt;/p&gt;



 &lt;p&gt;-jN pass -jN option to make while building LuaJIT 2.1&lt;/p&gt;



 &lt;p&gt;–without-http_echo_module disable ngx_http_echo_module  &lt;br /&gt;–without-http_xss_module disable ngx_http_xss_module  &lt;br /&gt;–without-http_coolkit_module disable ngx_http_coolkit_module  &lt;br /&gt;–without-http_set_misc_module disable ngx_http_set_misc_module  &lt;br /&gt;–without-http_form_input_module disable ngx_http_form_input_module  &lt;br /&gt;–without-http_encrypted_session_module  &lt;br /&gt;disable ngx_http_encrypted_session_module  &lt;br /&gt;–without-http_srcache_module disable ngx_http_srcache_module  &lt;br /&gt;–without-http_lua_module disable ngx_http_lua_module  &lt;br /&gt;–without-http_lua_upstream_module disable ngx_http_lua_upstream_module  &lt;br /&gt;–without-http_headers_more_module disable ngx_http_headers_more_module  &lt;br /&gt;–without-http_array_var_module disable ngx_http_array_var_module  &lt;br /&gt;–without-http_memc_module disable ngx_http_memc_module  &lt;br /&gt;–without-http_redis2_module disable ngx_http_redis2_module  &lt;br /&gt;–without-http_redis_module disable ngx_http_redis_module  &lt;br /&gt;–without-http_rds_json_module disable ngx_http_rds_json_module  &lt;br /&gt;–without-http_rds_csv_module disable ngx_http_rds_csv_module  &lt;br /&gt;–without-stream_lua_module disable ngx_stream_lua_module  &lt;br /&gt;–without-ngx_devel_kit_module disable ngx_devel_kit_module  &lt;br /&gt;–without-stream disable TCP/UDP proxy module  &lt;br /&gt;–without-http_ssl_module disable ngx_http_ssl_module  &lt;br /&gt;–without-stream_ssl_module disable ngx_stream_ssl_module&lt;/p&gt;



 &lt;p&gt;–with-http_iconv_module enable ngx_http_iconv_module  &lt;br /&gt;–with-http_drizzle_module enable ngx_http_drizzle_module  &lt;br /&gt;–with-http_postgres_module enable ngx_http_postgres_module&lt;/p&gt;



 &lt;p&gt;–without-lua_cjson disable the lua-cjson library  &lt;br /&gt;–without-lua_tablepool disable the lua-tablepool library (and by consequence, the  &lt;br /&gt;lua-resty-shell library)  &lt;br /&gt;–without-lua_redis_parser disable the lua-redis-parser library  &lt;br /&gt;–without-lua_rds_parser disable the lua-rds-parser library  &lt;br /&gt;–without-lua_resty_dns disable the lua-resty-dns library  &lt;br /&gt;–without-lua_resty_memcached disable the lua-resty-memcached library  &lt;br /&gt;–without-lua_resty_redis disable the lua-resty-redis library  &lt;br /&gt;–without-lua_resty_mysql disable the lua-resty-mysql library  &lt;br /&gt;–without-lua_resty_upload disable the lua-resty-upload library  &lt;br /&gt;–without-lua_resty_upstream_healthcheck  &lt;br /&gt;disable the lua-resty-upstream-healthcheck library  &lt;br /&gt;–without-lua_resty_string disable the lua-resty-string library  &lt;br /&gt;–without-lua_resty_websocket disable the lua-resty-websocket library  &lt;br /&gt;–without-lua_resty_limit_traffic disable the lua-resty-limit-traffic library  &lt;br /&gt;–without-lua_resty_lock disable the lua-resty-lock library  &lt;br /&gt;–without-lua_resty_lrucache disable the lua-resty-lrucache library  &lt;br /&gt;–without-lua_resty_signal disable the lua-resty-signal library (and by consequence,  &lt;br /&gt;the lua-resty-shell library)  &lt;br /&gt;–without-lua_resty_shell disable the lua-resty-shell library  &lt;br /&gt;–without-lua_resty_core disable the lua-resty-core library&lt;/p&gt;



 &lt;p&gt;–with-luajit=DIR use the external LuaJIT 2.1 installation specified by DIR  &lt;br /&gt;–with-luajit-xcflags=FLAGS Specify extra C compiler flags for LuaJIT 2.1  &lt;br /&gt;–with-luajit-ldflags=FLAGS Specify extra C linker flags for LuaJIT 2.1  &lt;br /&gt;–without-luajit-lua52 Turns off the LuaJIT extensions from Lua 5.2 that may break  &lt;br /&gt;backward compatibility  &lt;br /&gt;–without-luajit-gc64 Turns off the LuaJIT GC64 mode (which is enabled by default  &lt;br /&gt;on x86_64)&lt;/p&gt;



 &lt;p&gt;–with-libdrizzle=DIR specify the libdrizzle 1.0 (or drizzle) installation prefix  &lt;br /&gt;–with-libpq=DIR specify the libpq (or postgresql) installation prefix  &lt;br /&gt;–with-pg_config=PATH specify the path of the pg_config utility&lt;/p&gt;



 &lt;p&gt;Options directly inherited from nginx&lt;/p&gt;



 &lt;p&gt;–sbin-path=PATH set nginx binary pathname  &lt;br /&gt;–modules-path=PATH set modules path  &lt;br /&gt;–conf-path=PATH set nginx.conf pathname  &lt;br /&gt;–error-log-path=PATH set error log pathname  &lt;br /&gt;–pid-path=PATH set nginx.pid pathname  &lt;br /&gt;–lock-path=PATH set nginx.lock pathname&lt;/p&gt;



 &lt;p&gt;–user=USER set non-privileged user for  &lt;br /&gt;worker processes  &lt;br /&gt;–group=GROUP set non-privileged group for  &lt;br /&gt;worker processes&lt;/p&gt;



 &lt;p&gt;–build=NAME set build name  &lt;br /&gt;–builddir=DIR set build directory&lt;/p&gt;



 &lt;p&gt;–with-select_module enable select module  &lt;br /&gt;–without-select_module disable select module  &lt;br /&gt;–with-poll_module enable poll module  &lt;br /&gt;–without-poll_module disable poll module&lt;/p&gt;



 &lt;p&gt;–with-threads enable thread pool support&lt;/p&gt;



 &lt;p&gt;–with-file-aio enable file AIO support&lt;/p&gt;



 &lt;p&gt;–with-http_ssl_module enable ngx_http_ssl_module (default on)  &lt;br /&gt;–with-http_v2_module enable ngx_http_v2_module  &lt;br /&gt;–with-http_realip_module enable ngx_http_realip_module  &lt;br /&gt;–with-http_addition_module enable ngx_http_addition_module  &lt;br /&gt;–with-http_xslt_module enable ngx_http_xslt_module  &lt;br /&gt;–with-http_xslt_module=dynamic enable dynamic ngx_http_xslt_module  &lt;br /&gt;–with-http_image_filter_module enable ngx_http_image_filter_module  &lt;br /&gt;–with-http_image_filter_module=dynamic  &lt;br /&gt;enable dynamic ngx_http_image_filter_module  &lt;br /&gt;–with-http_geoip_module enable ngx_http_geoip_module  &lt;br /&gt;–with-http_geoip_module=dynamic enable dynamic ngx_http_geoip_module  &lt;br /&gt;–with-http_sub_module enable ngx_http_sub_module  &lt;br /&gt;–with-http_dav_module enable ngx_http_dav_module  &lt;br /&gt;–with-http_flv_module enable ngx_http_flv_module  &lt;br /&gt;–with-http_mp4_module enable ngx_http_mp4_module  &lt;br /&gt;–with-http_gunzip_module enable ngx_http_gunzip_module  &lt;br /&gt;–with-http_gzip_static_module enable ngx_http_gzip_static_module  &lt;br /&gt;–with-http_auth_request_module enable ngx_http_auth_request_module  &lt;br /&gt;–with-http_random_index_module enable ngx_http_random_index_module  &lt;br /&gt;–with-http_secure_link_module enable ngx_http_secure_link_module  &lt;br /&gt;–with-http_degradation_module enable ngx_http_degradation_module  &lt;br /&gt;–with-http_slice_module enable ngx_http_slice_module  &lt;br /&gt;–with-http_stub_status_module enable ngx_http_stub_status_module&lt;/p&gt;



 &lt;p&gt;–without-http_charset_module disable ngx_http_charset_module  &lt;br /&gt;–without-http_gzip_module disable ngx_http_gzip_module  &lt;br /&gt;–without-http_ssi_module disable ngx_http_ssi_module  &lt;br /&gt;–without-http_userid_module disable ngx_http_userid_module  &lt;br /&gt;–without-http_access_module disable ngx_http_access_module  &lt;br /&gt;–without-http_auth_basic_module disable ngx_http_auth_basic_module  &lt;br /&gt;–without-http_mirror_module disable ngx_http_mirror_module  &lt;br /&gt;–without-http_autoindex_module disable ngx_http_autoindex_module  &lt;br /&gt;–without-http_geo_module disable ngx_http_geo_module  &lt;br /&gt;–without-http_map_module disable ngx_http_map_module  &lt;br /&gt;–without-http_split_clients_module disable ngx_http_split_clients_module  &lt;br /&gt;–without-http_referer_module disable ngx_http_referer_module  &lt;br /&gt;–without-http_rewrite_module disable ngx_http_rewrite_module  &lt;br /&gt;–without-http_proxy_module disable ngx_http_proxy_module  &lt;br /&gt;–without-http_fastcgi_module disable ngx_http_fastcgi_module  &lt;br /&gt;–without-http_uwsgi_module disable ngx_http_uwsgi_module  &lt;br /&gt;–without-http_scgi_module disable ngx_http_scgi_module  &lt;br /&gt;–without-http_grpc_module disable ngx_http_grpc_module  &lt;br /&gt;–without-http_memcached_module disable ngx_http_memcached_module  &lt;br /&gt;–without-http_limit_conn_module disable ngx_http_limit_conn_module  &lt;br /&gt;–without-http_limit_req_module disable ngx_http_limit_req_module  &lt;br /&gt;–without-http_empty_gif_module disable ngx_http_empty_gif_module  &lt;br /&gt;–without-http_browser_module disable ngx_http_browser_module  &lt;br /&gt;–without-http_upstream_hash_module  &lt;br /&gt;disable ngx_http_upstream_hash_module  &lt;br /&gt;–without-http_upstream_ip_hash_module  &lt;br /&gt;disable ngx_http_upstream_ip_hash_module  &lt;br /&gt;–without-http_upstream_least_conn_module  &lt;br /&gt;disable ngx_http_upstream_least_conn_module  &lt;br /&gt;–without-http_upstream_random_module  &lt;br /&gt;disable ngx_http_upstream_random_module  &lt;br /&gt;–without-http_upstream_keepalive_module  &lt;br /&gt;disable ngx_http_upstream_keepalive_module  &lt;br /&gt;–without-http_upstream_zone_module  &lt;br /&gt;disable ngx_http_upstream_zone_module&lt;/p&gt;



 &lt;p&gt;–with-http_perl_module enable ngx_http_perl_module  &lt;br /&gt;–with-http_perl_module=dynamic enable dynamic ngx_http_perl_module  &lt;br /&gt;–with-perl_modules_path=PATH set Perl modules path  &lt;br /&gt;–with-perl=PATH set perl binary pathname&lt;/p&gt;



 &lt;p&gt;–http-log-path=PATH set http access log pathname  &lt;br /&gt;–http-client-body-temp-path=PATH set path to store  &lt;br /&gt;http client request body temporary files  &lt;br /&gt;–http-proxy-temp-path=PATH set path to store  &lt;br /&gt;http proxy temporary files  &lt;br /&gt;–http-fastcgi-temp-path=PATH set path to store  &lt;br /&gt;http fastcgi temporary files  &lt;br /&gt;–http-uwsgi-temp-path=PATH set path to store  &lt;br /&gt;http uwsgi temporary files  &lt;br /&gt;–http-scgi-temp-path=PATH set path to store  &lt;br /&gt;http scgi temporary files&lt;/p&gt;



 &lt;p&gt;–without-http disable HTTP server  &lt;br /&gt;–without-http-cache disable HTTP cache&lt;/p&gt;



 &lt;p&gt;–with-mail enable POP3/IMAP4/SMTP proxy module  &lt;br /&gt;–with-mail=dynamic enable dynamic POP3/IMAP4/SMTP proxy module  &lt;br /&gt;–with-mail_ssl_module enable ngx_mail_ssl_module  &lt;br /&gt;–without-mail_pop3_module disable ngx_mail_pop3_module  &lt;br /&gt;–without-mail_imap_module disable ngx_mail_imap_module  &lt;br /&gt;–without-mail_smtp_module disable ngx_mail_smtp_module&lt;/p&gt;



 &lt;p&gt;–with-stream enable TCP/UDP proxy module (default on)  &lt;br /&gt;–with-stream=dynamic enable dynamic TCP/UDP proxy module  &lt;br /&gt;–with-stream_ssl_module enable ngx_stream_ssl_module (default on)  &lt;br /&gt;–with-stream_realip_module enable ngx_stream_realip_module  &lt;br /&gt;–with-stream_geoip_module enable ngx_stream_geoip_module  &lt;br /&gt;–with-stream_geoip_module=dynamic enable dynamic ngx_stream_geoip_module  &lt;br /&gt;–with-stream_ssl_preread_module enable ngx_stream_ssl_preread_module  &lt;br /&gt;–without-stream_limit_conn_module disable ngx_stream_limit_conn_module  &lt;br /&gt;–without-stream_access_module disable ngx_stream_access_module  &lt;br /&gt;–without-stream_geo_module disable ngx_stream_geo_module  &lt;br /&gt;–without-stream_map_module disable ngx_stream_map_module  &lt;br /&gt;–without-stream_split_clients_module  &lt;br /&gt;disable ngx_stream_split_clients_module  &lt;br /&gt;–without-stream_return_module disable ngx_stream_return_module  &lt;br /&gt;–without-stream_upstream_hash_module  &lt;br /&gt;disable ngx_stream_upstream_hash_module  &lt;br /&gt;–without-stream_upstream_least_conn_module  &lt;br /&gt;disable ngx_stream_upstream_least_conn_module  &lt;br /&gt;–without-stream_upstream_random_module  &lt;br /&gt;disable ngx_stream_upstream_random_module  &lt;br /&gt;–without-stream_upstream_zone_module  &lt;br /&gt;disable ngx_stream_upstream_zone_module&lt;/p&gt;



 &lt;p&gt;–with-google_perftools_module enable ngx_google_perftools_module  &lt;br /&gt;–with-cpp_test_module enable ngx_cpp_test_module&lt;/p&gt;



 &lt;p&gt;–add-module=PATH enable external module  &lt;br /&gt;–add-dynamic-module=PATH enable dynamic external module&lt;/p&gt;



 &lt;p&gt;–with-compat dynamic modules compatibility&lt;/p&gt;



 &lt;p&gt;–with-cc=PATH set C compiler pathname  &lt;br /&gt;–with-cpp=PATH set C preprocessor pathname  &lt;br /&gt;–with-cc-opt=OPTIONS set additional C compiler options  &lt;br /&gt;–with-ld-opt=OPTIONS set additional linker options  &lt;br /&gt;–with-cpu-opt=CPU build for the specified CPU, valid values:  &lt;br /&gt;pentium, pentiumpro, pentium3, pentium4,  &lt;br /&gt;athlon, opteron, sparc32, sparc64, ppc64&lt;/p&gt;



 &lt;p&gt;–without-pcre disable PCRE library usage  &lt;br /&gt;–with-pcre force PCRE library usage  &lt;br /&gt;–with-pcre=DIR set path to PCRE library sources  &lt;br /&gt;–with-pcre-opt=OPTIONS set additional build options for PCRE  &lt;br /&gt;–with-pcre-jit build PCRE with JIT compilation support&lt;/p&gt;



 &lt;p&gt;–with-zlib=DIR set path to zlib library sources  &lt;br /&gt;–with-zlib-opt=OPTIONS set additional build options for zlib  &lt;br /&gt;–with-zlib-asm=CPU use zlib assembler sources optimized  &lt;br /&gt;for the specified CPU, valid values:  &lt;br /&gt;pentium, pentiumpro&lt;/p&gt;



 &lt;p&gt;–with-libatomic force libatomic_ops library usage  &lt;br /&gt;–with-libatomic=DIR set path to libatomic_ops library sources&lt;/p&gt;



 &lt;p&gt;–with-openssl=DIR set path to OpenSSL library sources  &lt;br /&gt;–with-openssl-opt=OPTIONS set additional build options for OpenSSL&lt;/p&gt;



 &lt;p&gt;–dry-run dry running the configure, for testing only  &lt;br /&gt;–platform=PLATFORM forcibly specify a platform name, for testing only&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;参考:&lt;/strong&gt;  &lt;br /&gt;接入层限流之OpenResty提供的Lua限流模块lua-resty-limit-tra  &lt;br /&gt;https://www.jianshu.com/p/687e63118d84&lt;/p&gt;



 &lt;p&gt;又拍云张聪：OpenResty 动态流控的几种姿势  &lt;br /&gt;https://www.cnblogs.com/upyun/p/10307741.html&lt;/p&gt;



 &lt;p&gt;&lt;/p&gt;



 &lt;p&gt;&lt;/p&gt; &lt;p&gt;The post   &lt;a href="http://blog.c1gstudio.com/archives/1886"&gt;openresty+lua实现WAF应用防火墙&lt;/a&gt; first appeared on   &lt;a href="http://blog.c1gstudio.com"&gt;C1G军火库&lt;/a&gt;.&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>Nginx lua_waf openresty</category>
      <guid isPermaLink="true">https://itindex.net/detail/62126-openresty-lua-waf</guid>
      <pubDate>Thu, 24 Feb 2022 15:46:04 CST</pubDate>
    </item>
    <item>
      <title>使用 Kafka、Debezium 和 Kubernetes 实现应用现代化的模式</title>
      <link>https://itindex.net/detail/62106-kafka-debezium-kubernetes</link>
      <description>&lt;p&gt;本文最初发表于 RedHat 的开发者站点，经原作者 Bilgin Ibryam 许可，由 InfoQ 中文站翻译分享。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;“我们建造计算机的方式与建造城市的方式是一样的，那就是随着时间的推移，依然毫无计划，并且要建造在废墟之上。”&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;Ellen Ullman 在 1998 年写下了这样一句话，但它今天依然适用于我们构建现代应用程序的方式，那就是，随着时间的推移，我们要在遗留的软件上构建应用，而且仅仅有短期的计划。在本文中，我将介绍一些模式和工具，我相信它们对于遗留应用的现代化以及构建现代事件驱动的系统非常有效。&lt;/p&gt; &lt;h3&gt;应用现代化概述&lt;/h3&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;应用现代化（Application modernization）指的是对现有遗留应用的基础设施（内部架构）进行现代化的过程，以提高新特性的交付速度、改善性能和可扩展性以及为新的使用场景提供功能等等。对应用程序的现代化和迁移类型已经有了很好的分类，如图 1 所示。&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;img alt="&amp;#22270;&amp;#29255;" src="https://static001.geekbang.org/infoq/a4/a4f858007c01881ba88e5fdf16b1fb43.webp"&gt;&lt;/img&gt; &lt;p&gt;图 1：三种现代化类型以及可能用到的技术&lt;/p&gt; &lt;div align="right"&gt;  &lt;a href="https://www.infoq.cn/article/ei0HSGgZFLwQwiHCNQ4S?utm_source=rss&amp;utm_medium=article"&gt;点击查看原文&amp;gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62106-kafka-debezium-kubernetes</guid>
      <pubDate>Sat, 12 Feb 2022 14:53:20 CST</pubDate>
    </item>
    <item>
      <title>使用 Kubernetes 部署 Flink 应用</title>
      <link>https://itindex.net/detail/62063-kubernetes-flink-%E5%BA%94%E7%94%A8</link>
      <description>&lt;p&gt;  &lt;a href="https://kubernetes.io/" rel="noopener" target="_blank"&gt;Kubernetes&lt;/a&gt; 是目前非常流行的容器编排系统，在其之上可以运行 Web 服务、大数据处理等各类应用。这些应用被打包在一个个非常轻量的容器中，我们通过声明的方式来告知 Kubernetes 要如何部署和扩容这些程序，并对外提供服务。  &lt;a href="https://flink.apache.org/" rel="noopener" target="_blank"&gt;Flink&lt;/a&gt; 同样是非常流行的分布式处理框架，它也可以运行在 Kubernetes 之上。将两者相结合，我们就可以得到一个健壮和高可扩的数据处理应用，并且能够更安全地和其它服务共享一个 Kubernetes 集群。&lt;/p&gt; &lt;p&gt;  &lt;img alt="Flink on Kubernetes" src="http://shzhangji.com/cnblogs/images/flink-on-kubernetes.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;在 Kubernetes 上部署 Flink 有两种方式：会话集群（Session Cluster）和脚本集群（Job Cluster）。会话集群和独立部署一个 Flink 集群类似，只是底层资源换成了 K8s 容器，而非直接运行在操作系统上。该集群可以提交多个脚本，因此适合运行那些短时脚本和即席查询。脚本集群则是为单个脚本部署一整套服务，包括 JobManager 和 TaskManager，运行结束后这些资源也随即释放。我们需要为每个脚本构建专门的容器镜像，分配独立的资源，因而这种方式可以更好地和其他脚本隔离开，同时便于扩容或缩容。文本将以脚本集群为例，演示如何在 K8s 上运行 Flink 实时处理程序，主要步骤如下：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;编译并打包 Flink 脚本 Jar 文件；&lt;/li&gt;  &lt;li&gt;构建 Docker 容器镜像，添加 Flink 运行时库和上述 Jar 包；&lt;/li&gt;  &lt;li&gt;使用 Kubernetes Job 部署 Flink JobManager 组件；&lt;/li&gt;  &lt;li&gt;使用 Kubernetes Service 将 JobManager 服务端口开放到集群中；&lt;/li&gt;  &lt;li&gt;使用 Kubernetes Deployment 部署 Flink TaskManager；&lt;/li&gt;  &lt;li&gt;配置 Flink JobManager 高可用，需使用 ZooKeeper 和 HDFS；&lt;/li&gt;  &lt;li&gt;借助 Flink SavePoint 机制来停止和恢复脚本。&lt;/li&gt;&lt;/ul&gt; &lt;a&gt;&lt;/a&gt; &lt;h2&gt;  &lt;a href="http://shzhangji.com/cnblogs/#Kubernetes-&amp;#23454;&amp;#39564;&amp;#29615;&amp;#22659;" title="Kubernetes &amp;#23454;&amp;#39564;&amp;#29615;&amp;#22659;"&gt;&lt;/a&gt;Kubernetes 实验环境&lt;/h2&gt; &lt;p&gt;如果手边没有 K8s 实验环境，我们可以用   &lt;a href="https://github.com/kubernetes/minikube" rel="noopener" target="_blank"&gt;Minikube&lt;/a&gt; 快速搭建一个，以 MacOS 系统为例：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;安装    &lt;a href="https://www.virtualbox.org" rel="noopener" target="_blank"&gt;VirtualBox&lt;/a&gt;，Minikube 将在虚拟机中启动 K8s 集群；&lt;/li&gt;  &lt;li&gt;下载    &lt;a href="https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-amd64" rel="noopener" target="_blank"&gt;Minikube 程序&lt;/a&gt;，权限修改为可运行，并加入到 PATH 环境变量中；&lt;/li&gt;  &lt;li&gt;执行    &lt;code&gt;minikube start&lt;/code&gt;，该命令会下载虚拟机镜像，安装    &lt;code&gt;kubelet&lt;/code&gt; 和    &lt;code&gt;kubeadm&lt;/code&gt; 程序，并构建一个完整的 K8s 集群。如果你在访问网络时遇到问题，可以配置一个代理，并告知    &lt;a href="https://minikube.sigs.k8s.io/docs/reference/networking/proxy/" rel="noopener" target="_blank"&gt;Minikube 使用它&lt;/a&gt;；&lt;/li&gt;  &lt;li&gt;下载并安装    &lt;a href="https://storage.googleapis.com/kubernetes-release/release/v1.15.0/bin/darwin/amd64/kubectl" rel="noopener" target="_blank"&gt;kubectl 程序&lt;/a&gt;，Minikube 已经将该命令指向虚拟机中的 K8s 集群了，所以可以直接运行    &lt;code&gt;kubectl get pods -A&lt;/code&gt; 来显示当前正在运行的 K8s Pods：&lt;/li&gt;&lt;/ul&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;NAMESPACE     NAME                               READY   STATUS    RESTARTS   AGE     &lt;br /&gt;kube-system   kube-apiserver-minikube            1/1     Running   0          16m     &lt;br /&gt;kube-system   etcd-minikube                      1/1     Running   0          15m     &lt;br /&gt;kube-system   coredns-5c98db65d4-d4t2h           1/1     Running   0          17m     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h2&gt;  &lt;a href="http://shzhangji.com/cnblogs/#Flink-&amp;#23454;&amp;#26102;&amp;#22788;&amp;#29702;&amp;#33050;&amp;#26412;&amp;#31034;&amp;#20363;" title="Flink &amp;#23454;&amp;#26102;&amp;#22788;&amp;#29702;&amp;#33050;&amp;#26412;&amp;#31034;&amp;#20363;"&gt;&lt;/a&gt;Flink 实时处理脚本示例&lt;/h2&gt; &lt;p&gt;我们可以编写一个简单的实时处理脚本，该脚本会从某个端口中读取文本，分割为单词，并且每 5 秒钟打印一次每个单词出现的次数。以下代码是从   &lt;a href="https://ci.apache.org/projects/flink/flink-docs-release-1.8/dev/datastream_api.html#example-program" rel="noopener" target="_blank"&gt;Flink 官方文档&lt;/a&gt; 上获取来的，完整的示例项目可以到   &lt;a href="https://github.com/jizhang/flink-on-kubernetes" rel="noopener" target="_blank"&gt;GitHub&lt;/a&gt; 上查看。&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;DataStream&amp;lt;Tuple2&amp;lt;String, Integer&amp;gt;&amp;gt; dataStream = env     &lt;br /&gt;    .socketTextStream(&amp;quot;192.168.99.1&amp;quot;, 9999)     &lt;br /&gt;    .flatMap(new Splitter())     &lt;br /&gt;    .keyBy(0)     &lt;br /&gt;    .timeWindow(Time.seconds(5))     &lt;br /&gt;    .sum(1);     &lt;br /&gt;     &lt;br /&gt;dataStream.print();     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;K8s 容器中的程序可以通过 IP   &lt;code&gt;192.168.99.1&lt;/code&gt; 来访问 Minikube 宿主机上的服务。因此在运行上述代码之前，需要先在宿主机上执行   &lt;code&gt;nc -lk 9999&lt;/code&gt; 命令打开一个端口。&lt;/p&gt; &lt;p&gt;接下来执行   &lt;code&gt;mvn clean package&lt;/code&gt; 命令，打包好的 Jar 文件路径为   &lt;code&gt;target/flink-on-kubernetes-0.0.1-SNAPSHOT-jar-with-dependencies.jar&lt;/code&gt;。&lt;/p&gt; &lt;h2&gt;  &lt;a href="http://shzhangji.com/cnblogs/#&amp;#26500;&amp;#24314;-Docker-&amp;#23481;&amp;#22120;&amp;#38236;&amp;#20687;" title="&amp;#26500;&amp;#24314; Docker &amp;#23481;&amp;#22120;&amp;#38236;&amp;#20687;"&gt;&lt;/a&gt;构建 Docker 容器镜像&lt;/h2&gt; &lt;p&gt;Flink 提供了一个官方的容器镜像，可以从   &lt;a href="https://hub.docker.com/_/flink" rel="noopener" target="_blank"&gt;DockerHub&lt;/a&gt; 上下载。我们将以这个镜像为基础，构建独立的脚本镜像，将打包好的 Jar 文件放置进去。此外，新版 Flink 已将 Hadoop 依赖从官方发行版中剥离，因此我们在打镜像时也需要包含进去。&lt;/p&gt; &lt;p&gt;简单看一下官方镜像的   &lt;a href="https://github.com/docker-flink/docker-flink/blob/master/1.8/scala_2.12-debian/Dockerfile" rel="noopener" target="_blank"&gt;Dockerfile&lt;/a&gt;，它做了以下几件事情：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;将 OpenJDK 1.8 作为基础镜像；&lt;/li&gt;  &lt;li&gt;下载并安装 Flink 至    &lt;code&gt;/opt/flink&lt;/code&gt; 目录中；&lt;/li&gt;  &lt;li&gt;添加    &lt;code&gt;flink&lt;/code&gt; 用户和组；&lt;/li&gt;  &lt;li&gt;指定入口文件，不过我们会在 K8s 配置中覆盖此项。&lt;/li&gt;&lt;/ul&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;FROM openjdk:8-jre     &lt;br /&gt;ENV FLINK_HOME=/opt/flink     &lt;br /&gt;WORKDIR $FLINK_HOME     &lt;br /&gt;RUN useradd flink &amp;amp;&amp;amp; \     &lt;br /&gt;  wget -O flink.tgz &amp;quot;$FLINK_TGZ_URL&amp;quot; &amp;amp;&amp;amp; \     &lt;br /&gt;  tar -xf flink.tgz     &lt;br /&gt;ENTRYPOINT [&amp;quot;/docker-entrypoint.sh&amp;quot;]     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;在此基础上，我们编写新的 Dockerfile：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;FROM flink:1.8.1-scala_2.12     &lt;br /&gt;ARG hadoop_jar     &lt;br /&gt;ARG job_jar     &lt;br /&gt;COPY --chown=flink:flink $hadoop_jar $job_jar $FLINK_HOME/lib/     &lt;br /&gt;USER flink     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;在构建镜像之前，我们需要安装 Docker 命令行工具，并将其指向 Minikube 中的 Docker 服务，这样打出来的镜像才能被 K8s 使用：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;$ brew install docker     &lt;br /&gt;$ eval $(minikube docker-env)     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;下载   &lt;a href="https://repo.maven.apache.org/maven2/org/apache/flink/flink-shaded-hadoop-2-uber/2.8.3-7.0/flink-shaded-hadoop-2-uber-2.8.3-7.0.jar" rel="noopener" target="_blank"&gt;Hadoop Jar 包&lt;/a&gt;，执行以下命令：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;$ cd /path/to/Dockerfile     &lt;br /&gt;$ cp /path/to/flink-shaded-hadoop-2-uber-2.8.3-7.0.jar hadoop.jar     &lt;br /&gt;$ cp /path/to/flink-on-kubernetes-0.0.1-SNAPSHOT-jar-with-dependencies.jar job.jar     &lt;br /&gt;$ docker build --build-arg hadoop_jar=hadoop.jar --build-arg job_jar=job.jar --tag flink-on-kubernetes:0.0.1 .     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;脚本镜像打包完毕，可用于部署：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;$ docker image ls     &lt;br /&gt;REPOSITORY           TAG    IMAGE ID      CREATED         SIZE     &lt;br /&gt;flink-on-kubernetes  0.0.1  505d2f11cc57  10 seconds ago  618MB     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h2&gt;  &lt;a href="http://shzhangji.com/cnblogs/#&amp;#37096;&amp;#32626;-JobManager" title="&amp;#37096;&amp;#32626; JobManager"&gt;&lt;/a&gt;部署 JobManager&lt;/h2&gt; &lt;p&gt;首先，我们通过创建 Kubernetes Job 对象来部署 Flink JobManager。Job 和 Deployment 是 K8s 中两种不同的管理方式，他们都可以通过启动和维护多个 Pod 来执行任务。不同的是，Job 会在 Pod 执行完成后自动退出，而 Deployment 则会不断重启 Pod，直到手工删除。Pod 成功与否是通过命令行返回状态判断的，如果异常退出，Job 也会负责重启它。因此，Job 更适合用来部署 Flink 应用，当我们手工关闭一个 Flink 脚本时，K8s 就不会错误地重新启动它。&lt;/p&gt; &lt;p&gt;以下是   &lt;a href="https://github.com/jizhang/flink-on-kubernetes/blob/master/docker/jobmanager.yml" rel="noopener" target="_blank"&gt;   &lt;code&gt;jobmanager.yml&lt;/code&gt;&lt;/a&gt; 配置文件：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;14     &lt;br /&gt;15     &lt;br /&gt;16     &lt;br /&gt;17     &lt;br /&gt;18     &lt;br /&gt;19     &lt;br /&gt;20     &lt;br /&gt;21     &lt;br /&gt;22     &lt;br /&gt;23     &lt;br /&gt;24     &lt;br /&gt;25     &lt;br /&gt;26     &lt;br /&gt;27     &lt;br /&gt;28     &lt;br /&gt;29     &lt;br /&gt;30     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;apiVersion: batch/v1     &lt;br /&gt;kind: Job     &lt;br /&gt;metadata:     &lt;br /&gt;  name: ${JOB}-jobmanager     &lt;br /&gt;spec:     &lt;br /&gt;  template:     &lt;br /&gt;    metadata:     &lt;br /&gt;      labels:     &lt;br /&gt;        app: flink     &lt;br /&gt;        instance: ${JOB}-jobmanager     &lt;br /&gt;    spec:     &lt;br /&gt;      restartPolicy: OnFailure     &lt;br /&gt;      containers:     &lt;br /&gt;      - name: jobmanager     &lt;br /&gt;        image: flink-on-kubernetes:0.0.1     &lt;br /&gt;        command: [&amp;quot;/opt/flink/bin/standalone-job.sh&amp;quot;]     &lt;br /&gt;        args: [&amp;quot;start-foreground&amp;quot;,     &lt;br /&gt;               &amp;quot;-Djobmanager.rpc.address=${JOB}-jobmanager&amp;quot;,     &lt;br /&gt;               &amp;quot;-Dparallelism.default=1&amp;quot;,     &lt;br /&gt;               &amp;quot;-Dblob.server.port=6124&amp;quot;,     &lt;br /&gt;               &amp;quot;-Dqueryable-state.server.ports=6125&amp;quot;]     &lt;br /&gt;        ports:     &lt;br /&gt;        - containerPort: 6123     &lt;br /&gt;          name: rpc     &lt;br /&gt;        - containerPort: 6124     &lt;br /&gt;          name: blob     &lt;br /&gt;        - containerPort: 6125     &lt;br /&gt;          name: query     &lt;br /&gt;        - containerPort: 8081     &lt;br /&gt;          name: ui     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;ul&gt;  &lt;li&gt;   &lt;code&gt;${JOB}&lt;/code&gt; 变量可以使用    &lt;code&gt;envsubst&lt;/code&gt; 命令来替换，这样同一份配置文件就能够为多个脚本使用了；&lt;/li&gt;  &lt;li&gt;容器的入口修改为了    &lt;code&gt;standalone-job.sh&lt;/code&gt;，这是 Flink 的官方脚本，会以前台模式启动 JobManager，扫描类加载路径中的    &lt;code&gt;Main-Class&lt;/code&gt; 作为脚本入口，我们也可以使用    &lt;code&gt;-j&lt;/code&gt; 参数来指定完整的类名。之后，这个脚本会被自动提交到集群中。&lt;/li&gt;  &lt;li&gt;JobManager 的 RPC 地址修改为了    &lt;a href="https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies" rel="noopener" target="_blank"&gt;Kubernetes Service&lt;/a&gt; 的名称，我们将在下文创建。集群中的其他组件将通过这个名称来访问 JobManager。&lt;/li&gt;  &lt;li&gt;Flink Blob Server &amp;amp; Queryable State Server 的端口号默认是随机的，为了方便将其开放到集群中，我们修改为了固定端口。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;使用   &lt;code&gt;kubectl&lt;/code&gt; 命令创建对象，并查看状态：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;$ export JOB=flink-on-kubernetes     &lt;br /&gt;$ envsubst &amp;lt;jobmanager.yml | kubectl create -f -     &lt;br /&gt;$ kubectl get pod     &lt;br /&gt;NAME                                   READY   STATUS    RESTARTS   AGE     &lt;br /&gt;flink-on-kubernetes-jobmanager-kc4kq   1/1     Running   0          2m26s     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;随后，我们创建一个 K8s Service 来将 JobManager 的端口开放出来，以便 TaskManager 前来注册：&lt;/p&gt; &lt;p&gt;  &lt;code&gt;service.yml&lt;/code&gt;&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;14     &lt;br /&gt;15     &lt;br /&gt;16     &lt;br /&gt;17     &lt;br /&gt;18     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;apiVersion: v1     &lt;br /&gt;kind: Service     &lt;br /&gt;metadata:     &lt;br /&gt;  name: ${JOB}-jobmanager     &lt;br /&gt;spec:     &lt;br /&gt;  selector:     &lt;br /&gt;    app: flink     &lt;br /&gt;    instance: ${JOB}-jobmanager     &lt;br /&gt;  type: NodePort     &lt;br /&gt;  ports:     &lt;br /&gt;  - name: rpc     &lt;br /&gt;    port: 6123     &lt;br /&gt;  - name: blob     &lt;br /&gt;    port: 6124     &lt;br /&gt;  - name: query     &lt;br /&gt;    port: 6125     &lt;br /&gt;  - name: ui     &lt;br /&gt;    port: 8081     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;这里   &lt;code&gt;type: NodePort&lt;/code&gt; 是必要的，因为通过这项配置，我们可以在 K8s 集群之外访问 JobManager UI 和 RESTful API。&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;$ envsubst &amp;lt;service.yml | kubectl create -f -     &lt;br /&gt;$ kubectl get service     &lt;br /&gt;NAME                             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                                                      AGE     &lt;br /&gt;flink-on-kubernetes-jobmanager   NodePort    10.109.78.143   &amp;lt;none&amp;gt;        6123:31476/TCP,6124:32268/TCP,6125:31602/TCP,8081:31254/TCP  15m     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;我们可以看到，Flink Dashboard 开放在了虚拟机的 31254 端口上。Minikube 提供了一个命令，可以获取到 K8s 服务的访问地址：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;$ minikube service $JOB-jobmanager --url     &lt;br /&gt;http://192.168.99.108:31476     &lt;br /&gt;http://192.168.99.108:32268     &lt;br /&gt;http://192.168.99.108:31602     &lt;br /&gt;http://192.168.99.108:31254     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h2&gt;  &lt;a href="http://shzhangji.com/cnblogs/#&amp;#37096;&amp;#32626;-TaskManager" title="&amp;#37096;&amp;#32626; TaskManager"&gt;&lt;/a&gt;部署 TaskManager&lt;/h2&gt; &lt;p&gt;  &lt;code&gt;taskmanager.yml&lt;/code&gt;&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;14     &lt;br /&gt;15     &lt;br /&gt;16     &lt;br /&gt;17     &lt;br /&gt;18     &lt;br /&gt;19     &lt;br /&gt;20     &lt;br /&gt;21     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;apiVersion: apps/v1     &lt;br /&gt;kind: Deployment     &lt;br /&gt;metadata:     &lt;br /&gt;  name: ${JOB}-taskmanager     &lt;br /&gt;spec:     &lt;br /&gt;  selector:     &lt;br /&gt;    matchLabels:     &lt;br /&gt;      app: flink     &lt;br /&gt;      instance: ${JOB}-taskmanager     &lt;br /&gt;  replicas: 1     &lt;br /&gt;  template:     &lt;br /&gt;    metadata:     &lt;br /&gt;      labels:     &lt;br /&gt;        app: flink     &lt;br /&gt;        instance: ${JOB}-taskmanager     &lt;br /&gt;    spec:     &lt;br /&gt;      containers:     &lt;br /&gt;      - name: taskmanager     &lt;br /&gt;        image: flink-on-kubernetes:0.0.1     &lt;br /&gt;        command: [&amp;quot;/opt/flink/bin/taskmanager.sh&amp;quot;]     &lt;br /&gt;        args: [&amp;quot;start-foreground&amp;quot;, &amp;quot;-Djobmanager.rpc.address=${JOB}-jobmanager&amp;quot;]     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;通过修改   &lt;code&gt;replicas&lt;/code&gt; 配置，我们可以开启多个 TaskManager。镜像中的   &lt;code&gt;taskmanager.numberOfTaskSlots&lt;/code&gt; 参数默认为   &lt;code&gt;1&lt;/code&gt;，这也是我们推荐的配置，因为扩容缩容方面的工作应该交由 K8s 来完成，而非直接使用 TaskManager 的槽位机制。&lt;/p&gt; &lt;p&gt;至此，Flink 脚本集群已经在运行中了。我们在之前已经打开的   &lt;code&gt;nc&lt;/code&gt; 命令窗口中输入一些文本：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;$ nc -lk 9999     &lt;br /&gt;hello world     &lt;br /&gt;hello flink     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;打开另一个终端，查看 TaskManager 的标准输出日志：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;$ kubectl logs -f -l instance=$JOB-taskmanager     &lt;br /&gt;(hello,2)     &lt;br /&gt;(flink,1)     &lt;br /&gt;(world,1)     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h2&gt;  &lt;a href="http://shzhangji.com/cnblogs/#&amp;#24320;&amp;#21551;&amp;#39640;&amp;#21487;&amp;#29992;&amp;#27169;&amp;#24335;" title="&amp;#24320;&amp;#21551;&amp;#39640;&amp;#21487;&amp;#29992;&amp;#27169;&amp;#24335;"&gt;&lt;/a&gt;开启高可用模式&lt;/h2&gt; &lt;p&gt;可用性方面，上述配置中的 TaskManager 如果发生故障退出，K8s 会自动进行重启，Flink 会从上一个 Checkpoint 中恢复工作。但是，JobManager 仍然存在单点问题，因此需要开启   &lt;a href="https://ci.apache.org/projects/flink/flink-docs-release-1.8/ops/jobmanager_high_availability.html" rel="noopener" target="_blank"&gt;HA 模式&lt;/a&gt;，配合 ZooKeeper 和分布式文件系统（如 HDFS）来实现 JobManager 的高可用。在独立集群中，我们需要运行多个 JobManager，作为主备服务器。然而在 K8s 模式下，我们只需开启一个 JobManager，当其异常退出后，K8s 会负责重启，新的 JobManager 将从 ZooKeeper 和 HDFS 中读取最近的工作状态，自动恢复运行。&lt;/p&gt; &lt;p&gt;开启 HA 模式需要修改 JobManager 和 TaskManager 的启动命令：&lt;/p&gt; &lt;p&gt;  &lt;code&gt;jobmanager-ha.yml&lt;/code&gt;&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;command: [&amp;quot;/opt/flink/bin/standalone-job.sh&amp;quot;]     &lt;br /&gt;args: [&amp;quot;start-foreground&amp;quot;,     &lt;br /&gt;       &amp;quot;-Djobmanager.rpc.address=${JOB}-jobmanager&amp;quot;,     &lt;br /&gt;       &amp;quot;-Dparallelism.default=1&amp;quot;,     &lt;br /&gt;       &amp;quot;-Dblob.server.port=6124&amp;quot;,     &lt;br /&gt;       &amp;quot;-Dqueryable-state.server.ports=6125&amp;quot;,     &lt;br /&gt;       &amp;quot;-Dhigh-availability=zookeeper&amp;quot;,     &lt;br /&gt;       &amp;quot;-Dhigh-availability.zookeeper.quorum=192.168.99.1:2181&amp;quot;,     &lt;br /&gt;       &amp;quot;-Dhigh-availability.zookeeper.path.root=/flink&amp;quot;,     &lt;br /&gt;       &amp;quot;-Dhigh-availability.cluster-id=/${JOB}&amp;quot;,     &lt;br /&gt;       &amp;quot;-Dhigh-availability.storageDir=hdfs://192.168.99.1:9000/flink/recovery&amp;quot;,     &lt;br /&gt;       &amp;quot;-Dhigh-availability.jobmanager.port=6123&amp;quot;,     &lt;br /&gt;       ]     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;  &lt;code&gt;taskmanager-ha.yml&lt;/code&gt;&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;command: [&amp;quot;/opt/flink/bin/taskmanager.sh&amp;quot;]     &lt;br /&gt;args: [&amp;quot;start-foreground&amp;quot;,     &lt;br /&gt;       &amp;quot;-Dhigh-availability=zookeeper&amp;quot;,     &lt;br /&gt;       &amp;quot;-Dhigh-availability.zookeeper.quorum=192.168.99.1:2181&amp;quot;,     &lt;br /&gt;       &amp;quot;-Dhigh-availability.zookeeper.path.root=/flink&amp;quot;,     &lt;br /&gt;       &amp;quot;-Dhigh-availability.cluster-id=/${JOB}&amp;quot;,     &lt;br /&gt;       &amp;quot;-Dhigh-availability.storageDir=hdfs://192.168.99.1:9000/flink/recovery&amp;quot;,     &lt;br /&gt;       ]     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;ul&gt;  &lt;li&gt;准备好 ZooKeeper 和 HDFS 测试环境，该配置中使用的是宿主机上的    &lt;code&gt;2181&lt;/code&gt; 和    &lt;code&gt;9000&lt;/code&gt; 端口；&lt;/li&gt;  &lt;li&gt;Flink 集群基本信息会存储在 ZooKeeper 的    &lt;code&gt;/flink/${JOB}&lt;/code&gt; 目录下；&lt;/li&gt;  &lt;li&gt;Checkpoint 数据会存储在 HDFS 的    &lt;code&gt;/flink/recovery&lt;/code&gt; 目录下。使用前，请先确保 Flink 有权限访问 HDFS 的    &lt;code&gt;/flink&lt;/code&gt; 目录；&lt;/li&gt;  &lt;li&gt;   &lt;code&gt;jobmanager.rpc.address&lt;/code&gt; 选项从 TaskManager 的启动命令中去除了，是因为在 HA 模式下，TaskManager 会通过访问 ZooKeeper 来获取到当前 JobManager 的连接信息。需要注意的是，HA 模式下的 JobManager RPC 端口默认是随机的，我们需要使用    &lt;code&gt;high-availability.jobmanager.port&lt;/code&gt; 配置项将其固定下来，方便在 K8s Service 中开放。&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;  &lt;a href="http://shzhangji.com/cnblogs/#&amp;#31649;&amp;#29702;-Flink-&amp;#33050;&amp;#26412;" title="&amp;#31649;&amp;#29702; Flink &amp;#33050;&amp;#26412;"&gt;&lt;/a&gt;管理 Flink 脚本&lt;/h2&gt; &lt;p&gt;我们可以通过 RESTful API 来与 Flink 集群交互，其端口号默认与 Dashboard UI 一致。在宿主机上安装 Flink 命令行工具，传入   &lt;code&gt;-m&lt;/code&gt; 参数来指定目标集群：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;$ bin/flink list -m 192.168.99.108:30206     &lt;br /&gt;------------------ Running/Restarting Jobs -------------------     &lt;br /&gt;24.08.2019 12:50:28 : 00000000000000000000000000000000 : Window WordCount (RUNNING)     &lt;br /&gt;--------------------------------------------------------------     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;在 HA 模式下，Flink 脚本 ID 默认为   &lt;code&gt;00000000000000000000000000000000&lt;/code&gt;，我们可以使用这个 ID 来手工停止脚本，并生成一个   &lt;a href="https://ci.apache.org/projects/flink/flink-docs-release-1.8/ops/state/savepoints.html" rel="noopener" target="_blank"&gt;SavePoint&lt;/a&gt; 快照：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;$ bin/flink cancel -m 192.168.99.108:30206 -s hdfs://192.168.99.1:9000/flink/savepoints/ 00000000000000000000000000000000     &lt;br /&gt;Cancelled job 00000000000000000000000000000000. Savepoint stored in hdfs://192.168.99.1:9000/flink/savepoints/savepoint-000000-f776c8e50a0c.     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;执行完毕后，可以看到 K8s Job 对象的状态变为了已完成：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;$ kubectl get job     &lt;br /&gt;NAME                             COMPLETIONS   DURATION   AGE     &lt;br /&gt;flink-on-kubernetes-jobmanager   1/1           4m40s      7m14s     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;重新启动脚本前，我们需要先将配置从 K8s 中删除：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;$ kubectl delete job $JOB-jobmanager     &lt;br /&gt;$ kubectl delete deployment $JOB-taskmanager     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;然后在 JobManager 的启动命令中加入   &lt;code&gt;--fromSavepoint&lt;/code&gt; 参数：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;command: [&amp;quot;/opt/flink/bin/standalone-job.sh&amp;quot;]     &lt;br /&gt;args: [&amp;quot;start-foreground&amp;quot;,     &lt;br /&gt;       ...     &lt;br /&gt;       &amp;quot;--fromSavepoint&amp;quot;, &amp;quot;${SAVEPOINT}&amp;quot;,     &lt;br /&gt;       ]     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;使用刚才得到的 SavePoint 路径替换该变量，并启动 JobManager：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;$ export SAVEPOINT=hdfs://192.168.99.1:9000/flink/savepoints/savepoint-000000-f776c8e50a0c     &lt;br /&gt;$ envsubst &amp;lt;jobmanager-savepoint.yml | kubectl create -f -     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;需要注意的是，SavePoint 必须和 HA 模式配合使用，因为当 JobManager 异常退出、K8s 重启它时，都会传入   &lt;code&gt;--fromSavepoint&lt;/code&gt;，使脚本进入一个异常的状态。而在开启 HA 模式时，JobManager 会优先读取最近的 CheckPoint 并从中恢复，忽略命令行中传入的 SavePoint。&lt;/p&gt; &lt;h3&gt;  &lt;a href="http://shzhangji.com/cnblogs/#&amp;#25193;&amp;#23481;" title="&amp;#25193;&amp;#23481;"&gt;&lt;/a&gt;扩容&lt;/h3&gt; &lt;p&gt;有两种方式可以对 Flink 脚本进行扩容。第一种方式是用上文提到的 SavePoint 机制：手动关闭脚本，并使用新的   &lt;code&gt;replicas&lt;/code&gt; 和   &lt;code&gt;parallelism.default&lt;/code&gt; 参数进行重启；另一种方式则是使用   &lt;code&gt;flink modify&lt;/code&gt; 命令行工具，该工具的工作机理和人工操作类似，也是先用 SavePoint 停止脚本，然后以新的并发度启动。在使用第二种方式前，我们需要在启动命令中指定默认的 SavePoint 路径：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;command: [&amp;quot;/opt/flink/bin/standalone-job.sh&amp;quot;]     &lt;br /&gt;args: [&amp;quot;start-foreground&amp;quot;,     &lt;br /&gt;       ...     &lt;br /&gt;       &amp;quot;-Dstate.savepoints.dir=hdfs://192.168.99.1:9000/flink/savepoints/&amp;quot;,     &lt;br /&gt;       ]     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;然后，使用   &lt;code&gt;kubectl scale&lt;/code&gt; 命令调整 TaskManager 的个数；&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;$ kubectl scale --replicas=2 deployment/$JOB-taskmanager     &lt;br /&gt;deployment.extensions/flink-on-kubernetes-taskmanager scaled     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;最后，使用   &lt;code&gt;flink modify&lt;/code&gt; 调整脚本并发度：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;$ bin/flink modify 755877434b676ce9dae5cfb533ed7f33 -m 192.168.99.108:30206 -p 2     &lt;br /&gt;Modify job 755877434b676ce9dae5cfb533ed7f33.     &lt;br /&gt;Rescaled job 755877434b676ce9dae5cfb533ed7f33. Its new parallelism is 2.     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;但是，因为存在一个尚未解决的   &lt;a href="https://issues.apache.org/jira/browse/FLINK-11997" rel="noopener" target="_blank"&gt;Issue&lt;/a&gt;，我们无法使用   &lt;code&gt;flink modify&lt;/code&gt; 命令来对 HA 模式下的 Flink 集群进行扩容，因此还请使用人工的方式操作。&lt;/p&gt; &lt;h2&gt;  &lt;a href="http://shzhangji.com/cnblogs/#Flink-&amp;#23558;&amp;#21407;&amp;#29983;&amp;#25903;&amp;#25345;-Kubernetes" title="Flink &amp;#23558;&amp;#21407;&amp;#29983;&amp;#25903;&amp;#25345; Kubernetes"&gt;&lt;/a&gt;Flink 将原生支持 Kubernetes&lt;/h2&gt; &lt;p&gt;Flink 有着非常活跃的开源社区，他们不断改进自身设计（  &lt;a href="https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=65147077" rel="noopener" target="_blank"&gt;FLIP-6&lt;/a&gt;），以适应现今的云原生环境。他们也注意到了 Kubernetes 的蓬勃发展，对 K8s 集群的原生支持也在开发中。我们知道，Flink 可以直接运行在 YARN 或 Mesos 资源管理框架上。以 YARN 为例，Flink 首先启动一个 ApplicationMaster，作为 JobManager，分析提交的脚本需要多少资源，并主动向 YARN ResourceManager 申请，开启对应的 TaskManager。当脚本的并行度改变后，Flink 会自动新增或释放 TaskManager 容器，达到扩容缩容的目的。这种主动管理资源的模式，社区正在开发针对 Kubernetes 的版本（  &lt;a href="https://issues.apache.org/jira/browse/FLINK-9953" rel="noopener" target="_blank"&gt;FLINK-9953&lt;/a&gt;），今后我们便可以使用简单的命令来将 Flink 部署到 K8s 上了。&lt;/p&gt; &lt;p&gt;此外，另一种资源管理模式也在开发中，社区称为响应式容器管理（  &lt;a href="https://issues.apache.org/jira/browse/FLINK-10407" rel="noopener" target="_blank"&gt;FLINK-10407 Reactive container mode&lt;/a&gt;）。简单来说，当 JobManager 发现手中有多余的 TaskManager 时，会自动将运行中的脚本扩容到相应的并发度。以上文中的操作为例，我们只需使用   &lt;code&gt;kubectl scale&lt;/code&gt; 命令修改 TaskManager Deployment 的   &lt;code&gt;replicas&lt;/code&gt; 个数，就能够达到扩容和缩容的目的，无需再执行   &lt;code&gt;flink modify&lt;/code&gt;。相信不久的将来我们就可以享受到这些便利的功能。&lt;/p&gt; &lt;h2&gt;  &lt;a href="http://shzhangji.com/cnblogs/#&amp;#21442;&amp;#32771;&amp;#36164;&amp;#26009;" title="&amp;#21442;&amp;#32771;&amp;#36164;&amp;#26009;"&gt;&lt;/a&gt;参考资料&lt;/h2&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://ci.apache.org/projects/flink/flink-docs-release-1.8/ops/deployment/kubernetes.html" rel="noopener" target="_blank"&gt;https://ci.apache.org/projects/flink/flink-docs-release-1.8/ops/deployment/kubernetes.html&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/" rel="noopener" target="_blank"&gt;https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://jobs.zalando.com/tech/blog/running-apache-flink-on-kubernetes/" rel="noopener" target="_blank"&gt;https://jobs.zalando.com/tech/blog/running-apache-flink-on-kubernetes/&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://www.slideshare.net/tillrohrmann/redesigning-apache-flinks-distributed-architecture-flink-forward-2017" rel="noopener" target="_blank"&gt;https://www.slideshare.net/tillrohrmann/redesigning-apache-flinks-distributed-architecture-flink-forward-2017&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://www.slideshare.net/tillrohrmann/future-of-apache-flink-deployments-containers-kubernetes-and-more-flink-forward-2019-sf" rel="noopener" target="_blank"&gt;https://www.slideshare.net/tillrohrmann/future-of-apache-flink-deployments-containers-kubernetes-and-more-flink-forward-2019-sf&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>Big Data flink kubernetes</category>
      <guid isPermaLink="true">https://itindex.net/detail/62063-kubernetes-flink-%E5%BA%94%E7%94%A8</guid>
      <pubDate>Sun, 25 Aug 2019 11:02:22 CST</pubDate>
    </item>
    <item>
      <title>序列特征在推荐算法中的应用</title>
      <link>https://itindex.net/detail/62062-%E5%BA%8F%E5%88%97-%E7%89%B9%E5%BE%81-%E6%8E%A8%E8%8D%90%E7%AE%97%E6%B3%95</link>
      <description>&lt;p&gt;**简介：**行为序列特征在推荐，广告等领域中有着广泛应用，最近几年涌现了很多有关行为序列的研究论文，讲解如何将行为序列应用到实际场景中。但是论文中的实际思想距离落地还有一段距离，因此本文先介绍一些论文中的序列特征的用法，然后介绍一下在大规模分布式推荐系统框架 EasyRec 中如何将序列特征快速落地，提升实际场景效果。&lt;/p&gt;
 &lt;p&gt;作者：刘国强 - 机器学习PAI团队&lt;/p&gt;
 &lt;h1&gt;序列特征简介&lt;/h1&gt;
 &lt;p&gt;序列特征推荐的目的是利用历史行为序列来预测下一次行为，在现实生活中，行为的前后都存在着极强的关联性，因此，不同于传统推荐任务以静态方式对行为偏好建模，序列特征推荐建模能够捕获动态偏好。例如，某段时间内对运动用品的兴趣较高，另一段时间内则更需要书籍，因此当前偏好可以通过时间顺序的“用户-物品”隐式反馈中推断出来。序列推荐系统不仅可以通过捕捉广义兴趣来提高用户体验，还可以准确预测当前时刻的兴趣，以增强下一时刻的交互效果。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8f074e2ebbb04d43ae1a0d951a907acc~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;图1 序列特征示例图 (图片来源于论文DIN[9])&lt;/p&gt;
 &lt;p&gt;​如图1所示，在电商推荐中，用户的序列特征包含了和用户发生过交互的各种商品。箭头下方包含了用户的行为序列，上方是 DIN[9] 方法中对发生交互关系的各种商品赋予的不同的权重。然后根据所有的这些特征共同得到最后用户的推荐序列。&lt;/p&gt;
 &lt;h1&gt;序列特征应用方法&lt;/h1&gt;
 &lt;p&gt;按序列发展来看，推荐场景下行为序列建模的模型经历了 pooling, Target Attention, RNN, Capsule, Transformer，图神经网络的发展路线。基于 pooling 模型方法的主要包含有 Youtube , 基于 Target Attention 模型方法主要包含有 DIN，DSTN, 基于 RNN 模型方法主要包含有 GRU4Rec, DIEN, DUPN, HUP, DHAN 等，基于 Capsule 模型方法主要包含有 MIND, ComiRec 等，基于 Transformer 模型方法主要包含有 ATRank, BST, DSIN, TISSA, SDM, KFAtt, DFN, SIM, DMT, AliSearch 等，基于图神经网络的方法主要有 SURGE 等。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;1. Pooling 方法&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;1.1 Youtube&lt;/p&gt;
 &lt;p&gt;基于 Pooling 模型方法的主要工作是 Youtube[10], 这些方法通常采用 mean, sum 或者 max pooling 的方法聚合行为序列，他们将用户的行为序列看得同等重要。Youtube[10] 通过基于 pooling 的操作建模用户的搜索序列、观看视频序列，应用在 Google Youtube 的视频推荐系统的召回和排序模块中。&lt;/p&gt;
 &lt;p&gt;在召回阶段的模型如下，使用了观看视频序列、搜索序列：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/36738e5e96274ef59529cc096720ec85~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;由上图可以看出，最底层的输入是用户观看过的 video 的 embedding 向量，以及搜索词的embedding向量，特征向量里面还包括了用户的地理位置的 embedding，年龄，性别等。然后把所有这些特征 concatenate 起来，喂给上层的ReLU神经网络。三层神经网络过后，经过 softmax 函数得到最后输出。&lt;/p&gt;
 &lt;p&gt;在排序阶段的模型如下所示，使用了观看视频序列：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d6304915034a4093a83922821d847004~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;​ 排序阶段引入另一套 DNN 作为ranking model的目的就是引入更多描述视频、用户以及二者之间关系的特征，达到对候选视频集合准确排序的目的。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;2. Target Attention 方法&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;基于pooling的方法中，将行为序列中的每个物品的重要性看作是相同的，无法区分历史行为中每个物品的重要性。对于不同的待排序物品，用户的兴趣向量也是相同的，无法建模多兴趣。&lt;/p&gt;
 &lt;p&gt;为了解决这些问题，研究者们提出了基于Target Attention的方法建模行为序列，主要包括 DIN [9], DSTN [11]等。它们通过attention机制建模行为序列中的物品和待排序物品的attention score (即关联程度)，作为每个行为的权重，然后再将它们聚合起来。&lt;/p&gt;
 &lt;p&gt;2.1 DIN&lt;/p&gt;
 &lt;p&gt;DIN (Deep Interest Network for Click-Through Rate Prediction)[9] 基于 Target attention 建模用户的商品点击行为序列。注意力机制顾名思义，就是模型在预测的时候，对用户不同行为的注意力是不一样的，“相关”的行为历史看重一些，“不相关”的历史甚至可以忽略。那么这样的思想反应到模型中也是直观的。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2594153f049d412b915e1d0e34356c1f~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;​上式中， 是用户的 embedding 向量， 是候选广告商品的embedding向量， 是用户 u 的第 i 次行为的embedding向量，因为这里用户的行为就是浏览商品或店铺，所以行为的embedding的向量就是那次浏览的商品或店铺的embedding向量。&lt;/p&gt;
 &lt;p&gt;因为加入了注意力机制， 从过去 的加和变成了 的加权和， 的权重 就由 与 的关系决定，也就是上式中的 。&lt;/p&gt;
 &lt;p&gt;基于 pooling 的 base 模型如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2ac32d25670e42eb8fadba3a0f22ec2d~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;改进后的 DIN 模型如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/66b28c98e1ef4d238ff2be1802f8e9af~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;DIN模型对于每个待排序的广告，计算出每个历史点击的商品和待排序广告的attention score, 作为这个历史行为的权重，然后基于这些权重，将历史行为序列物品的embedding，聚合成一个用户兴趣向量。对于不同的待排序广告，attention scores的分布也不相同，对应的用户的兴趣向量也不相同，从而建模用户的多个兴趣。&lt;/p&gt;
 &lt;p&gt;传统的Attention机制中，给定两个 item embedding，比如 u 和 v ，通常是直接做点积 uv 或者 uWv ，其中 W是一个 |u|x|v| 的权重矩阵，但 din 更进一步的改进，在上图右上角的activation unit 中，首先是把u和v以及u v的element wise差值向量合并起来作为输入，然后喂给全连接层，最后得出权重，这样的方法显然损失的信息更少。&lt;/p&gt;
 &lt;p&gt;2.2 DSTN&lt;/p&gt;
 &lt;p&gt;DSTN[11] 基于 Target attention 建模用户历史点击广告序列、未点击广告序列、附近的广告序列等信息，应用到神马搜索广告中。&lt;/p&gt;
 &lt;p&gt;在传统方法当我们要预估对一个广告的点击概率时，只考虑该广告的信息，而忽略了其他广告可能带来的影响。如用户历史点击或者曝光未点击的广告、当前上下文已经推荐过的广告等。因此，将这些广告作为辅助信息，加入到模型中，也许可以提升CTR预估的准确性。&lt;/p&gt;
 &lt;p&gt;多种行为序列的定义如下图所示：在用户发起搜索后，展示多个广告后用户会有点击、未点击的行为，以及附近的广告信息。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5469644e8ee74367aa2478020bcad757~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;​之前的模型可能一次计算所有广告的点击率，然后按点击率进行排序，取top-K进行展示。但 DSTN 中把一次推荐K个广告过程看作K个单次推荐的过程集合。先推荐第一个位置的广告，再推荐第二个位置的广告，，依次类推。在推荐第三个广告时，推荐的第一个广告和第二个广告便是这里所说的上下文广告。&lt;/p&gt;
 &lt;p&gt;为了将这些信息加入到模型中，必须要注意以下几点：&lt;/p&gt;
 &lt;p&gt;1）每种类型的辅助广告数量可能相差很多，模型必须适应这些所有可能的情况。&lt;/p&gt;
 &lt;p&gt;2）辅助的广告信息可能与目标广告是不相关的，因此，模型需要具备提取有效信息，而过滤无用信息的能力。举例来说，用户点击过的广告可能有咖啡广告、服装广告和汽车广告，当目标广告是咖啡相关的广告时，过往点击中咖啡相关的广告可能是起比较大作用的信息。&lt;/p&gt;
 &lt;p&gt;3）不同类型的辅助广告信息，有时候起到的作用可能是不同的，模型需要能够有能力对此进行判别。&lt;/p&gt;
 &lt;p&gt;总的来说，就是模型需要有能力有效处理和融合各方面的信息。&lt;/p&gt;
 &lt;p&gt;本文提出了DSTN（Deep Spatio-Temporal neural Networks）模型来处理和融合各种辅助广告信息。&lt;/p&gt;
 &lt;p&gt;DSTN 的模型如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b9dd63eb8bb246a088debb3e8430c577~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;其主要包含三个子模型，DNN 模型用来处理目标广告，Pooling 模型用来处理辅助广告，Interactive attention模型用来处理辅助广告和额外广告之间的交互关系。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;3.RNN 方法&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;基于pooling和attention的方法，不能建模行为序列的时间顺序特性，无法有效建模用户兴趣的不断演化。&lt;/p&gt;
 &lt;p&gt;为了解决这些问题，研究者们提出了基于RNN建模行为序列，主要包括 DIEN[12], DUPN [13], HUP [14], DHAN [15]等。它们通过RNN建模行为序列的时间顺序特性，能更好地建模用户实时兴趣的变化。&lt;/p&gt;
 &lt;p&gt;3.1 DIEN&lt;/p&gt;
 &lt;p&gt;DIEN [12] 基于双层RNN来建模用户的商品点击序列，应用在电商APP推荐广告排序中。同时，在模型提出了基于RNN中的隐藏向量和下一个商品计算辅助loss。第一层RNN，直接用来建模用户的商品点击序列；第二层RNN，基于第一层RNN的结果、待排序广告计算attention，用于RNN的更新。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/66e92cd5fcf7470c95eadcc2e5ccb084~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;​3.2 DUPN&lt;/p&gt;
 &lt;p&gt;DUPN[13] 基于RNN建模用户的行为序列，再基于attention计算每个行为的权重，得到用户的兴趣向量表示，应用到电商APP搜索中。其整体框架如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5e003f88bd9545d4912adcd1e0abeaf1~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;​ 在本论文中，作者在 RNN 和 Attention 中都考虑了行为的属性信息 (场景、时间、行为类型等)，如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f651d787f5eb44908bf43e3cf3a79f78~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;而且作者还利用多任务学习来学习模型参数：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/502ad571c1584703a828a6190e38d2ef~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;4. Capsule方法&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;在召回阶段，基于pooling, attention, RNN等序列模型建模行为序列得到一个用户兴趣向量，然后通过Faiss查找最近的邻居商品，通常召回的商品都很相似，无法满足搜索推荐多样性的需求。&lt;/p&gt;
 &lt;p&gt;为了解决这些问题，研究者们提出了基于 Capsule 建模行为序列，主要包括 MIND [14], ComiRec [15] 等。它们通过Capsule建模行为序列，通过隐式聚类行为序列，得到多个用户兴趣向量，从而实现召回多个类目的物品的目的。&lt;/p&gt;
 &lt;p&gt;4.1 MIND&lt;/p&gt;
 &lt;p&gt;MIND [14] 基于Capsule建模用户的行为序列向量，应用在天猫推荐的召回模块中, 其整体框架如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f414fd1179f3443dba4c6e79e5eb8848~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;MIND 中的用户特征由历史交互 item 和基础画像属性组成，用户历史交互 item 由 item ID,item 所属类目ID和Item所属品牌ID三部分组成，这三部分进行低维 embedding 之后做 average pooling，合成 item 本身的embedding表示。用户交互过的 items 生成的一组向量(即item embedding)作为 Multi-Interest Extractor Layer的输入，经过 dynamic routing 之后，产生另外一组向量(即兴趣embedding)，作为用户多样化兴趣的表示。用户基础画像属性的embedding与用户兴趣embedding分别做concat，接着经过两层ReLU隐层，得到用户的一组embedding。&lt;/p&gt;
 &lt;p&gt;在训练阶段，要进行预测的 label 只有一个 embedding，而用户有一组，没法直接求内积计算匹配度，这里MIND提出了Label-aware Attention，思路跟DIN是一致的，就是根据label的embedding对用户的一组embedding分别求出权重(所谓label-aware)，然后对用户的一组embedding求加权和，得到最终的一个embedding。图中右上角是Label-aware Attention的图示，K,V都是用户embedding(矩阵)，Q是label embedding(向量)，K、Q相乘可以得到不同的用户embedding对label的响应程度，Power对求得的响应做指数运算(可以控制指数的大小)，从而控制attention的程度，指数越大，响应高的用户embedding最终权重占比越大。Softmax 对结果进行归一化，最后一步的 MatMul 求得加权和。&lt;/p&gt;
 &lt;p&gt;MIND 借鉴了Hiton的胶囊网络(Capsule Network)，提出了Multi-Interest Extractor Layer来对用户历史行为embedding进行软聚类，算法如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f852960b0dca456d8991755a50a8c479~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;​ 4.2 ComiRec&lt;/p&gt;
 &lt;p&gt;ComiRec [15] 提出基于Capsule或Self-attention来建模行为序列，得到用户的多个兴趣向量，并使用一个可控的多兴趣召回结果聚合模块来平衡召回结果的准确性、多样性，应用到电商APP推荐召回模块中。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c1685f4906a546779c50ccbfb03db530~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;​ 5. Transformer 方法&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;基于RNN的序列建模模型，无法有效建模长序列、长依赖，以及行为序列内多个行为彼此间的关联关系。&lt;/p&gt;
 &lt;p&gt;为了解决这些问题，研究者们提出了基于Transformer建模行为序列，主要包括 ATRank [17], BST [4], DSIN [18], TISSA [19], SDM [20], KFAtt [21], DFN [22], SIM [23], DMT [24], AliSearch [17] 等。通过Transformer建模行为序列，逐步成为工业界搜索推荐行为序列建模的主流。&lt;/p&gt;
 &lt;p&gt;首先介绍一下 Transformer 方法，Transformer 模型的整体架构如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/be61cc5e51914d9ca5977f83eb4ca841~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;​ 这里面Multi-head Attention其实就是多个Self-Attention结构的结合，每个head学习到在不同表示空间中的特征，如下图所示，两个head学习到的Attention侧重点可能略有不同，这样给了模型更大的容量。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c43531c188e74bf5a0809ef24a928817~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;​ Self-Attention 的具体结构如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/63cd8502156649d7b99898b052cf95dc~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;​ 对于self-attention来讲，Q(Query), K(Key), V(Value) 三个矩阵均来自同一输入，首先我们要计算Q与K之间的点乘，然后为了防止其结果过大，会除以一个尺度标度 ，其中 为一个 query 和 key向量的维度。再利用Softmax操作将其结果归一化为概率分布，然后再乘以矩阵V就得到权重求和的表示。该操作可以表示为&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/41c99143e80048b886a1789c82e5db6e~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;​看一个将 Transformer 应用到翻译中的例子。“I arrived at the bank after crossing the river” 这里面的bank指的是银行还是河岸呢，这就需要我们联系上下文，当我们看到river之后就应该知道这里bank很大概率指的是河岸。在RNN中我们就需要一步步的顺序处理从bank到river的所有词语，而当它们相距较远时RNN的效果常常较差，且由于其顺序性处理效率也较低。Self-Attention则利用了Attention机制，计算每个单词与其他所有单词之间的关联，在这句话里，当翻译bank一词时，river一词就有较高的Attention score。利用这些Attention score就可以得到一个加权的表示，然后再放到一个前馈神经网络中得到新的表示，这一表示很好的考虑到上下文的信息。如下图所示，encoder读入输入数据，利用层层叠加的Self-Attention机制对每一个词得到新的考虑了上下文信息的表征。Decoder也利用类似的Self-Attention机制，但它不仅仅看之前产生的输出的文字，而且还要attend encoder的输出。&lt;/p&gt;
 &lt;p&gt;5.1 BST&lt;/p&gt;
 &lt;p&gt;BST[4] 基于Transformer建模行为序列，用于电商APP推荐。 BST 的模型结构主要是由 Embedding 层，Transformer 层与 MLP 层组成，如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c809db8762294b58b858ae84ca87af93~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;​ Embedding 层主要分为 Other Features, User Behavior Sequence, Target Item, Other Features 主要包括用户的基本特征、目标物品的基本特征、上下文信息、交叉特征等，先将每个大的种类特征内的所有内容进行拼接，再分别进行embedding映射为一个低维向量，最后得到一个embedding矩阵。User Behavior Sequence：由于表示一个序列特征，且不使用RNN神经网络，因此需要一个 Positional Feature 表示相对位置，Sequence Item Features表示历史物品信息。&lt;/p&gt;
 &lt;p&gt;自注意力机制层使用 self-attention[5] 论文中的多头自注意力机制。&lt;/p&gt;
 &lt;p&gt;MLP 层使用全连接神经网络。&lt;/p&gt;
 &lt;p&gt;BST 比较直接的将 Transformer 模型应用到推荐系统中，通过引入 Transformer Layer 来很好的利用了用户历史行为序列信息，最终在 Taobao 的数据集上取得了很好的效果。&lt;/p&gt;
 &lt;p&gt;5.2 AliSearch&lt;/p&gt;
 &lt;p&gt;AliSearch [16]使用Transformer分别建模用户的近期序列（点击，加购，购买的混合序列）、长期点击序列、长期购买序列、近期端上点击序列、近期端上曝光序列，用于电商APP搜索的CTR，CVR预测任务。其模型结构如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/71b4a5bbec7441989e0aef3e496a076c~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;AliSearch 模型结构大致所图所示，用户画像、多个行为序列特征、待打分的商品特征和一些其他的实时上下文特征（天气、网络、时间等特征），最终concat之后，进入DNN的分类器。行为序列建模是模型中最重要的部分之一，对于实时刻画用户兴趣尤其重要。在这里采用self atten和atten pooling的方式来做序列建模，self atten刻画行为之间的相互关系，atten pooling对行为进行匹配激活并实现combine。这是一个通用化的序列建模组件。&lt;/p&gt;
 &lt;p&gt;对于近期序列和端上点击序列，使用self-attention建模，然后使用user和query来解码得到用户近期兴趣向量。 DMT中是推荐系统所以使用target item来解码，AliSearch是搜索任务，所以使用user和query来解码（搜索对相关性要求高、不需要使用每个待排序商品解码效率更高）。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/31bec40a8ea84c4ebdfe31c127d8015d~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;​ 对于长期行为序列，作者将过去两年的行为分成8个季度序列。每个季度序列，使用self-atention建模，得到每个季度的兴趣表达。然后使用近期兴趣向量来解码多个长期行为序列，得到长期兴趣向量。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8c5cfd44b11246b4b809e5159013dbd2~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;​ 对于端上曝光未点击行为，使用pooling来建模。然后使用近期兴趣向量和曝光未点击兴趣向量，计算辅助loss。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/56a1f50de7a5464ba1aedb2ab43a5e84~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;6. 图神经网络方法&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;6.1 SURGE&lt;/p&gt;
 &lt;p&gt;在 SIGIR2021 中，Chang 等人将图卷积应用在序列特征建模中，提出 SURGE (SeqUential Recomendation with Graph neural nEtworks) 模型[3]。该模型主要分为四大部分，包括兴趣图构建，兴趣融合图卷积，兴趣图池化，和预测层。模型的框架如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f38fb5c015784e42b0a632b88c770ba6~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;兴趣图构建的主要目的是将用户的交互序列转变为一个兴趣图，而用户的核心兴趣就会体现在项目节点间的相关性上，而预测下一个点击项的任何就转换为通过用户的核心兴趣来判断交互发生的可能性。&lt;/p&gt;
 &lt;p&gt;兴趣融合卷积的目的是通过GNN的思想，利用上个阶段A得到的兴趣图 ，通过图卷积操作实现相关项目节点间的信息流通（即局部范围内有差别的邻居聚合）来形成多个代表用户不同兴趣的簇集群，这一过程类似于图表示学习任务常见的节点分类。而为了减轻聚合过程中产生的噪声，作者提出两种注意力，包括簇意识注意力和搜索意识注意力。&lt;/p&gt;
 &lt;p&gt;兴趣图池化有点类似于卷积神经网络中的池化操作，通过学习一个集群指定矩阵，再通过矩阵相乘的到不同的兴趣集群表示，然后利用图中每个节点的重要性分数，得到全局图表示。&lt;/p&gt;
 &lt;p&gt;预测层对于上一阶段得到的兴趣图进行位置平整的到精简版的兴趣序列，利用 AUGRU(GRU with attentional update gate) 输出用户层的表示，最后将用户表示，兴趣图表示以及下一个需要预测的项目表示进行级联(concat) 后接 MLP 层，得到最后 CTR 预估的结果。&lt;/p&gt;
 &lt;h1&gt;在 EasyRec 中将序列特征快速落地&lt;/h1&gt;
 &lt;p&gt;接下来以 DIN 模型为例介绍将序列特征快速落地。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;EasyRec 介绍&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;EasyRec[1] 是阿里云计算平台机器学习PAI团队开源的大规模分布式推荐算法框架，github 地址为：https://github.com/alibaba/EasyRec 。EasyRec 正如其名字一样，简单易用，集成了诸多优秀前沿的推荐系统论文思想，并且有在实际工业落地中取得优良效果的特征工程方法，集成训练、评估、部署，与阿里云产品无缝衔接，可以借助 EasyRec 在短时间内搭建起一套前沿的推荐系统。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;EasyRec 环境配置及运行&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;使用 EasyRec 可以见 EasyRec 文档[2], 快速运行 EasyRec 可以见教程：Local Tutorial — easy_rec 0.3.1 documentation 。首先数据必不可少，可以先参考 EasyRec 中自带的数据集配置，规范格式。其次需要配置文件，对于自有数据集，配置文件可以参考数据 — easy_rec 0.3.1 documentation ， 对于想先简单尝试 EasyRec 来说，可以直接使用 EasyRec 中自带的配置文件，见 samples/model_config 文件夹。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;序列特征数据前期准备&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;序列特征，是多个特征的集合，组成序列。构建序列特征一般分为两步，第一步是构建原始序列特征。以实际工作为例，原始序列特征一般用 &amp;quot;:&amp;quot; 来分隔特征名和值，用 &amp;quot;#&amp;quot; 来分隔多个特征，用 &amp;quot;;&amp;quot; 来分隔多个序列。&lt;/p&gt;
 &lt;p&gt;第二阶段是预处理。预处理的目的是要处理成 EasyRec 可以接受的处理方式，预处理后每个单独的序列特征要放在一起。如对于 item__svid 特征，就要把序列中的所有的 item__svid 特征都要挑出来，放在一起，可以用 &amp;quot;|&amp;quot; 分隔，在 EasyRec 中配置好后就可以进行训练。此处还要注意要保证离线在线一致性。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;在 Easyrec 中使用序列特征&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;序列特征在 EasyRec 中有专门的示例 config ，可以查看位于 samples/model_config/din_on_taobao.config 的配置文件，在配置文件中序列特征可用如下表示：&lt;/p&gt;
 &lt;p&gt;feature_configs : {&lt;/p&gt;
 &lt;p&gt;input_names: &amp;apos;tag_brand_list&amp;apos;&lt;/p&gt;
 &lt;p&gt;feature_type: SequenceFeature&lt;/p&gt;
 &lt;p&gt;separator: &amp;apos;|&amp;apos;&lt;/p&gt;
 &lt;p&gt;hash_bucket_size: 100000&lt;/p&gt;
 &lt;p&gt;embedding_dim: 16&lt;/p&gt;
 &lt;p&gt;}&lt;/p&gt;
 &lt;p&gt;feature_configs : {&lt;/p&gt;
 &lt;p&gt;input_names: &amp;apos;tag_category_list&amp;apos;&lt;/p&gt;
 &lt;p&gt;feature_type: SequenceFeature&lt;/p&gt;
 &lt;p&gt;separator: &amp;apos;|&amp;apos;&lt;/p&gt;
 &lt;p&gt;hash_bucket_size: 100000&lt;/p&gt;
 &lt;p&gt;embedding_dim: 16&lt;/p&gt;
 &lt;p&gt;}&lt;/p&gt;
 &lt;p&gt;其对应的两个特征的原始输入形式为：&lt;/p&gt;
 &lt;p&gt;4281|4281|4281|4281|4281|4281|4281|4281|4281|4281|4281|4281|4281|4281|4526|4526,283837|283837|283837|283837|283837|283837|283837|283837|283837|283837|283837|283837|283837|283837|367594|367594&lt;/p&gt;
 &lt;p&gt;这个配置文件使用了 DIN 模型，具体细节可以参考对应的论文[6]。 离线训练完成后，可以再参考文档[8]进行导出操作，部署到线上，快速应用到推荐场景中。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;参考文献&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;[1] EasyRec : https://github.com/alibaba/EasyRec&lt;/p&gt;
 &lt;p&gt;[2] EasyRec 文档：Welcome to easy_rec’s documentation! — easy_rec 0.3.1 documentation&lt;/p&gt;
 &lt;p&gt;[3] Chang J, Gao C, Zheng Y, et al. Sequential Recommendation with Graph Neural Networks[C]//Proceedings of the 44th International ACM SIGIR Conference on Research and Development in Information Retrieval. 2021: 378-387.&lt;/p&gt;
 &lt;p&gt;[4] Chen Q, Zhao H, Li W, et al. Behavior sequence transformer for e-commerce recommendation in alibaba[C]//Proceedings of the 1st International Workshop on Deep Learning Practice for High-Dimensional Sparse Data. 2019: 1-4.&lt;/p&gt;
 &lt;p&gt;[5] Vaswani A, Shazeer N, Parmar N, et al. Attention is all you need[C]//Advances in neural information processing systems. 2017: 5998-6008.&lt;/p&gt;
 &lt;p&gt;[6] Yu Z, Lian J, Mahmoody A, et al. Adaptive User Modeling with Long and Short-Term Preferences for Personalized Recommendation[C]//IJCAI. 2019: 4213-4219.&lt;/p&gt;
 &lt;p&gt;[7] Koren Y. Factorization meets the neighborhood: a multifaceted collaborative filtering model[C]//Proceedings of the 14th ACM SIGKDD international conference on Knowledge discovery and data mining. 2008: 426-434.&lt;/p&gt;
 &lt;p&gt;[8] EasyRec 导出文档：导出 — easy_rec 0.3.1 documentation&lt;/p&gt;
 &lt;p&gt;[9] Zhou G, Zhu X, Song C, et al. Deep interest network for click-through rate prediction[C]//Proceedings of the 24th ACM SIGKDD International Conference on Knowledge Discovery &amp;amp; Data Mining. 2018: 1059-1068.&lt;/p&gt;
 &lt;p&gt;[10] Covington, Paul, Jay Adams, and Emre Sargin. &amp;quot;Deep neural networks for youtube recommendations.&amp;quot; Recsys&amp;apos;16&lt;/p&gt;
 &lt;p&gt;[11] Ouyang, Wentao, Xiuwu Zhang, Li Li, Heng Zou, Xin Xing, Zhaojie Liu, and Yanlong Du. &amp;quot;Deep spatio-temporal neural networks for click-through rate prediction.&amp;quot; KDD&amp;apos;19.&lt;/p&gt;
 &lt;p&gt;[12] Zhou, Guorui, Na Mou, Ying Fan, Qi Pi, Weijie Bian, Chang Zhou, Xiaoqiang Zhu, and Kun Gai. &amp;quot;Deep interest evolution network for click-through rate prediction.&amp;quot; AAAI&amp;apos;19.&lt;/p&gt;
 &lt;p&gt;[13] Ni, Yabo, Dan Ou, Shichen Liu, Xiang Li, Wenwu Ou, Anxiang Zeng, and Luo Si. &amp;quot;Perceive your users in depth: Learning universal user representations from multiple e-commerce tasks.&amp;quot; KDD&amp;apos;18.&lt;/p&gt;
 &lt;p&gt;[14] Li, Chao, Zhiyuan Liu, Mengmeng Wu, Yuchi Xu, Huan Zhao, Pipei Huang, Guoliang Kang, Qiwei Chen, Wei Li, and Dik Lun Lee. &amp;quot;Multi-interest network with dynamic routing for recommendation at Tmall.&amp;quot; CIKM&amp;apos;19.&lt;/p&gt;
 &lt;p&gt;[15] Cen, Yukuo, Jianwei Zhang, Xu Zou, Chang Zhou, Hongxia Yang, and Jie Tang. &amp;quot;Controllable Multi-Interest Framework for Recommendation.&amp;quot; KDD&amp;apos;20.&lt;/p&gt;
 &lt;p&gt;[16] [AliSearch]: 搜索模型核心技术公开，淘宝如何做用户建模？&lt;/p&gt;
 &lt;p&gt;[17] Zhou, Chang, Jinze Bai, Junshuai Song, Xiaofei Liu, Zhengchao Zhao, Xiusi Chen, and Jun Gao. &amp;quot;Atrank: An attention-based user behavior modeling framework for recommendation.&amp;quot; AAAI&amp;apos;18.&lt;/p&gt;
 &lt;p&gt;[18] Feng, Yufei, Fuyu Lv, Weichen Shen, Menghan Wang, Fei Sun, Yu Zhu, and Keping Yang. &amp;quot;Deep session interest network for click-through rate prediction.&amp;quot; IJCAI&amp;apos;19.&lt;/p&gt;
 &lt;p&gt;[19] Lei, Chenyi, Shouling Ji, and Zhao Li. &amp;quot;Tissa: A time slice self-attention approach for modeling sequential user behaviors.&amp;quot; WWW&amp;apos;19.&lt;/p&gt;
 &lt;p&gt;[20] Lv, Fuyu, Taiwei Jin, Changlong Yu, Fei Sun, Quan Lin, Keping Yang, and Wilfred Ng. &amp;quot;SDM: Sequential deep matching model for online large-scale recommender system.&amp;quot; CIKM&amp;apos;19.&lt;/p&gt;
 &lt;p&gt;[21] Liu, Hu, Jing Lu, Xiwei Zhao, Sulong Xu, Hao Peng, Yutong Liu, Zehua Zhang et al. &amp;quot;Kalman Filtering Attention for User Behavior Modeling in CTR Prediction.&amp;quot; NIPS&amp;apos;20.&lt;/p&gt;
 &lt;p&gt;[22] Xie, Ruobing, Cheng Ling, Yalong Wang, Rui Wang, Feng Xia, and Leyu Lin. &amp;quot;Deep Feedback Network for Recommendation.&amp;quot; IJCAI&amp;apos;20.&lt;/p&gt;
 &lt;p&gt;[23] Pi, Qi, Guorui Zhou, Yujing Zhang, Zhe Wang, Lejian Ren, Ying Fan, Xiaoqiang Zhu, and Kun Gai. &amp;quot;Search-based User Interest Modeling with Lifelong Sequential Behavior Data for Click-Through Rate Prediction.&amp;quot; CIKM&amp;apos;20.&lt;/p&gt;
 &lt;p&gt;[24] Gu, Yulong, Zhuoye Ding, Shuaiqiang Wang, Lixin Zou, Yiding Liu, and Dawei Yin. &amp;quot;Deep Multifaceted Transformers for Multi-objective Ranking in Large-Scale E-commerce Recommender Systems.&amp;quot; CIKM&amp;apos;20.&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://click.aliyun.com/m/1000320669/"&gt;原文链接&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;本文为阿里云原创内容，未经允许不得转载。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62062-%E5%BA%8F%E5%88%97-%E7%89%B9%E5%BE%81-%E6%8E%A8%E8%8D%90%E7%AE%97%E6%B3%95</guid>
      <pubDate>Wed, 26 Jan 2022 10:07:03 CST</pubDate>
    </item>
    <item>
      <title>线上流量对比应用实践</title>
      <link>https://itindex.net/detail/62044-%E7%BA%BF%E4%B8%8A-%E6%B5%81%E9%87%8F-%E5%BA%94%E7%94%A8</link>
      <description>&lt;h4&gt;一、流量比对提出背景&lt;/h4&gt;
 &lt;p&gt;我们在进行代码重构以及需求迭代时，在上线之前需要进行一轮、二轮以及回归测试，如果业务场景比较复杂，那么就会存在以下几个方面的问题：&lt;/p&gt;
 &lt;p&gt;（1）测试的周期就会相应的拉的比较长；&lt;/p&gt;
 &lt;p&gt;（2）随着业务功能的不断迭代，测试案例不一定能够覆盖全，一些冷门的隐藏比较深的问题不一定可以及时发现，回归测试难度逐渐增大；&lt;/p&gt;
 &lt;p&gt;（3）影响发布频率；&lt;/p&gt;
 &lt;p&gt;（4）开发以及测试人员消耗在测试阶段时间比较长，不利于腾出时间进行下一步的业务迭代、技术改造等工作；&lt;/p&gt;
 &lt;p&gt;（5）代码质量很多时候依赖人为的code review，迭代代码行较多，code review占用时间较多；&lt;/p&gt;
 &lt;p&gt;为此，我们能否解决这些痛点，使得我们的工作效率得到一定的提升，以及我们代码的质量得到一定的保证，是我们需要思考的一个问题。业内的做法一帮将线上的流量引入对比机器，将生产结果和对比机器进行实时对比，及时暴露问题，开发和测试能够及时发现并进行修改。&lt;/p&gt;
 &lt;h4&gt;二、和流量回放平台的对比&lt;/h4&gt;
 &lt;p&gt;目前业内的流量回放平台基本都是基于流量的录制，将流量引到测试环境进行回放，回放后分析系统存在的问题，在一定程度上只能释放一些回归测试的资源，并不能使我们开发中存在的问题在提测之前可以及时暴露，提前让开发人员解决问题，提高代码提测的质量，我们的目标是要做到即使没有测试参与，也要保证我们的代码质量，如果对线上存量业务的有影响，能够及时得到反馈，形成一个闭环处理，一个实时性的比对方案是很有必要的。&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;三、流量比对方案&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;对于一般的业务系统，我们比对的维度一般包括数据库落地数据、mq消息、接口调用请求参数以及输出四个维度，一般情况下在对比流量是整个生产流量50%情况下，基本可以覆盖生产案例场景，在此基础上针对这三个维度对比结果一致率接近100%，那么我们的上线的迭代版本对存量业务的影响是可以得到保证的。主要思路是构建请求报文，执行请求再到分析比对结果。&lt;/p&gt;
 &lt;h5&gt;（1）整体架构&lt;/h5&gt;
 &lt;p&gt;当生产应用处理完对应请求时，所有涉及接口的调用，mq消息的发送等埋点信息都会被记录到ES（鉴于ES是分布式、高扩展、高实时的搜索和数据分析引擎，通过其提供的接口，可以很方便的获取埋点信息），这时候会发出一条mq消息，对比工具监听mq消息进行消费，根据对应的订单信息，查询生产订单数据并进行mock，然后调用对比应用，对比工具延迟一定时间后，根据需要对比的内容进行对比，将比对结果发送ES，可以根据ES对比结果埋点查看对比一致率以及不一致的具体原因。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0f45a982b4ab48c7a5ff473b13a56360~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;注&lt;/strong&gt;：生产应用：实际的生产应用集群&lt;/p&gt;
 &lt;p&gt;对比应用：我们的迭代版本部署集群&lt;/p&gt;
 &lt;p&gt;对比工具：单独专门用来跑对比的一个应用&lt;/p&gt;
 &lt;h5&gt;（2）数据埋点&lt;/h5&gt;
 &lt;p&gt;数据买点是比对的前提，包括接口请求的埋点、q消息的埋点，输入请求的埋点，后续对比工具读取数据进行比对&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a694e1c0afee4707b0055871a6fd549d~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h5&gt;（3）数据mock&lt;/h5&gt;
 &lt;p&gt;对比应用不能实际操作db以及和上下游系统进行交互，所有数据需要进行mock，mock的数据主要是调用下游接口的应答数据，mock的数据可以写入redis，对比应用在处理业务逻辑时，可以直接读取redis相应的mock数据作为返回。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aa05f27c575444b6a6b13b51bd0382b0~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h5&gt;（4）对比配置+数据对比&lt;/h5&gt;
 &lt;p&gt;对比配置一般配置在配置中心，可以根据实际对比的维度进行配置化管理,对比表字段的对比，比如一些时间，可以配置成忽略字段，接口和mq里面的一些参数也可以进行配置化忽略对比，比如订单号、发送时间之类。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/102d80a4f9f64810aea7dcf01f893cdc~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h5&gt;（5）结果一致率分析&lt;/h5&gt;
 &lt;p&gt;对比工具可以将对比结果发送至ES，我们可以通过ES根据具体的埋点actionType筛选对应的比对结果，一致率和不一致原因。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5bb093287c81496794ba82a0324d71a1~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;四、总结&lt;/h4&gt;
 &lt;p&gt;线上流量对比方案总体是基于基本的业务埋点，对一个业务流程里面所涉及到的具体接口，mq消息都需要有一个全面的梳理，需要指出的是，想要真正利用实时对比方案提前知道迭代风险的前提时，我们可以随时发布对比应用，这一点上面应该不要做限制。 我们的愿景是在即使没有测试参与的情况下，开发也可以保证上线代码的安全性，开发自己可以形成闭环。此对比方案存在一些优缺点如下：&lt;/p&gt;
 &lt;h5&gt;优点&lt;/h5&gt;
 &lt;p&gt;（1）可以及时发现代码重构以及版本迭代改动中存在的问题，并及时修复，不必等到提测之后再由测试发现，潜在问题可以提前暴露。不必过度依赖测试来发现问题；&lt;/p&gt;
 &lt;p&gt;（2）一定程度上释放回归测试的资源；&lt;/p&gt;
 &lt;p&gt;（3）保证整个研发过程的质量；&lt;/p&gt;
 &lt;p&gt;（4）可以提升发布频率；&lt;/p&gt;
 &lt;p&gt;（5）对比应用不需要单独搭建一套对比数据库用于对比，降低对比成本；&lt;/p&gt;
 &lt;p&gt;（6）对比应用和对比工具作为非核心应用，随改随发，有一定的灵活性；&lt;/p&gt;
 &lt;p&gt;（7）对比内容灵活化配置；&lt;/p&gt;
 &lt;h5&gt;缺点&lt;/h5&gt;
 &lt;p&gt;（1）对代码有一定程度的入侵，需要发出一个对比的mq消息&lt;/p&gt;
 &lt;p&gt;（2）对于增量业务，因为其没有对比参考对象，目前还没有有效的方式确保代码质量，只有通过ut单元测试、代码code review等手段进行辅助。&lt;/p&gt;
 &lt;p&gt;文/HUZHIMIN&lt;/p&gt;
 &lt;p&gt;关注得物技术，做最潮技术人！&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62044-%E7%BA%BF%E4%B8%8A-%E6%B5%81%E9%87%8F-%E5%BA%94%E7%94%A8</guid>
      <pubDate>Tue, 18 Jan 2022 03:12:34 CST</pubDate>
    </item>
  </channel>
</rss>

