<?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>阿里巴巴发布优化运行国产大模型的 RISC-V 服务器芯片</title>
      <link>https://itindex.net/detail/63186-%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4-%E4%BC%98%E5%8C%96-%E5%9B%BD%E4%BA%A7</link>
      <description>阿里巴巴发布了优化运行国产大模型的 RISC-V 服务器芯片玄铁 C950，原生支持 Qwen3、DeepSeek V3 等千亿参数大模型。阿里巴巴称玄铁 C950 单核通用性能在 Specint2006 基准测试中突破 70 分，刷新了全球 RISC-V 性能纪录。Google 研究员 Laurie Kirk 称玄铁 C950 的性能与苹果在 2020 年发布的 M1 芯片差不多。玄铁 C950  实现了 2025 年发布的 RISC-V RVA v23.1。该芯片使用 5 纳米工艺制造。
 &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/63186-%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4-%E4%BC%98%E5%8C%96-%E5%9B%BD%E4%BA%A7</guid>
      <pubDate>Wed, 25 Mar 2026 15:53:08 CST</pubDate>
    </item>
    <item>
      <title>[Apple] 入门款 4499 的 M4 Mac Mini 也许是性价比最高的高性能媒体服务器？</title>
      <link>https://itindex.net/detail/62953-apple-m4-mac</link>
      <description>&lt;p&gt;作为一个拥有约 150 用户，日均总观时达到 8 个小时的 Jellyfin 服务器运营者，在跟 Jellyfin 服务器打了一年多的交道之后，我意识到 Jellyfin 服务器能够让这么多人顺场地使用，还是对性能有所依赖的。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://geelao-oss.oss-cn-hangzhou.aliyuncs.com/db/202410311038570.png?x-oss-process=style/jpeg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;目前我的服务器通过 docker 跑在一台 N6005 的 NAS 上，和存储服务丢在一起。性能的羸弱对体验的影响显而易见：无法同时承载 2 条以上的并行 4K HEVC 转码、肉眼可见的缩略图加载时间、连转 7 天以上才能完成的首次媒体库扫描、生成 Trickplay 耗时过长等。&lt;/p&gt;
 &lt;p&gt;以前我也搜索过一些 Homalab 的成果，大部分人提到媒体服务器，会是&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;一部分高阶用户会搞一台    &lt;strong&gt;E5 的旧世纪服务器，或者淘汰的 R730&lt;/strong&gt;，配一张 DG1 或者 A380：&lt;/p&gt;
   &lt;p&gt;22nm 的古老科技空转 100w ，跑码 200w ，而且整体性能可能打不过现在的笔记本低压芯片。&lt;/p&gt;
   &lt;p&gt;优点是带“ECC”，以及成品主板有时候会有 IPMI 或者 BMC 的带外管理&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;一台树莓派&lt;/strong&gt;，因为性能过于落后在 Jellyfin 10.10 已经被放弃支持，包括整个 arm32 平台&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;N100 廉价盒子&lt;/strong&gt;：&lt;/p&gt;
   &lt;p&gt;对于家用（不是我这种 150 用户的工况）处于性能甜点，&lt;/p&gt;
   &lt;p&gt;但受本身产品定位限制，外围配置给得比较丐，主要体现在无法更换的有线网卡、SODIMM 内存以及单 M2 ，可靠性欠佳&lt;/p&gt;
   &lt;p&gt;（目前我家里用一台 N100 的盒子当作跳板机，我发现在跑 Windows11 Pro 的时候，连续运行 90 天以上会让系统进入一个近乎崩溃的状态，CPU 占用不高，内存占用良好，但是所有操作的响应都变慢到秒级，例如连接 RDP 、打开文件管理器等，最后会所有功能都会逐渐失效，连 RDP 也无法连接，只能强制重启）&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;MODT 平台的主板&lt;/strong&gt;：&lt;/p&gt;
   &lt;p&gt;缺点和 N100 差不多，可靠性欠佳，高速网卡的缺失可以用 PCIE 补齐。&lt;/p&gt;
   &lt;p&gt;并且做 MODT 平台主板的小厂设计经验不足，看过不少反馈说稳定性并不太好&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;MODT 平台的盒子&lt;/strong&gt;：&lt;/p&gt;
   &lt;p&gt;Intel 笔记本芯片，i5-i7 甚至 13900H 都有，&lt;/p&gt;
   &lt;p&gt;能耗比相比旧世纪 E5 是质的提升，并且 laptop 平台的部分 Iris 核显比桌面平台还强得多，某些厂子出于软路由的用户需求甚至给了 10G SFP+，不会构成 NFS 的读写瓶颈。&lt;/p&gt;
   &lt;p&gt;缺点是价格偏贵，而且因为 AMD 越来越强，规模大的厂子高端 MODT 都逐渐转向锐龙，i9h 级别的高端盒子在 discounted 后可能再无后续，但我们又都知道锐龙的核显编解码就是一坨，并不适合媒体服务器的需求。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;因为我预算偏高，打算用一台机器单独供着 Jellyfin+Metatube ，其它什么都不干，所以综合上面的调查结果，铭凡的这台盒子一直放在我的购物车里，5399 的价格让我迟迟下不去手，硬件配置合适，但价格实在是太™贵了&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://geelao-oss.oss-cn-hangzhou.aliyuncs.com/db/202410311109140.png?x-oss-process=style/jpeg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;自拥有了 ATV 4K 之后，我一直在等待苹果能够给我们带来下一代能 HDMI Passthrough ，HDMI2.1 4K120 的硬件，结果没等来，等来了个跟 ATV 长得一模一样的下一代 M4 mac mini&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://geelao-oss.oss-cn-hangzhou.aliyuncs.com/db/202410311114125.png?x-oss-process=style/jpeg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;好家伙这一看我发现不得了。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;本身 M1 就已经完全够跑 Jellyfin ，据 Jellyfin 项目组自己的测试，M1 可以并行跑 3 条 4K 转码，那么下下下代 M4 更不用说，性能上是绝对的力大砖飞（但是目前转码支持的制式相比 Intel Graphics 少，参考    &lt;a href="https://jellyfin.org/docs/general/administration/hardware-acceleration/apple/" rel="nofollow"&gt;https://jellyfin.org/docs/general/administration/hardware-acceleration/apple/&lt;/a&gt; ）&lt;/li&gt;
  &lt;li&gt;TSMC 3 工艺加上苹果的硬件-系统垂直开发能力，运行能耗比甚至更优于 Intel 的 H 平台，名副其实的省电王&lt;/li&gt;
  &lt;li&gt;因为 Apple Intelligence 的缘故，同样价格入门款标配了 16GB 内存，如果是前代 8GB 跑 Jellyfin 很有可能内存吃紧&lt;/li&gt;
  &lt;li&gt;+750 就能获得 10GBASET ，市面上的雷电 10G 网卡价格普遍要翻倍，以 QNAP 的产品为例
   &lt;img alt="" src="https://geelao-oss.oss-cn-hangzhou.aliyuncs.com/db/202410311123729.png?x-oss-process=style/jpeg"&gt;&lt;/img&gt;&lt;/li&gt;
  &lt;li&gt;macos 虽然不好用，但是相比纯命令行调试，白送个 GUI&lt;/li&gt;
  &lt;li&gt;Apple 出厂的产品，可靠性测试流程必然优于小厂产品，SLA 有更高保障，并且相比类似价位的无头 Macbook 还有保修&lt;/li&gt;
  &lt;li&gt;可以国补 9 折，成品比上面准系统还便宜很多&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;反正我很心动，已经在研究定制+10G 网卡的同时还能走国补的方案了&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/62953-apple-m4-mac</guid>
      <pubDate>Thu, 31 Oct 2024 11:29:49 CST</pubDate>
    </item>
    <item>
      <title>如何进行微服务的划分</title>
      <link>https://itindex.net/detail/62913-%E4%BD%95%E8%BF%9B-%E5%BE%AE%E6%9C%8D%E5%8A%A1</link>
      <description>&lt;div&gt;    &lt;div&gt;      &lt;p&gt;本文是我在 2021 GIAC 上的一次分享        &lt;a href="https://link.zhihu.com/?target=https%3A//giac.msup.com.cn/teacher%3Fid%3D8281" rel="nofollow noreferrer" target="_blank"&gt;《Hints for Microservice design》&lt;/a&gt;的精简过后的讲稿。&lt;/p&gt;      &lt;h2&gt;前言&lt;/h2&gt;      &lt;p&gt;本次分享的内容主要是关于业务架构的讨论，业务架构不像技术架构一样有明确的好坏衡量标准，例如评价数据库的技术架构好不好，就可以在限定的资源限制下去 benchmark 进行评判，但是业务架构不能 benchmark。你现在不能证明某个业务架构是好的，未来也不能。所以我把我分享的内容称为        &lt;strong&gt;hint&lt;/strong&gt;，而不是 Guide，Principle；这些只是写个人经验的简单总结而已，所以「Don&amp;apos;t Belive Me！」&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;本次分享总共分为两大部分&lt;/p&gt;      &lt;ul&gt;        &lt;li&gt;提出问题&lt;/li&gt;        &lt;li&gt;如何衡量是否解决了问题和可能的解决方法&lt;/li&gt;&lt;/ul&gt;      &lt;p&gt;即「        &lt;strong&gt;期待解决的问题，目标，现状&lt;/strong&gt;」和「        &lt;strong&gt;如何设计架构，有哪些维度，好的标准是什么&lt;/strong&gt;」&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;h2&gt;期待解决的问题，目标，现状&lt;/h2&gt;      &lt;p&gt;我们不从微服务的定义开始进行讨论，而是从我们期望解决的问题开始进行讨论。&lt;/p&gt;      &lt;h3&gt;期望微服务解决什么问题？&lt;/h3&gt;      &lt;p&gt;首先需要回答一个问题是「研发团队做得好坏的标准是什么」？我个人喜欢用        &lt;a href="https://link.zhihu.com/?target=https%3A//cloud.google.com/blog/products/devops-sre/using-the-four-keys-to-measure-your-devops-performance" rel="nofollow noreferrer" target="_blank"&gt;four key metrics&lt;/a&gt;进行衡量。即&lt;/p&gt;      &lt;ul&gt;        &lt;li&gt;Deployment Frequency—How often an organization successfully releases to production.&lt;/li&gt;        &lt;li&gt;Lead Time for Changes—The amount of time it takes a commit to get into production.&lt;/li&gt;        &lt;li&gt;Change Failure Rate—The percentage of deployments causing a failure in production.&lt;/li&gt;        &lt;li&gt;Time to Restore Service—How long it takes an organization to recover from a failure in production.&lt;/li&gt;&lt;/ul&gt;      &lt;p&gt;这四个指标从研发速度和研发质量两个维度进行了衡量。&lt;/p&gt;      &lt;p&gt;架构设计成什么样子有利于提高研发的速度和质量呢？其实是尽可能的自治，不互相依赖，自己搞自己的；我在公司的技术群里发起过一个投票「期望微服务解决什么问题？」，也一定程度印证了这个观点；&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;h3&gt;现状&lt;/h3&gt;      &lt;p&gt;现状是怎样的呢？大部分业务系统如下面两个动画所示&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='320' height='240'&gt;&lt;/svg&gt;" width="320"&gt;&lt;/img&gt;      &lt;p&gt;        &lt;br /&gt;&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='320' height='240'&gt;&lt;/svg&gt;" width="320"&gt;&lt;/img&gt;      &lt;h3&gt;目标&lt;/h3&gt;      &lt;p&gt;理想的情况又是怎样的呢？如果用一张图表示，我希望理想的业务系统可以像乐高一样，每一类乐高组件是一个子系统（业务能力），可以利用不同类的组件基础能力组合成复杂的业务能力（系统）。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;那我们来看看大部分公司的真实拆分微服务的历程，最后有没有达到像乐高一样各组件尽可能的自治，又能拼装出来复杂业务系统的目的。即「满足一定规范的尽可能的自治，又能自由组合」。接下来我们来看拆分案例&lt;/p&gt;      &lt;h3&gt;案例&lt;/h3&gt;      &lt;p&gt;最开始业务模型比较简单，要满足的需求比较少，只有一个系统。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;后来发现供应链很重要，出现问题就是重大故障。所以需要把供应链系统单独拆出来单独维护。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;后来大家发现课时 CRUD 模块是公用模块也拆出来吧&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;拆着，拆着就总结出了如下几条规律&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;最后把系统拆成了这个样子，一个十分复杂的系统&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;根据我们总结的规律最后拆分出来的系统，做到「在一定的规范下尽可能的自治」了吗？解决了所谓的耦合问题了吗？貌似没有解决，而且出现了下面的新问题。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;那么如何衡量是否做到了「在一定的规范下尽可能的自治」，标准是什么？又如何做到呢？我们在第二部分进行讨论&lt;/p&gt;      &lt;h2&gt;        &lt;strong&gt;如何设计架构，有哪些维度，好的标准是什么&lt;/strong&gt;&lt;/h2&gt;      &lt;h3&gt;从哪些维度设计架构？&lt;/h3&gt;      &lt;p&gt;1995 年有一篇相关的论文        &lt;a href="https://link.zhihu.com/?target=https%3A//www.cs.ubc.ca/~gregor/teaching/papers/4%2B1view-architecture.pdf" rel="nofollow noreferrer" target="_blank"&gt;Architectural Blueprints—The “4+1” View Model of Software Architecture&lt;/a&gt;即围绕业务场景从四个视角设计和观测架构。借鉴这个思路，但是我们简化一下重点讨论两个维度&lt;/p&gt;      &lt;ul&gt;        &lt;li&gt;Logical view: 业务架构，业务模块的设计与划分&lt;/li&gt;        &lt;li&gt;Process view：物理运行时架构，运行时进程组的设计与划分&lt;/li&gt;&lt;/ul&gt;      &lt;p&gt;其实也就是要达到的目的「自治要达到业务模块和物理模块（物理层面的弹性与隔离）的自治」&lt;/p&gt;      &lt;p&gt;        &lt;br /&gt;&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;Logical view 可以通过部署与 Process view 进行对应&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;那接下来我们开始讨论 Logical view 和 Process view&lt;/p&gt;      &lt;h3&gt;Logical view（业务架构）&lt;/h3&gt;      &lt;p&gt;提起业务业务架构我们一般会想到哪些建模方法和指导原则？更重要的是衡量业务架构的好的标准是什么？因为只要把好的标准搞清楚了，才能真正的讨论问题，&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;这些我们经常提到的方法和原则实际能帮到我们吗？确实有一定的指导意义。但是它们还是太笼统了，不够具体。不够具体造成的问题&lt;/p&gt;      &lt;ul&gt;        &lt;li&gt;不同的人理解差距太大，不能达成共识&lt;/li&gt;        &lt;li&gt;不能真正的实施，不能通过工具以数据的方式体现&lt;/li&gt;        &lt;li&gt;极难进行知识的传达&lt;/li&gt;&lt;/ul&gt;      &lt;p&gt;那么好的标准到底是什么呢？接下来我们尝试通过一些现象来总结一下&lt;/p&gt;      &lt;p&gt;        &lt;strong&gt;分层&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;下图是我们写代码时最常见的结构，将代码分成了这几层。对应到微服务上很容易地变成按照层次拆分成四类（DAO Service Application View）服务的实践。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;下图是我在        &lt;a href="https://www.zhihu.com/question/314356555/answer/625772570"&gt;知乎的技术架构是什么样的？&lt;/a&gt;中的一张关于基础架构的一张架构图。也是明显的分层架构。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;从业务架构视角来看，与上图的分层最大的区别是什么呢？&lt;/p&gt;      &lt;p&gt;最大的区别是第一个分层很多时候并没有达到分层的目的，很多时候一个新需求或者修改一个已有的需求都需要从头（view）改到尾（DAO），层与层之间的接口是不稳定的；第二张图里层与层之间的接口是稳定的，一般一个新需求只改一层就可以了，层与层是不知道彼此的存在的。&lt;/p&gt;      &lt;p&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;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;这个架构为什么比较清晰？&lt;/p&gt;      &lt;p&gt;因为模块除了 feed 之外模块之间没有任何数据和逻辑共享，彼此之前不互相影响。&lt;/p&gt;      &lt;p&gt;从业务架构角度还有哪些可以优化的地方吗？&lt;/p&gt;      &lt;p&gt;也许由 Feed  定义接口其他的系统（文章，问答，广告，知识市场）来实现会更好。这样更能保证接口的稳定。即业务架构上将 Feed 系统放到下边；下边电商的例子也会提到&lt;/p&gt;      &lt;p&gt;        &lt;strong&gt;电商（订单，价格）&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;        &lt;strong&gt;订单&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;电商领域一般被认为是比较复杂的，电商团队一般也是加班最多，最繁忙的；那我们一起来分析下单的这个业务。即用户点击「提交订单」，相关信息（收货人信息，支付方式，送货清单，发表信息，优惠券等）被保存，然后开始履约。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;对于这个业务场景，最直观地业务架构设计是将所有的信息存储到订单系统里，然后各方履约的时候从订单系统里获取（getOrderByID）。这样的设计十分容易理解。如果部分履约系统发生了发生了业务变化是怎样的；例如买了免费商品不需要付费了怎么办？新增了某种支付方式或者开发票的信息怎么办？都需要通知订单系统进行修改，进行接口的变动，跨团队的沟通。是不够自治，十分影响开发效率的。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;可以换一种思路来设计，最极端情况下订单是不是可以只一个 orderID，然后各个履约方存储自己关心的信息（不让订单系统代为存储），并且与 orderID 关联。这样各个履约方如果发生业务迭代的时候只需要改动自己的系统和存储就可以，与其他系统业务相对隔离，保持足够的自治。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;        &lt;strong&gt;价格&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;再看下图中右下角的价格，价格理论上是根据多方的信息计算出来的。例如商品的原始价格，优惠券，支付方式，配送方式等；实现价格系统时我当然可以把所有的信息都存储在一起，并且把所有价格相关的计算逻辑也写到一个模块里；这样维护价格的团队可能是全公司最忙的了，因为每个业务系统都想改变价格，频繁调整价格来强调自己业务的重要性&amp;lt;(*￣▽￣*)/。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;价格计算有两种计算逻辑。一种是类似        &lt;a href="https://link.zhihu.com/?target=https%3A//www.wikiwand.com/en/Chain-of-responsibility_pattern" rel="nofollow noreferrer" target="_blank"&gt;Chain-of-responsibility pattern&lt;/a&gt;，价格计算按照一定顺序交给各个系统轮流处理，报价系统按照特定顺序进行调用即可。另一类稍微复杂一些，必须根据各方系统的信息综合判断才能计算出价格，例如用户是会员，持有特定优惠券，并且选择特定支付方式的前提下才能计算出最后的价格，这种情况下报价系统从各方系统获取必要信息，进行小部分的计算的集成算出最后的价格&lt;/p&gt;      &lt;p&gt;这里需要特别注意的是：接口一定是由报价系统定义，然后由其他系统（优惠券，会员，团购，支付等）来实现的。而不是直接调用其他系统暴露出来的 getCouponByID 类似的接口，这样才能更好的保持在价格这个业务逻辑里接口的稳定，从而保证足够的自治。这也正是 Dependency inversion principle 在业务架构里的重要体现。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;        &lt;strong&gt;数仓&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;在很多公司做业务数仓的工程师和与业务数仓合作的业务研发工程师都很痛苦，因为面临如下问题&lt;/p&gt;      &lt;ul&gt;        &lt;li&gt;MySQL schema 的变化导致数仓同学改一个月，如果没有通知到数仓的同学直接导致数仓的故障&lt;/li&gt;        &lt;li&gt;业务研发想把 MySQL 换成其他的存储如 Redis，HBase；但是数仓同学不同意，因为不能像 MySQL 一样监听 binlog 进行数据统计&lt;/li&gt;        &lt;li&gt;数仓的数据错了，debug 超级困难&lt;/li&gt;&lt;/ul&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;造成这些问题的根本原因是什么呢？MySQL 即为业务服务又为数仓的数据服务，双方都强耦合了 MySQL 的 schema 的这个接口，但是这个接口却不稳定。可以有其他的解决方案吗？&lt;/p&gt;      &lt;p&gt;也许我们可以像维护 RPC 接口一样去维护数仓数据的接口，而不是依赖业务 MySQL schema。也就是说让每个业务团队定义好自己对外提供数据的接口，并将其实现，同时将接口注册到类似微服务注册中心的数据接口注册中心。从组织架构上直接上业务研发团队具备数据处理能力，统一的业务数仓团队从各个业务研发获取数据，做数据的互联互通，而不是把所有的细节的业务数据处理放到统一的数仓团队。这也是最近比较火的        &lt;a href="https://link.zhihu.com/?target=https%3A//martinfowler.com/articles/data-mesh-principles.html" rel="nofollow noreferrer" target="_blank"&gt;Data Mesh&lt;/a&gt;的核心思想。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;        &lt;strong&gt;总结&lt;/strong&gt;&lt;/p&gt;      &lt;p&gt;从上文的各个例子中，能否总结出一个业务架构好的标准是什么？如何才能做到在一定的规范下尽量自治，从而提高开发效率和质量。也许是这样的（按照顺序从上到下，越上边越好）&lt;/p&gt;      &lt;ul&gt;        &lt;li&gt;不同模块间完全没有影响&lt;/li&gt;        &lt;ul&gt;          &lt;li&gt;问答社区例子，能做这样是最好的。这种情况最理想，但是很多业务不会这么简单。&lt;/li&gt;&lt;/ul&gt;        &lt;li&gt;只共享 immutable data&lt;/li&gt;        &lt;ul&gt;          &lt;li&gt;电商订单的例子中不同的系统间只共享了 orderID，是不可变数据。不同系统间的影响是可控的&lt;/li&gt;&lt;/ul&gt;        &lt;li&gt;共享 mutable data，但是 interface 少，而且几乎不变&lt;/li&gt;        &lt;li&gt;interface 变化次数 / implementation 变化次数比例小&lt;/li&gt;        &lt;ul&gt;          &lt;li&gt;电商报价系统与业务数仓的例子，系统间共享的数据是会变化的，但是系统间的接口是稳定变化的的；哪怕会变化，接口的变化次数也是大大小于实现的变动次数的。&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;再看一下这张图，为什么我们在第一部分总结的「拆得越细越好」不对呢？乐高可以使用小组件组合成不同的模型是最核心原因是因为「拆得细」吗？其实并不是，而是「乐高小组件形状是不变的，即组件的接口是稳定不变的」&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;稍微上升一下高度，如果只用一个词来概念系统面临的问题，就是 Complexity。Complexity的核心来源又是什么？是「不同模块间的相互影响」。我们前边总结的业务架构好的标准和设计方法不就是在想办法衡量复杂度，控制复杂度吗？&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;那么有哪些具体的 hints 可以帮助我们达到上文提到的好的标准呢，如下图琐事。具体在这里就不详细介绍了，后再单独另开其他的 topic 进行展开。到此 Logical veiw 告一段落。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;h3&gt;Process view（物理运行时架构）&lt;/h3&gt;      &lt;p&gt;首先要问为什么要做物理运行时隔离？目的其实就是达成物理层面的自治。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;这是物理运行时自治带来的好处？那又有哪些负面影响呢？一方面是        &lt;a href="https://link.zhihu.com/?target=https%3A//www.wikiwand.com/en/Fallacies_of_distributed_computing" rel="nofollow noreferrer" target="_blank"&gt;Fallacies of distributed computing&lt;/a&gt;，另一方面是违背了        &lt;a href="https://link.zhihu.com/?target=https%3A//www.wikiwand.com/en/Law_of_Demeter" rel="nofollow noreferrer" target="_blank"&gt;Law of Demeter&lt;/a&gt;。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;https://www.wikiwand.com/en/Fallacies_of_distributed_computing      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;物理运行时的隔离面临这么多问题，那有什么好的解决方案吗？希望在做到物理层次的隔离的同时，又尽可能不受分布式的影响，并能尽量少的暴露 API ，不违背        &lt;a href="https://link.zhihu.com/?target=https%3A//www.wikiwand.com/en/Law_of_Demeter" rel="nofollow noreferrer" target="_blank"&gt;Law of Demeter&lt;/a&gt;。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;此图表示的是一个业务模块，但是在运行时有不同的 SLA 和弹性边界的要求，需要把 HTTP，RPC，Consumer 进行物理运行时的隔离。那么我们可以把整个业务模块的代码写到一个 git repo 里，同一个 git repo 跑起来后所有的通信都是进程内通信，把有物理隔离诉求的服务编译成不同的可运行可运行的包，然后分别部署到不同的进程组就可以了。如上图可以把 HTTP，module A，module B，backend  和  RPC ，module B，module C，backend 和 Consumer ，module B，backend 分别编译之后部署到不同的进程组，这几个进程组一定要一起部署一起回滚，从而保证版本分化的风险；这样的物理运行时设计就做到了物理层面的自治的目标了。&lt;/p&gt;      &lt;p&gt;到这里大家也看到了，也是需要重点强调的是业务架构模块与物理运行是模块并不是一一对应的，这也是大家普遍对于微服务的误解。千万不要把两者混为一谈，而是要先设计业务架构再设计物理运行时架构。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;h2&gt;总结&lt;/h2&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;p&gt;简单回顾一下，整个分享我们先看了我们面临的问题，现状和要达到的目标。然后第二部分通过一些现象总结了业务架构好的标准，并且给出了如何做到业务架构自治的标准与物理自治的一些 hints。整个分享内容很多，如果只记住两个 hint 的话，我希望是这两点。&lt;/p&gt;      &lt;img src="data:image/svg+xml;utf8,&lt;svg xmlns='http://www.w3.org/2000/svg' width='1024' height='768'&gt;&lt;/svg&gt;" width="1024"&gt;&lt;/img&gt;      &lt;h2&gt;后记&lt;/h2&gt;      &lt;p&gt;回头看起来这个分享有以下几个问题，需要进行改善。&lt;/p&gt;      &lt;ul&gt;        &lt;li&gt;想要表达的东西太多，造成了重点不是十分明确；&lt;/li&gt;        &lt;li&gt;目录划分不好，逻辑不够清晰，有些头重脚轻；&lt;/li&gt;        &lt;li&gt;笼统的现象举得太多，造成讲得都不够透彻，不如只讲一两个具体的案例。&lt;/li&gt;&lt;/ul&gt;      &lt;p&gt;此外需要声明的是文中的主要思想都是来源于        &lt;a href="https://www.zhihu.com/people/b1660a0ce5920b693c26fc1f23d81997"&gt;@陶文&lt;/a&gt;的        &lt;strong&gt;          &lt;a href="https://link.zhihu.com/?target=https%3A//autonomy.design/" rel="nofollow noreferrer" target="_blank"&gt;业务逻辑拆分模式&lt;/a&gt;&lt;/strong&gt;和        &lt;strong&gt;          &lt;a href="https://link.zhihu.com/?target=https%3A//twitter.com/udidahan%3Flang%3Den" rel="nofollow noreferrer" target="_blank"&gt;Udi Dahan&lt;/a&gt;，&lt;/strong&gt;在此郑重感谢两位老师的指导。&lt;/p&gt;      &lt;p&gt;希望大家多多评论或者以私信的方式提出批评和质疑。&lt;/p&gt;      &lt;p&gt;PS.如果上文对你有一点点儿帮助，欢迎来我和        &lt;a href="https://www.zhihu.com/people/b1660a0ce5920b693c26fc1f23d81997"&gt;@陶文&lt;/a&gt;        &lt;a href="https://www.zhihu.com/people/1824717da821307ac1151a15299666ed"&gt;@flaneur&lt;/a&gt;        &lt;a href="https://www.zhihu.com/people/0f51ea6191b958bc7c296b6161ce6922"&gt;@lfyzjck&lt;/a&gt;的        &lt;a href="https://link.zhihu.com/?target=https%3A//t.zsxq.com/16W4VcxtS" rel="nofollow noreferrer" target="_blank"&gt;《打开引擎盖》&lt;/a&gt;一起深入讨论&lt;/p&gt;      &lt;p&gt;参考&lt;/p&gt;      &lt;ul&gt;        &lt;li&gt;          &lt;a href="https://link.zhihu.com/?target=https%3A//autonomy.design/" rel="nofollow noreferrer" target="_blank"&gt;业务逻辑拆分模式&lt;/a&gt;&lt;/li&gt;        &lt;li&gt;          &lt;a href="https://link.zhihu.com/?target=https%3A//twitter.com/udidahan%3Flang%3Den" rel="nofollow noreferrer" target="_blank"&gt;Udi Dahan&lt;/a&gt;&lt;/li&gt;        &lt;li&gt;          &lt;a href="https://link.zhihu.com/?target=https%3A//ferd.ca/complexity-has-to-live-somewhere.html" rel="nofollow noreferrer" target="_blank"&gt;Complexity Has to Live Somewhere&lt;/a&gt;&lt;/li&gt;        &lt;li&gt;          &lt;a href="https://link.zhihu.com/?target=https%3A//martinfowler.com/bliki/MonolithFirst.html" rel="nofollow noreferrer" target="_blank"&gt;Monolith First&lt;/a&gt;&lt;/li&gt;        &lt;li&gt;          &lt;a href="https://link.zhihu.com/?target=https%3A//martinfowler.com/articles/dont-start-monolith.html" rel="nofollow noreferrer" target="_blank"&gt;Don’t start with a monolith&lt;/a&gt;&lt;/li&gt;        &lt;li&gt;          &lt;a href="https://link.zhihu.com/?target=https%3A//www.bilibili.com/video/BV1Rf4y1Q7Y4%3Ffrom%3Dsearch%26seid%3D12807152794497338058" rel="nofollow noreferrer" target="_blank"&gt;我们到底要微服务还是业务能力？&lt;/a&gt;&lt;/li&gt;        &lt;li&gt;          &lt;a href="https://link.zhihu.com/?target=https%3A//www.bilibili.com/video/BV1Rf4y1Q7Y4%3Ffrom%3Dsearch%26seid%3D12807152794497338058" rel="nofollow noreferrer" target="_blank"&gt;多“微”才算微服务&lt;/a&gt;&lt;/li&gt;        &lt;li&gt;          &lt;a href="https://link.zhihu.com/?target=https%3A//www.bilibili.com/video/BV1Rf4y1Q7Y4%3Ffrom%3Dsearch%26seid%3D12807152794497338058" rel="nofollow noreferrer" target="_blank"&gt;Modular Monolith&lt;/a&gt;&lt;/li&gt;        &lt;li&gt;          &lt;a href="https://link.zhihu.com/?target=http%3A//sunnyday.mit.edu/16.355/parnas-criteria.html" rel="nofollow noreferrer" target="_blank"&gt;On the Criteria To Be Used in Decomposing Systems into Modules&lt;/a&gt;&lt;/li&gt;        &lt;li&gt;          &lt;a href="https://link.zhihu.com/?target=https%3A//www.microsoft.com/en-us/research/wp-content/uploads/2016/02/acrobat-17.pdf" rel="nofollow noreferrer" target="_blank"&gt;Hints for Computer System Design&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 />
      <guid isPermaLink="true">https://itindex.net/detail/62913-%E4%BD%95%E8%BF%9B-%E5%BE%AE%E6%9C%8D%E5%8A%A1</guid>
      <pubDate>Thu, 08 Feb 2024 16:45:08 CST</pubDate>
    </item>
    <item>
      <title>适合个人网站的云服务组合</title>
      <link>https://itindex.net/detail/62906-%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99-%E6%9C%8D%E5%8A%A1-%E7%BB%84%E5%90%88</link>
      <description>&lt;div&gt;&lt;/div&gt; &lt;div&gt;回想起来我折腾博客和各类网站已将近 10 年光阴，在维护   &lt;a href="https://0xffff.one/" rel="noopener noreferrer" target="_blank"&gt;0xFFFF&lt;/a&gt; 的几年来，也尝试过各类不同的建站方案。当前用到的各类服务，综合速度和成本而言，感觉可能的选择方向差不多已经成型，可以做一个总结。&lt;/div&gt; &lt;div&gt;若你也想低成本搭建属于自己的网站，且想拥有不俗的访问速度，这里的方案也许可以是你的一个参考。&lt;/div&gt; &lt;h4&gt;  &lt;div&gt;&lt;/div&gt;基本结构&lt;/h4&gt; &lt;div&gt;抽象地说，运行一个网站需要的能力主要分两大块，一是域名与 DNS 解析，二是负责处理浏览器请求的服务器端。从需求来看，域名和 DNS 的能力相对比较固定，而服务器端需要的能力就比较多样，按角色和常用的业务来区分，可以分为这几类：&lt;/div&gt; &lt;ol start="1"&gt;  &lt;li&gt;接入层网关、反向代理：接收用户流量，转发到对应的服务&lt;/li&gt;&lt;/ol&gt; &lt;ol start="2"&gt;  &lt;li&gt;静态页面：HTML 网页 / Jamstack 应用的 Service-side Rendering&lt;/li&gt;&lt;/ol&gt; &lt;ol start="3"&gt;  &lt;li&gt;JS / CSS 等：通常会通过第三方域名 CDN 分发，提高网页的打开速度，减小对主站的依赖&lt;/li&gt;&lt;/ol&gt; &lt;ol start="4"&gt;  &lt;li&gt;媒体类附件：网站流量消耗的大头，通常托管在对象存储，也可通过 CDN 分发&lt;/li&gt;&lt;/ol&gt; &lt;ol start="5"&gt;  &lt;li&gt;动态服务：当一个网站需要交互的能力，需要有服务承载相关的 API 接口&lt;/li&gt;&lt;/ol&gt; &lt;div&gt;动态服务而言，大致可以按“无状态服务”和“有状态服务”去区分，无状态服务可以随时创建和销毁，有状态服务则需要持久化服务产生的数据。部分服务可能还需要数据库和缓存服务等的支持，类似 MySQL、PostgresSQL、Redis 等等。&lt;/div&gt; &lt;div&gt;当前云计算已经足够发达，基本你能想到的点，都有对应的云服务提供，因此也可以根据实际需要和网站的功能，来选择合适的服务去支撑。写这篇文章的主要目的，也是分享我当前在用的服务，以及选择 / 不选择它们背后的一些考虑点。&lt;/div&gt; &lt;h4&gt;  &lt;div&gt;&lt;/div&gt;DNS 与接入层网关&lt;/h4&gt; &lt;div&gt;DNS 方面我选择   &lt;a href="https://www.dnspod.cn/" rel="noopener noreferrer" target="_blank"&gt;DNSPod&lt;/a&gt;，考虑点在于它有中国大陆内的解析节点，并且在专业版可以支持全球服务器的解析，价格还可接受。最关键是它提供了精细化地在不同的地域去做分流解析的能力，这对于部署在海外的站点是非常有利的，可以针对大陆用户去专门部署优化的线路，相比于 Cloudflare、  &lt;a href="https://www.cloudns.net/" rel="noopener noreferrer" target="_blank"&gt;ClouDNS&lt;/a&gt; 等会更加灵活。&lt;/div&gt; &lt;div&gt;接入层网关方面，关注目标在于，地域上离用户越近越好，可以针对不同地域用户使用针对其最优的线路，以减少网络层面的不确定性，尽可能和用户维持稳定的连接。对应的服务选择，一是支持动态请求链路优化的 CDN 服务，二是网络条件较好的云服务器，三是现今一些支持边缘计算的网站服务商（如   &lt;a href="https://vercel.com/guides/vercel-reverse-proxy-rewrites-external" rel="noopener noreferrer" target="_blank"&gt;Vercel&lt;/a&gt;、  &lt;a href="https://fly.io/" rel="noopener noreferrer" target="_blank"&gt;Fly.io&lt;/a&gt; 等），通常它们的边缘节点之间的链路会做一些特殊优化，可以合理使用利用起来。&lt;/div&gt; &lt;div&gt;这里我选择二、三两种方案结合，以一个阿里云香港的轻量服务器来承接来自大陆的流量，其他地域的流量通过   &lt;a href="http://fly.io/" rel="noopener noreferrer" target="_blank"&gt;fly.io&lt;/a&gt; 承接，用 DNSPod 实现按地域的分流。目前（2023年底）的阿里云 hk 轻量服务器的网络质量非常优秀，全大陆访问的延迟可以   &lt;a href="https://ping.chinaz.com/0xffff.one" rel="noopener noreferrer" target="_blank"&gt;控制在 50ms 内&lt;/a&gt;；Fly.io 的好处则在于，他们有一个连通全球的边缘计算网络，理论上我只需要在我想要的地域部署服务，其他地域可以直接通过边缘节点接收用户请求，然后再在内部转发到服务所在地域的计算实例，网络请求相对也会更加可控一些。两者结合下来，实现各自中国大陆内外优势的互补。&lt;/div&gt; &lt;div&gt;对于反向代理的程序，这里直接用 Caddy Server，无论在轻量服务器还是   &lt;a href="http://fly.io" rel="noopener noreferrer" target="_blank"&gt;fly.io&lt;/a&gt;，只需做一些简单配置，就能实现流量转发，并自动申请签发配置好 https 证书，非常简单方便。&lt;/div&gt; &lt;h4&gt;  &lt;div&gt;&lt;/div&gt;静态页面&lt;/h4&gt; &lt;div&gt;众所周知，大部分网站、Web App 等实质上由静态的 HTML 网页以及各种脚本、样式图片音频资源等组合，因此托管静态页面和资源，应该是建站最基本的需求。&lt;/div&gt; &lt;div&gt;这方面的托管，基本没什么障碍的点，无非就是维护成本方面的差异，无论   &lt;a href="https://vercel.com/" rel="noopener noreferrer" target="_blank"&gt;Vercel&lt;/a&gt;、  &lt;a href="https://fly.io/" rel="noopener noreferrer" target="_blank"&gt;Fly.io&lt;/a&gt;、  &lt;a href="https://pages.github.com/" rel="noopener noreferrer" target="_blank"&gt;GitHub Pages&lt;/a&gt;、  &lt;a href="https://pages.cloudflare.com/" rel="noopener noreferrer" target="_blank"&gt;Cloudflare Pages&lt;/a&gt;，还是自己架 HTTP 服务器部署，都没有什么毛病。部分服务还提供了适应静态页面渲染的边缘节点。&lt;/div&gt; &lt;div&gt;当前我是用 Vercel 多一些，本博客和   &lt;a href="https://wiki.0xffff.one/" rel="noopener noreferrer" target="_blank"&gt;0xFFFF Wiki&lt;/a&gt; 都放在这上面，主打一个简单直接方便，push 代码直接就能自动触发构建和部署，非常省心。&lt;/div&gt; &lt;h4&gt;  &lt;div&gt;&lt;/div&gt;静态资源、CDN&lt;/h4&gt; &lt;div&gt;浏览器下载 HTML 后，通常需要加载脚本、样式、图片等资源，这会带来一个额外的流量消耗。接入层网关本身的带宽成本相对要贵一些，且访问速度方面不太可控，通常会单独对 HTML 外的资源，去根据访问情况做分布式的缓存。这也是 CDN 的一大作用，CDN 以大带宽低成本的优势，分摊源站点的流量，提升用户的访问体验。&lt;/div&gt; &lt;div&gt;这里我主要选择了 Bunny CDN，以及腾讯云 CDN 做大陆的静态资源加速。前者在价格和速度方面非常有优势；后者结合已备案域名，在大陆的访问体验更佳。主要考虑的点还是在速度方面的提升。&lt;/div&gt; &lt;h4&gt;  &lt;div&gt;&lt;/div&gt;媒体类附件&lt;/h4&gt; &lt;div&gt;一个网站的静态资源通常分为两部分，一部分是网站本身业务逻辑的一部分，这部分可以提前处理好交给服务托管；还有一部分是用户操作过程中新增的上传、后台处理需要的图片等，这种类型的数据就很适合放在对象存储上。&lt;/div&gt; &lt;div&gt;适合个人站长的选择不是太多，这方面鼻祖自然是 Amazon S3，不过传闻贼贵，不是一般学生可以承担的那种；另外还有国内大厂的 COS / OSS，Cloudflare R2，Backblaze B2 等。&lt;/div&gt; &lt;div&gt;Cloudflare R2 是很好的选择，它流量费全免的机制非常适合新手上路研究，但 Cloudflare 要求绑定的域名的 NS 记录都指向他们的 DNS 服务器。也就是说会和 DNSpod 的分流策略所冲突，无奈忍痛放弃。&lt;/div&gt; &lt;div&gt;经过一番尝试，发现 Blackblaze B2，这一服务在成本方面极具优势，虽然没有 Cloudflare 的 CDN 免流量费香，配合 Bunny CDN 去处理，效果还挺不错，0xFFFF 主站重度使用半个月下来，流量也才 2G 左右，费用 0.04 美分。&lt;/div&gt; &lt;div&gt;  &lt;img alt="&amp;#27880;&amp;#20876;&amp;#26102;&amp;#35760;&amp;#24471;&amp;#36873;&amp;#25321; US West&amp;#65292;&amp;#36317;&amp;#31163;&amp;#22823;&amp;#38470;&amp;#36817;&amp;#19968;&amp;#20123;&amp;#65292;&amp;#27880;&amp;#20876;&amp;#25104;&amp;#21151;&amp;#21518;&amp;#25913;&amp;#19981;&amp;#20102;&amp;#12290;" src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F5ef6a41f-4ad3-43d9-a314-d863352a143b%2Fceb0a778-3b2c-4f00-97ab-efde07302858%2FUntitled.png?table=block&amp;id=15a39f85-f78a-459e-b8a9-0ce5ef01a274&amp;cache=v2"&gt;&lt;/img&gt;注册时记得选择 US West，距离大陆近一些，注册成功后改不了。&lt;/div&gt; &lt;div&gt;Bunny CDN 和 Backblaze B2 有   &lt;a href="https://bunny.net/blog/bunny-net-partners-with-backblaze-for-free/" rel="noopener noreferrer" target="_blank"&gt;合作&lt;/a&gt;，可以优化接入相关体验。B2 官方还会有一些教程和文章，做的还挺贴心细致的：  &lt;a href="https://www.backblaze.com/blog/aws-cloudfront-vs-bunny-net-how-do-the-cdns-compare/" rel="noopener noreferrer" target="_blank"&gt;AWS CloudFront vs. bunny.net: How Do the CDNs Compare&lt;/a&gt;&lt;/div&gt; &lt;h4&gt;  &lt;div&gt;&lt;/div&gt;动态服务&lt;/h4&gt; &lt;div&gt;对于功能更强大的网站，静态的网页文件不太能满足需求，这时候就需要有后端服务支持，通常这类后端服务可能会提供以下的能力：&lt;/div&gt; &lt;ol start="1"&gt;  &lt;li&gt;动态生成 HTML&lt;/li&gt;&lt;/ol&gt; &lt;ol start="2"&gt;  &lt;li&gt;根据请求 API，动态生成响应，驱动业务逻辑&lt;/li&gt;&lt;/ol&gt; &lt;ol start="3"&gt;  &lt;li&gt;可能根据需要，生成其他资源（图片 / 待下载的文件等）&lt;/li&gt;&lt;/ol&gt; &lt;div&gt;如开头所说，动态服务有无状态服务和有状态服务两种：&lt;/div&gt; &lt;ul&gt;  &lt;li&gt;无状态服务：fly.io 会更适合，它可以以 Firecracker 虚拟机的形势一键部署运行 Docker 镜像，并快速部署到合适的地域，减少网络延迟的影响&lt;/li&gt;&lt;/ul&gt; &lt;ul&gt;  &lt;li&gt;有状态服务：用一个单独的 VPS 来处理比较合适，这里我继续用了阿里云香港的轻量服务器，业务规模不大的情况下，一台机器足矣。这里可选项有很多，腾讯云、AWS 的    &lt;a href="https://aws.amazon.com/cn/lightsail/" rel="noopener noreferrer" target="_blank"&gt;LightSail&lt;/a&gt;、Vultr、Linode 等也是不错的选择，最重要是稳定和可靠&lt;/li&gt;&lt;/ul&gt; &lt;div&gt;一个可行的方向是尽可能把服务做得无状态，可以部署到边缘节点的话速度会更具优势（但同时也需要考虑不同地域之间状态共享的问题）。&lt;/div&gt; &lt;h4&gt;  &lt;div&gt;&lt;/div&gt;数据库&lt;/h4&gt; &lt;div&gt;我原本比较倾向在本机去部署网站的数据库，但经历过一次误操作导致   &lt;a href="https://0xffff.one/d/1670" rel="noopener noreferrer" target="_blank"&gt;论坛数据丢失&lt;/a&gt; 的事件，也让我心有余悸，一方面日常备份得加强，另一方面也在考虑把数据库维护的工作交给专业的 DBA 服务。&lt;/div&gt; &lt;div&gt;这类服务通常很贵很贵，但感谢互联网，低成本的 Serverless 托管 DB 方案还是有的，目前看   &lt;a href="https://www.pingcap.com/pricing/" rel="noopener noreferrer" target="_blank"&gt;TiDB Cloud&lt;/a&gt; 和   &lt;a href="https://planetscale.com/pricing" rel="noopener noreferrer" target="_blank"&gt;PlanetScale&lt;/a&gt; 的免费额度能覆盖到，大概一点缺点是公网访问数据库，延迟会对应提高，需要做好 cache 的方案。目前亚太地区只有新加坡和东京机房，可能需要考虑把计算实例挪到新加坡区域（接入层可以不动）。&lt;/div&gt; &lt;div&gt;有的站点会依赖 Redis 做 cache 或者 DB，这时候也需要考虑备份和托管的方案，目前   &lt;a href="https://upstash.com/" rel="noopener noreferrer" target="_blank"&gt;Upstash Redis&lt;/a&gt; 应该还不错，看   &lt;a href="http://fly.io" rel="noopener noreferrer" target="_blank"&gt;fly.io&lt;/a&gt; 和他们合作搞了  &lt;a href="https://fly.io/docs/reference/redis/" rel="noopener noreferrer" target="_blank"&gt;内部方案&lt;/a&gt;。&lt;/div&gt; &lt;div&gt;如果业务涉及到大量 SQL 查询（博客 / 论坛等），还是需要单机部署数据库，那就需要从备份入手。这里的一点经验也是，在跑数据库的容器加入定时任务，然后定期 mysql dump 出最新的 sql，gzip 压缩作备份，再保存到备份专用的对象存储桶中，备份软件推荐   &lt;a href="https://www.duplicati.com/" rel="noopener noreferrer" target="_blank"&gt;Deplicati&lt;/a&gt;，存储桶我用的腾讯云的 COS。&lt;/div&gt; &lt;h4&gt;  &lt;div&gt;&lt;/div&gt;引荐链接&lt;/h4&gt; &lt;div&gt;若本文对你有所帮助，有需要的话，可以通过下方的引荐链接注册对应服务，可以为我多增加一些账户余额：&lt;/div&gt; &lt;ul&gt;  &lt;li&gt;Bunny CDN：   &lt;a href="https://bunny.net/?ref=f71884jr1m" rel="noopener noreferrer" target="_blank"&gt;https://bunny.net?ref=f71884jr1m&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt; &lt;h4&gt;  &lt;div&gt;&lt;/div&gt;参考&lt;/h4&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://0xffff.one/d/1530/4" rel="noopener noreferrer" target="_blank"&gt;Flarum 0x 及其迭代记录 - 0xFFFF&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://twitter.com/zgq354/status/1735342522577371436" rel="noopener noreferrer" target="_blank"&gt;独立开发者之穷鬼套餐 - X&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://limboy.me/posts/vercel-cf-fly/" rel="noopener noreferrer" target="_blank"&gt;Vercel, Cloudflare, fly.io 我的云服务三剑客&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://wzfou.com/bunny-cdn/" rel="noopener noreferrer" target="_blank"&gt;Bunny CDN网站CDN加速 - 挖站否&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://www.backblaze.com/blog/aws-cloudfront-vs-bunny-net-how-do-the-cdns-compare/" rel="noopener noreferrer" target="_blank"&gt;AWS CloudFront vs. bunny.net: How Do the CDNs Compare?&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://bunny.net/blog/bunny-net-partners-with-backblaze-for-free/" rel="noopener noreferrer" target="_blank"&gt;bunny.net partners with Backblaze to help reduce egress fees and supercharge content!&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 />
      <guid isPermaLink="true">https://itindex.net/detail/62906-%E4%B8%AA%E4%BA%BA%E7%BD%91%E7%AB%99-%E6%9C%8D%E5%8A%A1-%E7%BB%84%E5%90%88</guid>
      <pubDate>Thu, 21 Dec 2023 08:00:00 CST</pubDate>
    </item>
    <item>
      <title>高并发系统-如何做服务化拆分</title>
      <link>https://itindex.net/detail/62902-%E5%B9%B6%E5%8F%91-%E7%B3%BB%E7%BB%9F-%E6%9C%8D%E5%8A%A1</link>
      <description>&lt;hr&gt;&lt;/hr&gt;
 &lt;h2&gt;theme: channing-cyan
highlight: a11y-dark&lt;/h2&gt;
 &lt;p&gt;一般早期架构是  &lt;code&gt;一体化架构（Monolithic Architecture）&lt;/code&gt;，简单来说就是所有的业务都在一个后台服务来承载。&lt;/p&gt;
 &lt;p&gt;例如，一个Java web应用运行在Tomcat之类web容器上，仅包含单个WAR文件；一个Rails应用使用Phusion Passenger部署在Apache/Nginx上，或者使用JRuby部署在Tomcat上，它们都仅包含单个目录结构。为了伸缩和提高可用性，你可以在一个负载均衡器下面运行该应用的多份实例。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7a248aeb2faf483da090a2648dae1c7a~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=720&amp;h=540&amp;s=138158&amp;e=png&amp;b=faf2ee"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h1&gt;1. 一体化架构痛点&lt;/h1&gt;
 &lt;h2&gt;1.1 技术层面，数据库可能成为系统瓶颈&lt;/h2&gt;
 &lt;p&gt;一体化应用是可以快速复制，但是数据库一般没法横向扩展（除非做分库分表拆分）。  &lt;br /&gt;
数据库比如MYSQL的客户端连接数是有限制的，一般可在服务器端配置，比如2w。假设一个应用100数据库连接，最多100*200=2w，实际不能扩展到这么多，因为还有运维、监控相关也会使用一定的连接数。&lt;/p&gt;
 &lt;h2&gt;1.2 增加沟通成本，抑制研发效率提升&lt;/h2&gt;
 &lt;p&gt;《人月神话》中曾经提到：一个团队内部沟通成本，和人员数量 n 有关，约等于 n(n-1)/2，也就是说随着团队人员的增加，沟通的成本呈指数级增长，一个 100 人的团队，需要沟通的渠道大概是 100（100-1）/2 = 4950。&lt;/p&gt;
 &lt;p&gt;同时针对一体化项目，大多代码比较多，这会导致开发维护效率变低。之前在一个客户端项目，一个apk的代码量在150w，30+团队一起开发，合代码做需求非常刺激，有时候专门需要1整天来解决需求提交的代码冲突。&lt;/p&gt;
 &lt;h2&gt;1.3 运维复杂度增加&lt;/h2&gt;
 &lt;p&gt;系统编译、构建、启动等随着代码量的增多变得越来越复杂&lt;/p&gt;
 &lt;h2&gt;1.4 一体化架构优点&lt;/h2&gt;
 &lt;p&gt;一体化架构也是有着一些优点的，如果针对访问量不大、并发不高的系统，一体化架构可以节约机器、精简架构、提高研发效率有着自身的优势。&lt;/p&gt;
 &lt;p&gt;可以参考2023年的这篇文章：  &lt;a href="https://www.infoq.cn/article/nu2y3xiazg1cqianoxxa"&gt;从微服务转为单体架构、成本降低 90%，亚马逊内部案例引发轰动！CTO：莫慌，要持开放心态&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;针对单体架构还是微服务化架构，针对项目不同阶段、人员储备等情况按需进行选择&lt;/p&gt;
 &lt;h1&gt;2. 如何进行服务化拆分&lt;/h1&gt;
 &lt;h2&gt;2.1 做到单一服务内部功能的高内聚，和低耦合&lt;/h2&gt;
 &lt;p&gt;每个服务只完成自己职责之内的任务，对于不是自己职责的功能，交给其它服务来完成。&lt;/p&gt;
 &lt;h2&gt;2.2 服务拆分的粒度，先粗略拆分，再逐渐细化&lt;/h2&gt;
 &lt;p&gt;拆分初期可以把服务粒度拆的粗一些，后面随着团队对于业务和微服务理解的加深，再考虑把服务粒度细化。&lt;/p&gt;
 &lt;h2&gt;2.3 尽量避免影响产品的日常功能迭代，也就是说，要一边做产品功能迭代，一边完成服务化拆分&lt;/h2&gt;
 &lt;p&gt;优先剥离比较独立的边界服务（比如短信服务、地理位置服务），从非核心的服务出发，减少拆分对现有业务的影响  &lt;br /&gt;
当两个服务存在依赖关系时，优先拆分被依赖的服务。&lt;/p&gt;
 &lt;h2&gt;2.4 服务接口的定义要具备可扩展性&lt;/h2&gt;
 &lt;p&gt;为保证接口扩展性，接口入参和返回值采用对象而不是直接使用参数做入参  &lt;br /&gt;
比如下面test1要增加一个参数处理新增加一个方法外没有其他处理方式了，test2直接在入参加一个字段即可&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;void test1(int a, int b);

void test2(Test1Req req)
&lt;/code&gt;&lt;/pre&gt;
 &lt;h1&gt;3. 微服务化带来的问题和解决思路&lt;/h1&gt;
 &lt;h2&gt;3.1 服务接口的调用，不再是同一进程内的方法调用，而是跨进程的网络调用，这会增加接口响应时间的增加&lt;/h2&gt;
 &lt;p&gt;选择何时微服务框架，比如Dubbo&lt;/p&gt;
 &lt;h2&gt;3.2 多个服务之间有着错综复杂的依赖关系&lt;/h2&gt;
 &lt;p&gt;引入服务治理体系：熔断降级限流超时控制&lt;/p&gt;
 &lt;h2&gt;3.3 服务拆分到多个进程后，一条请求的调用链路上，涉及多个服务，那么一旦这个请求的响应时间增长，或者是出现错误，我们就很难知道，是哪一个服务出现的问题&lt;/h2&gt;
 &lt;p&gt;分布式追踪服务  &lt;br /&gt;
监控报表 APM工具，如PINPOINT&lt;/p&gt;
 &lt;h1&gt;4. 其他&lt;/h1&gt;
 &lt;h2&gt;4.1 康威定律&lt;/h2&gt;
 &lt;p&gt;设计系统的组织，其产生的设计等同于组织间的沟通结构。通俗一点说，就是你的团队组织结构是什么样的，你的架构就会长成什么样。&lt;/p&gt;
 &lt;h2&gt;4.2 贝索斯的两个披萨理论&lt;/h2&gt;
 &lt;p&gt;按照亚马逊 CEO，贝佐斯的“两个披萨”的理论，如果两个披萨不够你的团队吃，那么你的团队就太大了，需要拆分，所以一个小团队包括开发、运维、测试以 6～8 个人为最佳&lt;/p&gt;
 &lt;h2&gt;4.3 微服务化成本高，先采用工程拆分方式&lt;/h2&gt;
 &lt;p&gt;参考：  &lt;br /&gt;
  &lt;a href="https://imonce.github.io/2019/08/07/%E4%BB%80%E4%B9%88%E6%98%AF%E4%B8%80%E4%BD%93%E5%8C%96%E6%9E%B6%E6%9E%84-Monolithic-Architecure/"&gt;什么是一体化架构(Monolithic Architecture)&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="https://www.infoq.cn/article/nu2y3xiazg1cqianoxxa"&gt;从微服务转为单体架构、成本降低 90%，亚马逊内部案例引发轰动！CTO：莫慌，要持开放心态&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 />
      <guid isPermaLink="true">https://itindex.net/detail/62902-%E5%B9%B6%E5%8F%91-%E7%B3%BB%E7%BB%9F-%E6%9C%8D%E5%8A%A1</guid>
      <pubDate>Wed, 03 Jan 2024 20:56:54 CST</pubDate>
    </item>
    <item>
      <title>k8s-服务网格实战-配置 Mesh（灰度发布）</title>
      <link>https://itindex.net/detail/62877-k8s-%E6%9C%8D%E5%8A%A1-%E7%BD%91%E6%A0%BC</link>
      <description>&lt;p&gt;  &lt;img alt="istio-02.png" src="https://s2.loli.net/2023/11/07/rmwdGK6TQDuhAEW.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;在上一篇   &lt;a href="https://crossoverjie.top/2023/10/31/ob/k8s-Istio01/"&gt;k8s-服务网格实战-入门Istio&lt;/a&gt;中分享了如何安装部署   &lt;code&gt;Istio&lt;/code&gt;，同时可以利用   &lt;code&gt;Istio&lt;/code&gt; 实现   &lt;code&gt;gRPC&lt;/code&gt; 的负载均衡。&lt;/p&gt; &lt;p&gt;今天我们更进一步，深入了解使用 Istio 的功能。  &lt;br /&gt;  &lt;img alt="image.png" src="https://s2.loli.net/2023/11/07/jKIeEH7ir9uqDUd.png"&gt;&lt;/img&gt;  &lt;br /&gt;从 Istio 的流量模型中可以看出：Istio 支持管理集群的出入口请求（gateway），同时也支持管理集群内的 mesh 流量，也就是集群内服务之间的请求。&lt;/p&gt; &lt;p&gt;本次先讲解集群内部的请求，配合实现以下两个功能：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;灰度发布（对指定的请求分别路由到不同的 service 中）&lt;/li&gt;  &lt;li&gt;配置 service 的请求权重&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;  &lt;a href="http://crossoverjie.top/#&amp;#28784;&amp;#24230;&amp;#21457;&amp;#24067;" title="&amp;#28784;&amp;#24230;&amp;#21457;&amp;#24067;"&gt;&lt;/a&gt;灰度发布&lt;/h2&gt; &lt;p&gt;在开始之前会部署两个   &lt;code&gt;deployment&lt;/code&gt; 和一个   &lt;code&gt;service&lt;/code&gt;，同时这两个   &lt;code&gt;deployment&lt;/code&gt; 所关联的   &lt;code&gt;Pod&lt;/code&gt; 分别对应了两个不同的   &lt;code&gt;label&lt;/code&gt;，由于在灰度的时候进行分组。  &lt;br /&gt;  &lt;img alt="image.png" src="https://s2.loli.net/2023/11/07/tLOYQiNg5HEe2ry.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;使用这个   &lt;code&gt;yaml&lt;/code&gt; 会部署所需要的   &lt;code&gt;deployment&lt;/code&gt; 和   &lt;code&gt;service&lt;/code&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;kubectl apply -f https://raw.githubusercontent.com/crossoverJie/k8s-combat/main/deployment/istio-mesh.yaml      &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;hr&gt;&lt;/hr&gt; &lt;p&gt;首先设想下什么情况下我们需要灰度发布，一般是某个重大功能的测试，只对部分进入内测的用户开放入口。&lt;/p&gt; &lt;p&gt;假设我们做的是一个   &lt;code&gt;App&lt;/code&gt;，我们可以对拿到了内测包用户的所有请求头中加入一个版本号。&lt;/p&gt; &lt;p&gt;比如   &lt;code&gt;version=200&lt;/code&gt; 表示新版本，  &lt;code&gt;version=100&lt;/code&gt; 表示老版本。  &lt;br /&gt;同时在服务端会将这个版本号打印出来，用于区分请求是否进入了预期的 Pod。&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;// Client      &lt;br /&gt;version := r.URL.Query().Get(&amp;quot;version&amp;quot;)       &lt;br /&gt;name := &amp;quot;world&amp;quot;       &lt;br /&gt;ctx, cancel := context.WithTimeout(context.Background(), time.Second)       &lt;br /&gt;md := metadata.New(map[string]string{       &lt;br /&gt;    &amp;quot;version&amp;quot;: version,       &lt;br /&gt;})       &lt;br /&gt;ctx = metadata.NewOutgoingContext(ctx, md)       &lt;br /&gt;defer cancel()       &lt;br /&gt;g, err := c.SayHello(ctx, &amp;amp;pb.HelloRequest{Name: name})     &lt;br /&gt;     &lt;br /&gt;// Server     &lt;br /&gt;func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {       &lt;br /&gt;    md, ok := metadata.FromIncomingContext(ctx)       &lt;br /&gt;    var version string       &lt;br /&gt;    if ok {       &lt;br /&gt;       version = md.Get(&amp;quot;version&amp;quot;)[0]       &lt;br /&gt;    }    log.Printf(&amp;quot;Received: %v, version: %s&amp;quot;, in.GetName(), version)       &lt;br /&gt;    name, _ := os.Hostname()       &lt;br /&gt;    return &amp;amp;pb.HelloReply{Message: fmt.Sprintf(&amp;quot;hostname:%s, in:%s, version:%s&amp;quot;, name, in.Name, version)}, nil       &lt;br /&gt;}     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h3&gt;  &lt;a href="http://crossoverjie.top/#&amp;#23545;-service-&amp;#20998;&amp;#32452;" title="&amp;#23545; service &amp;#20998;&amp;#32452;"&gt;&lt;/a&gt;对 service 分组&lt;/h3&gt; &lt;p&gt;进行灰度测试时往往需要新增部署一个灰度服务，这里我们称为 v2（也就是上图中的 Pod2）。&lt;/p&gt; &lt;p&gt;同时需要将 v1 和 v2 分组：&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;apiVersion: networking.istio.io/v1alpha3       &lt;br /&gt;kind: DestinationRule       &lt;br /&gt;metadata:       &lt;br /&gt;  name: k8s-combat-service-ds       &lt;br /&gt;spec:       &lt;br /&gt;  host: k8s-combat-service-istio-mesh       &lt;br /&gt;  subsets:       &lt;br /&gt;    - name: v1       &lt;br /&gt;      labels:       &lt;br /&gt;        app: k8s-combat-service-v1       &lt;br /&gt;    - name: v2       &lt;br /&gt;      labels:       &lt;br /&gt;        app: k8s-combat-service-v2     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;这里我们使用 Istio 的   &lt;code&gt;DestinationRule&lt;/code&gt; 定义   &lt;code&gt;subset&lt;/code&gt;，也就是将我们的   &lt;code&gt;service&lt;/code&gt; 下的 Pod 分为 v1/v2。&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;使用 标签    &lt;code&gt;app&lt;/code&gt; 进行分组&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;注意这里的   &lt;code&gt;host: k8s-combat-service-istio-mesh&lt;/code&gt; 通常配置的是   &lt;code&gt;service&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;&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: k8s-combat-service-istio-mesh       &lt;br /&gt;spec:       &lt;br /&gt;  selector:       &lt;br /&gt;    appId: &amp;quot;12345&amp;quot;       &lt;br /&gt;  type: ClusterIP       &lt;br /&gt;  ports:       &lt;br /&gt;    - port: 8081       &lt;br /&gt;      targetPort: 8081       &lt;br /&gt;      name: app       &lt;br /&gt;    - name: grpc       &lt;br /&gt;      port: 50051       &lt;br /&gt;      targetPort: 50051     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;也就是这里 service 的名称，同时也支持配置为   &lt;code&gt;host: k8s-combat-service-istio-mesh.default.svc.cluster.local&lt;/code&gt;，如果使用的简写  &lt;code&gt;Istio&lt;/code&gt; 会根据当前指定的   &lt;code&gt;namespace&lt;/code&gt; 进行解析。&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;Istio 更推荐使用全限定名替代我们这里的简写，从而避免误操作。&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;当然我们也可以在   &lt;code&gt;DestinationRule&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;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;apiVersion: networking.istio.io/v1alpha3       &lt;br /&gt;kind: DestinationRule       &lt;br /&gt;metadata:       &lt;br /&gt;  name: k8s-combat-service-ds       &lt;br /&gt;spec:       &lt;br /&gt;  host: k8s-combat-service-istio-mesh      &lt;br /&gt;  trafficPolicy:     &lt;br /&gt;    loadBalancer:     &lt;br /&gt;      simple: ROUND_ROBIN       &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;  &lt;img alt="image.png" src="https://s2.loli.net/2023/11/07/TJyEV6eIiCcapSH.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;p&gt;这样我们就定义好了两个分组：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;v1：app: k8s-combat-service-v1&lt;/li&gt;  &lt;li&gt;v2：app: k8s-combat-service-v2&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;之后就可以配置路由规则将流量分别指定到两个不同的组中，这里我们使用   &lt;code&gt;VirtualService&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;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;31     &lt;br /&gt;32     &lt;br /&gt;33     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;apiVersion: networking.istio.io/v1alpha3       &lt;br /&gt;kind: VirtualService       &lt;br /&gt;metadata:       &lt;br /&gt;  name: k8s-combat-service-vs       &lt;br /&gt;spec:       &lt;br /&gt;  gateways:       &lt;br /&gt;    - mesh       &lt;br /&gt;  hosts:       &lt;br /&gt;    - k8s-combat-service-istio-mesh # match this host     &lt;br /&gt;http:       &lt;br /&gt;  - name: v1       &lt;br /&gt;    match:       &lt;br /&gt;      - headers:       &lt;br /&gt;          version:       &lt;br /&gt;            exact: &amp;apos;100&amp;apos;       &lt;br /&gt;    route:       &lt;br /&gt;      - destination:       &lt;br /&gt;          host: k8s-combat-service-istio-mesh       &lt;br /&gt;          subset: v1       &lt;br /&gt;  - name: v2       &lt;br /&gt;    match:       &lt;br /&gt;      - headers:       &lt;br /&gt;          version:       &lt;br /&gt;            exact: &amp;apos;200&amp;apos;       &lt;br /&gt;    route:       &lt;br /&gt;      - destination:       &lt;br /&gt;          host: k8s-combat-service-istio-mesh       &lt;br /&gt;          subset: v2       &lt;br /&gt;  - name: default       &lt;br /&gt;    route:       &lt;br /&gt;      - destination:       &lt;br /&gt;          host: k8s-combat-service-istio-mesh       &lt;br /&gt;          subset: v1     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;这个规则很简单，会检测 http 协议的   &lt;code&gt;header&lt;/code&gt; 中的   &lt;code&gt;version&lt;/code&gt; 字段值，如果为 100 这路由到   &lt;code&gt;subset=v1&lt;/code&gt; 这个分组的 Pod 中，同理为 200 时则路由到   &lt;code&gt;subset=v2&lt;/code&gt; 这个分组的 Pod 中。&lt;/p&gt; &lt;p&gt;当没有匹配到 header 时则进入默认的   &lt;code&gt;subset:v1&lt;/code&gt;&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;    &lt;code&gt;gRPC&lt;/code&gt; 也是基于 http 协议，它的    &lt;code&gt;metadata&lt;/code&gt; 也就对应了    &lt;code&gt;http&lt;/code&gt; 协议中的    &lt;code&gt;header&lt;/code&gt;。&lt;/p&gt;&lt;/blockquote&gt; &lt;h3&gt;  &lt;a href="http://crossoverjie.top/#header-x3D-100" title="header=100"&gt;&lt;/a&gt;header=100&lt;/h3&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;Greeting: hostname:k8s-combat-service-v1-5b998dc8c8-hkb72, in:world, version:100istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$ curl &amp;quot;http://127.0.0.1:8081/grpc_client?name=k8s-combat-service-istio-mesh&amp;amp;version=100&amp;quot;     &lt;br /&gt;Greeting: hostname:k8s-combat-service-v1-5b998dc8c8-hkb72, in:world, version:100istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$ curl &amp;quot;http://127.0.0.1:8081/grpc_client?name=k8s-combat-service-istio-mesh&amp;amp;version=100&amp;quot;     &lt;br /&gt;Greeting: hostname:k8s-combat-service-v1-5b998dc8c8-hkb72, in:world, version:100istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$ curl &amp;quot;http://127.0.0.1:8081/grpc_client?name=k8s-combat-service-istio-mesh&amp;amp;version=100&amp;quot;     &lt;br /&gt;Greeting: hostname:k8s-combat-service-v1-5b998dc8c8-hkb72, in:world, version:100istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$ curl &amp;quot;http://127.0.0.1:8081/grpc_client?name=k8s-combat-service-istio-mesh&amp;amp;version=100&amp;quot;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h3&gt;  &lt;a href="http://crossoverjie.top/#header-x3D-200" title="header=200"&gt;&lt;/a&gt;header=200&lt;/h3&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;Greeting: hostname:k8s-combat-service-v2-5db566fb76-xj7j6, in:world, version:200istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$ curl &amp;quot;http://127.0.0.1:8081/grpc_client?name=k8s-combat-service-istio-mesh&amp;amp;version=200&amp;quot;     &lt;br /&gt;Greeting: hostname:k8s-combat-service-v2-5db566fb76-xj7j6, in:world, version:200istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$ curl &amp;quot;http://127.0.0.1:8081/grpc_client?name=k8s-combat-service-istio-mesh&amp;amp;version=200&amp;quot;     &lt;br /&gt;Greeting: hostname:k8s-combat-service-v2-5db566fb76-xj7j6, in:world, version:200istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$ curl &amp;quot;http://127.0.0.1:8081/grpc_client?name=k8s-combat-service-istio-mesh&amp;amp;version=200&amp;quot;     &lt;br /&gt;Greeting: hostname:k8s-combat-service-v2-5db566fb76-xj7j6, in:world, version:200istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$ curl &amp;quot;http://127.0.0.1:8081/grpc_client?name=k8s-combat-service-istio-mesh&amp;amp;version=200&amp;quot;     &lt;br /&gt;Greeting: hostname:k8s-combat-service-v2-5db566fb76-xj7j6, in:world, version:200istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$ curl &amp;quot;http://127.0.0.1:8081/grpc_client?name=k8s-combat-service-istio-mesh&amp;amp;version=200&amp;quot;     &lt;br /&gt;Greeting: hostname:k8s-combat-service-v2-5db566fb76-xj7j6, in:world, version:200istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$ curl &amp;quot;http://127.0.0.1:8081/grpc_client?name=k8s-combat-service-istio-mesh&amp;amp;version=200&amp;quot;     &lt;br /&gt;Greeting: hostname:k8s-combat-service-v2-5db566fb76-xj7j6, in:world, version:200istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$ curl &amp;quot;http://127.0.0.1:8081/grpc_client?name=k8s-combat-service-istio-mesh&amp;amp;version=200&amp;quot;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;根据以上的上面的测试请求来看，只要我们请求头里带上指定的   &lt;code&gt;version&lt;/code&gt; 就会被路由到指定的   &lt;code&gt;Pod&lt;/code&gt; 中。&lt;/p&gt; &lt;p&gt;利用这个特性我们就可以在灰度验证的时候单独发一个灰度版本的   &lt;code&gt;Deployment&lt;/code&gt;，同时配合客户端指定版本就可以实现灰度功能了。&lt;/p&gt; &lt;h2&gt;  &lt;a href="http://crossoverjie.top/#&amp;#37197;&amp;#32622;&amp;#26435;&amp;#37325;" title="&amp;#37197;&amp;#32622;&amp;#26435;&amp;#37325;"&gt;&lt;/a&gt;配置权重&lt;/h2&gt; &lt;p&gt;同样基于   &lt;code&gt;VirtualService&lt;/code&gt; 我们还可以对不同的   &lt;code&gt;subset&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;23     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;apiVersion: networking.istio.io/v1alpha3       &lt;br /&gt;kind: VirtualService       &lt;br /&gt;metadata:       &lt;br /&gt;  name: k8s-combat-service-vs       &lt;br /&gt;spec:       &lt;br /&gt;  gateways:       &lt;br /&gt;    - mesh       &lt;br /&gt;  hosts:       &lt;br /&gt;    - k8s-combat-service-istio-mesh # match this host       &lt;br /&gt;  http:       &lt;br /&gt;    - match:       &lt;br /&gt;        - uri:       &lt;br /&gt;            exact: /helloworld.Greeter/SayHello       &lt;br /&gt;      route:       &lt;br /&gt;        - destination:       &lt;br /&gt;            host: k8s-combat-service-istio-mesh       &lt;br /&gt;            subset: v1       &lt;br /&gt;          weight: 10       &lt;br /&gt;        - destination:       &lt;br /&gt;            host: k8s-combat-service-istio-mesh       &lt;br /&gt;            subset: v2       &lt;br /&gt;          weight: 90       &lt;br /&gt;      timeout: 5000ms     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;这里演示的是针对   &lt;code&gt;SayHello&lt;/code&gt; 接口进行权重配置（当然还有多种匹配规则），90% 的流量会进入 v2 这个 subset，也就是在   &lt;code&gt;k8s-combat-service-istio-mesh&lt;/code&gt; service 下的   &lt;code&gt;app: k8s-combat-service-v2&lt;/code&gt; Pod。&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;Greeting: hostname:k8s-combat-service-v2-5db566fb76-xj7j6, in:world, version:200istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$ curl &amp;quot;http://127.0.0.1:8081/grpc_client?name=k8s-combat-service-istio-mesh&amp;amp;version=200&amp;quot;     &lt;br /&gt;Greeting: hostname:k8s-combat-service-v2-5db566fb76-xj7j6, in:world, version:200istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$ curl &amp;quot;http://127.0.0.1:8081/grpc_client?name=k8s-combat-service-istio-mesh&amp;amp;version=200&amp;quot;     &lt;br /&gt;Greeting: hostname:k8s-combat-service-v2-5db566fb76-xj7j6, in:world, version:200istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$ curl &amp;quot;http://127.0.0.1:8081/grpc_client?name=k8s-combat-service-istio-mesh&amp;amp;version=200&amp;quot;     &lt;br /&gt;Greeting: hostname:k8s-combat-service-v2-5db566fb76-xj7j6, in:world, version:200istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$ curl &amp;quot;http://127.0.0.1:8081/grpc_client?name=k8s-combat-service-istio-mesh&amp;amp;version=200&amp;quot;     &lt;br /&gt;Greeting: hostname:k8s-combat-service-v2-5db566fb76-xj7j6, in:world, version:200istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$ curl &amp;quot;http://127.0.0.1:8081/grpc_client?name=k8s-combat-service-istio-mesh&amp;amp;version=200&amp;quot;     &lt;br /&gt;Greeting: hostname:k8s-combat-service-v2-5db566fb76-xj7j6, in:world, version:200istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$ curl &amp;quot;http://127.0.0.1:8081/grpc_client?name=k8s-combat-service-istio-mesh&amp;amp;version=200&amp;quot;     &lt;br /&gt;Greeting: hostname:k8s-combat-service-**v1**-5b998dc8c8-hkb72, in:world, version:200istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$ curl &amp;quot;http://127.0.0.1:8081/grpc_client?name=k8s-combat-service-istio-mesh&amp;amp;version=200&amp;quot;     &lt;br /&gt;Greeting: hostname:k8s-combat-service-v2-5db566fb76-xj7j6, in:world, version:200istio-proxy@k8s-combat-service-v1-5b998dc8c8-hkb72:/$     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;经过测试会发现大部分的请求都会按照我们的预期进入 v2 这个分组。&lt;/p&gt; &lt;p&gt;当然除之外之外我们还可以：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;超时时间&lt;/li&gt;  &lt;li&gt;故障注入&lt;/li&gt;  &lt;li&gt;重试   &lt;br /&gt;具体的配置可以参考    &lt;a href="https://istio.io/latest/docs/reference/config/networking/virtual-service/#HTTPMatchRequest"&gt;Istio&lt;/a&gt; 官方文档：   &lt;br /&gt;   &lt;img alt="image.png" src="https://s2.loli.net/2023/11/07/LBjEtd1MP9VcAgl.png"&gt;&lt;/img&gt;   &lt;br /&gt;当然在一些云平台也提供了可视化的页面，可以更直观的使用。   &lt;br /&gt;   &lt;img alt="image.png" src="https://s2.loli.net/2023/11/07/2LVTgeiSK9HyxQJ.png"&gt;&lt;/img&gt;&lt;/li&gt;&lt;/ul&gt; &lt;blockquote&gt;  &lt;p&gt;以上是 阿里云的截图&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;但他们的管理的资源都偏   &lt;code&gt;kubernetes&lt;/code&gt;，一般是由运维或者是 DevOps 来配置，不方便开发使用，所以还需要一个介于云厂商和开发者之间的管理发布平台，可以由开发者以项目维度管理维护这些功能。&lt;/p&gt; &lt;p&gt;本文的所有源码在这里可以访问：  &lt;br /&gt;  &lt;a href="https://github.com/crossoverJie/k8s-combat"&gt;https://github.com/crossoverJie/k8s-combat&lt;/a&gt;&lt;/p&gt; &lt;p&gt;#Blog #Istio &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>k8s Istio</category>
      <guid isPermaLink="true">https://itindex.net/detail/62877-k8s-%E6%9C%8D%E5%8A%A1-%E7%BD%91%E6%A0%BC</guid>
      <pubDate>Tue, 07 Nov 2023 22:11:21 CST</pubDate>
    </item>
    <item>
      <title>12项性能位居第一，中国最接近GPT-4的大模型来了！现已全面开放服务！</title>
      <link>https://itindex.net/detail/62836-%E6%80%A7%E8%83%BD-%E4%B8%AD%E5%9B%BD-gpt</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;/p&gt;
 &lt;p&gt;在各大科技公司卷了半年的生成式AI热潮趋于冷静期的当下，这是一个极其值得思考的问题。&lt;/p&gt;
 &lt;p&gt;很多人会说，目前可能只有被称为业界标杆的GPT-4才能胜任，甚至对它加以“唯一”这样的头衔。&lt;/p&gt;
 &lt;p&gt;而与此同时，着眼于国内，即使这半年来各个大模型玩家争先恐后抢着落地，但“需要两到三年才能追赶GPT-4”的声音也是甚上尘嚣。&lt;/p&gt;
 &lt;p&gt;然而就在最近，一个名为  &lt;strong&gt;InternLM-123B&lt;/strong&gt;的国产预训练大语言模型，似乎将这种差距感极大地给拉近了一些。&lt;/p&gt;
 &lt;p&gt;因为在一场顶尖大模型“同台竞技”中，它的表现实属有些亮眼：&lt;/p&gt;
 &lt;p&gt;• 12项成绩排名第一。  &lt;br /&gt;• 综合实力全球第二，部分成绩超越GPT-4。  &lt;br /&gt;• 绝大部分性能超越ChatGPT和LLaMa-2-70B。&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="12&amp;#39033;&amp;#24615;&amp;#33021;&amp;#20301;&amp;#23621;&amp;#31532;&amp;#19968;&amp;#65292;&amp;#20013;&amp;#22269;&amp;#26368;&amp;#25509;&amp;#36817;GPT-4&amp;#30340;&amp;#22823;&amp;#27169;&amp;#22411;&amp;#26469;&amp;#20102;&amp;#65281;&amp;#29616;&amp;#24050;&amp;#20840;&amp;#38754;&amp;#24320;&amp;#25918;&amp;#26381;&amp;#21153;&amp;#65281;" src="https://www.qbitai.com/wp-content/uploads/replace/5f3b5d63420834246ac2e400e3441c6b.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;据了解，这个评测是在全球51个知名评测集（包括MMLU、AGIEval、ARC、CEval、Race、GSM8K等）、共计30万道问题集合上测试所得到的结果。&lt;/p&gt;
 &lt;p&gt;而细看评测内容，不难发现在所有的阅读理解、绝大部分推理和常识问答方面，InternLM-123B拿到了绝对的风头。&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="12&amp;#39033;&amp;#24615;&amp;#33021;&amp;#20301;&amp;#23621;&amp;#31532;&amp;#19968;&amp;#65292;&amp;#20013;&amp;#22269;&amp;#26368;&amp;#25509;&amp;#36817;GPT-4&amp;#30340;&amp;#22823;&amp;#27169;&amp;#22411;&amp;#26469;&amp;#20102;&amp;#65281;&amp;#29616;&amp;#24050;&amp;#20840;&amp;#38754;&amp;#24320;&amp;#25918;&amp;#26381;&amp;#21153;&amp;#65281;" src="https://www.qbitai.com/wp-content/uploads/replace/9bcb67472de12cb4c53ebc3b4f8daef9.jpeg"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;那么这个国产大模型到底是什么来头？&lt;/p&gt;
 &lt;p&gt;不卖关子，它正是由商汤联合上海AI实验室等多家国内顶尖科研机构发布的最新大语言模型。&lt;/p&gt;
 &lt;p&gt;但其实要是追溯一下这个大模型，不难发现在2个月前（6月份），当时参数量为1040亿的InternLM便已经解锁了“首个综合能力超越GPT-3.5-turbo的基模型”的成就，成为当时国内首家在多项权威评测集上超越ChatGPT水平的基模型。&lt;/p&gt;
 &lt;p&gt;不仅如此，在近日商汤所披露的中期业绩报告中，更是将如此生成式AI技术，落地所带来的“成绩”曝了出来：&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;仅半年时间，生成式AI相关收入暴涨670.4%！&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;div&gt;  &lt;img alt="12&amp;#39033;&amp;#24615;&amp;#33021;&amp;#20301;&amp;#23621;&amp;#31532;&amp;#19968;&amp;#65292;&amp;#20013;&amp;#22269;&amp;#26368;&amp;#25509;&amp;#36817;GPT-4&amp;#30340;&amp;#22823;&amp;#27169;&amp;#22411;&amp;#26469;&amp;#20102;&amp;#65281;&amp;#29616;&amp;#24050;&amp;#20840;&amp;#38754;&amp;#24320;&amp;#25918;&amp;#26381;&amp;#21153;&amp;#65281;" src="https://www.qbitai.com/wp-content/uploads/replace/533a6c0646daa6a56476783ed07b0d5a.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;如此技术，如此成绩，那么不免让人发出疑问：&lt;/p&gt;
 &lt;h1&gt;会是中国第一个达到GPT4水平的大模型吗？&lt;/h1&gt;
 &lt;p&gt;若想赶超GPT-4，单是在技术上达到或超越它就并非是一件易事。&lt;/p&gt;
 &lt;p&gt;毕竟即使是人才、资金、算力都兼备的谷歌和Meta至今都未能达到它的水平。&lt;/p&gt;
 &lt;p&gt;而且超强的算力、高质量的数据、正确的训练方法等等，都是一环扣一环，直接会影响大模型性能的好坏。&lt;/p&gt;
 &lt;p&gt;有人会说，为什么非要做到GPT-4水平呢？用Llama 2开源模型不香吗？&lt;/p&gt;
 &lt;p&gt;那是因为，如果要让GPT-4驱动类似于AutoGPT那样的智能体，打造“工具调用”的可用境界，就必须要依赖强大的基座模型。&lt;/p&gt;
 &lt;p&gt;在“工具调用”这件事情上，强如GPT-4也只能做到80%的准确率，Llama的准确率只有40%。&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="12&amp;#39033;&amp;#24615;&amp;#33021;&amp;#20301;&amp;#23621;&amp;#31532;&amp;#19968;&amp;#65292;&amp;#20013;&amp;#22269;&amp;#26368;&amp;#25509;&amp;#36817;GPT-4&amp;#30340;&amp;#22823;&amp;#27169;&amp;#22411;&amp;#26469;&amp;#20102;&amp;#65281;&amp;#29616;&amp;#24050;&amp;#20840;&amp;#38754;&amp;#24320;&amp;#25918;&amp;#26381;&amp;#21153;&amp;#65281;" src="https://www.qbitai.com/wp-content/uploads/replace/6175d33664c027a88723c47a17f9aeb4.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;h1&gt;△数据来源：《On the Tool Manipulation Capability of Open-source Large Language Models》&lt;/h1&gt;
 &lt;p&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;若是用一句话来概括商汤在技术上的打法，那便是“日日新大模型体系+SenseCore大装置”。&lt;/p&gt;
 &lt;p&gt;二者并非是简单的做加法那般叠加，而是之间有着深度融合的那种。&lt;/p&gt;
 &lt;p&gt;至于其技术实力效果，我们从大模型的迭代速度便可窥知一二。&lt;/p&gt;
 &lt;p&gt;早在今年四月，商汤便发布日日新大模型体系，成为国内第一批发布大模型的玩家之一；在国内大语言模型之战最白热化阶段，一口气将“生成式AI产品族”和盘托出——&lt;/p&gt;
 &lt;p&gt;商量（SenseChat）、秒画（SenseMirage）、如影（SenseAvatar）、琼宇（SenseSpace）和格物（SenseThings）。&lt;/p&gt;
 &lt;p&gt;它们分别对应的是自然语言交互、AI文生图、数字人、3D大场景重建、3D小物体生成这五个主流的生成式AI应用。&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="12&amp;#39033;&amp;#24615;&amp;#33021;&amp;#20301;&amp;#23621;&amp;#31532;&amp;#19968;&amp;#65292;&amp;#20013;&amp;#22269;&amp;#26368;&amp;#25509;&amp;#36817;GPT-4&amp;#30340;&amp;#22823;&amp;#27169;&amp;#22411;&amp;#26469;&amp;#20102;&amp;#65281;&amp;#29616;&amp;#24050;&amp;#20840;&amp;#38754;&amp;#24320;&amp;#25918;&amp;#26381;&amp;#21153;&amp;#65281;" src="https://www.qbitai.com/wp-content/uploads/replace/e3252f2b14ddf7e28b2c5bb807533ee3.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;时隔仅仅两个月（今年6月），商汤再次联合上海人工智能实验室发布InternLM-104B版本，并且当时在三个全球权威测评基准中（MMLU、AGIEval、C-Eval）的表现就均已超越ChatGPT。&lt;/p&gt;
 &lt;p&gt;不仅如此，在大模型的“玩法”上，商汤也是国内众多玩家里最早引入“工具调用”的选手之一。&lt;/p&gt;
 &lt;p&gt;例如知识库挂载，实现了无需训练，便可快速融合知识生成；搭配企业知识库可以快速解决相关领域问题。&lt;/p&gt;
 &lt;p&gt;再如InternLM-Chat-7B版本，也是在这种“玩法”之下，成为了第一个具有代码解释能力的开源对话模型。&lt;/p&gt;
 &lt;p&gt;能够根据需要灵活调用Python解释器等外部工具，在解决复杂数学计算等任务上的能力显著提升；此外，该模型还可通过搜索引擎获取实时信息，提供具有时效性的回答。&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="12&amp;#39033;&amp;#24615;&amp;#33021;&amp;#20301;&amp;#23621;&amp;#31532;&amp;#19968;&amp;#65292;&amp;#20013;&amp;#22269;&amp;#26368;&amp;#25509;&amp;#36817;GPT-4&amp;#30340;&amp;#22823;&amp;#27169;&amp;#22411;&amp;#26469;&amp;#20102;&amp;#65281;&amp;#29616;&amp;#24050;&amp;#20840;&amp;#38754;&amp;#24320;&amp;#25918;&amp;#26381;&amp;#21153;&amp;#65281;" src="https://www.qbitai.com/wp-content/uploads/replace/c2f6433e4406de8875ce6c8d3a3541e2.gif"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;并且从第三方对于大模型掌握工具能力的评估结果来看，InternLM-Chat-7B也是领先于主流“选手”。&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="12&amp;#39033;&amp;#24615;&amp;#33021;&amp;#20301;&amp;#23621;&amp;#31532;&amp;#19968;&amp;#65292;&amp;#20013;&amp;#22269;&amp;#26368;&amp;#25509;&amp;#36817;GPT-4&amp;#30340;&amp;#22823;&amp;#27169;&amp;#22411;&amp;#26469;&amp;#20102;&amp;#65281;&amp;#29616;&amp;#24050;&amp;#20840;&amp;#38754;&amp;#24320;&amp;#25918;&amp;#26381;&amp;#21153;&amp;#65281;" src="https://www.qbitai.com/wp-content/uploads/replace/a4abac84087259bcf5dcd2b507ccba53.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;在此之后，商汤大语言模型的发展，也就来到了我们最开始所提到的InternLM-123B，是仅次于GPT-4，综合实力位居全球第二的水平。&lt;/p&gt;
 &lt;p&gt;所以现在把发展路径中的几个重要迭代时间节点拎出来就是：四月→六月→七月→八月。&lt;/p&gt;
 &lt;p&gt;不得不说，快，着实是快。&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="12&amp;#39033;&amp;#24615;&amp;#33021;&amp;#20301;&amp;#23621;&amp;#31532;&amp;#19968;&amp;#65292;&amp;#20013;&amp;#22269;&amp;#26368;&amp;#25509;&amp;#36817;GPT-4&amp;#30340;&amp;#22823;&amp;#27169;&amp;#22411;&amp;#26469;&amp;#20102;&amp;#65281;&amp;#29616;&amp;#24050;&amp;#20840;&amp;#38754;&amp;#24320;&amp;#25918;&amp;#26381;&amp;#21153;&amp;#65281;" src="https://www.qbitai.com/wp-content/uploads/replace/9a03f6500c3f0fa5910a37d7e41afc1a.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;在与商汤联合创始人兼大装置首席科学家林达华交流过程中，我们了解到如此迭代速度的背后还有一个“杀手锏”——数据处理。&lt;/p&gt;
 &lt;p&gt;这里所指的并非只是数据的总量，商汤更侧重的是足够强、高吞吐量的数据清洗能力。正如林达华所述：&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;一个性能强大的大模型，训练数据不仅规模要大，质量更要高；而且大模型的价值观和安全性也是依赖于此。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;数据清洗的过程，犹如调制配方的实验，需要不断试错、不断重复，甚至从头再来。这个过程是每家训练大模型的公司都无法避免的过程，没有捷径可走。&lt;/p&gt;
 &lt;p&gt;OpenAI在无数场合都介绍过GPT4训练的经验，但从未公开过数据清洗的经验，这可谓是训练大模型的顶级机密。&lt;/p&gt;
 &lt;p&gt;商汤在数据清洗的过程中投入了上千块GPU的算力，并建立起大量系统化、工程化的途径来进行数据配方的试错，可以迅速发现大数据库中的有效数据再到小参数模型上进行验证。&lt;/p&gt;
 &lt;p&gt;从4月到8月，商汤花费了非常大的力气做数据清洗，过程中专注于补充和构建有多步骤的推理语料，形成一套非常高效的闭环进行模型的训练，使得模型的推理能力得到了大幅提升。&lt;/p&gt;
 &lt;p&gt;目前商汤每月能够产出约2万亿token的高质量数据，预计年底高质量数据储备将突破10万亿token，足以支持更加强大的基模型的训练。&lt;/p&gt;
 &lt;p&gt;通过数据清洗，商汤在中文语料的储备方面达到了一个相当高的水平，是业内领先的能力，因此在知识理解和推理方面都有非常优异的表现。&lt;/p&gt;
 &lt;div&gt;  &lt;img alt="12&amp;#39033;&amp;#24615;&amp;#33021;&amp;#20301;&amp;#23621;&amp;#31532;&amp;#19968;&amp;#65292;&amp;#20013;&amp;#22269;&amp;#26368;&amp;#25509;&amp;#36817;GPT-4&amp;#30340;&amp;#22823;&amp;#27169;&amp;#22411;&amp;#26469;&amp;#20102;&amp;#65281;&amp;#29616;&amp;#24050;&amp;#20840;&amp;#38754;&amp;#24320;&amp;#25918;&amp;#26381;&amp;#21153;&amp;#65281;" src="https://www.qbitai.com/wp-content/uploads/replace/89aa628c38b664ac08f69f9cfb8d1001.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;p&gt;而如此迅猛的迭代速度和高质量数据清洗，定然是离不开大算力的加持，也就是商汤技术版图中另一个关键——SenseCore大装置。&lt;/p&gt;
 &lt;p&gt;早在2022年1月，商汤便交付使用了总投资高达56亿元的人工智能计算中心（AIDC），而且是“出道”即成为亚洲最大的AI超算中心之一。&lt;/p&gt;
 &lt;p&gt;一年前它的算力就已经高达了2500 Petaflops，可以轻松应对万亿参数的大模型；而时隔仅1年，这个数值便翻了一倍多，达到了6000 Petaflops。&lt;/p&gt;
 &lt;p&gt;有强大的算力，有高质量数据，加之商汤对于“玩转”大模型多年来沉淀的know-how，也就不难理解为何能拥有如此迅猛的迭代速度了。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;二看落地应用&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;高质量的落地速度，是另一个关键点。&lt;/p&gt;
 &lt;p&gt;这也是目前趋于冷静的AIGC市场之下，各个大模型玩家所面对的骨感且实实在在的问题。&lt;/p&gt;
 &lt;p&gt;商汤可以说在这一点上提交了一份生成式AI相关收入暴涨670.4%的高分作业。&lt;/p&gt;
 &lt;p&gt;这个数据的亮相其实也并不意外，因为商汤在此前的活动中也早已对此有所披露。&lt;/p&gt;
 &lt;p&gt;例如结合商量2.0和秒画3.0的能力，商汤在移动端给客户带来了多种交互方面的“解法”。&lt;/p&gt;
 &lt;p&gt;针对信息获取的问答交互、生活场景的知识交互、语言和图像生成的内容交互等等，正因为商汤的大模型拥有轻量化版本，所以可以轻松在移动端上部署。&lt;/p&gt;
 &lt;p&gt;商汤还基于InternLM的轻量级模型，结合自研推理加速算法，与头部手机芯片厂商建立研发合作，成功实现了大语言模型的手机端实时计算能力。&lt;/p&gt;
 &lt;p&gt;……&lt;/p&gt;
 &lt;p&gt;而这仅是商汤将大模型落地应用的一隅，从众多案例来看，也正应了商汤联合创始人、执行董事徐冰的观察：&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;市场对于AIGC、大模型技术的需求是非常强烈的；谁能够在市场上迅速地推出对标业界领先能力的基模型，谁就能跑得更快一些。&lt;/p&gt;
  &lt;p&gt;我们在基础设施中投入如此大的力量，也是要解决市场对于大模型落地固有的碎片化的问题；让基模型的泛化能力强大起来，即便面对各种碎片化的需求都可以做到及时响应。&lt;/p&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;自从大模型热潮以来，一个经久不断的话题便是“开源”和“闭源”。&lt;/p&gt;
 &lt;p&gt;其各自的优势也是越发的明显：&lt;/p&gt;
 &lt;p&gt;• 开源大模型：可以促进技术的共享和交流，加速人工智能的发展；避免闭源模式下的技术独霸和垄断，促进市场的公平竞争。  &lt;br /&gt;• 闭源大模型：可以保护知识产权，为公司带来经济效益；提高公司的核心竞争力，为公司在市场中占据优势地位。&lt;/p&gt;
 &lt;p&gt;但二者之争也是进展地如火如荼，国外大模型巨头亦是如此，最为典型的便是Meta正在以开源LLaMa系列来挑战 OpenAI的地位。&lt;/p&gt;
 &lt;p&gt;在这个关键问题上，商汤的战略布局与它们截然相反——不做选择题，都要。&lt;/p&gt;
 &lt;p&gt;例如在开源方面，商汤与多家科研机构合作支持和推进AI开源平台建设，InternLM-7B的部分训练数据、训练代码及基模型权重已经向学术界及工业界免费开源，并支持免费商用。&lt;/p&gt;
 &lt;p&gt;而在闭源方面，商汤目标打造具有竞争力且好用的“基座模型”，如同英伟达卖“硬的铲子”，商汤希望通过“软的铲子”，支持上万个潜在行业大模型的需求，真正解决行业痛点。&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;p&gt;与此同时，商汤还非常看重基模型的能力，因为在它看来，只有把基模型做得足够强，才能应对碎片化极高的市场需求。&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;/blockquote&gt;
 &lt;p&gt;……&lt;/p&gt;
 &lt;p&gt;总而言之，有实力、有落地、有布局，商汤能否解锁“成为中国第一个达到GPT-4水平的大模型”，是值得期待一波了。&lt;/p&gt;
 &lt;h1&gt;商汤刷新了自己&lt;/h1&gt;
 &lt;p&gt;最后，我们再回到商汤本身。&lt;/p&gt;
 &lt;p&gt;若是要用一句话来评价商汤这次交出的“成绩单”，或许就是：&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;商汤自己刷新了自己。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;曾几何时，大众和市场对于商汤的印象可能依旧是停留在“AI视觉技术公司”这个标签上。&lt;/p&gt;
 &lt;p&gt;但也正是这样的一个起点，如果站在当下这个时间节点回首，或许正应了现在非常流行那句话：命运的齿轮开始转动了。&lt;/p&gt;
 &lt;p&gt;现在大模型所需要的多模态技术，不论是自然语言处理，还是图像处理等，商汤已经早早涉足且深耕；现在大算力上的“一票难求”，商汤也是早早布局打造AIDC，为日后的大装置做好了充足的铺垫。&lt;/p&gt;
 &lt;p&gt;而且商汤更是在ChatGPT引爆AIGC大热潮之前，便已经在大模型领域着手研发。&lt;/p&gt;
 &lt;p&gt;并且在2019年，商汤便使用上千张GPU进行单任务训练，推出了10亿参数规模的视觉模型，并实现了当时业界最好的算法效果。&lt;/p&gt;
 &lt;p&gt;后来在2021年到2022年期间，商汤训练并开源了30亿参数的多模态大模型书生。&lt;/p&gt;
 &lt;p&gt;而当热潮退去，市场迎来落地大考之际，商汤又能将长期准备好的一系列的成果通过完备生产要素和生产资料、灵活的“玩法”和布局来应对。&lt;/p&gt;
 &lt;p&gt;因此，现在的商汤更像是一个新型技术基建平台，时刻在为即将到来且充满变数的未来在做着准备。&lt;/p&gt;
 &lt;p&gt;总而言之，商汤，是时候需要被重估了。&lt;/p&gt;
 &lt;h1&gt;One More Thing&lt;/h1&gt;
 &lt;p&gt;好消息！商汤大模型应用“商量SenseChat”即日起全面向广大用户开放服务了！&lt;/p&gt;
 &lt;p&gt;可戳下方链接了解一下：  &lt;br /&gt;https://chat.sensetime.com&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>资讯 首页轮播 GPT-4 商汤科技 商量</category>
      <guid isPermaLink="true">https://itindex.net/detail/62836-%E6%80%A7%E8%83%BD-%E4%B8%AD%E5%9B%BD-gpt</guid>
      <pubDate>Thu, 31 Aug 2023 17:32:01 CST</pubDate>
    </item>
    <item>
      <title>Blog: 考虑所有微服务的脆弱性并对其行为进行监控</title>
      <link>https://itindex.net/detail/62833-blog-%E8%80%83%E8%99%91-%E5%BE%AE%E6%9C%8D%E5%8A%A1</link>
      <description>&lt;p&gt;  &lt;strong&gt;作者&lt;/strong&gt;：David Hadas (IBM Research Labs)&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;译者&lt;/strong&gt;：Wilson Wu (DaoCloud)&lt;/p&gt;

 &lt;p&gt;  &lt;em&gt;本文对 DevOps 产生的错误安全意识做出提醒。开发和配置微服务时遵循安全最佳实践并不能让微服务不易被攻击。
本文说明，即使所有已部署的微服务都容易被攻击，但仍然可以采取很多措施来确保微服务不被利用。
本文解释了如何从安全角度对客户端和服务的行为进行分析，此处称为    &lt;strong&gt;“安全行为分析”&lt;/strong&gt; ，
可以对已部署的易被攻击的微服务进行保护。本文会引用    &lt;a href="http://knative.dev/security-guard"&gt;Guard&lt;/a&gt;，
一个开源项目，对假定易被攻击的 Kubernetes 微服务的安全行为提供监测与控制。&lt;/em&gt;&lt;/p&gt;

 &lt;p&gt;随着网络攻击的复杂性不断加剧，部署云服务的组织持续追加网络安全投资，旨在提供安全且不易被攻击的服务。
然而，网络安全投资的逐年增长并没有造成网络安全事件的同步减少。相反，网络安全事件的数量每年都在持续增长。
显然，这些组织在这场斗争中注定会失败——无论付出多大努力来检测和消除已部署服务中的网络安全弱点，攻击者似乎总是占据上风。&lt;/p&gt;

 &lt;p&gt;考虑到当前广泛传播的攻击工具、复杂的攻击者以及攻击者们不断增长的网络安全经济收益，
在 2023 年，任何依赖于构建无漏洞、无弱点服务的网络安全战略显然都过于天真。看来唯一可行的策略是：&lt;/p&gt;

 &lt;p&gt;➥   &lt;strong&gt;承认你的服务容易被攻击！&lt;/strong&gt;&lt;/p&gt;

 &lt;p&gt;换句话说，清醒地接受你永远无法创建完全无懈可击服务的事实。
如果你的对手找到哪怕一个弱点作为切入点，你就输了！承认尽管你尽了最大努力，
你的所有服务仍然容易被攻击，这是重要的第一步。接下来，本文将讨论你可以对此做些什么......&lt;/p&gt;

 &lt;h2&gt;如何保护微服务不被利用&lt;/h2&gt;

 &lt;p&gt;容易被攻击并不一定意味着你的服务将被利用。尽管你的服务在某些方面存在你不知道的漏洞，
但攻击者仍然需要识别这些漏洞并利用它们。如果攻击者无法利用你服务的漏洞，你就赢了！
换句话说，拥有无法利用的漏洞就意味着拥有无法坐实的风险。&lt;/p&gt;


 &lt;img alt="&amp;#25915;&amp;#20987;&amp;#32773;&amp;#22312;&amp;#26381;&amp;#21153;&amp;#20013;&amp;#21462;&amp;#24471;&amp;#31435;&amp;#36275;&amp;#28857;&amp;#30340;&amp;#31034;&amp;#24847;&amp;#22270;" src="https://kubernetes.io/zh-cn/blog/2023/01/20/security-behavior-analysis/security_behavior_figure_1.svg"&gt;&lt;/img&gt; 
 &lt;p&gt;图 1：攻击者在易被攻击的服务中取得立足点&lt;/p&gt;



 &lt;p&gt;如上图所示，攻击者尚未在服务中取得立足点；也就是说，假设你的服务在第一天没有运行由攻击者控制的代码。
在我们的示例中，该服务暴露给客户端的 API 中存在漏洞。为了获得初始立足点，
攻击者使用恶意客户端尝试利用服务 API 的其中一个漏洞。恶意客户端发送一个操作触发服务的一些计划外行为。&lt;/p&gt;

 &lt;p&gt;更具体地说，我们假设该服务容易受到 SQL 注入攻击。开发人员未能正确过滤用户输入，
从而允许客户端发送会改变预期行为的值。在我们的示例中，如果客户端发送键为“username”且值为   &lt;strong&gt;“tom or 1=1”&lt;/strong&gt; 的查询字符串，
则客户端将收到所有用户的数据。要利用此漏洞需要客户端发送不规范的字符串作为值。
请注意，良性用户不会发送带有空格或等号字符的字符串作为用户名，相反，他们通常会发送合法的用户名，
例如可以定义为字符 a-z 的短序列。任何合法的用户名都不会触发服务计划外行为。&lt;/p&gt;

 &lt;p&gt;在这个简单的示例中，人们已经可以识别检测和阻止开发人员故意（无意）留下的漏洞被尝试利用的很多机会，
从而使该漏洞无法被利用。首先，恶意客户端的行为与良性客户端的行为不同，因为它发送不规范的请求。
如果检测到并阻止这种行为变化，则该漏洞将永远不会到达服务。其次，响应于漏洞利用的服务行为不同于响应于常规请求的服务行为。
此类行为可能包括对其他服务（例如数据存储）进行后续不规范调用、消耗不确定的时间来响应和/或以非正常的响应来回应恶意客户端
（例如，在良性客户端定期发出请求的情况下，包含比正常发送更多的数据）。
如果检测到服务行为变化，也将允许在利用尝试的不同阶段阻止利用。&lt;/p&gt;

 &lt;p&gt;更一般而言：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;监控客户端的行为可以帮助检测和阻止针对服务 API 漏洞的利用。事实上，
部署高效的客户端行为监控会使许多漏洞无法被利用，而剩余漏洞则很难实现。
为了成功，攻击者需要创建一个无法从常规请求中检测到的利用方式。&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;监视服务的行为可以帮助检测通过任何攻击媒介正在被利用的服务。
由于攻击者需要确保服务行为无法从常规服务行为中被检测到，所以有效的服务行为监控限制了攻击者可能实现的目的。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;结合这两种方法可以为已部署的易被攻击的服务添加一个保护层，从而大大降低任何人成功利用任何已部署的易被攻击服务的可能性。
接下来，让我们来确定需要使用安全行为监控的四个使用场景。&lt;/p&gt;

 &lt;h2&gt;使用场景&lt;/h2&gt;

 &lt;p&gt;从安全的角度来看，我们可以识别任何服务生命周期中的以下四个不同阶段。
每个阶段都需要安全行为监控来应对不同的挑战：&lt;/p&gt;

 &lt;table&gt;

  &lt;tr&gt;
   &lt;th&gt;服务状态&lt;/th&gt;
   &lt;th&gt;使用场景&lt;/th&gt;
   &lt;th&gt;为了应对这个使用场景，你需要什么？&lt;/th&gt;
&lt;/tr&gt;


  &lt;tr&gt;
   &lt;td&gt;    &lt;strong&gt;正常的&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;    &lt;strong&gt;无已知漏洞：&lt;/strong&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;strong&gt;存在漏洞的&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;    &lt;strong&gt;相关的 CVE 已被公开：&lt;/strong&gt; 服务所有者需要发布该服务的新的无漏洞修订版。研究表明，实际上，消除已知漏洞的过程可能需要数周才能完成（平均 2 个月）。&lt;/td&gt;
   &lt;td&gt;    &lt;strong&gt;基于 CVE 分析添加保护&lt;/strong&gt; - 检测/阻止包含可用于利用已发现漏洞特定模式的请求。尽管该服务存在已知漏洞，但仍然继续提供服务。&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;    &lt;strong&gt;可被利用的&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;    &lt;strong&gt;可利用漏洞已被公开：&lt;/strong&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;strong&gt;已被不当使用的&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;    &lt;strong&gt;攻击者不当使用服务背后的 Pod：&lt;/strong&gt; 攻击者可以遵循某种攻击模式，从而使他/她能够对 Pod 进行不当使用。服务所有者需要重新启动任何受损的 Pod，同时使用未受损的 Pod 继续提供服务。请注意，一旦 Pod 重新启动，攻击者需要重复进行攻击，然后才能再次对其进行不当使用。&lt;/td&gt;
   &lt;td&gt;    &lt;strong&gt;识别并重新启动被不当使用的组件实例&lt;/strong&gt; - 在任何给定时间，某些后端的 Pod 可能会受到损害和不当使用，而其他后端 Pod 则按计划运行。检测/删除被不当使用的 Pod，同时允许其他 Pod 继续为客户端请求提供服务。&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;

 &lt;p&gt;而幸运的是，微服务架构非常适合接下来讨论的安全行为监控。&lt;/p&gt;

 &lt;h2&gt;微服务与单体的安全行为对比&lt;/h2&gt;

 &lt;p&gt;Kubernetes 通常提供用于支持微服务架构设计的工作负载。在设计上，微服务旨在遵循“做一件事并将其做好”的 UNIX 哲学。
每个微服务都有一个有边界的上下文和一个清晰的接口。换句话说，你可以期望微服务客户端发送相对规范的请求，
并且微服务呈现相对规范的行为作为对这些请求的响应。因此，微服务架构是安全行为监控的绝佳候选者。&lt;/p&gt;


 &lt;img alt="&amp;#35813;&amp;#22270;&amp;#26174;&amp;#31034;&amp;#20102;&amp;#20026;&amp;#20160;&amp;#20040;&amp;#24494;&amp;#26381;&amp;#21153;&amp;#38750;&amp;#24120;&amp;#36866;&amp;#21512;&amp;#23433;&amp;#20840;&amp;#34892;&amp;#20026;&amp;#30417;&amp;#25511;" src="https://kubernetes.io/zh-cn/blog/2023/01/20/security-behavior-analysis/security_behavior_figure_2.svg"&gt;&lt;/img&gt; 
 &lt;p&gt;图 2：微服务非常适合安全行为监控&lt;/p&gt;



 &lt;p&gt;上图阐明了将单体服务划分为一组微服务是如何提高我们执行安全行为监测和控制的能力。
在单体服务方法中，不同的客户端请求交织在一起，导致识别不规则客户端行为的能力下降。
在没有先验知识的情况下，观察者将发现很难区分交织在一起的客户端请求的类型及其相关特征。
此外，内部客户端请求不会暴露给观察者。最后，单体服务的聚合行为是其组件的许多不同内部行为的复合体，因此很难识别不规范的服务行为。&lt;/p&gt;

 &lt;p&gt;在微服务环境中，每个微服务在设计上都期望提供定义更明确的服务，并服务于定义更明确的请求类型。
这使得观察者更容易识别不规范的客户端行为和不规范的服务行为。此外，微服务设计公开了内部请求和内部服务，
从而提供更多安全行为数据来识别观察者的违规行为。总的来说，这使得微服务设计模式更适合安全行为监控。&lt;/p&gt;

 &lt;h2&gt;Kubernetes 上的安全行为监控&lt;/h2&gt;

 &lt;p&gt;寻求添加安全行为的 Kubernetes 部署可以使用在 CNCF Knative 项目下开发的   &lt;a href="http://knative.dev/security-guard"&gt;Guard&lt;/a&gt;。
Guard 集成到在 Kubernetes 之上运行的完整 Knative 自动化套件中。或者，
  &lt;strong&gt;你可以将 Guard 作为独立工具部署&lt;/strong&gt;，以保护 Kubernetes 上任何基于 HTTP 的工作负载。&lt;/p&gt;

 &lt;p&gt;查看：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;Github 上的    &lt;a href="https://github.com/knative-sandbox/security-guard"&gt;Guard&lt;/a&gt;，用于将 Guard 用作独立工具。&lt;/li&gt;
  &lt;li&gt;Knative 自动化套件 - 在博客文章    &lt;a href="https://davidhadas.wordpress.com/2022/08/29/knative-an-opinionated-kubernetes"&gt;Opinionated Kubernetes&lt;/a&gt; 中了解 Knative，
其中描述了 Knative 如何简化和统一 Web 服务在 Kubernetes 上的部署方式。&lt;/li&gt;
  &lt;li&gt;你可以在    &lt;a href="https://kubernetes.slack.com/archives/C019LFTGNQ3"&gt;SIG Security&lt;/a&gt;
或 Knative 社区    &lt;a href="https://knative.slack.com/archives/CBYV1E0TG"&gt;Security&lt;/a&gt; Slack 频道上联系 Guard 维护人员。
Knative 社区频道将很快转移到    &lt;a href="https://communityinviter.com/apps/cloud-native/cncf"&gt;CNCF Slack&lt;/a&gt;，其名称为   &lt;code&gt;#knative-security&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;本文的目标是邀请 Kubernetes 社区采取行动，并引入安全行为监测和控制，
以帮助保护基于 Kubernetes 的部署。希望社区后续能够：&lt;/p&gt;

 &lt;ol&gt;
  &lt;li&gt;分析不同 Kubernetes 使用场景带来的网络挑战&lt;/li&gt;
  &lt;li&gt;为用户添加适当的安全文档，介绍如何引入安全行为监控。&lt;/li&gt;
  &lt;li&gt;考虑如何与帮助用户监测和控制其易被攻击服务的工具进行集成。&lt;/li&gt;
&lt;/ol&gt;

 &lt;h2&gt;欢迎参与&lt;/h2&gt;

 &lt;p&gt;欢迎你参与到对 Kubernetes 的开发安全行为监控的工作中；以代码或文档的形式分享反馈或做出贡献；并以任何形式完成或提议相关改进。&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/62833-blog-%E8%80%83%E8%99%91-%E5%BE%AE%E6%9C%8D%E5%8A%A1</guid>
      <pubDate>Fri, 20 Jan 2023 08:00:00 CST</pubDate>
    </item>
    <item>
      <title>微服务安全简介</title>
      <link>https://itindex.net/detail/62815-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E5%AE%89%E5%85%A8-%E7%AE%80%E4%BB%8B</link>
      <description>&lt;p&gt;​由于其可扩展性、灵活性和敏捷性，微服务架构已经变得越来越受欢迎。然而，随着这种架构的分布和复杂性增加，确保强大的安全措施变得至关重要。微服务的安全性超越了传统的方法，需要采用全面的策略来保护免受不断演变的威胁和漏洞的影响。通过理解核心原则并采取有效的安全措施，组织可以加强其微服务架构，并保护敏感数据和资源。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;微服务简介&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;微服务是一种软件开发架构风格，应用程序被创建为一组通过明确定义的API（应用程序编程接口）连接的小型独立服务。每个微服务负责一个独立的业务能力，并可以独立于其他服务创建、实施和扩展（Sascha Möllering, 2021）。微服务允许将应用程序划分为更小、可管理的组件，可以根据需求进行独立扩展。这种可扩展性使组织能够快速响应变化，并更有效地处理高流量负载。通过将应用程序分解为具有自己的界限上下文和功能的较小服务，可以更容易地理解、开发、测试和维护代码库。开发人员可以专注于单个服务而不会影响整个应用程序。微服务促进了故障隔离，因为一个服务的故障不一定会影响整个应用程序。它实现了优雅降级和容错，即使某个特定服务出现问题，其他服务仍然可以继续运行。微服务允许组织根据其特定需求为各个服务选择不同的技术、编程语言和框架。这种灵活性鼓励使用最合适的工具和技术来开发每个服务，提高整体开发效率和效果。微服务促进了分散式的开发和部署。多个开发团队可以同时开发不同的服务，每个服务可以独立部署、更新或替换，而不会影响其他服务或需要进行完整的应用程序发布。 通过微服务，更容易实施持续集成和持续部署（CI/CD）实践。服务可以独立开发、测试和部署，实现更快的发布周期，并降低与大型、单体式部署相关的风险。微服务与云原生和分布式系统架构相吻合。它们与容器化和编排技术（如Docker和Kubernetes）自然结合，使得在云上部署和维护微服务更加简单。与传统的单体式架构不同，微服务提供了更大的灵活性、可扩展性、可维护性和容错性。这些优势在现代应用程序开发中越来越受欢迎，特别是在寻求构建强大、可扩展和敏捷系统的组织中。&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;strong&gt;微服务的简要历史&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;微服务作为一种架构风格，受到了各种软件开发实践和技术进步的影响而不断演进。微服务的根源可以追溯到在20世纪90年代末和21世纪初出现的面向服务的架构（SOA）的概念。SOA旨在创建松耦合、可重用的软件服务，这些服务可以被编排以构建复杂的应用程序。2000年代末和2010年代初，Web API的出现和广泛采用的云计算对塑造微服务起到了重要作用。Web API允许分布式系统之间进行轻松集成和通信，而云平台提供了托管和扩展应用程序的基础设施。DevOps实践的兴起和对持续交付的关注进一步影响了微服务的演进。DevOps强调开发和运维团队之间的协作，促进了需要更小、独立组件的需求，这些组件可以更快地开发、测试和部署。2013年发布的反应式宣言强调了高响应性、可扩展性和弹性系统的需求。这推动开发人员探索新的架构模式，包括微服务，以解决与单体应用程序相关的可扩展性挑战。Netflix通过其基于微服务的架构的成功在推广微服务方面发挥了关键作用。2014年，Netflix分享了其经验和最佳实践，强调了松耦合、可独立部署的面向服务的方法的好处。同年，软件开发思想领袖Martin Fowler发表了一篇有影响力的关于微服务的文章，进一步引起了对这种架构风格的关注。该报告概述了微服务的关键特性、优势和挑战，巩固了这个概念在软件开发社区中的地位。随着时间的推移，出现了各种支持微服务开发、部署和管理的框架、工具和技术。这些包括像Docker这样的容器化技术、像Kubernetes这样的编排平台、服务发现工具、API网关和监控解决方案。微服务最近获得了显著的关注，许多组织都采用了这种架构风格。亚马逊、谷歌、优步和爱彼迎等公司都采用了微服务来实现软件开发和部署过程中的可扩展性、灵活性和敏捷性。值得注意的是，微服务并不是一种适合所有情况的解决方案，它也带来了复杂性和挑战。然而，随着开发人员和组织在日益分布式的云原生计算环境中寻求构建模块化、可扩展和弹性的应用程序，微服务的受欢迎程度不断增长。&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;strong&gt;单体应用与微服务&lt;/strong&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/34f7c2330a2040fb8f9327466f161f3f~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p align="center"&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;  &lt;strong&gt;微服务：&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;结构：微服务将应用程序拆分为小的、松散耦合的、可独立部署的服务。每个服务专注于特定的业务能力，并通过明确定义的API与其他服务通信。&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;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;strong&gt;保护微服务的安全性&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;确保微服务架构的安全性是至关重要的。微服务架构通常涉及多个服务之间的数据交换。确保敏感数据的安全性、完整性和可访问性至关重要。如果不解决安全风险，可能会导致未经授权的访问、数据泄露或敏感信息的泄露。微服务架构将应用程序功能分布在多个服务中，增加了攻击面。每个服务都成为攻击者的潜在入口点。确保每个服务的安全性，并实施适当的网络安全措施，是防止未经授权访问和减轻攻击风险的关键。 微服务在服务之间的通信中高度依赖API，确保API的安全性对于防止未经授权访问、数据篡改或注入攻击至关重要。组织必须实施适当的身份验证、授权和输入验证机制，以确保只有授权的服务和用户可以访问和与API进行交互。通常使用各种技术和框架开发和维护微服务的不同团队可能会引入潜在的漏洞。定期进行漏洞评估、代码审查和渗透测试是识别和解决每个服务中的安全弱点的关键。组织应该实施适当的身份和访问管理（IAM）实践，以管理用户身份并控制对微服务的访问，每个服务应具有细粒度的访问控制，并执行最小特权原则。可以利用集中式身份验证和授权机制，如OAuth或JWT，确保跨服务的安全用户身份验证和授权。微服务通过网络相互通信，因此实现安全通信至关重要，实施安全协议，如HTTPS或TLS，确保服务之间传输的数据加密，并防止窃听或篡改。有效的监控和日志记录机制对于检测和响应安全事件至关重要，集中式日志记录和监控解决方案可以实时监控服务活动，检测异常行为，并识别潜在的安全漏洞。由于微服务的分布式特性，实施威胁检测和响应机制、入侵检测系统（IDS）、安全事件监控和自动化事件响应系统可以帮助及时识别和响应安全事件。每个微服务应定期进行安全测试，包括渗透测试和漏洞扫描，以发现缺陷并确认安全保障的有效性。安全测试应成为开发和部署生命周期的重要组成部分。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;API网关在保护微服务中的作用&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;API网关在保护微服务架构中扮演着至关重要的角色。它们作为外部客户端请求的集中入口点，并提供各种安全功能和能力。以下是API网关在保护微服务中的关键贡献：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;
   &lt;p&gt;身份验证和授权：API网关处理传入请求的身份验证和授权，可以强制执行身份验证机制，如API密钥、令牌或OAuth，在允许访问底层微服务之前验证客户端的身份。将这些安全控制集中在网关上简化了身份验证过程，并减轻了各个微服务的负担。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;访问控制和权限：API网关实现了细粒度的访问控制和权限管理，可以强制执行访问策略、基于角色的访问控制（RBAC）或其他授权机制，确保客户端只能访问适当的微服务和操作，根据其角色和权限。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;速率限制和节流：API网关通过实施速率限制和节流，帮助保护微服务免受滥用和拒绝服务（DoS）攻击。它们可以对来自特定客户端或IP地址的每秒或每分钟请求数量设置限制，确保公平使用，并保持微服务的可用性和性能。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;请求验证和过滤：API网关可以执行输入验证和过滤，以保护微服务免受恶意或格式错误的请求。它们可以检查和清理传入请求，验证参数、头部或有效负载，以防止常见的安全漏洞，如注入攻击或跨站脚本（XSS）。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;加密和传输安全：API网关可以处理传入和传出请求的加密和传输安全，并终止SSL/TLS连接，确保客户端和网关之间的安全通信。这有助于保护传输中的敏感数据，防止窃听或篡改。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;日志记录和监控：API网关通常提供日志记录和监控功能，记录有关传入请求、响应和潜在安全事件的信息。集中记录和监控数据简化了安全事件的检测和分析，有助于取证分析、合规性和主动威胁检测。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;内容过滤和转换：API网关可以应用内容过滤和转换，对响应进行清理，防止漏洞。它们可以从响应中删除敏感或不必要的信息，修改有效负载或转换数据格式，确保客户端只接收所需的数据，同时防止信息泄露。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;负载均衡和高可用性：API网关可以处理跨多个微服务实例的负载均衡，将传入请求分发到确保资源利用率最佳和高可用性的微服务。负载均衡有助于防止单个微服务被压垮，并提供冗余以提高容错性。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;API版本控制和生命周期管理：API网关可以帮助管理微服务的生命周期和API的版本控制，它们提供了一个抽象层，允许微服务的演进而不直接影响客户端。这有助于管理向后兼容性，推出更新，并以受控的方式废弃旧版本。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;通过整合这些安全功能，API网关作为微服务周围的边界，强制执行安全策略，并减少了在各个服务中实施安全的复杂性。它们增强了整体的安全性、可见性和对微服务架构的控制。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/35b16501ba024dfe80fb60256fae7c6d~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p align="center"&gt;没有API网关的直接客户端到微服务通信架构&lt;/p&gt;
 &lt;p&gt;  &lt;em&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/43de0260ff524fbab2938e071919ea44~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/em&gt;&lt;/p&gt;
 &lt;p align="center"&gt;使用API网关微服务通信&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;strong&gt;保护微服务架构的挑战&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;由于微服务架构的分布式和去中心化特性，保护微服务架构面临着一系列挑战。以下是组织常常面临的一些常见挑战。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;身份验证和授权：在多个微服务中管理身份验证和授权可能很复杂。确保只有经过授权的服务和用户可以访问特定的微服务及其资源，需要仔细设计和实施身份验证和授权机制，这可能涉及到OAuth、JWT（JSON Web Tokens）或API网关等技术来强制执行访问控制策略（Goikhman，2023）。&lt;/li&gt;
  &lt;li&gt;通信安全：微服务通常通过网络相互通信，因此保证通信安全至关重要。通过实施加密和安全协议（如HTTPS）来保护微服务之间传输的数据，有助于防止未经授权的访问或对敏感信息的篡改。&lt;/li&gt;
  &lt;li&gt;服务发现和身份管理：随着微服务数量的增加，维护一个最新的可用服务注册表并管理其身份变得具有挑战性。服务发现机制和身份管理解决方案，如服务注册表或服务网格，可以帮助解决这些挑战，并确保微服务之间的安全通信。&lt;/li&gt;
  &lt;li&gt;分布式日志记录和监控：监控各个微服务和整个系统的安全性至关重要，然而，对于众多微服务来说，从各个来源收集和汇总日志、指标和安全事件可能具有挑战性。建立集中式的日志记录和监控基础设施，以跟踪和分析与安全相关的数据，有助于有效地检测和应对安全事件（Wajjakkara Kankanamge Anthony，2020）。&lt;/li&gt;
  &lt;li&gt;数据安全：微服务通常处理敏感数据，确保其机密性、完整性和可用性至关重要，实施适当的数据保护措施，如加密、访问控制和安全存储机制变得至关重要。此外，定义数据所有权和访问策略以防止未经授权的数据访问或泄露也是必要的。&lt;/li&gt;
  &lt;li&gt;代码和依赖管理：微服务依赖于许多依赖项，包括外部库、框架和第三方服务，确保这些依赖项安全并与最新的安全补丁保持同步可能是一个挑战。实施强大的代码审查流程、依赖管理实践和漏洞扫描工具可以帮助识别和减轻潜在的安全风险。&lt;/li&gt;
  &lt;li&gt;错误处理和弹性：微服务应该安全地处理错误和异常，以避免暴露敏感信息或向攻击者提供有关系统漏洞的信息。实施适当的错误处理机制，如集中式错误日志记录和安全错误消息，有助于在不损害安全性的情况下保持系统的弹性。&lt;/li&gt;
  &lt;li&gt;安全部署和DevOps实践：微服务通常是独立部署和更新的，因此需要安全的部署实践。确保安全的容器化、基础设施供应和持续集成/持续部署（CI/CD）流水线对于在部署过程中防止安全漏洞至关重要。&lt;/li&gt;
  &lt;li&gt;对服务接口的威胁：微服务通常会暴露API，使其成为各种攻击的潜在目标，如注入、跨站脚本（XSS）或拒绝服务（DoS）攻击。应用强大的API安全实践，如输入验证、输出编码、速率限制和API版本控制，有助于保护微服务接口免受安全威胁。&lt;/li&gt;
  &lt;li&gt;持续安全测试：在微服务架构中，定期进行安全测试是必不可少的，传统的安全测试技术如渗透测试和漏洞扫描可能需要进行调整以应对微服务的分布式特性。此外，将自动化安全测试纳入CI/CD流水线中，可以确保在开发周期的早期发现和解决安全漏洞。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;解决这些挑战需要采用全面和积极的微服务安全方法。重要的是从开发的早期阶段开始考虑安全，并在整个微服务的生命周期中持续监控、评估和改进安全状况。&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;strong&gt;保护微服务架构的最佳安全实践&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;保护微服务架构涉及实施全面的安全措施，以保护各个微服务和整个系统。在保护微服务架构时，考虑以下一些最佳安全实践：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;
   &lt;p&gt;实施最小权限原则：应用最小权限原则，确保每个微服务只具有执行其特定功能所需的必要权限和访问权限。这减少了未经授权访问的风险，并限制了被入侵的微服务可能带来的影响。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;使用安全通信协议：采用安全通信协议，如HTTPS/TLS，对微服务之间传输的数据进行加密。这可以防止窃听、篡改和未经授权访问敏感信息。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;实施身份验证和授权：使用强大的身份验证机制，如OAuth或JWT，验证访问微服务的微服务和用户的身份。实施细粒度的授权控制，确保只有授权的实体可以访问特定的微服务或执行特定的操作。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;应用输入验证和输出编码：验证和清理微服务接收到的所有输入数据，以防止注入攻击（如SQL注入或跨站脚本），同时对输出数据进行编码，以避免潜在的安全漏洞，并保护针对微服务接口的消费者免受攻击。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;实施集中化的日志记录和监控：建立集中化的日志记录和监控系统，收集和分析所有微服务的日志、指标和安全事件。这可以实现主动威胁检测、事件响应和安全事件的取证分析。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;实施速率限制和节流：通过实施速率限制和节流机制，保护微服务免受滥用、过量流量和拒绝服务（DoS）攻击。这有助于确保微服务的可用性和性能，同时防止资源耗尽。通过实施这些最佳安全实践，可以增强微服务架构的安全性，并保护其免受各种安全威胁和漏洞的影响。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;7.安全的数据存储和加密：采用适当的加密机制，保护静态存储的敏感数据，无论是存储在数据库、缓存还是文件系统中。使用强大的加密算法，并安全地管理加密密钥，以保护数据的机密性。&lt;/p&gt;
 &lt;p&gt;8.定期进行安全评估和渗透测试：定期进行安全评估、漏洞扫描和渗透测试，以识别和解决微服务及其依赖项中的安全弱点。这有助于发现潜在的漏洞，并及时进行修复。&lt;/p&gt;
 &lt;p&gt;9.安全配置管理：确保对微服务及其基础架构组件进行安全配置管理，使用安全默认值，定期更新软件版本，并遵循安全加固指南，以最小化攻击面，并降低配置错误的风险。&lt;/p&gt;
 &lt;p&gt;10.持续的安全培训和意识：在从事微服务开发的开发团队中推广安全意识文化，定期进行安全培训，教育开发人员有关安全编码实践，并提高对常见安全漏洞和缓解策略的认识。&lt;/p&gt;
 &lt;p&gt;11.实施容错和弹性设计：设计微服务具有容错和弹性，以抵御攻击或故障，实施冗余、故障转移机制和分布式负载均衡，以确保微服务的可用性和连续性。&lt;/p&gt;
 &lt;p&gt;12.安全的第三方集成：审查和评估微服务与之交互的第三方服务或API的安全状况，实施安全的集成实践，验证和验证来自外部来源的输入，并确保集成遵循安全通信标准。&lt;/p&gt;
 &lt;p&gt;在微服务架构中，安全是一个持续的工作。它需要结合安全编码实践、安全基础设施配置、定期测试和持续监控，以确保系统的整体安全性。&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;strong&gt;结论&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;微服务架构在可扩展性和灵活性方面带来了巨大的好处，但也引入了独特的安全挑战。通过实施全面的安全策略，包括身份验证、授权、安全通信和监控，组织可以确保其微服务基础架构的完整性、机密性和可用性。拥抱以安全为先的思维方式将使企业能够在当今动态和不断演变的威胁环境中构建弹性和可信赖的微服务架构。&lt;/p&gt;
 &lt;p&gt;作者： Jagdish Mohite&lt;/p&gt;
 &lt;p&gt;更多技术干货请关注公号“  &lt;strong&gt;云原生数据库&lt;/strong&gt;”&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;squids.cn&lt;/strong&gt;，目前可体验  &lt;strong&gt;全网zui低价RDS，免费的迁移工具DBMotion、SQL开发工具等&lt;/strong&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/62815-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E5%AE%89%E5%85%A8-%E7%AE%80%E4%BB%8B</guid>
      <pubDate>Tue, 25 Jul 2023 10:16:46 CST</pubDate>
    </item>
    <item>
      <title>如何收集K8S容器化部署的服务的日志？</title>
      <link>https://itindex.net/detail/62743-k8s-%E5%AE%B9%E5%99%A8-%E6%9C%8D%E5%8A%A1</link>
      <description>&lt;p&gt;做开发的同学都知道日志的重要性，日志的种类一般有接口日志、错误日志、关键步骤日志、用户操作日志等。本文主要详细讲解使用kubernetes容器化部署的服务该如何记录和收集日志。&lt;/p&gt;
 &lt;h1&gt;一、使用标准输出方式&lt;/h1&gt;
 &lt;p&gt;将想要记录的日志内容输出到stdout或stderr即可（DockerEngine本身具有LogDriver 功能，可通过配置不同的LogDriver将容器的stdout通过DockerEngine写入到日志系统），由DockerEngine将日志写入到日志系统。&lt;/p&gt;
 &lt;p&gt;这种方式的优点是使用简单，但是缺点也十分明显。所有的输出都混在一个流中，无法像文件一样分类输出，一个应用中一般都有几种类型的日志，这些日志的格式、用途不一，混在同一个流中将很难做分类和分析。&lt;/p&gt;
 &lt;p&gt;虽然使用Stdout是Docker官方推荐的方式，但是仅限于简单的场景。这种方式可定制化、灵活性、资源隔离性都比较差，一般不建议在生产环境中使用。&lt;/p&gt;
 &lt;h1&gt;二、服务直写方式&lt;/h1&gt;
 &lt;p&gt;在服务代码中将日志直接发送到日志系统（集成所使用日志系统的SDK或者自己实现SDK）。&lt;/p&gt;
 &lt;p&gt;这种方式的优点是日志文件不需要临时存储到磁盘，也不需要部署额外的日志采集Agent，可根据业务特点进行定制，不受集群规模限制。&lt;/p&gt;
 &lt;p&gt;缺点是日志直写会占用一定的服务器资源，例如cpu、内存、特别是网络IO，会影响服务的整体性能。&lt;/p&gt;
 &lt;h1&gt;三、DaemonSet方式&lt;/h1&gt;
 &lt;p&gt;通过修改Deployment文件使服务容器和宿主机共享一个目录，服务将日志输出这个目录下面。使用DaemonSet方式在每个node节点上面运行一个日志采集agent（例如filebeat、fluentd、logstash等）采集这个节点上指定目录下的所有日志文件到日志系统。&lt;/p&gt;
 &lt;p&gt;这种方式的优点资源相对占用要小很多，也可以做到日志分类采集，日志采集几乎不影响服务的性能。缺点是扩展性受限。&lt;/p&gt;
 &lt;h1&gt;四、Sidecar 方式&lt;/h1&gt;
 &lt;p&gt;通过修改Deployment文件在一个pod里面运行两个容器，一个容器运行服务，另一个容器运行日志采集agent，并且让这两个容器共享同一个目录，服务将日志输出这个目录下面，日志采集agent采集这个目录下的所有的日志文件到日志系统。&lt;/p&gt;
 &lt;p&gt;这种方式的优点是日志采集容器和业务服务容器是独立的，系统资源不会相互影响 ，灵活性强，不受集群规模限制。&lt;/p&gt;
 &lt;p&gt;缺点是资源相对占用较多，因为针对每一个服务都要部署一个独立的agent容器，运维成本也相对较高。&lt;/p&gt;
 &lt;h1&gt;小结&lt;/h1&gt;
 &lt;p&gt;本文详细讲解了采集kubernetes容器化部署的服务日志的四种方法，每种方法都有优缺点和对应的使用场景，需要根据实际场景选择最合适的方式。&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/62743-k8s-%E5%AE%B9%E5%99%A8-%E6%9C%8D%E5%8A%A1</guid>
      <pubDate>Sat, 22 Apr 2023 17:19:59 CST</pubDate>
    </item>
    <item>
      <title>微服务架构中的链路超时分析</title>
      <link>https://itindex.net/detail/62713-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84-%E5%88%86%E6%9E%90</link>
      <description>&lt;h3&gt;1、前言&lt;/h3&gt;
 &lt;h4&gt;1.1 现象（问题）&lt;/h4&gt;
 &lt;p&gt;​微服务架构项目落地过程中，开发人员一般都遇到过调用超时问题，大部分时候会出现在feign接口调用上，这是微服务与单体服务最大的区别，单体从来不用考虑服务之间调用因为网络、序列化等因素导致的额外时间损耗问题。很多开发人员在微服务开发中通常会随手设置一个较长超时，原则就是别在feign接口调用超时，这个随手的超时时间可能是5分钟、10分钟，甚至1个小时不等，看似解决超时导致的问题，实际如果没有从整体微服务架构来考虑超时背后的因素，这样会导致给整个链路调用埋下隐患，可能会随机或者在高并发等情况下爆发。&lt;/p&gt;
 &lt;p&gt;​超时设置不正确会导致以下现象：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;应用不稳定，时不时出现小问题，问题复现困难，用户感知差。&lt;/li&gt;
  &lt;li&gt;前端请求卡住，也不知道是网络问题，还是应用问题或者是数据库问题，排查问题费时费力。&lt;/li&gt;
  &lt;li&gt;数据库资源浪费，出现空算。&lt;/li&gt;
  &lt;li&gt;应用空算，引发资源浪费或内存溢出。&lt;/li&gt;
  &lt;li&gt;高并发下，TPS和QPS同时出现异常下降。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;1.2 原则（结论）&lt;/h4&gt;
 &lt;p&gt;微服务架构（基于  &lt;code&gt;Spring Cloud&lt;/code&gt;）中，在行业应用中，超时设置都应满足以下条件：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;不应超过客户等待最大容忍度时间，这是一个弹性指标，通常在这个指标可以考虑在5s到30s之间；&lt;/li&gt;
  &lt;li&gt;超时设置应大于API计算最大时间，如果和上一条冲突，API计算应转入异步计算；&lt;/li&gt;
  &lt;li&gt;微服务链路超时各环节应保持一致性，并且从前端到后端到数据库（定义为从做左往右），越靠右超时设置应越短，链路超时应该是向右收敛。&lt;/li&gt;
  &lt;li&gt;快速失败，节省核心资源，特别是数据库。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;2、链路超时（细节）&lt;/h3&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;链路超时应满足向右收敛原则&lt;/p&gt;
   &lt;p&gt;假设网关超时或服务超时，数据库还依然在执行客户端提交的的慢查询，等结果计算出来后，中间链路已经超时，这个时候数据是无法响应的，相当于数据库的计算被浪费。而数据库又是极为珍贵的资源，这种调用一旦过多很快就会导致与数据相关的API出现响应故障。&lt;/p&gt;
   &lt;blockquote&gt;
    &lt;p&gt;默认情况下，整个微服务的调用链路是不符合这个要求的，所以一旦发生慢调用，很多时候会产生无效的计算，浪费资源或者直接影响服务的使用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;快速失败，满足向右收敛原则后，发生慢调用的时候，靠右侧的核心资源会先超时，通过调用链传递，快速失败响应，这个时候服务无论是进行降级还是熔断都可以快速降低系统的压力，并且还能及时向开发团队或者运维团队反馈问题。&lt;/p&gt;
   &lt;blockquote&gt;
    &lt;p&gt;通常情况下，资源越靠右侧，说明资源越珍贵，调用的代价也越大。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;缺省值，如果使用组件缺省值，一定要显式的设置参数。&lt;/p&gt;
   &lt;blockquote&gt;
    &lt;p&gt;注意：不同版本，默认值可能是不同的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;通常对链路入口（网关），服务入口（web中间件），服务调用（Feign），数据库调用（sql）等环节调整超时参数，遵守向右收敛原则。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;2.1 网关关键配置&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;全局超时配置&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;spring:
  cloud:
    gateway:     
      httpclient:
        connect-timeout: 45000 #毫秒
        response-timeout: 10000
        pool:
          type: elastic
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;单路由配置&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;      - id: per_route_timeouts
        uri: https://example.org
        predicates:
          - name: Path
            args:
              pattern: /delay/{timeout}
        metadata:
          response-timeout: 200
          connect-timeout: 200
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;connect-timeout是指网关到目标路由的连接超时时间（缺省45秒）。&lt;/p&gt;
 &lt;p&gt;response-timeout是指服务给网关返回响应的时间（默认应该是无限时间，暂时没分析源码）。&lt;/p&gt;
 &lt;p&gt;网关默认使用弹性连接连接池，默认的连接数是  &lt;code&gt;Integer.MAX_VALUE&lt;/code&gt;。&lt;/p&gt;
 &lt;p&gt;网关使用netty组件并且采用了响应式设计，大部分时候，网关不是整个链路的瓶颈。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;官网：https://cloud.spring.io/spring-cloud-gateway/reference/html/#http-timeouts-configuration&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;ul&gt;
  &lt;li&gt;服务端超时&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;server:
  netty:
    connection-timeout: 60000
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这个参数是外部连接与gateway建立连接的超时时间（应该是指tcp连接三次握手超时时间），目前该参数有争议。&lt;/p&gt;
 &lt;p&gt;在某些版本应该是固定是10s，配置参数无效。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;https://stackoverflow.com/questions/53587611/how-to-configure-netty-connection-timeout-for-spring-webflux&lt;/p&gt;
  &lt;p&gt;https://github.com/spring-projects/spring-boot/issues/15368&lt;/p&gt;
  &lt;p&gt;https://github.com/spring-projects/spring-boot/issues/18473&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h4&gt;2.2 Tomcat关键配置&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;以内嵌Tomcat为例：&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;server:
  tomcat:   
    accept-count: 100
    threads:
      max: 200
    max-connections: 8192
    connection-timeout: 60000
    keep-alive-timeout: 60000
    max-keep-alive-requests: 100
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;**threads.max：**表示服务器最大有多少个线程处理请求，默认200，实际上这个参数超过大多数服务器核心数，实际会降低服务器cpu处理速度，所以在业务处理中，应让tomcat应该让业务快速响应。&lt;/p&gt;
 &lt;p&gt;**max-connections：**表示服务器与客户端可以建立多少个连接数，即持有的连接数。tomcat缺省是8192个连接数，cpu未必有时间给你处理，但是可以保持连接。这个参数是客户感知型参数。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;accept-count:&lt;/strong&gt; 与服务器内核相关，是客户端传入给服务器内核，请求的backlog值，该值与服务器内核参数  &lt;code&gt;net.core.somaxconn&lt;/code&gt;取小后的值为最终起效的TCP内核全队列值。它表示在max-connections值达到预设的值后，服务器内核还能建立的连接数，这个连接保存在内核，还未被上层应用（tomcat）取走。该值在tomcat中默认是100，在Centos7.x版本中内核  &lt;code&gt;net.core.somaxconn&lt;/code&gt;是128。如果超过max-connections和accept-count总和，新的连接会被拒绝，即直接拒绝服务（直接返回connection refused）。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;connection-timeout：&lt;/strong&gt; 连接超时，URI所请求的内容被呈现出来前的超时时间。在SpringBoot2.x中缺省是60秒，注意：如果是使用标准server.xml的tomcat，缺省是20秒，不同版本的SpringBoot，其内嵌tomcat的连接超时可能不同，所以，建议直接指定该值。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;The number of milliseconds this Connector will wait, after accepting a connection, for the request URI line to be presented. Use a value of -1 to indicate no (i.e. infinite) timeout. The default value is 60000 (i.e. 60 seconds) but note that the standard server.xml that ships with Tomcat sets this to 20000 (i.e. 20 seconds). Unless disableUploadTimeout is set to false, this timeout will also be used when reading the request body (if any).&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;strong&gt;keep-alive-timeout：&lt;/strong&gt; keepalive的超时时间，缺省与connection-timeout相同。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;max-keep-alive-requests:&lt;/strong&gt; 最大的保持keepalive的请求数量。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;缺省情况下：tomcat可以保持8192个socket连接，系统内核帮忙保持100个连接。直至connection-timeout的时间。&lt;/p&gt;
  &lt;p&gt;同一个连接在保活期间可以多次请求和响应。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h4&gt;2.3 feign接口配置&lt;/h4&gt;
 &lt;p&gt;feign接口配置影响的是链路中服务之间的调用。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;feign全局服务超时&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;default配置项影响全局配置（是否只是影响缺省客户端待查）。在使用第三方客户端的时候，应是以第三个客户端为基准，例如httpclient或okhttp。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;feign:  
  client:
    config:
      # 全局配置
      default:
        loggerLevel: basic # NONE(默认)、BASIC、HEADERS、FULL
        connectTimeout: 30000 #毫秒
        readTimeout: 30000 #毫秒
  # 开启httpClient客户端作为http连接池
  httpclient:
    enabled: true
    max-connections: 200
    max-connections-per-route: 50 # feign单个路径的最大连接数
    connection-timeout: 30000
    connection-timer-repeat: 3000
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;feign独立服务超时&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;feign:
  client:
    config:
      # 设置FooClient的超时时间
      FooClient:
        connectTimeout: 5000
        readTimeout: 3000
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;单独给某接口设置超时时间&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;在feign接口里加入这个参数就可以单独为接口单独设置超时时间了&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;@FeignClient(name = &amp;quot;wood-system&amp;quot;,contextId = &amp;quot;wood-system-holiday-feign&amp;quot;)
public interface HolidayFeign {
    @GetMapping(&amp;quot;/api/holiday/{id}&amp;quot;)
    Result&amp;lt;SysHoliday&amp;gt; selectOne(Request.Options options,@PathVariable Long id);

    @PostMapping(&amp;quot;/api/holiday/page/{pageNum}/{pageSize}&amp;quot;)
    Result&amp;lt;Object&amp;gt; queryAllByPage(Request.Options options, @RequestBody SysHoliday holiday, @PathVariable int pageNum, @PathVariable int pageSize);

    ... ...
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;调用的时候new 一下Options对象&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;   @Resource
    private HolidayFeign holidayFeign;

@GetMapping(&amp;quot;{id}&amp;quot;)
    public Result&amp;lt;SysHoliday&amp;gt; selectOne(@PathVariable Long id) {
        Request.Options options = new Request.Options(10, TimeUnit.SECONDS, 10, TimeUnit.SECONDS, true);
        return holidayFeign.selectOne(options, id);
    }

    @PostMapping(&amp;quot;/page/{pageNum}/{pageSize}&amp;quot;)
    public Result&amp;lt;Object&amp;gt; queryAllByPage(@RequestBody SysHoliday holiday, @PathVariable int pageNum, @PathVariable int pageSize) {
        Request.Options options = new Request.Options(1, TimeUnit.SECONDS, 1, TimeUnit.SECONDS, true);
        Result&amp;lt;Object&amp;gt; result = holidayFeign.queryAllByPage(options, holiday, pageNum, pageSize);
        return result;
    }
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;关于ribbon的超时&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;因为 Feign 是基于 ribbon 来实现的，所以通过 ribbon 的超时时间设置也能达到目的。类似配置：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt; ribbon:  
    ReadTimeout: 5000 #单位毫秒
    ConnectTimeout: 2000 #单位毫秒

&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;实际上，在使用OpenFeign之后，ribbon已经无法直接配置超时，通常就是使用Feign来配置超时。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;注意：ribbon 的默认 ConnectTimeout 和 ReadTimeout 都是 1000 ms。这里有两处默认值，见源码。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;blockquote&gt;
  &lt;p&gt;注意：@FeignClient 注解的 url 参数进行服务调用时是不走ribbon的。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h4&gt;2.4 数据库配置&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;数据库连接池&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;  datasource:
    druid:    
      # 连接池属性
      initial-size: 15
      max-active: 100
      min-idle: 15
      # 配置从连接池获取连接等待超时的时间
      max-wait: 30000
      login-timeout: 30000
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;重点是关注max-wait参数：从连接池获取连接等待的时间，其他诸如连接时间、登录时间的超时对于一个正常的连接池反而不是重点，sql执行的时间不建议在连接池上设置超时，因为sql超时后的终止行为需要数据库引擎来执行，应该在数据库层面上设置时间。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;数据库引擎，sql查询执行超时设置&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;-- 默认是0，即无限
select @@max_execution_time;
show variables like &amp;apos;max_execution_time&amp;apos;;

-- 全局设置
SET GLOBAL MAX_EXECUTION_TIME=1000;
-- 对某个session设置
SET SESSION MAX_EXECUTION_TIME=1000;

-- 单独设定sql设置超时时间
SELECT /*+ MAX_EXECUTION_TIME(1000) */ sleep(3), a.* from project_info a;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;sql执行超时抛出错误：Query execution was interrupted, maximum statement execution time exceeded。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;数据库事务超时&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;# 默认是50秒
select @@innodb_lock_wait_timeout;
SHOW VARIABLES LIKE &amp;apos;innodb_lock_wait_timeout&amp;apos;;

set innodb_lock_wait_timeout=30;
set global innodb_lock_wait_timeout=30;
&lt;/code&gt;&lt;/pre&gt;
 &lt;blockquote&gt;
  &lt;p&gt;注意global的修改对当前线程是不生效的，只有建立新的连接才生效&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h3&gt;3、建议&lt;/h3&gt;
 &lt;h4&gt;3.1 超时时间设置太大的潜在危害&lt;/h4&gt;
 &lt;p&gt;​可能会有人认为简单的把链路延迟都放大，比如5分钟，这样避免了链路超时的问题。在流量比较小的应用中，不会产生太大影响，在流量较大的微服务架构中，链路设定高延时的同时，如果遇到应用计算或数据库计算发生慢调用，会瞬间拉低TPS，甚至会造成QPS和TPS都为0的恶劣情况。这也是在压测中，某些慢接口会导致整个应用吞吐量急剧下降的原因。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;原因：&lt;/strong&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;应用的接入是有上限的，在tomcat下，默认就是8192+100，如果高并发下发生慢调用，并且超时时间较长，无法快速失败或降级熔断等，那么所有请求都将等待。&lt;/li&gt;
  &lt;li&gt;cpu的核心数远远小于api的请求数，慢调用较多的时候，cpu应该被拉满，无法及时对正常调用做出响应。&lt;/li&gt;
  &lt;li&gt;慢调用越多，无效的，被废弃的请求就越多（包括正常调用），挤压的请求无法正常响应后，请求失败就会产生雪崩现象。&lt;/li&gt;
&lt;/ol&gt;
 &lt;h4&gt;3.2 优化手段&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;快慢分离：分库时，不但要考虑按业务分库，还需要考虑按响应和计算分库，例如统计操作一般比较耗时，考虑专门建立聚合统计库，专门的服务来支撑统计功能或慢查询功能。&lt;/li&gt;
  &lt;li&gt;修改默认：数据库相关超时的默认时间都比较长，这些地方是优先需要修改的，在暂时没办法分析流量、响应、计算等要素的情况下，前期是可以考虑将数据库超时设置为30秒到120秒不等，左侧链路依次放大，然后在应用使用过程中观察流量和响应的实际情况，阶段性调整参数。&lt;/li&gt;
  &lt;li&gt;链路优化：找到最右侧（一般是数据库）节点，设定可接受最小超时，然后往左侧逐步放大。&lt;/li&gt;
  &lt;li&gt;计算理论：正常情况下，行业应用，流量流入以及数据计算量、API计算量是有理论最低和理论最高值的，可根据这些数据先预设超时限制。预先分析慢调用链路，在应用服务和数据库上区别设计，做到快慢分离。&lt;/li&gt;
  &lt;li&gt;核心优先：整个微服务架构中，最核心的资源是数据库，它很难做到横向弹性，即使做到，也会有其他诸如分布式事务等因素拉低整体性能，所以，所有的设计都应优先保证数据库相关资源的高效合理利用。&lt;/li&gt;
  &lt;li&gt;监控分析：数据库查询往往是整个微服务调用链路的性能瓶颈，在初期，性能监测阶段通过开启慢查询日志，开启性能分析profiles等手段定位性能问题和找出性能瓶颈，优化整体性能。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;9、源码&lt;/h3&gt;
 &lt;h4&gt;9.1 SpringBoot中Tomcat的连接超时源码&lt;/h4&gt;
 &lt;h5&gt;9.1.1 web服务器工厂定制类自动配置  &lt;code&gt;EmbeddedWebServerFactoryCustomizerAutoConfiguration&lt;/code&gt;&lt;/h5&gt;
 &lt;p&gt;注入  &lt;code&gt;TomcatWebServerFactoryCustomizer&lt;/code&gt;，该类用于定制具体的web server工厂。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
   /**
 * Nested configuration if Tomcat is being used.
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
public static class TomcatWebServerFactoryCustomizerConfiguration {

@Bean
public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
}
}
    
    ... ...
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;在自动配置类中创建tomcat自定义工厂配置类，这个类的目的就是通过tomcat工厂类  &lt;code&gt;ConfigurableTomcatWebServerFactory&lt;/code&gt;对tomcat的参数进行最后的设置或覆盖，它是通过后置处理器完成调用的。&lt;/p&gt;
 &lt;p&gt;tomcat工厂类  &lt;code&gt;TomcatWebServerFactoryCustomizer&lt;/code&gt;是  &lt;code&gt;WebServerFactoryCustomizer&lt;/code&gt;接口的实现类。&lt;/p&gt;
 &lt;p&gt;以SpringBoot的回调机制，肯定是对  &lt;code&gt;WebServerFactoryCustomizer&lt;/code&gt;接口进行统一处理，通过查找  &lt;code&gt;WebServerFactoryCustomizer&lt;/code&gt;的接口调用或者查找  &lt;code&gt;customize()&lt;/code&gt;的调用都可以追溯到  &lt;code&gt;WebServerFactoryCustomizerBeanPostProcessor&lt;/code&gt;。&lt;/p&gt;
 &lt;h5&gt;9.1.2 tomcat工厂定制类  &lt;code&gt;TomcatWebServerFactoryCustomizer&lt;/code&gt;&lt;/h5&gt;
 &lt;p&gt;用来定制tomcat工厂类，即对SpringBoot注入的  &lt;code&gt;TomcatServletWebServerFactory&lt;/code&gt;进行配置。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public class TomcatWebServerFactoryCustomizer
implements WebServerFactoryCustomizer&amp;lt;ConfigurableTomcatWebServerFactory&amp;gt;, Ordered {

private final Environment environment;

private final ServerProperties serverProperties;

public TomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
this.environment = environment;
this.serverProperties = serverProperties;
}

@Override
public void customize(ConfigurableTomcatWebServerFactory factory) {
ServerProperties properties = this.serverProperties;

        ... ...

propertyMapper.from(tomcatProperties::getConnectionTimeout).whenNonNull()
.to((connectionTimeout) -&amp;gt; customizeConnectionTimeout(factory, connectionTimeout));
propertyMapper.from(tomcatProperties::getMaxConnections).when(this::isPositive)
.to((maxConnections) -&amp;gt; customizeMaxConnections(factory, maxConnections));
propertyMapper.from(tomcatProperties::getAcceptCount).when(this::isPositive)
.to((acceptCount) -&amp;gt; customizeAcceptCount(factory, acceptCount));
... ...
customizeStaticResources(factory);
customizeErrorReportValve(properties.getError(), factory);
}


private void customizeConnectionTimeout(ConfigurableTomcatWebServerFactory factory, Duration connectionTimeout) {
factory.addConnectorCustomizers((connector) -&amp;gt; {
ProtocolHandler handler = connector.getProtocolHandler();
if (handler instanceof AbstractProtocol) {
AbstractProtocol&amp;lt;?&amp;gt; protocol = (AbstractProtocol&amp;lt;?&amp;gt;) handler;
protocol.setConnectionTimeout((int) connectionTimeout.toMillis());
}
});
}
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;在定制工厂类  &lt;code&gt;TomcatWebServerFactoryCustomizer&lt;/code&gt;中，获取ServerProperties属性，重新设置所有可配置项，超时时间的缺省值就是在这里被间接覆盖的。通过  &lt;code&gt;customizeConnectionTimeout&lt;/code&gt;函数，给  &lt;code&gt;TomcatServletWebServerFactory&lt;/code&gt;添加  &lt;code&gt;TomcatConnectorCustomizer&lt;/code&gt;定制连接器参数，在后续的使用  &lt;code&gt;TomcatServletWebServerFactory&lt;/code&gt;创建tomcat中会调用  &lt;code&gt;TomcatConnectorCustomizer&lt;/code&gt;来定制参数。同时，在  &lt;code&gt;customizeConnectionTimeout&lt;/code&gt;可以发现超时时间是在通讯协议里设置的，这点很重要，意味着，我们在跟踪源码时，需要跟踪到具体的HTTP协议创建的类中。&lt;/p&gt;
 &lt;h5&gt;9.1.3 web服务器工厂配置类  &lt;code&gt;ServletWebServerFactoryConfiguration&lt;/code&gt;&lt;/h5&gt;
 &lt;p&gt;用来注入工厂类  &lt;code&gt;TomcatServletWebServerFactory&lt;/code&gt;。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {

@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider&amp;lt;TomcatConnectorCustomizer&amp;gt; connectorCustomizers,
ObjectProvider&amp;lt;TomcatContextCustomizer&amp;gt; contextCustomizers,
ObjectProvider&amp;lt;TomcatProtocolHandlerCustomizer&amp;lt;?&amp;gt;&amp;gt; protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers()
.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers()
.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers()
.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}

}
}    
&lt;/code&gt;&lt;/pre&gt;
 &lt;h5&gt;9.1.4 tomcat工厂类  &lt;code&gt;TomcatServletWebServerFactory&lt;/code&gt;&lt;/h5&gt;
 &lt;p&gt;真正用来创建tomcat的类。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {

private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    public static final String DEFAULT_PROTOCOL = &amp;quot;org.apache.coyote.http11.Http11NioProtocol&amp;quot;;
    private String protocol = DEFAULT_PROTOCOL;
    ... ...

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir(&amp;quot;tomcat&amp;quot;);
tomcat.setBaseDir(baseDir.getAbsolutePath());
for (LifecycleListener listener : this.serverLifecycleListeners) {
tomcat.getServer().addLifecycleListener(listener);
}
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}        
    
}    
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;从  &lt;code&gt;TomcatWebServerFactoryCustomizer&lt;/code&gt;中我们了解到连接超时的参数在通讯协议里，在上述源码里Connector接收协议名称，所以跟踪Connector可以定位到具体内容。&lt;/p&gt;
 &lt;h5&gt;9.1.5 tomcat的连接超时&lt;/h5&gt;
 &lt;ul&gt;
  &lt;li&gt;Connector&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;    /**
     * Defaults to using HTTP/1.1 NIO implementation.
     */
    public Connector() {
        this(&amp;quot;HTTP/1.1&amp;quot;);
    }

public Connector(String protocol) {
        boolean apr = AprStatus.getUseAprConnector() &amp;amp;&amp;amp; AprStatus.isInstanceCreated()
                &amp;amp;&amp;amp; AprLifecycleListener.isAprAvailable();
        ProtocolHandler p = null;
        try {
            p = ProtocolHandler.create(protocol, apr);
        } catch (Exception e) {
            log.error(sm.getString(
                    &amp;quot;coyoteConnector.protocolHandlerInstantiationFailed&amp;quot;), e);
        }
        if (p != null) {
            protocolHandler = p;
            protocolHandlerClassName = protocolHandler.getClass().getName();
        } else {
            protocolHandler = null;
            protocolHandlerClassName = protocol;
        }
        // Default for Connector depends on this system property
        setThrowOnFailure(Boolean.getBoolean(&amp;quot;org.apache.catalina.startup.EXIT_ON_INIT_FAILURE&amp;quot;));
    }
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;ProtocolHandler&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;    public static ProtocolHandler create(String protocol, boolean apr)
            throws ClassNotFoundException, InstantiationException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
        if (protocol == null || &amp;quot;HTTP/1.1&amp;quot;.equals(protocol)
                || (!apr &amp;amp;&amp;amp; org.apache.coyote.http11.Http11NioProtocol.class.getName().equals(protocol))
                || (apr &amp;amp;&amp;amp; org.apache.coyote.http11.Http11AprProtocol.class.getName().equals(protocol))) {
            if (apr) {
                return new org.apache.coyote.http11.Http11AprProtocol();
            } else {
                return new org.apache.coyote.http11.Http11NioProtocol();
            }
        } else if (&amp;quot;AJP/1.3&amp;quot;.equals(protocol)
                || (!apr &amp;amp;&amp;amp; org.apache.coyote.ajp.AjpNioProtocol.class.getName().equals(protocol))
                || (apr &amp;amp;&amp;amp; org.apache.coyote.ajp.AjpAprProtocol.class.getName().equals(protocol))) {
            if (apr) {
                return new org.apache.coyote.ajp.AjpAprProtocol();
            } else {
                return new org.apache.coyote.ajp.AjpNioProtocol();
            }
        } else {
            // Instantiate protocol handler
            Class&amp;lt;?&amp;gt; clazz = Class.forName(protocol);
            return (ProtocolHandler) clazz.getConstructor().newInstance();
        }
    }
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;Http11NioProtocol&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;public class Http11NioProtocol extends AbstractHttp11JsseProtocol&amp;lt;NioChannel&amp;gt; {

    private static final Log log = LogFactory.getLog(Http11NioProtocol.class);


    public Http11NioProtocol() {
        super(new NioEndpoint());
    }    
}    


public abstract class AbstractHttp11JsseProtocol&amp;lt;S&amp;gt;
        extends AbstractHttp11Protocol&amp;lt;S&amp;gt; {

    public AbstractHttp11JsseProtocol(AbstractJsseEndpoint&amp;lt;S,?&amp;gt; endpoint) {
        super(endpoint);
    }
    ... ...
}

public abstract class AbstractHttp11Protocol&amp;lt;S&amp;gt; extends AbstractProtocol&amp;lt;S&amp;gt; {

... ...

    public AbstractHttp11Protocol(AbstractEndpoint&amp;lt;S,?&amp;gt; endpoint) {
        super(endpoint);
        setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
        ConnectionHandler&amp;lt;S&amp;gt; cHandler = new ConnectionHandler&amp;lt;&amp;gt;(this);
        setHandler(cHandler);
        getEndpoint().setHandler(cHandler);
    }
    
    public void setConnectionTimeout(int timeout) {
        endpoint.setConnectionTimeout(timeout);
    }
}    

public final class Constants {

    public static final int DEFAULT_CONNECTION_TIMEOUT = 60000;
    ... ...
}    
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;AbstractEndpoint&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;public abstract class AbstractEndpoint&amp;lt;S,U&amp;gt; {

   ... ... 

    public static long toTimeout(long timeout) {
        // Many calls can&amp;apos;t do infinite timeout so use Long.MAX_VALUE if timeout is &amp;lt;= 0
        return (timeout &amp;gt; 0) ? timeout : Long.MAX_VALUE;
    }
    
    /**
     * Socket timeout.
     *
     * @return The current socket timeout for sockets created by this endpoint
     */
    public int getConnectionTimeout() { return socketProperties.getSoTimeout(); }
    public void setConnectionTimeout(int soTimeout) { socketProperties.setSoTimeout(soTimeout); }    
}

/**
*Properties that can be set in the &amp;lt;Connector&amp;gt; element in server.xml. 
*All properties are prefixed with &amp;quot;socket.&amp;quot; and are currently only working for the Nio connector
*/
public class SocketProperties {
    ...
     /**
     * SO_TIMEOUT option. default is 20000.
     */
    protected Integer soTimeout = Integer.valueOf(20000);
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;最终的超时时间SO_TIMEOUT，体现在socket的read()上，并且在socket层面上，缺省超时是20秒，这个值会被tomcat创建Connector类实例化时的60秒常量参数覆盖。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;SO_TIMEOUT&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;Enable/disable SO_TIMEOUT with the specified timeout, in milliseconds. With this option set to a non-zero timeout, a read() call on the InputStream associated with this Socket will block for only this amount of time. If the timeout expires, a java.net.SocketTimeoutException is raised, though the Socket is still valid. The option must be enabled prior to entering the blocking operation to have effect. The timeout must be &amp;gt; 0. A timeout of zero is interpreted as an infinite timeout.
&lt;/code&gt;&lt;/pre&gt;
 &lt;h5&gt;9.1.6 SpringBoot创建tomcat服务&lt;/h5&gt;
 &lt;ul&gt;
  &lt;li&gt;入口   &lt;code&gt;refresh()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;//org.springframework.context.support.AbstractApplicationContext#refresh
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
......
try {
....       
// Initialize other special beans in specific context subclasses.                    
onRefresh();
                ....
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);                    
}
}
}

//org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh
@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException(&amp;quot;Unable to start web server&amp;quot;, ex);
   
    }
}

//org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer
private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null &amp;amp;&amp;amp; servletContext == null) {
        ServletWebServerFactory factory = getWebServerFactory();
        this.webServer = factory.getWebServer(getSelfInitializer());
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException(&amp;quot;Cannot initialize servlet context&amp;quot;, ex);
        }
    }
    initPropertySources();
}

//org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {
        Registry.disableRegistry();
    }
    Tomcat tomcat = new Tomcat();
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir(&amp;quot;tomcat&amp;quot;);
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    //初始化
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    prepareContext(tomcat.getHost(), initializers);
    return getTomcatWebServer(tomcat);
}

&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;在  &lt;code&gt;onRefresh()&lt;/code&gt;完成tomcat服务器创建，并赋予默认参数。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;创建bean以及后置处理   &lt;code&gt;finishBeanFactoryInitialization(beanFactory)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;/**
 * Finish the initialization of this context&amp;apos;s bean factory,
 * initializing all remaining singleton beans.
 */
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {

        ... ...

// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
}



public &amp;lt;T&amp;gt; T getBean(String name, @Nullable Class&amp;lt;T&amp;gt; requiredType, @Nullable Object... args)
throws BeansException {

return doGetBean(name, requiredType, args, false);
}


protected &amp;lt;T&amp;gt; T doGetBean(
String name, @Nullable Class&amp;lt;T&amp;gt; requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {

... ...
return createBean(beanName, mbd, args);
         ... ...
}


/**
 * Central method of this class: creates a bean instance,
 * populates the bean instance, applies post-processors, etc.
 * @see #doCreateBean
 */
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
        
        
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
...
return beanInstance;

... ...
}

/**
 * Actually create the specified bean. Pre-creation processing has already happened
 * at this point, e.g. checking {@code postProcessBeforeInstantiation} callbacks.
 * &amp;lt;p&amp;gt;Differentiates between default bean instantiation, use of a
 * factory method, and autowiring a constructor.
 * @param beanName the name of the bean
 * @param mbd the merged bean definition for the bean
 * @param args explicit arguments to use for constructor or factory method invocation
 * @return a new instance of the bean
 * @throws BeanCreationException if the bean could not be created
 * @see #instantiateBean
 * @see #instantiateUsingFactoryMethod
 * @see #autowireConstructor
 */
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {

// Instantiate the bean.
    ... ...
Object bean = instanceWrapper.getWrappedInstance();
... ...

// Initialize the bean instance.
Object exposedObject = bean;
        // Populate the bean instance in the given BeanWrapper with the property values from the bean definition.
populateBean(beanName, mbd, instanceWrapper);
        
exposedObject = initializeBean(beanName, exposedObject, mbd);
... ...
}


/**
 * Initialize the given bean instance, applying factory callbacks
 * as well as init methods and bean post processors.
 * &amp;lt;p&amp;gt;Called from {@link #createBean} for traditionally defined beans,
 * and from {@link #initializeBean} for existing bean instances.
 * @param beanName the bean name in the factory (for debugging purposes)
 * @param bean the new bean instance we may need to initialize
 * @param mbd the bean definition that the bean was created with
 * (can also be {@code null}, if given an existing bean instance)
 * @return the initialized bean instance (potentially wrapped)
 * @see BeanNameAware
 * @see BeanClassLoaderAware
 * @see BeanFactoryAware
 * @see #applyBeanPostProcessorsBeforeInitialization
 * @see #invokeInitMethods
 * @see #applyBeanPostProcessorsAfterInitialization
 */
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction&amp;lt;Object&amp;gt;) () -&amp;gt; {
invokeAwareMethods(beanName, bean);
return null;
}, getAccessControlContext());
}
else {
invokeAwareMethods(beanName, bean);
}

Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}

try {
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, &amp;quot;Invocation of init method failed&amp;quot;, ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}

return wrappedBean;
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;真正的bean后置处理在  &lt;code&gt;initializeBean&lt;/code&gt;中完成。&lt;/p&gt;
 &lt;h4&gt;9.2 Gateway连接数源码&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;网关自动配置   &lt;code&gt;GatewayAutoConfiguration&lt;/code&gt;，连接相关主要是   &lt;code&gt;HttpClient&lt;/code&gt;，注意：它是基于netty的响应式客户端，不是apache的HttpClient。&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = &amp;quot;spring.cloud.gateway.enabled&amp;quot;, matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore({ HttpHandlerAutoConfiguration.class,
WebFluxAutoConfiguration.class })
@AutoConfigureAfter({ GatewayLoadBalancerClientAutoConfiguration.class,
GatewayClassPathWarningAutoConfiguration.class })
@ConditionalOnClass(DispatcherHandler.class)
public class GatewayAutoConfiguration {
    ... ...
    
    @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HttpClient.class)
protected static class NettyConfiguration {

... ...

@Bean
@ConditionalOnMissingBean
public HttpClient gatewayHttpClient(HttpClientProperties properties,
List&amp;lt;HttpClientCustomizer&amp;gt; customizers) {

// configure pool resources
HttpClientProperties.Pool pool = properties.getPool();

ConnectionProvider connectionProvider;
if (pool.getType() == DISABLED) {
connectionProvider = ConnectionProvider.newConnection();
}
else if (pool.getType() == FIXED) {
connectionProvider = ConnectionProvider.fixed(pool.getName(),
pool.getMaxConnections(), pool.getAcquireTimeout(),
pool.getMaxIdleTime(), pool.getMaxLifeTime());
}
else {
connectionProvider = ConnectionProvider.elastic(pool.getName(),
pool.getMaxIdleTime(), pool.getMaxLifeTime());
}

HttpClient httpClient = HttpClient.create(connectionProvider)
                ...
// TODO: move customizations to HttpClientCustomizers
.tcpConfiguration(tcpClient -&amp;gt; {

if (properties.getConnectTimeout() != null) {
tcpClient = tcpClient.option(
ChannelOption.CONNECT_TIMEOUT_MILLIS,
properties.getConnectTimeout());
}


... ...

return httpClient;
}

... ...

}    
    
｝
                                      
static ConnectionProvider elastic(String name, @Nullable Duration maxIdleTime, @Nullable Duration maxLifeTime) {
return builder(name).maxConnections(Integer.MAX_VALUE)
                    .pendingAcquireTimeout(Duration.ofMillis(0))
                    .pendingAcquireMaxCount(-1)
                    .maxIdleTime(maxIdleTime)
                    .maxLifeTime(maxLifeTime)
                    .build();
}                                      
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;在elastic类型下，连接数是  &lt;code&gt;Integer.MAX_VALUE&lt;/code&gt;。&lt;/p&gt;
 &lt;h4&gt;9.3 Gateway连接超时源码&lt;/h4&gt;
 &lt;ul&gt;
  &lt;li&gt;缺省值45秒，是硬代码&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;public abstract class TcpClient {
    ... ...
/**
 * Block the {@link TcpClient} and return a {@link Connection}. Disposing must be
 * done by the user via {@link Connection#dispose()}. The max connection
 * timeout is 45 seconds.
 *
 * @return a {@link Mono} of {@link Connection}
 */
public final Connection connectNow() {
return connectNow(Duration.ofSeconds(45));
}

/**
 * Block the {@link TcpClient} and return a {@link Connection}. Disposing must be
 * done by the user via {@link Connection#dispose()}.
 *
 * @param timeout connect timeout
 *
 * @return a {@link Mono} of {@link Connection}
 */
public final Connection connectNow(Duration timeout) {
Objects.requireNonNull(timeout, &amp;quot;timeout&amp;quot;);
try {
return Objects.requireNonNull(connect().block(timeout), &amp;quot;aborted&amp;quot;);
}
catch (IllegalStateException e) {
if (e.getMessage().contains(&amp;quot;blocking read&amp;quot;)) {
throw new IllegalStateException(&amp;quot;TcpClient couldn&amp;apos;t be started within &amp;quot;
+ timeout.toMillis() + &amp;quot;ms&amp;quot;);
}
throw e;
}
}    
｝
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;参数覆盖&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;在  &lt;code&gt;GatewayAutoConfiguration&lt;/code&gt;中&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public class GatewayAutoConfiguration {
    ... ...
    
    @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HttpClient.class)
protected static class NettyConfiguration {

... ...

@Bean
@ConditionalOnMissingBean
public HttpClient gatewayHttpClient(HttpClientProperties properties,
List&amp;lt;HttpClientCustomizer&amp;gt; customizers) {


HttpClient httpClient = HttpClient.create(connectionProvider)
                ...

.tcpConfiguration(tcpClient -&amp;gt; {

if (properties.getConnectTimeout() != null) {
tcpClient = tcpClient.option(
ChannelOption.CONNECT_TIMEOUT_MILLIS,
properties.getConnectTimeout());
}


... ...

return httpClient;
}

... ...

}    
    
｝
     
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;code&gt;HttpClient&lt;/code&gt;是抽象类，其实现类  &lt;code&gt;HttpClientConnect&lt;/code&gt;聚合了  &lt;code&gt;TcpClient&lt;/code&gt;的实现类  &lt;code&gt;HttpTcpClient&lt;/code&gt;。&lt;/p&gt;
 &lt;h4&gt;9.4 ribbon超时源码&lt;/h4&gt;
 &lt;p&gt;ribbon 的默认配置在   &lt;code&gt;DefaultClientConfigImpl&lt;/code&gt; 。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;    public static final int DEFAULT_READ_TIMEOUT = 5000;

    public static final int DEFAULT_CONNECTION_MANAGER_TIMEOUT = 2000;

    public static final int DEFAULT_CONNECT_TIMEOUT = 2000;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;在使用 ribbon 请求接口时，第一次会构建一个 IClienConfig 对象，这个方法在 RibbonClientConfiguration 类中，此时，重新设置了 ConnectTimeout、ReadTimeout等。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public class RibbonClientConfiguration {

    /**
     * Ribbon client default connect timeout.
     */
    public static final int DEFAULT_CONNECT_TIMEOUT = 1000;

    /**
     * Ribbon client default read timeout.
     */
    public static final int DEFAULT_READ_TIMEOUT = 1000;

    /**
     * Ribbon client default Gzip Payload flag.
     */
    public static final boolean DEFAULT_GZIP_PAYLOAD = true;

    @RibbonClientName
    private String name = &amp;quot;client&amp;quot;;

    @Autowired
    private PropertiesFactory propertiesFactory;

    @Bean
    @ConditionalOnMissingBean
    public IClientConfig ribbonClientConfig() {
        DefaultClientConfigImpl config = new DefaultClientConfigImpl();
        config.loadProperties(this.name);
        config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
        config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
        config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
        return config;
    }
    
    //...
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;ribbon 的默认 ConnectTimeout 和 ReadTimeout 都是 1000 ms。&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/62713-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84-%E5%88%86%E6%9E%90</guid>
      <pubDate>Wed, 29 Mar 2023 08:47:10 CST</pubDate>
    </item>
    <item>
      <title>MySQL扛不住？B站千亿级点赞系统服务架构设计</title>
      <link>https://itindex.net/detail/62637-mysql-%E5%8D%83%E4%BA%BF-%E7%B3%BB%E7%BB%9F</link>
      <description>&lt;p&gt;为了在提供上述能力的前提下经受住流量、存储、容灾三大压力，点赞目前的系统实现方式如下：&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;   &lt;p&gt;    &lt;strong&gt; 1、系统架构简介&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;img alt="&amp;#22270;&amp;#29255;" src="https://dbaplus.cn/uploadfile/2023/0224/20230224110233662.png"&gt;&lt;/img&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;strong&gt;流量路由层&lt;/strong&gt;（决定流量应该去往哪个机房）&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;li&gt;      &lt;p&gt;        &lt;strong&gt;点赞服务&lt;/strong&gt;（thumbup-service）,提供统一的RPC接口&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;        &lt;strong&gt;点赞异步任务&lt;/strong&gt;（thumbup-job）&lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;        &lt;strong&gt;数据层&lt;/strong&gt;（db、kv、redis）&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;下文将重点分享下    &lt;strong&gt;数据存储层、点赞服务层（thumbup-service）&lt;/strong&gt;与     &lt;strong&gt;异步任务层（thumbup-job）&lt;/strong&gt;的系统设计&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;   &lt;p&gt;    &lt;strong&gt;2、三级数据存储&lt;/strong&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;ul&gt;    &lt;li&gt;      &lt;p&gt;点赞记录表：记录用户在什么时间对什么实体进行了什么类型的操作(是赞还是踩，是取消点赞还是取消点踩)等&lt;/p&gt;      &lt;p&gt; &lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;点赞计数表：记录被点赞实体的累计点赞（踩）数量&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;strong&gt;（1）第一层存储：DB层 - （TiDB）&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;点赞系统中最为重要的就是点赞记录表（likes）和点赞计数表（counts），负责整体数据的持久化保存，以及提供缓存失效时的回源查询能力。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;点赞记录表 - likes : 每一次的点赞记录（用户Mid、被点赞的实体ID（messageID）、点赞来源、时间）等信息，并且在Mid、messageID两个维度上建立了满足业务求的联合索引。&lt;/p&gt;      &lt;p&gt; &lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;点赞数表 - counts : 以业务ID（BusinessID）+实体ID(messageID)为主键，聚合了该实体的点赞数、点踩数等信息。并且按照messageID维度建立满足业务查询的索引。&lt;/p&gt;      &lt;p&gt; &lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;由于DB采用的是分布式数据库TiDB，所以对业务上无需考虑分库分表的操作&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;strong&gt;（2）第二层存储&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;缓存层Cache：点赞作为一个高流量的服务，缓存的设立肯定是必不可少的。点赞系统主要使用的是CacheAside模式。这一层缓存主要基于Redis缓存：以点赞数和用户点赞列表为例&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;img alt="&amp;#22270;&amp;#29255;" src="https://dbaplus.cn/uploadfile/2023/0224/20230224110245667.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;ul&gt;    &lt;li&gt; &lt;/li&gt;    &lt;li&gt; &lt;/li&gt;&lt;/ul&gt;  &lt;pre&gt;    &lt;code&gt;key-value= count:patten:{business_id}:{message_id} - {likes},{disLikes}&lt;/code&gt;    &lt;code&gt;用业务ID和该业务下的实体ID作为缓存的Key,并将点赞数与点踩数拼接起来存储以及更新&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;strong&gt;②用户点赞列表&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;img alt="&amp;#22270;&amp;#29255;" src="https://dbaplus.cn/uploadfile/2023/0224/20230224110254998.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;ul&gt;    &lt;li&gt; &lt;/li&gt;    &lt;li&gt; &lt;/li&gt;    &lt;li&gt; &lt;/li&gt;&lt;/ul&gt;  &lt;pre&gt;    &lt;code&gt;key-value= user:likes:patten:{mid}:{business_id} - member(messageID)-score(likeTimestamp)&lt;/code&gt;    &lt;code&gt;* 用mid与业务ID作为key，value则是一个ZSet,member为被点赞的实体ID，score为点赞的时间。当改业务下某用户有新的点赞操作的时候，被点赞的实体则会通过 zadd的方式把最新的点赞记录加入到该ZSet里面来&lt;/code&gt;    &lt;code&gt;为了维持用户点赞列表的长度（不至于无限扩张），需要在每一次加入新的点赞记录的时候，按照固定长度裁剪用户的点赞记录缓存。该设计也就代表用户的点赞记录在缓存中是有限制长度的，超过该长度的数据请求需要回源DB查询&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;strong&gt;（3）第三层存储&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;LocalCache - 本地缓存&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;本地缓存的建立，目的是为了应对缓存热点问题。&lt;/p&gt;      &lt;p&gt; &lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;利用最小堆算法，在可配置的时间窗口范围内，统计出访问最频繁的缓存Key,并将热Key（Value）按照业务可接受的TTL存储在本地内存中。&lt;/p&gt;      &lt;p&gt; &lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;其中热点的发现之前也有同步过：https://mp.weixin.qq.com/s/C8CI-1DDiQ4BC_LaMaeDBg&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;strong&gt;（4）针对TIDB海量历史数据的迁移归档&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;迁移归档的原因(初衷)，是为了减少TIDB的存储容量,节约成本的同时也多了一层存储，可以作为灾备数据。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;以下是在KV数据库（taishan）中点赞的数据与索引的组织形式：&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;ul&gt;    &lt;li&gt; &lt;/li&gt;&lt;/ul&gt;  &lt;pre&gt;    &lt;code&gt;1_{mid}_${business_id}_${type}_${message_id}=&amp;gt; {origin_id}_{mtime}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;strong&gt;②用户点赞列表索引&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;img alt="&amp;#22270;&amp;#29255;" src="https://dbaplus.cn/uploadfile/2023/0224/20230224110305476.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;ul&gt;    &lt;li&gt; &lt;/li&gt;&lt;/ul&gt;  &lt;pre&gt;    &lt;code&gt;2_{mid}_${business_id}_${type}_${mtime}_{message_id} =&amp;gt; {origin_id}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;strong&gt;③实体维度点赞记录索引&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;img alt="&amp;#22270;&amp;#29255;" src="https://dbaplus.cn/uploadfile/2023/0224/20230224110315814.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;ul&gt;    &lt;li&gt; &lt;/li&gt;&lt;/ul&gt;  &lt;pre&gt;    &lt;code&gt;3_{message_id}_${business_id}_${type}_${mtime}_${mid}=&amp;gt;{origin_id}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt; &lt;/p&gt;   &lt;p&gt;    &lt;strong&gt; 3、存储层的优化和思考&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;strong&gt;（1）满足业务读写需求的同时具备最大的可靠性&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;strong&gt;（2）选择合适的存储介质与数据存储形态，最小化存储成本&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;从以上两点触发，考虑到KV数据在业务查询以及性能上都更契合点赞的业务形态，且TaiShan可以水平扩容来满足业务的增长。点赞服务从当前的关系型数据库（TiDB）+ 缓存（Redis）逐渐过渡至KV型数据库（Taishan）+ 缓存（Redis），以具备更强的可靠性。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;同时TaiShan作为公司自研的KV数据库，在成本上也能更优于使用TiDB存储。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;   &lt;p&gt;    &lt;strong&gt; 4、点赞服务层（thumbup-service）&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;作为面对C端流量的直接接口，在提供服务的同时，需要思考在面对各种未知或者可预知的灾难时，如何尽可能提供服务&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;        &lt;strong&gt;存储（db、redis等）的容灾设计&lt;/strong&gt;（同城多活）&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;在DB的设计上，点赞服务有两地机房互为灾备，正常情况下，机房1承载所有写流量与部分读流量，机房2承载部分读流量。当DB发生故障时，通过db-proxy（sidercar）的切换可以将读写流量切换至备份机房继续提供服务。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;img alt="&amp;#22270;&amp;#29255;" src="https://dbaplus.cn/uploadfile/2023/0224/20230224110326315.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;img alt="&amp;#22270;&amp;#29255;" src="https://dbaplus.cn/uploadfile/2023/0224/20230224110336144.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;在缓存（Redis）上，点赞服务也拥有两套处于不同机房的集群，并且通过异步任务消费TiDB的binLog维护两地缓存的一致性。可以在需要时切换机房来保证服务的提供，而不会导致大量的冷数据回源数据库。&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;strong&gt;多层数据存储互为灾备&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;点赞的热数据在redis缓存中存有一份。&lt;/p&gt;      &lt;p&gt; &lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;kv数据库中存有全量的用户数据，当缓存不可用时，KV数据库会扛起用户的所有流量来提供服务。&lt;/p&gt;      &lt;p&gt; &lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;TIDB目前也存储有全量的用户数据，当缓存、KV均不可用时，tidb会依托于限流，最大程度提供用户数据的读写服务。&lt;/p&gt;      &lt;p&gt; &lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;因为存在多重存储，所以一致性也是业务需要衡量的点。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt; &lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;首先写入到每一个存储都是有错误重试机制的，且重要的环节，比如点赞记录等是无限重试的。&lt;/p&gt;      &lt;p&gt; &lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;另外，在拥有重试机制的场景下，极少数的不同存储的数据不一致在点赞的业务场景下是可以被接受的&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;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;ul&gt;    &lt;li&gt;      &lt;p&gt;点赞机房、缓存、数据库等都在不同机房有备份数据，可以在某一机房或者某地中间件发生故障时快速切换。&lt;/p&gt;&lt;/li&gt;&lt;/ul&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;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;strong&gt; 5、异步任务层（thumbup-job）&lt;/strong&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;首先是最重要的用户行为数据（点赞、点踩、取消等）的写入。搭配对数据库的限流组件以及消费速度监控，保证数据的写入不超过数据库的负荷的同时也不会出现数据堆积造成的C数据端查询延迟问题。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;img alt="&amp;#22270;&amp;#29255;" src="https://dbaplus.cn/uploadfile/2023/0224/20230224110351401.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;缓存刷新：点赞状态缓存、点赞列表缓存、点赞计数缓存&lt;/p&gt;      &lt;p&gt; &lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;同步点赞消息&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;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;ul&gt;    &lt;li&gt;      &lt;p&gt;针对 WriteBack方式的思考&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt; &lt;/p&gt;  &lt;ul&gt;    &lt;li&gt;      &lt;p&gt;由于目前点赞系统异步处理能力或者说速率是能够满足业务的。所以当前写DB与写缓存都放在异步流程中。&lt;/p&gt;      &lt;p&gt; &lt;/p&gt;&lt;/li&gt;    &lt;li&gt;      &lt;p&gt;后续随着流量的增加，实施流程中写缓存，再由异步Job写入持久层相对来说是一个更好的方案。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;    &lt;strong&gt;点赞job对binLog的容灾设计&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;由于点赞的存储为TiDB,且数据量较大。在实际生产情况中，binLog会偶遇数据延迟甚至是断流的问题。为了减少binLog数据延迟对服务数据的影响。服务做了以下改造。&lt;/p&gt;  &lt;p&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;/ul&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;首先在运维层面、代码层面都对binLog的实时性、是否断流做了监控&lt;/p&gt;  &lt;p&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;/ul&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;脱离binlog，由业务层（thumb-service）发送重要的数据信息（点赞数变更、点赞状态事件）等。当发生数据延迟时，程序会自动同时消费由thumbup-service发送的容灾消息，继续向下游发送。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt; &lt;p&gt;三、未来规划&lt;/p&gt; &lt;p&gt; &lt;/p&gt; &lt;p&gt;1、点赞服务单元化：要陆续往服务单元化的方向迭代、演进。&lt;/p&gt; &lt;p&gt; &lt;/p&gt; &lt;p&gt;2、点赞服务平台化：在目前的业务接入基础上增加迭代数据分库存储能力，做到服务、数据自定义隔离。&lt;/p&gt; &lt;p&gt; &lt;/p&gt; &lt;p&gt;3、点赞业务形态探索：以社区为基础，继续探索通过点赞衍生的业务形态。&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/62637-mysql-%E5%8D%83%E4%BA%BF-%E7%B3%BB%E7%BB%9F</guid>
      <pubDate>Sun, 26 Feb 2023 14:57:21 CST</pubDate>
    </item>
    <item>
      <title>面对突发流量，保证服务可用的4个手段</title>
      <link>https://itindex.net/detail/62630-%E6%B5%81%E9%87%8F-%E6%9C%8D%E5%8A%A1</link>
      <description>&lt;hr&gt;&lt;/hr&gt;
 &lt;h2&gt;theme: cyanosis&lt;/h2&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dd8b7683dc4643ccb2fe941945aa6b13~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;前言&lt;/h2&gt;
 &lt;p&gt;不知道你有没有这样的经历，线上的系统突然来了很大的流量，有可能是黑客的攻击，也有可能是业务量远远大于你的预估，如果你的系统没有做任何的防护措施，这时候系统负载过高，系统资源慢慢耗尽，接口响应越来越慢，直至不可用，这又导致了调用你接口的上游系统发生资源耗尽的情况，最终导致系统雪崩。想想就知道，这是一个灾难性的后果，那么有什么方法呢？&lt;/p&gt;
 &lt;p&gt;面对这种突发流量的场景，核心思路就是要优先保证优核心业务和优先保证绝大部分用户。常见的应对手段有四种，降级、熔断、限流和排队，下面我会一一讲解。&lt;/p&gt;
 &lt;h2&gt;1. 降级&lt;/h2&gt;
 &lt;p&gt;降级指系统将某些业务或者接口的功能降低，可以是只提供部分功能，也可以是完全停掉所有功能，优先保证核心功能。&lt;/p&gt;
 &lt;p&gt;比如淘宝双11零点抢购的时候你会发现商品的退货功能不可以使用了。又比如论坛可以降级为只能看帖子，不能发帖子；也可以降级为只能看帖子和评论，不能发评论；&lt;/p&gt;
 &lt;p&gt;常见的实现降级的方式有两种：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;系统后门降级&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;简单来说，就是系统预留了后门用于降级操作。例如，系统提供一个降级URL，当访问这个URL时，就相当于执行降级指令，具体的降级指令通过URL的参数传入即可。这种方案有一定的安全隐患，所以也会在URL中加入密码这类安全措施。&lt;/p&gt;
 &lt;p&gt;系统后门降级的方式实现成本低，但主要缺点是如果服务器数量多，需要一台一台去操作，效率比较低，这在故障处理争分夺秒的场景下是比较浪费时间的。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;独立降级系统&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&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/57e393e459584717b321cedf3966d91e~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;2. 熔断&lt;/h2&gt;
 &lt;p&gt;熔断是指按照一定的规则，比如1分钟内60%的请求响应错误就停掉对外部接口的访问，防止某些外部接口故障导致自己的系统处理能力急剧下降或者出故障。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/06a497fe456c4718b36a1afcd801618d~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;熔断和降级是两个比较容易混淆的概念，因为单纯从名字上看，好像都有禁止某个功能的意思。但它们的内涵是不同的，因为降级的目的是应对系统自身的故障，而熔断的目的是应对依赖的外部系统故障的情况。&lt;/p&gt;
 &lt;p&gt;关于服务熔断的实现，比较主流的有两种方案，  &lt;code&gt;Spring Cloud Netflix Hystrix&lt;/code&gt;和阿里的  &lt;code&gt;Sentinel&lt;/code&gt;,我们公司的项目用的是  &lt;code&gt;Sentinel&lt;/code&gt;。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Hystrix是一个用于处理分布式系统的延迟和容错的一个开源库，在分布式系统里，许多依赖不可避免的会调用失败，比如超时、异常等，Hystrix能保证在一个依赖出现问题的情况下，不会导致整体服务失败，避免级联故障，以提高分布式系统的稳定性。&lt;/li&gt;
  &lt;li&gt;Sentinel 是阿里中间件团队开源的，面向分布式服务架构的轻量级高可用流量控制组件，主要以流量为切入点，从流量控制、熔断降级、系统负载保护等多个维度来帮助用户保护服务的稳定性。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;3. 限流&lt;/h2&gt;
 &lt;p&gt;每个系统都有服务的上线，所以当流量超过服务极限能力时，系统可能会出现卡死、崩溃的情况，所以就有了降级和限流。限流其实就是：当高并发或者瞬时高并发时，为了保证系统的稳定性、可用性，系统以牺牲部分请求为代价或者延迟处理请求为代价，保证系统整体服务可用。&lt;/p&gt;
 &lt;p&gt;限流一般都是系统内实现的，常见的限流方式可以分为两类：基于请求限流和基于资源限流。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;基于请求限流&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;基于请求限流指从外部访问的请求角度考虑限流，常见的方式有两种。&lt;/p&gt;
 &lt;p&gt;第一种是限制总量，也就是限制某个指标的累积上限，常见的是限制当前系统服务的用户总量，例如：某个直播间限制总用户数上限为100万，超过100万后新的用户无法进入；某个抢购活动商品数量只有100个，限制参与抢购的用户上限为1万个，1万以后的用户直接拒绝。&lt;/p&gt;
 &lt;p&gt;第二种是限制时间量，也就是限制一段时间内某个指标的上限，例如1分钟内只允许10000个用户访问；每秒请求峰值最高为10万。&lt;/p&gt;
 &lt;p&gt;无论是限制总量还是限制时间量，共同的特点都是实现简单，但在实践中面临的主要问题是比较难以找到合适的阈值。例如系统设定了1分钟10000个用户，但实际上6000个用户的时候系统就扛不住了；或者达到1分钟10000用户后，其实系统压力还不大，但此时已经开始丢弃用户访问了。&lt;/p&gt;
 &lt;p&gt;即使找到了合适的阈值，基于请求限流还面临硬件相关的问题。例如一台32核的机器和64核的机器处理能力差别很大，阈值是不同的，可能有的技术人员以为简单根据硬件指标进行数学运算就可以得出来，实际上这样是不可行的，64核的机器比32核的机器，业务处理性能并不是2倍的关系，可能是1.5倍，甚至可能是1.1倍。&lt;/p&gt;
 &lt;p&gt;为了找到合理的阈值，通常情况下可以采用性能压测来确定阈值，但性能压测也存在覆盖场景有限的问题，可能出现某个性能压测没有覆盖的功能导致系统压力很大；另外一种方式是逐步优化：先设定一个阈值然后上线观察运行情况，发现不合理就调整阈值。&lt;/p&gt;
 &lt;p&gt;基于上述的分析，根据阈值来限制访问量的方式更多的适应于业务功能比较简单的系统，例如负载均衡系统、网关系统、抢购系统等。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;基于资源限流&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;基于请求限流是从系统外部考虑的，而基于资源限流是从系统内部考虑的，也就是找到系统内部影响性能的关键资源，对其使用上限进行限制。常见的内部资源包括连接数、文件句柄、线程数和请求队列等。&lt;/p&gt;
 &lt;p&gt;例如，采用Netty来实现服务器，每个进来的请求都先放入一个队列，业务线程再从队列读取请求进行处理，队列长度最大值为10000，队列满了就拒绝后面的请求；也可以根据CPU的负载或者占用率进行限流，当CPU的占用率超过80%的时候就开始拒绝新的请求。&lt;/p&gt;
 &lt;p&gt;基于资源限流相比基于请求限流能够更加有效地反映当前系统的压力，但实际设计时也面临两个主要的难点：如何确定关键资源，以及如何确定关键资源的阈值。&lt;/p&gt;
 &lt;p&gt;通常情况下，这也是一个逐步调优的过程：设计的时候先根据推断选择某个关键资源和阈值，然后测试验证，再上线观察，如果发现不合理，再进行优化。&lt;/p&gt;
 &lt;h2&gt;4. 排队&lt;/h2&gt;
 &lt;p&gt;排队这种方式，想必大家在熟悉不过了。大家在12306买火车票的时候，是不是会告诉你在排队中，等待一段时间后才会锁定车票，付款。年底时，全中国那么多人买票，12306就是通过排队机制来搞定的。但是也有缺点，那就是用户体验没那么好。&lt;/p&gt;
 &lt;p&gt;由于排队需要临时缓存大量的业务请求，单个系统内部无法缓存这么多数据，一般情况下，排队需要用独立的系统去实现，例如使用Kafka这类消息队列来缓存用户请求。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5c84bac89c224d758f68ed319ab5b560~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;排队模块&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;负责接收用户的抢购请求，将请求以先入先出的方式保存下来。每一个参加秒杀活动的商品保存一个队列，队列的大小可以根据参与秒杀的商品数量（或加点余量）自行定义。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;调度模块&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;负责排队模块到服务模块的动态调度，不断检查服务模块，一旦处理能力有空闲，就从排队队列头上把用户访问请求调入服务模块，并负责向服务模块分发请求。这里调度模块扮演一个中介的角色，但不只是传递请求而已，它还担负着调节系统处理能力的重任。我们可以根据服务模块的实际处理能力，动态调节向排队系统拉取请求的速度。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;服务模块&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;负责调用真正业务来处理服务，并返回处理结果，调用排队模块的接口回写业务处理结果。&lt;/p&gt;
 &lt;h2&gt;总结&lt;/h2&gt;
 &lt;p&gt;最后我们通过一个表格在总结以下上面4种保证服务高可用的手段。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6a7e0e543f2f4f2db02afd5698006842~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;
参考：  &lt;code&gt;https://freegeektime.com/100006601/10312/&lt;/code&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;欢迎关注个人公众号【JAVA旭阳】交流沟通&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/62630-%E6%B5%81%E9%87%8F-%E6%9C%8D%E5%8A%A1</guid>
      <pubDate>Sat, 25 Feb 2023 08:13:15 CST</pubDate>
    </item>
    <item>
      <title>微服务之间的调用方式哪种最佳？</title>
      <link>https://itindex.net/detail/62602-%E5%BE%AE%E6%9C%8D%E5%8A%A1</link>
      <description>&lt;div&gt;    &lt;br /&gt;    &lt;p&gt;在微服务架构中，需要调用很多服务才能完成一项功能。服务之间如何互相调用就变成微服务架构中的一个关键问题。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;服务调用有两种方式，一种是RPC方式，另一种是事件驱动（Event-driven）方式，也就是发消息方式。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;消息方式是松耦合方式，比紧耦合的RPC方式要优越，但RPC方式如果用在适合的场景也有它的一席之地。&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;strong&gt;一、耦合的种类&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;1）时间耦合：&lt;/strong&gt;客户端和服务端必须同时上线才能工作。发消息时，接受消息队列必须运行，但后台处理程序暂时不工作也不影响。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;2）容量耦合：&lt;/strong&gt;客户端和服务端的处理容量必须匹配。发消息时，如果后台处理能力不足也不要紧，消息队列会起到缓冲的作用。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;3）接口耦合：&lt;/strong&gt;RPC调用有函数标签，而消息队列只是一个消息。例如买了商品之后要调用发货服务，如果是发消息，那么就只需发送一个商品被买消息。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;4）发送方式耦合：&lt;/strong&gt;RPC是点对点方式，需要知道对方是谁，它的好处是能够传回返回值。消息既可以点对点，也可以用广播的方式，这样减少了耦合，但也使返回值比较困难。&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;真正需要的是自动伸缩（Auto-scaling），它能自动调整服务端处理能力去匹配请求数量。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;第三和第四，接口耦合和发送方式耦合，这两个确实是RPC方式的软肋。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;二、事件驱动（Event-Driven）方式&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;Martin Fowler把事件驱动分成四种方式(What do you mean by “Event-Driven”)，简化之后本质上只有两种方式。一种就是我们熟悉的的事件通知（Event Notification），另一种是事件溯源（Event Sourcing）。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;事件通知就是微服务之间不直接调用，而是通过发消息来进行合作。事件溯源有点像记账，它把所有的事件都记录下来，作为永久存储层，再在它的基础之上构建应用程序。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;实际上从应用的角度来讲，它们并不应该分属一类，它们的用途完全不同。事件通知是微服务的调用（或集成）方式，应该和RPC分在一起。事件溯源是一种存储数据的方式，应该和数据库分在一起。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;     &lt;strong&gt;1、事件通知（Event Notification）&lt;/strong&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;让我们用具体的例子来看一下。在下面的例子中，有三个微服务，“Order Service”，“Customer Service”和“Product Service”。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;      &lt;img alt="&amp;#22270;&amp;#29255;" src="https://dbaplus.cn/uploadfile/2023/0129/20230129100617795.jpg"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;先说读数据，假设要创建一个“Order”，在这个过程中需要读取“Customer”的数据和“Product”数据。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;如果用事件通知的方式就只能在“Order Service”本地也创建只读“Customer”和“Product”表，并把数据用消息的方式同步过来。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;再说写数据，如果在创建一个“Order”时需要创建一个新的“Customer”或要修改“Customer”的信息，那么可以在界面上跳转到用户创建页面，然后在“Customer Service”创建用户之后再发”用户已创建“的消息，“Order Service”接到消息，更新本地“Customer”表。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;这并不是一个很好的使用事件驱动的例子，因为事件驱动的优点就是不同的程序之间可以独立运行，没有绑定关系。但现在“Order Service”需要等待“Customer Service”创建完了之后才能继续运行，来完成整个创建“Order”的工作。主要是因为“Order”和“Customer”本身从逻辑上来讲就是紧耦合关系，没有“Customer”你是不能创建“Order”的。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;在这种紧耦合的情况下，也可以使用RPC。你可以建立一个更高层级的管理程序来管理这些微服务之间的调用，这样“Order Service”就不必直接调用“Customer Service”了。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;当然它从本质上来讲并没有解除耦合，只是把耦合转移到了上一层，但至少现在“order Service”和“Customer Service”可以互不影响了。之所以不能根除这种紧耦合关系是因为它们在业务上是紧耦合的。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;再举一个购物的例子。用户选好商品之后进行“Checkout”，生成“Order”，然后需要“payment”，再从“Inventory”取货，最后由“Shipment”发货，它们每一个都是微服务。这个例子用RPC方式和事件通知方式都可以完成。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;当用RPC方式时，由“Order”服务调用其他几个服务来完成整个功能。用事件通知方式时，“Checkout”服务完成之后发送“Order Placed”消息，“Payment”服务收到消息，接收用户付款，发送“Payment received”消息。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;“Inventory”服务收到消息，从仓库里取货，并发送“Goods fetched”消息。“Shipment”服务得到消息，发送货物，并发送“Goods shipped”消息。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;      &lt;img alt="&amp;#22270;&amp;#29255;" src="https://dbaplus.cn/uploadfile/2023/0129/20230129100632748.png"&gt;&lt;/img&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;如果一个业务逻辑有它自己相对固定的流程和步骤，那么使用RPC或业务流程管理（BPM）能够更方便地管理这些流程。在这种情况下选哪种方案呢？在我看来好处和坏处是大致相当的。从技术上来讲要选事件驱动，从业务上来讲要选RPC。不过现在越来越多的人采用事件通知作为微服务的集成方式，它似乎已经成了微服务之间的标椎调用方式。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;     &lt;strong&gt;2、事件溯源（Event Sourcing）&lt;/strong&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;这是一种具有颠覆性质的的设计，它把系统中所有的数据都以事件（Event）的方式记录下来，它的持久存储叫Event Store， 一般是建立在数据库或消息队列（例如Kafka）基础之上，并提供了对事件进行操作的接口，例如事件的读写和查询。事件溯源是由领域驱动设计(Domain-Driven Design)提出来的。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;DDD中有一个很重要的概念，有界上下文（Bounded Context），可以用有界上下文来划分微服务，每个有界上下文都可以是一个微服务。下面是有界上下文的示例。下图中有两个服务“Sales”和“Support”。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;有界上下文的一个关键是如何处理共享成员， 在图中是“Customer”和“Product”。在不同的有界上下文中，共享成员的含义、用法以及他们的对象属性都会有些不同，DDD建议这些共享成员在各自的有界上下文中都分别建自己的类（包括数据库表），而不是共享。可以通过数据同步的手段来保持数据的一致性。下面还会详细讲解。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;      &lt;img alt="&amp;#22270;&amp;#29255;" src="https://dbaplus.cn/uploadfile/2023/0129/20230129100645923.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;事件溯源是微服务的一种存储方式，它是微服务的内部实现细节。因此你可以决定哪些微服务采用事件溯源方式，哪些不采用，而不必所有的服务都变成事件溯源的。通常整个应用程序只有一个Event Store， 不同的微服务都通过向Event Store发送和接受消息而互相通信。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;Event Store内部可以分成不同的stream（相当于消息队列中的Topic）， 供不同的微服务中的领域实体（Domain Entity）使用。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;事件溯源的一个短板是数据查询，它有两种方式来解决。第一种是直接对stream进行查询，这只适合stream比较小并且查询比较简单的情况。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;查询复杂的话，就要采用第二种方式，那就是建立一个只读数据库，把需要的数据放在库中进行查询。数据库中的数据通过监听Event Store中相关的事件来更新。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;数据库存储方式只能保存当前状态，而事件溯源则存储了所有的历史状态，因而能根据需要回放到历史上任何一点的状态，具有很大优势。但它也不是一点问题都没有。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;第一，它的程序比较复杂，因为事件是一等公民，你必须把业务逻辑按照事件的方式整理出来，然后用事件来驱动程序。第二，如果你要想修改事件或事件的格式就比较麻烦，因为旧的事件已经存储在Event Store里了（事件就像日志，是只读的），没有办法再改。&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;当你要集成这些微服务时，你可以用事件通知的方式。注意现在有两种不同的事件需要区分开，一种是微服务的内部事件，是颗粒度比较细的，这种事件只发送到这个微服务的stream中，只被事件溯源使用。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;另一种是其他微服务也关心的，是颗粒度比较粗的，这种事件会放到另外一个或几个stream中，被多个微服务使用，是用来做服务之间集成的。这样做的好处是限制了事件的作用范围，减少了不相关事件对程序的干扰。详见&amp;quot;Domain Events vs. Event Sourcing&amp;quot;。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;事件溯源出现已经很长时间了，虽然热度一直在上升（尤其是这两年），但总的来说非常缓慢，谈论的人不少，但生产环境使用的不多。究其原因就是应为它对现在的体系结构颠覆太大，需要更改数据存储结构和程序的工作方式，还是有一定风险的。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;另外，微服务已经形成了一整套体系，从程序部署，服务发现与注册，到监控，服务韧性（Service Resilience），它们基本上都是针对RPC的，虽然也支持消息，但成熟度就差多了，因此有不少工作还是要自己来做。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;有意思的是Kafka一直在推动它作为事件驱动的工具，也取得了很大的成功，但它却没有得到事件溯源圈内的认可。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;多数事件溯源都使用一个叫evenstore的开源Event Store，或是基于某个数据库的Event Store，只有比较少的人用Kafka做Event Store。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;但如果用Kafka实现事件通知就一点问题都没有。总的来说，对大多数公司来讲事件溯源是有一定挑战的，应用时需要找到合适的场景。如果你要尝试的话，可以先拿一个微服务试水。&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;另外AI今后会渗入到具体程序中，使程序具有学习功能。而RPC模式注定没有自适应功能。事件驱动本身就具有对事件进行反应的能力，这是自我学习的基础。因此，这项技术长远来讲定会大放异彩，但短期内（3-5年）大概不会成为主流。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;三、RPC方式&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;RPC的方式就是远程函数调用，像RESTFul，gRPC, DUBBO 都是这种方式。它一般是同步的，可以马上得到结果。在实际中，大多数应用都要求立刻得到结果，这时同步方式更有优势，代码也更简单。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;     &lt;strong&gt;1、服务网关（API Gateway）&lt;/strong&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;熟悉微服务的人可能都知道服务网关（API Gateway）。当UI需要调用很多微服务时，它需要了解每个服务的接口，这个工作量很大。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;于是就用服务网关创建了一个Facade，把几个微服务封装起来，这样UI就只调用服务网关就可以了，不需要去对付每一个微服务。下面是API Gateway示例图：&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;      &lt;img alt="&amp;#22270;&amp;#29255;" src="https://dbaplus.cn/uploadfile/2023/0129/20230129100708412.jpg"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;服务网关（API Gateway）不是为了解决微服务之间调用的紧耦合问题，它主要是为了简化客户端的工作。其实它还可以用来降低函数之间的耦合度。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;有了API Gateway之后，一旦服务接口修改，你可能只需要修改API Gateway， 而不必修改每个调用这个函数的客户端，这样就减少了程序的耦合性。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;     &lt;strong&gt;2、服务调用&lt;/strong&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;可以借鉴API Gateway的思路来减少RPC调用的耦合度，例如把多个微服务组织起来形成一个完整功能的服务组合，并对外提供统一的服务接口。这种想法跟上面的API Gateway有些相似，都是把服务集中起来提供粗颗粒（Coarse Granular）服务，而不是细颗粒的服务（Fine Granular）。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;但这样建立的服务组合可能只适合一个程序使用，没有多少共享价值。因此如果有合适的场景就采用，否侧也不必强求。虽然我们不能降低RPC服务之间的耦合度，却可以减少这种紧耦合带来的影响。&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;      &lt;strong&gt;1）同时支持多个版本：&lt;/strong&gt;这个工作量比较大，因此大多数公司都不会采用这种方式。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;2）服务端向后兼容：&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;它的解决办法就是使用一个支持向后兼容的RPC协议，现在最好的就是Protobuf gRPC，尤其是在向后兼容上。&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;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;值得庆幸的是微服务带来的麻烦都是可解的。直到后来，微服务建立了全套的自动化体系，从程序集成到部署，从全链路跟踪到日志，以及服务检测，服务发现和注册，这样才把微服务的工作量降了下来。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;虽然微服务在技术上一无是处，但它的流行还是大大推动了容器技术，服务网格（Service Mesh）和全链路跟踪等新技术的发展。不过它本身在技术上还是没有发现任何优势。&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;Martin Fowler几年以前建议先从单体程序开始（详见 MonolithFirst），然后再逐步把功能拆分出去，变成一个个的微服务。但是后来有人反对这个建议，他也有些松口了。&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;strong&gt;1、内部微服务设计&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;img alt="&amp;#22270;&amp;#29255;" src="https://dbaplus.cn/uploadfile/2023/0129/20230129100735517.png"&gt;&lt;/img&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;“User”（在Conference Management模块中）是一个共享的类，但在不同的模块中的名字不同，含义和用法也不同，成员也不一样（例如，在“Customer Service”里叫“Customer”）。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;DDD（Domain-Driven Design）建议不要共享这个类，而是在每一个有界上下文（模块）中都建一个新类，并拥有新的名字。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;虽然它们的数据库中的数据应该大致相同，但DDD建议每一个有界上下文中都建一个新表，它们之间再进行数据同步。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;这个所谓的“内部微服务设计”其实就是DDD，但当时还没有微服务，因此外表看起来是单体程序，但内部已经是微服务的设计了。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;它的书在2003就出版了，当时就很有名。但它更偏重于业务逻辑的设计，践行起来也比较困难，因此大家谈论得很多，真正用的较少。&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;直到十年之后，微服务出来之后，人们发现它其实内部就是微服务，而且微服务的设计需要用它的思想来指导，于是就又重新焕发了青春，而且这次更猛，已经到了每个谈论微服务的人都不得不谈论DDD的地步。不过一本软件书籍，在十年之后还能指导新技术的设计，非常令人钦佩。&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;strong&gt;2、结论&lt;/strong&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;微服务之间的调用有两种方式，RPC和事件驱动。事件驱动是更好的方式，因为它是松耦合的。但如果业务逻辑是紧耦合的，RPC方式也是可行的（它的好处是代码更简单），而且你还可以通过选取合适的协议（Protobuf gRPC）来降低这种紧耦合带来的危害。&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;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/62602-%E5%BE%AE%E6%9C%8D%E5%8A%A1</guid>
      <pubDate>Tue, 31 Jan 2023 13:57:34 CST</pubDate>
    </item>
    <item>
      <title>分布式微服务系统的跨库查询/操作的解决思路（关系型数据库）</title>
      <link>https://itindex.net/detail/62592-%E5%88%86%E5%B8%83-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E7%B3%BB%E7%BB%9F</link>
      <description>&lt;p&gt;在后端开发过程中，我们绕不开的就是数据结构设计以及关联的问题。&lt;/p&gt;
 &lt;p&gt;然而在传统的单体架构的开发中，解决数据关联的问题并不难，通过关系型数据库中的关联查询功能，以及MyBatis的级联功能即可实现。&lt;/p&gt;
 &lt;p&gt;但是在分布式微服务中，  &lt;strong&gt;整个系统都被拆分成了一个个单独的模块，每个模块也都是使用的单独的数据库&lt;/strong&gt;。这种情况下，又如何解决不同模块之间数据关联问题呢？&lt;/p&gt;
 &lt;p&gt;事实上，分布式微服务是非常复杂的，无论是系统架构，还是数据结构设计，都没有一个统一的方案，因此根据实际情况进行确定即可，对于数据关联的跨库查询，事实上也有很多方法，在网上有如下思路：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;数据冗余法&lt;/li&gt;
  &lt;li&gt;远程连接表&lt;/li&gt;
  &lt;li&gt;数据复制&lt;/li&gt;
  &lt;li&gt;使用非关系型数据库&lt;/li&gt;
  &lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;今天，我就来分享一个简单的分布式微服务跨库查询操作，大家可以参考一下。&lt;/p&gt;
 &lt;p&gt;我们还是从  &lt;strong&gt;一对多&lt;/strong&gt;，  &lt;strong&gt;多对多&lt;/strong&gt;的角度来解决这个问题。&lt;/p&gt;
 &lt;h2&gt;1，学习前需要了解&lt;/h2&gt;
 &lt;p&gt;在继续往下看之前，我想先介绍一下这次的示例中所使用的组件：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Spring Cloud    &lt;code&gt;2022.0.0&lt;/code&gt;和Spring Cloud Alibaba    &lt;code&gt;2022.0.0.0-RC1&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;MyBatis-Plus作为ORM框架&lt;/li&gt;
  &lt;li&gt;Dynamic Datasource作为多数据源切换组件&lt;/li&gt;
  &lt;li&gt;Nacos作为注册中心&lt;/li&gt;
  &lt;li&gt;MySQL数据库&lt;/li&gt;
  &lt;li&gt;OpenFeign远程调用&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;因此，在往下看之前，需要先掌握上述这些组件的使用，本文不再赘述。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;之前都是使用MyBatis作为ORM框架，而MyBatis-Plus可以视作其升级版，省去了我们写繁杂的Mapper.xml文件的步骤，上手特别简单。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;ul&gt;
  &lt;li&gt;MyBatis-Plus官方文档：   &lt;a href="https://baomidou.com/"&gt;传送门&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Dynamic Datasource官方文档：   &lt;a href="https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611"&gt;传送门&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;单体架构中数据结构关联的操作方式：   &lt;a href="https://juejin.cn/post/7003761716680982536"&gt;传送门&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Maven多模块项目配置：   &lt;a href="https://juejin.cn/post/7063015951012200456"&gt;传送门&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Jackson注解过滤字段：   &lt;a href="https://juejin.cn/post/6989101311937478664"&gt;传送门&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;将上述所有前置内容掌握之后，再来往下看最好。&lt;/p&gt;
 &lt;h2&gt;2，跨库操作解决思路&lt;/h2&gt;
 &lt;p&gt;我们从数据的联系形式，即  &lt;strong&gt;一对多&lt;/strong&gt;和  &lt;strong&gt;多对多&lt;/strong&gt;这两个角度依次进行分析。&lt;/p&gt;
 &lt;h3&gt;(1) 一对多&lt;/h3&gt;
 &lt;p&gt;一对多事实上比较好解决，这里我使用  &lt;strong&gt;字段冗余 + 远程调用&lt;/strong&gt;的方式解决。&lt;/p&gt;
 &lt;p&gt;这里以**订单(  &lt;code&gt;Order&lt;/code&gt;)  &lt;strong&gt;和&lt;/strong&gt;用户(  &lt;code&gt;User&lt;/code&gt;)**为例，订单对象中通常要包含用户关系，一个用户会产生多个订单，因此用户和订单构成了一对多的关系。&lt;/p&gt;
 &lt;p&gt;在  &lt;strong&gt;单体架构&lt;/strong&gt;中，我们很容易想到设计成这样：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/36708b8731db4106b049a479aa0fbec7~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这样，在查询订单的时候，可以通过关联查询的方式得到用户字段信息。&lt;/p&gt;
 &lt;p&gt;但是在分布式微服务中，用户和订单模块被拆分开来，两者的数据库也分开了，无法使用关联查询了，怎么办呢？&lt;/p&gt;
 &lt;p&gt;这时，我们可以在订单类中，  &lt;strong&gt;冗余一个   &lt;code&gt;userId&lt;/code&gt;字段&lt;/strong&gt;，可以直接从数据库取出，再通过  &lt;strong&gt;远程调用&lt;/strong&gt;的方式调用用户模块，用这个  &lt;code&gt;userId&lt;/code&gt;去得到用户对象，  &lt;strong&gt;最后组装&lt;/strong&gt;即可。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/08bca370d4684b109ad4117aee1b8485~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这样，  &lt;strong&gt;订单服务查询订单对象&lt;/strong&gt;，可以分为如下几步：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;先直接   &lt;strong&gt;从数据库取出订单对象&lt;/strong&gt;，这样上述   &lt;code&gt;Order&lt;/code&gt;类中的   &lt;code&gt;id&lt;/code&gt;、   &lt;code&gt;name&lt;/code&gt;和   &lt;code&gt;userId&lt;/code&gt;都可以直接从数据库取出&lt;/li&gt;
  &lt;li&gt;然后拿着这个   &lt;code&gt;userId&lt;/code&gt;的值去   &lt;strong&gt;远程调用用户服务得到用户对象&lt;/strong&gt;，填充到   &lt;code&gt;user&lt;/code&gt;字段&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;与此同时，我们还可以注意一下细节：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;将   &lt;code&gt;Order&lt;/code&gt;对象返回给前端时，   &lt;strong&gt;可以过滤掉冗余字段    &lt;code&gt;userId&lt;/code&gt;&lt;/strong&gt;，节省流量，通过   &lt;code&gt;Jackson&lt;/code&gt;注解可以实现&lt;/li&gt;
  &lt;li&gt;前端若要将   &lt;code&gt;Order&lt;/code&gt;对象作为参数传递给后端，则   &lt;strong&gt;无需带着    &lt;code&gt;user&lt;/code&gt;字段内容&lt;/strong&gt;，这样前端传来后可以直接丢进数据库，并且更加简洁&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;(2) 多对多&lt;/h3&gt;
 &lt;p&gt;我们知道，多对多通常是以搭桥表方式实现关联。&lt;/p&gt;
 &lt;p&gt;在此我们增加一个  &lt;strong&gt;商品类(   &lt;code&gt;Product&lt;/code&gt;)&lt;/strong&gt;，和订单类构成多对多关系，即需要查询一个订单中包含的所有商品，还需要查询这个商品被哪些订单包含。&lt;/p&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/a53bc928a8da4262a5d7d18b6f196bf8~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;那么在分布式微服务中，数据库分开的情况下，  &lt;strong&gt;这个搭桥表   &lt;code&gt;order_product&lt;/code&gt;放在哪呢？&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;可以将其单独放在一个数据库中，这个数据库在这里称之为  &lt;strong&gt;搭桥表数据库&lt;/strong&gt;。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/de777f781469409c8ac582d77120caa9~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这样，比如说  &lt;strong&gt;订单服务查询订单&lt;/strong&gt;的时候，可以分为如下几步：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;先直接从   &lt;strong&gt;订单数据库&lt;/strong&gt;查询出订单信息，这样   &lt;code&gt;Order&lt;/code&gt;类中的   &lt;code&gt;id&lt;/code&gt;、   &lt;code&gt;name&lt;/code&gt;和   &lt;code&gt;userId&lt;/code&gt;就得到了&lt;/li&gt;
  &lt;li&gt;然后从   &lt;strong&gt;搭桥表数据库&lt;/strong&gt;去查询   &lt;strong&gt;和这个订单关联的商品    &lt;code&gt;id&lt;/code&gt;&lt;/strong&gt;，这样就得到了一个商品   &lt;code&gt;id&lt;/code&gt;列表&lt;/li&gt;
  &lt;li&gt;用这个商品   &lt;code&gt;id&lt;/code&gt;列表去   &lt;strong&gt;远程调用商品服务&lt;/strong&gt;，查询到每个   &lt;code&gt;id&lt;/code&gt;对应的商品对象得到一个   &lt;strong&gt;商品对象列表&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;将商品列表组装到   &lt;code&gt;Order&lt;/code&gt;中的   &lt;code&gt;products&lt;/code&gt;字段中&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;那么反过来，商品服务也是通过一样的方式得到订单列表并组装。&lt;/p&gt;
 &lt;p&gt;可见，这两个多对多模块，有下列细节：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;需要用到两个数据库，因此需要配置   &lt;strong&gt;多数据源&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;两者需要暴露批量   &lt;code&gt;id&lt;/code&gt;查询的接口，但是批量   &lt;code&gt;id&lt;/code&gt;查询的时候，要注意死循环问题，这个我们在下面代码中具体来看&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;(3) 总结&lt;/h3&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="image.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b942c429a86f4de39343c94c6278cbd5~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/3907d827f686470a91b4db76508de0a4~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;那么下面，我们就来实现一下。&lt;/p&gt;
 &lt;h2&gt;3，代码实现&lt;/h2&gt;
 &lt;h3&gt;(1) 环境配置&lt;/h3&gt;
 &lt;p&gt;在写代码之前，我们先要在本地搭建并运行好MySQL和Nacos注册中心，这里我已经在本地通过Docker的方式部署好了，大家可以先自行部署。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8caeece2dbe944b3b748c102c4495755~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&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/0f194b2a869942a097225d9498f6a225~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;存放全部实体类的模块，是普通Maven项目，被其它模块依赖&lt;/li&gt;
  &lt;li&gt;远程调用层，是普通Maven项目，其它服务模块依赖这个模块进行远程调用&lt;/li&gt;
  &lt;li&gt;订单服务模块，是Spring Boot项目&lt;/li&gt;
  &lt;li&gt;商品服务模块，是Spring Boot项目&lt;/li&gt;
  &lt;li&gt;用户服务模块，是Spring Boot项目&lt;/li&gt;
&lt;/ol&gt;
 &lt;blockquote&gt;
  &lt;p&gt;我们知道服务提供者和服务消费者在整个分布式微服务中是非常相对的概念，而服务消费者是需要进行远程调用的，这样每个服务消费者都要引入OpenFeign依赖并注入等等，因此我们可以   &lt;strong&gt;单独把所有的消费者的远程调用层    &lt;code&gt;feignclient&lt;/code&gt;抽离出来&lt;/strong&gt;，作为这个远程调用模块。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;在最后我会给出项目的仓库的地址，大家可以在示例仓库中自行查看每个模块的配置文件和依赖配置。&lt;/p&gt;
 &lt;h3&gt;(2) 数据库的初始化&lt;/h3&gt;
 &lt;p&gt;在MySQL中通过以下命令，创建如下数据库：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;create database `db_order`;
create database `db_product`;
create database `db_user`;
create database `db_bridge`;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;上述  &lt;code&gt;db_bridge&lt;/code&gt;就是专门存放搭桥表的数据库。&lt;/p&gt;
 &lt;p&gt;然后依次初始化三个数据库。&lt;/p&gt;
 &lt;p&gt;  &lt;code&gt;db_order&lt;/code&gt;数据库：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;-- 订单数据库
drop table if exists `order_info`;

create table `order_info`
(
`id`      int unsigned auto_increment,
`name`    varchar(16)  not null,
`user_id` int unsigned not null,
primary key (`id`)
) engine = InnoDB
  default charset = utf8mb4;

-- 测试数据
insert into `order_info` (`name`, `user_id`)
values (&amp;apos;订单1&amp;apos;, 1), -- id:1~4
   (&amp;apos;订单2&amp;apos;, 1),
   (&amp;apos;订单3&amp;apos;, 2),
   (&amp;apos;订单4&amp;apos;, 3);
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;code&gt;db_product&lt;/code&gt;数据库：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;-- 商品数据库
drop table if exists `product`;

create table `product`
(
`id`   int unsigned auto_increment,
`name` varchar(32) not null,
primary key (`id`)
) engine = InnoDB
  default charset = utf8mb4;

-- 初始化测试数据
insert into `product` (`name`)
values (&amp;apos;商品1&amp;apos;), -- id:1~3
   (&amp;apos;商品2&amp;apos;),
   (&amp;apos;商品3&amp;apos;);
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;code&gt;db_user&lt;/code&gt;数据库：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;-- 用户数据库
drop table if exists `user`;

create table `user`
(
`id`       int unsigned auto_increment,
`username` varchar(16) not null,
primary key (`id`)
) engine = InnoDB
  default charset = utf8mb4;

-- 初始化数据
insert into `user` (`username`)
values (&amp;apos;dev&amp;apos;), -- id:1~3
   (&amp;apos;test&amp;apos;),
   (&amp;apos;admin&amp;apos;);
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;code&gt;db_bridge&lt;/code&gt;数据库：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;-- 多对多关联记录数据库
drop table if exists `order_product`;

-- 订单-商品多对多关联表
create table `order_product`
(
`order_id`   int unsigned,
`product_id` int unsigned,
primary key (`order_id`, `product_id`)
) engine = InnoDB
  default charset = utf8mb4;

-- 初始化测试数据
insert into `order_product`
values (1, 1),
   (1, 2),
   (2, 1),
   (2, 2),
   (3, 2),
   (3, 3),
   (4, 1),
   (4, 2),
   (4, 3);
&lt;/code&gt;&lt;/pre&gt;
 &lt;h3&gt;(3) 实体类的定义&lt;/h3&gt;
 &lt;p&gt;所有的实体类存放在  &lt;code&gt;db-entity&lt;/code&gt;模块中。&lt;/p&gt;
 &lt;p&gt;首先我们还是定义一个结果类  &lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt;专用于返回给前端：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;package com.gitee.swsk33.dbentity.model;

import lombok.Data;

import java.io.Serializable;

/**
 * 返回给前端的结果对象
 */
@Data
public class Result&amp;lt;T&amp;gt; implements Serializable {

/**
 * 是否操作成功
 */
private boolean success;

/**
 * 消息
 */
private String message;

/**
 * 数据
 */
private T data;

/**
 * 设定成功
 *
 * @param message 消息
 */
public void setResultSuccess(String message) {
this.success = true;
this.message = message;
this.data = null;
}

/**
 * 设定成功
 *
 * @param message 消息
 * @param data    数据
 */
public void setResultSuccess(String message, T data) {
this.success = true;
this.message = message;
this.data = data;
}

/**
 * 设定失败
 *
 * @param message 消息
 */
public void setResultFailed(String message) {
this.success = false;
this.message = message;
this.data = null;
}

}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;然后就是数据库对象了。&lt;/p&gt;
 &lt;h4&gt;1. 用户类&lt;/h4&gt;
 &lt;pre&gt;  &lt;code&gt;package com.gitee.swsk33.dbentity.dataobject;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;

/**
 * 用户类
 */
@Data
public class User {

/**
 * 用户id
 */
@TableId(type = IdType.AUTO)
private Integer id;

/**
 * 用户名
 */
private String username;

}
&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;2. 商品类&lt;/h4&gt;
 &lt;pre&gt;  &lt;code&gt;package com.gitee.swsk33.dbentity.dataobject;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;

import java.util.List;

/**
 * 商品表
 */
@Data
public class Product {

/**
 * 商品id
 */
@TableId(type = IdType.AUTO)
private Integer id;

/**
 * 商品名
 */
private String name;

/**
 * 所有购买了这个商品的订单（需组装）
 */
@TableField(exist = false)
private List&amp;lt;Order&amp;gt; orders;

}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;可见这里用了  &lt;code&gt;@TableField&lt;/code&gt;注解将  &lt;code&gt;orders&lt;/code&gt;字段标注为非数据库字段，因为这个字段是我们后续要手动组装的多对多字段。&lt;/p&gt;
 &lt;h4&gt;3. 订单类&lt;/h4&gt;
 &lt;pre&gt;  &lt;code&gt;package com.gitee.swsk33.dbentity.dataobject;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;

import java.util.List;

/**
 * 订单类
 */
@Data
@JsonIgnoreProperties(allowSetters = true, value = {&amp;quot;userId&amp;quot;})
@TableName(&amp;quot;order_info&amp;quot;)
public class Order {

/**
 * 订单id
 */
@TableId(type = IdType.AUTO)
private Integer id;

/**
 * 订单名
 */
private String name;

/**
 * 关联用户id（一对多冗余字段，不返回给前端，但是前端作为参数传递）
 */
private Integer userId;

/**
 * 关联用户（需组装）
 */
@TableField(exist = false)
private User user;

/**
 * 这个订单中所包含的商品（需组装）
 */
@TableField(exist = false)
private List&amp;lt;Product&amp;gt; products;

}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;可见这里使用了  &lt;code&gt;@JsonIgnoreProperties&lt;/code&gt;过滤掉了冗余字段  &lt;code&gt;userId&lt;/code&gt;不返回给前端。&lt;/p&gt;
 &lt;h3&gt;(4) 各个服务模块&lt;/h3&gt;
 &lt;p&gt;基本上每个服务模块仍然是Spring Boot的四层架构中的三层，  &lt;strong&gt;即   &lt;code&gt;dao&lt;/code&gt;、   &lt;code&gt;service&lt;/code&gt;和   &lt;code&gt;api&lt;/code&gt;&lt;/strong&gt;。因此这里只讲关键性的东西，其余细节可以在文末示例仓库中看代码。&lt;/p&gt;
 &lt;p&gt;来看订单模块，定义数据库操作层  &lt;code&gt;OrderDAO&lt;/code&gt;如下：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;package com.gitee.swsk33.dborder.dao;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gitee.swsk33.dbentity.dataobject.Order;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface OrderDAO extends BaseMapper&amp;lt;Order&amp;gt; {

/**
 * 添加关联记录
 *
 * @param orderId   订单id
 * @param productId 商品id
 * @return 增加记录条数
 */
@Insert(&amp;quot;insert into `order_product` values (#{orderId}, #{productId})&amp;quot;)
@DS(&amp;quot;bridge&amp;quot;)
int insertRecord(int orderId, int productId);

/**
 * 根据订单id查询其对应的所有商品id列表
 *
 * @param orderId 订单id
 * @return 商品id列表
 */
@Select(&amp;quot;select `product_id` from `order_product` where `order_id` = #{orderId}&amp;quot;)
@DS(&amp;quot;bridge&amp;quot;)
List&amp;lt;Integer&amp;gt; selectProductIds(int orderId);

/**
 * 删除和某个订单关联的商品id记录
 *
 * @param orderId 订单id
 * @return 删除记录数
 */
@Delete(&amp;quot;delete from `order_product` where `order_id` = #{orderId}&amp;quot;)
@DS(&amp;quot;bridge&amp;quot;)
int deleteProductIds(int orderId);

}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;由于继承了MyBatis-Plus的  &lt;code&gt;BaseMapper&lt;/code&gt;，因此基本的增删改查这里不需要写了，所以这里只需要写  &lt;strong&gt;对搭桥表数据库中的操作&lt;/strong&gt;，比如说获取这个订单中包含的商品  &lt;code&gt;id&lt;/code&gt;列表等等，也可见  &lt;strong&gt;这里只获取   &lt;code&gt;id&lt;/code&gt;或者是传入   &lt;code&gt;id&lt;/code&gt;为参数&lt;/strong&gt;对搭桥表进行增删查操作，并且这些方法标注了  &lt;code&gt;@DS&lt;/code&gt;切换数据源查询。&lt;/p&gt;
 &lt;p&gt;再来看  &lt;code&gt;Service&lt;/code&gt;层代码：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;package com.gitee.swsk33.dborder.service.impl;

import com.gitee.swsk33.dbentity.dataobject.Order;
import com.gitee.swsk33.dbentity.dataobject.Product;
import com.gitee.swsk33.dbentity.dataobject.User;
import com.gitee.swsk33.dbentity.model.Result;
import com.gitee.swsk33.dbfeign.feignclient.ProductClient;
import com.gitee.swsk33.dbfeign.feignclient.UserClient;
import com.gitee.swsk33.dborder.dao.OrderDAO;
import com.gitee.swsk33.dborder.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class OrderServiceImpl implements OrderService {

@Autowired
private OrderDAO orderDAO;

@Autowired
private UserClient userClient;

@Autowired
private ProductClient productClient;

@Override
public Result&amp;lt;Void&amp;gt; add(Order order) {
Result&amp;lt;Void&amp;gt; result = new Result&amp;lt;&amp;gt;();
// 先直接插入
if (orderDAO.insert(order) &amp;lt; 1) {
result.setResultFailed(&amp;quot;插入失败！&amp;quot;);
return result;
}
// 插入后，添加与之关联的商品多对多记录
for (Product each : order.getProducts()) {
orderDAO.insertRecord(order.getId(), each.getId());
}
result.setResultSuccess(&amp;quot;插入完成！&amp;quot;);
return result;
}

@Override
public Result&amp;lt;Void&amp;gt; delete(int id) {
Result&amp;lt;Void&amp;gt; result = new Result&amp;lt;&amp;gt;();
// 先直接删除
if (orderDAO.deleteById(id) &amp;lt; 1) {
result.setResultFailed(&amp;quot;删除失败！&amp;quot;);
return result;
}
// 然后删除关联部分
orderDAO.deleteProductIds(id);
result.setResultSuccess(&amp;quot;删除成功！&amp;quot;);
return result;
}

@Override
public Result&amp;lt;Order&amp;gt; getById(int id) {
Result&amp;lt;Order&amp;gt; result = new Result&amp;lt;&amp;gt;();
// 先查询订单
Order getOrder = orderDAO.selectById(id);
if (getOrder == null) {
result.setResultFailed(&amp;quot;查询失败！&amp;quot;);
return result;
}
// 远程调用用户模块，组装订单中的用户对象字段（一对多关联查询）
User getUser = userClient.getById(getOrder.getUserId()).getData();
if (getUser == null) {
result.setResultFailed(&amp;quot;查询失败！&amp;quot;);
return result;
}
getOrder.setUser(getUser);
// 远程调用商品模块，组装订单中关联的商品列表（多对多关联查询）
List&amp;lt;Integer&amp;gt; productIds = orderDAO.selectProductIds(id);
getOrder.setProducts(productClient.getByBatchId(productIds).getData());
result.setResultSuccess(&amp;quot;查询成功！&amp;quot;, getOrder);
return result;
}

@Override
public Result&amp;lt;List&amp;lt;Order&amp;gt;&amp;gt; getByBatchId(List&amp;lt;Integer&amp;gt; ids) {
Result&amp;lt;List&amp;lt;Order&amp;gt;&amp;gt; result = new Result&amp;lt;&amp;gt;();
// 先批量查询
List&amp;lt;Order&amp;gt; getOrders = orderDAO.selectBatchIds(ids);
// 组装其中的用户对象字段
for (Order each : getOrders) {
each.setUser(userClient.getById(each.getUserId()).getData());
}
// 由于批量查询目前专门提供给内部模块作为多对多关联查询时远程调用，因此这里不再对每个对象进行多对多查询，否则会陷入死循环
result.setResultSuccess(&amp;quot;查询完成！&amp;quot;, getOrders);
return result;
}

}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;可以先看代码和注释，这里面已经写好了增删查记录的时候的流程和操作，包括查询多对多搭桥表中的  &lt;code&gt;id&lt;/code&gt;以及远程调用等等，远程调用代码这里不再赘述。&lt;/p&gt;
 &lt;p&gt;然后，将这些服务暴露为接口即可，反过来商品服务模块也是基本一样的思路。&lt;/p&gt;
 &lt;p&gt;上述有以下需要注意的地方：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;将搭桥表的操作，即   &lt;strong&gt;增加、查询和删除这个订单包含的商品    &lt;code&gt;id&lt;/code&gt;的操作，定义在了    &lt;code&gt;OrderDAO&lt;/code&gt;中&lt;/strong&gt;，反过来在商品服务中，   &lt;code&gt;ProductDAO&lt;/code&gt;中也需要定义增加、查询和删除和这个商品所有关联的订单   &lt;code&gt;id&lt;/code&gt;的操作，具体可以看项目源码&lt;/li&gt;
  &lt;li&gt;在服务中编写了   &lt;code&gt;getByBatchId&lt;/code&gt;这个方法专用于模块远程调用查询多对多的对象，但是可见在这个方法中批量查询时，没有继续组装每个对象中包含的多对多对象，防止死循环&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;对于远程调用，需要注意的是远程调用层的代码和这些服务不在一个模块中，因此在模块主类上标注  &lt;code&gt;@EnableFeignClients&lt;/code&gt;注解启用远程调用功能时，还需要指定需要用到的远程调用的  &lt;code&gt;FeignClient&lt;/code&gt;类，否则会注入失败：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5a1a711df67148abb21df70f2f9e4f23~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&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/eab7262f09af48e1b3f8b8591049d327~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/710ba7cfc2e14ed18f8234c2522dcd14~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;4，总结&lt;/h2&gt;
 &lt;p&gt;事实上，解决分布式微服务的跨库增删改查操作，有很多的方式，这里只是提供一个思路，大家可以适当采纳，不能说这里的方案就是最优雅、性能最好的，还需要根据实际情况考虑。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://gitee.com/swsk33/examples-and-learning/tree/master/Spring%20Cloud%E8%B7%A8%E5%BA%93%E6%93%8D%E4%BD%9C%E7%A4%BA%E4%BE%8B"&gt;示例仓库地址&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.apifox.cn/apidoc/shared-937f1529-e569-4699-a50d-c24bbba49160"&gt;Apifox测试配置&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 />
      <guid isPermaLink="true">https://itindex.net/detail/62592-%E5%88%86%E5%B8%83-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E7%B3%BB%E7%BB%9F</guid>
      <pubDate>Mon, 16 Jan 2023 18:34:56 CST</pubDate>
    </item>
    <item>
      <title>浅谈服务接口的高可用设计</title>
      <link>https://itindex.net/detail/62589-%E6%9C%8D%E5%8A%A1-%E6%8E%A5%E5%8F%A3-%E8%AE%BE%E8%AE%A1</link>
      <description>&lt;h5&gt;作者：京东零售 王磊&lt;/h5&gt;
 &lt;h3&gt;前言&lt;/h3&gt;
 &lt;blockquote&gt;
  &lt;p&gt;作为一个后端研发人员，开发服务接口是我正常不过的工作了，这些接口不管是面向前端HTTP或者是供其他服务RPC远程调用的，都绕不开一个共同的话题就是“高可用”，接口开发往往看似简单，但保证高可用这块实现起来却不并没有想想的那么容易，接下来我们就看一下，一个高可用的接口是该考虑哪些内容，同时文中有不足的欢迎批评指正。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h3&gt;到底啥是高可用&lt;/h3&gt;
 &lt;pre&gt;  &lt;code&gt;用一句简单的话来概就是我们的系统具不具备应对和规避风险的能力。
&lt;/code&gt;&lt;/pre&gt;
 &lt;h3&gt;为啥做高可用&lt;/h3&gt;
 &lt;pre&gt;  &lt;code&gt;1. 程序都是有人开发的，在开发过程中会犯错从而导致线上事故的发生
2. 系统运行依赖各种运行环境：CPU、内存、硬盘、网络等等，而这些都有可能损坏
3. 业务拉新用户正在注册账号，结果注册接口挂了用户体验受影响
4. 双十一、618等大促大量用户下单，结果下单服务接口挂了GMV受影响等等
5. 其他未知因素等等
总之为了应对这些不可控因素的发生，我们必须要做高可用
&lt;/code&gt;&lt;/pre&gt;
 &lt;h3&gt;高可用的关键点&lt;/h3&gt;
 &lt;blockquote&gt;
  &lt;p&gt;我们说过高可用的本质是系统是否具备应对和规避风险的能力，那么从这个角度出发来设计高可用接口的有以下几个关键因素：Dependence（依赖）、Probability（概率）、Time（时长）、Scope（范围）&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;pre&gt;  &lt;code&gt;1. 依赖的资源相对少
2. 风险的概率足够低
3. 影响的范围足够小
4. 影响时长足够短
&lt;/code&gt;&lt;/pre&gt;
 &lt;h3&gt;接口高可用设计的几个原则&lt;/h3&gt;
 &lt;blockquote&gt;
  &lt;p&gt;结合这些关键点，我们来看一下具体具体注意事项&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h4&gt;1、控制依赖&lt;/h4&gt;
 &lt;blockquote&gt;
  &lt;p&gt;能少依赖就少依赖，能不强依赖就不强依赖&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;pre&gt;  &lt;code&gt;少依赖
例如：日常每分钟10个请求，查询Mysql数据即可满足，此时盲目引入Redis中间件，不仅浪费资源而且增加系统复杂性

弱依赖
例如：用户注册服务强依赖新用户优惠券发放服务，当优惠券发放服务故障后，整个注册不可用，好的方式是采用弱依赖，使用异步化的
方式，这样优惠券发送服务不可用时，不会影响注册链路。
&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;2、避免单点&lt;/h4&gt;
 &lt;blockquote&gt;
  &lt;p&gt;避免单点故障的核心是通过备份或者冗余快速的进行容错&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;pre&gt;  &lt;code&gt;1. 我们采用多机房多实力部署我们应用来保障故障风险分摊，一旦有一台服务器出现问题，其他服务仍然能够继续支撑我们的服务
2. 每次上线我们都会保留上一次上线发布版本，这样一旦上线的程序出现问题我们能够快速回滚到上一版本
3. 每个接口至少保障2人知道相关业务，一旦线上服务出现问题，其中任何一人一个能够快速处理相关线上问题
4. 不管是Mysql还是Redis等中间件都支持数据主备机群部署

类似的例子很多这里就不再一一列举了
&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;3、负载均衡&lt;/h4&gt;
 &lt;blockquote&gt;
  &lt;p&gt;将风险进行分摊避免分险扩散&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;pre&gt;  &lt;code&gt;例如：无论是Ngnix或者JSF的，其负载均衡目的就是尽量的将流量分散到不同的服务器节点上，这样可以有效的保障单节点因系统瓶颈
问题而引发一系列的风险。 

像上面这个例子我想每个研发人员都知道也都会这么做，但是是不是所有的场景我们都考虑到均衡这个问题？

例如：通常为了提高读并发的能力，我们会把数据缓存到JIMDB中，但是因为缓存的key出现了热点数据导致JIMDB单分片负载过高，恰
好，这个分片上也缓存了其他数据，但是因为CPU负载过高，导致查询性能变差，大量的超时，影响了业务。所以，我们在接口设计
的时候，假如遇到类似场景，也要充分考虑数据存储的均衡性，同时针对热点数据做好监控，随时支持动态均衡。
&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;4、资源隔离&lt;/h4&gt;
 &lt;blockquote&gt;
  &lt;p&gt;隔离的目的将风险控制在可控范围内，避免风险扩散&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;pre&gt;  &lt;code&gt;例如：接口部署之间服务部署物理上是相互隔离的，避免单机房或者单服务器出现故障影响整个服务

例如：我们在存储业务数据的时候会将数据分库分表，数据通过不同分片存储，这样就不会导致某个服务器挂掉影响到整个服务
&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;5、接口限流&lt;/h4&gt;
 &lt;blockquote&gt;
  &lt;p&gt;限流是一种保护措施，目的是将风险控制在可控范围内&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;pre&gt;  &lt;code&gt;我们在开发接口的时候，一定要结合业务流量情况进行限流措施，限流一方面处于对自身服务资源的保护，同时也是对依赖资源的一种
保护措施。

目前集团JSF在流量控制这块已经有了对应的限流处理能力，同时我们也可以结合实际业务进行限流模块的开发。
&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;6、服务熔断&lt;/h4&gt;
 &lt;blockquote&gt;
  &lt;p&gt;熔断也是一种保护措施，目的是将风险控制在可控范围内，避免风险扩散&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;pre&gt;  &lt;code&gt;例如：经常我们服务A会同时调用B、C、D多个服务，当我们依赖的服务其中一个出现故障或者性能下降的时候，就是导致整体服务
可用率下降，所以我们在开发此类服务的时候，一定要注意接口之间的隔离。我们可以利用类似Hystrix组件实现，也可以借助DUCC
进行手动隔离。

其实熔断也是一种控制资源依赖的一种，将强依赖降级为弱依赖
&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;7、异步处理&lt;/h4&gt;
 &lt;blockquote&gt;
  &lt;p&gt;将同步操作转为异步操作&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;pre&gt;  &lt;code&gt;例如：用户页面领取一些权益，针对领取这个服务在大促期间因为用户流量较大，为了避免系统负载，此时采用MQ异步接收用户领取
请求然后进行优惠券发放,这样不仅极大的减少了事故的影响范围，也减少问题发生概率。
&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;8、降级方案&lt;/h4&gt;
 &lt;blockquote&gt;
  &lt;p&gt;服务降级属于一种问题发生后的补救措施，通过服务降级可以减少一部分风险影响范围&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;pre&gt;  &lt;code&gt;对于重要的服务接口我们都要具备完善的降级方案，这里需要说明的是，降级有损的，我们一定要在系统开发前就要考虑各种问题
发生的可能，降级的前提是通过降级非核心业务保证核心业务运行。

例如：大促峰值期间，一般会提前降级掉很多功能，同时限流，主要是为了保护峰值绝大部分人的交易支付体验。
&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;9、灰度发布&lt;/h4&gt;
 &lt;blockquote&gt;
  &lt;p&gt;通过灰度发布降低风险影响范围&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;pre&gt;  &lt;code&gt;例如：我们上线一个新服务，通过一定的灰度策略，让用户先行体验新版本的应用，通过收集这部分用户对新版本应用的反馈以及
对新版本功能、性能、稳定性等指标进行评论，进而决定继续放大新版本投放范围直至全量升级或回滚至老版本。根据线上反馈结果，
做到查漏补缺，发现重大问题，可回滚“旧版本”
&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;10、混沌工程&lt;/h4&gt;
 &lt;blockquote&gt;
  &lt;p&gt;通过提前对系统进行一些破坏性的手段，提前发现潜在问题&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;pre&gt;  &lt;code&gt;例如：一个复杂接口系统依赖了太多的服务和组件，这些组件随时随地都可能会发生故障，而一旦它们发生故障，会不会如蝴蝶效应
一般造成整体服务不可用呢，我们并不知道，因此我们可以借助泰山平台混沌工程进行演练，针对发生的场景制定各种预案，将风险
控制在可控范围内。
&lt;/code&gt;&lt;/pre&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/62589-%E6%9C%8D%E5%8A%A1-%E6%8E%A5%E5%8F%A3-%E8%AE%BE%E8%AE%A1</guid>
      <pubDate>Fri, 13 Jan 2023 09:35:58 CST</pubDate>
    </item>
    <item>
      <title>盘点微服务架构下的诸多身份验证方式</title>
      <link>https://itindex.net/detail/62587-%E7%9B%98%E7%82%B9-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84</link>
      <description>&lt;blockquote&gt;
  &lt;p&gt;联合作者：罗泽轩，API7.ai 技术专家、Apache APISIX PMC 成员&lt;/p&gt;
  &lt;p&gt;联合作者：赵士瑞，API7.ai 技术工程师，Apache APISIX Committer&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;p&gt;本文将从传统服务架构和微服务架构下的身份认证方式区别进行讨论，并最终衡量微服务架构中身份认证服务的各种实现方式的优劣。&lt;/p&gt;
 &lt;h2&gt;传统服务架构中的身份认证服务&lt;/h2&gt;
 &lt;p&gt;在企业开发服务的早期，所有功能都是做到同一个应用程序里面的。我们把这种模式称之为 “单体”，以跟当下更为主流的 “微服务” 架构区分开来。&lt;/p&gt;
 &lt;p&gt;单体应用由单个不可分割的单元组成。它通常由各个业务线各自开发，但是部署时放入到同一个环境中。所有这些都紧密集成以在一个单元中提供所有功能。这一单元里拥有所需的所有资源。单体应用的好处在于部署迭代简单，适合业务线较少且比较独立的公司采用。&lt;/p&gt;
 &lt;p&gt;随着企业开发出来的业务越来越复杂，我们会发现单体服务已经无法满足现实生活里面快速迭代的需要了。我们需要把这个单体的巨无霸拆分一下，同时保证现有的各个功能间的调用能正常进行。这时候，ESB（企业服务总线）便应运而生了。&lt;/p&gt;
 &lt;p&gt;所谓的 “企业服务总线”，就是一根连接各个企业服务的管道。ESB 的存在是为了集成基于不同协议的不同服务，ESB 做了消息的转化、解释以及路由的工作，以此来让不同的服务互联互通。从名称就能知道，它的概念借鉴了计算机组成原理中的通信模型 —— 总线，所有需要和外部系统通信的系统，统统接入 ESB，就可以利用现有的系统构建一个全新的松耦合的异构的分布式系统。&lt;/p&gt;
 &lt;p&gt;ESB 做了消息的转换解释与路由等工作，让不同的服务互联互通。传统的 ESB 的服务调用方式是，每一次服务的调用者要向服务提供者进行服务交互请求时都必须通过中心的 ESB 来进行路由。&lt;/p&gt;
 &lt;p&gt;接下来将按照这两种情况，分别描述对应的身份认证功能的实现。&lt;/p&gt;
 &lt;h3&gt;单体架构&lt;/h3&gt;
 &lt;p&gt;单体架构下，用户身份验证和会话管理相对简单。身份认证和授权发生在同一个应用程序中，通常使用基于 session 的认证方案。一旦通过身份验证，就会创建一个会话并将其存储在服务器上，任何需要它的组件都可以访问它并用于通知和授权后续要求。会话 ID 被发送到客户端并用于应用程序的所有后续请求，以将请求与当前会话相关联。&lt;/p&gt;
 &lt;h3&gt;ESB 架构&lt;/h3&gt;
 &lt;p&gt;在 ESB 架构下，所有用户与服务之间，服务与服务之间全部通过 ESB 总线进行处理。由于 ESB 的架构是从单体拆分下来的，身份认证方式相对于单体架构并没有变化。&lt;/p&gt;
 &lt;h2&gt;微服务架构中的身份认证服务&lt;/h2&gt;
 &lt;p&gt;从单体架构迁移到微服务架构有很多优势，但微服务架构作为一种分布式架构，会存在更大的攻击面，共享用户上下文更加困难。因此微服务架构下需要有跟传统架构不一样的身份认证服务，才能响应更大的安全性挑战。&lt;/p&gt;
 &lt;p&gt;目前，我们可以把微服务架构下的身份认证服务分为以下三类：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;通过每个微服务实现身份认证；&lt;/li&gt;
  &lt;li&gt;通过身份认证服务实现身份认证；&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;img alt="&amp;#27599;&amp;#20010;&amp;#24494;&amp;#26381;&amp;#21153;&amp;#33258;&amp;#24049;&amp;#23454;&amp;#29616;&amp;#36523;&amp;#20221;&amp;#35748;&amp;#35777;" src="https://static.apiseven.com/uploads/2023/01/10/DiCEnbrz_1713208657.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;每个微服务都需要实现自己独立的安全性保障，并在每个入口点上强制执行。此方法使微服务团队能够自主决定如何实现其安全解决方案。但是，这种方法有几个缺点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;安全逻辑需要在每个微服务中重复实现，这会导致服务之间的代码重复。&lt;/li&gt;
  &lt;li&gt;它分散了开发团队的注意力，使其无法专注于其主要服务。&lt;/li&gt;
  &lt;li&gt;每个微服务都依赖于它不拥有的用户身份验证数据。&lt;/li&gt;
  &lt;li&gt;很难维护和监控。
完善这个解决方案的选择之一就是使用一个加载在每个微服务上的共享认证库。这个操作可以防止代码重复，开发团队将只关注他们的业务领域，但仍然存在这种改进无法解决的缺点。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;因为共享的认证库仍然需要有对应的用户身份数据，而且还需要保证各个微服务使用同样版本的认证库。老实说，共享认证库更像是服务拆分不透彻的结果。&lt;/p&gt;
 &lt;p&gt;因此这种方式总结来说，优势在于实施速度快，独立性强；而劣势也比较明显，服务之间的代码重复、违反单一职责原则，较难维护。&lt;/p&gt;
 &lt;h3&gt;通过身份认证服务实现身份认证&lt;/h3&gt;
 &lt;p&gt;既然每个微服务自己实现身份认证难以维护，而使用共享认证库又违背了微服务拆分的本意，那么能不能把共享认证库升级成专门的身份认证服务呢？&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#36523;&amp;#20221;&amp;#35748;&amp;#35777;&amp;#26381;&amp;#21153;" src="https://static.apiseven.com/uploads/2023/01/10/crpx8OA6_3258593430.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在这种情况下，所有访问都通过同一服务进行控制，类似于单体应用里面的身份认证功能。每个业务服务都必须在执行操作时，向访问控制模块发送单独的授权请求。&lt;/p&gt;
 &lt;p&gt;但是，这种方法在一定程度上减慢了服务的运行速度，并增加了服务之间的互连量。并且各个微服务会依赖这个“单点”的身份认证服务。万一统一的身份认证服务出问题，会造成链式反应，带来二次伤害。&lt;/p&gt;
 &lt;p&gt;所以总结来看，这种方式虽然确保了每个微服务职责单一，使得身份认证功能更加集中。但是仍会造成单点依赖，进而增加请求延迟。&lt;/p&gt;
 &lt;h3&gt;通过网关实现身份认证&lt;/h3&gt;
 &lt;p&gt;迁移到微服务体系结构时，需要回答的问题之一是微服务之间如何通信。前面提到的 ESB 是种方案，但是更常见的选择则是采用 API 网关。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#32593;&amp;#20851;&amp;#23454;&amp;#29616;&amp;#36523;&amp;#20221;&amp;#35748;&amp;#35777;" src="https://static.apiseven.com/uploads/2023/01/10/crpx8OA6_3258593430.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;API 网关是所有请求的单个终端节点入口，它通过充当使用这些微服务的中央接口来提供灵活性。某个需要访问其他微服务的微服务（以下称之为“客户端”，以跟被它访问的微服务相区分）无权访问多个服务，而是需要向负责将其路由到上游服务的 API 网关发送请求。&lt;/p&gt;
 &lt;p&gt;由于 API 网关位于客户端访问的必经之路上，因此它是强制实施身份验证问题的绝佳选择。使用 API 网关可以减少延迟（调用身份验证服务），并确保身份验证过程在整个应用程序中保持一致。&lt;/p&gt;
 &lt;p&gt;举个例子，通过 APISIX 的   &lt;a href="https://apisix.apache.org/docs/apisix/plugins/jwt-auth/"&gt;jwt-auth&lt;/a&gt; 插件，我们可以在网关上实现身份认证。&lt;/p&gt;
 &lt;p&gt;首先，我们需要规划若干个用户身份信息（名称、密钥等等），并将其配置到 APISIX 上。 其次，根据给定的用户密钥，向 APISIX 发起签名请求，得到这个用户的 JWT token。 接着，当客户端需要访问某个上游服务时，带上 JWT token，由 APISIX 作为 API 网关代理该访问。 最后，APISIX 会通过 JWT token，完成身份认证的操作。&lt;/p&gt;
 &lt;p&gt;当然，凡事有利就有弊，没有完全无劣势的技术选型。使用网关来完成身份认证，还是带来了少许单点问题。比起在每个微服务内完成身份认证，在网关上解决该问题，安全性相比会降低些。比如 API 网关被攻破之后，就可以访问该网关背后的任何微服务。但是风险是相对的，比起统一的身份认证服务，使用 API 网关的单点问题并没有那么严重。&lt;/p&gt;
 &lt;p&gt;因此这种方式操作起来，在优势上较为明显，比如可以有效保护后端微服务，微服务不用处理任何认证逻辑等。但同时还是会存有少许的单点依赖。&lt;/p&gt;
 &lt;h2&gt;总结&lt;/h2&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/62587-%E7%9B%98%E7%82%B9-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84</guid>
      <pubDate>Tue, 10 Jan 2023 16:59:54 CST</pubDate>
    </item>
    <item>
      <title>服务端性能优化--最大QPS推算及验证 - huangyingsheng - 博客园</title>
      <link>https://itindex.net/detail/62539-%E6%9C%8D%E5%8A%A1-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-qps</link>
      <description>&lt;div&gt;    &lt;h1&gt;服务端性能优化--最大QPS推算及验证&lt;/h1&gt;    &lt;p&gt;影响QPS（即吞吐量）的因素有哪些？每个开发都有自己看法，一直以为众说纷纭，例如：&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;QPS受编程语言的影响。（PHP是最好的语言？）&lt;/li&gt;      &lt;li&gt;QPS主要受编程模型的影响，比如不是coroutine、是不是NIO、有没有阻塞？&lt;/li&gt;      &lt;li&gt;QPS主要由业务逻辑决定，业务逻辑越复杂，QPS越低。&lt;/li&gt;      &lt;li&gt;QPS受数据结构和算法的影响。&lt;/li&gt;      &lt;li&gt;QPS受线程数的影响。&lt;/li&gt;      &lt;li&gt;QPS受系统瓶颈的影响。&lt;/li&gt;      &lt;li&gt;QPS和RT关系非常紧密。&lt;/li&gt;      &lt;li&gt;more...&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;嗯，这些说法好像都对，但是好像又有点不对，好像总是不太完整，有没有一个系统点的说法能让人感觉一听就豁然开朗？      &lt;br /&gt;今天我们就这个话题来阐述一下，将一些现有的理论作为依据，把上方这些看起来比较零碎的看法总结归纳起来，希望能为服务端的性能提升进行一点优化，这也是一个优化的起点，未来才有可能做更多的优化，例如TCP、DNS、CDN、系统监控、多级缓存、多机房部署等等优化的手段。&lt;/p&gt;    &lt;img height="100" src="https://summer-blog-images.oss-cn-shanghai.aliyuncs.com/generic/v2-bc309f8a7eb0de9dd0c7277be07e9a46_1440w.jpg" width="100"&gt;&lt;/img&gt;    &lt;p&gt;      &lt;strong&gt;好了，废话不多说，直接开聊。&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;我们经常再做优化的时候，例如电商的促销秒杀等活动页，一开始可能会认为说Gzip并不是影响CPU的最大因子，直到拿出一次又一次的实验数据，研发们才开始慢慢接受（尬不尬），这是为什么？难道说Gzip真的是影响CPU的最大因子吗？那我们就拿出一点数据来验证一下对吧，接下来我们从RT着手开始慢慢了解，看到文章结尾就知道为什么Gzip和CPU的关系，同事也会发现，性能优化的相关知识其实也是体系化的，并不是分散零碎的。&lt;/p&gt;    &lt;h3&gt;RT&lt;/h3&gt;    &lt;p&gt;什么是 RT ？是概念还是名词还是理论？      &lt;br /&gt;      &lt;br /&gt;RT其实也没那么玄乎，就是 Response Time （就是响应时间嘛，哈哈哈），只不过看你目前在什么场景下，也许你是c端（app、pc等）的用户，响应时间是你请求服务器到服务器响应你的时间间隔，对于我们后端优化来说，就是接受到请求到响应用户的时间间隔。这听起来怎么感觉这不是在说废话吗？这说的不都是服务端的处理时间吗？不同在哪里？其实这里有个容易被忽略的因素，叫做网络开销。      &lt;br /&gt;所以服务端RT ≈ 网络开销 + 客户端RT。也就是说，一个差的网络环境会导致两个RT差距的悬殊（比如，从深圳访问上海的请求RT，远大于上海本地内的请求RT）&lt;/p&gt;    &lt;p&gt;客户端的RT则会直接影响客户体验，要降低客户端RT，提升用户的体验，必须考虑两点，第一点是服务端的RT，第二点是网络。对于网络来说常见的有CDN、AND、专线等等，分别适用于不同的场景，有机会写个blog聊一下这个话题。&lt;/p&gt;    &lt;p&gt;对于服务端RT来说，主要看服务端的做法。      &lt;br /&gt;有个公式：RT = Thread CPU Time + Thread Wait Time      &lt;br /&gt;从公式中可以看出，要想降低RT，就要降低 Thread CPU Time 或者 Thread Wait Time。这也是马上要重点深挖的一个知识点。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;Thread CPU Time（简称CPU Time）        &lt;br /&gt;        &lt;br /&gt;Thread Wait Time（简称Wait Time）&lt;/strong&gt;&lt;/p&gt;    &lt;h3&gt;单线程QPS&lt;/h3&gt;    &lt;p&gt;我们都知道 RT 是由两部分组成 CPU Time + Wait Time 。那如果系统里只有一个线程或者一个进程并且进程中只有一个线程的时候，那么最大的 QPS 是多少呢？      &lt;br /&gt;假设 RT 是 199ms （CPU Time 为 19ms ，Wait Time 是 180ms ），那么 1000s以内系统可以接收的最大请求就是      &lt;br /&gt;1000ms/(19ms+180ms)≈5.025。&lt;/p&gt;    &lt;p&gt;所以得出单线程的QPS公式：&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;div&gt;\[单线程QPS = 1000ms/RT
\]&lt;/div&gt;    &lt;p&gt;&lt;/p&gt;    &lt;h3&gt;最佳线程数&lt;/h3&gt;    &lt;p&gt;还是上面的那个话题 （CPU Time 为 19ms ，Wait Time 是 180ms ），假设CPU的核数1。假设只有一个线程，这个线程在执行某个请求的时候，CPU真正花在该线程上的时间就是CPU Time，可以看做19ms，那么在整个RT的生命周期中，还有 180ms 的 Wait Time，CPU在做什么呢？抛开系统层面的问题（这里不考虑什么时间片轮循、上下文切换等等），可以认为CPU在这180ms里没做什么，至少对于当前的业务来说，确实没做什么。&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;一核的情况        &lt;br /&gt;由于每个请求的接收，CPU只需要工作19ms，所以在180ms的时间内，可以认为系统还可以额外接收180ms/19ms≈9个的请求。由于在同步模型中，一个请求需要一个线程来处理，因此，我们需要额外的9个线程来处理这些请求。这样，总的线程数就是：&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;&lt;/p&gt;    &lt;div&gt;\[（180ms + 19ms）/19ms ≈ 10个
\]&lt;/div&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;        多线程之后，CPU Time从19ms变成了20ms，这1ms的差值代表多线程之后上下文切换、GC带来的额外开销（对于我们java来说是jvm，其他语言另外计算），这里的1ms只是代表一个概述，你也可以把它看做n。&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;p&gt;两核的情况          &lt;br /&gt;一核的情况下可以有10个线程，那么两核呢？在理想的情况下，可以认为最佳线程数为：2 x ( 180ms + 20ms )/20ms = 20个&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;CPU利用率          &lt;br /&gt;我们之前说的都是CPU满载下的情况，有时候由于某个瓶颈，导致CPU不得不有效利用，比如两核的CPU，因为某个资源，只能各自使用一半的能效，这样总的CPU利用率就变成了50%，再这样的情况下，最佳线程数应该是：50% x 2 x( 180ms + 20ms )/20ms = 10个          &lt;br /&gt;这个等式转换成公式就是：最佳线程数 = (RT/CPU Time) x CPU 核数 x CPU利用率          &lt;br /&gt;当然，这不是随便推测的，在收集到的很多的一些著作或者论坛的文档里都有这样的一些实验去论述这个公式或者这个说法是正确的。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;    &lt;h3&gt;最大QPS&lt;/h3&gt;    &lt;h4&gt;1.最大QPS公式推导&lt;/h4&gt;    &lt;p&gt;假设我们知道了最佳线程数，同时我们还知道每个线程的QPS，那么线程数乘以每个线程的QPS既这台机器在最佳线程数下的QPS。所以我们可以得到下图的推算。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="image" src="https://summer-blog-images.oss-cn-shanghai.aliyuncs.com/qps_optimization/image_001.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;我们可以把分子和分母去约数，如下图。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="image" src="https://summer-blog-images.oss-cn-shanghai.aliyuncs.com/qps_optimization/image_002.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;于是简化后的公式如下图.&lt;/p&gt;    &lt;p&gt;      &lt;img alt="image" src="https://summer-blog-images.oss-cn-shanghai.aliyuncs.com/qps_optimization/image_003.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;从公式可以看出，决定QPS的时CPU Time、CPU核数和CPU利用率。CPU核数是由硬件做决定的，很难操纵，但是CPU Time和CPU利用率与我们的代码息息相关。&lt;/p&gt;    &lt;p&gt;虽然宏观上是正确的，但是推算的过程中还是有一点小小的不完美，因为多线程下的CPU Time（比如高并发下的GC次数增加消耗更多的CPU Time、线程上下文切换等等）和单线程的CPU Time是不一样的，所以会导致推算出来的结果有误差。&lt;/p&gt;    &lt;p&gt;尤其是在同步模型下的相同业务逻辑中，单线程时的CPU Time肯定会比大量多线程的CPU Time小，但是对于异步模型来说，切换的开销会变得小很多，为什么？这里先卖个葫芦吧，看完本篇就知道了。&lt;/p&gt;    &lt;p&gt;既然决定QPS的是CPU Time和CPU核数，那么这两个因子又是由谁来决定的呢？（越看越懵哈）&lt;/p&gt;    &lt;h4&gt;2.CPU Time&lt;/h4&gt;    &lt;p&gt;终于讲到了 CPU Time，CPU Time不只是业务逻辑所消耗的CPU时间，而是一次请求中所有环节上消耗的CPU时间之和。比如在web应用中，一个请求过来的HTTP的解析所消耗的CPU时间，是CPU Time的一部分。另外，这个请求中请求RPC的encode和decode所消耗的CPU时间也是CPU Time的一部分。&lt;/p&gt;    &lt;p&gt;那么CPU Time是由哪些因素决定的呢？两个关键字：数据结构+算法。      &lt;br /&gt;举一些例子吧&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;均摊问题&lt;/li&gt;      &lt;li&gt;hash问题&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;h4&gt;3.CPU利用率&lt;/h4&gt;    &lt;p&gt;CPU利用率不高的情况时常发生，一下因素都会影响CPU的利用率，从而影响系统可以支持的最大QPS。&lt;/p&gt;    &lt;h5&gt;1) IO能力&lt;/h5&gt;    &lt;ul&gt;      &lt;li&gt;磁盘IO&lt;/li&gt;      &lt;li&gt;网络IO        &lt;br /&gt;·带宽，比如某大促压力测试时，由于某个应用放在Tair中的数据量大，导致Tair的机器网卡跑满。        &lt;br /&gt;·网路链路，还是这次大促，借用了其他核心交换机下的机器，导致客户端RT明显增加。&lt;/li&gt;&lt;/ul&gt;    &lt;h5&gt;2) 数据库连接池（并发能力=PoolWaitTime/RT(Client) x PoolSize）。&lt;/h5&gt;    &lt;h5&gt;3) 内存不足，GC大量占用CPU，导致给业务逻辑使用的CPU利用率下降，而且GC时还满足Amdahl定律锁定义的场景。&lt;/h5&gt;    &lt;h5&gt;4) 共享资源的竞争，比如各种锁策略(读写锁、锁分离等)，各种阻塞队列，等等。&lt;/h5&gt;    &lt;h5&gt;5) 所依赖的其他后端服务QPS低造成的瓶颈。&lt;/h5&gt;    &lt;h5&gt;6) 线程数或者进程数，甚至编程模型(同步模型，异步模型)。&lt;/h5&gt;    &lt;p&gt;在压力测试过程中，出现最多的就是网络IO层面的问题，GC大量占用CPU Time之类的问题也经常出现。&lt;/p&gt;    &lt;h4&gt;4.CPU核数，Amdahl定律，Gustafson定律&lt;/h4&gt;    &lt;h5&gt;1)Amdahl定律(安达尔定律，不是达尔文定律！！！)&lt;/h5&gt;    &lt;p&gt;Amdahl定律是用来描述可伸缩性的，什么是可伸缩性？说白了就是比如增加计算机资源，如CPU、内存、宽带等，QPS能够相应的进行改进。&lt;/p&gt;    &lt;p&gt;既然Amdahl定律是描述可伸缩性的，那它是如何描述的呢？&lt;/p&gt;    &lt;p&gt;Amdahl在自己的论文中指出，可伸缩性是指在一个系统中，基于可并行化和串行化的组件各自所占的比例，当程序获得额外的计算资源(如CPU或者内存等)时，系统理论上能够获得的加速值(QPS或者其他指标可以翻几倍)。用一个公式来表达，如果F表示必须串行化执行的比例，那么在一个N核处理器的机器中，加速：&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;div&gt;\[Speedup \leq \frac{1}{F+\frac{1-F}{N}}
\]&lt;/div&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;这个公式代表的意义是比较广泛的，在项目管理中也有一句类似的话：&lt;/p&gt;    &lt;p&gt;一个女人生一个孩子要9个月，但是永远不可能让9个女人在一个月内就生一个孩子。      &lt;br /&gt;我们根据这个例子套一个公式先，这里设F=100%，9个女人表示N=9，于是就有1/(100%+(1-100%)/9)=1，所以9个女人的加速比为1，等于没有加速。&lt;/p&gt;    &lt;p&gt;到这里，其实这个公式还只是描述了在增加资源的情况下系统的加速比，而不是在资源不变的情况下优化数据结构和算法之后带来的提升。优化数据结构和算法带来的提升要看前文中最大的QPS公式。不过这两个公式也不是完全没有联系的，在增加资源的情况下，它们的联系还是比较紧密的。&lt;/p&gt;    &lt;h5&gt;2) Gustafson定律（古斯塔夫森定律）&lt;/h5&gt;    &lt;p&gt;这个定律名字有点长，但这不是关键，关键的是，它是Amdahl定律的补充，公式为：&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;div&gt;\[S(P) = P-α·(P-1)
\]&lt;/div&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;P是处理器的个数，α是串行时间占总执行时间的比例。      &lt;br /&gt;生孩子的案例再次套上这个公式，P为女人的个数，等于9，串行比例是100%。Speedup=9-100%x(9-1)=1，也就是9个女人是无法在一个月内把孩子生出来的……&lt;/p&gt;    &lt;p&gt;之所以说是Amdahl定律的补充，是因为两个定律的关系是相辅相成的关系。前者从串行和并行执行时间的角度来推导，后者从串行和并行的计算量角度来推导，不管是哪个角度，最终的结果其实是一样的。&lt;/p&gt;    &lt;h5&gt;3)CPU核数和Amdahl定律的关系&lt;/h5&gt;    &lt;p&gt;通过最大QPS公式，我们发现，在CPU Time和CPU利用率不变的情况下，核数越多，QPS就越大。比如核数从1到4，在CPU Time和CPU利用率不变的情况下，加速比应该是4，所以QPS应该也是增加4倍。&lt;/p&gt;    &lt;p&gt;这是资源增加（CPU核数增加）的情况下的加速比，也可以通过Amdahl定律来衡量，考虑串行和并行的比例在增加资源的情况下是否会改变。也就是要考虑在N增加的情况下，F受哪些因素的影响：&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;div&gt;\[Speedup \leq \frac{1}{F+\frac{1-F}{N}}
\]&lt;/div&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;只要F大于0，最大QPS就不会翻4倍。      &lt;br /&gt;      &lt;br /&gt;一个公式说要增加4被倍，一个定理说 没有4倍，互相矛盾？      &lt;br /&gt;      &lt;br /&gt;其实事情是这样的，通过最大QPS公式，我们可以发现，如果CPU Time和CPU 利用率不变，核数从1增加到4，QPS会相应的增加4倍。但是在实际情况下，当核数增加时，CPU Time和CPU 利用率大部分时候是变化的，所以前面的假设不成立，即一般场景下QPS不能增加4倍。&lt;/p&gt;    &lt;p&gt;而Amdahl定律中的N变化时，F也可能会变化，即一般场景下，最大QPS并不能增加4被，所以这其实并不矛盾。相反它们是相辅相成的。这里一定要注意，这里说的是一般场景，如果你的场景完全没有串行（程序没有串行，系统没有串行，上下文切换没有串行，什么串行都没有），那么理论上是可以增加4倍的。&lt;/p&gt;    &lt;p&gt;为什么增加计算机资源时，最大QPS公式中的CPU Time和CPU利用率会变化，F也会变化呢？我们可以从宏观上分析一下，增加计算机资源时，达到满载:&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;p&gt;QPS会更高，单位时间内产生的对象会更多。在同等条件下，minor GC被触发的次数增加，还有些场景发生过对象多到响应没返回它们就进了“老年代”，从而fullGC被触发。宏观上，这是属于串行的部分，对于Amdahl公式来说F会受到影响，对于最大QPS公式来说，CPU Time和CPU利用率也受到影响.&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;在同步模型下大量的线程在完成一次请求中，上下文被切换的次数大大增加。&lt;/p&gt;&lt;/li&gt;      &lt;li&gt;&lt;/li&gt;      &lt;li&gt;        &lt;p&gt;尤其是在有 串行模块的时候，串行的执行和等待时间增加，F会变化，某些场景下CPU          &lt;br /&gt;利用率也达不到理想效果，这取决于你的代码。这也是要做锁分离、为什么要缩小同步          &lt;br /&gt;块的原因。当然还有锁自身的优化，比如偏向、自旋、读写分离等技术，都是为了不断          &lt;br /&gt;地减少Amdahl定律中的F，也是为了减少CPU Time ( 锁本身的优化)，提高CPU利          &lt;br /&gt;用率(使用锁的方法的优化)。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;。锁本身的优化最为津津乐道的是自旋、偏向、自适应，synchronized分析，还有reetrantLock的代码及AQS等等。&lt;/p&gt;    &lt;p&gt;。使用锁的优化方法最常见的是缩小锁区间、锁分离、无锁化、volatile。&lt;/p&gt;    &lt;p&gt;所以在增加计算资源时，更高的并发产生，会引起最大QPS公式中两个参数的变化，也会      &lt;br /&gt;引起Amdahl定律中F值的变化，同时公式和定律变化的趋势是相同的。Amdahl定律是得到广      &lt;br /&gt;泛认可的，也是得到数据验证的。最大QPS公式好像没有人验证过，这里引用一个比较有名的      &lt;br /&gt;测试结果，如下图.&lt;/p&gt;    &lt;p&gt;      &lt;img alt="image" src="https://summer-blog-images.oss-cn-shanghai.aliyuncs.com/qps_optimization/image_005.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;当计算资源(处理器数量)增加时，在串行部分比例不变的情况下，CPU利用率下降。&lt;/li&gt;      &lt;li&gt;当计算资源(处理器数量)增加时，串行占的比例越大，CPU利用率下降得越多。&lt;/li&gt;&lt;/ul&gt;    &lt;h3&gt;实验数据验证公式&lt;/h3&gt;    &lt;p&gt;所以其实到现在我们一直在说理论，带了一点点的公式，听起来好像是那么回事，但是公式到底怎么用？准不准确？可以精准测试还是概要测试即可？&lt;/p&gt;    &lt;p&gt;我们接下来实验一下吧。&lt;/p&gt;    &lt;p&gt;!!! 接下来会涉及到CPU Time 包含了操作系统对CPU的消耗，比如进程，线程调度等。&lt;/p&gt;    &lt;h4&gt;1.数据准备&lt;/h4&gt;    &lt;p&gt;这里就用之前的一个电商活动页面的优化来说吧，在这个过程中，我们做了大量测试，由于测试中使用了localhost方式，所以Java进程在IO上的Wait Time是非常小的。接下来，由于最佳线程数接近CPU核数,      &lt;br /&gt;所以在两核的机器上使用了10个Java进程，客户端发起了10个并发请求,这是在最佳线程数下(最佳线程数在一个区间里，在这个区间里QPS总体变化不大,并且也用了5、15个并发测试效果，发现QPS值基本相同)得出的大量结果，接下来就分析一下这些测试结果，见下表。&lt;/p&gt;    &lt;h5&gt;1)测试QPS结果&lt;/h5&gt;    &lt;table&gt;      &lt;tr&gt;        &lt;th&gt;原始页面大小&lt;/th&gt;        &lt;th&gt;压缩后的大小&lt;/th&gt;        &lt;th&gt;优化前QPS&lt;/th&gt;        &lt;th&gt;优化后QPS&lt;/th&gt;        &lt;th&gt;优化前RT&lt;/th&gt;        &lt;th&gt;优化后RT&lt;/th&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;92kb&lt;/td&gt;        &lt;td&gt;17kb&lt;/td&gt;        &lt;td&gt;164&lt;/td&gt;        &lt;td&gt;2024&lt;/td&gt;        &lt;td&gt;60.7ms&lt;/td&gt;        &lt;td&gt;4.9ms&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;138kb&lt;/td&gt;        &lt;td&gt;8.7kb&lt;/td&gt;        &lt;td&gt;143&lt;/td&gt;        &lt;td&gt;1859&lt;/td&gt;        &lt;td&gt;69.8ms&lt;/td&gt;        &lt;td&gt;3.3ms&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;182kb&lt;/td&gt;        &lt;td&gt;11.4kb&lt;/td&gt;        &lt;td&gt;121&lt;/td&gt;        &lt;td&gt;2083&lt;/td&gt;        &lt;td&gt;82.3ms&lt;/td&gt;        &lt;td&gt;4.8ms&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;248kb&lt;/td&gt;        &lt;td&gt;32kb&lt;/td&gt;        &lt;td&gt;77&lt;/td&gt;        &lt;td&gt;1977&lt;/td&gt;        &lt;td&gt;129.6ms&lt;/td&gt;        &lt;td&gt;5.0ms&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;295kb&lt;/td&gt;        &lt;td&gt;34.4kb&lt;/td&gt;        &lt;td&gt;70&lt;/td&gt;        &lt;td&gt;1722&lt;/td&gt;        &lt;td&gt;141.1ms&lt;/td&gt;        &lt;td&gt;5.8ms&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;    &lt;p&gt;      &lt;strong&gt;我们其实只要关注各项优化前后的QPS变化即可。&lt;/strong&gt;&lt;/p&gt;    &lt;h5&gt;2） CPU利用率&lt;/h5&gt;    &lt;p&gt;由于Apache Bench和Java部署在同一台机器.上，所以CPU利用率应该减去Apache Bench      &lt;br /&gt;的CPU资源消耗。根据观察，优化前Apache Bench 的CPU消耗在1.7%到2%之间，优化后      &lt;br /&gt;Apache Bench 的CPU资源消耗在20%左右。为什么优化前后有这么大的差距呢?因为优化后      &lt;br /&gt;响应能够及时返回，所以导致Apache Bench使用的CPU资源多了。&lt;/p&gt;    &lt;p&gt;在接下来的计算中，我们将优化前的CPU利用率设置为98%，优化后的CPU利用率设置为80%。&lt;/p&gt;    &lt;h5&gt;3）CPU Time 计算公式&lt;/h5&gt;    &lt;p&gt;根据QPS的计算方法，把QPS挪到右边的分母中，CPU  Time移到等号左边，就会得到下图的公式。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="image" src="https://summer-blog-images.oss-cn-shanghai.aliyuncs.com/qps_optimization/image_004.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;h5&gt;4) CPU Time计算示例&lt;/h5&gt;    &lt;p&gt;根据上方列出的三点（CPU利用率、QPS和CPU核数），接下里我们就详细的描述一下推算方法了。&lt;/p&gt;    &lt;h4&gt;计算得到的CPU Time&lt;/h4&gt;    &lt;p&gt;根据上方的表格计算方法，利用QPS计算出各页面理论上的CPU Time，计算后的结果如下表：&lt;/p&gt;    &lt;table&gt;      &lt;tr&gt;        &lt;th&gt;原始页面92kb&lt;/th&gt;        &lt;th&gt;计算公式&lt;/th&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;优化前CPU Time计算&lt;/td&gt;        &lt;td&gt;1000 / 164 x 2 x 0.98 = 12ms&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;优化后CPU Time计算&lt;/td&gt;        &lt;td&gt;1000 / 2024 x 2 x 0.8 = 0.8ms&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;    &lt;p&gt;      &lt;br /&gt;      &lt;br /&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;优化前QPS&lt;/th&gt;        &lt;th&gt;优化后QPS&lt;/th&gt;        &lt;th&gt;优化前CPU Time&lt;/th&gt;        &lt;th&gt;优化后CPU Time&lt;/th&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;92kb&lt;/td&gt;        &lt;td&gt;17kb&lt;/td&gt;        &lt;td&gt;164&lt;/td&gt;        &lt;td&gt;2024&lt;/td&gt;        &lt;td&gt;12ms&lt;/td&gt;        &lt;td&gt;0.8ms&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;138kb&lt;/td&gt;        &lt;td&gt;8.7kb&lt;/td&gt;        &lt;td&gt;143&lt;/td&gt;        &lt;td&gt;1859&lt;/td&gt;        &lt;td&gt;13.7ms&lt;/td&gt;        &lt;td&gt;0.86ms&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;182kb&lt;/td&gt;        &lt;td&gt;11.4kb&lt;/td&gt;        &lt;td&gt;121&lt;/td&gt;        &lt;td&gt;2083&lt;/td&gt;        &lt;td&gt;16.2ms&lt;/td&gt;        &lt;td&gt;0.77ms&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;248kb&lt;/td&gt;        &lt;td&gt;32kb&lt;/td&gt;        &lt;td&gt;77&lt;/td&gt;        &lt;td&gt;1977&lt;/td&gt;        &lt;td&gt;25.5ms&lt;/td&gt;        &lt;td&gt;0.81ms&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;295kb&lt;/td&gt;        &lt;td&gt;34.4kb&lt;/td&gt;        &lt;td&gt;70&lt;/td&gt;        &lt;td&gt;1722&lt;/td&gt;        &lt;td&gt;28ms&lt;/td&gt;        &lt;td&gt;0.93ms&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;    &lt;p&gt;这里主要看一下各项的CPU Time优化前后的变化，接下来，我们把两个值做减法，然后和开篇中提到过的实测程序中Gzip的CPU Time进行对比。&lt;/p&gt;    &lt;h4&gt;实测CPU Time&lt;/h4&gt;    &lt;h5&gt;1） 5个页面的Gzip所消耗的CPU Time&lt;/h5&gt;    &lt;p&gt;实测5个页面做Gzip所消耗的时间，然后跟公式计算出来的CPU Time做一个对比，如下表：&lt;/p&gt;    &lt;table&gt;      &lt;tr&gt;        &lt;th&gt;原始页面大小&lt;/th&gt;        &lt;th&gt;CPU Time公式差值(上表的CPU Time差值)&lt;/th&gt;        &lt;th&gt;Gzip CPU Time测量值(10次平均值)&lt;/th&gt;        &lt;th&gt;差值&lt;/th&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;92kb&lt;/td&gt;        &lt;td&gt;11.2ms&lt;/td&gt;        &lt;td&gt;8ms&lt;/td&gt;        &lt;td&gt;3.2ms&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;138kb&lt;/td&gt;        &lt;td&gt;12.8ms&lt;/td&gt;        &lt;td&gt;7ms&lt;/td&gt;        &lt;td&gt;5.8ms&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;182kb&lt;/td&gt;        &lt;td&gt;15.4ms&lt;/td&gt;        &lt;td&gt;9ms&lt;/td&gt;        &lt;td&gt;6.4ms&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;248kb&lt;/td&gt;        &lt;td&gt;24.7ms&lt;/td&gt;        &lt;td&gt;21ms&lt;/td&gt;        &lt;td&gt;3.0ms&lt;/td&gt;&lt;/tr&gt;      &lt;tr&gt;        &lt;td&gt;295kb&lt;/td&gt;        &lt;td&gt;27.1ms&lt;/td&gt;        &lt;td&gt;23ms&lt;/td&gt;        &lt;td&gt;4.1ms&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;    &lt;p&gt;可以看到，计算出来的CPU Time值要比测出来的要多一点，多了几毫秒，这是为什么呢?      &lt;br /&gt;      &lt;br /&gt;      &lt;br /&gt;其实是因为在优化前，有两个消耗CPU Time的阶段，一个是执行Java代码时，另一个是执行Gzip时。而优化后，整个逻辑变成了从缓存获取数据后直接返回，只有非常少的Java代码在消耗CPU Time(10行以内)。&lt;/p&gt;    &lt;h5&gt;2） Java页面执行消耗的CPU Time&lt;/h5&gt;    &lt;p&gt;大体上可以认为：&lt;/p&gt;    &lt;p&gt;优化前的CPU Time - 优化后的CPU Time = Gzip CPU Time + 全页面Java代码的CPU Time&lt;/p&gt;    &lt;p&gt;在实验中，一开始只统计了 Gzip 本身的消耗，而在 Java 文件中 Java代码执行的时间并没有包含在内，所以两者差距比较大。于是，我们单独统计了5个页面的Java代码的执行时间，发现文件中Java码执行的时间为3~6ms。实际测量的Gzip CPU Time 加上3~6ms的Java代码执行时间，和使用公式计算出来的CPUTime基本吻合。&lt;/p&gt;    &lt;p&gt;根据上面的计算和测量结果，我们发现 Gzip 的  CPU Time消耗加上Java代码的CPU Time消耗，与公式测量出来的总的CPU Time消耗非常接近，误差为1~2ms。考虑到CPU Time测量是单线程测量，而压力测试QPS是并发情况下(会多出进程切换的开销和GC等的开销)，我们认为这点误差是合理的，测试结果说明公式在宏观上是正确的。&lt;/p&gt;    &lt;h3&gt;压力测试最佳线程数和QPS临界点&lt;/h3&gt;    &lt;p&gt;前面讲到了公式的推导，并在一个固定的条件下验证了公式在该场景下的正确性。&lt;/p&gt;    &lt;p&gt;假设在一个thread-per-client的场景，有一一个Ajax请求，这个请求返回一个Json字符串，每个请求的CPU Time为1ms，WaitTime为300ms(比如读写Socket和线程调度的等待开销)。那么最佳线程数是&lt;/p&gt;    &lt;p&gt;&lt;/p&gt;    &lt;div&gt;\[(300+1/1 )x4x 100%=1204
\]&lt;/div&gt;    &lt;p&gt;&lt;/p&gt;    &lt;p&gt;尤其在广域网上，Wait Time=300ms是正常的数值。在国际环境下，300ms就更加常见了。这意味着如果是4核的机器，需要1204个线程，如果是8核的机器则需要2408个线程。实际上，有些HTTP服务的CPU Time是远小于1ms的，比如上面的场景中将页面压缩并缓存起来之后,CPU Time基本为0.8ms,如果WaitTime还是300ms,那么需要数以千计的线程啊!当线程数不断增加的时候，到达某个临界点之后对系统就开始产生负面影响了。&lt;/p&gt;    &lt;p&gt;(1) 大量线程上下文切换的开销，引起CPU Time的增加及QPS的减少。所以，有时候还没有达到最佳线程数，而QPS已经开始略微下降了。因为CPU Time发生变化、线程多了之后，调度引起的CPU Time提升的百分比和QPS下降的百分比成正比(上方的QPS公式)，上下文切换带来的开销如下。&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;上下文切换(微妙级别)&lt;/li&gt;      &lt;li&gt;JVM本身的开销&lt;/li&gt;      &lt;li&gt;CPU Cache加载&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;(2)线程的栈空间会占用大量的内存，假设每个线程的栈空间是1MB，这么多的线程就要占用数GB的内存。&lt;/p&gt;    &lt;p&gt;(3)在CPU Time不变的情况下，因为线程上下文切换和操作系统想尽力为线程在宏观上平均分配时间片的行为，导致每个线程的Wait Time都增加了，于是每个请求的RT也增加了，最终就会产生用户体验下降的情况。&lt;/p&gt;    &lt;p&gt;可以 用一张图来表示一下临界点的概念。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="image" src="https://summer-blog-images.oss-cn-shanghai.aliyuncs.com/qps_optimization/image_006.png"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;由于线程数增加超过某个临界点会影响CPU Time、QPS和RT，所以很难精确测量高并发下的CPU Time,它随着机器硬件、操作系统、线程数等因素不断变化。我们能做的就是压力测试QPS，并在压力测试的过程中调整线程数，使QPS达到临界点，这个临界点是QPS的一个峰值点。这个峰值点的线程数即当前系统的最佳线程数，当然如果这个时候CPU利用率没有达到100%，那么证明系统中可能存在瓶颈，应该在找到并处理瓶颈之后继续压力测试，并且重新找到这个临界点。&lt;/p&gt;    &lt;p&gt;当数据结构发生改变、算法改进或者业务逻辑发生改变时，最佳线程数有可能会跟着变化。&lt;/p&gt;    &lt;h3&gt;总结：&lt;/h3&gt;    &lt;p&gt;这篇 blog的例子中，在CPU Time下降到1ms左右而Wait Time需要数百毫秒的场景下，我们需要很多线程。但是当达到这个线程数的时候，有可能早就达到了临界点。所以系统整体已经不是最健康的状态了，但是现有的编程模型已经阻碍了我们前进，那么应该怎么办呢?为使某个系统达到最优状态?&lt;/p&gt;    &lt;p&gt;所以下一篇blog我们来说一下编程中的同步模型和异步模型问题，以及为什么异步模型只需要这么少的线程，是不是公式在异步模型下失效了。&lt;/p&gt;    &lt;br /&gt;    &lt;br /&gt;    &lt;br /&gt;    &lt;br /&gt;    &lt;br /&gt;    &lt;p&gt;我的个人站点      &lt;br /&gt;SUMMER      &lt;a href="https://www.huangyingsheng.com/about" rel="noopener" target="_blank"&gt;https://www.huangyingsheng.com/about&lt;/a&gt;&lt;/p&gt;    &lt;br /&gt;    &lt;br /&gt;    &lt;br /&gt;    &lt;br /&gt;    &lt;br /&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/62539-%E6%9C%8D%E5%8A%A1-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-qps</guid>
      <pubDate>Mon, 12 Dec 2022 15:48:36 CST</pubDate>
    </item>
    <item>
      <title>详解服务幂等性设计</title>
      <link>https://itindex.net/detail/62520-%E6%9C%8D%E5%8A%A1-%E5%B9%82%E7%AD%89-%E8%AE%BE%E8%AE%A1</link>
      <description>&lt;p&gt;  &lt;strong&gt;本文正在参加   &lt;a href="https://juejin.cn/post/7162096952883019783" title="https://juejin.cn/post/7162096952883019783"&gt;「金石计划 . 瓜分6万现金大奖」&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;hello，大家好，我是张张，「架构精进之路」公号作者。&lt;/p&gt;
 &lt;h2&gt;引子&lt;/h2&gt;
 &lt;p&gt;在日常工作中的一些技术设计方案评审会上，经常会有人提到注意服务接口的幂等性问题，最近就有个组内同学就跑到跟前问我，幂等性到底是个啥？&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;在目前分布式/微服务化的今天，提供的服务能力丰富多样，基于 HTTP 协议的 Web API 是时下最为流行的一种分布式服务提供方式，对于服务的幂等性保障尤为重要。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;我想了想，觉得有必要好好给他普及一下才行，否则以后做事还是一头雾水&lt;/p&gt;
 &lt;p&gt;今天计划就关于服务幂等性的一系列问题，在此将材料总结整理，顺便分享给大家~&lt;/p&gt;
 &lt;h2&gt;1、何为幂等性？&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;幂等（idempotence），来源于数学中的一个概念，例如：幂等函数/幂等方法（指用相同的参数重复执行，并能获得相同结果的函数，这些函数不影响系统状态，也不用担心重复执行会对系统造成改变）。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;简单理解即：  &lt;strong&gt;多次调用对系统的产生的影响是一样的，即对资源的作用是一样的&lt;/strong&gt;。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5e68d25f7c6649bf9ee0e3f2acc61839~tplv-k3u1fbpfcp-zoom-in-crop-mark:3024:0:0:0.awebp"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;幂等性&lt;/p&gt;
 &lt;p&gt;幂等性强调的是外界通过接口对系统内部的影响, 只要一次或多次调用对某一个资源应该具有同样的副作用就行。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;注意：这里指对资源造成的副作用必须是一样的，但是返回值允许不同！&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h2&gt;2、幂等性主要场景有哪些？&lt;/h2&gt;
 &lt;p&gt;根据上面对幂等性的定义我们得知：  &lt;strong&gt;产生重复数据或数据不一致，这个绝大部分是由于发生了重复请求&lt;/strong&gt;。&lt;/p&gt;
 &lt;p&gt;这里的重复请求是指同一个请求在一些情况下被多次发起。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;导致这个情况会有哪些场景呢？&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;微服务架构下，不同微服务间会有大量的基于 http,rpc 或者 mq 消息的网络通信，会有第三个情况【未知】，也就是超时。如果超时了，微服务框架会进行重试。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;用户交互的时候多次点击,无意地触发多笔交易。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;MQ 消息中间件，消息重复消费&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;第三方平台的接口（如：支付成功回调接口），因为异常也会导致多次异步回调&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;其他中间件/应用服务根据自身的特性，也有可能进行重试。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;3、幂等性的作用是什么？&lt;/h2&gt;
 &lt;p&gt;幂等性主要保证  &lt;strong&gt;多次调用对资源的影响是一致的&lt;/strong&gt;。&lt;/p&gt;
 &lt;p&gt;在阐述作用之前，我们利用资源处理应用来说明一下：&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;HTTP 与数据库的 CRUD 操作对应：&lt;/p&gt;
  &lt;ul&gt;
   &lt;li&gt;PUT ：CREATE&lt;/li&gt;
   &lt;li&gt;GET ：READ&lt;/li&gt;
   &lt;li&gt;POST ：UPDATE&lt;/li&gt;
   &lt;li&gt;DELETE ：DELETE&lt;/li&gt;
&lt;/ul&gt;
  &lt;p&gt;（其实不光是数据库，任何数据如文件图表都是这样）&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;1）  &lt;strong&gt;查询&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;SELECT * FROM users WHERE xxx;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;不会对数据产生任何变化，天然具备幂等性。&lt;/p&gt;
 &lt;p&gt;2）  &lt;strong&gt;新增&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;INSERT INTO users (user_id, name) VALUES (1, &amp;apos;zhangsan&amp;apos;);
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;case1：带有唯一索引（如：`user_id`），重复插入会导致后续执行失败，具有幂等性；&lt;/p&gt;
 &lt;p&gt;case2：不带有唯一索引，多次插入会导致数据重复，不具有幂等性。&lt;/p&gt;
 &lt;p&gt;3）  &lt;strong&gt;修改&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;case1：直接赋值，不管执行多少次 score 都一样，具备幂等性。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;UPDATE users SET score = 30 WHERE user_id = 1;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;case2：计算赋值，每次操作 score 数据都不一样，不具备幂等性。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;UPDATE users SET score = score + 30 WHERE user_id = 1;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;4）  &lt;strong&gt;删除&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;case1：绝对值删除，重复多次结果一样，具备幂等性。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;DELETE FROM users WHERE id = 1;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;case2：相对值删除，重复多次结果不一致，不具备幂等性。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;DELETE top(3) FROM users;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;总结：  &lt;strong&gt;通常只需要对写请求（新增 &amp;amp;更新）作幂等性保证&lt;/strong&gt;。&lt;/p&gt;
 &lt;h2&gt;4、如何解决幂等性问题？&lt;/h2&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;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1d1bea9028eb4242a55748de7f2668cd~tplv-k3u1fbpfcp-zoom-in-crop-mark:3024:0:0:0.awebp"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;幂等性问题分析&lt;/p&gt;
 &lt;h3&gt;4.1 控制重复请求&lt;/h3&gt;
 &lt;p&gt;控制动作触发源头，即前端做幂等性控制实现&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;相对不太可靠，没有从根本上解决问题，仅算作辅助解决方案。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;strong&gt;主要解决方案：&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;控制操作次数，例如：提交按钮仅可操作一次（提交动作后按钮置灰）&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;及时重定向，例如：下单/支付成功后跳转到成功提示页面，这样消除了浏览器前进或后退造成的重复提交问题。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;4.2 过滤重复动作&lt;/h3&gt;
 &lt;p&gt;控制过滤重复动作，是指在动作流转过程中控制有效请求数量。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;1）分布式锁&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;利用 Redis 记录当前处理的业务标识，当检测到没有此任务在处理中，就进入处理，否则判为重复请求，可做过滤处理。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;订单发起支付请求，支付系统会去 Redis 缓存中查询是否存在该订单号的 Key，如果不存在，则向 Redis 增加 Key 为订单号。查询订单支付已经支付，如果没有则进行支付，支付完成后删除该订单号的 Key。通过 Redis 做到了分布式锁，只有这次订单订单支付请求完成，下次请求才能进来。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;分布式锁相比去重表，将放并发做到了缓存中，较为高效。思路相同，同一时间只能完成一次支付请求。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;2）token 令牌&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;应用流程如下：&lt;/p&gt;
 &lt;p&gt;1）服务端提供了发送 token 的接口。执行业务前先去获取 token，同时服务端会把 token 保存到 redis 中；&lt;/p&gt;
 &lt;p&gt;2）然后业务端发起业务请求时，把 token 一起携带过去，一般放在请求头部；&lt;/p&gt;
 &lt;p&gt;3）服务器判断 token 是否存在 redis 中，存在即第一次请求，可继续执行业务，执行业务完成后将 token 从 redis 中删除；&lt;/p&gt;
 &lt;p&gt;4）如果判断 token 不存在 redis 中，就表示是重复操作，直接返回重复标记给 client，这样就保证了业务代码不被重复执行。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/feaa6aba126f4e6793858e8ffd05b7d4~tplv-k3u1fbpfcp-zoom-in-crop-mark:3024:0:0:0.awebp"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;3）缓冲队列&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;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/738b6c9bcf284cf7a906810178669d88~tplv-k3u1fbpfcp-zoom-in-crop-mark:3024:0:0:0.awebp"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;4.3 解决重复写&lt;/h3&gt;
 &lt;p&gt;实现幂等性常见的方式有：  &lt;strong&gt;悲观锁（for update）、乐观锁、唯一约束&lt;/strong&gt;。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;1）悲观锁（Pessimistic Lock）&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;简单理解就是：假设每一次拿数据，都有认为会被修改，所以给数据库的行或表上锁。&lt;/p&gt;
 &lt;p&gt;当数据库执行 select for update 时会获取被 select 中的数据行的行锁，因此其他并发执行的 select for update 如果试图选中同一行则会发生排斥（需要等待行锁被释放），因此达到锁的效果。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;select for update 获取的行锁会在当前事务结束时自动释放，因此必须在事务中使用。（注意 for update 要用在索引上，不然会锁表）&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;pre&gt;  &lt;code&gt;START TRANSACTION; 
# 开启事务
SELETE * FROM users WHERE id=1 FOR UPDATE;
UPDATE users SET name= &amp;apos;xiaoming&amp;apos; WHERE id = 1;
COMMIT; 
# 提交事务
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;2）乐观锁（Optimistic Lock）&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;简单理解就是：就是很乐观，每次去拿数据的时候都认为别人不会修改。更新时如果 version 变化了，更新不会成功。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;不过，乐观锁存在失效的情况，就是常说的 ABA 问题，不过如果 version 版本一直是自增的就不会出现 ABA 的情况。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;pre&gt;  &lt;code&gt;UPDATE users 
SET name=&amp;apos;xiaoxiao&amp;apos;, version=(version+1) 
WHERE id=1 AND version=version;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;缺点：就是在操作业务前，需要先查询出当前的 version 版本&lt;/p&gt;
 &lt;p&gt;另外，还存在一种：  &lt;strong&gt;状态机控制&lt;/strong&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;例如：支付状态流转流程：待支付-&amp;gt;支付中-&amp;gt;已支付&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;具有一定要的前置要求的，严格来讲，也属于乐观锁的一种。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;3）唯一约束&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;常见的就是利用  &lt;strong&gt;数据库唯一索引&lt;/strong&gt;或者  &lt;strong&gt;全局业务唯一标识&lt;/strong&gt;（如：source+序列号等）。&lt;/p&gt;
 &lt;p&gt;这个机制是利用了数据库的主键唯一约束的特性，解决了在 insert 场景时幂等问题。但主键的要求不是自增的主键，这样就需要业务生成全局唯一的主键。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;全局 ID 生成方案&lt;/strong&gt;：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;UUID&lt;/strong&gt;：结合机器的网卡、当地时间、一个随记数来生成 UUID；&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;数据库自增 ID&lt;/strong&gt;：使用数据库的 id 自增策略，如 MySQL 的 auto_increment。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;Redis 实现&lt;/strong&gt;：通过提供像 INCR 和 INCRBY 这样的自增原子命令，保证生成的 ID 肯定是唯一有序的。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;    &lt;strong&gt;雪花算法-Snowflake&lt;/strong&gt;：由 Twitter 开源的分布式 ID 生成算法，以划分命名空间的方式将 64-bit 位分割成多个部分，每个部分代表不同的含义。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;小结&lt;/strong&gt;：按照应用上的最优收益，推荐排序为：  &lt;strong&gt;乐观锁 &amp;gt; 唯一约束 &amp;gt; 悲观锁&lt;/strong&gt;。&lt;/p&gt;
 &lt;h2&gt;5、总结&lt;/h2&gt;
 &lt;p&gt;通常情况下，非幂等问题，主要是  &lt;strong&gt;由于重复且不确定的写操作&lt;/strong&gt;造成的。&lt;/p&gt;
 &lt;h4&gt;1、解决重复的主要思考点&lt;/h4&gt;
 &lt;p&gt;从请求全流程，控制重复请求触发以及重复数据处理&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;客户端 控制发起重复请求&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;服务端 过滤重复无效请求&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;底层数据处理 避免重复写操作&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;2、控制不确定性主要思考点&lt;/h4&gt;
 &lt;p&gt;从服务设计思路上做改变，尽量避免不确定性：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;统计变量改为数据记录方式&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;范围操作改为确定操作&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;后记&lt;/h2&gt;
 &lt;p&gt;听了我以上大段的讲述后，他好像收获感满满的似的说：大概理解了...&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;·················· END ··················&lt;/p&gt;
 &lt;p&gt;希望今天的讲解对大家有所帮助，谢谢！&lt;/p&gt;
 &lt;p&gt;Thanks for reading!&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;作者：架构精进之路，十年研发风雨路，大厂架构师，CSDN 博客专家，专注架构技术沉淀学习及分享，职业与认知升级，坚持分享接地气儿的干货文章，期待与你一起成长。   &lt;br /&gt;
**关注并私信我回复“01”，送你一份程序员成长进阶大礼包，欢迎勾搭。   &lt;br /&gt;
**&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/62520-%E6%9C%8D%E5%8A%A1-%E5%B9%82%E7%AD%89-%E8%AE%BE%E8%AE%A1</guid>
      <pubDate>Sun, 04 Dec 2022 14:41:23 CST</pubDate>
    </item>
    <item>
      <title>微服务架构下的认证鉴权解决方案</title>
      <link>https://itindex.net/detail/62510-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84-%E8%AE%A4%E8%AF%81</link>
      <description>&lt;blockquote&gt;
  &lt;p&gt;我们来自字节跳动飞书商业应用研发部(Lark Business Applications)，目前我们在北京、深圳、上海、武汉、杭州、成都、广州、三亚都设立了办公区域。我们关注的产品领域主要在企业经验管理软件上，包括飞书 OKR、飞书绩效、飞书招聘、飞书人事等 HCM 领域系统，也包括飞书审批、OA、法务、财务、采购、差旅与报销等系统。欢迎各位加入我们。&lt;/p&gt;
  &lt;p&gt;本文作者：飞书商业应用研发部 许家强&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;blockquote&gt;
  &lt;p&gt;欢迎大家关注   &lt;a href="https://juejin.cn/user/712139266595784" title="https://juejin.cn/user/712139266595784"&gt;    &lt;strong&gt;飞书技术&lt;/strong&gt;&lt;/a&gt;，每周定期更新飞书技术团队技术干货内容，想看什么内容，欢迎大家评论区留言~&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h1&gt;背景&lt;/h1&gt;
 &lt;p&gt;单体应用在向微服务化架构演进时，需要考虑如何解决服务认证授权的问题。如果处理不好，会引发架构的混乱，带来安全、性能、难以维护的问题。 以最典型的包含WEB页面的具备登录态管理的系统为例。在最初阶段，登录鉴权一般通过cookie+redis分布式session来实现。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b31e5b9a24014b5798dbf263800c4e0f~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在服务化过程中，单体系统会拆分为多个微服务，这时微服务间会出现相互调用。对于使用Dubbo、Grpc等RPC协议的系统而言，由于给web页面提供的是HTTP接口，而给微服务间调用提供的RPC接口，架构比较清晰。而对于Springcloud技术体系，微服间调用和页面都是通过HTTP RESTFUL接口，这时候要解决两个问题：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;web页面的登录校验&lt;/li&gt;
  &lt;li&gt;微服务之间的鉴权&lt;/li&gt;
&lt;/ol&gt;
 &lt;h1&gt;解决方案&lt;/h1&gt;
 &lt;h2&gt;透传cookie反模式&lt;/h2&gt;
 &lt;p&gt;这种方案希望保持单体架构时的调用方式，微服务间调用接口复用了提供给WEB页面的接口。 为了解决登录鉴权问题，微服务间调用时，会将WEB页面的cookie透传至其他服务，这样鉴权逻辑可以保持不动。 很显然，该方案虽然看上去能很好work，但它是一种反模式设计，完全违背了微服务的初衷，微服务一定是无状态的！&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a16935f9ce50462c9b820e59e2367cd9~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;内外接口分离模式&lt;/h2&gt;
 &lt;p&gt;很显然，上述方案是不合理的。如果想继续保持服务间调用使用RESTFUL域名，则可以将面向前端的接口与面向微服务的接口区分开，实现方式是为两者加上不同的URL前缀，如/inner、/outer。外部接口是给WEB页面调用的，内部接口是专门给微服务间调用的。在认证鉴权时，仅针对外部接口进行鉴权，而内部接口直接放行。 该方案的问题是，内部接口存在安全隐患，可以被外界肆意攻击。 如果要缓解该问题，可通过如下方式：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;申请一个额外的生产网段的域名（需做七网隔离，外网/办公网/生产网段无法互相访问），专门用于服务间调用。&lt;/li&gt;
  &lt;li&gt;在Nignx侧增加配置，对于原有的域名，只放行以outer前缀开始的请求。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a36a5a19c8ee450a85b18e51f9c17442~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;web和服务分离模式&lt;/h2&gt;
 &lt;p&gt;上述方案的另外一个变种，仍是将对外接口和对内接口分开，但是划分的更加彻底，新增了一个专门提供面向前端提供web接口的服务，如下图所示。该方案的优点：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;微服务不需要关心如何校验session，所有认证都在web服务做。这个微服务是真正无状态的。&lt;/li&gt;
  &lt;li&gt;微服务间的调用不做鉴权。&lt;/li&gt;
  &lt;li&gt;微服务是高度通用的，只需要处理领域核心逻辑，不用关心如何和前端页面适配，这点对于平台型服务尤其如此。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;对于该方案中因内部接口调用产生的安全问题，可参考上个方案，申请一个额外的生产网段的域名。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5c8ddba5964949228843e04d51363206~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;网关鉴权&lt;/h2&gt;
 &lt;p&gt;对于有中台的技术团队而言，一般都会有提供一个通用的平台网关。我们希望在网关解决所有登录鉴权的问题，这使得登录鉴权问题对业务服务完全透明。而对于服务间调用的问题，可使用rpc、类rpc方式（ServiceMesh）、内部域名来实现，而这些调用方式都是零信任无鉴权的。 对于这种方案，微服务的api有很大的通用性和灵活性，接口设计不需要区分接口使用的场景，是目前业界比较推荐的做法。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6197a2cc34d94a6d9202191e2e8fec8e~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h1&gt;总结&lt;/h1&gt;
 &lt;p&gt;本文介绍了服务在从单体演进到微服务架构过程中，对于服务认证鉴权遇到的问题，并提供了开发人员可能会用到的解决方案。除方案1外（不大合理、属于野路子），其他方案在具体实践过程中都有较多的case。如今微服务架构已经成为事实上的标准，我们希望微服务一定是无状态的，专注于处理业务流程和规则，而鉴权认证的逻辑应交给专门的技术组件来负责，因此让网关来统一处理鉴权是一个更优雅的方案。&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;h1&gt;加入我们&lt;/h1&gt;
 &lt;p&gt;扫码发现职位 &amp;amp; 投递简历：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/002bf03e0afc43c9807714e11efc3216~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;官网投递：  &lt;a href="https://link.juejin.cn/?target=https%3A%2F%2Fjob.toutiao.com%2Fs%2FFyL7DRg"&gt;job.toutiao.com/s/FyL7DRg&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 />
      <guid isPermaLink="true">https://itindex.net/detail/62510-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84-%E8%AE%A4%E8%AF%81</guid>
      <pubDate>Wed, 30 Nov 2022 16:55:40 CST</pubDate>
    </item>
    <item>
      <title>为什么说 Gateway API 是 Kubernetes 和服务网格入口中网关的未来？</title>
      <link>https://itindex.net/detail/62482-gateway-api-kubernetes</link>
      <description>&lt;p&gt;本文将以 Kubernetes Ingress、Istio 和 Envoy Gateway 为例，向你介绍 Kubernetes 中的入口网关和 Gateway API，同时介绍 Gateway API 使得 Kubernetes 和服务网格入口网关融合的新趋势。&lt;/p&gt;
 &lt;h2&gt;本文观点&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;Ingress 作为 Kubernetes 的初代入口网关，它的资源模型过于简单以致于无法适应当今的可编程网络；&lt;/li&gt;
  &lt;li&gt;Gateway API 作为 Kubernetes 入口网关的最新成果，它通过角色划分将关注点分离，并提供跨 namespace 支持使其更适应多云环境，已获得大多数 API 网关的支持；&lt;/li&gt;
  &lt;li&gt;入口网关（南北向）与服务网格（东西向，集群内路由）存在部分功能重叠，Gateway API 为两者的融合提供了新的参考模型；&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;Kubernetes 入口网关的历史&lt;/h2&gt;
 &lt;p&gt;2014 年 6 月 Kubernetes 开源，起初只能使用 NodePort 和 LoadBalancer 类型的 Service 对象来暴露集群内服务，后来才诞生了   &lt;a href="https://kubernetes.io/zh-cn/docs/concepts/services-networking/ingress/" rel="noopener" target="_blank" title="Ingress"&gt;Ingress&lt;/a&gt;
，两年后（Kubernetes 1.2） Ingress API 进入 Beta 版本，随后为了保持其轻量和可移植的特性，Ingress API 相较于 Kubernetes 其他 API 发展得比较缓慢，直到 Kubernetes 1.19 它才升级到 GA。&lt;/p&gt;
 &lt;p&gt;Ingress 的主要目标是用简单的、声明性的语法来暴露 HTTP 应用。你可以在 Kubernetes 中部署多种 Ingress Controller，并在创建 Ingress 的时候通过 IngressClass 指定该网关使用的控制器，或者在 Kubernetes 中设置默认的默认的 IngressClass。Kubernetes 默认只支持 AWS、GCE 和 Nginx Ingress Controller，同时还支持大量的  &lt;a href="https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/#additional-controllers" rel="noopener" target="_blank" title="&amp;#31532;&amp;#19977;&amp;#26041; Ingress Controller"&gt;第三方 Ingress Controller&lt;/a&gt;
。&lt;/p&gt;
 &lt;p&gt;下图展示了 Kubernetes Ingress 的工作流程。&lt;/p&gt;
 &lt;p&gt;
  
  
  
  
    
      &lt;img alt="image" src="https://jimmysong.io/blog/why-gateway-api-is-the-future-of-ingress-and-mesh/ingress-flow.svg"&gt;&lt;/img&gt;
    
  
  Kubernetes Ingress 工作流程

&lt;/p&gt;
 &lt;p&gt;详细流程如下：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;Kubernetes 集群管理员在 Kubernetes 中部署 Ingress Controller；&lt;/li&gt;
  &lt;li&gt;Ingress Controller 会持续监视 Kubernetes  API Server 中的 IngressClass 和 Ingress 对象的变动；&lt;/li&gt;
  &lt;li&gt;管理员应用 IngressClass 和 Ingress 来部署网关；&lt;/li&gt;
  &lt;li&gt;Ingress Controller 会根据管理员的配置来创建对应的入口网关并配置路由规则；&lt;/li&gt;
  &lt;li&gt;如果在云中，客户端会访问该入口网关的负载均衡器；&lt;/li&gt;
  &lt;li&gt;网关将根据 HTTP 请求中的 host 和 path 将流量路由到对应的后端服务；&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;Istio 同时支持 Ingress 和 Gateway API，下面是一个使用 Istio 入口网关的配置示例，在后文中会使用 Gateway API 创建该配置。&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;   &lt;code&gt;apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: istio
spec:
  controller: istio.io/ingress-controller
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress
spec:
  ingressClassName: istio
  rules:
  - host: httpbin.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: httpbin
            port: 8000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;注意：Ingress 的 spec 中必须在   &lt;code&gt;ingressClassName&lt;/code&gt; 字段中指定使用的   &lt;code&gt;IngressClass&lt;/code&gt;，否则将无法创建对应的入口网关。&lt;/p&gt;
 &lt;h2&gt;Kubernetes Ingress 的局限性&lt;/h2&gt;
 &lt;p&gt;虽然   &lt;code&gt;IngressClass&lt;/code&gt; 实现了入口网关与后台实现的解耦，但是它仍然有着巨大的局限性：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Ingress 的配置过于简单，仅支持 HTTP 协议路由；&lt;/li&gt;
  &lt;li&gt;HTTP 路由仅支持 host 和 path 匹配，对于高级路由功能没有通用配置，只能通过 annotation 来实现，比如   &lt;a href="https://help.aliyun.com/document_detail/86533.html#section-xsg-g5g-1uy" rel="noopener" target="_blank" title="&amp;#20351;&amp;#29992; Nginx Ingress Controller &amp;#23454;&amp;#29616; URL &amp;#37325;&amp;#23450;&amp;#21521;"&gt;使用 Nginx Ingress Controller 实现 URL 重定向&lt;/a&gt;
，需要配置    &lt;code&gt;nginx.ingress.kubernetes.io/rewrite-target&lt;/code&gt; annotation，已经无法适应可编程路由的需求；&lt;/li&gt;
  &lt;li&gt;不同命名空间中的服务要绑定到同一个网关中的情况在实际情况下经常出现，而入口网关无法在多个命名空间中共享；&lt;/li&gt;
  &lt;li&gt;入口网关的创建和管理的职责没有划分界限，导致开发者不仅要&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;Kubernetes Gateway API&lt;/h2&gt;
 &lt;p&gt;Gateway API 是一个 API 资源的集合 ——   &lt;code&gt;GatewayClass&lt;/code&gt;、  &lt;code&gt;Gateway&lt;/code&gt;、  &lt;code&gt;HTTPRoute&lt;/code&gt;、  &lt;code&gt;TCPRoute&lt;/code&gt;、  &lt;code&gt;ReferenceGrant&lt;/code&gt; 等。Gateway API 暴露了一个更通用的代理 API，可以用于更多的协议，而不仅仅是 HTTP，并为更多的基础设施组件建模，为集群运营提供更好的部署和管理选项。&lt;/p&gt;
 &lt;p&gt;另外 Gateway API 通过将资源对象分离，实现配置上的解耦，可以由不同的角色的人员来管理，其中的 API 对象如下图所示。&lt;/p&gt;
 &lt;p&gt;
  
  
  
  
    
      &lt;img alt="image" src="https://jimmysong.io/blog/why-gateway-api-is-the-future-of-ingress-and-mesh/gateway-api-roles.svg"&gt;&lt;/img&gt;
    
  
  Gateway API 及角色

&lt;/p&gt;
 &lt;p&gt;下面是在 Istio 中使用 Gateway API 的示例。&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;   &lt;code&gt;apiVersion: gateway.networking.k8s.io/v1alpha2
kind: Gateway
metadata:
  name: gateway
  namespace: istio-ingress
spec:
  gatewayClassName: istio
  listeners:
  - name: default
    hostname: &amp;quot;*.example.com&amp;quot;
    port: 80
    protocol: HTTP
    allowedRoutes:
      namespaces:
        from: All
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: HTTPRoute
metadata:
  name: http
  namespace: default
spec:
  parentRefs:
  - name: gateway
    namespace: istio-ingress
  hostnames: [&amp;quot;httpbin.example.com&amp;quot;]
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: httpbin
      port: 8000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;与 Ingress 类似，Gateway 使用   &lt;code&gt;gatewayClassName&lt;/code&gt; 声明其使用的控制器，该控制器需要平台管理员创建，并允许客户端对   &lt;code&gt;*.example.com&lt;/code&gt; 域名的请求。应用开发者可以在其服务所在的命名空间中，在此示例中是   &lt;code&gt;default&lt;/code&gt; 创建路由规则，并通过   &lt;code&gt;parentRefs&lt;/code&gt; 绑定到 Gateway 上，当然这必须是在 Gateway 明确允许其绑定的情况下（通过   &lt;code&gt;allowRoutes&lt;/code&gt; 字段中的规则设置）。&lt;/p&gt;
 &lt;p&gt;当你应用上面的配置后，Istio 会自动为你创建一个负载均衡网关，下图展示了 Gateway API 的工作流程。&lt;/p&gt;
 &lt;p&gt;
  
  
  
  
    
      &lt;img alt="image" src="https://jimmysong.io/blog/why-gateway-api-is-the-future-of-ingress-and-mesh/gateway-api-flow.svg"&gt;&lt;/img&gt;
    
  
  Gateway API 工作流程

&lt;/p&gt;
 &lt;p&gt;详细流程如下：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;基础设施供应商提供了    &lt;code&gt;GatewayClass&lt;/code&gt; 和 Gateway 控制器；&lt;/li&gt;
  &lt;li&gt;平台运维部署 Gateway（可以部署多个，或使用不同的    &lt;code&gt;GatewayClass&lt;/code&gt;）；&lt;/li&gt;
  &lt;li&gt;Gateway Controller 会持续监视 Kubernetes  API Server 中的    &lt;code&gt;GatewayClass&lt;/code&gt; 和    &lt;code&gt;Gateway&lt;/code&gt; 对象的变动；&lt;/li&gt;
  &lt;li&gt;Gateway Controller 会根据集群运维的配置来创建对应的网关；&lt;/li&gt;
  &lt;li&gt;应用开发者应用 xRoute 并绑定服务上；&lt;/li&gt;
  &lt;li&gt;如果在云中，客户端会访问该入口网关的负载均衡器；&lt;/li&gt;
  &lt;li&gt;网关将根据流量请求中的匹配条件将路由到对应的后端服务；&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;从以上步骤中我们可以看出 Gateway API 相比 Ingress 有了明确的角色划分，而且路由规则可以与网关配置解耦，这大大增加了管理的灵活性。&lt;/p&gt;
 &lt;p&gt;下图展示了流量接入网关后经过处理的流程。&lt;/p&gt;
 &lt;p&gt;
  
  
  
  
    
      &lt;img alt="image" src="https://jimmysong.io/blog/why-gateway-api-is-the-future-of-ingress-and-mesh/traffic-flow.svg"&gt;&lt;/img&gt;
    
  
  网关处理流程图

&lt;/p&gt;
 &lt;p&gt;从图中我们可以看出路由是与网关绑定的，路由一般与其后端服务部署在同一个命名空间中，如果在不同的命名空间中时，需要在   &lt;a href="https://gateway-api.sigs.k8s.io/api-types/referencegrant/" rel="noopener" target="_blank" title="&lt;code&gt;ReferenceGrant&lt;/code&gt;"&gt;   &lt;code&gt;ReferenceGrant&lt;/code&gt;&lt;/a&gt;
 中明确赋予该路由跨命名空间的引用权限，例如下面的   &lt;code&gt;foo&lt;/code&gt; 命名空间中的 HTTPRoute   &lt;code&gt;foo&lt;/code&gt; 可以引用   &lt;code&gt;bar&lt;/code&gt; 命名空间中的   &lt;code&gt;bar&lt;/code&gt; 服务。&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;   &lt;code&gt;kind: HTTPRoute
metadata:
  name: foo
  namespace: foo
spec:
  rules:
  - matches:
    - path: /bar
    forwardTo:
      backend:
      - name: bar
        namespace: bar
---
kind: ReferenceGrant
metadata:
  name: bar
  namespace: bar
spec:
  from:
  - group: networking.gateway.k8s.io
    kind: HTTPRoute
    namespace: foo
  to:
  - group: &amp;quot;&amp;quot;
    kind: Service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;目前，Gateway API 仅支持   &lt;code&gt;HTTPRoute&lt;/code&gt;，  &lt;code&gt;TCPRoute&lt;/code&gt;、  &lt;code&gt;UDPRoute&lt;/code&gt;、  &lt;code&gt;TLSRoute&lt;/code&gt; 和   &lt;code&gt;GRCPRoute&lt;/code&gt; 还在实验阶段。Gateway API 已经得到了大量的网关和服务网格项目的支持，请  &lt;a href="https://gateway-api.sigs.k8s.io/implementations/" rel="noopener" target="_blank" title="&amp;#22312; Gateway &amp;#23448;&amp;#26041;&amp;#25991;&amp;#26723;&amp;#20013;&amp;#26597;&amp;#30475;&amp;#25903;&amp;#25345;&amp;#29366;&amp;#20917;"&gt;在 Gateway 官方文档中查看支持状况&lt;/a&gt;
。&lt;/p&gt;
 &lt;h2&gt;入口网关与服务网格&lt;/h2&gt;
 &lt;p&gt;服务网格主要关注的是东西向流量，即 Kubernetes 集群内部的流量，但是大部分服务网格同样提供了入口网关功能，例如 Istio。但是 Istio 的功能和 API 过于复杂，在本文中我们就以 SMI 为例来说明入口网关和服务网格的关系。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://smi-spec.io/" rel="noopener" target="_blank" title="SMI"&gt;SMI&lt;/a&gt;
（Service Mesh Interface）是 CNCF 的孵化项目，开源与 2019 年，它定义了独立于供应商的在 Kubernetes 中运行的服务网格通用标准。&lt;/p&gt;
 &lt;p&gt;下图说明 Gateway API 与服务网格 API 的重叠点。&lt;/p&gt;
 &lt;p&gt;
  
  
  
  
    
      &lt;img alt="image" src="https://jimmysong.io/blog/why-gateway-api-is-the-future-of-ingress-and-mesh/gateway-smi-overlay.svg"&gt;&lt;/img&gt;
    
  
  Gateway API 与 SMI 有部分重合

&lt;/p&gt;
 &lt;p&gt;从图中我们可以看到 Gateway API 与 SMI 在流量规范部分有明显的重叠。这些重叠导致同样的功能，需要在 Gateway API 和服务网格中重复实现。&lt;/p&gt;
 &lt;h3&gt;Istio 服务网格&lt;/h3&gt;
 &lt;p&gt;当然，并不是所有的服务网格是完全符合 SMI 标准，Istio 是目前最流行的服务网格实现，它提供了丰富的流量管理功能，但是没有对这些功能制定单独的策略 API，而是耦合在   &lt;code&gt;VirtualService&lt;/code&gt; 和   &lt;code&gt;DestinationRule&lt;/code&gt; 中，如下所示。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;VirtualService&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;路由：金丝雀发布、基于用户身、URI、Header 等匹配路由等；&lt;/li&gt;
  &lt;li&gt;错误注入：HTTP 错误代码注入、HTTP 延时注入；&lt;/li&gt;
  &lt;li&gt;流量切分：基于百分比的流量切分路由；&lt;/li&gt;
  &lt;li&gt;流量镜像：将一定百分比的流量镜像发送到其他集群；&lt;/li&gt;
  &lt;li&gt;超时：设置超时时间，超过设置的时间请求将失败；&lt;/li&gt;
  &lt;li&gt;重试：设置重试策略，如触发条件、重试次数、间隔时间等；&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;DestinationRule&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;负载均衡：设置负载均衡策略，如简单负载均衡、区域感知负载均衡、区域权重负载均衡；&lt;/li&gt;
  &lt;li&gt;熔断（Circuit Breaking）：通过异常点检测（Outlier Detection）和连接池设置将异常节点从负载均衡池中剔除；&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;code&gt;VirtualService&lt;/code&gt; 主要处理路由相关功能，而   &lt;code&gt;DestinationRule&lt;/code&gt; 负责集群节点的开合和负载均衡。&lt;/p&gt;
 &lt;h3&gt;Gateway API 融合 Kubernetes 和服务网格的入口网关&lt;/h3&gt;
 &lt;p&gt;正如上文所述，Gateway API 与服务网格之间有部分功能交集，为了减少重复开发，促成对 Gateway API 与服务网格之间共同关注点的建模，Gateway API 工作组提出了   &lt;a href="https://gateway-api.sigs.k8s.io/contributing/gamma/" rel="noopener" target="_blank" title="GAMMA"&gt;GAMMA&lt;/a&gt;
（Gateway API Mesh Management and Administration）倡议。&lt;/p&gt;
 &lt;p&gt;在该倡议的倡导下，那些在不同网关实现中的细节各不相同的高级流量管理功能，例如超时、重试、健康检查等，全部通过  &lt;a href="https://gateway-api.sigs.k8s.io/references/policy-attachment/" rel="noopener" target="_blank" title="&amp;#31574;&amp;#30053;&amp;#38468;&amp;#20214;"&gt;策略附件&lt;/a&gt;
（Policy Attachment）的方式将由各个提供商来实现。你可以通过通过   &lt;code&gt;targetRef&lt;/code&gt; 字段指定策略附件所附加到的资源对象，例如下所示：&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;   &lt;code&gt;apiVersion: networking.acme.io/v1alpha1
kind: RetryPolicy
metadata:
  name: foo
spec:
  override:
    maxRetries: 10
  default:
    maxRetries: 5
  targetRef:
    group: gateway.networking.k8s.io/v1alpha2
    kind: HTTPRoute
    name: foo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;在这里例子中重试策略被附加到了名为   &lt;code&gt;foo&lt;/code&gt; 和   &lt;code&gt;HTTPRoute&lt;/code&gt; 上。策略附件附加到不同的资源对象上，其生效的优先级也不同，例如 GatewayClass 是集群级的资源，如果策略附件覆盖在它上面的话，将优先生效。&lt;/p&gt;
 &lt;p&gt;你可以给附加策略指定   &lt;code&gt;override&lt;/code&gt; 和   &lt;code&gt;default&lt;/code&gt; 值，其在入口和网格内不同资源上的层次结构的优先级是如下图所示。&lt;/p&gt;
 &lt;p&gt;
  
  
  
  
    
      &lt;img alt="image" src="https://jimmysong.io/blog/why-gateway-api-is-the-future-of-ingress-and-mesh/policy-attachment-priority.svg"&gt;&lt;/img&gt;
    
  
  Kubernetes 入口与网格中的覆盖和默认值的优先级

&lt;/p&gt;
 &lt;p&gt;目前，Gateway API 正在探索用来处理网格流量，并提出了一些  &lt;a href="https://docs.google.com/document/d/1T_DtMQoq2tccLAtJTpo3c0ohjm25vRS35MsestSL9QU/edit#heading=h.6ks49gf06yii" rel="noopener" target="_blank" title="&amp;#35774;&amp;#35745;&amp;#26041;&amp;#26696;"&gt;设计方案&lt;/a&gt;
。&lt;/p&gt;
 &lt;h2&gt;Envoy Gateway&lt;/h2&gt;
 &lt;p&gt;2022 年 10 月 Envoy Gateway 首个开源版本   &lt;a href="https://jimmysong.io/blog/envoy-gateway-release/" title="v0.2 &amp;#21457;&amp;#24067;"&gt;v0.2 发布&lt;/a&gt;
，这是一个基于 Envoy 代理的遵循 Gateway API 而创建的网关，  &lt;a href="https://tetrate.io" rel="noopener" target="_blank" title="Tetrate"&gt;Tetrate&lt;/a&gt;
 是该项目的核心发起者之一。Envoy Gateway 的目标是降低用户采用 Envoy 作为 API 网关的障碍，以吸引更多用户采用 Envoy。它通过入口和 L4/L7 流量路由，表达式、可扩展、面向角色的 API 设计，使其成为供应商建立 API 网关增值产品的基础。&lt;/p&gt;
 &lt;p&gt;早在 Envoy Gateway 发布之前，Envoy 作为最流行了云原生代理之一，已被大规模采用，有多款 Gateway 软件基于 Envoy 构建，Istio 服务网格使用它作为默认的 sidecar 代理，并通过 xDS 协议来配置这些分布式代理。在 Envoy Gateway 中，它同样使用 xDS 来配置 Envoy 机群，下图展示了 Envoy Gateway 的架构。&lt;/p&gt;
 &lt;p&gt;
  
  
  
  
    
      &lt;img alt="image" src="https://jimmysong.io/blog/why-gateway-api-is-the-future-of-ingress-and-mesh/envoy-gateway-arch.svg"&gt;&lt;/img&gt;
    
  
  Envoy Gateway 架构图

&lt;/p&gt;
 &lt;p&gt;基础设施供应商会为你提供   &lt;code&gt;GatewayGlass&lt;/code&gt;，你可以通过创建一个 Gateway 声明来创建一个 Envoy Gateway，你在 Gateway 中的路由和策略附件会通过 xDS 协议发送给 Envoy 机群。&lt;/p&gt;
 &lt;p&gt;关于 Envoy Gateway 的进一步介绍，请阅读：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://lib.jimmysong.io/blog/hands-on-with-envoy-gateway/" rel="noopener" target="_blank" title="&amp;#20351;&amp;#29992; Envoy Gateway 0.2 &amp;#20307;&amp;#39564;&amp;#26032;&amp;#30340; Kubernetes Gateway API"&gt;使用 Envoy Gateway 0.2 体验新的 Kubernetes Gateway API&lt;/a&gt;
&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://lib.jimmysong.io/blog/envoy-gateway-to-the-future/" rel="noopener" target="_blank" title="&amp;#38754;&amp;#21521;&amp;#26410;&amp;#26469;&amp;#30340;&amp;#32593;&amp;#20851;&amp;#65306;&amp;#26032;&amp;#30340; Kubernetes Gateway API &amp;#21644; Envoy Gateway 0.2 &amp;#20171;&amp;#32461;"&gt;面向未来的网关：新的 Kubernetes Gateway API 和 Envoy Gateway 0.2 介绍&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;总结&lt;/h2&gt;
 &lt;p&gt;Gateway API 作为下一代 Kubernetes Ingress API，为 Kubernetes 网关供应商提供一定程度上的 API 规范，在保证其可移植性的前提下丰富了入口网关的功能，同时通过关注点分离方便不同觉得对网关进行管理。最后GAMMA 倡议正在促进服务网格的入口网关与 Gateway API 的融合，策略附件可能将 Gateway API 的功能进一步扩展到东西向网关，我们拭目以待。&lt;/p&gt;
 &lt;h2&gt;参考&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://lib.jimmysong.io/kubernetes-handbook/service-discovery/gateway/" rel="noopener" target="_blank" title="Gateway API - lib.jimmysong.io"&gt;Gateway API - lib.jimmysong.io&lt;/a&gt;
&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://atbug.com/explore-k8s-gateway-api-policy-attachment/" rel="noopener" target="_blank" title="&amp;#19968;&amp;#25991;&amp;#25630;&amp;#25026; Kubernetes Gateway API &amp;#30340; Policy Attachment - atbug.com"&gt;一文搞懂 Kubernetes Gateway API 的 Policy Attachment - atbug.com&lt;/a&gt;
&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://atbug.com/why-smi-collaborating-in-gateway-api-gamma/" rel="noopener" target="_blank" title="SMI &amp;#19982; Gateway API &amp;#30340; GAMMA &amp;#20513;&amp;#35758;&amp;#24847;&amp;#21619;&amp;#30528;&amp;#20160;&amp;#20040;&amp;#65311;- atbug.com"&gt;SMI 与 Gateway API 的 GAMMA 倡议意味着什么？- atbug.com&lt;/a&gt;
&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://kccncna19.sched.com/#" rel="noopener" target="_blank" title="Evolving the Kubernetes Ingress APIs to GA and Beyond - Christopher M Luciano, IBM &amp;amp; Bowei Du, Google"&gt;Evolving the Kubernetes Ingress APIs to GA and Beyond - Christopher M Luciano, IBM &amp;amp; Bowei Du, Google&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 />
      <guid isPermaLink="true">https://itindex.net/detail/62482-gateway-api-kubernetes</guid>
      <pubDate>Wed, 02 Nov 2022 11:18:40 CST</pubDate>
    </item>
    <item>
      <title>知识库检索匹配的服务化实践</title>
      <link>https://itindex.net/detail/62477-%E7%9F%A5%E8%AF%86%E5%BA%93-%E6%A3%80%E7%B4%A2-%E5%8C%B9%E9%85%8D</link>
      <description>&lt;h1&gt;一、背景&lt;/h1&gt;

 &lt;img alt="&amp;#30693;&amp;#35782;&amp;#24211;&amp;#26816;&amp;#32034;&amp;#21305;&amp;#37197;&amp;#30340;&amp;#26381;&amp;#21153;&amp;#21270;&amp;#23454;&amp;#36341;" src="https://tech.youzan.com/content/images/2022/09/image-3.png"&gt;&lt;/img&gt; &lt;p&gt;　　知识库是企业经营过程中的面向客户和内部员工的知识沉淀文档库，里面包含各类教程、问答、案例等，知识库的检索匹配是自然语言处理(NLP)中一个重要的基础问题，本质是进行文本语义的相似度计算，也就是语义匹配，我们很多领域的任务都可以抽象为文本匹配检索任务，例如检索引擎、智能客服、知识检索、信息推荐等领域。  &lt;br /&gt;
　　知识库检索匹配可以概述为：给定一个query和大量候选知识库的文档，从这些文档中找出与用户输入query最匹配的TopK个文档。&lt;/p&gt;

 &lt;h1&gt;二、架构流程&lt;/h1&gt;

 &lt;h2&gt;2.1、整体架构&lt;/h2&gt;

 &lt;p&gt;  &lt;img alt="&amp;#30693;&amp;#35782;&amp;#24211;&amp;#26816;&amp;#32034;&amp;#21305;&amp;#37197;&amp;#30340;&amp;#26381;&amp;#21153;&amp;#21270;&amp;#23454;&amp;#36341;" src="https://tech.youzan.com/content/images/2022/09/----.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;h2&gt;2.2、请求链路&lt;/h2&gt;

 &lt;p&gt;  &lt;img alt="&amp;#30693;&amp;#35782;&amp;#24211;&amp;#26816;&amp;#32034;&amp;#21305;&amp;#37197;&amp;#30340;&amp;#26381;&amp;#21153;&amp;#21270;&amp;#23454;&amp;#36341;" src="https://tech.youzan.com/content/images/2022/09/-----1.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;h1&gt;三、算法模型&lt;/h1&gt;

 &lt;h2&gt;3.1、DSL改写&lt;/h2&gt;

 &lt;p&gt;　　检索优化第一步：DSL改写，接手前业务方自己已经对检索结果做过优化，调整不同字段的匹配权重，这一方法的已经难以继续优化。从知识运营的角度出发，在用户检索时，将运营认为重要的文档推到前面，由于文档之间互相有链接引用，可以使用PageRank算法给每个文档计算重要分(PR值)。  &lt;br /&gt;
　　PageRank的核心思想是，被引用次数越多的文档越重要。算法原理如下，假设只有四个网页ABCD，以AB间的箭头为例，代表可以从B网页跳转到A网页，对B即一次引用（链出），对A则一次被引用（链入）。L(B) 表示B网页的链出数量，PR(B)表示B网页的PageRank分数。
  &lt;img alt="&amp;#30693;&amp;#35782;&amp;#24211;&amp;#26816;&amp;#32034;&amp;#21305;&amp;#37197;&amp;#30340;&amp;#26381;&amp;#21153;&amp;#21270;&amp;#23454;&amp;#36341;" src="https://tech.youzan.com/content/images/2022/10/dsl1.png"&gt;&lt;/img&gt;
  &lt;img alt="&amp;#30693;&amp;#35782;&amp;#24211;&amp;#26816;&amp;#32034;&amp;#21305;&amp;#37197;&amp;#30340;&amp;#26381;&amp;#21153;&amp;#21270;&amp;#23454;&amp;#36341;" src="https://tech.youzan.com/content/images/2022/10/dsl2.png"&gt;&lt;/img&gt;
　　假设所有文档的初始PR值是0.25，这里L(B)=2，L(C)=1，L(D)=3，计算出PR(A)=0.458，接下来计算所有其他被引用（有链入）的文档PR值，PageRank是个迭代算法，反复计算以后所有的PR值会收敛，那就是最终每个文档的PR值，也是用来改写DSL的关键信息：  &lt;br /&gt;&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;new score = old score * log(1+2*PageRank)  
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;　　old score指原来不同字段加不同权重由ES算出来的BM25分数，PageRank缺失值使用1代替。&lt;/p&gt;

 &lt;h2&gt;3.２、文本召回&lt;/h2&gt;

 &lt;p&gt;　　文本召回是召回中最常用的一种策略，最常见的方式是通过对Query直接进行分词，然后将分词后的关键词到ES构建倒排索引，进行tf-idf等相似计算匹配索引召回，这种召回方式的优点是实现简单，不需要训练模型、低资源需求、检索速度快，然而它的缺点也很明显，文本是具有语义的、是有语法结构的，文本召回忽略了语句的语法结构，同时也无法解决一词多义和同义词的问题，对 query 进行语义层面相似的召回效果就比较一般，解决这个问题就要用到向量召回。&lt;/p&gt;

 &lt;h2&gt;3.３、向量召回&lt;/h2&gt;

 &lt;p&gt;　　向量召回的思想就是计算搜索词的向量和文档标题/相似问的向量的余弦相似度，返回相似度分数最高的TopK个文档，计算向量相似度的步骤放在Milvus进行，Milvus作为向量检索库，对计算过程有优化，性能更好。  &lt;br /&gt;
　　向量计算模型结构：
  &lt;img alt="&amp;#30693;&amp;#35782;&amp;#24211;&amp;#26816;&amp;#32034;&amp;#21305;&amp;#37197;&amp;#30340;&amp;#26381;&amp;#21153;&amp;#21270;&amp;#23454;&amp;#36341;" src="https://tech.youzan.com/content/images/2022/09/------.png"&gt;&lt;/img&gt;
　　文本转向量的算法模型由embedding、两层transformer和MLP组成，模型最后会对编码向量做L2归一化，采用典型的双塔模式，可以将左塔的搜索词和右塔的文档标题形成独立的子网络，左右塔的结构分离但编码器参数共享，双塔结构天然的可以用于召回，将这个模型部署到小盒子就可以在线计算搜索词的向量，将海量的知识库文档作为右塔离线训练成文本向量后刷入向量检索工具Milvus。  &lt;br /&gt;
　　双塔召回模型的核心思想是将query/item嵌入到共享低维空间，然后通过向量距离来度量相关性。  &lt;br /&gt;
　　向量召回流程：
  &lt;img alt="&amp;#30693;&amp;#35782;&amp;#24211;&amp;#26816;&amp;#32034;&amp;#21305;&amp;#37197;&amp;#30340;&amp;#26381;&amp;#21153;&amp;#21270;&amp;#23454;&amp;#36341;" src="https://tech.youzan.com/content/images/2022/09/------1-1.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;　　工程实现部分的DP离线任务中实现了计算文档标题和相似问向量并将其导入Milvus的功能。由于Milvus对string类型属性信息存储检索不够友好，所以在DB阶段会请求mysql库表对召回结果进行扩展，匹配补全相关信息。  &lt;br /&gt;&lt;/p&gt;

 &lt;h2&gt;3.4、精排序&lt;/h2&gt;

 &lt;p&gt;　　经过召回和粗排后，可以理解为将重要相关的文档排在了前面，但是距离用户真正的检索意图还有差距，可以使用用户的检索记录对结果再进行排序。  &lt;br /&gt;
　　基于所有场景的用户检索点击数据，有点击行为就认为检索词和文档标题匹配（正样本），其他就认为没有那么匹配（负样本）  &lt;br /&gt;
　　训练数据样例：  &lt;br /&gt;
  &lt;img alt="&amp;#30693;&amp;#35782;&amp;#24211;&amp;#26816;&amp;#32034;&amp;#21305;&amp;#37197;&amp;#30340;&amp;#26381;&amp;#21153;&amp;#21270;&amp;#23454;&amp;#36341;" src="https://tech.youzan.com/content/images/2022/10/------.png"&gt;&lt;/img&gt;
　　采用 in_batch 负采样就不需要提前构造负样本，模型的设计如下：
  &lt;img alt="&amp;#30693;&amp;#35782;&amp;#24211;&amp;#26816;&amp;#32034;&amp;#21305;&amp;#37197;&amp;#30340;&amp;#26381;&amp;#21153;&amp;#21270;&amp;#23454;&amp;#36341;" src="https://tech.youzan.com/content/images/2022/10/---1.png"&gt;&lt;/img&gt;
　　检索词与正负样本的相似度会进入InfoNCE（info Noise Contrastive Estimation，噪声对比估计）的函数计算损失，使用这个损失来更新模型参数。这个由很多负样本组成的双塔结构也称为对比学习，核心思想是去拉近相似的样本，推开不相似的样本，目标是要从样本中学习到一个好的语义表示空间。  &lt;br /&gt;
　　精排模型与文本转向量的算法原理相同。  &lt;br /&gt;
　　InfoNCE计算公式：（可以理解为带温度超参的CrossEntropy）  &lt;br /&gt;&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;L_i=-log(\frac{exp(sim(z_i, z_i^+)/\tau)}{\sum_j{exp(sim(z_i, z_j)/\tau)}})  
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;  &lt;img alt="&amp;#30693;&amp;#35782;&amp;#24211;&amp;#26816;&amp;#32034;&amp;#21305;&amp;#37197;&amp;#30340;&amp;#26381;&amp;#21153;&amp;#21270;&amp;#23454;&amp;#36341;" src="https://tech.youzan.com/content/images/2022/10/---2.png"&gt;&lt;/img&gt;
　　分子是正例对的相似度，分母是正例对+所有负例对的相似度，最小化infoNCE loss，就是最大化正例对的相似度，最小化负例对的相似度。  &lt;br /&gt;
　　在计算损失时，label可以在batch内生成，检索词和文档的编码向量经过矩阵乘法可以得到一个相似度方阵，对角位置就是互相匹配的检索词和文档的分数，如果batch  &lt;em&gt;size=4，那每行对应的label就是 [0,1,2,3]。in&lt;/em&gt;batch负采样损失计算示意图：  &lt;br /&gt;
  &lt;img alt="&amp;#30693;&amp;#35782;&amp;#24211;&amp;#26816;&amp;#32034;&amp;#21305;&amp;#37197;&amp;#30340;&amp;#26381;&amp;#21153;&amp;#21270;&amp;#23454;&amp;#36341;" src="https://tech.youzan.com/content/images/2022/10/---3.png"&gt;&lt;/img&gt;
  &lt;img alt="&amp;#30693;&amp;#35782;&amp;#24211;&amp;#26816;&amp;#32034;&amp;#21305;&amp;#37197;&amp;#30340;&amp;#26381;&amp;#21153;&amp;#21270;&amp;#23454;&amp;#36341;" src="https://tech.youzan.com/content/images/2022/10/---4.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;　　模型训练好以后，就得到文本的编码器，输入两个文本，就可以得到一个匹配的分数，将这个模型部署到小盒子，在需要排序时，输入候选的文档标题和检索词，按计算出来的分数从高到低排序，就完成了一次对检索结果的排序。  &lt;br /&gt;&lt;/p&gt;

 &lt;h2&gt;3.5、排序优化&lt;/h2&gt;

 &lt;p&gt;　　上述向量召回介绍的在模型服务中计算两个文本相似度的方法，在只需要对20个文档（一页）排序时是没有问题的，但是每个文档还会有若干个相似问，只使用20个商品标题没法很好的代表整个文档，如果能使用每个文档的标题和全部相似问，那效果会更稳定一些，但这样的话，每次进入小盒子排序的文档数就不固定了，少则20，多则几百，上百的batch_size走小盒子很容易超时，就算切成小的batch，也会有一个失败全部失败的情况，而且总体上rt也没下降多少。  &lt;br /&gt;
　　与向量召回时类似，模型也改为输入只有一个文本，输出这个文本的向量。每个文档的标题和全部相似问向量都与Query向量算相似度后计算均值，等价于先计算文档的标题和全部相似问的向量均值，再与Query向量计算相似度。基于此，排序任务也可以转换为向量召回任务。  &lt;br /&gt;
　　在进入Milvus之前，会按照向量召回和ES召回的文档ID作为过滤条件，一次计算的RT就得以保证，可以支持的QPS也可以比基于小盒子的版本高：  &lt;br /&gt;
  &lt;img alt="&amp;#30693;&amp;#35782;&amp;#24211;&amp;#26816;&amp;#32034;&amp;#21305;&amp;#37197;&amp;#30340;&amp;#26381;&amp;#21153;&amp;#21270;&amp;#23454;&amp;#36341;" src="https://tech.youzan.com/content/images/2022/10/----1.png"&gt;&lt;/img&gt;
　　此方法可以解决RT和QPS问题，但是也有局限，只能优化“每个文档的标题和全部相似问向量都与Query向量算相似度后计算均值”这个均值计算逻辑，其他的比如“取最大的相似度”就不能这么做了，而且Query与文档的交互太少，只在最后算相似度，可能不如多次交互的模型的效果好。  &lt;br /&gt;&lt;/p&gt;

 &lt;h1&gt;四、工程实现&lt;/h1&gt;

 &lt;p&gt;　　当线上接受一条检索请求文本后，先调用在线推理-小盒子计算Query向量，然后去Milvus向量库中和知识库向量进行相似度计算，并返回距离最近的Top N个Item作为向量召回的结果。  &lt;br /&gt;&lt;/p&gt;

 &lt;h2&gt;4.1、离线训练（DP平台）&lt;/h2&gt;

 &lt;p&gt;　　海量的知识语料库向量化计算在自研DP平台离线运行，使得全库文本匹配速度较快：  &lt;br /&gt;
  　　1）语料库预处理：包括语料库的文本清洗、文本筛选等预处理逻辑  &lt;br /&gt;
  　　2）语料库向量化：利用上述的向量计算模型进行向量化  &lt;br /&gt;
  　　3）导入Milvus库：将集合部署在Milvus集群，依次批量导入更新机器的集合保证线上可用  &lt;br /&gt;&lt;/p&gt;

 &lt;h2&gt;4.2、在线推理（Sunfish平台）&lt;/h2&gt;

 &lt;p&gt;　　自研算法平台（Sunfish）对模型训练提供一站式闭环服务，支持分布式训练、GPU/CPU切换、模型版本管理、一键式运行和部署等功能，其中：  &lt;br /&gt;
  　　1）算法工程模块：一键运行训练、任务作业管理、模型输出  &lt;br /&gt;
  　　2）模型管理模块：实现训练任务、地址导入、本地上传等多途径模型来源选择  &lt;br /&gt;
  　　3）模型部署模块：简单配置、模型格式选择、线上资源配置等便捷署方式  &lt;br /&gt;&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;{
    &amp;quot;inputs&amp;quot;:[
        {
            &amp;quot;name&amp;quot;:&amp;quot;INPUT&amp;quot;,
            &amp;quot;shape&amp;quot;:[
                1,
                1
            ],
            &amp;quot;datatype&amp;quot;:&amp;quot;BYTES&amp;quot;,
            &amp;quot;data&amp;quot;:[
                &amp;quot;满足条件满减送没有赠品&amp;quot;
            ]
        }
    ],
    &amp;quot;outputs&amp;quot;:[
        {
            &amp;quot;name&amp;quot;:&amp;quot;OUTPUT&amp;quot;
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;h2&gt;4.3、Milvus向量检索&lt;/h2&gt;

 &lt;p&gt;　　Milvus 是一款开源的、针对海量特征向量的向量相似性检索(ANNS，Approximately nearest neighbor search)引擎，集成了 Faiss、Annoy 等广泛应用的向量索引，成本更低、性能更好、高度灵活、稳定可靠以及高速查询等特点，十亿向量检索仅毫秒响应。  &lt;br /&gt;
　　1、Milvus向量索引列表如下：  &lt;br /&gt;
  &lt;img alt="&amp;#30693;&amp;#35782;&amp;#24211;&amp;#26816;&amp;#32034;&amp;#21305;&amp;#37197;&amp;#30340;&amp;#26381;&amp;#21153;&amp;#21270;&amp;#23454;&amp;#36341;" src="https://tech.youzan.com/content/images/2022/10/milvus1.png"&gt;&lt;/img&gt;
　　简言之，每种索引都有自己的适用场景，如何选择合适的索引可以简单遵循如下原则：  &lt;br /&gt;
  　　1）当查询数据规模小，且需要 100％查询召回率时，用 FLAT；  &lt;br /&gt;
  　　2）当需要高性能查询，且要求召回率尽可能高时，用 IVF_FLAT；  &lt;br /&gt;
  　　3）当需要高性能查询，且磁盘、内存、显存资源有限时，用 IVFSQ8H；  &lt;br /&gt;
  　　4）当需要高性能查询，且磁盘、内存资源有限，且只有 CPU 资源时，用 IVFSQ8。  &lt;br /&gt;&lt;/p&gt;

 &lt;p&gt;　　2、Milvus 目前支持的距离计算方式与数据格式、索引类型之间的兼容关系：
  &lt;img alt="&amp;#30693;&amp;#35782;&amp;#24211;&amp;#26816;&amp;#32034;&amp;#21305;&amp;#37197;&amp;#30340;&amp;#26381;&amp;#21153;&amp;#21270;&amp;#23454;&amp;#36341;" src="https://tech.youzan.com/content/images/2022/10/milvus2-1.png"&gt;&lt;/img&gt;
　　选择合适的距离计算方式比较向量间的距离，能很大程度地提高数据分类和聚类性能，主要采用内积 (IP)的计算方式，内积更适合计算向量的方向。  &lt;br /&gt;
　　内积计算两条向量之间的夹角余弦，并返回相应的点积。内积距离的计算公式为：  &lt;br /&gt;
  &lt;img alt="&amp;#30693;&amp;#35782;&amp;#24211;&amp;#26816;&amp;#32034;&amp;#21305;&amp;#37197;&amp;#30340;&amp;#26381;&amp;#21153;&amp;#21270;&amp;#23454;&amp;#36341;" src="https://tech.youzan.com/content/images/2022/09/--.png"&gt;&lt;/img&gt;
　　假设有 A 和 B 两条向量，则 ||A|| 与 ||B|| 分别代表 A 和 B 归一化后的值。cosθ 代表 A 与 B 之间的余弦夹角。  &lt;br /&gt;
　　在向量归一化之后，内积与余弦相似度等价。因此 Milvus 并没有单独提供余弦相似度作为向量距离计算方式。  &lt;br /&gt;&lt;/p&gt;

 &lt;h2&gt;4.4、AI模型接口服务&lt;/h2&gt;

 &lt;p&gt;　　算法模型接口服务由ai-service和ai-app两个服务组成，ai-service负责调用算法模型在线推理、Milvus实时向量召回等接入库，ai-app负责业务逻辑的开发。  &lt;br /&gt;
　　1、ai-service配置示例：  &lt;br /&gt;&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;{
    &amp;quot;model_name&amp;quot;: &amp;quot;similarity_jira&amp;quot;,
    &amp;quot;model_source_type&amp;quot;: &amp;quot;YZ_MODEL&amp;quot;,
    &amp;quot;model_version&amp;quot;: 1,
    &amp;quot;model_invoke_timeout&amp;quot;: 3000,
    &amp;quot;protocol&amp;quot;: &amp;quot;kfserving&amp;quot;,
    &amp;quot;infer_type&amp;quot;: &amp;quot;triton&amp;quot;,
    &amp;quot;feature_maps&amp;quot;: [
        {
            &amp;quot;model_feature_key&amp;quot;: &amp;quot;INPUT&amp;quot;,
            &amp;quot;data_type&amp;quot;: &amp;quot;string&amp;quot;,
            &amp;quot;shape&amp;quot;: &amp;quot;(-1,1)&amp;quot;,
            &amp;quot;default_value&amp;quot;: &amp;quot;&amp;quot;,
            &amp;quot;feature_source&amp;quot;: &amp;quot;PARAMS&amp;quot;,
            &amp;quot;source_key&amp;quot;: &amp;quot;jira_text&amp;quot;,
            &amp;quot;is_required&amp;quot;: 1
        }
    ],
    &amp;quot;param_mapping&amp;quot;: {
        &amp;quot;jira_text&amp;quot;: &amp;quot;&amp;lt;objectList.jira_text&amp;gt;&amp;quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;　　2、ai-app接口设计  &lt;br /&gt;
　　实现业务逻辑开发测试后，发布上线即可提供前后端调用。  &lt;br /&gt;
　　a、Maven示例:  &lt;br /&gt;&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;&amp;lt;dependency&amp;gt;  
  &amp;lt;groupId&amp;gt;com.youzan&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;ai-app-api&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;1.0.13-RELEASE&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;  
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;　　b、请求示例：  &lt;br /&gt;&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;invoke com.youzan.ai.app.api.service.jira.Service.retrieve({&amp;quot;fromApp&amp;quot;:&amp;quot;test&amp;quot;,&amp;quot;scene&amp;quot;:&amp;quot;similarity_predict&amp;quot;,&amp;quot;Title&amp;quot;:&amp;quot;满足条件没有赠品&amp;quot;,&amp;quot;Key&amp;quot;:&amp;quot;XXX&amp;quot;})  
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;　　c、返回示例：  &lt;br /&gt;&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;{
    &amp;quot;code&amp;quot;:200,
    &amp;quot;data&amp;quot;:{
        &amp;quot;Similaritys&amp;quot;:[
            {
                &amp;quot;createdAt&amp;quot;:1648137600000,
                &amp;quot;score&amp;quot;:0.9390,
                &amp;quot;key&amp;quot;:&amp;quot;XXX0123442334&amp;quot;,
                &amp;quot;title&amp;quot;:&amp;quot;满足条件没有赠品&amp;quot;
            },
            {
                &amp;quot;createdAt&amp;quot;:1636214400000,
                &amp;quot;score&amp;quot;:0.9010,
                &amp;quot;key&amp;quot;:&amp;quot;XXX0123365819&amp;quot;,
                &amp;quot;title&amp;quot;:&amp;quot;满足条件没有送赠品&amp;quot;
            },
            {
                &amp;quot;createdAt&amp;quot;:1653408000000,
                &amp;quot;score&amp;quot;:0.8735,
                &amp;quot;key&amp;quot;:&amp;quot;XXX0123482446&amp;quot;,
                &amp;quot;title&amp;quot;:&amp;quot;订单满足条件没有送赠品&amp;quot;
            },
            {
                &amp;quot;createdAt&amp;quot;:1655308800000,
                &amp;quot;score&amp;quot;:0.8312,
                &amp;quot;key&amp;quot;:&amp;quot;XXX0123496337&amp;quot;,
                &amp;quot;title&amp;quot;:&amp;quot;订单满足条件但是没有送赠品&amp;quot;
            },
            {
                &amp;quot;createdAt&amp;quot;:1659628800000,
                &amp;quot;score&amp;quot;:0.8028,
                &amp;quot;key&amp;quot;:&amp;quot;XXX0123527965&amp;quot;,
                &amp;quot;title&amp;quot;:&amp;quot;订单满条件但是赠品没有送&amp;quot;
            }
        ]
    },
    &amp;quot;success&amp;quot;:true,
    &amp;quot;message&amp;quot;:&amp;quot;successful&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;h1&gt;五、服务场景&lt;/h1&gt;

 &lt;h2&gt;5.1、官网帮助中心&lt;/h2&gt;

 &lt;p&gt;  &lt;img alt="&amp;#30693;&amp;#35782;&amp;#24211;&amp;#26816;&amp;#32034;&amp;#21305;&amp;#37197;&amp;#30340;&amp;#26381;&amp;#21153;&amp;#21270;&amp;#23454;&amp;#36341;" src="https://tech.youzan.com/content/images/2022/09/-------1.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;h2&gt;5.2、相似商品推荐&lt;/h2&gt;

 &lt;p&gt;  &lt;img alt="&amp;#30693;&amp;#35782;&amp;#24211;&amp;#26816;&amp;#32034;&amp;#21305;&amp;#37197;&amp;#30340;&amp;#26381;&amp;#21153;&amp;#21270;&amp;#23454;&amp;#36341;" src="https://tech.youzan.com/content/images/2022/09/-------2.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;h1&gt;六、总结&lt;/h1&gt;

 &lt;p&gt;　　本文大致介绍了知识库检索匹配的算法和工程服务化实践过程，服务应用的业务场景比较广泛，并对类似场景的接入做了低代码封装和策略配置，便于快速轻量级的服务化落地。在知识库检索匹配服务化实践过程中，后续值得关注以下几点：  &lt;br /&gt;
　　1）对于知识库中低频或缺失的新文档或新商品的Embedding学习还不够充分，可考虑利用图结算法结构，把更多query和其他属性的语义信息聚合，进而提升query和知识库文档的语义表征能力；  &lt;br /&gt;
　　2）Embedding 的实现算法比较多，可考虑结合业务需要，将词嵌入模型模块内置化；  &lt;br /&gt;
　　3）服务的性能和稳定性，确保服务在高准确率和高QPS下的响应性能依然是重中之重。&lt;/p&gt;

 &lt;h1&gt;七、招聘号外&lt;/h1&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/62477-%E7%9F%A5%E8%AF%86%E5%BA%93-%E6%A3%80%E7%B4%A2-%E5%8C%B9%E9%85%8D</guid>
      <pubDate>Fri, 28 Oct 2022 15:02:54 CST</pubDate>
    </item>
    <item>
      <title>优化 Kubernetes 中的 Java 无服务器函数</title>
      <link>https://itindex.net/detail/62472-%E4%BC%98%E5%8C%96-kubernetes-java</link>
      <description>&lt;blockquote&gt;
  &lt;p&gt;在 Kubernetes 中运行无服务器函数时，实现更快的启动速度和更小的内存占用。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img height="720" src="https://img.linux.net.cn/data/attachment/album/202210/26/151603a4a44w1a71zk8b11.jpg" title="Ship captain sailing the Kubernetes seas" width="1280"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;由于运行上千个应用程序容器荚Pod所耗费的资源多，令它实现较少工作节点和资源占用所需成本也较高，所以在使用   &lt;a href="https://opensource.com/article/19/6/reasons-kubernetes"&gt;Kubernetes&lt;/a&gt; 时，快速启动和较少的内存占用是至关重要的。在 Kubernetes 平台运行容器化微服务时，内存占用是比吞吐量更重要的考量因素，这是因为：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;由于需要持续运行，所以耗费资源更多（不同于 CPU 占用）&lt;/li&gt;
  &lt;li&gt;微服务令开销成本成倍增加&lt;/li&gt;
  &lt;li&gt;一个单体应用程序变为若干个微服务的情况（例如 20 个微服务占用的存储空间约有 20GB）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;这些情况极大影响了无服务器函数的发展和 Java 部署模型。到目前为止，许多企业开发人员选择 Go、Python 或 Node.js 这些替代方案来解决性能瓶颈，直到出现了   &lt;a href="https://quarkus.io/"&gt;Quarkus&lt;/a&gt; 这种基于 kubernetes 的原生 Java 堆栈，才有所改观。本文介绍如何在使用了 Quarkus 的 kubernetes 平台上进行性能优化，以便运行无服务器函数。&lt;/p&gt;
 &lt;h3&gt;容器优先的设计理念&lt;/h3&gt;
 &lt;p&gt;由于 Java 生态系统中传统的框架都要进行框架的初始化，包括配置文件的处理、  &lt;code&gt;classpath&lt;/code&gt; 的扫描、类加载、注解的处理以及构建元模型，这些过程都是必不可少的，所以它们都比较耗费资源。如果使用了几种不同的框架，所耗费的资源也是成倍增加。&lt;/p&gt;
 &lt;p&gt;Quarkus 通过“左移shifting left”，把所有的资源开销大的操作都转移到构建阶段，解决了这些 Java 性能问题。在构建阶段进行代码和框架分析、字节码转换和动态元模型生成，而且只有一次，结果是：运行时可执行文件经过高度优化，启动非常快，不需要经过那些传统的启动过程，全过程只在构建阶段执行一次。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Quarkus Build phase" src="https://img.linux.net.cn/data/attachment/album/202210/26/151634r3l3gzbp27bkke0f.png" title="Quarkus Build phase"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;更重要的是：Quarkus 支持构建原生可执行文件，它具有良好性能，包括快速启动和极小的驻留集大小resident set size（RSS）内存占用，跟传统的云原生 Java 栈相比，具备即时扩展的能力和高密度的内存利用。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Quarkus RSS and Boot Time Metrics" src="https://img.linux.net.cn/data/attachment/album/202210/26/151635p95d33596ilju4rd.png" title="Quarkus RSS and Boot Time Metrics"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这里有个例子，展示如何使用 Quarkus 将一个   &lt;a href="https://opensource.com/article/21/5/what-serverless-java"&gt;Java 无服务器&lt;/a&gt; 项目构建为本地可执行文件。&lt;/p&gt;
 &lt;h3&gt;1、使用 Quarkus 创建无服务器 Maven 项目&lt;/h3&gt;
 &lt;p&gt;以下命令生成一个 Quarkus 项目，（例如   &lt;code&gt;quarkus-serverless-native&lt;/code&gt;）以此创建一个简单的函数：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;$ mvn io.quarkus:quarkus-maven-plugin:1.13.4.Final:create \
       -DprojectGroupId=org.acme \
       -DprojectArtifactId=quarkus-serverless-native \
       -DclassName=&amp;quot;org.acme.getting.started.GreetingResource&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
 &lt;h3&gt;2、构建一个本地可执行文件&lt;/h3&gt;
 &lt;p&gt;你需要使用 GraalVM 为 Java 程序构建一个本地可执行文件。你可以选择 GraalVM 的任何发行版，例如   &lt;a href="https://www.graalvm.org/community/"&gt;Oracle GraalVM Community Edition (CE)&lt;/a&gt; 或   &lt;a href="https://github.com/graalvm/mandrel"&gt;Mandrel&lt;/a&gt;（Oracle GraalVM CE 的下游发行版）。Mandrel 是为支持 OpenJDK 11 上的 Quarkus-native 可执行文件的构建而设计的。&lt;/p&gt;
 &lt;p&gt;打开   &lt;code&gt;pom.xml&lt;/code&gt;，你将发现其中的   &lt;code&gt;native&lt;/code&gt; 设置。你将使用它来构建本地可执行文件。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;profiles&amp;gt;
    &amp;lt;profile&amp;gt;
        &amp;lt;id&amp;gt;native&amp;lt;/id&amp;gt;
        &amp;lt;properties&amp;gt;
            &amp;lt;quarkus.package.type&amp;gt;native&amp;lt;/quarkus.package.type&amp;gt;
        &amp;lt;/properties&amp;gt;
    &amp;lt;/profile&amp;gt;
&amp;lt;/profiles&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
 &lt;blockquote&gt;
  &lt;p&gt;   &lt;strong&gt;注意：&lt;/strong&gt; 你可以在本地安装 GraalVM 或 Mandrel 发行版。你也可以下载 Mandrel 容器映像来构建它（像我那样），因此你还需要在本地运行一个容器引擎（例如 Docker）。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;假设你已经打开了容器运行时，此时需要运行一下 Maven 命令：&lt;/p&gt;
 &lt;p&gt;使用   &lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt; 作为容器引擎：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;$ ./mvnw package -Pnative \
  -Dquarkus.native.container-build=true \
  -Dquarkus.native.container-runtime=docker
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;使用   &lt;a href="https://podman.io/"&gt;Podman&lt;/a&gt; 作为容器引擎：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;$ ./mvnw package -Pnative \
  -Dquarkus.native.container-build=true \
  -Dquarkus.native.container-runtime=podman
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;输出信息结尾应当是   &lt;code&gt;BUILD SUCCESS&lt;/code&gt;。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Native Build Logs" src="https://img.linux.net.cn/data/attachment/album/202210/26/151635iuy2m9l5zul5zu5i.png" title="Native Build Logs"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;不借助 JVM 直接运行本地可执行文件：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;$ target/quarkus-serverless-native-1.0.0-SNAPSHOT-runner
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;输出信息类似于：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,&amp;lt; / /_/ /\ \  
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/  
INFO  [io.quarkus] (main) quarkus-serverless-native 1.0.0-SNAPSHOT native
(powered by Quarkus xx.xx.xx.) Started in 0.019s. Listening on: http://0.0.0.0:8080
INFO [io.quarkus] (main) Profile prod activated.
INFO [io.quarkus] (main) Installed features: [cdi, kubernetes, resteasy]
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;简直是超音速！启动只花了 19 毫秒。你的运行时间可能稍有不同。&lt;/p&gt;
 &lt;p&gt;使用 Linux 的   &lt;code&gt;ps&lt;/code&gt; 工具检测一下，结果内存占用还是很低。检测的方法是：在应用程序运行期间，另外打开一个终端，运行如下命令：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;$ ps -o pid,rss,command -p $(pgrep -f runner)
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;输出结果类似于：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;  PID    RSS COMMAND
10246  11360 target/quarkus-serverless-native-1.0.0-SNAPSHOT-runner
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;该进程只占 11MB 内存。非常小！&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;   &lt;strong&gt;注意:&lt;/strong&gt; 各种应用程序（包括 Quarkus）的驻留集大小和内存占用，都因运行环境而异，并随着应用程序载入而上升。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;你也可以使用 REST API 访问这个函数。输出结果应该是   &lt;code&gt;Hello RESTEasy&lt;/code&gt;:&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;$ curl localhost:8080/hello
Hello RESTEasy
&lt;/code&gt;&lt;/pre&gt;
 &lt;h3&gt;3、把函数部署到 Knative 服务&lt;/h3&gt;
 &lt;p&gt;如果你还没有创建命名空间，现在就在   &lt;a href="https://docs.okd.io/latest/welcome/index.html"&gt;OKD&lt;/a&gt;（OpenShift Kubernetes 发行版）  &lt;a href="https://docs.okd.io/latest/applications/projects/configuring-project-creation.html"&gt;创建一个命名空间&lt;/a&gt;（例如   &lt;code&gt;quarkus-serverless-native&lt;/code&gt;），进而把这个本地可执行文件部署为无服务器函数。然后添加   &lt;code&gt;quarkus-openshift&lt;/code&gt; 扩展：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;$ ./mvnw -q quarkus:add-extension -Dextensions=&amp;quot;openshift&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;向   &lt;code&gt;src/main/resources/application.properties&lt;/code&gt; 文件中添加以下内容，配置 Knative 和 Kubernetes 的相关资源：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;quarkus.container-image.group=quarkus-serverless-native
quarkus.container-image.registry=image-registry.openshift-image-registry.svc:5000
quarkus.native.container-build=true
quarkus.kubernetes-client.trust-certs=true
quarkus.kubernetes.deployment-target=knative
quarkus.kubernetes.deploy=true
quarkus.openshift.build-strategy=docker
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;构建本地可执行文件，并把它直接部署到 OKD 集群：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;$ ./mvnw clean package -Pnative
&lt;/code&gt;&lt;/pre&gt;
 &lt;blockquote&gt;
  &lt;p&gt;   &lt;strong&gt;注意：&lt;/strong&gt; 提前使用    &lt;code&gt;oc login&lt;/code&gt; 命令，确保登录的是正确的项目（例如    &lt;code&gt;quarkus-serverless-native&lt;/code&gt;）。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;输出信息结尾应当是   &lt;code&gt;BUILD SUCCESS&lt;/code&gt;。完成一个本地二进制文件的构建并部署为 Knative 服务需要花费几分钟。成功创建服务后，使用   &lt;code&gt;kubectl&lt;/code&gt; 或   &lt;code&gt;oc&lt;/code&gt; 命令工具，可以查看 Knative 服务和版本信息：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;$ kubectl get ksvc
NAME                        URL   [...]
quarkus-serverless-native   http://quarkus-serverless-native-[...].SUBDOMAIN  True

$ kubectl get rev
NAME                              CONFIG NAME                 K8S SERVICE NAME                  GENERATION   READY   REASON
quarkus-serverless-native-00001   quarkus-serverless-native   quarkus-serverless-native-00001   1            True
&lt;/code&gt;&lt;/pre&gt;
 &lt;h3&gt;4、访问本地可执行函数&lt;/h3&gt;
 &lt;p&gt;运行   &lt;code&gt;kubectl&lt;/code&gt; 命令，搜索无服务器函数的节点：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;$ kubectl get rt/quarkus-serverless-native
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;输出信息类似于：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;NAME                         URL                                                                                                          READY   REASON
quarkus-serverless-native   http://quarkus-serverless-restapi-quarkus-serverless-native.SUBDOMAIN   True
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;用   &lt;code&gt;curl&lt;/code&gt; 命令访问上述信息中的   &lt;code&gt;URL&lt;/code&gt; 字段：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;$ curl http://quarkus-serverless-restapi-quarkus-serverless-native.SUBDOMAIN/hello
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;过了不超过一秒钟，你也会得到跟本地操作一样的结果：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;Hello RESTEasy
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;当你在 OKD 群集中访问 Quarkus 运行中的节点的日志，你会发现本地可执行文件正在以 Knative 服务的形式运行。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Native Quarkus Log" src="https://img.linux.net.cn/data/attachment/album/202210/26/151635hrov9o2fwvfwlrwl.png" title="Native Quarkus Log"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;下一步呢？&lt;/h3&gt;
 &lt;p&gt;你可以借助 GraalVM 发行版优化 Java 无服务器函数，从而在 Knative 中使用 Kubernetes 将它们部署为无服务器函数。Quarkus 支持在普通的微服务中使用简易配置进行性能优化。&lt;/p&gt;
 &lt;p&gt;本系列的下一篇文章将指导你在不更改代码的情况下跨多个无服务器平台实现可移植函数。&lt;/p&gt;
 &lt;p&gt;  &lt;em&gt;(Daniel Oh,    &lt;a href="https://creativecommons.org/licenses/by-sa/4.0/"&gt;CC BY-SA 4.0&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;via:   &lt;a href="https://opensource.com/article/21/6/java-serverless-functions-kubernetes"&gt;https://opensource.com/article/21/6/java-serverless-functions-kubernetes&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;作者：  &lt;a href="https://opensource.com/users/daniel-oh"&gt;Daniel Oh&lt;/a&gt; 选题：  &lt;a href="https://github.com/lujun9972"&gt;lujun9972&lt;/a&gt; 译者：  &lt;a href="https://github.com/cool-summer-021"&gt;cool-summer-021&lt;/a&gt; 校对：  &lt;a href="https://github.com/wxy"&gt;wxy&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;本文由   &lt;a href="https://github.com/LCTT/TranslateProject"&gt;LCTT&lt;/a&gt; 原创编译，  &lt;a href="https://linux.cn/"&gt;Linux中国&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 />
      <guid isPermaLink="true">https://itindex.net/detail/62472-%E4%BC%98%E5%8C%96-kubernetes-java</guid>
      <pubDate>Wed, 26 Oct 2022 15:16:34 CST</pubDate>
    </item>
    <item>
      <title>微服务之间的最佳调用方式 - Java学习之道</title>
      <link>https://itindex.net/detail/62463-%E5%BE%AE%E6%9C%8D%E5%8A%A1-java-%E5%AD%A6%E4%B9%A0</link>
      <description>&lt;div&gt;    &lt;blockquote&gt;      &lt;p&gt;原文链接：        &lt;a href="https://blog.csdn.net/weixin_38748858/article/details/101062272" target="_blank"&gt;https://blog.csdn.net/weixin_38748858/article/details/101062272&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;    &lt;h2&gt;序&lt;/h2&gt;    &lt;p&gt;在微服务架构中，需要调用很多服务才能完成一项功能。服务之间如何互相调用就变成微服务架构中的一个关键问题。服务调用有两种方式，一种是RPC方式，另一种是事件驱动（Event-driven）方式，也就是发消息方式。消息方式是松耦合方式，比紧耦合的RPC方式要优越，但RPC方式如果用在适合的场景也有它的一席之地.&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;耦合的种类：&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;我们总在谈耦合，那么耦合到底意味着什么呢？&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;ol&gt;          &lt;li&gt;时间耦合：客户端和服务端必须同时上线才能工作。发消息时，接受消息队列必须运行，但后台处理程序暂时不工作也不影响。&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;      &lt;li&gt;        &lt;ol start="2"&gt;          &lt;li&gt;容量耦合：客户端和服务端的处理容量必须匹配。发消息时，如果后台处理能力不足也不要紧，消息队列会起到缓冲的作用。&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;      &lt;li&gt;        &lt;ol start="3"&gt;          &lt;li&gt;接口耦合：RPC调用有函数标签，而消息队列只是一个消息。例如买了商品之后要调用发货服务，如果是发消息，那么就只需发送一个商品被买消息。&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;      &lt;li&gt;        &lt;ol start="4"&gt;          &lt;li&gt;发送方式耦合：RPC是点对点方式，需要知道对方是谁，它的好处是能够传回返回值。消息既可以点对点，也可以用广播的方式，这样减少了耦合，但也使返回值比较困难。&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;下面我们来逐一分析这些耦合的影响。&lt;/p&gt;    &lt;p&gt;第一，时间耦合，对于多数应用来讲，你希望能马上得到回答，因此即使使用消息队列，后台也需要一直工作。&lt;/p&gt;    &lt;p&gt;第二，容量耦合，如果你对回复有时间要求，那么消息队列的缓冲功能作用不大，因为你希望及时响应。真正需要的是自动伸缩（Auto-scaling），它能自动调整服务端处理能力去匹配请求数量。第三和第四，接口耦合和发送方式耦合，这两个确实是RPC方式的软肋。&lt;/p&gt;    &lt;h2&gt;事件驱动（Event-Driven）方式：&lt;/h2&gt;    &lt;p&gt;Martin Fowler把事件驱动分成四种方式(What do you mean by “Event-Driven”)，简化之后本质上只有两种方式。&lt;/p&gt;    &lt;p&gt;一种就是我们熟悉的的事件通知（Event Notification），另一种是事件溯源（Event Sourcing）。&lt;/p&gt;    &lt;p&gt;事件通知就是微服务之间不直接调用，而是通过发消息来进行合作。事件溯源有点像记账，它把所有的事件都记录下来，作为永久存储层，再在它的基础之上构建应用程序。实际上从应用的角度来讲，它们并不应该分属一类，它们的用途完全不同。事件通知是微服务的调用（或集成）方式，应该和RPC分在一起。事件溯源是一种存储数据的方式，应该和数据库分在一起。&lt;/p&gt;    &lt;h3&gt;事件通知（Event Notification）：&lt;/h3&gt;    &lt;p&gt;让我们用具体的例子来看一下。在下面的例子中，有三个微服务，“Order Service”， “Customer Service” 和“Product Service”.&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="https://b3logfile.com/file/2022/10/solo-fetchupload-7767375754900095213-AImfIoV.jpeg?imageView2/2/w/1280/format/jpg/interlace/1/q/100"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;先说读数据，假设要创建一个“Order”，在这个过程中需要读取“Customer”的数据和“Product”数据。如果用事件通知的方式就只能在“Order      &lt;br /&gt;Service”本地也创建只读“Customer”和“Product”表，并把数据用消息的方式同步过来。&lt;/p&gt;    &lt;p&gt;再说写数据，如果在创建一个“Order”时需要创建一个新的“Customer”或要修改“Customer”的信息，那么可以在界面上跳转到用户创建页面，然后在“Customer      &lt;br /&gt;Service”创建用户之后再发”用户已创建“的消息，“Order Service”接到消息，更新本地“Customer”表。&lt;/p&gt;    &lt;p&gt;这并不是一个很好的使用事件驱动的例子，因为事件驱动的优点就是不同的程序之间可以独立运行，没有绑定关系。但现在“Order      &lt;br /&gt;Service”需要等待“Customer      &lt;br /&gt;Service”创建完了之后才能继续运行，来完成整个创建“Order”的工作。主要是因为“Order”和“Customer”本身从逻辑上来讲就是紧耦合关系，没有“Customer”你是不能创建“Order”的。&lt;/p&gt;    &lt;p&gt;在这种紧耦合的情况下，也可以使用RPC。你可以建立一个更高层级的管理程序来管理这些微服务之间的调用，这样“Order      &lt;br /&gt;Service”就不必直接调用“Customer      &lt;br /&gt;Service”了。当然它从本质上来讲并没有解除耦合，只是把耦合转移到了上一层，但至少现在“order Service”和“Customer      &lt;br /&gt;Service”可以互不影响了。之所以不能根除这种紧耦合关系是因为它们在业务上是紧耦合的。&lt;/p&gt;    &lt;p&gt;再举一个购物的例子。用户选好商品之后进行“Checkout”，生成“Order”，然后需要“payment”，再从“Inventory”取货，最后由“Shipment”发货，它们每一个都是微服务。这个例子用RPC方式和事件通知方式都可以完成。当用RPC方式时，由“Order”服务调用其他几个服务来完成整个功能。用事件通知方式时，“Checkout”服务完成之后发送“Order Placed”消息，“Payment”服务收到消息，接收用户付款，发送“Payment received”消息。“Inventory”服务收到消息，从仓库里取货，并发送“Goods fetched”消息。“Shipment”服务得到消息，发送货物，并发送“Goods shipped”消息。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="file" src="https://b3logfile.com/file/2022/10/solo-fetchupload-1301955951371960502-K0YIyFf.jpeg?imageView2/2/w/1280/format/jpg/interlace/1/q/100"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;a href="https://blog.bernd-ruecker.com/the-microservice-workflow-automation-cheat-sheet-fc0a80dc25aa" target="_blank"&gt;图片来源&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;对这个例子来讲，使用事件驱动是一个不错的选择，因为每个服务发消息之后它不需要任何反馈，这个消息由下一个模块接收来完成下一步动作，时间上的要求也比上一个要宽松。用事件驱动的好处是降低了耦合度，坏处是你现在不能在程序里找到整个购物过程的步骤。如果一个业务逻辑有它自己相对固定的流程和步骤，那么使用RPC或业务流程管理（BPM）能够更方便地管理这些流程。在这种情况下选哪种方案呢？在我看来好处和坏处是大致相当的。从技术上来讲要选事件驱动，从业务上来讲要选RPC。不过现在越来越多的人采用事件通知作为微服务的集成方式，它似乎已经成了微服务之间的标椎调用方式。&lt;/p&gt;    &lt;h3&gt;事件溯源(Event Sourcing)：&lt;/h3&gt;    &lt;p&gt;这是一种具有颠覆性质的的设计，它把系统中所有的数据都以事件（Event）的方式记录下来，它的持久存储叫Event Store，      &lt;br /&gt;一般是建立在数据库或消息队列（例如Kafka）基础之上，并提供了对事件进行操作的接口，例如事件的读写和查询。事件溯源是由领域驱动设计(Domain-Driven Design)提出来的。DDD中有一个很重要的概念，有界上下文（Bounded Context），可以用有界上下文来划分微服务，每个有界上下文都可以是一个微服务。      &lt;br /&gt;下面是有界上下文的示例。下图中有两个服务“Sales”和“Support”。有界上下文的一个关键是如何处理共享成员，      &lt;br /&gt;在图中是“Customer”和“Product”。在不同的有界上下文中，共享成员的含义、用法以及他们的对象属性都会有些不同，DDD建议这些共享成员在各自的有界上下文中都分别建自己的类（包括数据库表），而不是共享。可以通过数据同步的手段来保持数据的一致性。下面还会详细讲解。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="file" src="https://b3logfile.com/file/2022/10/solo-fetchupload-2476952831403201417-XUGt845.jpeg?imageView2/2/w/1280/format/jpg/interlace/1/q/100"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;事件溯源是微服务的一种存储方式，它是微服务的内部实现细节。因此你可以决定哪些微服务采用事件溯源方式，哪些不采用，而不必所有的服务都变成事件溯源的。      &lt;br /&gt;通常整个应用程序只有一个Event Store， 不同的微服务都通过向Event Store发送和接受消息而互相通信。Event      &lt;br /&gt;Store内部可以分成不同的stream（相当于消息队列中的Topic）， 供不同的微服务中的领域实体（Domain Entity）使用。&lt;/p&gt;    &lt;p&gt;事件溯源的一个短板是数据查询，它有两种方式来解决。第一种是直接对stream进行查询，这只适合stream比较小并且查询比较简单的情况。查询复杂的话，就要采用第二种方式，那就是建立一个只读数据库，把需要的数据放在库中进行查询。数据库中的数据通过监听Event      &lt;br /&gt;Store中相关的事件来更新。&lt;/p&gt;    &lt;p&gt;数据库存储方式只能保存当前状态，而事件溯源则存储了所有的历史状态，因而能根据需要回放到历史上任何一点的状态，具有很大优势。但它也不是一点问题都没有。第一，它的程序比较复杂，因为事件是一等公民，你必须把业务逻辑按照事件的方式整理出来，然后用事件来驱动程序。第二，如果你要想修改事件或事件的格式就比较麻烦，因为旧的事件已经存储在Event      &lt;br /&gt;Store里了（事件就像日志，是只读的），没有办法再改。&lt;/p&gt;    &lt;p&gt;由于事件溯源和事件通知表面上看起来很像，不少人都搞不清楚它们的区别。事件通知只是微服务的集成方式，程序内部是不使用事件溯源的，内部实现仍然是传统的数据库方式。只有当要与其他微服务集成时才会发消息。而在事件溯源中，事件是一等公民，可以不要数据库，全部数据都是按照事件的方式存储的。&lt;/p&gt;    &lt;p&gt;虽然事件溯源的践行者有不同的意见，但有不少人都认为事件溯源不是微服务的集成方式，而是微服务的一种内部实现方式。因此，在一个系统中，可以某些微服务用事件溯源，另外一些微服务用数据库。当你要集成这些微服务时，你可以用事件通知的方式。注意现在有两种不同的事件需要区分开，一种是微服务的内部事件，是颗粒度比较细的，这种事件只发送到这个微服务的stream中，只被事件溯源使用。另一种是其他微服务也关心的，是颗粒度比较粗的，这种事件会放到另外一个或几个stream中，被多个微服务使用，是用来做服务之间集成的。这样做的好处是限制了事件的作用范围，减少了不相关事件对程序的干扰。详见&amp;quot;      &lt;a href="https://www.innoq.com/en/blog/domain-events-versus-event-sourcing/" target="_blank"&gt;Domain Events vs. Event Sourcing&lt;/a&gt;&amp;quot;.&lt;/p&gt;    &lt;p&gt;事件溯源出现已经很长时间了，虽然热度一直在上升（尤其是这两年），但总的来说非常缓慢，谈论的人不少，但生产环境使用的不多。究其原因就是应为它对现在的体系结构颠覆太大，需要更改数据存储结构和程序的工作方式，还是有一定风险的。另外，微服务已经形成了一整套体系，从程序部署，服务发现与注册，到监控，服务韧性（Service&lt;/p&gt;    &lt;p&gt;Resilience），它们基本上都是针对RPC的，虽然也支持消息，但成熟度就差多了，因此有不少工作还是要自己来做。有意思的是Kafka一直在推动它作为事件驱动的工具，也取得了很大的成功。但它却没有得到事件溯源圈内的认可（详见      &lt;a href="https://stackoverflow.com/a/49868866" target="_blank"&gt;这里&lt;/a&gt;）。&lt;/p&gt;    &lt;p&gt;多数事件溯源都使用一个叫      &lt;a href="https://eventstore.org/" target="_blank"&gt;evenstore&lt;/a&gt;的开源Event Store，或是基于某个数据库的Event Store，只有比较少的人用Kafka做Event Store。&lt;/p&gt;    &lt;p&gt;但如果用Kafka实现事件通知就一点问题都没有。总的来说，对大多数公司来讲事件溯源是有一定挑战的，应用时需要找到合适的场景。如果你要尝试的话，可以先拿一个微服务试水。&lt;/p&gt;    &lt;p&gt;虽然现在事件驱动还有些生涩，但从长远来讲，还是很看好它的。像其他全新的技术一样，事件溯源需要大规模的适用场景来推动。例如容器技术就是因为微服务的流行和推动，才走向主流。事件溯源以前的适用场景只限于记账和源代码库，局限性较大。区块链可能会成为它的下一个机遇，因为它用的也是事件溯源技术。另外AI今后会渗入到具体程序中，使程序具有学习功能。而RPC模式注定没有自适应功能。事件驱动本身就具有对事件进行反应的能力，这是自我学习的基础。因此，这项技术长远来讲定会大放异彩，但短期内（3-5年）大概不会成为主流。&lt;/p&gt;    &lt;h2&gt;RPC方式：&lt;/h2&gt;    &lt;p&gt;RPC的方式就是远程函数调用，像RESTFul，gRPC, DUBBO 都是这种方式。它一般是同步的，可以马上得到结果。在实际中，大多数应用都要求立刻得到结果，这时同步方式更有优势，代码也更简单。&lt;/p&gt;    &lt;h3&gt;服务网关（API Gateway）:&lt;/h3&gt;    &lt;p&gt;熟悉微服务的人可能都知道服务网关（API      &lt;br /&gt;Gateway）。当UI需要调用很多微服务时，它需要了解每个服务的接口，这个工作量很大。于是就用服务网关创建了一个Facade，把几个微服务封装起来，这样UI就只调用服务网关就可以了，不需要去对付每一个微服务。下面是API      &lt;br /&gt;Gateway示例图：&lt;/p&gt;    &lt;p&gt;      &lt;img alt="file" src="https://b3logfile.com/file/2022/10/solo-fetchupload-12616951294764548813-zjIVLLp.jpeg?imageView2/2/w/1280/format/jpg/interlace/1/q/100"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;服务网关（API      &lt;br /&gt;Gateway）不是为了解决微服务之间调用的紧耦合问题，它主要是为了简化客户端的工作。其实它还可以用来降低函数之间的耦合度。 有了API      &lt;br /&gt;Gateway之后，一旦服务接口修改，你可能只需要修改API Gateway， 而不必修改每个调用这个函数的客户端，这样就减少了程序的耦合性。&lt;/p&gt;    &lt;h3&gt;服务调用：&lt;/h3&gt;    &lt;p&gt;可以借鉴API Gateway的思路来减少RPC调用的耦合度，例如把多个微服务组织起来形成一个完整功能的服务组合，并对外提供统一的服务接口。这种想法跟上面的API Gateway有些相似，都是把服务集中起来提供粗颗粒（Coarse Granular）服务，而不是细颗粒的服务（Fine      &lt;br /&gt;Granular）。但这样建立的服务组合可能只适合一个程序使用，没有多少共享价值。因此如果有合适的场景就采用，否侧也不必强求。虽然我们不能降低RPC服务之间的耦合度，却可以减少这种紧耦合带来的影响。&lt;/p&gt;    &lt;h2&gt;降低紧耦合的影响：&lt;/h2&gt;    &lt;p&gt;什么是紧耦合的主要问题呢？就是客户端和服务端的升级不同步。服务端总是先升级，客户端可能有很多，如果要求它们同时升级是不现实的。它们有各自的部署时间表，一般都会选择在下一次部署时顺带升级。&lt;/p&gt;    &lt;p&gt;一般有两个办法可以解决这个问题：&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;同时支持多个版本：这个工作量比较大，因此大多数公司都不会采用这种方式。&lt;/li&gt;      &lt;li&gt;服务端向后兼容：这是更通用的方式。例如你要加一个新功能或有些客户要求给原来的函数增加一个新的参数，但别的客户不需要这个参数。这时你只好新建一个函数，跟原来的功能差不多，只是多了一个参数。这样新旧客户的需求都能满足。它的好处是向后兼容（当然这取决于你使用的协议）。它的坏处是当以后新的客户来了，看到两个差不多的函数就糊涂了，不知道该用那个。而且时间越长越严重，你的服务端可能功能增加的不多，但相似的函数却越来越多，无法选择。&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;它的解决办法就是使用一个支持向后兼容的RPC协议，现在最好的就是Protobuf      &lt;br /&gt;gRPC，尤其是在向后兼容上。它给每个服务定义了一个接口，这个接口是与编程语言无关的中性接口，然后你可以用工具生成各个语言的实现代码，供不同语言使用。函数定义的变量都有编号，变量可以是可选类型的，这样就比较好地解决了函数兼容的问题。就用上面的例子，当你要增加一个可选参数时，你就定义一个新的可选变量。由于它是可选的，原来的客户端不需要提供这个参数，因此不需要修改程序。而新的客户端可以提供这个参数。你只要在服务端能同时处理这两种情况就行了。这样服务端并没有增加新的函数，但用户的新需求满足了，而且还是向后兼容的。&lt;/p&gt;    &lt;h2&gt;微服务的数量有没有上限？&lt;/h2&gt;    &lt;p&gt;总的来说微服务的数量不要太多，不然会有比较重的运维负担。有一点需要明确的是微服务的流行不是因为技术上的创新，而是为了满足管理上的需要。单体程序大了之后，各个模块的部署时间要求不同，对服务器的优化要求也不同，而且团队人数众多，很难协调管理。把程序拆分成微服务之后，每个团队负责几个服务，就容易管理了，而且每个团队也可以按照自己的节奏进行创新，但它给运维带来了巨大的麻烦。所以在微服务刚出来时，我一直觉得它是一个退步，弊大于利。但由于管理上的问题没有其他解决方案，只有硬着头皮上了。值得庆幸的是微服务带来的麻烦都是可解的。直到后来，微服务建立了全套的自动化体系，从程序集成到部署，从全链路跟踪到日志，以及服务检测，服务发现和注册，这样才把微服务的工作量降了下来。虽然微服务在技术上一无是处，但它的流行还是大大推动了容器技术，服务网格（Service&lt;/p&gt;    &lt;p&gt;Mesh）和全链路跟踪等新技术的发展。不过它本身在技术上还是没有发现任何优势。。直到有一天，我意识到单体程序其实性能调试是很困难的（很难分离出瓶颈点），而微服务配置了全链路跟踪之后，能很快找到症结所在。看来微服务从技术来讲也不全是缺点，总算也有好的地方。但微服务的颗粒度不宜过细，否则工作量还是太大。&lt;/p&gt;    &lt;p&gt;一般规模的公司十几个或几十个微服务都是可以承受的，但如果有几百个甚至上千个，那么绝不是一般公司可以管理的。尽管现有的工具已经很齐全了，而且与微服务有关的整个流程也已经基本上全部自动化了，但它还是会增加很多工作。Martin      &lt;br /&gt;Fowler几年以前建议先从单体程序开始（详见      &lt;a href="https://martinfowler.com/bliki/MonolithFirst.html" target="_blank"&gt;MonolithFirst&lt;/a&gt;），然后再逐步把功能拆分出去，变成一个个的微服务。但是后来有人反对这个建议，他也有些松口了。如果单体程序不是太大，这是个好主意。可以用数据额库表的数量来衡量程序的大小，我见过大的单体程序有几百张表，这就太多了，很难管理。正常情况下，一个微服务可以有两、三张表到五、六张表，一般不超过十张表。但如果要减少微服务数量的话，可以把这个标准放宽到不要超过二十张表。用这个做为大致的指标来创建微程序，如果使用一段时间之后还是觉得太大了，那么再逐渐拆分。当然，按照这个标准建立的服务更像是服务组合，而不是单个的微服务。不过它会为你减少工作量。只要不影响业务部门的创新进度，这是一个不错的方案。&lt;/p&gt;    &lt;p&gt;到底应不应该选择微服务呢？如果单体程序已经没法管理了，那么你别无选择。如果没有管理上的问题，那么微服务带给你的只有问题和麻烦。其实，一般公司都没有太多选择，只能采用微服务，不过你可以选择建立比较少的微服务。如果还是没法决定，有一个折中的方案，“内部微服务设计”。&lt;/p&gt;    &lt;h3&gt;内部微服务设计：&lt;/h3&gt;    &lt;p&gt;这种设计表面上看起来是一个单体程序，它只有一个源代码存储仓库，一个数据库，一个部署，但在程序内部可以按照微服务的思想来进行设计。它可以分成多个模块，每个模块是一个微服务，可以由不同的团队管理。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="file" src="https://b3logfile.com/file/2022/10/solo-fetchupload-11743228522252746396-T245CGA.jpeg?imageView2/2/w/1280/format/jpg/interlace/1/q/100"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;用这张图做例子。这个图里的每个圆角方块大致是一个微服务，但我们可以把它作为一个单体程序来设计，内部有五个微服务。每个模块都有自己的数据库表，它们都在一个数据库中，但模块之间不能跨数据库访问（不要建立模块之间数据库表的外键）。“User”（在Conference      &lt;br /&gt;Management模块中）是一个共享的类，但在不同的模块中的名字不同，含义和用法也不同，成员也不一样（例如，在“Customer      &lt;br /&gt;Service”里叫“Customer”）。DDD（Domain-Driven      &lt;br /&gt;Design）建议不要共享这个类，而是在每一个有界上下文（模块）中都建一个新类，并拥有新的名字。虽然它们的数据库中的数据应该大致相同，但DDD建议每一个有界上下文中都建一个新表，它们之间再进行数据同步。&lt;/p&gt;    &lt;p&gt;这个所谓的“内部微服务设计”其实就是DDD，但当时还没有微服务，因此外表看起来是单体程序，但内部已经是微服务的设计了。它的书在2003就出版了，当时就很有名。但它更偏重于业务逻辑的设计，践行起来也比较困难，因此大家谈论得很多，真正用的较少。直到十年之后，微服务出来之后，人们发现它其实内部就是微服务，而且微服务的设计需要用它的思想来指导，于是就又重新焕发了青春，而且这次更猛，已经到了每个谈论微服务的人都不得不谈论DDD的地步。不过一本软件书籍，在十年之后还能指导新技术的设计，非常令人钦佩。&lt;/p&gt;    &lt;p&gt;这样设计的好处是它是一个单体程序，省去了多个微服务带来的部署、运维的麻烦。但它内部是按微服务设计的，如果以后要拆分成微服务会比较容易。至于什么时候拆分不是一个技术问题。如果负责这个单体程序的各个团队之间不能在部署时间表，服务器优化等方面达成一致，那么就需要拆分了。当然你也要应对随之而来的各种运维麻烦。内部微服务设计是一个折中的方案，如果你想试水微服务，但又不愿意冒太大风险时，这是一个不错的选择。      &lt;br /&gt;微服务的数据库设计也有很多内容，包括如何把服务从单体程序一步步里拆分出来请参见      &lt;a href="https://blog.csdn.net/weixin_38748858/article/details/102634941" target="_blank"&gt;“微服务的数据库设计&amp;quot;&lt;/a&gt;.&lt;/p&gt;    &lt;h2&gt;结论：&lt;/h2&gt;    &lt;p&gt;微服务之间的调用有两种方式，RPC和事件驱动。事件驱动是更好的方式，因为它是松耦合的。但如果业务逻辑是紧耦合的，RPC方式也是可行的（它的好处是代码更简单），而且你还可以通过选取合适的协议（Protobuf&lt;/p&gt;    &lt;p&gt;gRPC）来降低这种紧耦合带来的危害。由于事件溯源和事件通知的相似性，很多人把两者弄混了，但它们实际上是完全不同的东西。微服务的数量不宜太多，可以先创建比较大的微服务（更像是服务组合）。如果你还是不能确定是否采用微服务架构，可以先从“内部微服务设计”开始，再逐渐拆分。&lt;/p&gt;    &lt;h2&gt;索引：&lt;/h2&gt;    &lt;ul&gt;      &lt;li&gt;[1]        &lt;a href="https://martinfowler.com/articles/201701-event-driven.html" target="_blank"&gt;What do you mean by “Event-Driven”&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;[2]        &lt;a href="https://dddcommunity.org/book/evans_2003/" target="_blank"&gt;Domain-Driven Design&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;[3]        &lt;a href="https://martinfowler.com/bliki/BoundedContext.html" target="_blank"&gt;BoundedContext&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;[4]        &lt;a href="https://www.innoq.com/en/blog/domain-events-versus-event-sourcing/" target="_blank"&gt;Domain Events vs. Event Sourcing&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;[5]        &lt;a href="https://stackoverflow.com/a/49868866" target="_blank"&gt;Using Kafka as a (CQRS) Eventstore. Good idea&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;[6]        &lt;a href="https://eventstore.org/" target="_blank"&gt;Evenstore&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;[7]        &lt;a href="https://martinfowler.com/bliki/MonolithFirst.html" target="_blank"&gt;MonolithFirst&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;[8]        &lt;a href="https://blog.csdn.net/weixin_38748858/article/details/102634941" target="_blank"&gt;微服务的数据库设计&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;    &lt;div&gt;      &lt;hr&gt;&lt;/hr&gt;标题：微服务之间的最佳调用方式      &lt;br /&gt;作者：      &lt;a href="https://www.mmzsblog.cn" target="_blank"&gt;mmzsblog&lt;/a&gt;      &lt;br /&gt;地址：      &lt;a href="https://www.mmzsblog.cn/articles/2022/10/20/1666273303717.html" target="_blank"&gt;https://www.mmzsblog.cn/articles/2022/10/20/1666273303717.html&lt;/a&gt;      &lt;br /&gt;      &lt;strong&gt;-------------------------&lt;/strong&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/62463-%E5%BE%AE%E6%9C%8D%E5%8A%A1-java-%E5%AD%A6%E4%B9%A0</guid>
      <pubDate>Fri, 21 Oct 2022 07:47:32 CST</pubDate>
    </item>
    <item>
      <title>【SSR】漫谈服务端渲染</title>
      <link>https://itindex.net/detail/62385-ssr-%E6%9C%8D%E5%8A%A1-%E6%B8%B2%E6%9F%93</link>
      <description>&lt;hr&gt;&lt;/hr&gt;
 &lt;h2&gt;theme: channing-cyan&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;大家好，我是Laffery，本文同步发表在我的个人博客「   &lt;a href="https://kuqiochi.cn/blogs/19"&gt;Kuqiochi | 谈谈服务端渲染「SSR」&lt;/a&gt;」。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;SSR（服务端渲染，Server Side Render），顾名思义就是在服务端渲染出页面。与之相对应的是CSR（客户端渲染，Client Side Render），即在浏览器上渲染完整的页面。&lt;/p&gt;
 &lt;h2&gt;Web页面渲染发展历程&lt;/h2&gt;
 &lt;p&gt;在介绍SSR之前，我们先来看看历来Web页面是如何渲染的。&lt;/p&gt;
 &lt;h3&gt;纯HTML时代&lt;/h3&gt;
 &lt;p&gt;在网络初开的远古时期，一切皆文件，网页本质上是  &lt;strong&gt;托管在服务器上的HTML文件&lt;/strong&gt;，甚至最开始是完全静态的页面，直到后来有了JavaScript和CSS，网页的交互性和内容的动态丰富性才有了保障。&lt;/p&gt;
 &lt;h3&gt;模板引擎 「前后端分离」&lt;/h3&gt;
 &lt;p&gt;模板引擎将视图层与数据层分离开来，是前后端分离的开端。&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;服务端为模板绑定数据&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;举个最直观的例子，flask渲染一个页面的方式就是向预设的HTML模板中传入数据，并在服务端渲染出最终的HTML（string / stream）。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;flask.   &lt;strong&gt;render_template&lt;/strong&gt;(   &lt;em&gt;template_name_or_list&lt;/em&gt;,    &lt;em&gt;**context&lt;/em&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;pre&gt;  &lt;code&gt;render_template(
    &amp;apos;index.html&amp;apos;, 
    labels=[&amp;apos;Name&amp;apos;, &amp;apos;Description&amp;apos;, &amp;apos;Confidence&amp;apos;],
    records=records
)
&lt;/code&gt;&lt;/pre&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;table&amp;gt;
  &amp;lt;thead&amp;gt;
    &amp;lt;tr&amp;gt;
      {% for label in labels %}
      &amp;lt;th&amp;gt;{{ label }}&amp;lt;/th&amp;gt;
      {% endfor %}
    &amp;lt;/tr&amp;gt;
  &amp;lt;/thead&amp;gt;
  &amp;lt;tbody&amp;gt;
    {% for record in records %}
    &amp;lt;tr&amp;gt;
      {% for part in record %}
      &amp;lt;td&amp;gt;{{ part }}&amp;lt;/td&amp;gt;
      {% endfor %}
    &amp;lt;/tr&amp;gt;
    {% endfor %}
  &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="d2fabc7d-51ea-45ee-8045-cf2efc2767b3.png" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1342bcb098da40868177c2858e118994~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ol start="2"&gt;
  &lt;li&gt;客户端为模板绑定数据&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;上面仍然是一个在服务端渲染的例子，那么ejs无疑是典型的客户端渲染。在客户端渲染很显然能够减轻服务器的渲染压力。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;script src=&amp;quot;ejs.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script&amp;gt;
  let people = [&amp;apos;geddy&amp;apos;, &amp;apos;neil&amp;apos;, &amp;apos;alex&amp;apos;],
  html = ejs.render(&amp;apos;&amp;lt;%= people.join(&amp;quot;, &amp;quot;); %&amp;gt;&amp;apos;, { people: people });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
 &lt;ol start="3"&gt;
  &lt;li&gt;客户端请求数据更新视图&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;自从  &lt;strong&gt;ajax&lt;/strong&gt;技术兴起，可以在浏览器上加载服务端数据，并且可以在不刷新页面的情况下更新视图。这时，彻底拉开了前后端分离的帷幕，前端和后端可以分开开发，解除耦合。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="8fcb0cf4-c90c-40d7-9a7f-a6d0c85d4cd9.png" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2038b5637b1b4cc2b74af8ec7323be73~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;再后来有了我们熟知的“御三家” Angular / React / Vue，前端开发彻底繁荣。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="2e4a1f72-acfb-4e44-9e4e-4482e025ead7.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/34e0558902af450c8a8ca4a641b6d57d~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;这个时候，Web应用最初从服务端获取的HTML是一个白页，依赖构建生成的   &lt;code&gt;index.[hash].js&lt;/code&gt;在客户端执行并渲染出页面&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;blockquote&gt;
  &lt;p&gt;   &lt;strong&gt;黑魔法：CSS Database Queries&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;   &lt;a href="https://www.leemeichin.com/posts/yes-i-can-connect-to-a-db-in-css.html"&gt;Yes, I can connect to a DB in CSS&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h3&gt;服务端渲染&lt;/h3&gt;
 &lt;p&gt;好不容易前后端分离了，在客户端渲染大家也都很受用，怎么开了历史的倒车，又放到服务端上渲染呢？&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;先从几个概念说起&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;**TTFB（Time to First Byte）：**首个字节数据到达的时间&lt;/li&gt;
  &lt;li&gt;**FP（First Paint）：**首次绘制的时间&lt;/li&gt;
  &lt;li&gt;**FMP（First Meaningful Paint）：**首次有意义的内容绘制的时间&lt;/li&gt;
  &lt;li&gt;**TTI（Time to Interactive）：**页面变得可交互的时间&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;服务端渲染主要针对：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;需要更高SEO（Search Engine Optimization），便于搜索引擎爬虫抓取网站内容的，如博客网站。&lt;/li&gt;
&lt;/ul&gt;
 &lt;blockquote&gt;
  &lt;p&gt;截至目前，Google 和 Bing 可以很好地对同步 JavaScript 应用进行索引。这里的“同步”是关键词。如果你的应用以一个 loading 动画开始，然后通过 Ajax 获取内容，爬虫并不会等到内容加载完成再抓取。也就是说，如果 SEO 对你的页面至关重要，而你的内容又是异步获取的，那么 SSR 可能是必需的。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;ul&gt;
  &lt;li&gt;提高首屏渲染速度（FMP），减少白屏时间，提升用户体验。&lt;/li&gt;
&lt;/ul&gt;
 &lt;blockquote&gt;
  &lt;p&gt;这一点在慢网速或者运行缓慢的设备上尤为重要。服务端渲染的 HTML 无需等到所有的 JavaScript 都下载并执行完成之后才显示，所以你的用户将会更快地看到完整渲染的页面。除此之外，数据获取过程在首次访问时在服务端完成，相比于从客户端获取，可能有更快的数据库连接。这通常可以带来更高的   &lt;a href="https://web.dev/vitals/"&gt;Core Web Vitals&lt;/a&gt;评分、更好的用户体验，而对于那些“首屏加载速度与转化率直接相关”的应用来说，这点可能至关重要。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;ul&gt;
  &lt;li&gt;提升低网络和低配置设备上的性能&lt;/li&gt;
&lt;/ul&gt;
 &lt;blockquote&gt;
  &lt;p&gt;有些设备不支持 JavaScript 或 JavaScript 执行得很差，导致用户体验不可接受。对于这些情况，你可能会需要该应用的服务端渲染的、无 JavaScript 的版本。虽然有一些限制，不过这个版本可能是那些完全没办法使用该应用的人的唯一选择。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;现在的服务端渲染主要分为两种：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;code&gt;Pre-render&lt;/code&gt;，在服务端预渲染；&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;Static render&lt;/code&gt;，在服务端渲染出最终的HTML。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;预渲染的服务端渲染流程如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="b78b12fe-73d1-487c-b3aa-2bd64a352021.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/568545edbf3141598a459f498f11a98c~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;边缘计算渲染&lt;/h3&gt;
 &lt;p&gt;一方面，服务端渲染依赖中心的服务器（集群）请求动态数据并渲染HTML；另一方面，存在一个事实是服务端渲染的页面存在静态部分，没有必要重复渲染，可以考虑托管在CDN上。于是有人考虑使用边缘计算节点渲染。  &lt;br /&gt;简单地说，  &lt;strong&gt;边缘计算是将计算资源部署靠近用户和数据源的网络边缘侧，通过更靠近数据源的位置（如路由器、基站）执行计算&lt;/strong&gt;。  &lt;br /&gt;边缘计算渲染将需要服务端渲染的页面分为静态和动态两部分。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;静态部分缓存在边缘计算节点上（相当于CDN），相比传统的客户端向服务端请求，更快渲染出内容（FMP）&lt;/li&gt;
  &lt;li&gt;边缘计算节点与后端服务器长连接，动态部分的数据请求无需再建立TCP连接，响应更快&lt;/li&gt;
  &lt;li&gt;边缘计算节点将动态数据拼接到静态部分上&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="2d3c7559-7fa3-4d40-9151-d88ceb4e8a4b.png" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/722a616725954573aad48b48d7fd40b9~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;现代SSR技术&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;code&gt;CSR``Client Side Render&lt;/code&gt;客户端渲染&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;SSR``Server Side Render&lt;/code&gt;服务端渲染（✅，现在说的服务端渲染默认是上述pre render的）&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;SSG``Static Site Generate&lt;/code&gt;静态页面生成&lt;/li&gt;
&lt;/ul&gt;
 &lt;img alt="logo-next" height="100" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c3a9bce0a13a477bb2d5459b487c0781~tplv-k3u1fbpfcp-watermark.image"&gt;&lt;/img&gt;
 &lt;img alt="logo-nuxt" height="100" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1ebf2a53d4e74c0c894dcd9523ae371a~tplv-k3u1fbpfcp-watermark.image"&gt;&lt;/img&gt;
 &lt;img alt="logo-ng-universal" height="100" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c3fb16e3c1004228b43c71e4f7824e36~tplv-k3u1fbpfcp-watermark.image"&gt;&lt;/img&gt;
 &lt;h3&gt;基本原理&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="a4615133-f319-45b2-92f5-7459402e29b7.jpeg" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/52245300a312479c8f795049ed04a7aa~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;服务端根据路由找到要渲染的component&lt;/li&gt;
  &lt;li&gt;服务端将页面渲染成HTML（string / stream）&lt;/li&gt;
  &lt;li&gt;服务端根据component中预定义的数据预取方法请求数据&lt;/li&gt;
  &lt;li&gt;服务端将数据序列化拼接到HTML中&lt;/li&gt;
  &lt;li&gt;客户端接收到服务器响应，渲染收到的HTML（以及CSS等）&lt;/li&gt;
  &lt;li&gt;用户可正常浏览&lt;/li&gt;
  &lt;li&gt;客户端执行hydration（水化），激活页面element的事件监听方法&lt;/li&gt;
  &lt;li&gt;用户可正常交互&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;同构&lt;/h4&gt;
 &lt;p&gt;同构（Isomorphic）是SSR的核心理念。单纯实现SSR很简单，任何传统的服务端语言都能做到，但是SSR希望一套代码能在双端渲染，最大限度地重用代码，并抹除差异性，这是传统的SSR无法做到的。&lt;/p&gt;
 &lt;div&gt;
      &lt;img alt="logo-express" height="100" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5134ecb53bc94680b585881cbe3c18ac~tplv-k3u1fbpfcp-watermark.image"&gt;&lt;/img&gt;
      &lt;img alt="logo-koa" height="100" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6c1e2f54ddbb495399bc0f8671a7f316~tplv-k3u1fbpfcp-watermark.image"&gt;&lt;/img&gt;
&lt;/div&gt;
 &lt;h4&gt;路由同构&lt;/h4&gt;
 &lt;p&gt;双端使用同一套路由规则。  &lt;br /&gt;服务端根据  &lt;code&gt;request.url&lt;/code&gt;查找要渲染的组件。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;路由配置文件   &lt;code&gt;routes.json|ts&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;约定式路由（如根据目录结构）&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;数据同构&lt;/h4&gt;
 &lt;p&gt;双端使用同一套数据请求方法获取数据。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;逻辑一致&lt;/li&gt;
  &lt;li&gt;方法一致（node-fetch）&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;渲染同构&lt;/h4&gt;
 &lt;p&gt;双端渲染出来的结果是一致的。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;浏览器有时会在解析HTML时自动优化，如&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;!-- server side --&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;div&amp;gt;hi&amp;lt;/div&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;!-- client side --&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;div&amp;gt;hi&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
 &lt;ul&gt;
  &lt;li&gt;随机数 / hash，在双端要保持使用相同的随机数种子&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;React SSR demo&lt;/h3&gt;
 &lt;p&gt;在React下，基于react-dom/server下的  &lt;code&gt;renderToString&lt;/code&gt;等方法，将Component渲染成HTML string或使用  &lt;code&gt;renderToXxxStream&lt;/code&gt;渲染成byte stream。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;import App from &amp;apos;path/to/client/App&amp;apos;; 
import RenderDOMServer from &amp;apos;render-dom/server&amp;apos;; 

app.get(&amp;apos;/*&amp;apos;, (req, res) =&amp;gt; { 
  const html = RenderDOMServer.renderToString(&amp;lt;App /&amp;gt;); 
  return res.end(html);
});
&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;Data Fetching&lt;/h4&gt;
 &lt;p&gt;  &lt;code&gt;renderToString&lt;/code&gt;是一个同步的函数，换句话说，服务端渲染的一系列API，并不能等到将组件中的异步数据加载完成后才执行渲染，所以一个直观的问题就是，前端渲染出来的HTML是没有数据的。   &lt;br /&gt;可以在  &lt;code&gt;renderToString&lt;/code&gt;执行前，提前加载需要的数据，并作为参数传递给组件。&lt;/p&gt;
 &lt;p&gt;约定服务端渲染的页面，其入口文件除了默认导出的组件，还导出一个命名为  &lt;code&gt;getServerSideProps&lt;/code&gt;的函数，用来定义数据获取的逻辑--由服务端执行。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;type AppProps = Record&amp;lt;string, any&amp;gt;;

export default App(props: AppProps) { 
    return &amp;lt;&amp;gt;...&amp;lt;/&amp;gt; 
} 

export const getServerSideProps = async () =&amp;gt; { 
    const res = await fetch(&amp;apos;xxx&amp;apos;); 
    const data = await res.json() as AppProps; 
    return { props: data } 
} 
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;服务端在加载这个页面的时候，识别出  &lt;code&gt;getServerSideProps&lt;/code&gt;的定义存在，则执行其数据获取逻辑：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;import RenderDOMServer from &amp;apos;render-dom/server&amp;apos;; 
 
app.get(&amp;apos;/*&amp;apos;, async (req, res) =&amp;gt; { 
    const { default: Page, getServerSideProps } = require(&amp;apos;path/to/client/some-page&amp;apos;); 
     
    if (getServerSideProps) { 
        const { props } = await getServerSideProps(); 
        res.end(RenderDOMServer.renderToString(&amp;lt;Page {...props}/&amp;gt;)); 
        return; 
    } 

    res.end(RenderDOMServer.renderToString(&amp;lt;Page /&amp;gt;)) 
}); 
&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;SSR Data Context&lt;/h4&gt;
 &lt;p&gt;当页面上用到了服务端获取的数据时，客户端上调用  &lt;code&gt;hydrate&lt;/code&gt;方法后会察觉到渲染出来的差别，比如如下的SSR页面&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;import HelloWorld from &amp;quot;@/components/hello-world&amp;quot;; 
 
export default function Homepage(props: { mode?: &amp;quot;CSR&amp;quot; | &amp;quot;SSR&amp;quot; }) { 
  return &amp;lt;div&amp;gt;{props.mode ?? &amp;quot;CSR&amp;quot;}&amp;lt;/div&amp;gt;; 
} 
 
export const getServerSideProps = async () =&amp;gt; { 
  return { props: { mode: &amp;quot;SSR&amp;quot; } }; 
}; 
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;服务端渲染的结果是“SSR”，而  &lt;code&gt;hydrate&lt;/code&gt;又将组件渲染一遍之后页面就会变成“CSR”。   &lt;br /&gt;那么如何将SSR加载的数据也提供给浏览器呢，可以考虑为HTML模板添加脚本，&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;script defer type=&amp;quot;text/javascript&amp;quot;&amp;gt; 
  (function() {
    window.SSR = true;
    window.SSR_DATA = data;
  })()
&amp;lt;/script&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这样在客户端执行hydrate前，从window中获取SSR_DATA对象，并为之创建上下文，为页面组件包裹一层，并在其中向页面组件注入上下文中的数据：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;import { createContext, useContext } from &amp;quot;react&amp;quot;; 
import ReactDOM from &amp;quot;react-dom/client&amp;quot;; 
import Page from &amp;quot;./pages/xx&amp;quot;; 
 
const Context = createContext({}); 
 
function App() { 
  const { props } = useContext(Context); 
  return (
    &amp;lt;Context.Provider value={window.SSR_DATA || {}}&amp;gt;
      &amp;lt;Page {...props} /&amp;gt; 
    &amp;lt;/Context.Provider&amp;gt;
  );
}

ReactDOM.createRoot(rootNode).render(&amp;lt;App /&amp;gt;);
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这样客户端hydrate时就能渲染出和服务端一致的数据。&lt;/p&gt;
 &lt;h4&gt;Hydration&lt;/h4&gt;
 &lt;p&gt;客户端虽然拿到了正确的数据并拼接到HTML中，但还没有为各个element绑定事件处理函数，此时SSR页面无法响应用户的交互。   &lt;br /&gt;这时，在客户端React会将页面重新解析生成VDOM，为每个节点绑定事件处理方法，这样就可以像CSR一样正常的响应各个事件。传统的  &lt;code&gt;render&lt;/code&gt;方法在首次调用时会清除SSR渲染的容器中的已有节点，而使用  &lt;code&gt;hydrate&lt;/code&gt;方法则会对容器中的节点进行解析，并为之绑定事件监听器。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;function render() {
  const rootNode = document.getElementById(&amp;quot;root&amp;quot;) as HTMLElement;

  if (window &amp;amp;&amp;amp; window.SSR) {
    ReactDOM.hydrateRoot(rootNode, &amp;lt;App /&amp;gt;);
  } else {
    ReactDOM.createRoot(rootNode).render(&amp;lt;App /&amp;gt;);
  }
}

render();
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;值得注意的是，  &lt;code&gt;hydrate&lt;/code&gt;会比较解析出的VDOM与SSR渲染结果，如果存在差异，则会重新渲染VDOM，退化成CSR。&lt;/p&gt;
 &lt;h4&gt;约定式路由&lt;/h4&gt;
 &lt;p&gt;约定式路由无需routers配置，根据路由文件结构与路径一一映射。例如Next.js的约定：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;code&gt;pages/index.js&lt;/code&gt; →    &lt;code&gt;/&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;pages/blog/index.js&lt;/code&gt; →    &lt;code&gt;/blog&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;pages/blog/first-post.js&lt;/code&gt; →    &lt;code&gt;/blog/first-post&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;pages/dashboard/settings/username.js&lt;/code&gt; →    &lt;code&gt;/dashboard/settings/username&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;pages/blog/[slug].js&lt;/code&gt; →    &lt;code&gt;/blog/:slug (/blog/hello-world)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;pages/[username]/settings.js&lt;/code&gt; →    &lt;code&gt;/:username/settings (/foo/settings)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;pages/post/[...all].js&lt;/code&gt; →    &lt;code&gt;/post/* (/post/2020/id/title)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;因为服务端只能根据用户请求的url确定渲染哪个页面，所以还是需要在构建时生成一个manifest文件，告诉服务端改如何找到对应的页面。&lt;/p&gt;
 &lt;h3&gt;Prospect&lt;/h3&gt;
 &lt;h4&gt;Qwik&lt;/h4&gt;
 &lt;p&gt;  &lt;img alt="0bccf85f-2ba1-4523-827f-6e37d11a0801.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/42e66081bb914e9fa1789408108e0386~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;resumable: 将序列化的数据挂在element上&lt;/li&gt;
  &lt;li&gt;fine-grained lazy loading：事件处理函数懒加载&lt;/li&gt;
  &lt;li&gt;prefetch：根据用户行为分析常用区域，优先预加载事件处理函数&lt;/li&gt;
&lt;/ul&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;button q:obj=&amp;quot;1&amp;quot; on:click=&amp;quot;./chunk-a.js#Counter_button_onClick[0]&amp;quot;&amp;gt;
  0
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;React Server Components(RSC)&lt;/h4&gt;
 &lt;p&gt;数据加载的三种方案：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;waterfalls：客户端js加载完后发出大量数据请求&lt;/li&gt;
  &lt;li&gt;pre fetch：先请求所有数据，拼接到HTML上客户端再渲染&lt;/li&gt;
  &lt;li&gt;render as you fetch：不是所有的数据都是需要立刻展示的，请求优先展示的数据，剩余的放到客户端慢慢加载&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;组件级的服务端渲染，客户端组件不能依赖服务端组件，服务端组件使用  &lt;code&gt;Suspense&lt;/code&gt; API包裹。在客户端，JavaScript不包含服务端渲染部分的代码，客户端组件仍会在客户端hydrate。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;import { Suspense } from &amp;apos;react&amp;apos;;

import Profile from &amp;apos;../components/profile.server&amp;apos;;
import Content from &amp;apos;../components/content.client&amp;apos;;

export default function Home() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Welcome to React Server Components&amp;lt;/h1&amp;gt;
      &amp;lt;Suspense fallback={&amp;apos;Loading...&amp;apos;}&amp;gt;
        &amp;lt;Profile /&amp;gt;
      &amp;lt;/Suspense&amp;gt;
      &amp;lt;Content /&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;h2&gt;参考资料&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://flask.palletsprojects.com/en/2.2.x/api/?highlight=render_template#flask.render_template"&gt;flask Doc | render_template&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://ejs.co/#docs"&gt;ejs Doc&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://zhuanlan.zhihu.com/p/453275090"&gt;知乎 ｜ 当前端渲染遇上边缘计算&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://reactjs.org/docs/react-dom-server.html"&gt;React - ReactDomServer&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://angular.cn/guide/universal#server-side-rendering-ssr-with-angular-universal"&gt;Angular | NgUniversal&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://cn.vuejs.org/guide/scaling-up/ssr.html#vite-ssr"&gt;Vue Guide | SSR&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://qwik.builder.io/docs/overview/"&gt;Qwik | Overview&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://nextjs.org/docs/advanced-features/react-18/server-components"&gt;Next.js | React Server Components&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://remix.run/blog/react-server-components"&gt;Remix | React Server Components&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.builder.io/blog/hydration-is-pure-overhead"&gt;Builder.io | hydration is overhead&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 />
      <guid isPermaLink="true">https://itindex.net/detail/62385-ssr-%E6%9C%8D%E5%8A%A1-%E6%B8%B2%E6%9F%93</guid>
      <pubDate>Sat, 27 Aug 2022 17:31:18 CST</pubDate>
    </item>
    <item>
      <title>Vercel, Cloudflare, fly.io 我的云服务三剑客</title>
      <link>https://itindex.net/detail/62332-vercel-cloudflare-fly</link>
      <description>Update: 在排查 Cloudflare 缓存命中率低的问题时，发现没有一台 Server 实在是不方便。比如想看下哪些请求透过了 CF，直接到了源站，如果内容 deploy 在 Vercel 上，就看不到这些请求日志。所以虽然 Vercel 很方便，用起来也很舒服，但一些能力上的缺失在某些场景下还是挺难受的，因此还是转向了 VPS（Vultr） + Cloudflare 的传统配搭。

 &lt;p&gt;自己时不时地会写一些网页（如   &lt;a href="https://pinyincaichengyu.com/"&gt;拼音猜成语&lt;/a&gt;）或部署一些静态页面（如这个博客），或者托管一些资源文件（如播客音频），这就涉及到线上服务的选择。目前来看   &lt;a href="https://vercel.com"&gt;Vercel&lt;/a&gt;,   &lt;a href="https://cloudflare.net"&gt;Cloudflare&lt;/a&gt; 和   &lt;a href="https://fly.io"&gt;fly.io&lt;/a&gt; 能较好地满足我的大部分需求。&lt;/p&gt;
 &lt;h3&gt;DNS 管理 &amp;amp; CDN&lt;/h3&gt;
 &lt;p&gt;这部分交由 Cloudflare(简称 CF) 来管理。在 CF 后台新建 Site 后，在域名注册商处，将 Name Server 设置为 Cloudflare 的 Name Server 就行了，就是下面这两个&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;leah.ns.cloudflare.com
wesley.ns.cloudflare.com
&lt;/code&gt;&lt;/pre&gt;

  CF 也提供域名注册服务，如果是新的域名，可以直接在 CF 注册，管理起来更方便，价格也不贵。

 &lt;p&gt;因为 CF 的主营业务还是 CDN，所以提供了一个很方便的功能：代理网站静态资源，也就是这朵橘色的云（Proxy Status）。&lt;/p&gt;

 &lt;p&gt;  &lt;img src="https://limboy.me/posts/vercel-cf-fly/cf-dns.jpg" width="1746"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;开启了之后，domain 下的资源（如图片，CSS 等）会被 CF 缓存到 CDN，用户访问这些资源时，就直接访问 CDN 上的缓存，不会到自己的服务器，可以节省很多流量。而且 CF 的 CDN 流量是免费的，所以不用担心哪天站点访问量剧增带来的流量费用。&lt;/p&gt;
 &lt;p&gt;PS: 开启「自动代理」后，如果出现直接访问 OK，但通过 CF 访问就会出现问题的现象，可以把   &lt;code&gt;SSL/TLS&lt;/code&gt; 设置为「灵活」。&lt;/p&gt;

 &lt;p&gt;  &lt;img src="https://limboy.me/posts/vercel-cf-fly/cf-forwarding-settings.jpg" width="2106"&gt;&lt;/img&gt;&lt;/p&gt;
  如果发现 CF 访问统计中，有很多的流量没有缓存，可能是跟 Vercel 返回的  &lt;code&gt;Cache-Control&lt;/code&gt; 有关，比如  &lt;code&gt;Cache-Control: public, max-age=0, must-revalidate&lt;/code&gt;，表示不缓存，这样 CF 也要遵从这个约定，当用户访问资源文件时，如果浏览器本地没有缓存，或者已经过期，每次请求到了 CF 这里后，CF 也还是要到源站（Vercel）去取。这样不仅没有让 CF 发挥 CDN 的优势，反而因为要回源，导致请求变慢。 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;好在 Vercel 支持自定义 header 规则（更多的规则可以看  &lt;a href="https://vercel.com/docs/project-configuration"&gt;这里&lt;/a&gt;），我们可以在   &lt;code&gt;vercel.json&lt;/code&gt; 里重新定义   &lt;code&gt;Cache-Control&lt;/code&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;{
  &amp;quot;headers&amp;quot;: [
    {
      &amp;quot;source&amp;quot;: &amp;quot;/(.*).(jpe?g|png|ico|webp|svg|mp4|gif|xml|ttf|woff2?)&amp;quot;,
      &amp;quot;headers&amp;quot;: [
        {
          &amp;quot;key&amp;quot;: &amp;quot;Cache-Control&amp;quot;,
          &amp;quot;value&amp;quot;: &amp;quot;public, max-age=31536000, immutable&amp;quot;
        }
      ]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这样当访问特定后缀的资源文件时，就会返回我们定义的   &lt;code&gt;Cache-Control&lt;/code&gt;，CF 会根据新的   &lt;code&gt;Cache-Control&lt;/code&gt; 策略来缓存，比如这里设置了永不过期。&lt;/p&gt;
 &lt;p&gt;PS: Vercel 有个  &lt;a href="https://vercel.com/support/articles/using-cloudflare-with-vercel"&gt;页面&lt;/a&gt;专门讨论了与 CF 并存的问题，想进一步了解的话，可以看一下。&lt;/p&gt;
 &lt;p&gt;PPS: 这么做确实可以达到效果，但 CF 后台显示缓存命中率只有不到 10%，需要进一步排查。
&lt;/p&gt;
  CF 默认不缓存 HTML 文件，如果要设置更加灵活的缓存策略，可以通过 Page Rules 来完成，免费用户可以设置 3 个规则，基本够用了。

 &lt;p&gt;  &lt;img src="https://limboy.me/posts/vercel-cf-fly/cf-page-rules.jpg" width="1608"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;还有一个好处是，可以在 CF 的后台看到页面的访问统计，虽然比较简单（能看到访问次数，但无法精确到页面，或者来源），但不需要额外的 JS 代码，不用依赖第三方访问统计服务，也还不错。&lt;/p&gt;

  CF 还可以托管一些存储服务的文件，以  &lt;a href="https://www.backblaze.com/"&gt;Backblaze&lt;/a&gt;(又称 B2) 为例，对 DNS 的 CNAME 做一下映射（同样要将 Proxy 设置为 Proxied），比如将  &lt;code&gt;assets.example.com&lt;/code&gt; 映射到  &lt;code&gt;f002.backblazeb2.com&lt;/code&gt;，通过后台或 API 上传资源文件（如播客的音频）后会得到 B2 的 URL，将域名换一下就行了。比如将  &lt;code&gt;https://f002.backblazeb2.com/a/b.mp3&lt;/code&gt; 替换为  &lt;code&gt;https://assets.example.com/a/b.mp3&lt;/code&gt;，就可以使用 CF 的 CDN 服务了。
 &lt;h3&gt;静态页面&lt;/h3&gt;
 &lt;p&gt;通过 SSG（静态内容生成器）生成的博客或文档等静态资源，会部署到   &lt;a href="https://vercel.com/"&gt;Vercel&lt;/a&gt; 上，体验确实很不错。比如 Connect Github 后，做一些简单的配置，就能方便地 Deploy 了。push 到 main 之外的分支，可以生成 Preview 页面，确定内容正确后，再 push 到 main 分支，就上线了。这也是 Vercel 提倡的 Develop, Preview, Ship 流程。&lt;/p&gt;
 &lt;p&gt;每个月 100 G 的流量（所有 Projects 加起来）如果都走 Vercel，可能不太够，但结合 CF 就完全能 hold 住了（流量主要是一些 HTML 页面）。&lt;/p&gt;
 &lt;p&gt;如果要写一些复杂的页面或涉及到一些后端逻辑，也可以使用 Vercel 开源的   &lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt;。API 会以 Serverless 的方式运行（所以不要依赖一些内存全局变量）。最近 Middleware 也   &lt;a href="https://vercel.com/blog/vercel-edge-middleware-dynamic-at-the-speed-of-static"&gt;GA&lt;/a&gt; 了，可以在请求到达具体页面前再做一些处理（比如判断是否有特定的 Cookie，然后重定向到特定的页面）。&lt;/p&gt;

  CF 也提供类似的服务  &lt;a href="https://pages.cloudflare.com/"&gt;Cloudflare Pages&lt;/a&gt;，不过目前对 Vercel 还比较满意，就没考虑切换。
 &lt;h3&gt;动态数据&lt;/h3&gt;
 &lt;p&gt;如果涉及到数据库，对个人项目来说，要么在单独的 VPS 上部署，要么在 Serverless code 里通过 API 去连接 Cloud Database Service，前者有一定的维护成本，要保障 VPS、后端服务、DB Service 的正常运行，还要考虑各种运维（网络耗时、磁盘空间、CPU、内存占用等等），后者就要找一个合适的服务商，确保价格、空间、流量、使用体验等比较满意。&lt;/p&gt;
 &lt;p&gt;其实也可以有第三种选择，我选的是   &lt;a href="https://fly.io/"&gt;fly.io&lt;/a&gt;，可以简单地理解为把 Docker 部署到边缘节点，让用户可以就近访问服务。fly 的免费版本提供了一定的存储空间（3GB，先创建 Volume，然后 mount 到 App 上），一些简单的项目，一个 SQLite 文件就搞定了，备份起来也很方便。更 Serious 的项目可以使用他们提供的 Postgres 数据库（支持多节点部署，读写分离）。&lt;/p&gt;
 &lt;p&gt;这样就兼具了 VPS 的灵活性，又免去了不少运维成本，又因为服务可以部署在全球的各个节点，访问速度上也比较有保障（挂了 Volume 后，就只能锁定单个区域，因为 Volume 跟 App 和 Region 绑定）。&lt;/p&gt;
 &lt;p&gt;目前只是在上面部署了一个小项目，还没有较正经地使用，不过有   &lt;a href="https://remix.run/"&gt;Remix&lt;/a&gt; 和   &lt;a href="https://kentcdodds.com/uses"&gt;Kent&lt;/a&gt; 的背书，从一些  &lt;a href="https://xeiaso.net/blog/fly.io-heroku-replacement"&gt;其他的反馈&lt;/a&gt;来看也还可以，应该会是不错的选择。&lt;/p&gt;
 &lt;p&gt;还有一个原因是 fly.io 给我的感觉也是满酷的，比如   &lt;a href="https://fly.io/jobs/rust-developer/"&gt;Rust 开发的招聘文&lt;/a&gt;，这是其中一段：&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;The premise of our hiring process is that we’re going to show you the kind of work we’re doing and then see if you enjoy actually doing it; “work-sample challenges”. Unlike a lot of places that assign “take-home problems”, our challenges are the backbone of our whole process; they’re not pre-screeners for an interview gauntlet.&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;这三家服务商基本覆盖了我所有的线上需求，而且只需支付很少（或者免费）的费用就能支撑足够大的访问量（Cloudflare 功不可没）。如果你也有类似的需求，或许可以考虑下。&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/62332-vercel-cloudflare-fly</guid>
      <pubDate>Thu, 30 Jun 2022 08:00:00 CST</pubDate>
    </item>
    <item>
      <title>Airbnb 的微服务架构质量工程之旅</title>
      <link>https://itindex.net/detail/62308-airbnb-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84</link>
      <description>&lt;br /&gt;【编者的话】本文通过介绍 Airbnb 在将“纯微服务架构”改造为“Micro macroservices 架构”过程中，所采用的四个实践步骤：1）提供基础设施即代码以提高开发人员的生产力，2）明确所有权并通过工具和可观察性进行改进，3）定义由组织和方法支持的新架构，4）引入一个弃用工作组以加速迁移，详细阐述了质量工程（Quality Engineering）的增量迭代解决问题方法：1）定义问题，2）找到改进的解决方案，3）使用解决方案，4）提高解决方案采用率，5）处理解决方案的扩展挑战，为我们实践质量工程提供了一个很好的范例。 &lt;br /&gt;
 &lt;br /&gt; &lt;div&gt;
  &lt;a href="http://dockone.io/uploads/article/20220619/ea6a572916ba8880c52aeaffd9ec684f.png" rel="lightbox" target="_blank"&gt;   &lt;img alt="png1.png" src="http://dockone.io/uploads/article/20220619/ea6a572916ba8880c52aeaffd9ec684f.png" title="png1.png"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;
 &lt;br /&gt;
 &lt;br /&gt;实现平衡是一个永无止境的开始。 &lt;br /&gt;
 &lt;br /&gt;当业务依赖软件质量和速度来生存时，这种平衡就更难维持了。 &lt;br /&gt;
 &lt;br /&gt;许多公司都面临着持续快速交付高质量软件的挑战，这些软件通过 &lt;a href="https://qeunit.com/blog/quality-engineering-is-about-surviving-the-digital-transformation/"&gt;质量工程&lt;/a&gt;限制生命周期。 &lt;br /&gt;
 &lt;br /&gt;Airbnb 在加速和扩展其价值主张的过程中面临着众多挑战，尤其是其信息系统的发展过程。 &lt;br /&gt;
 &lt;br /&gt;本文分享了 Airbnb 在质量工程方面的架构迭代之旅，并附有实用要点。参考文献可在文章末尾找到。 &lt;br /&gt;
 &lt;br /&gt; &lt;a href="https://qeunit.com/follow"&gt;关注 QE 社区&lt;/a&gt;以获取来自社区的更多质量工程信息。 &lt;br /&gt;
 &lt;br /&gt; &lt;h2&gt;从单体应用到微服务，什么都没改变&lt;/h2&gt;Airbnb 和许多软件公司一样，也是由一些积极进取的工程师发起构建最小可行产品以进入他们的市场从而发展起来的。 &lt;br /&gt;
 &lt;br /&gt;该产品最初是单仓内的单体架构（在此处访问完整的 &lt;a href="https://qeunit.com/blog/airbnbs-monorepo-journey-to-quality-engineering/"&gt; Airbnb 单仓旅程&lt;/a&gt;）。 Airbnb 在这种模式下从 2008 年开始发展，其功能由各小团队完成，小团队之间的依赖及其有限。 &lt;br /&gt;
 &lt;br /&gt; &lt;div&gt;
  &lt;a href="http://dockone.io/uploads/article/20220619/98f30cbad2c3132ef73c454fdd64207c.png" rel="lightbox" target="_blank"&gt;   &lt;img alt="png2.png" src="http://dockone.io/uploads/article/20220619/98f30cbad2c3132ef73c454fdd64207c.png" title="png2.png"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;
 &lt;br /&gt;
 &lt;br /&gt;但在 2017 年，这个集中式架构达到了它的极限： &lt;br /&gt;
 &lt;ul&gt;  &lt;li&gt;   &lt;strong&gt;软件变更速度降低&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;平行演进面临限制因素&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;组件所有权混乱&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt; &lt;br /&gt;
 &lt;br /&gt;Airbnb 决定采用微服务。 &lt;br /&gt;
 &lt;br /&gt;现有的单体应用被拆分为前台和后台（分别是为了速度的 &lt;em&gt;Hyperloop&lt;/em&gt;和为了稳定性的 &lt;em&gt;Treehouse&lt;/em&gt;）。 微服务出现在各自仓库中，而专门的服务迁移团队负责组件的转换。 &lt;br /&gt;
 &lt;br /&gt; &lt;div&gt;
  &lt;a href="http://dockone.io/uploads/article/20220619/a84c70eb5dfa57a2d857a46930011272.png" rel="lightbox" target="_blank"&gt;   &lt;img alt="png3.png" src="http://dockone.io/uploads/article/20220619/a84c70eb5dfa57a2d857a46930011272.png" title="png3.png"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;
 &lt;br /&gt;
 &lt;br /&gt;3 年后的 2020 年，该业务的收入将 &lt;a href="https://www.statista.com/statistics/1193134/airbnb-revenue-worldwide/"&gt;达到 50 亿美元&lt;/a&gt;（直到 COVID 爆发）。团队和微服务持续增加。但是，原来的问题又回来了：功能需要由多个服务和不同团队同时开发。 &lt;br /&gt;
 &lt;br /&gt;在他们的架构口号“我们不能影响业务增长”的推动下，工程团队跳出原有方式，为这些反复出现的问题寻找可持续的解决方案。 &lt;br /&gt;
 &lt;br /&gt; &lt;h2&gt;Airbnb 架构演进的挑战&lt;/h2&gt;Airbnb 面临着架构解决方案的挑战，不但要解决现在的问题，同时要支持未来的扩张；这就是质量工程要解决的问题。 &lt;br /&gt;
 &lt;br /&gt;Airbnb 采用了增量和迭代过程： &lt;br /&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;br /&gt;
 &lt;br /&gt;这种方法能够分离每个问题的关注点，找到结构化的解决方案并在以后扩展它们。 &lt;br /&gt;
 &lt;br /&gt; &lt;div&gt;
  &lt;a href="http://dockone.io/uploads/article/20220619/37acabfd104a7de9e35a2476ded4e51c.png" rel="lightbox" target="_blank"&gt;   &lt;img alt="png4.png" src="http://dockone.io/uploads/article/20220619/37acabfd104a7de9e35a2476ded4e51c.png" title="png4.png"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;
 &lt;br /&gt;
 &lt;br /&gt;Airbnb 在采用上述方法过程中实施了以下做法： &lt;br /&gt;
 &lt;ol&gt;  &lt;li&gt;   &lt;strong&gt;提供基础设施即代码以提高开发人员的生产力&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;明确所有权并通过工具和可观察性进行改进&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;定义由组织和方法支持的新架构&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;引入一个弃用工作组以加速迁移&lt;/strong&gt;&lt;/li&gt;&lt;/ol&gt; &lt;br /&gt;
 &lt;br /&gt;让我们看看前述问题是如何解决的。 &lt;br /&gt;
 &lt;br /&gt; &lt;h2&gt;1. 提供基础设施即代码以提高开发人员的生产力&lt;/h2&gt;当业务变革能力依赖于软件时，缓慢和错误的软件开发直接影响公司的竞争力。 &lt;br /&gt;
 &lt;br /&gt;凭借在微服务架构方面的经验，Airbnb 投资了自动化和工具化。 但在一系列不断发展的技术中需要更快的迭代周期。 &lt;br /&gt;
 &lt;br /&gt; &lt;div&gt;
  &lt;a href="http://dockone.io/uploads/article/20220619/44aedfb9c9c1537607c8078027accb01.png" rel="lightbox" target="_blank"&gt;   &lt;img alt="png5.png" src="http://dockone.io/uploads/article/20220619/44aedfb9c9c1537607c8078027accb01.png" title="png5.png"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;
 &lt;br /&gt;
 &lt;br /&gt;务实的解决方案是在单个仓库中投资基础设施即代码，从而逐步并行提升各个服务的采用率。 &lt;br /&gt;
 &lt;br /&gt;当运行更多服务时，就会出现理解这种复杂性的扩展挑战。 &lt;br /&gt;
 &lt;br /&gt; &lt;h2&gt;2. 明确所有权并通过工具和可观察性进行改进&lt;/h2&gt;有太多的微服务相互捆绑； 即使很小的变化也会导致相互依赖的变化和影响，掌握起来很复杂。 &lt;br /&gt;
 &lt;br /&gt; &lt;div&gt;
  &lt;a href="http://dockone.io/uploads/article/20220619/eefbf8ba5abdd201300f3745a226ce69.png" rel="lightbox" target="_blank"&gt;   &lt;img alt="png6.png" src="http://dockone.io/uploads/article/20220619/eefbf8ba5abdd201300f3745a226ce69.png" title="png6.png"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;
 &lt;br /&gt;
 &lt;br /&gt;Airbnb 投资于提高生产力，重点支持三个领域的新架构： &lt;br /&gt;
 &lt;ul&gt;  &lt;li&gt;   &lt;strong&gt;所有权&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;可观察性&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;工具&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt; &lt;br /&gt;
 &lt;br /&gt; &lt;h3&gt;所有权&lt;/h3&gt;Airbnb 部署了“Scry Ownership”，它是技术组件所有权数据（如所有者、维护者、通信渠道）的应用管理者。 &lt;br /&gt;
 &lt;br /&gt; &lt;div&gt;
  &lt;a href="http://dockone.io/uploads/article/20220619/1b513b030b799a567acfbe547228be2b.png" rel="lightbox" target="_blank"&gt;   &lt;img alt="png7.png" src="http://dockone.io/uploads/article/20220619/1b513b030b799a567acfbe547228be2b.png" title="png7.png"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;
 &lt;br /&gt;
 &lt;br /&gt; &lt;h3&gt;可观察性&lt;/h3&gt;Airbnb  设置了一系列可观察性仪表板，以系统地审查实施过程。 下面是一个仪表板示例，用于跟踪正在设置的所有权： &lt;br /&gt;
 &lt;br /&gt; &lt;div&gt;
  &lt;a href="http://dockone.io/uploads/article/20220619/32205a9fd78ecec93ca2b17383445586.png" rel="lightbox" target="_blank"&gt;   &lt;img alt="png8.png" src="http://dockone.io/uploads/article/20220619/32205a9fd78ecec93ca2b17383445586.png" title="png8.png"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;
 &lt;br /&gt;
 &lt;br /&gt; &lt;h3&gt;工具&lt;/h3&gt;数据空间仍然存在挑战。 定制的  &lt;a href="https://thrift.apache.org/"&gt;Thrift&lt;/a&gt; 模式是有用的序列化器和数据描述符，但它们需要其他组件来检索产品中的数据。 &lt;br /&gt;
 &lt;br /&gt;Airbnb 利用 GraphQL 构建了统一的数据访问层，直接提供查询能力。 &lt;br /&gt;
 &lt;br /&gt; &lt;div&gt;
  &lt;a href="http://dockone.io/uploads/article/20220619/07322f08541b049383b4f1ba3675c04b.png" rel="lightbox" target="_blank"&gt;   &lt;img alt="png9.png" src="http://dockone.io/uploads/article/20220619/07322f08541b049383b4f1ba3675c04b.png" title="png9.png"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;
 &lt;br /&gt;
 &lt;br /&gt;代码生成是提高开发人员生产力的最后一步。 随着技术的增加，Airbnb 为每一层的标准组件提供了模板。 &lt;br /&gt;
 &lt;br /&gt;通过设计指明数据类型和访问类型的代码注释，数据的访问甚至直接就被嵌入在了代码中。 &lt;br /&gt;
 &lt;br /&gt; &lt;div&gt;
  &lt;a href="http://dockone.io/uploads/article/20220619/ba23b8e467bbc31ea31018e8d4f446ae.png" rel="lightbox" target="_blank"&gt;   &lt;img alt="png10.png" src="http://dockone.io/uploads/article/20220619/ba23b8e467bbc31ea31018e8d4f446ae.png" title="png10.png"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;
 &lt;br /&gt;
 &lt;br /&gt;当星号看起来对齐时，就会出现另一个速度问题。 这一次，中央数据聚合器组件成为了限制因素。 &lt;br /&gt;
 &lt;br /&gt; &lt;h2&gt;3. 定义由组织和方法支持的新架构&lt;/h2&gt;中央数据聚合器的构建和部署时间太慢，团队无法按时迭代。 &lt;br /&gt;
 &lt;br /&gt;即使提高了生产力，累积的结构复杂性仍然太高而无法在中央组件中处理。 需要一个新的组织。 &lt;br /&gt;
 &lt;br /&gt;熵是系统随着时间的推移变得更加复杂的自然趋势，需要支持和反作用力来平衡生态系统。 &lt;br /&gt;
 &lt;br /&gt;质量工程力量在架构、组织和 &lt;a href="https://qeunit.com/blog/this-is-why-you-need-methods-for-quality-at-speed/"&gt;方法&lt;/a&gt;领域发挥作用。 &lt;br /&gt;
 &lt;br /&gt; &lt;h3&gt;支持增长的架构&lt;/h3&gt;太复杂了；团队必须对不同领域执行缓慢且成本高昂的影响分析，协调多个团队并纠正副作用错误。 &lt;br /&gt;
 &lt;br /&gt;让我们分析一下“ &lt;em&gt;纯微服务架构&lt;/em&gt;”级联问题树： &lt;br /&gt;
 &lt;ul&gt;  &lt;li&gt;复杂性过度分布在细粒度服务中&lt;/li&gt;  &lt;li&gt;这些细粒度的微服务缺乏稳定的协作点&lt;/li&gt;  &lt;li&gt;缺失的粘合剂最终分布在组件和团队之间&lt;/li&gt;  &lt;li&gt;即使是很小的变化也往往会导致混合影响。&lt;/li&gt;&lt;/ul&gt; &lt;br /&gt;
 &lt;br /&gt;核心问题是缺乏城市化，导致单体或微服务架构缺乏模块化和关注点分离。 &lt;br /&gt;
 &lt;br /&gt;Airbnb 采用了一种新的架构风格  &lt;em&gt;Micro   macroservices&lt;/em&gt;： &lt;br /&gt;
 &lt;br /&gt; &lt;div&gt;
  &lt;a href="http://dockone.io/uploads/article/20220619/6f0f8d0f27d2fe4e4a9917b9938681b0.png" rel="lightbox" target="_blank"&gt;   &lt;img alt="png11.png" src="http://dockone.io/uploads/article/20220619/6f0f8d0f27d2fe4e4a9917b9938681b0.png" title="png11.png"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;
 &lt;br /&gt;
 &lt;br /&gt;在这样的架构中，每种类型的复杂性分布是一个清晰的层： &lt;br /&gt;
 &lt;ul&gt;  &lt;li&gt;   &lt;strong&gt;统一 API&lt;/strong&gt;是支持产品快速迭代的微服务&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;中央数据聚合器&lt;/strong&gt;是具有紧密耦合的稳定单体&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;服务块外观 API&lt;/strong&gt;抽象了提供实体块的微服务。&lt;/li&gt;&lt;/ul&gt; &lt;br /&gt;
 &lt;br /&gt;阅读本文，了解有关 &lt;a href="https://qeunit.com/blog/how-architecture-contributes-to-quality-at-speed/"&gt;速度和质量架构的价值&lt;/a&gt;的更多信息。 &lt;br /&gt;
 &lt;br /&gt; &lt;h3&gt;组织一致性支持新架构&lt;/h3&gt;旨在支持业务目标的组织可以改变游戏规则。 这种调整对于 Airbnb 的加速发展至关重要。 &lt;br /&gt;
 &lt;br /&gt;该组织与不断发展的架构保持一致，产品团队在统一 API 之上工作，数据聚合团队在中央数据层（即“胶水”），以及每个数据方面的域平台团队（预留、用户）。 &lt;br /&gt;
 &lt;br /&gt; &lt;div&gt;
  &lt;a href="http://dockone.io/uploads/article/20220619/d764f3a2a589437203724636cf5cfb0f.png" rel="lightbox" target="_blank"&gt;   &lt;img alt="png12.png" src="http://dockone.io/uploads/article/20220619/d764f3a2a589437203724636cf5cfb0f.png" title="png12.png"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;
 &lt;br /&gt;
 &lt;br /&gt; &lt;h3&gt;方法强制通往新架构的铺平道路&lt;/h3&gt; &lt;em&gt;架构审查、IT 委员会、工艺治理&lt;/em&gt;——这些都是用于根据当前环境和未来架构审查提出的解决方案的方法。 &lt;br /&gt;
 &lt;br /&gt;这种方法的价值在于作为一种反力量，在由其他目标驱动的项目环境之外，以平衡选择。 &lt;br /&gt;
 &lt;br /&gt;Airbnb 使用这些方法为  &lt;em&gt;Micro   macroservices&lt;/em&gt; 架构铺平了道路。 &lt;br /&gt;
 &lt;br /&gt; &lt;div&gt;
  &lt;a href="http://dockone.io/uploads/article/20220619/7d8078865523055b97da94918cb68baf.png" rel="lightbox" target="_blank"&gt;   &lt;img alt="png13.png" src="http://dockone.io/uploads/article/20220619/7d8078865523055b97da94918cb68baf.png" title="png13.png"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;
 &lt;br /&gt;
 &lt;br /&gt;即使不包括在内，Airbnb 的*管理层**肯定对领导转型和发展新模式的文化以及发展*技能*产生了巨大影响，从而完成了  &lt;a href="http://qeunit.com/mamos/"&gt;MAMOS&lt;/a&gt; 的范围。 &lt;br /&gt;
 &lt;br /&gt;有了这些变化，Airbnb 能够引领平行的迁移轨道。但是弃用 Monolith 仍然需要太多时间。 &lt;br /&gt;
 &lt;br /&gt; &lt;h2&gt;4. 引入弃用工作组加速迁移&lt;/h2&gt;单独更改我们的银行账户已经是一场噩梦。当需要数年时间与多个团队协调才能完成时，这项任务就更加复杂了。 &lt;br /&gt;
 &lt;br /&gt;Airbnb 就像许多其他拥有遗留系统和持续业务的组织一样：他们无法在建造新房子的同时炸毁他们居住的房子。 &lt;br /&gt;
 &lt;br /&gt;一个特定的组织单位致力于加速从单体架构中迁移出来，领导以下工作： &lt;br /&gt;
 &lt;ul&gt;  &lt;li&gt;   &lt;strong&gt;移动应用程序弃用达12个月以上&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;提升和跟踪整体债务以获得可见性&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;日落低使用率的终端以加速删除&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;迁移的长期所有权&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;认识到折旧对估值有影响&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt; &lt;br /&gt;
 &lt;br /&gt;这个团体有游说的反击力量，但对于实现迁移目标至关重要。反对单体应用不能是“每个人的责任”。 &lt;br /&gt;
 &lt;br /&gt;当这项任务完成时，将面临其他挑战。 &lt;br /&gt;
 &lt;br /&gt; &lt;div&gt;
  &lt;a href="http://dockone.io/uploads/article/20220619/215a494eec5a1fe7bc871724ebd1c4a8.png" rel="lightbox" target="_blank"&gt;   &lt;img alt="png14.png" src="http://dockone.io/uploads/article/20220619/215a494eec5a1fe7bc871724ebd1c4a8.png" title="png14.png"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;
 &lt;br /&gt;
 &lt;br /&gt; &lt;h2&gt;Airbnb 的架构质量工程之旅&lt;/h2&gt;Airbnb 架构演变的这一观点为 Quality at Speed 软件提供了具体的质量工程要点。 &lt;br /&gt;
 &lt;br /&gt;首先，区分局部问题或扩展问题可以更好地识别潜在原因，从而更好地采用增量解决方案。 &lt;br /&gt;
 &lt;br /&gt;在精益方法中，Airbnb 的团队在遇到问题时解决问题，避免不必要的预优化等浪费。 &lt;br /&gt;
 &lt;br /&gt;从我的角度来看，一个可行的要点在于架构，MAMOS 的其他元素是这个结构块的对齐。 &lt;br /&gt;
 &lt;br /&gt;从单仓内的单体应用出发，然后根据业务发展对其进行划分，就像他们对两个存储库所做的那样。 &lt;br /&gt;
 &lt;br /&gt;然后，他们的故事举例说明了“ &lt;em&gt;纯微服务架构&lt;/em&gt;”的问题，并被具有延迟的反馈循环的  &lt;em&gt;Micro macroservices&lt;/em&gt; 架构所取代。 &lt;br /&gt;
 &lt;br /&gt;您将使用哪种架构风格？ &lt;br /&gt;
 &lt;br /&gt; &lt;strong&gt;原文链接：  &lt;a href="https://medium.com/qe-unit/airbnbs-microservices-architecture-journey-to-quality-engineering-d5a490e6ba4f"&gt;Airbnb’s Microservices Architecture Journey To Quality Engineering&lt;/a&gt;（翻译：池剑锋）&lt;/strong&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/62308-airbnb-%E5%BE%AE%E6%9C%8D%E5%8A%A1-%E6%9E%B6%E6%9E%84</guid>
      <pubDate>Sun, 19 Jun 2022 22:54:24 CST</pubDate>
    </item>
    <item>
      <title>请暂时抛弃使用 eBPF 取代服务网格和 sidecar 模式的幻想</title>
      <link>https://itindex.net/detail/62299-ebpf-%E5%8F%96%E4%BB%A3-%E6%9C%8D%E5%8A%A1</link>
      <description>&lt;p&gt;最近 eBPF 技术在云原生社区中持续火热，在我翻译了《  &lt;a href="https://lib.jimmysong.io/what-is-ebpf/" rel="noopener" target="_blank" title="&amp;#20160;&amp;#20040;&amp;#26159; eBPF"&gt;什么是 eBPF&lt;/a&gt;
》之后，当阅读“云原生环境中的 eBPF”之后就一直在思考 eBPF 在云原生环境中究竟处于什么地位，发挥什么样的作用。当时我评论说“eBPF 开启了上帝视角，可以看到主机上所有的活动，而 sidecar 只能观测到 pod 内的活动，只要搞好进程隔离，基于 eBPF 的 proxy per-node 才是最佳选择”，再看到 William Morgan 的  &lt;a href="https://buoyant.io/2022/06/07/ebpf-sidecars-and-the-future-of-the-service-mesh/" rel="noopener" target="_blank" title="&amp;#36825;&amp;#31687;&amp;#25991;&amp;#31456;"&gt;这篇文章&lt;/a&gt;
   &lt;sup&gt;   &lt;a href="https://jimmysong.io/blog/#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;之后，让我恍然大悟。下面节选翻译了文章我比统同意的观点，即 eBPF 无法替代服务网格和 sidecar，感兴趣的读者可以阅读 William 的原文。&lt;/p&gt;
 &lt;h2&gt;什么是 eBPF&lt;/h2&gt;
 &lt;p&gt;在过去，如果你想让应用程序处理网络数据包，那是不可能的。因为应用程序运行在 Linux 用户空间，它是不能直接访问主机的网络缓冲区。缓冲区是由内核管理的，受到内核保护，内核需要确保进程隔离，进程之间不能直接读取对方的网络数据包。正确的做法是，应用程序通过系统调用（syscall）来请求网络数据包信息，这本质上是内核 API 调用——应用程序调用 syscall，内核检查应用程序是否有权限获得其请求的数据包；如果有，就把返回数据包。&lt;/p&gt;
 &lt;p&gt;有了 eBPF 之后，应用程序不再需要 syscall，数据包不需要在内核空间和用户空间之间来回交互传递。而是我们将代码直接交给内核，让内核自己执行，这样就可以让代码全速运行，效率更高。eBPF 允许应用程序和内核以安全的方式共享内存，eBPF 允许应用程序直接向内核提交代码，目标都是通过超越系统调用的方式来实现性能提升。&lt;/p&gt;
 &lt;p&gt;eBPF 不是银弹，你不能用 eBPF 运行任意程序，实际上 eBPF 可以做的事情是非常有限的。&lt;/p&gt;
 &lt;h2&gt;eBPF 的局限性&lt;/h2&gt;
 &lt;p&gt;eBPF 的局限性也是因为内核造成的。内核中运行的应用程序应当有自己的租户，这些租户之间会争抢系统的内存、磁盘和网络，内核的职责就是隔离和调度这些应用程序的资源，同时内核还要保护确认应用程序的权限，保护其不被其他程序破坏。&lt;/p&gt;
 &lt;p&gt;因为我们直接将 eBPF 代码交给内核执行，这绕过了内核安全保护（如 syscall），内核将面临直接的安全风险。为了保护内核，所有 eBPF 程序要想运行都必须先通过一个  &lt;strong&gt;验证器&lt;/strong&gt;。但是要想自动验证程序是很困难的，验证器可能会过度限制程序的功能。比如 eBPF 程序不能是阻塞的，不能有无限循环，不能超过预定的大小；其复杂性也受到限制，验证器会评估所有可能的执行路径，如果 eBPF 程序不能在某些范围内完成，或者不能证明每个循环都有一个退出条件，那么验证器就不会允许该程序运行。有很多应用程序都违反了这些限制，要想将它们作为 eBPF 程序来运行的话，要么重写以满足验证器的需求，要么给内核打补丁，来绕过一些验证（这可能比较困难）。不过随着内核版本的升级，这些验证器也变得更加智能，限制也逐渐变得宽松，也有一些创造性的方法来绕过这些限制。&lt;/p&gt;
 &lt;p&gt;但总的来说，eBPF 程序能做的事情非常有限。对于一些重量级事件的处理，例如处理全局范围内的 HTTP/2 流量，或者 TLS 握手协商不能在纯 eBPF 环境中完成。充其量，eBPF 可以做其中的一小部分工作，然后调用用户空间应用程序来处理对于 eBPF 来说过于复杂而无法处理的部分。&lt;/p&gt;
 &lt;h2&gt;eBPF 与服务网格的关系&lt;/h2&gt;
 &lt;p&gt;因为上文所述的 eBPF 的各项限制，七层流量仍然需要用户空间的网络代理来完成，eBPF 并不能替代服务网格。eBPF 可以与 CNI（容器网络接口）一起运行，处理三层/四层流量，而服务网格处理七层流量。&lt;/p&gt;
 &lt;h3&gt;每个主机一个代理的模式比 sidecar 更糟&lt;/h3&gt;
 &lt;p&gt;对于每个主机一个代理（per-host）的模式，服务网格的早期实践者 Linkerd 1.x 就是这么用的，笔者也是从那个时候开始关注服务网格，Linkerd 1.x 还使用了 JVM 虚拟机！但是经过 Linkerd 1.x 的用户实践证明，这种模式相对于 sidecar 模式，对于运维和安全来说会更糟糕。&lt;/p&gt;
 &lt;p&gt;为什么说 sidecar 模式比 per-host 模式更好呢？因为 sidecar 模式有以下几个优势，这是 per-host 模式所不具备的：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;代理的资源消耗随着应用程序的负载而变化。随着实例流量的增加，sidecar 会消耗更多的资源，就像应用程序一样。如果应用程序的流量非常小，那么 sidecar 就不需要消耗很多资源。Kubernetes 现有的管理资源消耗的机制，如资源请求和限制以及 OOM kill，都会继续工作。&lt;/li&gt;
  &lt;li&gt;代理失败的爆炸半径只限于一个 pod。代理失败与应用失败相同，由 Kubernetes 负责处理失败的 pod。&lt;/li&gt;
  &lt;li&gt;代理维护。例如代理版本的升级，是通过如滚动更新，灰度发布等应用程序本身相同的机制完成的。&lt;/li&gt;
  &lt;li&gt;安全边界很清楚（而且很小）：在 pod 级别。Sidecar 在应用程序实例的同一安全上下文中运行。它是 pod 的一部分，与应用程序具有一样的 IP 地址。Sidecar 执行策略，并将 mTLS 应用于进出该 pod 的流量，而且它只需要该 pod 的密钥。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;而对于 per-host 模式，就没有上述好处了。代理与应用程序 pod 完全解耦，处理主机上所有 pod 的流量，这样会代理各种问题：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;代理消耗的资源是高度可变的，这取决于在某个时间点 Kubernetes 调度了多少个 pod 在该主机上。你无法有效的预测特定代理的资源消耗情况，这样代理就有崩溃的风险（原文是这么说的，这点笔者还是存疑的，希望有点读者能解帮忙解释下）。&lt;/li&gt;
  &lt;li&gt;主机上 pod 之间的流量争抢问题。因为主机上的所有流量都经过同一个代理，如果有一个应用程序 pod 的流量极高，消耗了代理的所有资源，主机上的其他应用程序就有被饿死的危险。&lt;/li&gt;
  &lt;li&gt;代理的爆炸半径很大，而且是不断变化的。代理的故障和升级现在影响到随机的应用程序集合中的一个随机的 pod 子集，意味着任何故障或维护任务都有难以预测的风险。&lt;/li&gt;
  &lt;li&gt;使得安全问题更加复杂。以 TLS 为例，主机上的代理必须包含该主机上所有应用程序的密钥，这使得它成为一个新的攻击媒介，容易受到   &lt;a href="https://en.wikipedia.org/wiki/Confused_deputy_problem" rel="noopener" target="_blank" title="&amp;#28151;&amp;#28102;&amp;#20195;&amp;#29702;"&gt;混淆代理&lt;/a&gt;
问题的影响——代理中的任何 CVE 或漏洞都是潜在的密钥泄露风险。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;简而言之，sidecar 模式继续贯彻了容器级别的隔离保护——内核可以在容器级别执行所有安全保护和公平的多租户调度。容器的隔离仍然可以完美的运行，而 per-host 模式却破坏了这一切，重新引入了争抢式的多租户隔离问题。&lt;/p&gt;
 &lt;p&gt;当然 per-host 也不是一无是处，该模式最大的好处是可以成数量级的减少代理的数量，减少网络跳数，这也就减少了资源消耗和网络延迟。但是与该模式带来的运维和安全性问题相比，这些优势都是次要的。我们也可以通过持续优化 sidecar 来弥补 sidecar 模式在这方面的不足，而 per-host 模式的缺陷确是致命性的。&lt;/p&gt;
 &lt;p&gt;其实归根结底还是回到了争抢式多租户问题上，那么能否利用现有的内核解决方案，改进一下 per-host 模式中的代理，让其支持多租户呢？比如改造 Envoy 代理，使其支持多租户模式。虽然从理论来说这是可行的，但是工作量巨大，Matt Klein 也觉得不值得这样做   &lt;sup&gt;   &lt;a href="https://jimmysong.io/blog/#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;，还不如使用容器来实现租户隔离。而且即使让 per-host 模式中的代理支持了多租户，仍然还有爆炸半径和安全问题需要解决。&lt;/p&gt;
 &lt;h2&gt;总结&lt;/h2&gt;
 &lt;p&gt;不管有没有 eBPF，在可预见的未来，服务网格都会基于运行在用户空间的 sidecar 代理（proxyless 模式除外）。Sidecar 模式虽然也有弊端，但它依然是既能保持容器隔离和操作的优势，又能处理云原生网络复杂性的最优方案。eBPF 的能力将来是否会发展到可以处理七层网络流量，从而替代服务网格和 sidecar，也许吧，但那一天可能很遥远。&lt;/p&gt;
 &lt;h2&gt;参考&lt;/h2&gt;
 &lt;div&gt;
  &lt;hr&gt;&lt;/hr&gt;
  &lt;ol&gt;
   &lt;li&gt;
    &lt;p&gt;William Morgan 的      &lt;a href="https://buoyant.io/2022/06/07/ebpf-sidecars-and-the-future-of-the-service-mesh/" rel="noopener" target="_blank" title="eBPF, sidecars, and the future of the service mesh"&gt;eBPF, sidecars, and the future of the service mesh&lt;/a&gt;
 这篇文章正好回答了我的关于 eBPF、sidecar 的疑问。      &lt;a href="https://jimmysong.io/blog/#fnref:1"&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
   &lt;li&gt;
    &lt;p&gt;关于 per-host 模式中的代理改造问题，Twitter 上有一个精彩的     &lt;a href="https://twitter.com/mattklein123/status/1522925333053272065" rel="noopener" target="_blank" title="&amp;#35752;&amp;#35770;"&gt;讨论&lt;/a&gt;
。      &lt;a href="https://jimmysong.io/blog/#fnref:2"&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&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/62299-ebpf-%E5%8F%96%E4%BB%A3-%E6%9C%8D%E5%8A%A1</guid>
      <pubDate>Sat, 11 Jun 2022 11:08:49 CST</pubDate>
    </item>
    <item>
      <title>使用ebpf跟踪rpcx微服务</title>
      <link>https://itindex.net/detail/62268-ebpf-%E8%B7%9F%E8%B8%AA-rpcx</link>
      <description>&lt;div&gt;  &lt;p&gt;   &lt;a href="https://ebpf.io/zh-cn/" rel="external" target="_blank"&gt;ebpf&lt;/a&gt;是一种创新的革命性技术，它能在内核中运行沙箱程序， 而无需修改内核源码或者加载内核模块。将 Linux 内核变成可编程之后，就能基于现有的（而非增加新的）抽象层来打造更加智能、 功能更加丰富的基础设施软件，而不会增加系统的复杂度，也不会牺牲执行效率和安全性。&lt;/p&gt;  &lt;p&gt;BPF的第一个版本在1994年问世。我们在使用tcpdump工具编写规则的时候其实就使用到它了，该工具用于查看或”嗅探”网络数据包。&lt;/p&gt;  &lt;a&gt;&lt;/a&gt;  &lt;p&gt;   &lt;img alt="" src="http://itindex.net/ebpf.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;使用ebpf技术，你可以从安全、跟踪&amp;amp;性能分析、网络、观测&amp;amp;监控等方向提供新的思路和技术：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;安全：可以从系统调用级、packet层、socket层进行安全检查，比如开发DDOS防护系统，编写防火墙程序。&lt;/li&gt;   &lt;li&gt;网络：可以开发内核层高性能包处理程序，比如Cilium提供内核层的负载均衡，把service mesh往更深层推进，解决sidecar的性能问题。&lt;/li&gt;   &lt;li&gt;跟踪&amp;amp;性能分析: Linux提供多种类型的探针点(probe point),比如Kernel probes、perf events、Tracepoints、User-space probes、User statically defined tracepoints、XDP等等，我们可以编写probe程序收集这些探针点的信息，所以我们可以通过这种方式跟踪程序，分析性能。&lt;/li&gt;   &lt;li&gt;观测&amp;amp;监控: 对这些探针点的持续观测和监控，我们可以丰富我们的trace程序。关键是，我们不需要更改既有的程序，而是通过ebpf方法从其它程序进行观测。2014年，著名的内核黑客Alexei Starovoitov对BPF的功能进行了扩展。他增加了寄存器的数量和程序允许的大小，增加了JIT编译，并创建了一个用于检查程序是否安全的程序。然而，最令人印象深刻的是，新的BPF程序不仅能够在处理数据包时运行，而且能够响应其他内核事件，并在内核和用户空间之间来回传递信息。Alexei Starovoitov的新版本的BPF被称为eBPF（e代表扩展：extended）。但现在，它已经取代了所有旧版的BPF用法，并且已经变得非常流行，为了简单起见，它仍然被称为BPF。&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;   &lt;img alt="" src="http://itindex.net/functions.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;你可以自己编写bpf程序，进行定制化的逻辑处理和分析，也可以使用大神们写好的工具，利用这些工具对程序进行通用的性能分析和跟踪。本文主要介绍使用一些工具对rpcx微服务程序进行通用的分析，既然是通用的，你可以可以对其它的Go程序进行分析，而且不仅限于Go程序，其它应用程序甚至内核你可以进行分析和跟踪。&lt;/p&gt;  &lt;p&gt;自己编写bpf程序我准备再新开一篇文章介绍。&lt;/p&gt;  &lt;p&gt;这一次主要介绍   &lt;a href="https://github.com/iovisor/bcc" rel="external" target="_blank"&gt;bcc提供的相关工具&lt;/a&gt;和   &lt;a href="https://github.com/iovisor/bpftrace" rel="external" target="_blank"&gt;bpftrace&lt;/a&gt;。&lt;/p&gt;  &lt;p&gt;bcc是用于创建基于eBPF的高效内核跟踪和操作程序的工具包，其中包括一些有用的命令行工具和示例。 BCC简化了用C进行内核检测的eBPF程序的编写，包括LLVM的包装器以及Python和Lua的前端。它还提供了用于直接集成到应用程序中的高级库。&lt;/p&gt;  &lt;p&gt;bpftrace是Linux eBPF的高级跟踪语言。它的语言受awk和C以及DTrace和SystemTap等以前的跟踪程序的启发。 bpftrace使用LLVM作为后端将脚本编译为eBPF字节码，并利用BCC作为与Linux eBPF子系统以及现有Linux跟踪功能和连接点进行交互的库。&lt;/p&gt;  &lt;h2&gt;简单的 rpcx 微服务程序&lt;/h2&gt;  &lt;p&gt;既然要使用ebpf分析程序，首先我们要有一个程序。这里我选取了   &lt;a href="https://github.com/smallnest/rpcx" rel="external" target="_blank"&gt;rpcx&lt;/a&gt;一个最简单的例子，实现一个乘法的最小的微服务。&lt;/p&gt;  &lt;p&gt;这个程序的代码可以在   &lt;a href="https://github.com/rpcxio/rpcx-examples/tree/master/102basic" rel="external" target="_blank"&gt;rpcx-examples-102basic&lt;/a&gt;下载到。&lt;/p&gt;  &lt;p&gt;服务端的程序如下:&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;1&lt;/div&gt;      &lt;div&gt;2&lt;/div&gt;      &lt;div&gt;3&lt;/div&gt;      &lt;div&gt;4&lt;/div&gt;      &lt;div&gt;5&lt;/div&gt;      &lt;div&gt;6&lt;/div&gt;      &lt;div&gt;7&lt;/div&gt;      &lt;div&gt;8&lt;/div&gt;      &lt;div&gt;9&lt;/div&gt;      &lt;div&gt;10&lt;/div&gt;      &lt;div&gt;11&lt;/div&gt;      &lt;div&gt;12&lt;/div&gt;      &lt;div&gt;13&lt;/div&gt;      &lt;div&gt;14&lt;/div&gt;      &lt;div&gt;15&lt;/div&gt;      &lt;div&gt;16&lt;/div&gt;      &lt;div&gt;17&lt;/div&gt;      &lt;div&gt;18&lt;/div&gt;      &lt;div&gt;19&lt;/div&gt;      &lt;div&gt;20&lt;/div&gt;      &lt;div&gt;21&lt;/div&gt;      &lt;div&gt;22&lt;/div&gt;      &lt;div&gt;23&lt;/div&gt;      &lt;div&gt;24&lt;/div&gt;      &lt;div&gt;25&lt;/div&gt;      &lt;div&gt;26&lt;/div&gt;      &lt;div&gt;27&lt;/div&gt;      &lt;div&gt;28&lt;/div&gt;      &lt;div&gt;29&lt;/div&gt;      &lt;div&gt;30&lt;/div&gt;      &lt;div&gt;31&lt;/div&gt;      &lt;div&gt;32&lt;/div&gt;      &lt;div&gt;33&lt;/div&gt;      &lt;div&gt;34&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;packagemain&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;import(&lt;/div&gt;      &lt;div&gt;&amp;quot;context&amp;quot;&lt;/div&gt;      &lt;div&gt;&amp;quot;flag&amp;quot;&lt;/div&gt;      &lt;div&gt;&amp;quot;fmt&amp;quot;&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;example&amp;quot;github.com/rpcxio/rpcx-examples&amp;quot;&lt;/div&gt;      &lt;div&gt;&amp;quot;github.com/smallnest/rpcx/server&amp;quot;&lt;/div&gt;      &lt;div&gt;)&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;var(&lt;/div&gt;      &lt;div&gt;addr = flag.String(&amp;quot;addr&amp;quot;,&amp;quot;localhost:8972&amp;quot;,&amp;quot;server address&amp;quot;)&lt;/div&gt;      &lt;div&gt;)&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;typeArithstruct{}&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;// 使用ebpf跟踪这个服务调用&lt;/div&gt;      &lt;div&gt;func(t *Arith) Mul(ctx context.Context, args example.Args, reply *example.Reply) error {&lt;/div&gt;      &lt;div&gt;reply.C = args.A * args.B&lt;/div&gt;      &lt;div&gt;fmt.Println(&amp;quot;C=&amp;quot;, reply.C)&lt;/div&gt;      &lt;div&gt;returnnil&lt;/div&gt;      &lt;div&gt;}&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;funcmain() {&lt;/div&gt;      &lt;div&gt;flag.Parse()&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;s := server.NewServer()&lt;/div&gt;      &lt;div&gt;s.RegisterName(&amp;quot;Arith&amp;quot;,new(Arith),&amp;quot;&amp;quot;)&lt;/div&gt;      &lt;div&gt;err := s.Serve(&amp;quot;tcp&amp;quot;, *addr)&lt;/div&gt;      &lt;div&gt;iferr !=nil{&lt;/div&gt;      &lt;div&gt;panic(err)&lt;/div&gt;      &lt;div&gt;}&lt;/div&gt;      &lt;div&gt;}&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;使用   &lt;code&gt;go build server.go&lt;/code&gt;编译出   &lt;code&gt;server&lt;/code&gt;程序并运行(   &lt;code&gt;./server&lt;/code&gt;)。&lt;/p&gt;  &lt;p&gt;客户端程序如下:&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;1&lt;/div&gt;      &lt;div&gt;2&lt;/div&gt;      &lt;div&gt;3&lt;/div&gt;      &lt;div&gt;4&lt;/div&gt;      &lt;div&gt;5&lt;/div&gt;      &lt;div&gt;6&lt;/div&gt;      &lt;div&gt;7&lt;/div&gt;      &lt;div&gt;8&lt;/div&gt;      &lt;div&gt;9&lt;/div&gt;      &lt;div&gt;10&lt;/div&gt;      &lt;div&gt;11&lt;/div&gt;      &lt;div&gt;12&lt;/div&gt;      &lt;div&gt;13&lt;/div&gt;      &lt;div&gt;14&lt;/div&gt;      &lt;div&gt;15&lt;/div&gt;      &lt;div&gt;16&lt;/div&gt;      &lt;div&gt;17&lt;/div&gt;      &lt;div&gt;18&lt;/div&gt;      &lt;div&gt;19&lt;/div&gt;      &lt;div&gt;20&lt;/div&gt;      &lt;div&gt;21&lt;/div&gt;      &lt;div&gt;22&lt;/div&gt;      &lt;div&gt;23&lt;/div&gt;      &lt;div&gt;24&lt;/div&gt;      &lt;div&gt;25&lt;/div&gt;      &lt;div&gt;26&lt;/div&gt;      &lt;div&gt;27&lt;/div&gt;      &lt;div&gt;28&lt;/div&gt;      &lt;div&gt;29&lt;/div&gt;      &lt;div&gt;30&lt;/div&gt;      &lt;div&gt;31&lt;/div&gt;      &lt;div&gt;32&lt;/div&gt;      &lt;div&gt;33&lt;/div&gt;      &lt;div&gt;34&lt;/div&gt;      &lt;div&gt;35&lt;/div&gt;      &lt;div&gt;36&lt;/div&gt;      &lt;div&gt;37&lt;/div&gt;      &lt;div&gt;38&lt;/div&gt;      &lt;div&gt;39&lt;/div&gt;      &lt;div&gt;40&lt;/div&gt;      &lt;div&gt;41&lt;/div&gt;      &lt;div&gt;42&lt;/div&gt;      &lt;div&gt;43&lt;/div&gt;      &lt;div&gt;44&lt;/div&gt;      &lt;div&gt;45&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;packagemain&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;import(&lt;/div&gt;      &lt;div&gt;&amp;quot;context&amp;quot;&lt;/div&gt;      &lt;div&gt;&amp;quot;flag&amp;quot;&lt;/div&gt;      &lt;div&gt;&amp;quot;log&amp;quot;&lt;/div&gt;      &lt;div&gt;&amp;quot;time&amp;quot;&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;&amp;quot;github.com/smallnest/rpcx/protocol&amp;quot;&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;example&amp;quot;github.com/rpcxio/rpcx-examples&amp;quot;&lt;/div&gt;      &lt;div&gt;&amp;quot;github.com/smallnest/rpcx/client&amp;quot;&lt;/div&gt;      &lt;div&gt;)&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;var(&lt;/div&gt;      &lt;div&gt;addr = flag.String(&amp;quot;addr&amp;quot;,&amp;quot;localhost:8972&amp;quot;,&amp;quot;server address&amp;quot;)&lt;/div&gt;      &lt;div&gt;)&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;funcmain() {&lt;/div&gt;      &lt;div&gt;flag.Parse()&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;d, _ := client.NewPeer2PeerDiscovery(&amp;quot;tcp@&amp;quot;+*addr,&amp;quot;&amp;quot;)&lt;/div&gt;      &lt;div&gt;opt := client.DefaultOption&lt;/div&gt;      &lt;div&gt;opt.SerializeType = protocol.JSON&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;xclient := client.NewXClient(&amp;quot;Arith&amp;quot;, client.Failtry, client.RandomSelect, d, opt)&lt;/div&gt;      &lt;div&gt;deferxclient.Close()&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;args := example.Args{&lt;/div&gt;      &lt;div&gt;A:10,&lt;/div&gt;      &lt;div&gt;B:20,&lt;/div&gt;      &lt;div&gt;}&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;for{&lt;/div&gt;      &lt;div&gt;reply := &amp;amp;example.Reply{}&lt;/div&gt;      &lt;div&gt;err := xclient.Call(context.Background(),&amp;quot;Mul&amp;quot;, args, reply)&lt;/div&gt;      &lt;div&gt;iferr !=nil{&lt;/div&gt;      &lt;div&gt;log.Fatalf(&amp;quot;failed to call: %v&amp;quot;, err)&lt;/div&gt;      &lt;div&gt;}&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;log.Printf(&amp;quot;%d * %d = %d&amp;quot;, args.A, args.B, reply.C)&lt;/div&gt;      &lt;div&gt;time.Sleep(time.Second)&lt;/div&gt;      &lt;div&gt;}&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;}&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;客户端每一秒会调用   &lt;code&gt;Arith.Mul&lt;/code&gt;微服务一次，微服务的逻辑也很简单，就是执行乘法，并把结果返回给客户端。&lt;/p&gt;  &lt;h2&gt;跟踪和分析微服务&lt;/h2&gt;  &lt;p&gt;作为演示，本文只跟踪服务端   &lt;code&gt;Arith.Mul&lt;/code&gt;调用情况。&lt;/p&gt;  &lt;p&gt;bcc提供了很多的基于bpf的分析程序，如下图(大神Brendan Gregg整理的经典图)&lt;/p&gt;  &lt;p&gt;   &lt;img alt="" src="http://itindex.net/bcc_tracing_tools_early2019.png"&gt;&lt;/img&gt;   &lt;br /&gt;   &lt;img alt="" src="http://itindex.net/bpftrace_tools_early2019.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;这里我们会选取几个相关的工具演示如何使用这些工具分析运行中的程序。 注意是运行中的程序，我们并没有给程序添加额外的一些埋点。&lt;/p&gt;  &lt;h3&gt;bcc套件&lt;/h3&gt;  &lt;p&gt;首先你得安装bcc套件，而且你的Linux内核还要足够新，在一些大厂的机房内，还有一些内核版本的2.6.x服务器，这些老的内核服务器不能支持ebpf或者ebpf的新特性。&lt;/p&gt;  &lt;p&gt;我是在我的阿里云的一台虚机上测试的，它的版本是:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Linux lab 4.18.0-348.2.1.el8_5.x86_64&lt;/li&gt;   &lt;li&gt;CentOS Stream release 8&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;直接   &lt;code&gt;yum install bcc-tools&lt;/code&gt;就可以安装这些工具。&lt;/p&gt;  &lt;p&gt;如果你是其它的版本的操作系统，你可以参考bcc的安装文档进行安装:   &lt;a href="https://github.com/iovisor/bcc/blob/master/INSTALL.md" rel="external" target="_blank"&gt;bcc/INSTALL&lt;/a&gt;。&lt;/p&gt;  &lt;p&gt;在使用工具分析之前，你首先要知道你的微服务   &lt;code&gt;Arith.Mul&lt;/code&gt;在符号表中的名称，你可以使用objdump查询到:&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;1&lt;/div&gt;      &lt;div&gt;2&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;[root@lab server]# objdump -t server|grep Mul|grep main&lt;/div&gt;      &lt;div&gt;000000000075a5e0 g     F .text00000000000000d0              main.(*Arith).Mul&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;它的名称是   &lt;code&gt;main.(*Arith).Mul&lt;/code&gt;,下面我们会使用这个名称分析这个微服务。&lt;/p&gt;  &lt;p&gt;确保刚才的服务器一直在运行中。&lt;/p&gt;  &lt;h4&gt;funccount&lt;/h4&gt;  &lt;p&gt;funccount 用来统计一段时间内某个函数的调用次数。&lt;/p&gt;  &lt;p&gt;在server所在的目录下执行下面的命令(如果在不同的路径，你需要更改命令参数中程序的路径):&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;1&lt;/div&gt;      &lt;div&gt;2&lt;/div&gt;      &lt;div&gt;3&lt;/div&gt;      &lt;div&gt;4&lt;/div&gt;      &lt;div&gt;5&lt;/div&gt;      &lt;div&gt;6&lt;/div&gt;      &lt;div&gt;7&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;[root@lab server]# funccount -d 10  &amp;apos;./server:main.*.Mul&amp;apos;&lt;/div&gt;      &lt;div&gt;Tracing1functionsfor&amp;quot;b&amp;apos;./server:main.*.Mul&amp;apos;&amp;quot;...Hit Ctrl-C to end.&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;FUNC                                    COUNT&lt;/div&gt;      &lt;div&gt;b&amp;apos;main.(*Arith).Mul&amp;apos;10&lt;/div&gt;      &lt;div&gt;Detaching...&lt;/div&gt;      &lt;div&gt;[root@lab server]#&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;这里我们设置观察时间是10秒，可以看到在这10秒内，这个函数被调用了10次。&lt;/p&gt;  &lt;p&gt;它包含几个参数，比如你可以持续观察，每5秒输出一次结果:&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;1&lt;/div&gt;      &lt;div&gt;2&lt;/div&gt;      &lt;div&gt;3&lt;/div&gt;      &lt;div&gt;4&lt;/div&gt;      &lt;div&gt;5&lt;/div&gt;      &lt;div&gt;6&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;[root@lab server]# funccount -Ti 5  &amp;apos;./server:main.*.Mul&amp;apos;&lt;/div&gt;      &lt;div&gt;Tracing1functionsfor&amp;quot;b&amp;apos;./server:main.*.Mul&amp;apos;&amp;quot;... Hit Ctrl-C to end.&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;18:08:29&lt;/div&gt;      &lt;div&gt;FUNC                                    COUNT&lt;/div&gt;      &lt;div&gt;b&amp;apos;main.(*Arith).Mul&amp;apos;5&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;我们甚至可以用它进行Go GC相关函数的跟踪:&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;1&lt;/div&gt;      &lt;div&gt;2&lt;/div&gt;      &lt;div&gt;3&lt;/div&gt;      &lt;div&gt;4&lt;/div&gt;      &lt;div&gt;5&lt;/div&gt;      &lt;div&gt;6&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;[root@lab server]# funccount -d 10  &amp;apos;./server:runtime.*.gc*&amp;apos;&lt;/div&gt;      &lt;div&gt;Tracing21functionsfor&amp;quot;b&amp;apos;./server:runtime.*.gc*&amp;apos;&amp;quot;... Hit Ctrl-C to end.&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;FUNC                                    COUNT&lt;/div&gt;      &lt;div&gt;b&amp;apos;runtime.(*gcControllerState).update&amp;apos;2&lt;/div&gt;      &lt;div&gt;b&amp;apos;runtime.mallocgc&amp;apos;250&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;抑或是跟踪Go运行时的调度:&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;1&lt;/div&gt;      &lt;div&gt;2&lt;/div&gt;      &lt;div&gt;3&lt;/div&gt;      &lt;div&gt;4&lt;/div&gt;      &lt;div&gt;5&lt;/div&gt;      &lt;div&gt;6&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;[root@lab server]# funccount -d 10  &amp;apos;./server:runtime.schedule&amp;apos;&lt;/div&gt;      &lt;div&gt;Tracing1functionsfor&amp;quot;b&amp;apos;./server:runtime.schedule&amp;apos;&amp;quot;... Hit Ctrl-C to end.&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;FUNC                                    COUNT&lt;/div&gt;      &lt;div&gt;b&amp;apos;runtime.schedule&amp;apos;20&lt;/div&gt;      &lt;div&gt;Detaching...&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;h4&gt;funclatency&lt;/h4&gt;  &lt;p&gt;funclatency统计函数的执行的耗时情况。   &lt;br /&gt;如果我们想分析   &lt;code&gt;Arith.Mul&lt;/code&gt;方法执行的情况，我们可以使用下面的命令，它会用直方图的形式展示这个函数调用的耗时分布:&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;1&lt;/div&gt;      &lt;div&gt;2&lt;/div&gt;      &lt;div&gt;3&lt;/div&gt;      &lt;div&gt;4&lt;/div&gt;      &lt;div&gt;5&lt;/div&gt;      &lt;div&gt;6&lt;/div&gt;      &lt;div&gt;7&lt;/div&gt;      &lt;div&gt;8&lt;/div&gt;      &lt;div&gt;9&lt;/div&gt;      &lt;div&gt;10&lt;/div&gt;      &lt;div&gt;11&lt;/div&gt;      &lt;div&gt;12&lt;/div&gt;      &lt;div&gt;13&lt;/div&gt;      &lt;div&gt;14&lt;/div&gt;      &lt;div&gt;15&lt;/div&gt;      &lt;div&gt;16&lt;/div&gt;      &lt;div&gt;17&lt;/div&gt;      &lt;div&gt;18&lt;/div&gt;      &lt;div&gt;19&lt;/div&gt;      &lt;div&gt;20&lt;/div&gt;      &lt;div&gt;21&lt;/div&gt;      &lt;div&gt;22&lt;/div&gt;      &lt;div&gt;23&lt;/div&gt;      &lt;div&gt;24&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;[root@labserver]# funclatency -d 10  &amp;apos;./server:main.*.Mul&amp;apos;&lt;/div&gt;      &lt;div&gt;Tracing1functionsfor&amp;quot;./server:main.*.Mul&amp;quot;...Hit Ctrl-Ctoend.&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;Function= b&amp;apos;main.(*Arith).Mul&amp;apos;[359284]&lt;/div&gt;      &lt;div&gt;nsecs:count     distribution&lt;/div&gt;      &lt;div&gt;0-&amp;gt;1:0|                                        |&lt;/div&gt;      &lt;div&gt;2-&amp;gt;3:0|                                        |&lt;/div&gt;      &lt;div&gt;4-&amp;gt;7:0|                                        |&lt;/div&gt;      &lt;div&gt;8-&amp;gt;15:0|                                        |&lt;/div&gt;      &lt;div&gt;16-&amp;gt;31:0|                                        |&lt;/div&gt;      &lt;div&gt;32-&amp;gt;63:0|                                        |&lt;/div&gt;      &lt;div&gt;64-&amp;gt;127:0|                                        |&lt;/div&gt;      &lt;div&gt;128-&amp;gt;255:0|                                        |&lt;/div&gt;      &lt;div&gt;256-&amp;gt;511:0|                                        |&lt;/div&gt;      &lt;div&gt;512-&amp;gt;1023:0|                                        |&lt;/div&gt;      &lt;div&gt;1024-&amp;gt;2047:0|                                        |&lt;/div&gt;      &lt;div&gt;2048-&amp;gt;4095:0|                                        |&lt;/div&gt;      &lt;div&gt;4096-&amp;gt;8191:0|                                        |&lt;/div&gt;      &lt;div&gt;8192-&amp;gt;16383:0|                                        |&lt;/div&gt;      &lt;div&gt;16384-&amp;gt;32767:7|****************************************|&lt;/div&gt;      &lt;div&gt;32768-&amp;gt;65535:3|*****************                       |&lt;/div&gt;      &lt;div&gt;&lt;/div&gt;      &lt;div&gt;avg =31978nsecs,total:319783nsecs,count:10&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;我们统计了10秒的数据。可以看到期间这个函数被调用了10次。平均耗时31微秒。&lt;/p&gt;  &lt;p&gt;如果我们想检查线上的程序有没有长尾的现象，使用这个工具很容易分析统计。&lt;/p&gt;  &lt;h4&gt;funcslower&lt;/h4&gt;  &lt;p&gt;funcslower 这个工具可以跟踪内核和程序的执行慢的函数，比如使用下面的命令:&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;1&lt;/div&gt;      &lt;div&gt;2&lt;/div&gt;      &lt;div&gt;3&lt;/div&gt;      &lt;div&gt;4&lt;/div&gt;      &lt;div&gt;5&lt;/div&gt;      &lt;div&gt;6&lt;/div&gt;      &lt;div&gt;7&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;[root@lab server]# funcslower -u 10  &amp;apos;./server:main.(*Arith).Mul&amp;apos;&lt;/div&gt;      &lt;div&gt;Tracing function calls slower than10us... Ctrl+C to quit.&lt;/div&gt;      &lt;div&gt;COMM           PID    LAT(us)             RVAL FUNC&lt;/div&gt;      &lt;div&gt;server35928444.750./server:main.(*Arith).Mul&lt;/div&gt;      &lt;div&gt;server35928430.970./server:main.(*Arith).Mul&lt;/div&gt;      &lt;div&gt;server35928433.380./server:main.(*Arith).Mul&lt;/div&gt;      &lt;div&gt;server35928431.280./server:main.(*Arith).Mul&lt;/div&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;      &lt;div&gt;1&lt;/div&gt;      &lt;div&gt;2&lt;/div&gt;      &lt;div&gt;3&lt;/div&gt;      &lt;div&gt;4&lt;/div&gt;      &lt;div&gt;5&lt;/div&gt;      &lt;div&gt;6&lt;/div&gt;      &lt;div&gt;7&lt;/div&gt;      &lt;div&gt;8&lt;/div&gt;      &lt;div&gt;9&lt;/div&gt;      &lt;div&gt;10&lt;/div&gt;      &lt;div&gt;11&lt;/div&gt;      &lt;div&gt;12&lt;/div&gt;      &lt;div&gt;13&lt;/div&gt;      &lt;div&gt;14&lt;/div&gt;      &lt;div&gt;15&lt;/div&gt;      &lt;div&gt;16&lt;/div&gt;      &lt;div&gt;17&lt;/div&gt;      &lt;div&gt;18&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;[root@lab server]funcslower -UK -u10&amp;apos;./server:main.(*Arith).Mul&amp;apos;&lt;/div&gt;      &lt;div&gt;Tracing function calls slower than10us... Ctrl+C to quit.&lt;/div&gt;      &lt;div&gt;COMM           PID    LAT(us)             RVAL FUNC&lt;/div&gt;      &lt;div&gt;server35928431.200./server:main.(*Arith).Mul&lt;/div&gt;      &lt;div&gt;b&amp;apos;runtime.call64.abi0&amp;apos;&lt;/div&gt;      &lt;div&gt;b&amp;apos;runtime.reflectcall&amp;apos;&lt;/div&gt;      &lt;div&gt;b&amp;apos;reflect.Value.call&amp;apos;&lt;/div&gt;      &lt;div&gt;b&amp;apos;reflect.Value.Call&amp;apos;&lt;/div&gt;      &lt;div&gt;b&amp;apos;github.com/smallnest/rpcx/server.(*service).call&amp;apos;&lt;/div&gt;      &lt;div&gt;b&amp;apos;github.com/smallnest/rpcx/server.(*Server).handleRequest&amp;apos;&lt;/div&gt;      &lt;div&gt;b&amp;apos;github.com/smallnest/rpcx/server.(*Server).serveConn.func2&amp;apos;&lt;/div&gt;      &lt;div&gt;b&amp;apos;runtime.goexit.abi0&amp;apos;&lt;/div&gt;      &lt;div&gt;server35928432.230./server:main.(*Arith).Mul&lt;/div&gt;      &lt;div&gt;b&amp;apos;runtime.call64.abi0&amp;apos;&lt;/div&gt;      &lt;div&gt;b&amp;apos;runtime.reflectcall&amp;apos;&lt;/div&gt;      &lt;div&gt;b&amp;apos;reflect.Value.call&amp;apos;&lt;/div&gt;      &lt;div&gt;b&amp;apos;reflect.Value.Call&amp;apos;&lt;/div&gt;      &lt;div&gt;b&amp;apos;github.com/smallnest/rpcx/server.(*service).call&amp;apos;&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;h4&gt;tcp 系列工具&lt;/h4&gt;  &lt;p&gt;bcc提供了一堆的对tcp的跟踪情况，我们可以针对不同的场景选择使用相应的工具。&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;tools/tcpaccept: 跟踪TCP被动连接 (accept()).&lt;/li&gt;   &lt;li&gt;tools/tcpconnect: 跟踪TCP主动的连接 (connect()).&lt;/li&gt;   &lt;li&gt;tools/tcpconnlat: 跟踪TCP主动连接的延迟(connect()).&lt;/li&gt;   &lt;li&gt;tools/tcpdrop: 跟踪内核的TCP包的丢包细节.&lt;/li&gt;   &lt;li&gt;tools/tcplife: 跟踪TCP session(生命周期指标汇总).&lt;/li&gt;   &lt;li&gt;tools/tcpretrans: 跟踪TCP重传.&lt;/li&gt;   &lt;li&gt;tools/tcprtt: 跟踪TCP来回的耗时.&lt;/li&gt;   &lt;li&gt;tools/tcpstates: 跟踪TCP session状态的改变.&lt;/li&gt;   &lt;li&gt;tools/tcpsubnet: 按子网汇总和聚合TCP发送情况.&lt;/li&gt;   &lt;li&gt;tools/tcpsynbl: 显示TCP SYN backlog的情况.&lt;/li&gt;   &lt;li&gt;tools/tcptop: 按主机汇总TCP send/recv吞吐情况.&lt;/li&gt;   &lt;li&gt;tools/tcptracer: 跟踪TCP 建立/关闭连接的情况 (connect(), accept(), close()).&lt;/li&gt;   &lt;li&gt;tools/tcpcong: 跟踪TCP套接字拥塞控制状态持续时间.&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;比如我们如果关注连接的建立情况，可以使用   &lt;code&gt;tcptracer&lt;/code&gt;:&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;1&lt;/div&gt;      &lt;div&gt;2&lt;/div&gt;      &lt;div&gt;3&lt;/div&gt;      &lt;div&gt;4&lt;/div&gt;      &lt;div&gt;5&lt;/div&gt;      &lt;div&gt;6&lt;/div&gt;      &lt;div&gt;7&lt;/div&gt;      &lt;div&gt;8&lt;/div&gt;      &lt;div&gt;9&lt;/div&gt;      &lt;div&gt;10&lt;/div&gt;      &lt;div&gt;11&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;[root@lab lib]# tcptracer&lt;/div&gt;      &lt;div&gt;Tracing TCP established connections. Ctrl-C to end.&lt;/div&gt;      &lt;div&gt;T  PID    COMM             IP SADDR            DADDR            SPORT  DPORT&lt;/div&gt;      &lt;div&gt;C360005client4127.0.0.1127.0.0.1431268972&lt;/div&gt;      &lt;div&gt;X360005client6[::1]            [::1]430108972&lt;/div&gt;      &lt;div&gt;A359284server4127.0.0.1127.0.0.1897243126&lt;/div&gt;      &lt;div&gt;X360005client4127.0.0.1127.0.0.1431268972&lt;/div&gt;      &lt;div&gt;X359284server4127.0.0.1127.0.0.1897243126&lt;/div&gt;      &lt;div&gt;C360009client4127.0.0.1127.0.0.1431308972&lt;/div&gt;      &lt;div&gt;X360009client6[::1]            [::1]430148972&lt;/div&gt;      &lt;div&gt;A359284server4127.0.0.1127.0.0.1897243130&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;另外还有一堆的   &lt;code&gt;xxxxsnoop&lt;/code&gt;程序，可以对特定的系统调用进行跟踪。&lt;/p&gt;  &lt;h3&gt;bpftrace&lt;/h3&gt;  &lt;p&gt;有时候，我们想使用脚本实现一些定制化的跟踪，比如类似awk这样的工具，可以提供简单的脚本编写。&lt;/p&gt;  &lt;p&gt;bpftrace就是这样的工具, 它使用LLVM作为后端将脚本编译为eBPF字节码，并利用BCC作为与Linux eBPF子系统以及现有Linux跟踪功能和连接点进行交互的库。&lt;/p&gt;  &lt;p&gt;bpftrace参考手册可以在   &lt;a href="https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md#20-override-override-return-value" rel="external" target="_blank"&gt;bpftrace reference_guide&lt;/a&gt;找到。&lt;/p&gt;  &lt;p&gt;以我们的   &lt;code&gt;Arith.Mul&lt;/code&gt;为例，我们可以使用下面的命令，在函数调用时加入探针，把输入的参数打印出来:&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;1&lt;/div&gt;      &lt;div&gt;2&lt;/div&gt;      &lt;div&gt;3&lt;/div&gt;      &lt;div&gt;4&lt;/div&gt;      &lt;div&gt;5&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;[root@lab server]# bpftrace -e&amp;apos;uprobe:./server:main.*.Mul {printf(&amp;quot;%s - %s: arg1: %d, arg2: %d\n&amp;quot;, comm, func, arg0, arg1)}&amp;apos;&lt;/div&gt;      &lt;div&gt;Attaching1probe...&lt;/div&gt;      &lt;div&gt;server - main.(*Arith).Mul: arg1:10, arg2:20&lt;/div&gt;      &lt;div&gt;server - main.(*Arith).Mul: arg1:10, arg2:20&lt;/div&gt;      &lt;div&gt;server - main.(*Arith).Mul: arg1:10, arg2:20&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;为什么arg0,arg1就能把参数打印出来呢？简单说，我们的微服务参数正好是两个int64的整数，正好对应arg0,arg1。&lt;/p&gt;  &lt;p&gt;rpcx的服务返回值也是当做参数传入的，函数调用的时候还没有设置，所以你如果打印arg3并不是reply返回值。&lt;/p&gt;  &lt;p&gt;这个时候我们需要移动探针，加一个偏移量，加多少的偏移量呢？通过反汇编我们看到加92时返回值已经赋值了，所以使用下面的命令就可以打印返回值了(这个时候第一个参数就被覆盖掉了):&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;1&lt;/div&gt;      &lt;div&gt;2&lt;/div&gt;      &lt;div&gt;3&lt;/div&gt;      &lt;div&gt;4&lt;/div&gt;      &lt;div&gt;5&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;    &lt;td&gt;     &lt;pre&gt;      &lt;div&gt;[root@labserver]# bpftrace -e &amp;apos;uprobe:./server:main.*.Mul+92 {printf(&amp;quot;%s - %s: reply: %d\n&amp;quot;, comm, func, arg0)}&amp;apos;&lt;/div&gt;      &lt;div&gt;Attaching1probe...&lt;/div&gt;      &lt;div&gt;server- main.(*Arith).Mul: reply:200&lt;/div&gt;      &lt;div&gt;server- main.(*Arith).Mul: reply:200&lt;/div&gt;      &lt;div&gt;server- main.(*Arith).Mul: reply:200&lt;/div&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;blockquote&gt;   &lt;p&gt;Go自1.17开始已经改成了基于寄存器的调用惯例，所以这里使用了内建的arg0、arg1、..., 如果你使用更早的Go版本，这里你可以换成sarg0,sarg1,...试试(stack arguments)。&lt;/p&gt;&lt;/blockquote&gt;  &lt;h2&gt;参考文档&lt;/h2&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;a href="https://www.brendangregg.com/blog/2017-01-31/golang-bcc-bpf-function-tracing.html" rel="external" target="_blank"&gt;https://www.brendangregg.com/blog/2017-01-31/golang-bcc-bpf-function-tracing.html&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://golangexample.com/library-to-work-with-ebpf-programs-from-golang/" rel="external" target="_blank"&gt;https://golangexample.com/library-to-work-with-ebpf-programs-from-golang/&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://tonybai.com/2020/12/25/bpf-and-go-modern-forms-of-introspection-in-linux/" rel="external" target="_blank"&gt;https://tonybai.com/2020/12/25/bpf-and-go-modern-forms-of-introspection-in-linux/&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://networkop.co.uk/post/2021-03-ebpf-intro/" rel="external" target="_blank"&gt;https://networkop.co.uk/post/2021-03-ebpf-intro/&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://medium.com/bumble-tech/bpf-and-go-modern-forms-of-introspection-in-linux-6b9802682223#db17" rel="external" target="_blank"&gt;https://medium.com/bumble-tech/bpf-and-go-modern-forms-of-introspection-in-linux-6b9802682223#db17&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://blog.px.dev/ebpf-http-tracing/" rel="external" target="_blank"&gt;https://blog.px.dev/ebpf-http-tracing/&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://www.ebpf.top/post/ebpf_and_go/" rel="external" target="_blank"&gt;https://www.ebpf.top/post/ebpf_and_go/&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://www.ebpf.top/" rel="external" target="_blank"&gt;https://www.ebpf.top/&lt;/a&gt;&lt;/li&gt;&lt;/ol&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>dev</category>
      <guid isPermaLink="true">https://itindex.net/detail/62268-ebpf-%E8%B7%9F%E8%B8%AA-rpcx</guid>
      <pubDate>Tue, 24 May 2022 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>Vercel 部署 Node 服务</title>
      <link>https://itindex.net/detail/62244-vercel-node-%E6%9C%8D%E5%8A%A1</link>
      <description>&lt;h2&gt;引子&lt;/h2&gt;
 &lt;p&gt;之前在写  &lt;a href="https://juejin.cn/post/7090858103612112927"&gt;面试常客：HTTP 缓存&lt;/a&gt;时，曾经就强缓存和协商缓存写过两个demo，但缓存要在服务端做，只能贴上代码，不能在网页上感受（虽然我贴了gif）&lt;/p&gt;
 &lt;p&gt;笔者的所有 demo 例子都放在 github page 上，其特点是不需要服务器即可部署静态资源，但它不具备部署服务端应用能力&lt;/p&gt;
 &lt;p&gt;最近笔者在了解 CI/CD 方面的知识点，想起了 Vercel，就想着能否将服务端应用架在 vercel 上呢？&lt;/p&gt;
 &lt;h2&gt;Vercel 是什么&lt;/h2&gt;
 &lt;p&gt;Vercel 是一个开箱即用的网站托管平台，方便开发者快速部署自己的网站。它在全球都拥有 CND 节点，因此比 Github 官方自带的 github pages 更加稳定，访问速度更快&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://space.bilibili.com/489667127"&gt;Koala聊开源&lt;/a&gt; 曾经对其有过介绍：  &lt;a href="https://www.bilibili.com/video/BV1gR4y1u76v"&gt;Vercel 与 Next.js：开源全明星团队背后的商业逻辑&lt;/a&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;文字版：   &lt;a href="https://juejin.cn/post/7057333396359348255"&gt;Vercel 与 Next.js：开源全明星团队背后的商业逻辑&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;简单的说，它能极简部署应用到服务端，且是免费不用买服务器&lt;/p&gt;
 &lt;h2&gt;官网&lt;/h2&gt;
 &lt;p&gt;  &lt;a href="https://vercel.com/"&gt;Vercel 官网&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://vercel.com/workflow"&gt;Vercel 工作流官网&lt;/a&gt;（网页效果炫酷）&lt;/p&gt;
 &lt;h2&gt;常见命令行&lt;/h2&gt;
 &lt;p&gt;将 Vercel 下载到全局（  &lt;code&gt;npm i vercel -g&lt;/code&gt;），不知道有什么命令就  &lt;code&gt;-h&lt;/code&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="vercel&amp;#24110;&amp;#21161;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a47e1d937ab74d4792e9e247c4200fa3~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;笔者对其了解有限，这里罗列下笔者知道的命令&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;code&gt;vercel login&lt;/code&gt;：登录 Vercel 账号&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;vercel dev&lt;/code&gt;：本地开启服务&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;vercel dev --bug&lt;/code&gt;：本地开启服务并打印日志&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;vercel&lt;/code&gt;：部署本地资源到 Vercel 上&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;vercel --prod&lt;/code&gt;：更新本地网页&lt;/li&gt;
&lt;/ul&gt;
 &lt;blockquote&gt;
  &lt;p&gt;vercel 可以用 vc 来代替，vc 是 Vercel 的缩写&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h2&gt;部署静态服务&lt;/h2&gt;
 &lt;p&gt;我们现在已经对 vercel 有所了解，前文中说到 Vercel 能简化开发者部署服务，那它能简化到什么程度呢？&lt;/p&gt;
 &lt;p&gt;这里我们从零部署一个简易静态服务&lt;/p&gt;
 &lt;p&gt;本地安装 Vercel&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;npm i vercel -g
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;登录 Vercel&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;vercel login
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="vercel login" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f61fe606ba75450a9d7e65b028867279~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;选择好连接的方式后，会在网站弹出&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="vercel &amp;#30331;&amp;#24405;&amp;#25104;&amp;#21151;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f4e9ec262c914fd1adf9f54591cb2363~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;哟哟，man。what&amp;apos;s your name？&lt;/p&gt;
 &lt;p&gt;创建一个HTML文件，后续我们要将其上传至 Vercel 服务器上&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;

&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
    &amp;lt;meta http-equiv=&amp;quot;X-UA-Compatible&amp;quot; content=&amp;quot;IE=edge&amp;quot;&amp;gt;
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&amp;gt;
    &amp;lt;title&amp;gt;Vercel Demo&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;

&amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Vercel Demo&amp;lt;/h1&amp;gt;
&amp;lt;/body&amp;gt;

&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;本地测试一番，输入命令行&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;vercel dev
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="vercel dev" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f45018c2525d4c30aa44b217e42fbd63~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;因为我们这是第一次执行，根目录下没有  &lt;code&gt;.vercel&lt;/code&gt;，所以要填写一些必要信息，这时你的本地和 Vercel 服务器就绑定好了&lt;/p&gt;
 &lt;p&gt;部署服务&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;vercel
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="image-20220507100558414" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a4073cf678d54aea861130cdd13a3752~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在 https://vercel-sample-ten.vercel.app/ 中能访问到我们的静态服务&lt;/p&gt;
 &lt;p&gt;在截图中我们也看到了这句话  &lt;code&gt;Deployed to production. Run vercel --prod to overwrite later&lt;/code&gt;，后续我们要更新资源，用   &lt;code&gt;vercel --prod&lt;/code&gt; 即可&lt;/p&gt;
 &lt;p&gt;好了，除去必要的登录，我们就用了三个命令就把本地服务部署到 Vercel 服务器上&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;code&gt;vercel dev&lt;/code&gt; ：开发时使用，查看应用是否跑得起来&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;vercel&lt;/code&gt;：部署服务&lt;/li&gt;
  &lt;li&gt;   &lt;code&gt;vercel --prod&lt;/code&gt;：更新应用（资源）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;可以登录   &lt;a href="https://vercel.com/dashboard"&gt;Vercel 后台&lt;/a&gt;查看部署情况&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image-20220507114502355" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fdd582c45f8e45a2a0c5d84aab8d308b~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;部署 Node 服务&lt;/h2&gt;
 &lt;p&gt;回归主题，最终我们想部署的是 Node 服务，是后端服务，而非前端静态资源服务，这是关键&lt;/p&gt;
 &lt;p&gt;同样建立新项目&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;mkdir vercel-koa2
cd vercel-koa2
npm init -y
npm i koa -S
touch index.js
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;编写   &lt;code&gt;index.js&lt;/code&gt; 中的内容&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;const Koa = require(&amp;apos;koa&amp;apos;);
const app = new Koa();

app.use(async ctx =&amp;gt; {
    ctx.body = &amp;apos;Hello Vercel&amp;apos;;
});

app.listen(3008, () =&amp;gt; {
    console.log(&amp;apos;3008项目启动&amp;apos;)
});
&lt;/code&gt;&lt;/pre&gt;
 &lt;blockquote&gt;
  &lt;p&gt;PS： 3000端口默认会被 Vercel 使用，所以 Koa 服务要换个端口&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;使用命令  &lt;code&gt;vercel dev&lt;/code&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="vercel koa dev" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a55ed3b8bd8941e9aa1cfe02b394b8b7~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;发现给我报错了，理由是   &lt;code&gt;package.json&lt;/code&gt; 的 scripts 中没有 build 快捷符，修改之&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;...
&amp;quot;scripts&amp;quot;: {
    &amp;quot;build&amp;quot;: &amp;quot;node index.js&amp;quot;,
},
...
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;再次使用   &lt;code&gt;vercel dev&lt;/code&gt;，node 服务跑起来了&lt;/p&gt;
 &lt;p&gt;于是乎我们部署它&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;vercel
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;img alt="vercel &amp;#37096;&amp;#32626;&amp;#22833;&amp;#36133;" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/563db82258204802a5bbbcda717c4a86~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;搞半天没部署上去，后台查看也是无果，呜呼悲哉&lt;/p&gt;
 &lt;p&gt;google后，发现原来还有一个   &lt;a href="https://vercel.com/docs/project-configuration"&gt;vercel.json&lt;/a&gt;，可以用 vercel.json 配置和覆盖 vercel 默认行为&lt;/p&gt;
 &lt;p&gt;下载   &lt;code&gt;@vercel/node&lt;/code&gt; 包&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;npm i @vercel/node -S
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;填写配置：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;{
  &amp;quot;version&amp;quot;: 2,
  &amp;quot;builds&amp;quot;: [
    {
      &amp;quot;src&amp;quot;: &amp;quot;index.js&amp;quot;,
      &amp;quot;use&amp;quot;: &amp;quot;@vercel/node&amp;quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;执行   &lt;code&gt;vercel&lt;/code&gt; 部署服务&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="vercel koa" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7e0d43815fea47dda728ca389c8f616b~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;访问地址：https://vercel-koa2-t511069160.vercel.app&lt;/p&gt;
 &lt;p&gt;至此，就完成了 Koa 服务的部署&lt;/p&gt;
 &lt;p&gt;与部署静态资源多了两个步骤&lt;/p&gt;
 &lt;p&gt;下载   &lt;code&gt;@vercel/node&lt;/code&gt; 和配置   &lt;code&gt;vercel.json&lt;/code&gt;&lt;/p&gt;
 &lt;h2&gt;延伸思考&lt;/h2&gt;
 &lt;p&gt;Vercel 当然不止笔者所说的这一功能，它还可以自定义域名、serverless、全球支持的 CDN等等&lt;/p&gt;
 &lt;p&gt;可以毫不夸张地说，用 Vercel 来代替繁琐的云服务器，配合 Github Action 做 CI/CD，就&lt;/p&gt;
 &lt;p&gt;个人开发者或小团队而言，这或许就是神器&lt;/p&gt;
 &lt;p&gt;后续笔者也会尝试用 Vercel 部署一些小应用，实践出真理&lt;/p&gt;
 &lt;p&gt;附上项目地址：https://github.com/johanazhu/vercel-demo&lt;/p&gt;
 &lt;h2&gt;参考资料&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://tangly1024.com/article/vercel-free-serverless-api"&gt;Vercel搭建API 服务，无需服务器&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://juejin.cn/post/7057333396359348255"&gt;Vercel 与 Next.js：开源全明星团队背后的商业逻辑：文字版&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.bilibili.com/video/BV1gR4y1u76v"&gt;Vercel 与 Next.js：开源全明星团队背后的商业逻辑：视频版&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 />
      <guid isPermaLink="true">https://itindex.net/detail/62244-vercel-node-%E6%9C%8D%E5%8A%A1</guid>
      <pubDate>Sat, 07 May 2022 08:42:50 CST</pubDate>
    </item>
  </channel>
</rss>

